diff --git a/app/extensions/safe/test/app/saferSafe.spec.ts b/app/extensions/safe/test/app/saferSafe.spec.ts new file mode 100644 index 000000000..cd7fb827f --- /dev/null +++ b/app/extensions/safe/test/app/saferSafe.spec.ts @@ -0,0 +1,65 @@ +import { SaferSafe } from '$App/extensions/safe/webviewProcess/saferSafe'; + +// jest.mock('safe-nodejs'); + +describe( 'Filesystem safe SAFE', () => { + let safe; + beforeEach( () => { + safe = new SaferSafe(); + } ); + + test( 'a SAFE object has needed methods', async () => { + expect( typeof safe.files_container_create ).toBe( 'function' ); + expect( typeof safe.files_container_add ).toBe( 'function' ); + expect( typeof safe.files_container_sync ).toBe( 'function' ); + + // not strictly needed, but lets check it exists on our new class + expect( typeof safe.files_container_add_from_raw ).toBe( 'function' ); + } ); + + test( 'attempting to use location fails', async () => { + expect( () => { + safe.files_container_create( 'some_location', '', true, true, true ); + } ).toThrow( /"location"/ ); + expect( () => { + safe.files_container_sync( 'some_location', '', true, true, true ); + } ).toThrow( /"location"/ ); + expect( () => { + safe.files_container_add( 'some_none_safe_location', '', true, true, true ); + } ).toThrow( /"location".+safe:/ ); + } ); + + test( 'attempting to use safe container add with a safe: url does not fail', async () => { + // it will still fail as args not correct and we're not mocking safe here. + expect( () => { + safe.files_container_add( + 'safe://some_safe_location', + '', + true, + true, + true + ); + } ).not.toThrow( /"location".+safe:/ ); + } ); + + test( 'attempting to use safe container create should fail when a string is passed', async () => { + expect( () => { + safe.files_container_create( 's', '', true, true, true ); + } ).toThrow( /File object/ ); + } ); + + test( 'attempting to use safe container create with locaton object should fail', async () => { + const x = {}; + + expect( () => { + safe.files_container_create( x, '', true, true, true ); + } ).toThrow( /File object/ ); + } ); + + test( 'attempting to use safe container create with File object should throw temp error', async () => { + const x = new File( [], 'test' ); + expect( () => { + safe.files_container_create( x, '', true, true, true ); + } ).toThrow( /location" argument cannot be used/ ); + } ); +} ); diff --git a/app/extensions/safe/webviewProcess/saferSafe.ts b/app/extensions/safe/webviewProcess/saferSafe.ts new file mode 100644 index 000000000..cd2fcaeea --- /dev/null +++ b/app/extensions/safe/webviewProcess/saferSafe.ts @@ -0,0 +1,94 @@ +import { Safe } from 'safe-nodejs'; + +/** + * Override the nodejs filesystem API to prevent file system access byt apps, and require them to use the browser's File APIs. + */ +export class SaferSafe extends Safe { + files_container_create = ( + location?: any, + destination?: string, + recursive: boolean, + del: boolean, + updateNrs: boolean, + dryRun: boolean + ): void => { + if ( location && !( location instanceof File ) ) { + throw new Error( ` + "location", if passed, must be a File object. + ` ); + } + + if ( location ) { + // Please use "Safe.files_container_create_from_raw" and the native browser "FileReader" API: https://developer.mozilla.org/en-US/docs/Web/API/FileReader + throw new Error( ` + The "location" argument cannot be used on "files_container_create" in the browser yet. + ` ); + } + + return Reflect.apply( Safe.prototype.files_container_create, this, [ + null, + null, + del, + updateNrs, + dryRun + ] ); + }; + + files_container_sync = ( + location?: any, + destination?: string, + recursive: boolean, + del: boolean, + updateNrs: boolean, + dryRun: boolean + ): void => { + if ( location && !( location instanceof File ) ) { + throw new Error( ` + "location", if passed, must be a File object. + ` ); + } + + if ( location ) { + // TODO: enable Files object use + throw new Error( ` + The "location" argument cannot be used on "files_container_sync" in the browser yet. + ` ); + } + + return Reflect.apply( Safe.prototype.files_container_sync, this, [ + null, + null, + del, + updateNrs, + dryRun + ] ); + }; + + files_container_add = ( + location: string, + destination: string, + force: boolean, + updateNrs: boolean, + dryRun: boolean + ): void => { + if ( typeof location !== 'string' ) { + throw new Error( ` + The "location" must be a "safe:" url string. + ` ); + } + + if ( !location.startsWith( 'safe:' ) ) { + throw new Error( ` + "location" must start with "safe:" + ` ); + } + + return Reflect.apply( Safe.prototype.files_container_add, this, [ + location, + destination, + force, + updateNrs, + dryRun + ] ); + }; +} diff --git a/app/extensions/safe/webviewProcess/webviewPreload.ts b/app/extensions/safe/webviewProcess/webviewPreload.ts index 0fdfdc286..0afaaace5 100644 --- a/app/extensions/safe/webviewProcess/webviewPreload.ts +++ b/app/extensions/safe/webviewProcess/webviewPreload.ts @@ -1,11 +1,10 @@ import EventEmitter from 'events'; -import { Safe, XorUrlEncoder } from 'safe-nodejs'; +import { XorUrlEncoder } from 'safe-nodejs'; +import { SaferSafe } from '$Extensions/safe/webviewProcess/saferSafe'; // eslint-disable-next-line import/extensions import pkg from '$Package'; import { logger } from '$Logger'; -// import * as remoteCallActions from '$Actions/remoteCall_actions'; -import { PROTOCOLS, CONFIG, isRunningTestCafeProcess } from '$Constants'; // shim for rdflib.js // eslint-disable-next-line no-underscore-dangle @@ -103,10 +102,11 @@ export const setupWebIdEventEmitter = ( passedStore, win = window ) => { export const setupSafeAPIs = ( passedStore, win = window ) => { const theWindow = win; - logger.info( 'Setup up SAFE Dom API...' ); + logger.info( 'Setup up SAFE Dom API... UPDATED' ); // use from passed object if present (for testing) - theWindow.Safe = theWindow.Safe || Safe; + theWindow.Safe = theWindow.Safe || SaferSafe; + theWindow.XorUrlEncoder = theWindow.XorUrlEncoder || XorUrlEncoder; };