From 0fd8f6cb0bc6e8b5e7ce051d0bd2332be1db8732 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Fri, 23 Mar 2018 11:23:52 +0000 Subject: [PATCH 1/9] adds function to scope mfs calls to an application path --- add-on/src/lib/ipfs-proxy/index.js | 6 +- add-on/src/lib/ipfs-proxy/pre-acl.js | 20 +------ add-on/src/lib/ipfs-proxy/pre-mfs-scope.js | 65 ++++++++++++++++++++++ 3 files changed, 71 insertions(+), 20 deletions(-) create mode 100644 add-on/src/lib/ipfs-proxy/pre-mfs-scope.js diff --git a/add-on/src/lib/ipfs-proxy/index.js b/add-on/src/lib/ipfs-proxy/index.js index a9bb00430..4d4e42adf 100644 --- a/add-on/src/lib/ipfs-proxy/index.js +++ b/add-on/src/lib/ipfs-proxy/index.js @@ -5,6 +5,7 @@ const browser = require('webextension-polyfill') const { createProxyServer, closeProxyServer } = require('ipfs-postmsg-proxy') const AccessControl = require('./access-control') const createPreAcl = require('./pre-acl') +const createPreMfsScope = require('./pre-mfs-scope') const createRequestAccess = require('./request-access') // Creates an object that manages the "server side" of the IPFS proxy @@ -31,7 +32,10 @@ function createIpfsProxy (getIpfs, getState) { removeListener: (_, handler) => port.onMessage.removeListener(handler), postMessage: (data) => port.postMessage(data), getMessageData: (d) => d, - pre: (fnName) => createPreAcl(getState, accessControl, getScope, fnName, requestAccess) + pre: (fnName) => [ + createPreAcl(fnName, getState, getScope, accessControl, requestAccess), + createPreMfsScope(fnName, getScope) + ] }) const close = () => { diff --git a/add-on/src/lib/ipfs-proxy/pre-acl.js b/add-on/src/lib/ipfs-proxy/pre-acl.js index 8bd372b95..990a0e4a8 100644 --- a/add-on/src/lib/ipfs-proxy/pre-acl.js +++ b/add-on/src/lib/ipfs-proxy/pre-acl.js @@ -2,32 +2,14 @@ // All other IPFS functions require authorization. const ACL_WHITELIST = Object.freeze(require('./acl-whitelist.json')) -// TEMPORARY blacklist of MFS functions that are automatically denied access -// https://github.com/ipfs-shipyard/ipfs-companion/issues/330#issuecomment-367651787 -const MFS_BLACKLIST = Object.freeze([ - 'files.cp', - 'files.mkdir', - 'files.stat', - 'files.rm', - 'files.read', - 'files.write', - 'files.mv', - 'files.flush', - 'files.ls' -]) - // Creates a "pre" function that is called prior to calling a real function // on the IPFS instance. It will throw if access is denied, and ask the user if // no access decision has been made yet. -function createPreAcl (getState, accessControl, getScope, permission, requestAccess) { +function createPreAcl (permission, getState, getScope, accessControl, requestAccess) { return async (...args) => { // Check if all access to the IPFS node is disabled if (!getState().ipfsProxy) throw new Error('User disabled access to IPFS') - if (MFS_BLACKLIST.includes(permission)) { - throw new Error('MFS functions are temporarily disabled') - } - // No need to verify access if permission is on the whitelist if (ACL_WHITELIST.includes(permission)) return args diff --git a/add-on/src/lib/ipfs-proxy/pre-mfs-scope.js b/add-on/src/lib/ipfs-proxy/pre-mfs-scope.js new file mode 100644 index 000000000..6a56fed4d --- /dev/null +++ b/add-on/src/lib/ipfs-proxy/pre-mfs-scope.js @@ -0,0 +1,65 @@ +const Path = require('path') +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 +} + +module.exports = createPreMfsScope + +const MfsPre = { + 'files.cp': srcDestPre, + 'files.mkdir': srcPre, + 'files.stat': srcPre, + 'files.rm': srcPre, + 'files.read': srcPre, + 'files.write': srcPre, + 'files.mv': srcDestPre, + 'files.flush': optionalSrcPre, + 'files.ls': optionalSrcPre +} + +// Scope a src/dest tuple to the app path +function srcDestPre (getScope, rootPath) { + return async (...args) => { + const appPath = await getAppPath(getScope, rootPath) + args[0][0] = appPath + safePath(args[0][0]) + args[0][1] = appPath + safePath(args[0][1]) + return args + } +} + +// Scope a src path to the app path +function srcPre (getScope, rootPath) { + return async (...args) => { + const appPath = await getAppPath(getScope, rootPath) + args[0] = appPath + safePath(args[0]) + return args + } +} + +// Scope an optional src path to the app path +function optionalSrcPre (getScope, rootPath) { + return async (...args) => { + const appPath = await getAppPath(getScope, rootPath) + if (Object.prototype.toString.call(args[0]) === '[object String]') { + args[0] = appPath + safePath(args[0]) + } + return args + } +} + +// Get the app path for a scope, prefixed with rootPath +const getAppPath = async (getScope, rootPath) => rootPath + scopeToPath(await getScope()) + +// Turn http://ipfs.io/ipfs/QmUmaEnH1uMmvckMZbh3yShaasvELPW4ZLPWnB4entMTEn +// into /http/ipfs.io/ipfs/QmUmaEnH1uMmvckMZbh3yShaasvELPW4ZLPWnB4entMTEn +const scopeToPath = (scope) => ('/' + scope).replace(/\/\//g, '/') + +// 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 +// breaking out of their jail. +const safePath = (path) => Path.resolve('/', path) From bb82619aff40d5a4c22f150440aced9dc4560ec0 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Fri, 23 Mar 2018 12:00:29 +0000 Subject: [PATCH 2/9] updates ipfs-postmsg-proxy --- package.json | 2 +- yarn.lock | 24 +++++++++++++++++------- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index f21dbfa00..497cfdfc2 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,7 @@ "ipfs": "0.28.2", "ipfs-api": "18.2.0", "ipfs-css": "0.2.0", - "ipfs-postmsg-proxy": "2.8.4", + "ipfs-postmsg-proxy": "2.10.0", "is-ipfs": "0.3.2", "is-svg": "3.0.0", "lru_map": "0.3.3", diff --git a/yarn.lock b/yarn.lock index 03a08f1b9..f10a1c523 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4146,9 +4146,9 @@ ipfs-multipart@~0.1.0: content "^3.0.0" dicer "^0.2.5" -ipfs-postmsg-proxy@2.8.4: - version "2.8.4" - resolved "https://registry.yarnpkg.com/ipfs-postmsg-proxy/-/ipfs-postmsg-proxy-2.8.4.tgz#eb1d1f0e78e0ee63664081abbb7dc2b126034fc0" +ipfs-postmsg-proxy@2.10.0: + version "2.10.0" + resolved "https://registry.yarnpkg.com/ipfs-postmsg-proxy/-/ipfs-postmsg-proxy-2.10.0.tgz#9260aea31dee68f218153a55f01f2dae40d5bed0" dependencies: big.js "^5.0.3" callbackify "^1.1.0" @@ -4158,8 +4158,8 @@ ipfs-postmsg-proxy@2.8.4: is-stream "^1.1.0" peer-id "^0.10.4" peer-info "^0.11.4" - postmsg-rpc "^2.2.0" - prepost "^1.0.0" + postmsg-rpc "^2.4.0" + prepost "^1.1.0" pull-abortable "^4.1.1" pull-defer "^0.2.2" pull-postmsg-stream "^1.1.3" @@ -7229,12 +7229,18 @@ postcss@6.0.14: source-map "^0.6.1" supports-color "^4.4.0" -postmsg-rpc@^2.1.1, postmsg-rpc@^2.2.0: +postmsg-rpc@^2.1.1: version "2.3.0" resolved "https://registry.yarnpkg.com/postmsg-rpc/-/postmsg-rpc-2.3.0.tgz#b355afabff1371457af831fe439dd84804229392" dependencies: shortid "^2.2.8" +postmsg-rpc@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/postmsg-rpc/-/postmsg-rpc-2.4.0.tgz#4e2daf6851852364696debd5d6bf6936d1424cdf" + dependencies: + shortid "^2.2.8" + prebuild-install@^2.1.0: version "2.5.1" resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-2.5.1.tgz#0f234140a73760813657c413cdccdda58296b1da" @@ -7263,10 +7269,14 @@ prepend-http@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" -prepost@^1.0.0, prepost@^1.0.1: +prepost@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/prepost/-/prepost-1.0.1.tgz#62c55d1ced516127e40ce2e3d8fc1a390cd86c7b" +prepost@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/prepost/-/prepost-1.1.0.tgz#6131567ab6fe3007b50762679f4b500e93e8ccbf" + preserve@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" From e079d9ca5938c0bb6219021b4f00636301b60a23 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Fri, 23 Mar 2018 12:00:48 +0000 Subject: [PATCH 3/9] adds MFS scope docs --- docs/window.ipfs.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/window.ipfs.md b/docs/window.ipfs.md index 828c7f9e3..86a1c0b7a 100644 --- a/docs/window.ipfs.md +++ b/docs/window.ipfs.md @@ -160,3 +160,20 @@ e.g. * `https://domain.com/` * `https://domain.com/files` * etc. + +## Are mutable file system (MFS) files sandboxed to a directory? + +Yes. To avoid conflicts, each app gets it's own MFS directory where it can store files. When using MFS functions (see [docs](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md#mutable-file-system)) this directory will be prefixed to paths you pass automatically. Your app's MFS directory is based on the **origin and path** where your application is running. + +e.g. + +* `files.write` to `/myfile.txt` on `https://domain.com/` + * writes to `/dapps/https/domain.com/myfile.txt` +* `files.write` to `/path/to/myfile.txt` on `https://domain.com/feature` + * writes to `/dapps/https/domain.com/feature/path/to/myfile.txt` +* `files.read` from `/feature/path/to/myfile.txt` on `https://domain.com/` + * reads from `/dapps/https/domain.com/feature/path/to/myfile.txt` +* `files.stat` to `/` on `https://domain.com/feature` + * stats `/dapps/https/domain.com/feature` +* `files.read` from `/../myfile.txt` on `https://domain.com/feature` + * reads from `/dapps/https/domain.com/feature/myfile.txt` (no traverse above your app's root) From 656b8918e1a82be5b60eb996354f8f1270770028 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Fri, 23 Mar 2018 12:03:51 +0000 Subject: [PATCH 4/9] changes wording and adds link --- docs/window.ipfs.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/window.ipfs.md b/docs/window.ipfs.md index 86a1c0b7a..ca1b32dde 100644 --- a/docs/window.ipfs.md +++ b/docs/window.ipfs.md @@ -11,6 +11,7 @@ - [Do I need to confirm every API call?](#do-i-need-to-confirm-every-api-call) - [Can I disable this for now?](#can-i-disable-this-for-now) - [How are permissions scoped?](#how-are-permissions-scoped) + - [Are mutable file system (MFS) files sandboxed to a directory?](#are-mutable-file-system-mfs-files-sandboxed-to-a-directory) ## Background @@ -163,7 +164,7 @@ e.g. ## Are mutable file system (MFS) files sandboxed to a directory? -Yes. To avoid conflicts, each app gets it's own MFS directory where it can store files. When using MFS functions (see [docs](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md#mutable-file-system)) this directory will be prefixed to paths you pass automatically. Your app's MFS directory is based on the **origin and path** where your application is running. +Yes. To avoid conflicts, each app gets it's own MFS directory where it can store files. When using MFS functions (see [docs](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md#mutable-file-system)) this directory will be automatically added to paths you pass. Your app's MFS directory is based on the **origin and path** where your application is running. e.g. From 3dd7ecc12fa9c919478439cb2e3a0bfb95f9eead Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Fri, 23 Mar 2018 15:43:41 +0000 Subject: [PATCH 5/9] adds mfs scope tests --- add-on/src/lib/ipfs-proxy/pre-mfs-scope.js | 63 ++++--- docs/window.ipfs.md | 10 +- .../functional/lib/ipfs-proxy/pre-acl.test.js | 12 +- .../lib/ipfs-proxy/pre-mfs-scope.test.js | 167 ++++++++++++++++++ 4 files changed, 220 insertions(+), 32 deletions(-) create mode 100644 test/functional/lib/ipfs-proxy/pre-mfs-scope.test.js 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 6a56fed4d..a5f007281 100644 --- a/add-on/src/lib/ipfs-proxy/pre-mfs-scope.js +++ b/add-on/src/lib/ipfs-proxy/pre-mfs-scope.js @@ -11,55 +11,76 @@ function createPreMfsScope (fnName, getScope, rootPath = DEFAULT_ROOT_PATH) { module.exports = createPreMfsScope const MfsPre = { - 'files.cp': srcDestPre, - 'files.mkdir': srcPre, - 'files.stat': srcPre, - 'files.rm': srcPre, - 'files.read': srcPre, - 'files.write': srcPre, - 'files.mv': srcDestPre, - 'files.flush': optionalSrcPre, - 'files.ls': optionalSrcPre + 'files.cp': createSrcDestPre, + 'files.mkdir': createSrcPre, + 'files.stat': createSrcPre, + 'files.rm' (getScope, rootPath) { + const srcPre = createSrcPre(getScope, rootPath) + return (...args) => { // Do not allow rm app root + if (isRoot(args[0])) throw new Error('cannot delete root') + return srcPre(...args) + } + }, + 'files.read': createSrcPre, + 'files.write' (getScope, rootPath) { + const srcPre = createSrcPre(getScope, rootPath) + return (...args) => { // Do not allow write to app root + if (isRoot(args[0])) throw new Error('/ was not a file') + return srcPre(...args) + } + }, + 'files.mv': createSrcDestPre, + 'files.flush': createOptionalSrcPre, + 'files.ls': createOptionalSrcPre } // Scope a src/dest tuple to the app path -function srcDestPre (getScope, rootPath) { +function createSrcDestPre (getScope, rootPath) { return async (...args) => { - const appPath = await getAppPath(getScope, rootPath) - args[0][0] = appPath + safePath(args[0][0]) - args[0][1] = appPath + safePath(args[0][1]) + const appPath = getAppPath(await getScope(), rootPath) + args[0][0] = Path.join(appPath, safePath(args[0][0])) + args[0][1] = Path.join(appPath, safePath(args[0][1])) return args } } // Scope a src path to the app path -function srcPre (getScope, rootPath) { +function createSrcPre (getScope, rootPath) { return async (...args) => { - const appPath = await getAppPath(getScope, rootPath) - args[0] = appPath + safePath(args[0]) + const appPath = getAppPath(await getScope(), rootPath) + args[0] = Path.join(appPath, safePath(args[0])) return args } } // Scope an optional src path to the app path -function optionalSrcPre (getScope, rootPath) { +function createOptionalSrcPre (getScope, rootPath) { return async (...args) => { - const appPath = await getAppPath(getScope, rootPath) + const appPath = getAppPath(await getScope(), rootPath) + if (Object.prototype.toString.call(args[0]) === '[object String]') { - args[0] = appPath + safePath(args[0]) + args[0] = Path.join(appPath, safePath(args[0])) + } else { + switch (args.length) { + case 0: return [appPath] // e.g. ipfs.files.ls() + case 1: return [appPath, args[0]] // e.g. ipfs.files.ls(options) + case 2: return [appPath, args[1]] // e.g. ipfs.files.ls(null, options) + } } return args } } // Get the app path for a scope, prefixed with rootPath -const getAppPath = async (getScope, rootPath) => rootPath + scopeToPath(await getScope()) +const getAppPath = (scope, rootPath) => rootPath + scopeToPath(scope) // Turn http://ipfs.io/ipfs/QmUmaEnH1uMmvckMZbh3yShaasvELPW4ZLPWnB4entMTEn -// into /http/ipfs.io/ipfs/QmUmaEnH1uMmvckMZbh3yShaasvELPW4ZLPWnB4entMTEn +// into /http:/ipfs.io/ipfs/QmUmaEnH1uMmvckMZbh3yShaasvELPW4ZLPWnB4entMTEn const scopeToPath = (scope) => ('/' + scope).replace(/\/\//g, '/') // 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 // breaking out of their jail. const safePath = (path) => Path.resolve('/', path) + +const isRoot = (path) => Path.resolve('/', path) === '/' diff --git a/docs/window.ipfs.md b/docs/window.ipfs.md index ca1b32dde..ee4dd0fd8 100644 --- a/docs/window.ipfs.md +++ b/docs/window.ipfs.md @@ -169,12 +169,12 @@ Yes. To avoid conflicts, each app gets it's own MFS directory where it can store e.g. * `files.write` to `/myfile.txt` on `https://domain.com/` - * writes to `/dapps/https/domain.com/myfile.txt` + * writes to `/dapps/https:/domain.com/myfile.txt` * `files.write` to `/path/to/myfile.txt` on `https://domain.com/feature` - * writes to `/dapps/https/domain.com/feature/path/to/myfile.txt` + * writes to `/dapps/https:/domain.com/feature/path/to/myfile.txt` * `files.read` from `/feature/path/to/myfile.txt` on `https://domain.com/` - * reads from `/dapps/https/domain.com/feature/path/to/myfile.txt` + * reads from `/dapps/https:/domain.com/feature/path/to/myfile.txt` * `files.stat` to `/` on `https://domain.com/feature` - * stats `/dapps/https/domain.com/feature` + * stats `/dapps/https:/domain.com/feature` * `files.read` from `/../myfile.txt` on `https://domain.com/feature` - * reads from `/dapps/https/domain.com/feature/myfile.txt` (no traverse above your app's root) + * reads from `/dapps/https:/domain.com/feature/myfile.txt` (no traverse above your app's root) diff --git a/test/functional/lib/ipfs-proxy/pre-acl.test.js b/test/functional/lib/ipfs-proxy/pre-acl.test.js index dd9b29b0e..1a9ee00d8 100644 --- a/test/functional/lib/ipfs-proxy/pre-acl.test.js +++ b/test/functional/lib/ipfs-proxy/pre-acl.test.js @@ -19,7 +19,7 @@ describe('lib/ipfs-proxy/pre-acl', () => { const getScope = () => 'https://ipfs.io/' const permission = 'files.add' - const preAcl = createPreAcl(getState, accessControl, getScope, permission) + const preAcl = createPreAcl(permission, getState, getScope, accessControl) let error @@ -42,7 +42,7 @@ describe('lib/ipfs-proxy/pre-acl', () => { try { await Promise.all(ACL_WHITELIST.map(permission => { - const preAcl = createPreAcl(getState, accessControl, getScope, permission, requestAccess) + const preAcl = createPreAcl(permission, getState, getScope, accessControl, requestAccess) return preAcl() })) } catch (err) { @@ -58,7 +58,7 @@ describe('lib/ipfs-proxy/pre-acl', () => { const getScope = () => 'https://ipfs.io/' const permission = 'files.add' const requestAccess = Sinon.spy(async () => ({ allow: true })) - const preAcl = createPreAcl(getState, accessControl, getScope, permission, requestAccess) + const preAcl = createPreAcl(permission, getState, getScope, accessControl, requestAccess) await preAcl() @@ -71,7 +71,7 @@ describe('lib/ipfs-proxy/pre-acl', () => { const getScope = () => 'https://ipfs.io/' const permission = 'files.add' const requestAccess = Sinon.spy(async () => ({ allow: false })) - const preAcl = createPreAcl(getState, accessControl, getScope, permission, requestAccess) + const preAcl = createPreAcl(permission, getState, getScope, accessControl, requestAccess) let error @@ -91,7 +91,7 @@ describe('lib/ipfs-proxy/pre-acl', () => { const getScope = () => 'https://ipfs.io/' const permission = 'files.add' const requestAccess = Sinon.spy(async () => ({ allow: false })) - const preAcl = createPreAcl(getState, accessControl, getScope, permission, requestAccess) + const preAcl = createPreAcl(permission, getState, getScope, accessControl, requestAccess) let error @@ -123,7 +123,7 @@ describe('lib/ipfs-proxy/pre-acl', () => { const getScope = () => 'https://ipfs.io/' const permission = 'files.add' const requestAccess = Sinon.spy(async () => ({ allow: true })) - const preAcl = createPreAcl(getState, accessControl, getScope, permission, requestAccess) + const preAcl = createPreAcl(permission, getState, getScope, accessControl, requestAccess) await preAcl() expect(requestAccess.callCount).to.equal(1) diff --git a/test/functional/lib/ipfs-proxy/pre-mfs-scope.test.js b/test/functional/lib/ipfs-proxy/pre-mfs-scope.test.js new file mode 100644 index 000000000..f966dc523 --- /dev/null +++ b/test/functional/lib/ipfs-proxy/pre-mfs-scope.test.js @@ -0,0 +1,167 @@ +'use strict' +const { describe, it } = require('mocha') +const { expect } = require('chai') +const createPreMfsScope = require('../../../../add-on/src/lib/ipfs-proxy/pre-mfs-scope') + +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) + 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 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') + }) + + 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 args = await pre('/dir') + expect(args[0]).to.equal('/test-dapps/https:/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 args = await pre('/') + expect(args[0]).to.equal('/test-dapps/https:/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 args = await pre('/file') + expect(args[0]).to.equal('/test-dapps/https:/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 args = await pre('/path/to/file.md') + expect(args[0]).to.equal('/test-dapps/https:/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 args = await pre('/path/to/file.md') + expect(args[0]).to.equal('/test-dapps/https:/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 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') + }) + + 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 args = await pre('/path/to/file.md') + expect(args[0]).to.equal('/test-dapps/https:/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 args = await pre() + expect(args[0]).to.equal('/test-dapps/https:/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 args = await pre({}) + expect(args[0]).to.equal('/test-dapps/https:/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 args = await pre(null, {}) + expect(args[0]).to.equal('/test-dapps/https:/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 args = await pre('/path/to/file.md') + expect(args[0]).to.equal('/test-dapps/https:/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 args = await pre() + expect(args[0]).to.equal('/test-dapps/https:/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 args = await pre({}) + expect(args[0]).to.equal('/test-dapps/https:/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 args = await pre(null, {}) + expect(args[0]).to.equal('/test-dapps/https:/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') + + let error + + try { + await pre('/') + } catch (err) { + error = err + } + + expect(() => { if (error) throw error }).to.throw('/ was not a file') + }) + + it('should not allow remove root', async () => { + const fnName = 'files.rm' + const getScope = () => 'https://ipfs.io/' + const pre = createPreMfsScope(fnName, getScope, '/test-dapps') + + let error + + try { + await pre('/', { recursive: true }) + } catch (err) { + error = err + } + + expect(() => { if (error) throw error }).to.throw('cannot delete root') + }) +}) From 36eb609ce8ec95dd824914404d3189d2e7850e70 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Fri, 23 Mar 2018 15:53:28 +0000 Subject: [PATCH 6/9] adds default case and comments --- add-on/src/lib/ipfs-proxy/pre-mfs-scope.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) 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 a5f007281..a4b59580e 100644 --- a/add-on/src/lib/ipfs-proxy/pre-mfs-scope.js +++ b/add-on/src/lib/ipfs-proxy/pre-mfs-scope.js @@ -16,7 +16,9 @@ const MfsPre = { 'files.stat': createSrcPre, 'files.rm' (getScope, rootPath) { const srcPre = createSrcPre(getScope, rootPath) - return (...args) => { // Do not allow rm app root + // Do not allow rm app root + // Need to explicitly deny because it's ok to rm -rf /a/path that's not / + return (...args) => { if (isRoot(args[0])) throw new Error('cannot delete root') return srcPre(...args) } @@ -24,7 +26,9 @@ const MfsPre = { 'files.read': createSrcPre, 'files.write' (getScope, rootPath) { const srcPre = createSrcPre(getScope, rootPath) - return (...args) => { // Do not allow write to app root + // Do not allow write to app root + // Need to explicitly deny because app path might not exist yet + return (...args) => { if (isRoot(args[0])) throw new Error('/ was not a file') return srcPre(...args) } @@ -62,9 +66,10 @@ function createOptionalSrcPre (getScope, rootPath) { args[0] = Path.join(appPath, safePath(args[0])) } else { switch (args.length) { - case 0: return [appPath] // e.g. ipfs.files.ls() + case 0: return [appPath] // e.g. ipfs.files.ls() case 1: return [appPath, args[0]] // e.g. ipfs.files.ls(options) case 2: return [appPath, args[1]] // e.g. ipfs.files.ls(null, options) + default: throw new Error('Unexpected number of arguments') } } return args From 53d33262bde0c1917740d04b54df540ec55b6cd2 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Fri, 23 Mar 2018 17:13:59 +0000 Subject: [PATCH 7/9] 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 From ff7ed6f969a693b426d7b80ac5006b3daa5dd386 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Tue, 27 Mar 2018 11:10:58 +0100 Subject: [PATCH 8/9] tweaks mfs path to remove : from protocol --- add-on/src/lib/ipfs-proxy/pre-mfs-scope.js | 4 +- docs/window.ipfs.md | 10 +-- .../lib/ipfs-proxy/pre-mfs-scope.test.js | 63 ++++++++++++++----- 3 files changed, 54 insertions(+), 23 deletions(-) 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 c1d46fada..f8af1e341 100644 --- a/add-on/src/lib/ipfs-proxy/pre-mfs-scope.js +++ b/add-on/src/lib/ipfs-proxy/pre-mfs-scope.js @@ -84,11 +84,13 @@ const getAppPath = async (getScope, getIpfs, rootPath) => { } // Turn http://ipfs.io/ipfs/QmUmaEnH1uMmvckMZbh3yShaasvELPW4ZLPWnB4entMTEn -// into /http%3A/ipfs.io/ipfs/QmUmaEnH1uMmvckMZbh3yShaasvELPW4ZLPWnB4entMTEn +// into /http/ipfs.io/ipfs/QmUmaEnH1uMmvckMZbh3yShaasvELPW4ZLPWnB4entMTEn const scopeToPath = (scope) => { return ('/' + scope) .replace(/\/\//g, '/') .split('/') + // Special case for protocol in scope, remove : from the end + .map((seg, i) => i === 1 && seg.endsWith(':') ? seg.slice(0, -1) : seg) .map(encodeURIComponent) .join('/') } diff --git a/docs/window.ipfs.md b/docs/window.ipfs.md index ee4dd0fd8..ca1b32dde 100644 --- a/docs/window.ipfs.md +++ b/docs/window.ipfs.md @@ -169,12 +169,12 @@ Yes. To avoid conflicts, each app gets it's own MFS directory where it can store e.g. * `files.write` to `/myfile.txt` on `https://domain.com/` - * writes to `/dapps/https:/domain.com/myfile.txt` + * writes to `/dapps/https/domain.com/myfile.txt` * `files.write` to `/path/to/myfile.txt` on `https://domain.com/feature` - * writes to `/dapps/https:/domain.com/feature/path/to/myfile.txt` + * writes to `/dapps/https/domain.com/feature/path/to/myfile.txt` * `files.read` from `/feature/path/to/myfile.txt` on `https://domain.com/` - * reads from `/dapps/https:/domain.com/feature/path/to/myfile.txt` + * reads from `/dapps/https/domain.com/feature/path/to/myfile.txt` * `files.stat` to `/` on `https://domain.com/feature` - * stats `/dapps/https:/domain.com/feature` + * stats `/dapps/https/domain.com/feature` * `files.read` from `/../myfile.txt` on `https://domain.com/feature` - * reads from `/dapps/https:/domain.com/feature/myfile.txt` (no traverse above your app's root) + * reads from `/dapps/https/domain.com/feature/myfile.txt` (no traverse above your app's root) 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 5222a76b2..bb399350e 100644 --- a/test/functional/lib/ipfs-proxy/pre-mfs-scope.test.js +++ b/test/functional/lib/ipfs-proxy/pre-mfs-scope.test.js @@ -18,8 +18,8 @@ describe('lib/ipfs-proxy/pre-mfs-scope', () => { 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%3A/ipfs.io/source.txt') - expect(args[0][1]).to.equal('/test-dapps/https%3A/ipfs.io/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') }) it('should scope src path for files.mkdir', async () => { @@ -28,7 +28,7 @@ describe('lib/ipfs-proxy/pre-mfs-scope', () => { 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%3A/ipfs.io/dir') + expect(args[0]).to.equal('/test-dapps/https/ipfs.io/dir') }) it('should scope src path for files.stat', async () => { @@ -37,7 +37,7 @@ describe('lib/ipfs-proxy/pre-mfs-scope', () => { 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%3A/ipfs.io/') + expect(args[0]).to.equal('/test-dapps/https/ipfs.io/') }) it('should scope src path for files.rm', async () => { @@ -46,7 +46,7 @@ describe('lib/ipfs-proxy/pre-mfs-scope', () => { 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%3A/ipfs.io/file') + expect(args[0]).to.equal('/test-dapps/https/ipfs.io/file') }) it('should scope src path for files.read', async () => { @@ -55,7 +55,7 @@ describe('lib/ipfs-proxy/pre-mfs-scope', () => { 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%3A/ipfs.io/path/to/file.md') + expect(args[0]).to.equal('/test-dapps/https/ipfs.io/path/to/file.md') }) it('should scope src path for files.write', async () => { @@ -64,7 +64,7 @@ describe('lib/ipfs-proxy/pre-mfs-scope', () => { 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%3A/ipfs.io/path/to/file.md') + expect(args[0]).to.equal('/test-dapps/https/ipfs.io/path/to/file.md') }) it('should scope src/dest paths for files.mv', async () => { @@ -73,8 +73,8 @@ describe('lib/ipfs-proxy/pre-mfs-scope', () => { 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%3A/ipfs.io/source.txt') - expect(args[0][1]).to.equal('/test-dapps/https%3A/ipfs.io/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') }) it('should scope src path for files.flush', async () => { @@ -83,7 +83,7 @@ describe('lib/ipfs-proxy/pre-mfs-scope', () => { 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%3A/ipfs.io/path/to/file.md') + expect(args[0]).to.equal('/test-dapps/https/ipfs.io/path/to/file.md') }) it('should scope src path for files.flush with no path and no options', async () => { @@ -92,7 +92,7 @@ describe('lib/ipfs-proxy/pre-mfs-scope', () => { 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%3A/ipfs.io/') + expect(args[0]).to.equal('/test-dapps/https/ipfs.io/') }) it('should scope src path for files.flush with no path and options', async () => { @@ -101,7 +101,7 @@ describe('lib/ipfs-proxy/pre-mfs-scope', () => { 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%3A/ipfs.io/') + expect(args[0]).to.equal('/test-dapps/https/ipfs.io/') }) it('should scope src path for files.flush with null path and options', async () => { @@ -110,7 +110,7 @@ describe('lib/ipfs-proxy/pre-mfs-scope', () => { 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%3A/ipfs.io/') + expect(args[0]).to.equal('/test-dapps/https/ipfs.io/') }) it('should scope src path for files.ls', async () => { @@ -119,7 +119,7 @@ describe('lib/ipfs-proxy/pre-mfs-scope', () => { 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%3A/ipfs.io/path/to/file.md') + expect(args[0]).to.equal('/test-dapps/https/ipfs.io/path/to/file.md') }) it('should scope src path for files.ls with no path and no options', async () => { @@ -128,7 +128,7 @@ describe('lib/ipfs-proxy/pre-mfs-scope', () => { 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%3A/ipfs.io/') + expect(args[0]).to.equal('/test-dapps/https/ipfs.io/') }) it('should scope src path for files.ls with no path and options', async () => { @@ -137,7 +137,7 @@ describe('lib/ipfs-proxy/pre-mfs-scope', () => { 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%3A/ipfs.io/') + expect(args[0]).to.equal('/test-dapps/https/ipfs.io/') }) it('should scope src path for files.ls with null path and options', async () => { @@ -146,7 +146,7 @@ describe('lib/ipfs-proxy/pre-mfs-scope', () => { 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%3A/ipfs.io/') + expect(args[0]).to.equal('/test-dapps/https/ipfs.io/') }) it('should not allow write to root', async () => { @@ -182,4 +182,33 @@ describe('lib/ipfs-proxy/pre-mfs-scope', () => { expect(() => { if (error) throw error }).to.throw('cannot delete root') }) + + it.only('should scope dweb paths', async () => { + const testData = [ + // 0: scope, 1: expected path (after mkdir('/dir') call) + ['/ipfs/QmUmaEnH1uMmvckMZbh3yShaasvELPW4ZLPWnB4entMTEn', '/test-dapps/ipfs/QmUmaEnH1uMmvckMZbh3yShaasvELPW4ZLPWnB4entMTEn/dir'], + ['ipfs:/QmUmaEnH1uMmvckMZbh3yShaasvELPW4ZLPWnB4entMTEn', '/test-dapps/ipfs/QmUmaEnH1uMmvckMZbh3yShaasvELPW4ZLPWnB4entMTEn/dir'], + ['ipfs://QmUmaEnH1uMmvckMZbh3yShaasvELPW4ZLPWnB4entMTEn', '/test-dapps/ipfs/QmUmaEnH1uMmvckMZbh3yShaasvELPW4ZLPWnB4entMTEn/dir'], + ['/ipns/arewedistributedyet.com', '/test-dapps/ipns/arewedistributedyet.com/dir'], + ['ipns:/arewedistributedyet.com', '/test-dapps/ipns/arewedistributedyet.com/dir'], + ['ipns://arewedistributedyet.com', '/test-dapps/ipns/arewedistributedyet.com/dir'], + ['dweb:/ipfs/QmUmaEnH1uMmvckMZbh3yShaasvELPW4ZLPWnB4entMTEn', '/test-dapps/dweb/ipfs/QmUmaEnH1uMmvckMZbh3yShaasvELPW4ZLPWnB4entMTEn/dir'], + ['dweb://ipfs/QmUmaEnH1uMmvckMZbh3yShaasvELPW4ZLPWnB4entMTEn', '/test-dapps/dweb/ipfs/QmUmaEnH1uMmvckMZbh3yShaasvELPW4ZLPWnB4entMTEn/dir'], + ['web+ipfs:/QmUmaEnH1uMmvckMZbh3yShaasvELPW4ZLPWnB4entMTEn', '/test-dapps/web%2Bipfs/QmUmaEnH1uMmvckMZbh3yShaasvELPW4ZLPWnB4entMTEn/dir'], + ['web+ipfs://QmUmaEnH1uMmvckMZbh3yShaasvELPW4ZLPWnB4entMTEn', '/test-dapps/web%2Bipfs/QmUmaEnH1uMmvckMZbh3yShaasvELPW4ZLPWnB4entMTEn/dir'], + ['web+ipns:/arewedistributedyet.com', '/test-dapps/web%2Bipns/arewedistributedyet.com/dir'], + ['web+ipns://arewedistributedyet.com', '/test-dapps/web%2Bipns/arewedistributedyet.com/dir'], + ['web+dweb://ipns/arewedistributedyet.com', '/test-dapps/web%2Bdweb/ipns/arewedistributedyet.com/dir'] + ] + + const fnName = 'files.mkdir' + const getIpfs = () => ({ files: { mkdir: () => Promise.resolve() } }) + + for (let i = 0; i < testData.length; i++) { + const getScope = () => testData[i][0] + const pre = createPreMfsScope(fnName, getScope, getIpfs, '/test-dapps') + const args = await pre('/dir') + expect(args[0]).to.equal(testData[i][1]) + } + }) }) From 07f0d269a4556c16d5e9ff3af4d0a9ccf32cfb08 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Tue, 27 Mar 2018 11:11:39 +0100 Subject: [PATCH 9/9] removes .only --- test/functional/lib/ipfs-proxy/pre-mfs-scope.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 bb399350e..ad75cf91c 100644 --- a/test/functional/lib/ipfs-proxy/pre-mfs-scope.test.js +++ b/test/functional/lib/ipfs-proxy/pre-mfs-scope.test.js @@ -183,7 +183,7 @@ describe('lib/ipfs-proxy/pre-mfs-scope', () => { expect(() => { if (error) throw error }).to.throw('cannot delete root') }) - it.only('should scope dweb paths', async () => { + it('should scope dweb paths', async () => { const testData = [ // 0: scope, 1: expected path (after mkdir('/dir') call) ['/ipfs/QmUmaEnH1uMmvckMZbh3yShaasvELPW4ZLPWnB4entMTEn', '/test-dapps/ipfs/QmUmaEnH1uMmvckMZbh3yShaasvELPW4ZLPWnB4entMTEn/dir'],