Skip to content

Instantly share code, notes, and snippets.

@yozlet
Created March 18, 2011 05:52
Show Gist options
  • Save yozlet/875668 to your computer and use it in GitHub Desktop.
Save yozlet/875668 to your computer and use it in GitHub Desktop.
Creation of a limited sandbox in node.js 0.2.x
/**
* sandbox.js - create a single sandbox object with exposed functions and services
*
* by [email protected]
*
*
**/
// Started doing this with proper constructors and stuff. Javascript Patterns
// book has a good Sandbox pattern. However, didn't have time to do that
// properly here. Next time.
var LIBS_ROOT = '/local/node/';
var ES5_PATH = LIBS_ROOT+"es5-shim/es5-shim.js";
var ORBDB_PATH = '/local/node/orbital/orbdb/';
var request_fields = [ 'method', 'params', 'body', 'headers', 'scriptName', 'projectName', 'scriptID'];
var
http = require('http'),
fs = require('fs'),
url = require('url'),
oauth = require('oauth'),
path = require('path'),
querystring = require("querystring"),
request = require('request'),
Script = process.binding('evals').Script,
OrbDB = require('./orbdb').OrbDB;
;
/**
* makeBasicSandbox() - creates a basic sandbox for use by require()'d scripts
* and is the basis for the main sandbox
**/
var makeBasicSandbox = function(req, project_path) {
var projectname = req.projectName;
var sandbox = {
MODULES: {
http: { createClient: http.createClient },
url: url,
JSON: JSON,
oauth: oauth,
querystring: querystring,
request: request // the library called "request"
},
setTimeout: setTimeout,
REQUEST: {}
};
// populate the REQUEST object
for (var i=0; i<request_fields.length; i++) {
sandbox.REQUEST[request_fields[i]] = req[request_fields[i]];
}
sandbox.REQUEST.path = req.params.pathname + "." + req.params.pathsuffix;
// add the custom closures
sandbox.readFile = make_readFile(sandbox, project_path, req.projectName);
sandbox.require = make_require(sandbox,req,project_path);
sandbox.log = make_log(sandbox, req, project_path);
sandbox.MODULES.OrbDB = new OrbDB(ORBDB_PATH, req.username, req.projectName);
// load up es5-shim
var scriptSource = fs.readFileSync(ES5_PATH,'utf8');
var script = new Script(scriptSource,"es5-shim.js");
script.runInNewContext(sandbox);
return sandbox;
};
exports.makeSandbox = function(req, res, project_path) {
// shorthand for responding 200 + text/html
function respond(body) {
if (!body) {
body = "ERROR: Script did not return a response";
}
body = body.toString();
res.writeHead(200, {
'Content-Type': 'text/html',
'Content-Length': body.length
});
res.end(body);
}
var sandbox = makeBasicSandbox(req, project_path);
sandbox.respond = respond;
sandbox.RESPONSE = res;
return sandbox;
};
/**
* make_readFile(sandbox, project_path, projectname)
* Added by Yoz for Orbital
* Function factory, returns a sandboxed file-reader limited to the project path
* TODO: Handle file read failures
* TODO: Abstract file-reading for readFile() and use()
**/
function make_readFile(mainSandbox,project_path,projectname) {
return function (filename) {
// TODO: Finer-grained exception handling
try {
if (/\.\.\//.test(filename)) {
mainSandbox.respond("readFile() can't climb up directories. Nice try!");
}
var filePath = path.join(project_path, projectname, filename);
var fileContents = fs.readFileSync(filePath,'utf8');
return fileContents;
} catch (e) {
// TODO: work out best exception behaviour here
mainSandbox.respond("readFile('"+filename+"') failed with error: " + e.message);
}
}
}
/**
* makeRequire(req,project_path)
* Added by Yoz for Orbital
* Function factory, returns a sandboxed equivalent of require()
* for loading other user-created libraries
* TODO: Cache compiled scripts
* TODO: Block circular use()s
* TODO: Handle file read failures
* TODO: Abstract sandboxed file-reading for readFile() and use()
* Maybe use() should work in existing sandbox, and make a new require()
* that uses temp sandbox?
**/
function make_require(mainSandbox,req,project_path) {
var projectname = req.projectName;
return function (scriptname) {
if (/^\./.test(scriptname)) {
mainSandbox.respond("require() can't load script name with a dot in it. Nice try!");
}
// add .js suffix if there isn't one
// TODO: Finer-grained exception handling
try {
var scriptSource = mainSandbox.readFile(scriptname);
tempSandbox = makeBasicSandbox(req, project_path);
tempSandbox.exports = {};
tempSandbox.REQUEST = {
scriptName: scriptname,
projectName: projectname,
username: req.username
};
var script = new Script(scriptSource, req.username + "/" + projectname + "/" + scriptname);
script.runInNewContext(tempSandbox);
return tempSandbox.exports;
} catch (e) {
// TODO: work out best exception behaviour here
mainSandbox.respond("use('"+scriptname+"') failed with error: " + e.message);
}
}
}
/**
* make_log(scriptName, project_path, projectname)
* creates a log() function for the project, outputting to a log file
* in the project dir
**/
function make_log(mainSandbox, req, project_path) {
return function (msg) {
var logFile = path.join(project_path, req.projectName, "log.txt");
try {
var date = new Date().toUTCString();
var f = fs.openSync(logFile,"a");
fs.write(f,date+" ["+req.scriptName+"] "+msg+"\n");
fs.close(f);
} catch (e) {
// TODO: work out best exception behaviour here
mainSandbox.respond("log('"+msg+"') failed with error: " + e.message);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment