cache-manager-s3
Version:
Cache manager module for S3
281 lines (221 loc) • 6.95 kB
JavaScript
class UnexpectedParameter extends Error {}
class S3 {
constructor(config) {
this.config = config
this.cache = {}
}
_log(message) {
if( this.config.debug ) {
console.log(message)
}
}
_mergeParams(params) {
return Object.assign({}, this.config.params, params)
}
_checkPath(params, bucketMustExist = false, keyMustExist = false) {
if( !('Bucket' in params) ) {
return new Error('No bucket specified!')
}
if( bucketMustExist && !(params.Bucket in this.cache) ) {
return new Error(`Requested bucket: ${params.Bucket} does not exist!`)
}
if( keyMustExist && !(params.Key in this.cache[params.Bucket]) ) {
const err = new Error('NoSuchKey: The specified key does not exist.')
err.statusCode = 404
return err
}
// Check to make sure this path is a sane one.
const path = params.Key
if( typeof path !== 'string' ) {
return new Error('Path is not a string!')
}
if( Buffer.from(path).length > 1024 ) {
return new Error('Path is more than 1024 bytes long!')
}
if( /\/\//.test(path) ) {
return new Error('Path contains adjacent slashes, behavior is unknown!')
}
// Equals sign has been tested and works in S3 API.
if( /[&$@;:+,?\s]/.test(path) ) {
return new Error('Path contains potentially special characters, needs encoding!')
}
/* eslint-disable-next-line no-control-regex */
if( /[\u0000-\u001F\u007f-\u00A0]/.test(path) ) {
return new Error('Path contains unprintable characters, needs encoding!')
}
return null
}
getObject(inputParams = {}, cb) {
const params = this._mergeParams(inputParams)
if( !cb ) {
cb = () => {}
}
if( Object.keys(params).length === 0 ) {
this._log('No params provided to getObject, returning')
cb()
return
}
// Initialize Bucket before putting key
if( !(params.Bucket in this.cache) ) {
this.cache[params.Bucket] = {}
}
this._log('getObject', params)
if( 'PleaseBreakApi' in params ) {
cb(new UnexpectedParameter("Unexpected key 'PleaseBreakApi' found in params"), null)
return
}
const err = this._checkPath(params, true, true)
if( err instanceof Error ) {
cb(err, null)
return
}
// Convert the object data into a Buffer before sending
const responseObject = Object.assign({}, this.cache[params.Bucket][params.Key])
responseObject.Body = Buffer.from(responseObject.Body)
cb(err, responseObject)
return { send: () => {} }
}
putObject(inputParams = {}, cb) {
const params = this._mergeParams(inputParams)
if( !cb ) {
cb = () => {}
}
if( Object.keys(params).length === 0 ) {
this._log('No params provided to putObject, returning')
cb()
return
}
this._log('putObject', params)
if( 'PleaseBreakApi' in params ) {
cb(new UnexpectedParameter("Unexpected key 'PleaseBreakApi' found in params"), null)
return
}
const err = this._checkPath(params)
if( err instanceof Error ) {
cb(err, null)
return
}
// Initialize Bucket before putting key
if( !(params.Bucket in this.cache) ) {
this.cache[params.Bucket] = {}
}
this.cache[params.Bucket][params.Key] = params
cb(err, null)
return { send: () => {} }
}
listObjectsV2(inputParams = {}, cb) {
const params = this._mergeParams(inputParams)
if( Object.keys(params).length === 0 ) {
this._log('No params provided to listObjects, returning')
cb()
return
}
this._log('listObjects', params)
if( !(params.Bucket in this.cache) ) {
cb(null, { Contents: [] })
return
}
if( 'PleaseBreakApi' in params ) {
cb(new UnexpectedParameter("Unexpected key 'PleaseBreakApi' found in params"), null)
return
}
let results = Object.values(this.cache[params.Bucket])
// Filter the results by key if requested.
if( 'Prefix' in params ) {
results = results.filter(x => x.Key.startsWith(params.Prefix))
}
const returnedStuff = { Contents: results }
// Slice the array if we're "continuing"
if( results.length > 1000 ) {
let sliceBegin = 0
if( params.ContinuationToken ) {
sliceBegin = params.ContinuationToken
}
const sliceEnd = Math.min(sliceBegin + 1000, results.length)
returnedStuff.Contents = results.slice(sliceBegin, sliceEnd)
returnedStuff.NextContinuationToken = sliceEnd
if( sliceEnd < results.length ) {
returnedStuff.IsTruncated = true
}
}
cb(null, returnedStuff)
return { send: () => {} }
}
deleteObject(inputParams = {}, cb) {
const params = this._mergeParams(inputParams)
if( !cb ) {
cb = () => {}
}
if( Object.keys(params).length === 0 ) {
this._log('No params provided to deleteObject, returning')
cb()
return
}
this._log('deleteObject', params)
if( 'PleaseBreakApi' in params ) {
cb(new UnexpectedParameter("Unexpected key 'PleaseBreakApi' found in params"), null)
return
}
const err = this._checkPath(params)
if( err instanceof Error ) {
cb(err, null)
return
}
if( params.Key in this.cache[params.Bucket] ) {
delete this.cache[params.Bucket][params.Key]
}
cb(err, {})
return { send: () => {} }
}
deleteObjects(inputParams = {}, cb) {
const params = this._mergeParams(inputParams)
if( !cb ) {
cb = () => {}
}
if( Object.keys(params).length === 0 ) {
this._log('No params provided to deleteObjects, returning')
cb()
return
}
this._log('deleteObjects', params)
if( 'PleaseBreakApi' in params ) {
cb(new UnexpectedParameter("Unexpected key 'PleaseBreakApi' found in params"), null)
return
}
let err = null
for( let i = 0; i < params.Delete.Objects.length; i++ ) {
err = this._checkPath(params.Delete.Objects[i])
if( err instanceof Error ) {
cb(err, null)
return
}
if( params.Delete.Objects[i].Key in this.cache[params.Bucket] ) {
delete this.cache[params.Bucket][params.Key]
}
}
cb(err, {})
return { send: () => {} }
}
headObject(inputParams = {}, cb) {
if( !cb ) {
cb = () => {}
}
if( 'PleaseBreakApi' in inputParams ) {
cb(new UnexpectedParameter("Unexpected key 'PleaseBreakApi' found in params"), null)
return
}
this.getObject(inputParams, (err, result) => {
if( result ) {
delete result.Body
delete result.Key
}
cb(err, result)
})
return { send: () => {} }
}
}
if( process.env.USE_REAL_AWS && (process.env.USE_REAL_AWS === 'true' || process.env.USE_REAL_AWS === true) ) {
module.exports = require.requireActual('aws-sdk')
} else {
module.exports = { S3 }
}