UNPKG

cache-manager-s3

Version:
281 lines (221 loc) 6.95 kB
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 } }