-
Notifications
You must be signed in to change notification settings - Fork 325
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #431 from tableflip/feat/mfs-scope
feat: scope mfs calls to an application path
- Loading branch information
Showing
8 changed files
with
365 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
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, getIpfs, rootPath = DEFAULT_ROOT_PATH) { | ||
return MfsPre[fnName] ? MfsPre[fnName](getScope, getIpfs, rootPath) : null | ||
} | ||
|
||
module.exports = createPreMfsScope | ||
|
||
const MfsPre = { | ||
'files.cp': createSrcDestPre, | ||
'files.mkdir': createSrcPre, | ||
'files.stat': createSrcPre, | ||
'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) => { | ||
if (isRoot(args[0])) throw new Error('cannot delete root') | ||
return srcPre(...args) | ||
} | ||
}, | ||
'files.read': createSrcPre, | ||
'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) => { | ||
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 createSrcDestPre (getScope, getIpfs, rootPath) { | ||
return async (...args) => { | ||
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, getIpfs, rootPath) { | ||
return async (...args) => { | ||
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, getIpfs, rootPath) { | ||
return async (...args) => { | ||
const appPath = await getAppPath(getScope, getIpfs, rootPath) | ||
|
||
if (Object.prototype.toString.call(args[0]) === '[object String]') { | ||
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) | ||
default: throw new Error('Unexpected number of arguments') | ||
} | ||
} | ||
return args | ||
} | ||
} | ||
|
||
// 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) => { | ||
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('/') | ||
} | ||
|
||
// 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) === '/' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.