Skip to content

Commit

Permalink
ensure app path exists, URI encode path segs for safe directory names
Browse files Browse the repository at this point in the history
  • Loading branch information
alanshaw committed Mar 23, 2018
1 parent fab3a15 commit 95b9402
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 52 deletions.
2 changes: 1 addition & 1 deletion add-on/src/lib/ipfs-proxy/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
]
})

Expand Down
42 changes: 26 additions & 16 deletions add-on/src/lib/ipfs-proxy/pre-mfs-scope.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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) => {
Expand All @@ -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) => {
Expand All @@ -39,28 +39,28 @@ 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
}
}

// 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]))
Expand All @@ -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
Expand Down
88 changes: 53 additions & 35 deletions test/functional/lib/ipfs-proxy/pre-mfs-scope.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand Down

0 comments on commit 95b9402

Please sign in to comment.