import fs from 'fs-extra' ; import path from 'path' ; import { error as StoreEngineError } from '@wireapp/store-engine' ; import { createSpec } from '@wireapp/store-engine/src/main/test/createSpec' ; import { deleteAllSpec } from '@wireapp/store-engine/src/main/test/deleteAllSpec' ; import { deleteSpec } from '@wireapp/store-engine/src/main/test/deleteSpec' ; import { purgeSpec } from '@wireapp/store-engine/src/main/test/purgeSpec' ; import { readAllPrimaryKeysSpec } from '@wireapp/store-engine/src/main/test/readAllPrimaryKeysSpec' ; import { readAllSpec } from '@wireapp/store-engine/src/main/test/readAllSpec' ; import { readSpec } from '@wireapp/store-engine/src/main/test/readSpec' ; import { updateOrCreateSpec } from '@wireapp/store-engine/src/main/test/updateOrCreateSpec' ; import { updateSpec } from '@wireapp/store-engine/src/main/test/updateSpec' ; import { FileEngine } from './index' ; describe ( 'FileEngine' , ( ) => { const BASE_DIRECTORY = path . join ( process . cwd ( ) , '.tmp' ) ; const STORE_NAME = 'the-simpsons' ; const TEST_DIRECTORY = path . join ( BASE_DIRECTORY , STORE_NAME ) ; let engine ; async function initEngine ( shouldCreateNewEngine = true ) { const storeEngine = shouldCreateNewEngine ? new FileEngine ( BASE_DIRECTORY ) : engine ; await storeEngine . init ( STORE_NAME ) ; return storeEngine ; } beforeEach ( async ( ) => { FileEngine . path = path ; engine = await initEngine ( ) ; } ) ; afterEach ( async ( ) => fs . remove ( TEST_DIRECTORY ) ) ; describe ( 'enforcePathRestrictions' , ( ) => { const enforcePathRestrictions = ( givenTrustedRoot , givenPath ) => ( ) => FileEngine . enforcePathRestrictions ( givenTrustedRoot , givenPath ) ; const expectedError = StoreEngineError . PathValidationError ; const unixFolder = '/home/marge/test/' ; const windowsFolder = 'C:\\Users\\bart\\Documents\\Database\\' ; it ( 'allows dots inside of primary keys.' , ( ) => { const tableName = 'amplify' ; const primaryKey = 'z.storage.StorageKey.EVENT.LAST_DATE' ; FileEngine . path = path . posix ; const actual = FileEngine . enforcePathRestrictions ( FileEngine . path . join ( unixFolder , tableName ) , primaryKey ) ; expect ( actual ) . toBeDefined ( ) ; } ) ; it ( 'allows slashes inside of primary keys as long as they do not navigate outside of the base folder.' , ( ) => { FileEngine . path = path . posix ; expect ( FileEngine . enforcePathRestrictions ( unixFolder , 'users/..' ) ) . toBeDefined ( ) ; expect ( FileEngine . enforcePathRestrictions ( unixFolder , 'users/../' ) ) . toBeDefined ( ) ; expect ( FileEngine . enforcePathRestrictions ( unixFolder , 'users/../sandbox' ) ) . toBeDefined ( ) ; expect ( FileEngine . enforcePathRestrictions ( unixFolder , 'users/me' ) ) . toBeDefined ( ) ; expect ( FileEngine . enforcePathRestrictions ( unixFolder , 'a/b/c/d/e/f/g/../../../../ok' ) ) . toBeDefined ( ) ; expect ( FileEngine . enforcePathRestrictions ( unixFolder , 'a/b/c/../../../' ) ) . toBeDefined ( ) ; expect ( enforcePathRestrictions ( unixFolder , 'a/b/c/../../../../' ) ) . toThrowError ( expectedError ) ; } ) ; it ( 'allows empty strings.' , ( ) => { const tableName = 'amplify' ; const primaryKey = '' ; FileEngine . path = path . posix ; const actual = FileEngine . enforcePathRestrictions ( FileEngine . path . join ( unixFolder , tableName ) , primaryKey ) ; expect ( actual ) . toBeDefined ( ) ; } ) ; it ( 'throws errors on path traversals.' , ( ) => { FileEngine . path = path . win32 ; expect ( enforcePathRestrictions ( windowsFolder , 'malicious\\..\\..\\test\\..\\..' ) ) . toThrowError ( expectedError ) ; expect ( enforcePathRestrictions ( windowsFolder , '\\malicious\\..\\\\..entry\\..\\..' ) ) . toThrowError ( expectedError ) ; expect ( enforcePathRestrictions ( windowsFolder , 'malicious\\..\\entry\\..\\..' ) ) . toThrowError ( expectedError ) ; expect ( enforcePathRestrictions ( windowsFolder , '\\\\server\\..\\..\\..' ) ) . toThrowError ( expectedError ) ; expect ( enforcePathRestrictions ( windowsFolder , 'malicious\\..\\..\\entry\\..\\' ) ) . toThrowError ( expectedError ) ; expect ( enforcePathRestrictions ( windowsFolder , '..\\etc' ) ) . toThrowError ( expectedError ) ; FileEngine . path = path . posix ; expect ( enforcePathRestrictions ( unixFolder , '../etc' ) ) . toThrowError ( expectedError ) ; expect ( enforcePathRestrictions ( unixFolder , '/malicious/../../../entry/../test' ) ) . toThrowError ( expectedError ) ; expect ( enforcePathRestrictions ( unixFolder , 'malicious/../../../entry/..' ) ) . toThrowError ( expectedError ) ; expect ( enforcePathRestrictions ( unixFolder , 'documents/../../../../../etc/hosts' ) ) . toThrowError ( expectedError ) ; expect ( enforcePathRestrictions ( unixFolder , 'malicious/../../../entry/../' ) ) . toThrowError ( expectedError ) ; expect ( enforcePathRestrictions ( unixFolder , '../etc' ) ) . toThrowError ( expectedError ) ; expect ( enforcePathRestrictions ( unixFolder , 'users/../../tigris' ) ) . toThrowError ( expectedError ) ; expect ( enforcePathRestrictions ( unixFolder , 'users/../tigris/../../' ) ) . toThrowError ( expectedError ) ; } ) ; it ( 'throws errors when attempting to use the root folder as a trusted root.' , ( ) => { FileEngine . path = path . posix ; expect ( enforcePathRestrictions ( '/' , 'etc/hosts' ) ) . toThrowError ( expectedError ) ; FileEngine . path = path . win32 ; expect ( enforcePathRestrictions ( 'C:/' , '\\Windows\\System32\\drivers\\etc\\hosts' ) ) . toThrowError ( expectedError ) ; } ) ; it ( 'is applied to all store operations.' , async ( ) => { const functionNames = [ 'create' , 'delete' , 'deleteAll' , 'read' , 'readAll' , 'readAllPrimaryKeys' , 'update' , 'updateOrCreate' , ] ; for ( const operation of functionNames ) { try { await engine [ operation ] ( '../etc' , 'primary-key' , { } ) ; fail ( ) ; } catch ( error ) { expect ( error instanceof expectedError ) . toBe ( true ) ; } } } ) ; } ) ; describe ( 'init' , ( ) => { it ( 'resolves with the directory to which the records will be saved.' , async ( ) => { const options = { fileExtension : '.json' , } ; engine = new FileEngine ( BASE_DIRECTORY ) ; const directory = await engine . init ( STORE_NAME , options ) ; const fileStatus = fs . statSync ( directory ) ; expect ( fileStatus . isDirectory ( ) ) . toBe ( true ) ; } ) ; } ) ; describe ( 'create' , ( ) => { describe ( 'create' , ( ) => { Object . entries ( createSpec ) . map ( ( [ description , testFunction ] ) => { it ( description , ( ) => testFunction ( engine ) ) ; } ) ; } ) ; it ( 'accepts custom file extensions.' , async ( ) => { const options = { fileExtension : '.json' , } ; engine = new FileEngine ( BASE_DIRECTORY ) ; await engine . init ( STORE_NAME , options ) ; expect ( engine . options . fileExtension ) . toBe ( options . fileExtension ) ; } ) ; } ) ; describe ( 'delete' , ( ) => { Object . entries ( deleteSpec ) . map ( ( [ description , testFunction ] ) => { it ( description , ( ) => testFunction ( engine ) ) ; } ) ; } ) ; describe ( 'deleteAll' , ( ) => { Object . entries ( deleteAllSpec ) . map ( ( [ description , testFunction ] ) => { it ( description , ( ) => testFunction ( engine ) ) ; } ) ; } ) ; describe ( 'purge' , ( ) => { Object . entries ( purgeSpec ) . map ( ( [ description , testFunction ] ) => { it ( description , ( ) => testFunction ( engine , initEngine ) ) ; } ) ; } ) ; describe ( 'readAllPrimaryKeys' , ( ) => { Object . entries ( readAllPrimaryKeysSpec ) . map ( ( [ description , testFunction ] ) => { it ( description , ( ) => testFunction ( engine ) ) ; } ) ; } ) ; describe ( 'readAll' , ( ) => { Object . entries ( readAllSpec ) . map ( ( [ description , testFunction ] ) => { it ( description , ( ) => testFunction ( engine ) ) ; } ) ; } ) ; describe ( 'read' , ( ) => { Object . entries ( readSpec ) . map ( ( [ description , testFunction ] ) => { it ( description , ( ) => testFunction ( engine ) ) ; } ) ; } ) ; describe ( 'updateOrCreate' , ( ) => { Object . entries ( updateOrCreateSpec ) . map ( ( [ description , testFunction ] ) => { it ( description , ( ) => testFunction ( engine ) ) ; } ) ; } ) ; describe ( 'update' , ( ) => { Object . entries ( updateSpec ) . map ( ( [ description , testFunction ] ) => { it ( description , ( ) => testFunction ( engine ) ) ; } ) ; } ) ; } ) ;