Skip to content
This repository has been archived by the owner on Feb 25, 2021. It is now read-only.

Commit

Permalink
Merge pull request #32 from bcoe/independent-functions
Browse files Browse the repository at this point in the history
Merging Independent Functions Feature
  • Loading branch information
bcoe committed Jun 7, 2014
2 parents 69a0544 + 5dbbcdb commit 1addb33
Show file tree
Hide file tree
Showing 7 changed files with 222 additions and 31 deletions.
100 changes: 95 additions & 5 deletions README.md
Expand Up @@ -79,7 +79,6 @@ The following options may be passed to the SandCastle constructor:
* `cwd` — path to the current working directory that the script will be run in (defaults to `process.cwd()`)



Executing Scripts on Pool of SandCastles
----------------------
A pool consists of several SandCastle child-processes, which will handle the script execution. Pool-object is a drop-in replacement of single Sandcastle instance. Only difference is, when creating the Pool-instance.
Expand Down Expand Up @@ -179,7 +178,7 @@ Providing an API

When creating an instance of SandCastle, you can provide an API. Functions within this API will be available inside of the untrustred scripts being executed.

__AN Example of an API:__
__An Example of an API:__

```javascript
var fs = require('fs');
Expand Down Expand Up @@ -223,13 +222,104 @@ script.on('exit', function(err, result) {
script.run();
```

SandCastle will be an ongoing project, please be liberal with your feedback, criticism, and contributions.
Exporting Multiple Functions
------------------------

Rather than main, you create a script file that exports multiple methods.
_Notice that one extra parameter `methodName` is available within the callback functions._

```javascript
var SandCastle = require('sandcastle').SandCastle;

var sandcastle = new SandCastle();

var script = sandcastle.createScript("\
exports = {\
foo: function() {\
exit('Hello Foo!');\
},\
bar: function() {\
exit('Hello Bar!');\
},\
hello: function() {\
exit('Hey ' + name + ' Hello World!');\
}\
}\
");

script.on('timeout', function(methodName) {
console.log(methodName);
});

script.on('exit', function(err, output, methodName) {
console.log(methodName); / foo, bar, hello
});

script.run('foo'); // Hello Foo!
script.run('bar'); // Hello Bar!
script.run('hello', {name: 'Ben'}); // Hey, Ben Hello World!
```

Testing
As all functions belong to the same script you can pass objects to the __same API instance__ and receive them later.

__State API:__

```javascript
exports.api = {
_state: {},

getState: function () {
return _state;
},

setState: function (state) {
_state = state;
}
};
```

__A Script Using the API:__

```javascript
var script = sandcastle.createScript("\
exports.main = {\
foo: function() {\
setState('foo', true);\
exit('Hello Foo!');\
},\
bar: function() {\
setState('bar', true);\
exit('Hello Bar!');\
}\,
hello: function() {\
setState('hello', true);\
exit('Hey ' + name + ' Hello World!');\
}\,
getStates: function() {\
return {\
foo: getState('foo'),\
bar: getState('bar'),\
hello: getState('hello')\
};\
}\
};\
");

script.run('main.getStates'); // { foo: undefined, bar: undefined, hello: undefined }
script.run('main.foo');
script.run('main.bar');
script.run('main.hello', {name: 'Ben'});
script.run('main.getStates'); // { foo: true, bar: true, hello: true }
```


Contributing
---------

Run the test suite with `npm test`.
SandCastle will be an ongoing project, please be liberal with your feedback, criticism, and contributions.

* send pull requests, for creative exploits that you find find for the SandBox. Sandboxing JavaScript is hard, it's unlikely that this library will ever be 100% bullet-proof.
* write unit tests for your contributions!

Copyright
---------
Expand Down
56 changes: 56 additions & 0 deletions examples/independent.js
@@ -0,0 +1,56 @@
var fs = require('fs');
var SandCastle = require('../lib').SandCastle;

var sandcastle = new SandCastle({
api: './stateApi.js'
});

var script = sandcastle.createScript("\
exports.main = {\
foo: function() {\
setState({ fooName: name });\
exit('Hello ' + name + '!');\
},\
bar: function() {\
var state = getState();\
state.barName = name;\
setState(state);\
\
exit('Hello ' + name + '! Your old name was: ' + state.fooName);\
},\
hello: function() {\
var state = getState();\
exit('Hey ' + name + '! You changed your name from ' + state.fooName + ' to ' + state.barName + '!');\
}\
}\
");

script.on('exit', function (err, output, methodName) {
console.log(output);

run();
});

var start = 0;
var methodsToCall = [
{
method: 'foo',
name: 'Foo'
},
{
method: 'bar',
name: 'Bar'
},
{
method: 'hello',
name: 'Hello'
}
];
var run = function() {
if (start < methodsToCall.length) {
script.run('main.' + methodsToCall[start].method, { name: methodsToCall[start].name });
start++;
}
};

