From 53d33262bde0c1917740d04b54df540ec55b6cd2 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Fri, 23 Mar 2018 17:13:59 +0000 Subject: [PATCH] ensure app path exists, URI encode path segs for safe directory names --- add-on/src/lib/ipfs-proxy/index.js | 2 +- add-on/src/lib/ipfs-proxy/pre-mfs-scope.js | 42 +++++---- .../lib/ipfs-proxy/pre-mfs-scope.test.js | 88 +++++++++++-------- 3 files changed, 80 insertions(+), 52 deletions(-) diff --git a/add-on/src/lib/ipfs-proxy/index.js b/add-on/src/lib/ipfs-proxy/index.js index 4d4e42adf..91dc081dd 100644 --- a/add-on/src/lib/ipfs-proxy/index.js +++ b/add-on/src/lib/ipfs-proxy/index.js @@ -34,7 +34,7 @@ function createIpfsProxy (getIpfs, getState) { getMessageData: (d) => d, pre: (fnName) => [ createPreAcl(fnName, getState, getScope, accessControl, requestAccess), - createPreMfsScope(fnName, getScope) + createPreMfsScope(fnName, getScope, getIpfs) ] }) diff --git a/add-on/src/lib/ipfs-proxy/pre-mfs-scope.js b/add-on/src/lib/ipfs-proxy/pre-mfs-scope.js index a4b59580e..c1d46fada 100644 --- a/add-on/src/lib/ipfs-proxy/pre-mfs-scope.js +++ b/add-on/src/lib/ipfs-proxy/pre-mfs-scope.js @@ -4,8 +4,8 @@ const DEFAULT_ROOT_PATH = '/dapps' // Creates a "pre" function that is called prior to calling a real function // on the IPFS instance. It modifies the arguments to MFS functions to scope // file access to a directory designated to the web page -function createPreMfsScope (fnName, getScope, rootPath = DEFAULT_ROOT_PATH) { - return MfsPre[fnName] ? MfsPre[fnName](getScope, rootPath) : null +function createPreMfsScope (fnName, getScope, getIpfs, rootPath = DEFAULT_ROOT_PATH) { + return MfsPre[fnName] ? MfsPre[fnName](getScope, getIpfs, rootPath) : null } module.exports = createPreMfsScope @@ -14,8 +14,8 @@ const MfsPre = { 'files.cp': createSrcDestPre, 'files.mkdir': createSrcPre, 'files.stat': createSrcPre, - 'files.rm' (getScope, rootPath) { - const srcPre = createSrcPre(getScope, rootPath) + 'files.rm' (getScope, getIpfs, rootPath) { + const srcPre = createSrcPre(getScope, getIpfs, rootPath) // Do not allow rm app root // Need to explicitly deny because it's ok to rm -rf /a/path that's not / return (...args) => { @@ -24,8 +24,8 @@ const MfsPre = { } }, 'files.read': createSrcPre, - 'files.write' (getScope, rootPath) { - const srcPre = createSrcPre(getScope, rootPath) + 'files.write' (getScope, getIpfs, rootPath) { + const srcPre = createSrcPre(getScope, getIpfs, rootPath) // Do not allow write to app root // Need to explicitly deny because app path might not exist yet return (...args) => { @@ -39,9 +39,9 @@ const MfsPre = { } // Scope a src/dest tuple to the app path -function createSrcDestPre (getScope, rootPath) { +function createSrcDestPre (getScope, getIpfs, rootPath) { return async (...args) => { - const appPath = getAppPath(await getScope(), rootPath) + const appPath = await getAppPath(getScope, getIpfs, rootPath) args[0][0] = Path.join(appPath, safePath(args[0][0])) args[0][1] = Path.join(appPath, safePath(args[0][1])) return args @@ -49,18 +49,18 @@ function createSrcDestPre (getScope, rootPath) { } // Scope a src path to the app path -function createSrcPre (getScope, rootPath) { +function createSrcPre (getScope, getIpfs, rootPath) { return async (...args) => { - const appPath = getAppPath(await getScope(), rootPath) + const appPath = await getAppPath(getScope, getIpfs, rootPath) args[0] = Path.join(appPath, safePath(args[0])) return args } } // Scope an optional src path to the app path -function createOptionalSrcPre (getScope, rootPath) { +function createOptionalSrcPre (getScope, getIpfs, rootPath) { return async (...args) => { - const appPath = getAppPath(await getScope(), rootPath) + const appPath = await getAppPath(getScope, getIpfs, rootPath) if (Object.prototype.toString.call(args[0]) === '[object String]') { args[0] = Path.join(appPath, safePath(args[0])) @@ -76,12 +76,22 @@ function createOptionalSrcPre (getScope, rootPath) { } } -// Get the app path for a scope, prefixed with rootPath -const getAppPath = (scope, rootPath) => rootPath + scopeToPath(scope) +// Get the app path (create if not exists) for a scope, prefixed with rootPath +const getAppPath = async (getScope, getIpfs, rootPath) => { + const appPath = rootPath + scopeToPath(await getScope()) + await getIpfs().files.mkdir(appPath, { parents: true }) + return appPath +} // Turn http://ipfs.io/ipfs/QmUmaEnH1uMmvckMZbh3yShaasvELPW4ZLPWnB4entMTEn -// into /http:/ipfs.io/ipfs/QmUmaEnH1uMmvckMZbh3yShaasvELPW4ZLPWnB4entMTEn -const scopeToPath = (scope) => ('/' + scope).replace(/\/\//g, '/') +// into /http%3A/ipfs.io/ipfs/QmUmaEnH1uMmvckMZbh3yShaasvELPW4ZLPWnB4entMTEn +const scopeToPath = (scope) => { + return ('/' + scope) + .replace(/\/\//g, '/') + .split('/') + .map(encodeURIComponent) + .join('/') +} // Make a path "safe" by resolving any directory traversal segments relative to // '/'. Allows us to then prefix the app path without worrying about the user diff --git a/test/functional/lib/ipfs-proxy/pre-mfs-scope.test.js b/test/functional/lib/ipfs-proxy/pre-mfs-scope.test.js index f966dc523..5222a76b2 100644 --- a/test/functional/lib/ipfs-proxy/pre-mfs-scope.test.js +++ b/test/functional/lib/ipfs-proxy/pre-mfs-scope.test.js @@ -7,136 +7,153 @@ describe('lib/ipfs-proxy/pre-mfs-scope', () => { it('should return null for non MFS function', () => { const fnName = 'object.get' const getScope = () => 'https://ipfs.io/' - const pre = createPreMfsScope(fnName, getScope) + const getIpfs = () => ({ files: { mkdir: () => Promise.resolve() } }) + const pre = createPreMfsScope(fnName, getScope, getIpfs) expect(pre).to.equal(null) }) it('should scope src/dest paths for files.cp', async () => { const fnName = 'files.cp' const getScope = () => 'https://ipfs.io/' - const pre = createPreMfsScope(fnName, getScope, '/test-dapps') + const getIpfs = () => ({ files: { mkdir: () => Promise.resolve() } }) + const pre = createPreMfsScope(fnName, getScope, getIpfs, '/test-dapps') const args = await pre(['/source.txt', '/destination.txt']) - expect(args[0][0]).to.equal('/test-dapps/https:/ipfs.io/source.txt') - expect(args[0][1]).to.equal('/test-dapps/https:/ipfs.io/destination.txt') + expect(args[0][0]).to.equal('/test-dapps/https%3A/ipfs.io/source.txt') + expect(args[0][1]).to.equal('/test-dapps/https%3A/ipfs.io/destination.txt') }) it('should scope src path for files.mkdir', async () => { const fnName = 'files.mkdir' const getScope = () => 'https://ipfs.io/' - const pre = createPreMfsScope(fnName, getScope, '/test-dapps') + const getIpfs = () => ({ files: { mkdir: () => Promise.resolve() } }) + const pre = createPreMfsScope(fnName, getScope, getIpfs, '/test-dapps') const args = await pre('/dir') - expect(args[0]).to.equal('/test-dapps/https:/ipfs.io/dir') + expect(args[0]).to.equal('/test-dapps/https%3A/ipfs.io/dir') }) it('should scope src path for files.stat', async () => { const fnName = 'files.stat' const getScope = () => 'https://ipfs.io/' - const pre = createPreMfsScope(fnName, getScope, '/test-dapps') + const getIpfs = () => ({ files: { mkdir: () => Promise.resolve() } }) + const pre = createPreMfsScope(fnName, getScope, getIpfs, '/test-dapps') const args = await pre('/') - expect(args[0]).to.equal('/test-dapps/https:/ipfs.io/') + expect(args[0]).to.equal('/test-dapps/https%3A/ipfs.io/') }) it('should scope src path for files.rm', async () => { const fnName = 'files.rm' const getScope = () => 'https://ipfs.io/' - const pre = createPreMfsScope(fnName, getScope, '/test-dapps') + const getIpfs = () => ({ files: { mkdir: () => Promise.resolve() } }) + const pre = createPreMfsScope(fnName, getScope, getIpfs, '/test-dapps') const args = await pre('/file') - expect(args[0]).to.equal('/test-dapps/https:/ipfs.io/file') + expect(args[0]).to.equal('/test-dapps/https%3A/ipfs.io/file') }) it('should scope src path for files.read', async () => { const fnName = 'files.read' const getScope = () => 'https://ipfs.io/' - const pre = createPreMfsScope(fnName, getScope, '/test-dapps') + const getIpfs = () => ({ files: { mkdir: () => Promise.resolve() } }) + const pre = createPreMfsScope(fnName, getScope, getIpfs, '/test-dapps') const args = await pre('/path/to/file.md') - expect(args[0]).to.equal('/test-dapps/https:/ipfs.io/path/to/file.md') + expect(args[0]).to.equal('/test-dapps/https%3A/ipfs.io/path/to/file.md') }) it('should scope src path for files.write', async () => { const fnName = 'files.write' const getScope = () => 'https://ipfs.io/' - const pre = createPreMfsScope(fnName, getScope, '/test-dapps') + const getIpfs = () => ({ files: { mkdir: () => Promise.resolve() } }) + const pre = createPreMfsScope(fnName, getScope, getIpfs, '/test-dapps') const args = await pre('/path/to/file.md') - expect(args[0]).to.equal('/test-dapps/https:/ipfs.io/path/to/file.md') + expect(args[0]).to.equal('/test-dapps/https%3A/ipfs.io/path/to/file.md') }) it('should scope src/dest paths for files.mv', async () => { const fnName = 'files.mv' const getScope = () => 'https://ipfs.io/' - const pre = createPreMfsScope(fnName, getScope, '/test-dapps') + const getIpfs = () => ({ files: { mkdir: () => Promise.resolve() } }) + const pre = createPreMfsScope(fnName, getScope, getIpfs, '/test-dapps') const args = await pre(['/source.txt', '/destination.txt']) - expect(args[0][0]).to.equal('/test-dapps/https:/ipfs.io/source.txt') - expect(args[0][1]).to.equal('/test-dapps/https:/ipfs.io/destination.txt') + expect(args[0][0]).to.equal('/test-dapps/https%3A/ipfs.io/source.txt') + expect(args[0][1]).to.equal('/test-dapps/https%3A/ipfs.io/destination.txt') }) it('should scope src path for files.flush', async () => { const fnName = 'files.flush' const getScope = () => 'https://ipfs.io/' - const pre = createPreMfsScope(fnName, getScope, '/test-dapps') + const getIpfs = () => ({ files: { mkdir: () => Promise.resolve() } }) + const pre = createPreMfsScope(fnName, getScope, getIpfs, '/test-dapps') const args = await pre('/path/to/file.md') - expect(args[0]).to.equal('/test-dapps/https:/ipfs.io/path/to/file.md') + expect(args[0]).to.equal('/test-dapps/https%3A/ipfs.io/path/to/file.md') }) it('should scope src path for files.flush with no path and no options', async () => { const fnName = 'files.flush' const getScope = () => 'https://ipfs.io/' - const pre = createPreMfsScope(fnName, getScope, '/test-dapps') + const getIpfs = () => ({ files: { mkdir: () => Promise.resolve() } }) + const pre = createPreMfsScope(fnName, getScope, getIpfs, '/test-dapps') const args = await pre() - expect(args[0]).to.equal('/test-dapps/https:/ipfs.io/') + expect(args[0]).to.equal('/test-dapps/https%3A/ipfs.io/') }) it('should scope src path for files.flush with no path and options', async () => { const fnName = 'files.flush' const getScope = () => 'https://ipfs.io/' - const pre = createPreMfsScope(fnName, getScope, '/test-dapps') + const getIpfs = () => ({ files: { mkdir: () => Promise.resolve() } }) + const pre = createPreMfsScope(fnName, getScope, getIpfs, '/test-dapps') const args = await pre({}) - expect(args[0]).to.equal('/test-dapps/https:/ipfs.io/') + expect(args[0]).to.equal('/test-dapps/https%3A/ipfs.io/') }) it('should scope src path for files.flush with null path and options', async () => { const fnName = 'files.flush' const getScope = () => 'https://ipfs.io/' - const pre = createPreMfsScope(fnName, getScope, '/test-dapps') + const getIpfs = () => ({ files: { mkdir: () => Promise.resolve() } }) + const pre = createPreMfsScope(fnName, getScope, getIpfs, '/test-dapps') const args = await pre(null, {}) - expect(args[0]).to.equal('/test-dapps/https:/ipfs.io/') + expect(args[0]).to.equal('/test-dapps/https%3A/ipfs.io/') }) it('should scope src path for files.ls', async () => { const fnName = 'files.ls' const getScope = () => 'https://ipfs.io/' - const pre = createPreMfsScope(fnName, getScope, '/test-dapps') + const getIpfs = () => ({ files: { mkdir: () => Promise.resolve() } }) + const pre = createPreMfsScope(fnName, getScope, getIpfs, '/test-dapps') const args = await pre('/path/to/file.md') - expect(args[0]).to.equal('/test-dapps/https:/ipfs.io/path/to/file.md') + expect(args[0]).to.equal('/test-dapps/https%3A/ipfs.io/path/to/file.md') }) it('should scope src path for files.ls with no path and no options', async () => { const fnName = 'files.ls' const getScope = () => 'https://ipfs.io/' - const pre = createPreMfsScope(fnName, getScope, '/test-dapps') + const getIpfs = () => ({ files: { mkdir: () => Promise.resolve() } }) + const pre = createPreMfsScope(fnName, getScope, getIpfs, '/test-dapps') const args = await pre() - expect(args[0]).to.equal('/test-dapps/https:/ipfs.io/') + expect(args[0]).to.equal('/test-dapps/https%3A/ipfs.io/') }) it('should scope src path for files.ls with no path and options', async () => { const fnName = 'files.ls' const getScope = () => 'https://ipfs.io/' - const pre = createPreMfsScope(fnName, getScope, '/test-dapps') + const getIpfs = () => ({ files: { mkdir: () => Promise.resolve() } }) + const pre = createPreMfsScope(fnName, getScope, getIpfs, '/test-dapps') const args = await pre({}) - expect(args[0]).to.equal('/test-dapps/https:/ipfs.io/') + expect(args[0]).to.equal('/test-dapps/https%3A/ipfs.io/') }) it('should scope src path for files.ls with null path and options', async () => { const fnName = 'files.ls' const getScope = () => 'https://ipfs.io/' - const pre = createPreMfsScope(fnName, getScope, '/test-dapps') + const getIpfs = () => ({ files: { mkdir: () => Promise.resolve() } }) + const pre = createPreMfsScope(fnName, getScope, getIpfs, '/test-dapps') const args = await pre(null, {}) - expect(args[0]).to.equal('/test-dapps/https:/ipfs.io/') + expect(args[0]).to.equal('/test-dapps/https%3A/ipfs.io/') }) it('should not allow write to root', async () => { const fnName = 'files.write' const getScope = () => 'https://ipfs.io/' - const pre = createPreMfsScope(fnName, getScope, '/test-dapps') + const getIpfs = () => ({ files: { mkdir: () => Promise.resolve() } }) + const pre = createPreMfsScope(fnName, getScope, getIpfs, '/test-dapps') let error @@ -152,7 +169,8 @@ describe('lib/ipfs-proxy/pre-mfs-scope', () => { it('should not allow remove root', async () => { const fnName = 'files.rm' const getScope = () => 'https://ipfs.io/' - const pre = createPreMfsScope(fnName, getScope, '/test-dapps') + const getIpfs = () => ({ files: { mkdir: () => Promise.resolve() } }) + const pre = createPreMfsScope(fnName, getScope, getIpfs, '/test-dapps') let error