run();
11 changes: 11 additions & 0 deletions examples/stateApi.js
@@ -0,0 +1,11 @@
exports.api = {
_state: {},

getState: function () {
return _state;
},

setState: function (state) {
_state = state;
}
};
6 changes: 3 additions & 3 deletions lib/sandbox.js
Expand Up @@ -79,7 +79,7 @@ Sandbox.prototype.executeScript = function(connection, data) {
// https://github.com/bcoe/sandcastle/pull/21
contextObject = clone(contextObject, true, Infinity, null);

vm.runInNewContext( this.wrapForExecution(script.source), vm.createContext(contextObject));
vm.runInNewContext( this.wrapForExecution(script.source, script.methodName), vm.createContext(contextObject));
} catch (e) {
connection.write(JSON.stringify({
error: {
Expand All @@ -91,8 +91,8 @@ Sandbox.prototype.executeScript = function(connection, data) {

};

Sandbox.prototype.wrapForExecution = function(source) {
return "\"use strict\"; var exports = Object.create(null);" + source + "\nexports.main();"
Sandbox.prototype.wrapForExecution = function(source, methodName) {
return "\"use strict\"; var exports = Object.create(null);" + source + "\nexports." + methodName + "();"
};

exports.Sandbox = Sandbox;
2 changes: 1 addition & 1 deletion lib/sandcastle.js
Expand Up @@ -126,7 +126,7 @@ SandCastle.prototype.runHeartbeatScript = function() {
}
});

script.run();
script.run('main');
};

SandCastle.prototype.createScript = function(source, opts) {
Expand Down
47 changes: 29 additions & 18 deletions lib/script.js
Expand Up @@ -17,54 +17,63 @@ function Script(opts) {

util.inherits(Script, events.EventEmitter);

Script.prototype.run = function(globals) {

Script.prototype.run = function(methodName, globals) {
var _this = this;

// passed in methodName argument is the globals object
if (globals === undefined && typeof methodName === 'object') {
globals = methodName;
methodName = null;
}

// we default to executing 'main'
methodName = methodName ? methodName : 'main';

this.reset();

this.timeoutId = setTimeout(function() {
if (_this.exited) return;
_this.exited = true;
_this.sandcastle.kickOverSandCastle();
_this.emit('timeout');
_this.emit('timeout', methodName);
}, this.timeout);

this.createClient(globals);
this.createClient(methodName, globals);
};

Script.prototype.reset = function() {
if (this.timeoutId) clearTimeout(this.timeoutId);
this.exited = false;
};

Script.prototype.createClient = function(globals) {
Script.prototype.createClient = function(methodName, globals) {

var _this = this;

this.sandcastle.sandboxReady(function() {

if (_this.exited) return;

var client = net.createConnection(_this.sandcastle.getSocket(), function() {
client.write(JSON.stringify({
source: _this.source,// the untrusted JS.
sourceAPI: _this.sourceAPI,// the trusted API.
globals: JSON.stringify(globals)// trusted global variables.
globals: JSON.stringify(globals), // trusted global variables.
methodName: methodName
}) + '\u0000'); // the chunk separator
});

client.on('close', function() {
if (!_this.dataReceived) {
setTimeout(function() {
_this.createClient();
_this.createClient(methodName);
}, 500);
}
});

client.on('error', function(err) {
setTimeout(function() {
_this.createClient();
_this.createClient(methodName);
}, 500);
});

Expand All @@ -73,7 +82,7 @@ Script.prototype.createClient = function(globals) {
stream.split('\u0000');
stream.on('split', function(chunk) {
client.end();
_this.onExit(chunk);
_this.onExit(methodName, chunk);
});

client.on('data', function(chunk) {
Expand All @@ -84,7 +93,7 @@ Script.prototype.createClient = function(globals) {
});
};

Script.prototype.onExit = function(data) {
Script.prototype.onExit = function(methodName, data) {
var _this = this,
output = null,
error = null;
Expand All @@ -93,21 +102,23 @@ Script.prototype.onExit = function(data) {
this.exited = true;

try {
output = JSON.parse(data);
if (output.error) {
error = new Error(output.error.message);
error.stack = output.error.stack;
output = null;
if (data.toString() !== 'undefined') {
output = JSON.parse(data);
if (output.error) {
error = new Error(output.error.message);
error.stack = output.error.stack;
output = null;
}
}
} catch (e) {
error = e;
}

this.emit('exit', error, output);
this.emit('exit', error, output, methodName);
};

Script.prototype.setSandCastle = function(sandcastle) {
this.sandcastle = sandcastle;
}

exports.Script = Script;
exports.Script = Script;

0 comments on commit 1addb33

Please sign in to comment.