├── LICENSE ├── README.md ├── bower.json ├── demos ├── node │ ├── fs │ │ ├── log.txt │ │ ├── plugin.js │ │ ├── readme.md │ │ └── start.js │ └── timeout │ │ ├── plugin.js │ │ ├── readme.md │ │ └── start.js └── web │ ├── banner │ ├── application.js │ ├── bad.png │ ├── banner.js │ ├── empty.png │ ├── good.png │ ├── index.html │ ├── readme.md │ └── style.css │ ├── circle │ ├── application.js │ ├── index.html │ ├── plugin.js │ ├── readme.md │ └── style.css │ ├── console │ ├── application.js │ ├── bg.png │ ├── index.html │ ├── plugin.js │ ├── readme.md │ └── style.css │ └── process │ ├── application.js │ ├── bg.png │ ├── index.html │ ├── intence.js │ ├── plugin.js │ ├── refresh.svg │ ├── scroll.png │ └── style.css ├── lib ├── _JailedSite.js ├── _frame.html ├── _frame.js ├── _pluginCore.js ├── _pluginNode.js ├── _pluginWebIframe.js ├── _pluginWebWorker.js └── jailed.js ├── package.json └── tests ├── lighttest.js ├── stage01 └── plugin1.js ├── stage02 └── plugin2.js ├── stage03 └── plugin3.js ├── stage04 └── plugin4.js ├── stage05 └── plugin5.js ├── stage06 ├── plugin6_1.js └── plugin6_2.js ├── stage07 └── plugin7.js ├── stage08 └── plugin8.js ├── stage09 └── plugin9.js ├── stage10 └── plugin10.js ├── stage11 ├── plugin11.js └── plugin11_1.js ├── stage12 └── plugin12.js ├── stage13 └── plugin13.js ├── stage15 ├── plugin15_bad.js └── plugin15_good.js ├── stage16 └── plugin16.js ├── stage17 └── plugin17.js ├── stage19 └── plugin19.js ├── stage20 └── plugin20.js ├── stage21 └── plugin21.js ├── stage22 └── plugin22.js ├── stage23 └── plugin23.js ├── stage24 └── plugin24.js ├── stage25 └── plugin25.js ├── stage26 └── plugin26.js ├── stage27 └── plugin27.js ├── stage28 └── plugin28.js ├── stage29 └── plugin29.js ├── stage30 └── plugin30.js ├── stage31 └── plugin31.js ├── stage32 └── plugin32.js ├── tests.js ├── tests_node.js └── tests_web.html /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 asvd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Jailed — flexible JS sandbox 2 | ============================ 3 | 4 | Jailed is a small JavaScript library for running untrusted code in a 5 | sandbox. The library is written in vanilla-js and has no dependencies. 6 | 7 | With Jailed you can: 8 | 9 | - Load an untrusted code into a secure sandbox; 10 | 11 | - Export a set of external functions into the sandbox. 12 | 13 | The untrusted code may then interract with the main application by 14 | directly calling those functions, but the application owner decides 15 | which functions to export, and therefore what will be allowed for the 16 | untrusted code to perform. 17 | 18 | The code is executed as a *plugin*, a special instance running as a 19 | restricted subprocess (in Node.js), or in a web-worker inside a 20 | sandboxed frame (in case of web-browser environment). The iframe is 21 | created locally, so that you don't need to host it on a separate 22 | (sub)domain. 23 | 24 | 25 | You can use Jailed to: 26 | 27 | - Setup a safe environment for executing untrusted code, without a 28 | need to create a sandboxed worker / subprocess manually; 29 | 30 | - Do that in an isomorphic way: the syntax is same both for Node.js 31 | and web-browser, the code works unchanged; 32 | 33 | - Execute a code from a string or from a file; 34 | 35 | - Initiate and interrupt the execution anytime; 36 | 37 | - *[Demo](http://asvd.github.io/jailed/demos/web/console/)* safely 38 | execute user-submitted code; 39 | 40 | - *[Demo](http://asvd.github.io/jailed/demos/web/banner/)* embed 41 | 3rd-party code and provide it the precise set of functions to 42 | harmlessly operate on the part of your application; 43 | 44 | - Export the particular set of application functions into the sandbox 45 | (or in the opposite direction), and let those functions be invoked 46 | from the other site (without a need for manual messaging) thus 47 | building any custom API and set of permissions. 48 | 49 | 50 | For instance: 51 | 52 | 53 | ```js 54 | var path = 'http://path.to/the/plugin.js'; 55 | 56 | // exported methods, will be available to the plugin 57 | var api = { 58 | alert: alert 59 | }; 60 | 61 | var plugin = new jailed.Plugin(path, api); 62 | ``` 63 | 64 | 65 | *plugin.js:* 66 | 67 | ```js 68 | // runs in a sandboxed worker, cannot access the main application, 69 | // with except for the explicitly exported alert() method 70 | 71 | // exported methods are stored in the application.remote object 72 | application.remote.alert('Hello from the plugin!'); 73 | ``` 74 | 75 | *(exporting the `alert()` method is not that good idea actually)* 76 | 77 | Under the hood, an application may only communicate to a plugin 78 | (sandboxed worker / jailed subprocess) through a messaging mechanism, 79 | which is reused by Jailed in order to simulate the exporting of 80 | particular functions. Each exported function is duplicated on the 81 | opposite site with a special wrapper method with the same name. Upon 82 | the wrapper method is called, arguments are serialized, and the 83 | corresponding message is sent, which leads to the actual function 84 | invocation on the other site. If the executed function then issues a 85 | callback, the responce message will be sent back and handled by the 86 | opposite site, which will, in turn, execute the actual callback 87 | previously stored upon the initial wrapper method invocation. A 88 | callback is in fact a short-term exported function and behaves in the 89 | same way, particularly it may invoke a newer callback in reply. 90 | 91 | 92 | ### Installation 93 | 94 | For the web-browser environment — download and unpack the 95 | [distribution](https://github.com/asvd/jailed/releases/download/v0.3.1/jailed-0.3.1.tar.gz), or install it using [Bower](http://bower.io/): 96 | 97 | ```sh 98 | $ bower install jailed 99 | ``` 100 | 101 | Load the `jailed.js` in a preferrable way. That is an UMD module, thus 102 | for instance it may simply be loaded as a plain JavaScript file using 103 | the ` 107 | ``` 108 | 109 | For Node.js — install Jailed with npm: 110 | 111 | ```sh 112 | $ npm install jailed 113 | ``` 114 | 115 | and then in your code: 116 | 117 | ```js 118 | var jailed = require('jailed'); 119 | ``` 120 | 121 | Optionally you may load the script from the 122 | [distribution](https://github.com/asvd/jailed/releases/download/v0.3.1/jailed-0.3.1.tar.gz): 123 | 124 | ```js 125 | var jailed = require('path/to/jailed.js'); 126 | ``` 127 | 128 | After the module is loaded, the two plugin constructors are available: 129 | `jailed.Plugin` and `jailed.DynamicPlugin`. 130 | 131 | 132 | 133 | ### Usage 134 | 135 | The messaging mechanism reused beyond the remote method invocation 136 | introduces some natural limitations for the exported functions and 137 | their usage (nevertheless the most common use-cases are still 138 | straightforward): 139 | 140 | - Exported function arguments may only be either simple objects (which 141 | are then serialized and sent within a message), or callbacks (which 142 | are preserved and replaced with special identifiers before 143 | sending). Custom object instance may not be used as an argument. 144 | 145 | - A callback can not be executed several times, it will be destroyed 146 | upon the first invocation. 147 | 148 | - If several callbacks are provided, only one of them may be called. 149 | 150 | - Returned value of an exported function is ignored, result should be 151 | provided to a callback instead. 152 | 153 | 154 | *In Node.js the 155 | [send()](http://nodejs.org/api/child_process.html#child_process_child_send_message_sendhandle) 156 | method of a child process object is used for transfering messages, 157 | which serializes an object into a JSON-string. In a web-browser 158 | environment, the messages are transfered via 159 | [postMessage()](https://developer.mozilla.org/en-US/docs/Web/API/Window.postMessage) 160 | method, which implements [the structured clone 161 | algorithm](https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/The_structured_clone_algorithm) 162 | for the serialization. That algorithm is more capable than JSON (for 163 | instance, in a web-browser you may send a RegExp object, which is not 164 | possible in Node.js). [More details about structured clone algorithm 165 | and its comparsion to 166 | JSON](https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/The_structured_clone_algorithm).* 167 | 168 | A plugin object may be created either from a string containing a 169 | source code to be executed, or with a path to the script. To load a 170 | plugin code from a file, create the plugin using `jailed.Plugin` 171 | constructor and provide the path: 172 | 173 | ```js 174 | var path = 'http://path.to/some/plugin.js'; 175 | 176 | // set of methods to be exported into the plugin 177 | var api = { 178 | alert: alert 179 | } 180 | 181 | var plugin = new jailed.Plugin(path, api); 182 | ``` 183 | 184 | 185 | *plugin.js:* 186 | 187 | ```js 188 | application.remote.alert('Hello from the plugin!'); 189 | ``` 190 | 191 | 192 | Creating a plugin from a string containing a code is very similar, 193 | this is performed using `jailed.DynamicPlugin` constructor: 194 | 195 | 196 | ```js 197 | var code = "application.remote.alert('Hello from the plugin!');"; 198 | 199 | var api = { 200 | alert: alert 201 | } 202 | 203 | var plugin = new jailed.DynamicPlugin(code, api); 204 | ``` 205 | 206 | 207 | The second `api` argument provided to the `jailed.Plugin` and 208 | `jailed.DynamicPlugin` constructors is an interface object with a set 209 | of functions to be exported into the plugin. It is also possible to 210 | export functions in the opposite direction — from a plugin to the main 211 | application. It may be used for instance if a plugin provides a method 212 | to perform a calculation. In this case the second argument of a plugin 213 | constructor may be omitted. To export some plugin functions, use 214 | `application.setInterface()` method in the plugin code: 215 | 216 | 217 | ```js 218 | // create a plugin 219 | var path = "http://path.to/some/plugin.js"; 220 | var plugin = new jailed.Plugin(path); 221 | 222 | // called after the plugin is loaded 223 | var start = function() { 224 | // exported method is available at this point 225 | plugin.remote.square(2, reportResult); 226 | } 227 | 228 | var reportResult = function(result) { 229 | window.alert("Result is: " + result); 230 | } 231 | 232 | // execute start() upon the plugin is loaded 233 | plugin.whenConnected(start); 234 | ``` 235 | 236 | *plugin.js:* 237 | 238 | ```js 239 | // provides the method to square a number 240 | var api = { 241 | square: function(num, cb) { 242 | // result reported to the callback 243 | cb(num*num); 244 | } 245 | } 246 | 247 | // exports the api to the application environment 248 | application.setInterface(api); 249 | ``` 250 | 251 | In this example the `whenConnected()` plugin method is used at the 252 | application site: that method subscribes the given function to the 253 | plugin connection event, after which the functions exported by the 254 | plugin become accessible at the `remote` property of a plugin. 255 | 256 | The `whenConnected()` method may be used as many times as needed and 257 | thus subscribe several handlers for a single connection event. For 258 | additional convenience, it is also possible to set a connection 259 | handler even after the plugin has already been connected — in this 260 | case the handler is issued immediately (yet asynchronously). 261 | 262 | When a plugin code is executed, a set of functions exported by the 263 | application is already prepared. But if one of those functions is 264 | invoked, it will actually be called on the application site. If in 265 | this case the code of that function will try to use a function 266 | exported by the plugin, it may not be prepared yet. To solve this, the 267 | similar `application.whenConnected()` method is available on the 268 | plugin site. The method works same as the one of the plugin object: 269 | the subscribed handler function will be executed after the connection 270 | is initialized, and a set of functions exported by each site is 271 | available on the opposite site. 272 | 273 | 274 | Therefore: 275 | 276 | - If you need to load a plugin and supply it with a set of exported 277 | functions, simply provide those functions into the plugin 278 | constructor, and then access those at `application.remote` property 279 | on the plugin site — the exported functions are already prepared 280 | when the plugin code is exectued. 281 | 282 | - If you need to load a plugin and use the functions it provides 283 | through exporting, set up a handler using `plugin.whenConnected()` 284 | method on the application site. After the event is fired, the 285 | functions exported by the plugin are available at its `remote` 286 | property of the plugin object;. 287 | 288 | - If both application and a plugin use the exported functions of each 289 | other, *and* the communication is initiated by the plugin, you will 290 | most likely need to use the `application.whenConnected()` method on 291 | the plugin site before initiating the communication, in order to 292 | make sure that the functions exported by the plugin are already 293 | available to the application. 294 | 295 | 296 | To disconnect a plugin, use the `disconnect()` method: it kills a 297 | worker / subprocess immediately without any chance for its code to 298 | react. 299 | 300 | A plugin may also disconnect itself by calling the 301 | `application.disconnect()` method. 302 | 303 | In addition to `whenConnected()` method, the plugin object also 304 | provides similar `whenFailed()` and `whenDisconnected()` methods: 305 | 306 | - `whenFailed()` subscribes a handler function to the connection 307 | failure event, which happens if there have been some error during 308 | the plugin initialization, like a network problem or a syntax error 309 | in the plugin initialization code. 310 | 311 | - `whenDisconnected()` subscribes a function to the disconnect event, 312 | which happens if a plugin was disconnected by calling the 313 | `disconnect()` method, or a plugin has disconnected itself by 314 | calling `application.disconnect()`, or if a plugin failed to 315 | initialize (along with the failure event mentioned above). After the 316 | event is fired, the plugin is not usable anymore. 317 | 318 | Just like as for `whenConnected()` method, those two methods may also 319 | be used several times or even after the event has actually been fired. 320 | 321 | 322 | ### Compatibility 323 | 324 | Jailed was tested and should work in Node.js, and in the following 325 | browsers: 326 | 327 | - Internet Explorer 10+, Edge 328 | - Firefox 26+ 329 | - Opera 12+ 330 | - Safari 6+ 331 | - Chrome 10+ 332 | 333 | 334 | ### Security 335 | 336 | This is how the sandbox is built: 337 | 338 | ##### In a web-browser: 339 | 340 | - a [sandboxed 341 | iframe](http://www.html5rocks.com/en/tutorials/security/sandboxed-iframes/) 342 | is created with its `sandbox` attribute only set to `"allow-scripts"` 343 | (to prevent the content of the frame from accessing anything of the 344 | main application origin); 345 | 346 | - then a web-worker is started inside that frame; 347 | 348 | - finally the code is loaded by the worker and executed. 349 | 350 | *Note: when Jailed library is loaded from the local source (its path 351 | starts with `file://`), the `"allow-same-origin"` permission is added 352 | to the `sandbox` attribute of the iframe. Local installations are 353 | mostly used for testing, and without that permission it would not be 354 | possible to load the plugin code from a local file. This means that 355 | the plugin code has an access to the local filesystem, and to some 356 | origin-shared things like IndexedDB (though the main application page 357 | is still not accessible from the worker). Therefore if you need to 358 | safely execute untrusted code on a local system, reuse the Jailed 359 | library in Node.js.* 360 | 361 | 362 | ##### In Node.js: 363 | 364 | *Warning: according to recent reports 365 | ([#33](https://github.com/asvd/jailed/issues/33)) this way of 366 | sandboxing is not secure any longer, the fix is being prepared...* 367 | 368 | - A Node.js subprocess is created by the Jailed library; 369 | 370 | - the subprocess (down)loads the file containing an untrusted code as 371 | a string (or, in case of `DynamicPlugin`, simply uses the provided 372 | string with code) 373 | 374 | - then `"use strict";` is appended to the head of that code (in order 375 | to prevent breaking the sandbox using `arguments.callee.caller`); 376 | 377 | - finally the code is executed using `vm.runInNewContext()` method, 378 | where the provided sandbox only exposes some basic methods like 379 | `setTimeout()`, and the `application` object for messaging with the 380 | application site. 381 | 382 | -- 383 | 384 | follow me on twitter: https://twitter.com/asvd0 385 | 386 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jailed", 3 | "version": "0.3.1", 4 | "homepage": "https://github.com/asvd/jailed", 5 | "authors": [ 6 | "Dmitry Prokashev " 7 | ], 8 | "description": "execute untrusted code in a sandbox with custom permissions", 9 | "main": "jailed.js", 10 | "moduleType": [ 11 | "amd", 12 | "globals", 13 | "node" 14 | ], 15 | "keywords": [ 16 | "jailed", 17 | "subprocess", 18 | "untrusted", 19 | "sandbox", 20 | "user-submitted-code", 21 | "web-worker", 22 | "iframe", 23 | "restricted", 24 | "subprocess" 25 | ], 26 | "license": "MIT", 27 | "ignore": [ 28 | "**/.*", 29 | "node_modules", 30 | "bower_components", 31 | "test", 32 | "tests", 33 | "demos" 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /demos/node/fs/log.txt: -------------------------------------------------------------------------------- 1 | New message added by the plugin on Wed Aug 27 2014 01:51:07 GMT+0200 (CEST) 2 | New message added by the plugin on Sun Sep 21 2014 14:16:24 GMT+0200 (CEST) 3 | -------------------------------------------------------------------------------- /demos/node/fs/plugin.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * A Plugin code, executed within a restricted subprocess and may only 4 | * perform the methods explicitly exported by the main aplication 5 | * (available at application.remote) 6 | */ 7 | 8 | var timeout = 3; 9 | var timeoutMsec = timeout*1000; 10 | var timeoutMsg = 'in '+timeout+' seconds'; 11 | var backup = ''; 12 | 13 | 14 | // functions order nearly corresponds to the execution sequence 15 | 16 | var start = function() { 17 | application.remote.readLog(displayLog, reportNoLog); 18 | } 19 | 20 | 21 | var reportNoLog = function() { 22 | application.remote.print( 23 | 'No log file found, '+ 24 | 'will create a new one '+timeoutMsg 25 | ); 26 | 27 | setTimeout(createLog, timeoutMsec); 28 | } 29 | 30 | 31 | var displayLog = function(content) { 32 | var message = 33 | '=== Logfile content start: ===\n\n' + 34 | content + 35 | '\n=== Logfile content end ===\n'; 36 | 37 | application.remote.print(message); 38 | backup = content; 39 | 40 | application.remote.print( 41 | 'Logfile will be removed '+timeoutMsg 42 | ); 43 | 44 | setTimeout(removeLog, timeoutMsec); 45 | } 46 | 47 | 48 | var removeLog = function() { 49 | application.remote.print('Removing logfile...'); 50 | application.remote.deleteLog(reportRemove, createLog); 51 | } 52 | 53 | 54 | var reportRemove = function() { 55 | application.remote.print( 56 | 'Logfile removed! Will be recreated '+timeoutMsg 57 | ); 58 | 59 | setTimeout(createLog, timeoutMsec); 60 | } 61 | 62 | 63 | var createLog = function() { 64 | application.remote.print('Creating an empty log file'); 65 | application.remote.createLog(reportCreate, finalize); 66 | } 67 | 68 | 69 | var reportCreate = function() { 70 | application.remote.print( 71 | 'Empty logfile created, '+ 72 | 'will be filled with content '+timeoutMsg 73 | ); 74 | 75 | setTimeout(writeLog, timeoutMsec); 76 | } 77 | 78 | 79 | var writeLog = function() { 80 | application.remote.print( 81 | 'Writing the old content into the log file '+ 82 | 'along with a new message added' 83 | ); 84 | var message = 'New message added by the plugin on ' + 85 | (new Date).toString(); 86 | var text = backup + message + '\n'; 87 | application.remote.updateLog(text, reportWrite, finalize); 88 | } 89 | 90 | 91 | var reportWrite = function() { 92 | application.remote.print('Logfile updated'); 93 | finalize(); 94 | } 95 | 96 | 97 | var finalize = function() { 98 | application.remote.print('Plugin finished'); 99 | application.disconnect(); 100 | } 101 | 102 | 103 | start(); 104 | 105 | -------------------------------------------------------------------------------- /demos/node/fs/readme.md: -------------------------------------------------------------------------------- 1 | # Filesystem demo 2 | 3 | This example demonstrates a plugin provided with a restricted access 4 | to the filesystem. The plugin may only create, read, update and delete 5 | a single file (`log.txt`). A method for printing a message is 6 | additionally exported for the demonstration purposes. 7 | 8 | 9 | To launch this demo, first clone the repo: 10 | 11 | ```sh 12 | $ git clone https://github.com/asvd/jailed 13 | ``` 14 | 15 | and then run with Node.js: 16 | 17 | ```sh 18 | $ node jailed/demos/node/fs/start.js 19 | ``` 20 | 21 | You will then get an output produced by a plugin explaining in details 22 | what is going on: 23 | 24 | ```sh 25 | $ nodejs node/fs/start.js 26 | 27 | === Logfile content start: === 28 | 29 | New message added by the plugin on Wed Aug 27 2014 01:50:49 GMT+0200 (CEST) 30 | New message added by the plugin on Wed Aug 27 2014 01:51:07 GMT+0200 (CEST) 31 | 32 | === Logfile content end === 33 | 34 | Logfile will be removed in 3 seconds 35 | Removing logfile... 36 | Logfile removed! Will be recreated in 3 seconds 37 | Creating an empty log file 38 | Empty logfile created, will be filled with content in 3 seconds 39 | Writing the old content into the log file along with a new message added 40 | Logfile updated 41 | Plugin finished 42 | ``` 43 | -------------------------------------------------------------------------------- /demos/node/fs/start.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Filesystem demo 4 | * 5 | * A code loaded as a Plugin is provided with a restricted access to 6 | * the filesystem: it can only create, read, update and delete the 7 | * particular file (log.txt). A method for printing a message is 8 | * additionally exported for the demonstration purposes. 9 | */ 10 | 11 | var jailed = require('../../../lib/jailed.js'); 12 | var fs = require('fs'); 13 | 14 | 15 | // building plugin interface 16 | var log = __dirname + '/log.txt'; 17 | 18 | // creates the empty log file 19 | var createLog = function(sCb, fCb) { 20 | var cb = function(err) { 21 | if (err) { 22 | fCb(); 23 | } else { 24 | sCb(); 25 | } 26 | } 27 | 28 | fs.writeFile(log, '', cb); 29 | } 30 | 31 | 32 | // reads the log file 33 | var readLog = function(sCb, fCb) { 34 | var cb = function(err, data) { 35 | if (err) { 36 | fCb(); 37 | } else { 38 | sCb(data.toString()); 39 | } 40 | } 41 | 42 | fs.readFile(log, cb); 43 | } 44 | 45 | 46 | // puts the given content into the log file 47 | var updateLog = function(data, sCb, fCb) { 48 | var cb = function(err) { 49 | if (err) { 50 | fCb(); 51 | } else { 52 | sCb(); 53 | } 54 | } 55 | 56 | fs.writeFile(log, data, cb); 57 | } 58 | 59 | 60 | // removes the log file 61 | var deleteLog = function(sCb, fCb) { 62 | var cb = function(err) { 63 | if (err) { 64 | fCb(); 65 | } else { 66 | sCb(); 67 | } 68 | } 69 | 70 | fs.unlink(log, cb); 71 | } 72 | 73 | 74 | // prints the given message on the console 75 | var print = function(msg) { 76 | console.log(msg); 77 | } 78 | 79 | 80 | var api = { 81 | print: print, 82 | createLog: createLog, 83 | readLog: readLog, 84 | updateLog: updateLog, 85 | deleteLog: deleteLog 86 | }; 87 | 88 | 89 | var plugin = new jailed.Plugin(__dirname + '/plugin.js', api); 90 | 91 | // all other work is performed by the plugin 92 | -------------------------------------------------------------------------------- /demos/node/timeout/plugin.js: -------------------------------------------------------------------------------- 1 | 2 | var fib = function(num) { 3 | var result = 0; 4 | if (num < 2) { 5 | result = num; 6 | } else { 7 | result = fib(num-1) + fib(num-2); 8 | } 9 | 10 | return result; 11 | } 12 | 13 | 14 | 15 | var api = { 16 | fib: function(num, cb) { 17 | cb(fib(num)); 18 | } 19 | }; 20 | 21 | application.setInterface(api); 22 | 23 | -------------------------------------------------------------------------------- /demos/node/timeout/readme.md: -------------------------------------------------------------------------------- 1 | # Timeout demo 2 | 3 | Performs a heavy calculation within a plugin, controls the execution 4 | time. The Plugin runs in a separate and restricted process which has 5 | no access to the external environment, it only provides a method to 6 | perform the calculation. If the calculation is not completed within a 7 | timeout, the Plugin process is terminated. This approach may be used 8 | for the untrusted code which may become too slow or even fall into an 9 | infinite loop. 10 | 11 | To launch this demo, first clone the repo: 12 | 13 | ```sh 14 | $ git clone https://github.com/asvd/jailed 15 | ``` 16 | 17 | and then run with Node.js: 18 | 19 | ```sh 20 | $ node jailed/demos/node/timeout/start.js 21 | ``` 22 | 23 | The plugin calculates the Fibonacci number (40th by default), you may 24 | provide a custom argument: 25 | 26 | ```sh 27 | $ node jailed/demos/node/timeout/start.js 50 28 | ``` 29 | 30 | (The number of 50 probably will not be calculated on time) 31 | 32 | -------------------------------------------------------------------------------- /demos/node/timeout/start.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Timeout demo 4 | * 5 | * Performs a heavy calculation within a Plugin, controls the 6 | * execution time. The Plugin runs in a separate and restricted 7 | * process which has no access to the external environment, it only 8 | * provides a method to perform the calculation. If the calculation is 9 | * not completed within a timeout, the Plugin process is 10 | * terminated. This approach may be used for the untrusted code which 11 | * may become too slow or even fall into an infinite loop. 12 | */ 13 | 14 | var jailed = require('../../../lib/jailed.js'); 15 | 16 | 17 | // determining the fibonacci number to calculate 18 | var num = 40; 19 | if (process.argv.length < 3 || !+process.argv[2]) { 20 | console.log( 21 | 'Number was not provided, the default value of ' 22 | + num + ' used instead' 23 | ); 24 | 25 | console.log( 26 | 'Usage: ' +process.argv[0]+ ' ' +process.argv[1]+ ' [number]' 27 | ); 28 | 29 | } else { 30 | num = +process.argv[2]||num; 31 | } 32 | 33 | 34 | // displays the result 35 | var report = function(result) { 36 | if (result) { 37 | console.log('Success! The result is: ' + result); 38 | } else { 39 | console.log('The result is unknown :-/'); 40 | } 41 | 42 | plugin.disconnect(); 43 | clearInterval(countdownInterval); 44 | } 45 | 46 | 47 | // destroys the plugin after several calls 48 | var count = 6; 49 | var countdown = function() { 50 | if (--count > 0) { 51 | console.log('Plugin will be destroyed in '+ count +'"'); 52 | } else { 53 | var msg = 'Plugin has failed to calculate on time ' + 54 | 'and will be destroyed!'; 55 | console.log(msg); 56 | report(null); 57 | } 58 | } 59 | 60 | 61 | // initialize everything 62 | var countdownInterval; 63 | 64 | var init = function() { 65 | console.log('Calculating the fibonacci number for '+num+'...'); 66 | plugin.remote.fib(num, report); 67 | countdownInterval = setInterval(countdown, 1000); 68 | } 69 | 70 | var plugin = new jailed.Plugin(__dirname+'/plugin.js'); 71 | plugin.whenConnected(init); 72 | 73 | -------------------------------------------------------------------------------- /demos/web/banner/application.js: -------------------------------------------------------------------------------- 1 | 2 | // interface provided to the plugin 3 | var image = document.getElementById('image'); 4 | var setImage = function(src) { 5 | image.style.backgroundImage = 'url('+src+')'; 6 | } 7 | 8 | var link = document.getElementById('link'); 9 | var setLink = function(src) { 10 | link.href = src; 11 | } 12 | 13 | var api = { 14 | setImage: setImage, 15 | setLink: setLink 16 | } 17 | 18 | 19 | // clears the banner 20 | var reset = function() { 21 | plugin = null; 22 | setImage('empty.png'); 23 | setLink('#'); 24 | } 25 | 26 | 27 | // initalize everything 28 | var plugin; 29 | var scripts = document.getElementsByTagName('script'); 30 | var path = scripts[scripts.length-1].src 31 | .split('/') 32 | .slice(0, -1) 33 | .join('/')+'/'; 34 | 35 | 36 | // creates the plugin, provides the interface 37 | document.getElementById('connect').onclick = function() { 38 | plugin = plugin || new jailed.Plugin(path+'banner.js', api); 39 | } 40 | 41 | 42 | // disconnects the plugin, resets the banner 43 | document.getElementById('disconnect').onclick = function() { 44 | if (plugin) { 45 | plugin.disconnect(); 46 | reset(); 47 | } 48 | } 49 | 50 | reset(); 51 | 52 | -------------------------------------------------------------------------------- /demos/web/banner/bad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asvd/jailed/4ef4e172e59f32df0fc2e5b0046b1734b6a58740/demos/web/banner/bad.png -------------------------------------------------------------------------------- /demos/web/banner/banner.js: -------------------------------------------------------------------------------- 1 | 2 | // potentially insecure 3rd-party partner code which may only 3 | // perform what is permitted by the application 4 | 5 | var bad = false; 6 | 7 | // updates banner image and link 8 | var update = function() { 9 | var image = bad ? 'bad.png' : 'good.png'; 10 | var link = bad ? 'http://facebook.com/' : 'http://google.com'; 11 | 12 | application.remote.setImage(image); 13 | application.remote.setLink(link); 14 | 15 | bad = !bad; 16 | } 17 | 18 | update(); 19 | setInterval(update, 2000); 20 | 21 | -------------------------------------------------------------------------------- /demos/web/banner/empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asvd/jailed/4ef4e172e59f32df0fc2e5b0046b1734b6a58740/demos/web/banner/empty.png -------------------------------------------------------------------------------- /demos/web/banner/good.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asvd/jailed/4ef4e172e59f32df0fc2e5b0046b1734b6a58740/demos/web/banner/good.png -------------------------------------------------------------------------------- /demos/web/banner/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Secured banner example 4 | 5 | 6 | 7 | 8 |
9 |
10 | 11 |

banner

12 | 13 |
14 |
15 | 16 |
Connect
17 |
Disconnect
18 |
19 | The squared area above is a place for a partner's 20 | advertising banner managed by a "3rd party code" loaded as a 21 | secure Plugin created with 22 | the Jailed 23 | library. The Plugin runs in a sandboxed Worker and cannot 24 | access the main document, with except for the two methods 25 | explicitily exported by the application: for changing the 26 | url the banner is pointing to, and for changing the banner 27 | image. Even if the Plugin performs an attempt to set too big 28 | image, it will be cropped by the application. The Plugin is 29 | fully controlled by the application and may be connected or 30 | disconnected anytime (operated with the two buttons in this 31 | demo). 32 |
33 | 34 | 35 | 40 | 41 |
42 |
43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /demos/web/banner/readme.md: -------------------------------------------------------------------------------- 1 | # Banner demo 2 | 3 | This example demonstrates how a third-party partner's library 4 | (`banner.js`) may be used to safely manipulate on the part of an 5 | application. 6 | 7 | Watch this demo online: 8 | 9 | [https://asvd.github.io/jailed/demos/web/banner/](https://asvd.github.io/jailed/demos/web/banner/) 10 | -------------------------------------------------------------------------------- /demos/web/banner/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #112233; 3 | color: #aabbcc; 4 | font-family: sans; 5 | font-size: 14px; 6 | line-height: 24px; 7 | cursor: default; 8 | } 9 | 10 | 11 | .rel { 12 | position: relative; 13 | padding: 0px; 14 | } 15 | 16 | 17 | .centered { 18 | width: 450px; 19 | padding: 0px; 20 | height: 300px; 21 | display: block; 22 | margin-left: auto; 23 | margin-right: auto; 24 | } 25 | 26 | 27 | .image { 28 | width: 100px; 29 | height: 100px; 30 | position: absolute; 31 | left: 176px; 32 | top: 100px; 33 | } 34 | 35 | 36 | .button { 37 | position: absolute; 38 | padding-top: 5px; 39 | padding-bottom: 1px; 40 | width: 200px; 41 | height: 25px; 42 | text-align: center; 43 | background-color: #112233; 44 | color: #aabbcc; 45 | cursor: pointer; 46 | font-family: sans; 47 | border: 1px solid #556677; 48 | border-radius: 2px; 49 | top: 230px; 50 | } 51 | 52 | 53 | .button:hover { 54 | background-color: #223344; 55 | color: #ddeeff; 56 | } 57 | 58 | 59 | .button:active { 60 | background-color: #334455; 61 | box-shadow: 0px 0px 3px 0px #556677; 62 | border: 1px solid #99AABB; 63 | cursor: default; 64 | color: #ccddee; 65 | } 66 | 67 | 68 | .first { 69 | left: 20px; 70 | } 71 | 72 | 73 | .second { 74 | left: 230px; 75 | } 76 | 77 | 78 | h3 { 79 | position: absolute; 80 | width: 412px; 81 | height: 100px; 82 | left: 20px; 83 | top: 20px; 84 | text-align: center; 85 | color: #aabbcc; 86 | font-family: sans; 87 | font-variant: small-caps; 88 | } 89 | 90 | 91 | .text { 92 | position: absolute; 93 | # text-indent: 2px; 94 | text-align: justify; 95 | width: 412px; 96 | height: 400px; 97 | top: 300px; 98 | left: 20px; 99 | } 100 | 101 | 102 | .links { 103 | position: absolute; 104 | width: 412; 105 | height: 50px; 106 | top: 600px; 107 | left: 20px; 108 | text-align: center; 109 | font-size: 11px; 110 | } 111 | 112 | a { 113 | color: #48D7E9; 114 | text-shadow: 0px 0px 3px #133153; 115 | } 116 | 117 | 118 | a:hover { 119 | color: #72EDFD; 120 | text-shadow: 0px 0px 8px #3F638B; 121 | } 122 | 123 | 124 | -------------------------------------------------------------------------------- /demos/web/circle/application.js: -------------------------------------------------------------------------------- 1 | 2 | // animates the circle 3 | var circle = document.getElementById('circle'); 4 | var X = 0; // animation parameters 5 | var Y = 0; 6 | 7 | setInterval( 8 | function() { 9 | X += .04; 10 | Y += .05; 11 | circle.style.marginLeft = Math.sin(Math.sin(X)*4)*13; 12 | circle.style.marginTop = Math.sin(Math.sin(Y)*4)*5; 13 | }, 20 14 | ); 15 | 16 | 17 | // (de)activates the circle highlight during the calculations 18 | var highlight = function(activate) { 19 | if (activate) { 20 | circle.style.backgroundColor = '#223344'; 21 | circle.style.border = '1px solid #99AABB'; 22 | circle.style.boxShadow = 23 | '0px 0px 5px 0px rgba(100, 120, 140, .5)'; 24 | } else { 25 | circle.style.backgroundColor = '#112233'; 26 | circle.style.border = '1px solid #556677'; 27 | circle.style.boxShadow = 'none'; 28 | } 29 | } 30 | 31 | 32 | // slowly calculates the fibonacci number 33 | var fib = function(num) { 34 | var result = 0; 35 | if (num < 2) { 36 | result = num; 37 | } else { 38 | result = fib(num-1) + fib(num-2); 39 | } 40 | 41 | return result; 42 | } 43 | 44 | 45 | var arg = 40; // fibonacci number argument 46 | 47 | // starts local calculation 48 | var calculateLocal = function() { 49 | var output = document.getElementById('outputLocal'); 50 | 51 | output.innerHTML = ''; 52 | highlight(true); 53 | 54 | setTimeout( 55 | function() { 56 | var result = fib(arg); 57 | output.innerHTML = result; 58 | highlight(false); 59 | }, 100 60 | ); 61 | } 62 | 63 | // starts calculation in the plugin 64 | var calculatePlugin = function() { 65 | var output = document.getElementById('outputPlugin'); 66 | 67 | output.innerHTML = ''; 68 | highlight(true); 69 | 70 | var cb = function(result) { 71 | output.innerHTML = result; 72 | highlight(false); 73 | } 74 | 75 | plugin.remote.fib(arg, cb); 76 | } 77 | 78 | 79 | // initialize everything 80 | var start = function() { 81 | document.getElementById('btnLocal').onclick = calculateLocal; 82 | document.getElementById('btnPlugin').onclick = calculatePlugin; 83 | } 84 | 85 | var scripts = document.getElementsByTagName('script'); 86 | var path = scripts[scripts.length-1].src 87 | .split('/') 88 | .slice(0, -1) 89 | .join('/')+'/'; 90 | 91 | var plugin = new jailed.Plugin(path+'plugin.js'); 92 | plugin.whenConnected(start); 93 | 94 | -------------------------------------------------------------------------------- /demos/web/circle/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Heavy calculations example 4 | 5 | 6 | 7 | 8 |
9 |
10 |

the dangling circle

11 | 12 |
13 | 14 |
15 |
Calculate locally
16 |
17 |
18 | Starts a heavy calculation simply as a function call 19 | within the main page. This will interrupt the animation 20 | (which is performed with JavaScript) until the 21 | calculation is completed, the page will hang up for 22 | some time. 23 |
24 |
25 | 26 |
27 | 28 |
29 |
Calculate in the plugin
30 |
31 |
32 | Performs exactly the same calculation, but uses a method 33 | provided by the Plugin created with 34 | the Jailed 35 | library instead. The Plugin runs in a separate thread of 36 | a Worker, so the page will not freeze during the 37 | calculation. 38 |
39 |
40 | 41 | 42 | 47 | 48 |
49 |
50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /demos/web/circle/plugin.js: -------------------------------------------------------------------------------- 1 | 2 | // slowly calculates the fibonacci number 3 | var fib = function(num) { 4 | var result = 0; 5 | if (num < 2) { 6 | result = num; 7 | } else { 8 | result = fib(num-1) + fib(num-2); 9 | } 10 | 11 | return result; 12 | } 13 | 14 | 15 | // provided to the application 16 | var api = { 17 | fib: function(num, cb) { 18 | cb(fib(num)); 19 | } 20 | }; 21 | 22 | application.setInterface(api); 23 | -------------------------------------------------------------------------------- /demos/web/circle/readme.md: -------------------------------------------------------------------------------- 1 | # Dangling Circle demo 2 | 3 | This example demonstrates how to use a plugin to easily perform a 4 | heavy calculation in a separate therad. 5 | 6 | Watch this demo online: 7 | 8 | [https://asvd.github.io/jailed/demos/web/circle/](https://asvd.github.io/jailed/demos/web/circle/) 9 | -------------------------------------------------------------------------------- /demos/web/circle/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #112233; 3 | color: #aabbcc; 4 | font-family: sans; 5 | font-size: 14px; 6 | line-height: 22px; 7 | cursor: default; 8 | } 9 | 10 | 11 | .rel { 12 | position: relative; 13 | padding: 0px; 14 | } 15 | 16 | 17 | h3 { 18 | position: absolute; 19 | width: 800px; 20 | height: 100px; 21 | left: 10px; 22 | top: 20px; 23 | text-align: center; 24 | color: #aabbcc; 25 | font-family: sans; 26 | font-variant: small-caps; 27 | # font-size: 20px; 28 | } 29 | 30 | .centered { 31 | width: 800px; 32 | padding: 0px; 33 | height: 500px; 34 | display: block; 35 | margin-left: auto; 36 | margin-right: auto; 37 | } 38 | 39 | .circle { 40 | position: absolute; 41 | width : 30px; 42 | height : 30px; 43 | top : 110px; 44 | left: 385px; 45 | border: 1px solid #556677; 46 | border-radius: 20px; 47 | } 48 | 49 | .output { 50 | position: absolute; 51 | font-family: mono; 52 | font-size: 10px; 53 | color: #667788; 54 | top: 200px; 55 | width : 200px; 56 | } 57 | 58 | .left .output { 59 | left: 150px; 60 | text-align: right; 61 | } 62 | 63 | .right .output { 64 | left: 450px; 65 | text-align: left; 66 | } 67 | 68 | 69 | .separator { 70 | position: absolute; 71 | background-color:#223344; 72 | width: 1px; 73 | height: 250px; 74 | top: 200px; 75 | left: 400; 76 | } 77 | 78 | .links { 79 | position: absolute; 80 | width: 800px; 81 | height: 50px; 82 | top: 500px; 83 | text-align: center; 84 | font-size: 11px; 85 | } 86 | 87 | .button { 88 | position: absolute; 89 | padding-top: 5px; 90 | padding-bottom: 1px; 91 | width: 200px; 92 | height: 25px; 93 | text-align: center; 94 | background-color: #112233; 95 | color: #aabbcc; 96 | cursor: pointer; 97 | font-family: sans; 98 | border: 1px solid #556677; 99 | border-radius: 2px; 100 | top : 220px; 101 | } 102 | 103 | .button:hover { 104 | background-color: #223344; 105 | color: #ddeeff; 106 | } 107 | 108 | .button:active { 109 | background-color: #334455; 110 | box-shadow: 0px 0px 3px 0px #556677; 111 | border: 1px solid #99AABB; 112 | cursor: default; 113 | color: #ccddee; 114 | } 115 | 116 | 117 | .left .button { 118 | left: 150px; 119 | } 120 | 121 | .right .button { 122 | left: 450px; 123 | } 124 | 125 | .text { 126 | position: absolute; 127 | width: 250px; 128 | height: 300px; 129 | top: 270px; 130 | } 131 | 132 | .left .text { 133 | left: 100px; 134 | text-align: right; 135 | } 136 | 137 | .right .text{ 138 | left: 450px; 139 | } 140 | 141 | 142 | .text h3 { 143 | } 144 | 145 | a { 146 | color: #48D7E9; 147 | text-shadow: 0px 0px 3px #133153; 148 | } 149 | 150 | a:hover { 151 | color: #72EDFD; 152 | text-shadow: 0px 0px 8px #3F638B; 153 | } 154 | 155 | 156 | -------------------------------------------------------------------------------- /demos/web/console/application.js: -------------------------------------------------------------------------------- 1 | 2 | // component shortcuts 3 | var el = {}; 4 | var list = [ 5 | 'terminalWrap', 'command', 'line', 'terminal', 'indicator' 6 | ]; 7 | 8 | for (var i = 0; i < list.length; i++){ 9 | el[list[i]] = document.getElementById(list[i]); 10 | } 11 | 12 | 13 | // newer elements template 14 | var sample = document.createElement('div'); 15 | 16 | 17 | // command history 18 | var hist = { 19 | _list: [''], 20 | _pos: 0, 21 | 22 | push: function(val) { 23 | var last = this._list.length-1; 24 | 25 | // prevent duplicates 26 | if (this._list[last-1] !== val) { 27 | this._list[last] = val; 28 | this._list.push(''); 29 | } 30 | 31 | this._pos = this._list.length-1; 32 | }, 33 | 34 | set: function(val) { 35 | if (this._pos >= this._list.length-1) { 36 | this._list[this._pos] = val; 37 | } 38 | }, 39 | 40 | next: function() { 41 | if (typeof this._list[this._pos+1] != 'undefined') { 42 | this._pos++; 43 | } 44 | 45 | return this._list[this._pos]; 46 | }, 47 | 48 | prev: function() { 49 | if (typeof this._list[this._pos-1] != 'undefined') { 50 | this._pos--; 51 | } 52 | 53 | return this._list[this._pos]; 54 | } 55 | }; 56 | 57 | 58 | // command line keypress handlers 59 | el.line.onkeydown = function(e) { 60 | switch (e.keyCode) { 61 | case 38: // up arrow 62 | var val = hist.prev(); 63 | el.line.value = val; 64 | el.line.setSelectionRange(val.length, val.length); 65 | e.preventDefault(); 66 | break; 67 | 68 | case 40: // down arrow 69 | var val = hist.next(); 70 | el.line.value = val; 71 | el.line.setSelectionRange(val.length, val.length); 72 | e.preventDefault(); 73 | break; 74 | 75 | case 13: // enter 76 | var val = el.line.value; 77 | if (val) { 78 | hist.push(val); 79 | el.line.value = ''; 80 | submit(val); 81 | } 82 | break; 83 | } 84 | } 85 | 86 | 87 | el.line.onkeyup = function(e) { 88 | switch (e.keyCode) { 89 | case 38: // up arrow 90 | case 40: // down arrow 91 | case 13: // enter 92 | break; 93 | 94 | default: 95 | // update current history entry 96 | hist.set(el.line.value); 97 | break; 98 | } 99 | } 100 | 101 | 102 | // sends the input to the plugin for evaluation 103 | var submit = function(code) { 104 | animateSubmit(code); 105 | 106 | // postpone the evaluation until the plugin is initialized 107 | plugin.whenConnected( 108 | function() { 109 | if (requests == 0) { 110 | startLoading(); 111 | } 112 | 113 | requests++; 114 | plugin.remote.run(code); 115 | } 116 | ); 117 | } 118 | 119 | 120 | // animates input submission 121 | var animateSubmit = function(code) { 122 | var anim = sample.cloneNode(false); 123 | anim.setAttribute('class', 'commandTextAnimated'); 124 | anim.innerHTML = escape(code); 125 | 126 | el.command.appendChild(anim); 127 | 128 | anim.offsetHeight; 129 | anim.style.top = -20; 130 | anim.style.opacity = .6; 131 | 132 | setTimeout( 133 | function() { 134 | el.command.removeChild(anim); 135 | }, 500 136 | ); 137 | 138 | } 139 | 140 | 141 | // prepares the string to be printed on the terminal 142 | var escape = function(msg) { 143 | return msg. 144 | replace(/&/g,'&'). 145 | replace(//g,'>'). 147 | replace(/\n/g, '
'). 148 | replace(/ /g, ' '); 149 | } 150 | 151 | 152 | // puts the message on the terminal 153 | var print = function(cls, msg) { 154 | var cmp = sample.cloneNode(false); 155 | cmp.setAttribute('class', cls); 156 | 157 | if (msg) { 158 | cmp.innerHTML = escape(msg); 159 | } 160 | 161 | el.terminal.appendChild(cmp); 162 | scroll(); 163 | } 164 | 165 | 166 | // slides the terminal content to display the newly added message 167 | var scroll = function() { 168 | el.terminal.offsetHeight; // redraw 169 | el.terminal.style.transition = 'top .15s'; 170 | el.terminal.style.top = 171 | el.terminalWrap.offsetHeight - el.terminal.scrollHeight; 172 | } 173 | 174 | 175 | // will restart the plugin if it does not respond 176 | var disconnectTimeout = null; 177 | var startLoading = function() { 178 | disconnectTimeout = setTimeout(disconnect, 3000); 179 | el.indicator.offsetHeight; 180 | el.indicator.style.transition = 'background-color 4s'; 181 | el.indicator.style.backgroundColor = '#839AC2'; 182 | } 183 | 184 | var endLoading = function() { 185 | clearTimeout(disconnectTimeout); 186 | el.indicator.offsetHeight; 187 | el.indicator.style.transition = 'background-color .3s'; 188 | el.indicator.style.backgroundColor = '#152637'; 189 | } 190 | 191 | var disconnect = function() { 192 | plugin.disconnect(); 193 | } 194 | 195 | 196 | // interface provided to the plugin 197 | var api = { 198 | output: function(data) { 199 | if (!--requests) { 200 | endLoading(); 201 | } 202 | 203 | print('separator'); 204 | print('input', data.input); 205 | if (data.error) { 206 | print('message', data.error); 207 | } else { 208 | print('output', data.output); 209 | } 210 | } 211 | }; 212 | 213 | 214 | // obtaining absolute path of this script 215 | var scripts = document.getElementsByTagName('script'); 216 | var path = scripts[scripts.length-1].src 217 | .split('?')[0] 218 | .split('/') 219 | .slice(0, -1) 220 | .join('/')+'/'; 221 | 222 | 223 | 224 | var requests; 225 | 226 | // (re)initializes the plugin 227 | var reset = function() { 228 | requests = 0; 229 | plugin = new jailed.Plugin(path+'plugin.js', api); 230 | plugin.whenDisconnected( function() { 231 | // give some time to handle the last responce 232 | setTimeout( function() { 233 | endLoading(); 234 | 235 | while (el.terminal.hasChildNodes()) { 236 | el.terminal.removeChild(el.terminal.childNodes[0]); 237 | } 238 | 239 | el.terminal.style.transition = 'top 0s'; 240 | el.terminal.style.top = el.terminalWrap.offsetHeight; 241 | 242 | print('message', 'Well done, you have just ruined everything :-/'); 243 | print('message', 'The console was restarted'); 244 | 245 | reset(); 246 | }, 10); 247 | }); 248 | } 249 | 250 | 251 | // initialize everything 252 | var plugin = null; 253 | 254 | reset(); 255 | el.line.focus(); 256 | 257 | el.terminalWrap.onclick = function(e) { 258 | if (!window.getSelection().toString()) { 259 | el.line.focus(); 260 | e.preventDefault(); 261 | } // otherwise text selected 262 | } 263 | 264 | -------------------------------------------------------------------------------- /demos/web/console/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asvd/jailed/4ef4e172e59f32df0fc2e5b0046b1734b6a58740/demos/web/console/bg.png -------------------------------------------------------------------------------- /demos/web/console/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | JS console demo 5 | 6 | 7 | 8 | 9 |
10 |
11 |
12 | 13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | 22 |
23 |
24 |
25 | 26 |
27 |

console

28 | 29 | User-submitted code is evaluated in a Plugin created 30 | with the Jailed 31 | library. The code is executed in a sandboxed iframe in order 32 | to prevent accessing the main application. 33 |
34 | 35 | 40 | 41 |
42 |
43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /demos/web/console/plugin.js: -------------------------------------------------------------------------------- 1 | 2 | // executes the given code and handles the result 3 | var run = function(code) { 4 | var result = { 5 | input: code, 6 | output: null, 7 | error: null 8 | }; 9 | 10 | try { 11 | result.output = stringify(runHidden(code)); 12 | } catch(e) { 13 | result.error = e.message; 14 | } 15 | 16 | application.remote.output(result); 17 | } 18 | 19 | 20 | // protects even the worker scope from being accessed 21 | var runHidden = function(code) { 22 | var indexedDB = null; 23 | var location = null; 24 | var navigator = null; 25 | var onerror = null; 26 | var onmessage = null; 27 | var performance = null; 28 | var self = null; 29 | var webkitIndexedDB = null; 30 | var postMessage = null; 31 | var close = null; 32 | var openDatabase = null; 33 | var openDatabaseSync = null; 34 | var webkitRequestFileSystem = null; 35 | var webkitRequestFileSystemSync = null; 36 | var webkitResolveLocalFileSystemSyncURL = null; 37 | var webkitResolveLocalFileSystemURL = null; 38 | var addEventListener = null; 39 | var dispatchEvent = null; 40 | var removeEventListener = null; 41 | var dump = null; 42 | var onoffline = null; 43 | var ononline = null; 44 | var importScripts = null; 45 | var console = null; 46 | var application = null; 47 | 48 | return eval(code); 49 | } 50 | 51 | 52 | // converts the output into a string 53 | var stringify = function(output) { 54 | var result; 55 | 56 | if (typeof output == 'undefined') { 57 | result = 'undefined'; 58 | } else if (output === null) { 59 | result = 'null'; 60 | } else { 61 | result = JSON.stringify(output) || output.toString(); 62 | } 63 | 64 | return result; 65 | } 66 | 67 | 68 | application.setInterface({run:run}); 69 | 70 | -------------------------------------------------------------------------------- /demos/web/console/readme.md: -------------------------------------------------------------------------------- 1 | # Console demo 2 | 3 | This example demonstrates the usage of a plugin for evaluating a 4 | potentially untrusted code submitted by the user 5 | 6 | Watch this demo online: 7 | 8 | [https://asvd.github.io/jailed/demos/web/console/](https://asvd.github.io/jailed/demos/web/console/) 9 | -------------------------------------------------------------------------------- /demos/web/console/style.css: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | background-color: #112233; 4 | color: #aabbcc; 5 | font-family: sans; 6 | font-size: 14px; 7 | line-height: 21px; 8 | cursor: default; 9 | } 10 | 11 | .rel { 12 | position: relative; 13 | padding: 0px; 14 | } 15 | 16 | .centered { 17 | display: block; 18 | width: 900px; 19 | height: 400px; 20 | margin-left: auto; 21 | margin-right: auto; 22 | padding: 0px; 23 | } 24 | 25 | .border { 26 | position: absolute; 27 | left: 16px; 28 | top: 40px; 29 | width: 500px; 30 | height: 275px; 31 | background-color: #051627; 32 | background-image: url('bg.png'); 33 | border: 1px solid #334455; 34 | color: #aabbcc; 35 | font-family: monospace; 36 | transition: border .3s; 37 | } 38 | 39 | .border:hover { 40 | transition: box-shadow .3s, border .3s; 41 | box-shadow: inset 0px 0px 15px 0px rgba(0, 0, 0, .1); 42 | } 43 | 44 | .border:active { 45 | transition: border 0s; 46 | border: 1px solid #445566; 47 | } 48 | 49 | .text { 50 | position: absolute; 51 | left: 545px; 52 | top: 25px; 53 | width: 300px; 54 | height: 400px; 55 | } 56 | 57 | .text h3 { 58 | font-variant: small-caps; 59 | } 60 | 61 | .links { 62 | position: absolute; 63 | width: 900; 64 | height: 50px; 65 | top: 350; 66 | left: 20px; 67 | font-size: 11px; 68 | } 69 | 70 | 71 | a { 72 | color: #48D7E9; 73 | text-shadow: 0px 0px 3px #133153; 74 | } 75 | 76 | a:hover { 77 | color: #72EDFD; 78 | text-shadow: 0px 0px 8px #3F638B; 79 | } 80 | 81 | .code { 82 | font: 10pt Bitstream Vera Sans Mono, monospace; 83 | color: #7EC3DD; 84 | text-shadow: 0px 0px 5px rgba(145, 194, 221, 0.26); 85 | } 86 | 87 | .terminalWrap { 88 | left: 0px; 89 | top: 0px; 90 | width: 500px; 91 | height: 253px; 92 | padding: 0px; 93 | overflow: hidden; 94 | font-family: mono; 95 | font-size: 14px; 96 | } 97 | 98 | .shadow { 99 | position: absolute; 100 | left: 0px; 101 | top:0px; 102 | width: 500px; 103 | height: 50px; 104 | background: linear-gradient( 105 | to bottom, 106 | rgba(2,19,35,.8) 0%, 107 | rgba(2,19,35,0) 100% 108 | ); 109 | 110 | } 111 | 112 | .indicator { 113 | position: absolute; 114 | left: 0px; 115 | top: 253px; 116 | width: 500px; 117 | height: 2px; 118 | padding-top: 6px; 119 | background-color: #152637; 120 | padding: 0px; 121 | } 122 | 123 | 124 | 125 | .inv { 126 | position: absolute; 127 | left: 0px; 128 | top:295px; 129 | color: #556677; 130 | font-family: monospace; 131 | font-size:18px; 132 | cursor: default; 133 | } 134 | 135 | .command { 136 | position: absolute; 137 | left: 0px; 138 | top: 255px; 139 | width: 500px; 140 | height: 20px; 141 | padding: 0px; 142 | overflow: hidden; 143 | font-family: monospace; 144 | } 145 | 146 | .commandTextAnimated { 147 | position: absolute; 148 | left: 2px; 149 | top: -2px; 150 | width: 497px; 151 | height: 20px; 152 | padding: 0px; 153 | color: #aabbcc; 154 | text-shadow: 0px 0px 4px rgba(160, 196, 228, 0.1); 155 | font-family: mono; 156 | font-size: 14px; 157 | transition: top .15s, opacity .1s; 158 | margin: 2px 0px 0px 0px; 159 | } 160 | 161 | .commandText { 162 | position: absolute; 163 | left: 2px; 164 | top: 0px; 165 | width: 497px; 166 | height: 20px; 167 | padding: 0px; 168 | color: #aabbcc; 169 | text-shadow: 0px 0px 4px rgba(160, 196, 228, 0.1); 170 | font-family: mono; 171 | font-size: 14px; 172 | margin: 0px; 173 | } 174 | 175 | 176 | .command input { 177 | position: absolute; 178 | background-color: rgba(0,0,0,0); 179 | border: none; 180 | outline: none; 181 | } 182 | 183 | .terminal { 184 | position: absolute; 185 | top: 253px; 186 | left: 0px; 187 | width : 495px; 188 | padding-left: 2px; 189 | cursor: default; 190 | } 191 | 192 | .separator { 193 | width: 497px; 194 | height: 0px; 195 | float: left; 196 | } 197 | 198 | .input { 199 | float: left; 200 | overflow: hidden; 201 | max-width: 496px; 202 | color: #aabbcc; 203 | } 204 | 205 | .output { 206 | padding-left: 10px; 207 | float: right; 208 | color: #627A93; 209 | } 210 | 211 | .message { 212 | padding-left: 10px; 213 | float: right; 214 | color: #355D87; 215 | } 216 | 217 | -------------------------------------------------------------------------------- /demos/web/process/application.js: -------------------------------------------------------------------------------- 1 | var scripts = document.getElementsByTagName('script'); 2 | var path = scripts[scripts.length-1].src 3 | .split('/') 4 | .slice(0, -1) 5 | .join('/')+'/'; 6 | 7 | 8 | // component shortcuts 9 | var el = {}; 10 | var list = [ 11 | 'input_data', 12 | 'output_data', 13 | 'button_regen_input', 14 | 'button_process', 15 | 'code' 16 | ]; 17 | 18 | for (var i = 0; i < list.length; i++){ 19 | el[list[i]] = document.getElementById(list[i]); 20 | } 21 | 22 | // generates random input and puts it into input field 23 | var input; 24 | function regen_input() { 25 | input = []; 26 | var len = 10; 27 | for (var i = 0; i < len; i++) { 28 | input.push(Math.floor(Math.random()*10)); 29 | } 30 | 31 | el.input_data.innerHTML = stringify(input); 32 | } 33 | 34 | // processes the input data using provided code 35 | function process() { 36 | el.output_data.innerHTML = ''; 37 | var code = el.code.innerText; 38 | var input = el.input_data.innerText; 39 | 40 | var plugin = new jailed.Plugin(path+'plugin.js'); 41 | var process = function() { 42 | var displayResult = function(result) { 43 | if (result.error) { 44 | el.output_data.innerHTML = 45 | ''+result.error + ''; 46 | } else { 47 | el.output_data.innerHTML = stringify(result.output); 48 | } 49 | plugin.disconnect(); 50 | } 51 | 52 | plugin.remote.process(input, code, displayResult); 53 | } 54 | 55 | plugin.whenConnected(process); 56 | } 57 | 58 | 59 | // converts an object into a string 60 | function stringify(object) { 61 | var result; 62 | 63 | if (typeof object == 'undefined') { 64 | result = 'undefined'; 65 | } else if (object === null) { 66 | result = 'null'; 67 | } else { 68 | result = JSON.stringify(object) || object.toString(); 69 | } 70 | 71 | return result; 72 | } 73 | 74 | 75 | 76 | // removes spaces from the end of the strings 77 | function trim_tails(string) { 78 | var arr = string.split('\n'); 79 | 80 | for (var i = 0; i < arr.length; i++) { 81 | arr[i] = arr[i].replace(/[\s\uFEFF\xA0]+$/g, ''); 82 | } 83 | 84 | return arr.join('\n'); 85 | } 86 | 87 | // fills the processing code textarea with initial content 88 | function fill_code() { 89 | var code = trim_tails([ 90 | 'function(input) { ', 91 | ' // bubble sorting the input array ', 92 | ' ', 93 | ' // switches the two elems if needed ', 94 | ' // returns true if switched ', 95 | ' function switchEls(idx) { ', 96 | ' var switched = false; ', 97 | ' if (input[idx] < input[idx-1]) { ', 98 | ' var tmp = input[idx]; ', 99 | ' input[idx] = input[idx-1]; ', 100 | ' input[idx-1] = tmp; ', 101 | ' switched = true; ', 102 | ' } ', 103 | ' return switched; ', 104 | ' } ', 105 | ' ', 106 | ' var switched; ', 107 | ' do { ', 108 | ' switched = false; ', 109 | ' for (var i = 1; i < input.length; i++) { ', 110 | ' switched |= switchEls(i); ', 111 | ' } ', 112 | ' } while(switched); ', 113 | ' ', 114 | ' return input; ', 115 | '} ', 116 | ' ', 117 | ' ' 118 | ].join('\n')); 119 | 120 | el.code.container.innerHTML = code; 121 | } 122 | 123 | 124 | function init() { 125 | el.button_regen_input.onclick = regen_input; 126 | el.button_process.onclick = process; 127 | fill_code(); 128 | regen_input(); 129 | 130 | // caching 131 | var plugin = new jailed.Plugin(path+'plugin.js'); 132 | plugin.whenConnected(function(){plugin.disconnect();}); 133 | 134 | el.code.container.focus(); 135 | } 136 | 137 | window.addEventListener("load", init, false); 138 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /demos/web/process/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asvd/jailed/4ef4e172e59f32df0fc2e5b0046b1734b6a58740/demos/web/process/bg.png -------------------------------------------------------------------------------- /demos/web/process/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Data processing demo 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 |
14 |
15 |
Input:
16 |
17 |
18 |
19 | 20 |
21 |
Processing code:
22 |
23 |
24 | 25 |
26 |
Process
27 |
28 | 29 |
30 |
Output:
31 |
32 |
33 | 34 |
35 | 36 | 44 | 45 | 53 | 54 | 55 |
56 | 57 |
58 |

data processing

59 | 60 | The input data is shipped by the application. User-submitted 61 | code for processing the data is evaluated in 62 | a Plugin created with 63 | the Jailed 64 | library. The code is executed in a sandboxed iframe in order 65 | to prevent accessing the main application. 66 |
67 | 68 | 69 |
70 |
71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /demos/web/process/plugin.js: -------------------------------------------------------------------------------- 1 | application.setInterface({ 2 | process : function(input, code, cb) { 3 | var result = { 4 | output: null, 5 | error: null 6 | }; 7 | 8 | try { 9 | eval('method = '+code); 10 | eval('data = '+ input); 11 | result.output = method(data); 12 | } catch(e) { 13 | result.error = e.message; 14 | } 15 | 16 | cb(result); 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /demos/web/process/refresh.svg: -------------------------------------------------------------------------------- 1 | 2 | image/svg+xml -------------------------------------------------------------------------------- /demos/web/process/scroll.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asvd/jailed/4ef4e172e59f32df0fc2e5b0046b1734b6a58740/demos/web/process/scroll.png -------------------------------------------------------------------------------- /demos/web/process/style.css: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | background-color: #112233; 4 | color: #aabbcc; 5 | font-family: sans; 6 | font-size: 14px; 7 | line-height: 21px; 8 | cursor: default; 9 | } 10 | 11 | 12 | a { 13 | color: #48D7E9; 14 | text-shadow: 0px 0px 3px #133153; 15 | } 16 | 17 | a:hover { 18 | color: #72EDFD; 19 | text-shadow: 0px 0px 8px #3F638B; 20 | } 21 | 22 | .hpad { 23 | height : 15px; 24 | } 25 | 26 | .rel { 27 | position: relative; 28 | padding: 0px; 29 | } 30 | 31 | .centered { 32 | display: block; 33 | width: 900px; 34 | height: 400px; 35 | margin-left: auto; 36 | margin-right: auto; 37 | padding: 0px; 38 | } 39 | 40 | .annotation { 41 | position: absolute; 42 | left: 545px; 43 | top: 25px; 44 | width: 200px; 45 | height: 400px; 46 | } 47 | 48 | .annotation h3 { 49 | margin-top: 0px; 50 | font-variant: small-caps; 51 | } 52 | 53 | .label { 54 | font-variant: small-caps; 55 | text-align: right; 56 | width: 150px; 57 | } 58 | 59 | .link_block { 60 | position : relative; 61 | } 62 | 63 | .link_label { 64 | font-variant: small-caps; 65 | font-size : 12px; 66 | text-align: right; 67 | height : 20px; 68 | width: 150px; 69 | } 70 | 71 | .link { 72 | position:absolute; 73 | width: 500px; 74 | height : 20px; 75 | top : 0px; 76 | left: 160px; 77 | font-size: 11px; 78 | } 79 | 80 | 81 | .main { 82 | position: absolute; 83 | padding-top: 6px; 84 | left: 0; 85 | top: 25px; 86 | width : 550px; 87 | height : 400px; 88 | } 89 | 90 | 91 | .input_block { 92 | position:relative; 93 | width: 500px; 94 | height : 30px; 95 | } 96 | 97 | .code_block { 98 | position:relative; 99 | width: 500px; 100 | height : 200px; 101 | } 102 | 103 | .process_block { 104 | position:relative; 105 | margin-top: 10px; 106 | margin-bottom: 10px; 107 | width: 500px; 108 | height : 30px; 109 | } 110 | 111 | .output_block { 112 | position:relative; 113 | width: 500px; 114 | height : 30px; 115 | } 116 | 117 | 118 | .code { 119 | position: absolute; 120 | padding: 5px; 121 | top: 0px; 122 | left: 160px; 123 | height: 200px; 124 | width: 360px; 125 | background-color: #051627; 126 | background-image: url('bg.png'); 127 | border: 1px solid #365666; 128 | color: #B4EEF5; 129 | font-family: monospace; 130 | font-size: 12px; 131 | line-height: 18px; 132 | overflow: auto; 133 | white-space: pre; 134 | -moz-appearance:none; 135 | outline:0px none transparent; 136 | border : 1px solid #365666; 137 | } 138 | 139 | .code:focus { 140 | outline: 0; 141 | } 142 | 143 | .data { 144 | position: absolute; 145 | height: 22px; 146 | top: 0px; 147 | left: 160px; 148 | color: #B4EEF5; 149 | font-family: monospace; 150 | background-color: #06192B; 151 | padding-left: 5px; 152 | } 153 | 154 | #input_data { 155 | width: 330px; 156 | } 157 | 158 | #output_data { 159 | width: 360px; 160 | } 161 | 162 | .button { 163 | font-variant: small-caps; 164 | text-align: center; 165 | } 166 | 167 | .button:hover { 168 | background-color: #172839 !important; 169 | } 170 | 171 | .button:active { 172 | background-color: #0B1C2E !important; 173 | } 174 | 175 | #button_regen_input { 176 | position: absolute; 177 | top: 0px; 178 | left: 500px; 179 | width : 22px; 180 | height: 22px; 181 | background-image: url('refresh.svg'); 182 | background-repeat: no-repeat; 183 | } 184 | 185 | #button_process { 186 | position: absolute; 187 | top: 5px; 188 | left: 286px; 189 | width : 130px; 190 | height: 22px; 191 | border : 1px solid #365666; 192 | background-color: #06192B; 193 | } 194 | 195 | .error { 196 | color : #E67C60; 197 | } 198 | 199 | .loading { 200 | padding-top: 3px; 201 | opacity: .7; 202 | } 203 | 204 | 205 | -------------------------------------------------------------------------------- /lib/_JailedSite.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Contains the JailedSite object used both by the application 4 | * site, and by each plugin 5 | */ 6 | 7 | (function(){ 8 | 9 | /** 10 | * JailedSite object represents a single site in the 11 | * communication protocol between the application and the plugin 12 | * 13 | * @param {Object} connection a special object allowing to send 14 | * and receive messages from the opposite site (basically it 15 | * should only provide send() and onMessage() methods) 16 | */ 17 | JailedSite = function(connection) { 18 | this._interface = {}; 19 | this._remote = null; 20 | this._remoteUpdateHandler = function(){}; 21 | this._getInterfaceHandler = function(){}; 22 | this._interfaceSetAsRemoteHandler = function(){}; 23 | this._disconnectHandler = function(){}; 24 | this._store = new ReferenceStore; 25 | 26 | var me = this; 27 | this._connection = connection; 28 | this._connection.onMessage( 29 | function(data){ me._processMessage(data); } 30 | ); 31 | 32 | this._connection.onDisconnect( 33 | function(m){ 34 | me._disconnectHandler(m); 35 | } 36 | ); 37 | } 38 | 39 | 40 | /** 41 | * Set a handler to be called when the remote site updates its 42 | * interface 43 | * 44 | * @param {Function} handler 45 | */ 46 | JailedSite.prototype.onRemoteUpdate = function(handler) { 47 | this._remoteUpdateHandler = handler; 48 | } 49 | 50 | 51 | /** 52 | * Set a handler to be called when received a responce from the 53 | * remote site reporting that the previously provided interface 54 | * has been succesfully set as remote for that site 55 | * 56 | * @param {Function} handler 57 | */ 58 | JailedSite.prototype.onInterfaceSetAsRemote = function(handler) { 59 | this._interfaceSetAsRemoteHandler = handler; 60 | } 61 | 62 | 63 | /** 64 | * Set a handler to be called when the remote site requests to 65 | * (re)send the interface. Used to detect an initialzation 66 | * completion without sending additional request, since in fact 67 | * 'getInterface' request is only sent by application at the last 68 | * step of the plugin initialization 69 | * 70 | * @param {Function} handler 71 | */ 72 | JailedSite.prototype.onGetInterface = function(handler) { 73 | this._getInterfaceHandler = handler; 74 | } 75 | 76 | 77 | /** 78 | * @returns {Object} set of remote interface methods 79 | */ 80 | JailedSite.prototype.getRemote = function() { 81 | return this._remote; 82 | } 83 | 84 | 85 | /** 86 | * Sets the interface of this site making it available to the 87 | * remote site by sending a message with a set of methods names 88 | * 89 | * @param {Object} _interface to set 90 | */ 91 | JailedSite.prototype.setInterface = function(_interface) { 92 | this._interface = _interface; 93 | this._sendInterface(); 94 | } 95 | 96 | 97 | /** 98 | * Sends the actual interface to the remote site upon it was 99 | * updated or by a special request of the remote site 100 | */ 101 | JailedSite.prototype._sendInterface = function() { 102 | var names = []; 103 | for (var name in this._interface) { 104 | if (this._interface.hasOwnProperty(name)) { 105 | names.push(name); 106 | } 107 | } 108 | 109 | this._connection.send({type:'setInterface', api: names}); 110 | } 111 | 112 | 113 | /** 114 | * Handles a message from the remote site 115 | */ 116 | JailedSite.prototype._processMessage = function(data) { 117 | switch(data.type) { 118 | case 'method': 119 | var method = this._interface[data.name]; 120 | var args = this._unwrap(data.args); 121 | method.apply(null, args); 122 | break; 123 | case 'callback': 124 | var method = this._store.fetch(data.id)[data.num]; 125 | var args = this._unwrap(data.args); 126 | method.apply(null, args); 127 | break; 128 | case 'setInterface': 129 | this._setRemote(data.api); 130 | break; 131 | case 'getInterface': 132 | this._sendInterface(); 133 | this._getInterfaceHandler(); 134 | break; 135 | case 'interfaceSetAsRemote': 136 | this._interfaceSetAsRemoteHandler(); 137 | break; 138 | case 'disconnect': 139 | this._disconnectHandler(); 140 | this._connection.disconnect(); 141 | break; 142 | } 143 | } 144 | 145 | 146 | /** 147 | * Sends a requests to the remote site asking it to provide its 148 | * current interface 149 | */ 150 | JailedSite.prototype.requestRemote = function() { 151 | this._connection.send({type:'getInterface'}); 152 | } 153 | 154 | 155 | /** 156 | * Sets the new remote interface provided by the other site 157 | * 158 | * @param {Array} names list of function names 159 | */ 160 | JailedSite.prototype._setRemote = function(names) { 161 | this._remote = {}; 162 | var i, name; 163 | for (i = 0; i < names.length; i++) { 164 | name = names[i]; 165 | this._remote[name] = this._genRemoteMethod(name); 166 | } 167 | 168 | this._remoteUpdateHandler(); 169 | this._reportRemoteSet(); 170 | } 171 | 172 | 173 | /** 174 | * Generates the wrapped function corresponding to a single remote 175 | * method. When the generated function is called, it will send the 176 | * corresponding message to the remote site asking it to execute 177 | * the particular method of its interface 178 | * 179 | * @param {String} name of the remote method 180 | * 181 | * @returns {Function} wrapped remote method 182 | */ 183 | JailedSite.prototype._genRemoteMethod = function(name) { 184 | var me = this; 185 | var remoteMethod = function() { 186 | me._connection.send({ 187 | type: 'method', 188 | name: name, 189 | args: me._wrap(arguments) 190 | }); 191 | }; 192 | 193 | return remoteMethod; 194 | } 195 | 196 | 197 | /** 198 | * Sends a responce reporting that interface just provided by the 199 | * remote site was sucessfully set by this site as remote 200 | */ 201 | JailedSite.prototype._reportRemoteSet = function() { 202 | this._connection.send({type:'interfaceSetAsRemote'}); 203 | } 204 | 205 | 206 | /** 207 | * Prepares the provided set of remote method arguments for 208 | * sending to the remote site, replaces all the callbacks with 209 | * identifiers 210 | * 211 | * @param {Array} args to wrap 212 | * 213 | * @returns {Array} wrapped arguments 214 | */ 215 | JailedSite.prototype._wrap = function(args) { 216 | var wrapped = []; 217 | var callbacks = {}; 218 | var callbacksPresent = false; 219 | for (var i = 0; i < args.length; i++) { 220 | if (typeof args[i] == 'function') { 221 | callbacks[i] = args[i]; 222 | wrapped[i] = {type: 'callback', num : i}; 223 | callbacksPresent = true; 224 | } else { 225 | wrapped[i] = {type: 'argument', value : args[i]}; 226 | } 227 | } 228 | 229 | var result = {args: wrapped}; 230 | 231 | if (callbacksPresent) { 232 | result.callbackId = this._store.put(callbacks); 233 | } 234 | 235 | return result; 236 | } 237 | 238 | 239 | /** 240 | * Unwraps the set of arguments delivered from the remote site, 241 | * replaces all callback identifiers with a function which will 242 | * initiate sending that callback identifier back to other site 243 | * 244 | * @param {Object} args to unwrap 245 | * 246 | * @returns {Array} unwrapped args 247 | */ 248 | JailedSite.prototype._unwrap = function(args) { 249 | var called = false; 250 | 251 | // wraps each callback so that the only one could be called 252 | var once = function(cb) { 253 | return function() { 254 | if (!called) { 255 | called = true; 256 | cb.apply(this, arguments); 257 | } else { 258 | var msg = 259 | 'A callback from this set has already been executed'; 260 | throw new Error(msg); 261 | } 262 | }; 263 | } 264 | 265 | var result = []; 266 | var i, arg, cb, me = this; 267 | for (i = 0; i < args.args.length; i++) { 268 | arg = args.args[i]; 269 | if (arg.type == 'argument') { 270 | result.push(arg.value); 271 | } else { 272 | cb = once( 273 | this._genRemoteCallback(args.callbackId, i) 274 | ); 275 | result.push(cb); 276 | } 277 | } 278 | 279 | return result; 280 | } 281 | 282 | 283 | /** 284 | * Generates the wrapped function corresponding to a single remote 285 | * callback. When the generated function is called, it will send 286 | * the corresponding message to the remote site asking it to 287 | * execute the particular callback previously saved during a call 288 | * by the remote site a method from the interface of this site 289 | * 290 | * @param {Number} id of the remote callback to execute 291 | * @param {Number} argNum argument index of the callback 292 | * 293 | * @returns {Function} wrapped remote callback 294 | */ 295 | JailedSite.prototype._genRemoteCallback = function(id, argNum) { 296 | var me = this; 297 | var remoteCallback = function() { 298 | me._connection.send({ 299 | type : 'callback', 300 | id : id, 301 | num : argNum, 302 | args : me._wrap(arguments) 303 | }); 304 | }; 305 | 306 | return remoteCallback; 307 | } 308 | 309 | 310 | /** 311 | * Sends the notification message and breaks the connection 312 | */ 313 | JailedSite.prototype.disconnect = function() { 314 | this._connection.send({type: 'disconnect'}); 315 | this._connection.disconnect(); 316 | } 317 | 318 | 319 | /** 320 | * Set a handler to be called when received a disconnect message 321 | * from the remote site 322 | * 323 | * @param {Function} handler 324 | */ 325 | JailedSite.prototype.onDisconnect = function(handler) { 326 | this._disconnectHandler = handler; 327 | } 328 | 329 | 330 | 331 | 332 | /** 333 | * ReferenceStore is a special object which stores other objects 334 | * and provides the references (number) instead. This reference 335 | * may then be sent over a json-based communication channel (IPC 336 | * to another Node.js process or a message to the Worker). Other 337 | * site may then provide the reference in the responce message 338 | * implying the given object should be activated. 339 | * 340 | * Primary usage for the ReferenceStore is a storage for the 341 | * callbacks, which therefore makes it possible to initiate a 342 | * callback execution by the opposite site (which normally cannot 343 | * directly execute functions over the communication channel). 344 | * 345 | * Each stored object can only be fetched once and is not 346 | * available for the second time. Each stored object must be 347 | * fetched, since otherwise it will remain stored forever and 348 | * consume memory. 349 | * 350 | * Stored object indeces are simply the numbers, which are however 351 | * released along with the objects, and are later reused again (in 352 | * order to postpone the overflow, which should not likely happen, 353 | * but anyway). 354 | */ 355 | var ReferenceStore = function() { 356 | this._store = {}; // stored object 357 | this._indices = [0]; // smallest available indices 358 | } 359 | 360 | 361 | /** 362 | * @function _genId() generates the new reference id 363 | * 364 | * @returns {Number} smallest available id and reserves it 365 | */ 366 | ReferenceStore.prototype._genId = function() { 367 | var id; 368 | if (this._indices.length == 1) { 369 | id = this._indices[0]++; 370 | } else { 371 | id = this._indices.shift(); 372 | } 373 | 374 | return id; 375 | } 376 | 377 | 378 | /** 379 | * Releases the given reference id so that it will be available by 380 | * another object stored 381 | * 382 | * @param {Number} id to release 383 | */ 384 | ReferenceStore.prototype._releaseId = function(id) { 385 | for (var i = 0; i < this._indices.length; i++) { 386 | if (id < this._indices[i]) { 387 | this._indices.splice(i, 0, id); 388 | break; 389 | } 390 | } 391 | 392 | // cleaning-up the sequence tail 393 | for (i = this._indices.length-1; i >= 0; i--) { 394 | if (this._indices[i]-1 == this._indices[i-1]) { 395 | this._indices.pop(); 396 | } else { 397 | break; 398 | } 399 | } 400 | } 401 | 402 | 403 | /** 404 | * Stores the given object and returns the refernce id instead 405 | * 406 | * @param {Object} obj to store 407 | * 408 | * @returns {Number} reference id of the stored object 409 | */ 410 | ReferenceStore.prototype.put = function(obj) { 411 | var id = this._genId(); 412 | this._store[id] = obj; 413 | return id; 414 | } 415 | 416 | 417 | /** 418 | * Retrieves previously stored object and releases its reference 419 | * 420 | * @param {Number} id of an object to retrieve 421 | */ 422 | ReferenceStore.prototype.fetch = function(id) { 423 | var obj = this._store[id]; 424 | this._store[id] = null; 425 | delete this._store[id]; 426 | this._releaseId(id); 427 | return obj; 428 | } 429 | 430 | 431 | })(); 432 | 433 | -------------------------------------------------------------------------------- /lib/_frame.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /lib/_frame.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Contains the code executed in the sandboxed frame under web-browser 4 | * 5 | * Tries to create a Web-Worker inside the frame and set up the 6 | * communication between the worker and the parent window. Some 7 | * browsers restrict creating a worker inside a sandboxed iframe - if 8 | * this happens, the plugin initialized right inside the frame (in the 9 | * same thread) 10 | */ 11 | 12 | 13 | var scripts = document.getElementsByTagName('script'); 14 | var thisScript = scripts[scripts.length-1]; 15 | var parentNode = thisScript.parentNode; 16 | var __jailed__path__ = thisScript.src 17 | .split('?')[0] 18 | .split('/') 19 | .slice(0, -1) 20 | .join('/')+'/'; 21 | 22 | 23 | /** 24 | * Initializes the plugin inside a webworker. May throw an exception 25 | * in case this was not permitted by the browser. 26 | */ 27 | var initWebworkerPlugin = function() { 28 | // creating worker as a blob enables import of local files 29 | var blobCode = [ 30 | ' self.addEventListener("message", function(m){ ', 31 | ' if (m.data.type == "initImport") { ', 32 | ' importScripts(m.data.url); ', 33 | ' self.postMessage({ ', 34 | ' type : "initialized", ', 35 | ' dedicatedThread : true ', 36 | ' }); ', 37 | ' } ', 38 | ' }); ' 39 | ].join('\n'); 40 | 41 | var blobUrl = window.URL.createObjectURL( 42 | new Blob([blobCode]) 43 | ); 44 | 45 | var worker = new Worker(blobUrl); 46 | 47 | // telling worker to load _pluginWebWorker.js (see blob code above) 48 | worker.postMessage({ 49 | type: 'initImport', 50 | url: __jailed__path__ + '_pluginWebWorker.js' 51 | }); 52 | 53 | 54 | // mixed content warning in Chrome silently skips worker 55 | // initialization without exception, handling this with timeout 56 | var fallbackTimeout = setTimeout(function() { 57 | worker.terminate(); 58 | initIframePlugin(); 59 | }, 300); 60 | 61 | // forwarding messages between the worker and parent window 62 | worker.addEventListener('message', function(m) { 63 | if (m.data.type == 'initialized') { 64 | clearTimeout(fallbackTimeout); 65 | } 66 | 67 | parent.postMessage(m.data, '*'); 68 | }); 69 | 70 | window.addEventListener('message', function(m) { 71 | worker.postMessage(m.data); 72 | }); 73 | } 74 | 75 | 76 | /** 77 | * Creates plugin right in this iframe 78 | */ 79 | var initIframePlugin = function() { 80 | // loads additional script into the frame 81 | window.loadScript = function(path, sCb, fCb) { 82 | var script = document.createElement('script'); 83 | script.src = path; 84 | 85 | var clear = function() { 86 | script.onload = null; 87 | script.onerror = null; 88 | script.onreadystatechange = null; 89 | script.parentNode.removeChild(script); 90 | currentErrorHandler = function(){}; 91 | } 92 | 93 | var success = function() { 94 | clear(); 95 | sCb(); 96 | } 97 | 98 | var failure = function() { 99 | clear(); 100 | fCb(); 101 | } 102 | 103 | currentErrorHandler = failure; 104 | 105 | script.onerror = failure; 106 | script.onload = success; 107 | script.onreadystatechange = function() { 108 | var state = script.readyState; 109 | if (state==='loaded' || state==='complete') { 110 | success(); 111 | } 112 | } 113 | 114 | parentNode.appendChild(script); 115 | } 116 | 117 | 118 | // handles script loading error 119 | // (assuming scripts are loaded one by one in the iframe) 120 | var currentErrorHandler = function(){}; 121 | window.addEventListener('error', function(message) { 122 | currentErrorHandler(); 123 | }); 124 | 125 | 126 | window.loadScript( 127 | __jailed__path__ + '_pluginWebIframe.js', 128 | function(){}, function(){} 129 | ); 130 | } 131 | 132 | 133 | 134 | try { 135 | initWebworkerPlugin(); 136 | } catch(e) { 137 | initIframePlugin(); 138 | } 139 | 140 | -------------------------------------------------------------------------------- /lib/_pluginCore.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Core plugin script loaded into the plugin process/thread. 4 | * 5 | * Initializes the plugin-site API global methods. 6 | */ 7 | 8 | (function(){ 9 | 10 | // localize 11 | var site = new JailedSite(connection); 12 | delete JailedSite; 13 | delete connection; 14 | 15 | site.onGetInterface(function(){ 16 | launchConnected(); 17 | }); 18 | 19 | site.onRemoteUpdate(function(){ 20 | application.remote = site.getRemote(); 21 | }); 22 | 23 | 24 | 25 | /** 26 | * Simplified clone of Whenable instance (the object can not be 27 | * placed into a shared script, because the main library needs it 28 | * before the additional scripts may load) 29 | */ 30 | var connected = false; 31 | var connectedHandlers = []; 32 | 33 | var launchConnected = function() { 34 | if (!connected) { 35 | connected = true; 36 | 37 | var handler; 38 | while(handler = connectedHandlers.pop()) { 39 | handler(); 40 | } 41 | } 42 | } 43 | 44 | var checkHandler = function(handler){ 45 | var type = typeof handler; 46 | if (type != 'function') { 47 | var msg = 48 | 'A function may only be subsribed to the event, ' 49 | + type 50 | + ' was provided instead' 51 | throw new Error(msg); 52 | } 53 | 54 | return handler; 55 | } 56 | 57 | 58 | /** 59 | * Sets a function executed after the connection to the 60 | * application is estaplished, and the initial interface-exchange 61 | * messaging is completed 62 | * 63 | * @param {Function} handler to be called upon initialization 64 | */ 65 | application.whenConnected = function(handler) { 66 | handler = checkHandler(handler); 67 | if (connected) { 68 | handler(); 69 | } else { 70 | connectedHandlers.push(handler); 71 | } 72 | } 73 | 74 | 75 | /** 76 | * Sets the plugin interface available to the application 77 | * 78 | * @param {Object} _interface to set 79 | */ 80 | application.setInterface = function(_interface) { 81 | site.setInterface(_interface); 82 | } 83 | 84 | 85 | 86 | /** 87 | * Disconnects the plugin from the application (sending 88 | * notification message) and destroys itself 89 | */ 90 | application.disconnect = function(_interface) { 91 | site.disconnect(); 92 | } 93 | 94 | })(); 95 | 96 | -------------------------------------------------------------------------------- /lib/_pluginNode.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Contains the routines loaded by the plugin process under Node.js 4 | * 5 | * Initializes the Node.js environment version of the 6 | * platform-dependent connection object for the plugin site 7 | */ 8 | 9 | application = {}; 10 | connection = {}; 11 | 12 | 13 | /** 14 | * Prints error message and its stack 15 | * 16 | * @param {Object} msg stack provided by error.stack or a message 17 | */ 18 | var printError = function(msg) { 19 | console.error(); 20 | console.error(msg); 21 | } 22 | 23 | 24 | /** 25 | * Event lisener for the plugin message 26 | */ 27 | process.on('message', function(m) { 28 | switch(m.type){ 29 | case 'import': 30 | importScript(m.url); 31 | break; 32 | case 'importJailed': 33 | importScriptJailed(m.url); 34 | break; 35 | case 'execute': 36 | execute(m.code); 37 | break; 38 | case 'message': 39 | // unhandled exception would break the IPC channel 40 | try { 41 | conn._messageHandler(m.data); 42 | } catch(e) { 43 | printError(e.stack); 44 | } 45 | break; 46 | } 47 | }); 48 | 49 | 50 | /** 51 | * Checks if the given path is remote 52 | * 53 | * @param {String} path to check 54 | * @returns {Boolean} true if path is remote 55 | */ 56 | var isRemote = function(path) { 57 | return (path.substr(0,7).toLowerCase() == 'http://' || 58 | path.substr(0,8).toLowerCase() == 'https://'); 59 | } 60 | 61 | 62 | /** 63 | * Loads and executes the JavaScript file with the given url 64 | * 65 | * @param {String} url of the script to load 66 | */ 67 | var importScript = function(url) { 68 | var sCb = function() { 69 | process.send({type: 'importSuccess', url: url}); 70 | } 71 | 72 | var fCb = function() { 73 | process.send({type: 'importFailure', url: url}); 74 | } 75 | 76 | var run = function(code) { 77 | executeNormal(code, url, sCb, fCb); 78 | } 79 | 80 | if (isRemote(url)) { 81 | loadRemote(url, run, fCb); 82 | } else { 83 | try { 84 | run(loadLocal(url)); 85 | } catch(e) { 86 | printError(e.stack); 87 | fCb(); 88 | } 89 | } 90 | 91 | } 92 | 93 | 94 | /** 95 | * Loads and executes the JavaScript file with the given url in a 96 | * jailed environment 97 | * 98 | * @param {String} url of the script to load 99 | */ 100 | var importScriptJailed = function(url) { 101 | var sCb = function() { 102 | process.send({type: 'importSuccess', url: url}); 103 | } 104 | 105 | var fCb = function() { 106 | process.send({type: 'importFailure', url: url}); 107 | } 108 | 109 | var run = function(code) { 110 | executeJailed(code, url, sCb, fCb); 111 | } 112 | 113 | if (isRemote(url)) { 114 | loadRemote(url, run, fCb); 115 | } else { 116 | try { 117 | run(loadLocal(url)); 118 | } catch (e) { 119 | printError(e.stack); 120 | fCb(); 121 | } 122 | 123 | } 124 | 125 | } 126 | 127 | 128 | /** 129 | * Executes the given code in the jailed environment, sends the 130 | * corresponding message to the application site when succeeded/failed 131 | * 132 | * @param {String} code to execute 133 | */ 134 | var execute = function(code) { 135 | var sCb = function() { 136 | process.send({type: 'executeSuccess'}); 137 | } 138 | 139 | var fCb = function() { 140 | process.send({type: 'executeFailure'}); 141 | } 142 | 143 | executeJailed(code, 'DYNAMIC PLUGIN', sCb, fCb); 144 | } 145 | 146 | 147 | /** 148 | * Executes the given code in the current environment / scope, runs 149 | * the corresponding callback when done 150 | * 151 | * @param {String} code to execute 152 | * @param {String} url of the script (for displaying the stack) 153 | * @param {Function} sCb 154 | * @param {Function} fCb 155 | */ 156 | var executeNormal = function(code, url, sCb, fCb) { 157 | var err = null; 158 | try { 159 | require('vm').runInThisContext(code, url); 160 | sCb(); 161 | } catch (e) { 162 | printError(e.stack); 163 | fCb(); 164 | } 165 | } 166 | 167 | 168 | /** 169 | * Executes the given code in a jailed environment, runs the 170 | * corresponding callback when done 171 | * 172 | * @param {String} code to execute 173 | * @param {String} url of the script (for displaying the stack) 174 | * @param {Function} sCb 175 | * @param {Function} fCb 176 | */ 177 | var executeJailed = function(code, url, sCb, fCb) { 178 | var vm = require('vm'); 179 | var sandbox = {}; 180 | var expose = [ 181 | 'application', 182 | 'setTimeout', 183 | 'setInterval', 184 | 'clearTimeout', 185 | 'clearInterval' 186 | ]; 187 | 188 | for (var i = 0; i < expose.length; i++) { 189 | sandbox[expose[i]] = global[expose[i]]; 190 | } 191 | 192 | code = '"use strict";\n'+code; 193 | try { 194 | vm.runInNewContext(code, vm.createContext(sandbox), url); 195 | sCb(); 196 | } catch (e) { 197 | printError(e.stack); 198 | fCb(); 199 | } 200 | } 201 | 202 | 203 | /** 204 | * Loads local file and 205 | * 206 | * @param {String} path of the file to read 207 | * 208 | * @returns {String} file contents 209 | */ 210 | var loadLocal = function(path) { 211 | return require("fs").readFileSync(path).toString(); 212 | } 213 | 214 | 215 | /** 216 | * Downloads the script by remote url and provides its content as a 217 | * string to the callback 218 | * 219 | * @param {String} url of the remote module to load 220 | * @param {Function} sCb success callback 221 | * @param {Function} fCb failure callback 222 | */ 223 | var loadRemote = function(url, sCb, fCb) { 224 | var receive = function(res) { 225 | if (res.statusCode != 200) { 226 | var msg = 'Failed to load ' + url + '\n' + 227 | 'HTTP responce status code: ' + res.statusCode; 228 | printError(msg); 229 | fCb(); 230 | } else { 231 | var content = ''; 232 | res.on('end', function(){ sCb(content); }); 233 | res.on( 234 | 'readable', 235 | function() { 236 | var chunk = res.read(); 237 | content += chunk.toString(); 238 | } 239 | ); 240 | } 241 | } 242 | 243 | try { 244 | require('http').get(url, receive).on('error', fCb); 245 | } catch (e) { 246 | printError(e.stack); 247 | fCb(); 248 | } 249 | } 250 | 251 | 252 | /** 253 | * Connection object provided to the SandboxedSite constructor, plugin 254 | * site implementation for the Node.js environment 255 | */ 256 | var conn = { 257 | disconnect: function(){ process.exit(); }, 258 | send: function(data) { 259 | process.send({type: 'message', data: data}); 260 | }, 261 | onMessage: function(h){ conn._messageHandler = h; }, 262 | _messageHandler: function(){}, 263 | onDisconnect: function() {} 264 | }; 265 | 266 | connection = conn; 267 | 268 | -------------------------------------------------------------------------------- /lib/_pluginWebIframe.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Contains the routines loaded by the plugin iframe under web-browser 4 | * in case when worker failed to initialize 5 | * 6 | * Initializes the web environment version of the platform-dependent 7 | * connection object for the plugin site 8 | */ 9 | 10 | 11 | window.application = {}; 12 | window.connection = {}; 13 | 14 | 15 | // event listener for the plugin message 16 | window.addEventListener('message', function(e) { 17 | var m = e.data.data; 18 | switch (m.type) { 19 | case 'import': 20 | case 'importJailed': // already jailed in the iframe 21 | importScript(m.url); 22 | break; 23 | case 'execute': 24 | execute(m.code); 25 | break; 26 | case 'message': 27 | conn._messageHandler(m.data); 28 | break; 29 | } 30 | }); 31 | 32 | 33 | // loads and executes the javascript file with the given url 34 | var importScript = function(url) { 35 | var success = function() { 36 | parent.postMessage({ 37 | type : 'importSuccess', 38 | url : url 39 | }, '*'); 40 | } 41 | 42 | var failure = function() { 43 | parent.postMessage({ 44 | type : 'importFailure', 45 | url : url 46 | }, '*'); 47 | } 48 | 49 | var error = null; 50 | try { 51 | window.loadScript(url, success, failure); 52 | } catch (e) { 53 | error = e; 54 | } 55 | 56 | if (error) { 57 | throw error; 58 | failure(); 59 | } 60 | } 61 | 62 | 63 | // evaluates the provided string 64 | var execute = function(code) { 65 | try { 66 | eval(code); 67 | } catch (e) { 68 | parent.postMessage({type : 'executeFailure'}, '*'); 69 | throw e; 70 | } 71 | 72 | parent.postMessage({type : 'executeSuccess'}, '*'); 73 | } 74 | 75 | 76 | // connection object for the JailedSite constructor 77 | var conn = { 78 | disconnect : function() {}, 79 | send: function(data) { 80 | parent.postMessage({type: 'message', data: data}, '*'); 81 | }, 82 | onMessage: function(h){ conn._messageHandler = h }, 83 | _messageHandler: function(){}, 84 | onDisconnect: function(){} 85 | }; 86 | 87 | window.connection = conn; 88 | 89 | parent.postMessage({ 90 | type : 'initialized', 91 | dedicatedThread : false 92 | }, '*'); 93 | 94 | -------------------------------------------------------------------------------- /lib/_pluginWebWorker.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Contains the routines loaded by the plugin Worker under web-browser. 4 | * 5 | * Initializes the web environment version of the platform-dependent 6 | * connection object for the plugin site 7 | */ 8 | 9 | self.application = {}; 10 | self.connection = {}; 11 | 12 | 13 | (function(){ 14 | 15 | /** 16 | * Event lisener for the plugin message 17 | */ 18 | self.addEventListener('message', function(e){ 19 | var m = e.data.data; 20 | switch (m.type) { 21 | case 'import': 22 | case 'importJailed': // already jailed in the iframe 23 | importScript(m.url); 24 | break; 25 | case 'execute': 26 | execute(m.code); 27 | break; 28 | case 'message': 29 | conn._messageHandler(m.data); 30 | break; 31 | } 32 | }); 33 | 34 | 35 | /** 36 | * Loads and executes the JavaScript file with the given url 37 | * 38 | * @param {String} url to load 39 | */ 40 | var importScript = function(url) { 41 | var error = null; 42 | 43 | // importScripts does not throw an exception in old webkits 44 | // (Opera 15.0), but we can determine a failure by the 45 | // returned value which must be undefined in case of success 46 | var returned = true; 47 | try { 48 | returned = importScripts(url); 49 | } catch (e) { 50 | error = e; 51 | } 52 | 53 | if (error || typeof returned != 'undefined') { 54 | self.postMessage({type: 'importFailure', url: url}); 55 | if (error) { 56 | throw error; 57 | } 58 | } else { 59 | self.postMessage({type: 'importSuccess', url: url}); 60 | } 61 | 62 | } 63 | 64 | 65 | /** 66 | * Executes the given code in a jailed environment. For web 67 | * implementation, we're already jailed in the iframe and the 68 | * worker, so simply eval() 69 | * 70 | * @param {String} code code to execute 71 | */ 72 | var execute = function(code) { 73 | try { 74 | eval(code); 75 | } catch (e) { 76 | self.postMessage({type: 'executeFailure'}); 77 | throw e; 78 | } 79 | 80 | self.postMessage({type: 'executeSuccess'}); 81 | } 82 | 83 | 84 | /** 85 | * Connection object provided to the JailedSite constructor, 86 | * plugin site implementation for the web-based environment. 87 | * Global will be then cleared to prevent exposure into the 88 | * Worker, so we put this local connection object into a closure 89 | */ 90 | var conn = { 91 | disconnect: function(){ self.close(); }, 92 | send: function(data) { 93 | self.postMessage({type: 'message', data: data}); 94 | }, 95 | onMessage: function(h){ conn._messageHandler = h; }, 96 | _messageHandler: function(){}, 97 | onDisconnect: function() {} 98 | }; 99 | 100 | connection = conn; 101 | 102 | })(); 103 | 104 | -------------------------------------------------------------------------------- /lib/jailed.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Jailed - safe yet flexible sandbox 3 | * @version 0.3.1 4 | * 5 | * @license MIT, see http://github.com/asvd/jailed 6 | * Copyright (c) 2014 asvd 7 | * 8 | * Main library script, the only one to be loaded by a developer into 9 | * the application. Other scrips shipped along will be loaded by the 10 | * library either here (application site), or into the plugin site 11 | * (Worker/child process): 12 | * 13 | * _JailedSite.js loaded into both applicaiton and plugin sites 14 | * _frame.html sandboxed frame (web) 15 | * _frame.js sandboxed frame code (web) 16 | * _pluginWebWorker.js platform-dependent plugin routines (web / worker) 17 | * _pluginWebIframe.js platform-dependent plugin routines (web / iframe) 18 | * _pluginNode.js platform-dependent plugin routines (Node.js) 19 | * _pluginCore.js common plugin site protocol implementation 20 | */ 21 | 22 | 23 | var __jailed__path__; 24 | var __is__node__ = ((typeof process !== 'undefined') && 25 | (!process.browser) && 26 | (process.release.name.search(/node|io.js/) !== -1)); 27 | if (__is__node__) { 28 | // Node.js 29 | __jailed__path__ = __dirname + '/'; 30 | } else { 31 | // web 32 | var scripts = document.getElementsByTagName('script'); 33 | __jailed__path__ = scripts[scripts.length-1].src 34 | .split('?')[0] 35 | .split('/') 36 | .slice(0, -1) 37 | .join('/')+'/'; 38 | } 39 | 40 | 41 | (function (root, factory) { 42 | if (typeof define === 'function' && define.amd) { 43 | define(['exports'], factory); 44 | } else if (typeof exports !== 'undefined') { 45 | factory(exports); 46 | } else { 47 | factory((root.jailed = {})); 48 | } 49 | }(this, function (exports) { 50 | /** 51 | * A special kind of event: 52 | * - which can only be emitted once; 53 | * - executes a set of subscribed handlers upon emission; 54 | * - if a handler is subscribed after the event was emitted, it 55 | * will be invoked immideately. 56 | * 57 | * Used for the events which only happen once (or do not happen at 58 | * all) during a single plugin lifecycle - connect, disconnect and 59 | * connection failure 60 | */ 61 | var Whenable = function() { 62 | this._emitted = false; 63 | this._handlers = []; 64 | } 65 | 66 | 67 | /** 68 | * Emits the Whenable event, calls all the handlers already 69 | * subscribed, switches the object to the 'emitted' state (when 70 | * all future subscibed listeners will be immideately issued 71 | * instead of being stored) 72 | */ 73 | Whenable.prototype.emit = function(){ 74 | if (!this._emitted) { 75 | this._emitted = true; 76 | 77 | var handler; 78 | while(handler = this._handlers.pop()) { 79 | setTimeout(handler,0); 80 | } 81 | } 82 | } 83 | 84 | 85 | /** 86 | * Saves the provided function as a handler for the Whenable 87 | * event. This handler will then be called upon the event emission 88 | * (if it has not been emitted yet), or will be scheduled for 89 | * immediate issue (if the event has already been emmitted before) 90 | * 91 | * @param {Function} handler to subscribe for the event 92 | */ 93 | Whenable.prototype.whenEmitted = function(handler){ 94 | handler = this._checkHandler(handler); 95 | if (this._emitted) { 96 | setTimeout(handler, 0); 97 | } else { 98 | this._handlers.push(handler); 99 | } 100 | } 101 | 102 | 103 | /** 104 | * Checks if the provided object is suitable for being subscribed 105 | * to the event (= is a function), throws an exception if not 106 | * 107 | * @param {Object} obj to check for being subscribable 108 | * 109 | * @throws {Exception} if object is not suitable for subscription 110 | * 111 | * @returns {Object} the provided object if yes 112 | */ 113 | Whenable.prototype._checkHandler = function(handler){ 114 | var type = typeof handler; 115 | if (type != 'function') { 116 | var msg = 117 | 'A function may only be subsribed to the event, ' 118 | + type 119 | + ' was provided instead' 120 | throw new Error(msg); 121 | } 122 | 123 | return handler; 124 | } 125 | 126 | 127 | 128 | /** 129 | * Initializes the library site for Node.js environment (loads 130 | * _JailedSite.js) 131 | */ 132 | var initNode = function() { 133 | require('./_JailedSite.js'); 134 | } 135 | 136 | 137 | /** 138 | * Initializes the library site for web environment (loads 139 | * _JailedSite.js) 140 | */ 141 | var platformInit; 142 | var initWeb = function() { 143 | // loads additional script to the application environment 144 | var load = function(path, cb) { 145 | var script = document.createElement('script'); 146 | script.src = path; 147 | 148 | var clear = function() { 149 | script.onload = null; 150 | script.onerror = null; 151 | script.onreadystatechange = null; 152 | script.parentNode.removeChild(script); 153 | } 154 | 155 | var success = function() { 156 | clear(); 157 | cb(); 158 | } 159 | 160 | script.onerror = clear; 161 | script.onload = success; 162 | script.onreadystatechange = function() { 163 | var state = script.readyState; 164 | if (state==='loaded' || state==='complete') { 165 | success(); 166 | } 167 | } 168 | 169 | document.body.appendChild(script); 170 | } 171 | 172 | platformInit = new Whenable; 173 | var origOnload = window.onload || function(){}; 174 | 175 | window.onload = function(){ 176 | origOnload(); 177 | load( 178 | __jailed__path__+'_JailedSite.js', 179 | function(){ platformInit.emit(); } 180 | ); 181 | } 182 | } 183 | 184 | 185 | var BasicConnection; 186 | 187 | /** 188 | * Creates the platform-dependent BasicConnection object in the 189 | * Node.js environment 190 | */ 191 | var basicConnectionNode = function() { 192 | var childProcess = require('child_process'); 193 | 194 | /** 195 | * Platform-dependent implementation of the BasicConnection 196 | * object, initializes the plugin site and provides the basic 197 | * messaging-based connection with it 198 | * 199 | * For Node.js the plugin is created as a forked process 200 | */ 201 | BasicConnection = function() { 202 | // in Node.js always has a subprocess 203 | this.dedicatedThread = true; 204 | this._disconnected = false; 205 | this._messageHandler = function(){}; 206 | this._disconnectHandler = function(){}; 207 | 208 | this._process = childProcess.fork( 209 | __jailed__path__+'_pluginNode.js' 210 | ); 211 | 212 | var me = this; 213 | this._process.on('message', function(m){ 214 | me._messageHandler(m); 215 | }); 216 | 217 | this._process.on('exit', function(m){ 218 | me._disconnected = true; 219 | me._disconnectHandler(m); 220 | }); 221 | } 222 | 223 | 224 | /** 225 | * Sets-up the handler to be called upon the BasicConnection 226 | * initialization is completed. 227 | * 228 | * For Node.js the connection is fully initialized within the 229 | * constructor, so simply calls the provided handler. 230 | * 231 | * @param {Function} handler to be called upon connection init 232 | */ 233 | BasicConnection.prototype.whenInit = function(handler) { 234 | handler(); 235 | } 236 | 237 | 238 | /** 239 | * Sends a message to the plugin site 240 | * 241 | * @param {Object} data to send 242 | */ 243 | BasicConnection.prototype.send = function(data) { 244 | if (!this._disconnected) { 245 | this._process.send(data); 246 | } 247 | } 248 | 249 | 250 | /** 251 | * Adds a handler for a message received from the plugin site 252 | * 253 | * @param {Function} handler to call upon a message 254 | */ 255 | BasicConnection.prototype.onMessage = function(handler) { 256 | this._messageHandler = function(data) { 257 | // broken stack would break the IPC in Node.js 258 | try { 259 | handler(data); 260 | } catch (e) { 261 | console.error(); 262 | console.error(e.stack); 263 | } 264 | } 265 | } 266 | 267 | 268 | /** 269 | * Adds a handler for the event of plugin disconnection 270 | * (= plugin process exit) 271 | * 272 | * @param {Function} handler to call upon a disconnect 273 | */ 274 | BasicConnection.prototype.onDisconnect = function(handler) { 275 | this._disconnectHandler = handler; 276 | } 277 | 278 | 279 | /** 280 | * Disconnects the plugin (= kills the forked process) 281 | */ 282 | BasicConnection.prototype.disconnect = function() { 283 | this._process.kill('SIGKILL'); 284 | this._disconnected = true; 285 | } 286 | 287 | } 288 | 289 | 290 | /** 291 | * Creates the platform-dependent BasicConnection object in the 292 | * web-browser environment 293 | */ 294 | var basicConnectionWeb = function() { 295 | var perm = ['allow-scripts']; 296 | 297 | if (__jailed__path__.substr(0,7).toLowerCase() == 'file://') { 298 | // local instance requires extra permission 299 | perm.push('allow-same-origin'); 300 | } 301 | 302 | // frame element to be cloned 303 | var sample = document.createElement('iframe'); 304 | sample.src = __jailed__path__ + '_frame.html'; 305 | sample.sandbox = perm.join(' '); 306 | sample.style.display = 'none'; 307 | 308 | 309 | /** 310 | * Platform-dependent implementation of the BasicConnection 311 | * object, initializes the plugin site and provides the basic 312 | * messaging-based connection with it 313 | * 314 | * For the web-browser environment, the plugin is created as a 315 | * Worker in a sandbaxed frame 316 | */ 317 | BasicConnection = function() { 318 | this._init = new Whenable; 319 | this._disconnected = false; 320 | 321 | var me = this; 322 | platformInit.whenEmitted(function() { 323 | if (!me._disconnected) { 324 | me._frame = sample.cloneNode(false); 325 | document.body.appendChild(me._frame); 326 | 327 | window.addEventListener('message', function (e) { 328 | if (e.source === me._frame.contentWindow) { 329 | if (e.data.type == 'initialized') { 330 | me.dedicatedThread = 331 | e.data.dedicatedThread; 332 | me._init.emit(); 333 | } else { 334 | me._messageHandler(e.data); 335 | } 336 | } 337 | }); 338 | } 339 | }); 340 | } 341 | 342 | 343 | /** 344 | * Sets-up the handler to be called upon the BasicConnection 345 | * initialization is completed. 346 | * 347 | * For the web-browser environment, the handler is issued when 348 | * the plugin worker successfully imported and executed the 349 | * _pluginWebWorker.js or _pluginWebIframe.js, and replied to 350 | * the application site with the initImprotSuccess message. 351 | * 352 | * @param {Function} handler to be called upon connection init 353 | */ 354 | BasicConnection.prototype.whenInit = function(handler) { 355 | this._init.whenEmitted(handler); 356 | } 357 | 358 | 359 | /** 360 | * Sends a message to the plugin site 361 | * 362 | * @param {Object} data to send 363 | */ 364 | BasicConnection.prototype.send = function(data) { 365 | this._frame.contentWindow.postMessage( 366 | {type: 'message', data: data}, '*' 367 | ); 368 | } 369 | 370 | 371 | /** 372 | * Adds a handler for a message received from the plugin site 373 | * 374 | * @param {Function} handler to call upon a message 375 | */ 376 | BasicConnection.prototype.onMessage = function(handler) { 377 | this._messageHandler = handler; 378 | } 379 | 380 | 381 | /** 382 | * Adds a handler for the event of plugin disconnection 383 | * (not used in case of Worker) 384 | * 385 | * @param {Function} handler to call upon a disconnect 386 | */ 387 | BasicConnection.prototype.onDisconnect = function(){}; 388 | 389 | 390 | /** 391 | * Disconnects the plugin (= kills the frame) 392 | */ 393 | BasicConnection.prototype.disconnect = function() { 394 | if (!this._disconnected) { 395 | this._disconnected = true; 396 | if (typeof this._frame != 'undefined') { 397 | this._frame.parentNode.removeChild(this._frame); 398 | } // otherwise farme is not yet created 399 | } 400 | } 401 | 402 | } 403 | 404 | 405 | if (__is__node__) { 406 | initNode(); 407 | basicConnectionNode(); 408 | } else { 409 | initWeb(); 410 | basicConnectionWeb(); 411 | } 412 | 413 | 414 | 415 | /** 416 | * Application-site Connection object constructon, reuses the 417 | * platform-dependent BasicConnection declared above in order to 418 | * communicate with the plugin environment, implements the 419 | * application-site protocol of the interraction: provides some 420 | * methods for loading scripts and executing the given code in the 421 | * plugin 422 | */ 423 | var Connection = function(){ 424 | this._platformConnection = new BasicConnection; 425 | 426 | this._importCallbacks = {}; 427 | this._executeSCb = function(){}; 428 | this._executeFCb = function(){}; 429 | this._messageHandler = function(){}; 430 | 431 | var me = this; 432 | this.whenInit = function(cb){ 433 | me._platformConnection.whenInit(cb); 434 | }; 435 | 436 | this._platformConnection.onMessage(function(m) { 437 | switch(m.type) { 438 | case 'message': 439 | me._messageHandler(m.data); 440 | break; 441 | case 'importSuccess': 442 | me._handleImportSuccess(m.url); 443 | break; 444 | case 'importFailure': 445 | me._handleImportFailure(m.url); 446 | break; 447 | case 'executeSuccess': 448 | me._executeSCb(); 449 | break; 450 | case 'executeFailure': 451 | me._executeFCb(); 452 | break; 453 | } 454 | }); 455 | } 456 | 457 | 458 | /** 459 | * @returns {Boolean} true if a connection obtained a dedicated 460 | * thread (subprocess in Node.js or a subworker in browser) and 461 | * therefore will not hang up on the infinite loop in the 462 | * untrusted code 463 | */ 464 | Connection.prototype.hasDedicatedThread = function() { 465 | return this._platformConnection.dedicatedThread; 466 | } 467 | 468 | 469 | /** 470 | * Tells the plugin to load a script with the given path, and to 471 | * execute it. Callbacks executed upon the corresponding responce 472 | * message from the plugin site 473 | * 474 | * @param {String} path of a script to load 475 | * @param {Function} sCb to call upon success 476 | * @param {Function} fCb to call upon failure 477 | */ 478 | Connection.prototype.importScript = function(path, sCb, fCb) { 479 | var f = function(){}; 480 | this._importCallbacks[path] = {sCb: sCb||f, fCb: fCb||f}; 481 | this._platformConnection.send({type: 'import', url: path}); 482 | } 483 | 484 | 485 | /** 486 | * Tells the plugin to load a script with the given path, and to 487 | * execute it in the JAILED environment. Callbacks executed upon 488 | * the corresponding responce message from the plugin site 489 | * 490 | * @param {String} path of a script to load 491 | * @param {Function} sCb to call upon success 492 | * @param {Function} fCb to call upon failure 493 | */ 494 | Connection.prototype.importJailedScript = function(path, sCb, fCb) { 495 | var f = function(){}; 496 | this._importCallbacks[path] = {sCb: sCb||f, fCb: fCb||f}; 497 | this._platformConnection.send({type: 'importJailed', url: path}); 498 | } 499 | 500 | 501 | /** 502 | * Sends the code to the plugin site in order to have it executed 503 | * in the JAILED enviroment. Assuming the execution may only be 504 | * requested once by the Plugin object, which means a single set 505 | * of callbacks is enough (unlike importing additional scripts) 506 | * 507 | * @param {String} code code to execute 508 | * @param {Function} sCb to call upon success 509 | * @param {Function} fCb to call upon failure 510 | */ 511 | Connection.prototype.execute = function(code, sCb, fCb) { 512 | this._executeSCb = sCb||function(){}; 513 | this._executeFCb = fCb||function(){}; 514 | this._platformConnection.send({type: 'execute', code: code}); 515 | } 516 | 517 | 518 | /** 519 | * Adds a handler for a message received from the plugin site 520 | * 521 | * @param {Function} handler to call upon a message 522 | */ 523 | Connection.prototype.onMessage = function(handler) { 524 | this._messageHandler = handler; 525 | } 526 | 527 | 528 | /** 529 | * Adds a handler for a disconnect message received from the 530 | * plugin site 531 | * 532 | * @param {Function} handler to call upon disconnect 533 | */ 534 | Connection.prototype.onDisconnect = function(handler) { 535 | this._platformConnection.onDisconnect(handler); 536 | } 537 | 538 | 539 | /** 540 | * Sends a message to the plugin 541 | * 542 | * @param {Object} data of the message to send 543 | */ 544 | Connection.prototype.send = function(data) { 545 | this._platformConnection.send({ 546 | type: 'message', 547 | data: data 548 | }); 549 | } 550 | 551 | 552 | /** 553 | * Handles import succeeded message from the plugin 554 | * 555 | * @param {String} url of a script loaded by the plugin 556 | */ 557 | Connection.prototype._handleImportSuccess = function(url) { 558 | var sCb = this._importCallbacks[url].sCb; 559 | this._importCallbacks[url] = null; 560 | delete this._importCallbacks[url]; 561 | sCb(); 562 | } 563 | 564 | 565 | /** 566 | * Handles import failure message from the plugin 567 | * 568 | * @param {String} url of a script loaded by the plugin 569 | */ 570 | Connection.prototype._handleImportFailure = function(url) { 571 | var fCb = this._importCallbacks[url].fCb; 572 | this._importCallbacks[url] = null; 573 | delete this._importCallbacks[url]; 574 | fCb(); 575 | } 576 | 577 | 578 | /** 579 | * Disconnects the plugin when it is not needed anymore 580 | */ 581 | Connection.prototype.disconnect = function() { 582 | this._platformConnection.disconnect(); 583 | } 584 | 585 | 586 | 587 | 588 | /** 589 | * Plugin constructor, represents a plugin initialized by a script 590 | * with the given path 591 | * 592 | * @param {String} url of a plugin source 593 | * @param {Object} _interface to provide for the plugin 594 | */ 595 | var Plugin = function(url, _interface) { 596 | this._path = url; 597 | this._initialInterface = _interface||{}; 598 | this._connect(); 599 | }; 600 | 601 | 602 | /** 603 | * DynamicPlugin constructor, represents a plugin initialized by a 604 | * string containing the code to be executed 605 | * 606 | * @param {String} code of the plugin 607 | * @param {Object} _interface to provide to the plugin 608 | */ 609 | var DynamicPlugin = function(code, _interface) { 610 | this._code = code; 611 | this._initialInterface = _interface||{}; 612 | this._connect(); 613 | }; 614 | 615 | 616 | /** 617 | * Creates the connection to the plugin site 618 | */ 619 | DynamicPlugin.prototype._connect = 620 | Plugin.prototype._connect = function() { 621 | this.remote = null; 622 | 623 | this._connect = new Whenable; 624 | this._fail = new Whenable; 625 | this._disconnect = new Whenable; 626 | 627 | var me = this; 628 | 629 | // binded failure callback 630 | this._fCb = function(){ 631 | me._fail.emit(); 632 | me.disconnect(); 633 | } 634 | 635 | this._connection = new Connection; 636 | this._connection.whenInit(function(){ 637 | me._init(); 638 | }); 639 | } 640 | 641 | 642 | /** 643 | * Creates the Site object for the plugin, and then loads the 644 | * common routines (_JailedSite.js) 645 | */ 646 | DynamicPlugin.prototype._init = 647 | Plugin.prototype._init = function() { 648 | this._site = new JailedSite(this._connection); 649 | 650 | var me = this; 651 | this._site.onDisconnect(function() { 652 | me._disconnect.emit(); 653 | }); 654 | 655 | var sCb = function() { 656 | me._loadCore(); 657 | } 658 | 659 | this._connection.importScript( 660 | __jailed__path__+'_JailedSite.js', sCb, this._fCb 661 | ); 662 | } 663 | 664 | 665 | /** 666 | * Loads the core scirpt into the plugin 667 | */ 668 | DynamicPlugin.prototype._loadCore = 669 | Plugin.prototype._loadCore = function() { 670 | var me = this; 671 | var sCb = function() { 672 | me._sendInterface(); 673 | } 674 | 675 | this._connection.importScript( 676 | __jailed__path__+'_pluginCore.js', sCb, this._fCb 677 | ); 678 | } 679 | 680 | 681 | /** 682 | * Sends to the remote site a signature of the interface provided 683 | * upon the Plugin creation 684 | */ 685 | DynamicPlugin.prototype._sendInterface = 686 | Plugin.prototype._sendInterface = function() { 687 | var me = this; 688 | this._site.onInterfaceSetAsRemote(function() { 689 | if (!me._connected) { 690 | me._loadPlugin(); 691 | } 692 | }); 693 | 694 | this._site.setInterface(this._initialInterface); 695 | } 696 | 697 | 698 | /** 699 | * Loads the plugin body (loads the plugin url in case of the 700 | * Plugin) 701 | */ 702 | Plugin.prototype._loadPlugin = function() { 703 | var me = this; 704 | var sCb = function() { 705 | me._requestRemote(); 706 | } 707 | 708 | this._connection.importJailedScript(this._path, sCb, this._fCb); 709 | } 710 | 711 | 712 | /** 713 | * Loads the plugin body (executes the code in case of the 714 | * DynamicPlugin) 715 | */ 716 | DynamicPlugin.prototype._loadPlugin = function() { 717 | var me = this; 718 | var sCb = function() { 719 | me._requestRemote(); 720 | } 721 | 722 | this._connection.execute(this._code, sCb, this._fCb); 723 | } 724 | 725 | 726 | /** 727 | * Requests the remote interface from the plugin (which was 728 | * probably set by the plugin during its initialization), emits 729 | * the connect event when done, then the plugin is fully usable 730 | * (meaning both the plugin and the application can use the 731 | * interfaces provided to each other) 732 | */ 733 | DynamicPlugin.prototype._requestRemote = 734 | Plugin.prototype._requestRemote = function() { 735 | var me = this; 736 | this._site.onRemoteUpdate(function(){ 737 | me.remote = me._site.getRemote(); 738 | me._connect.emit(); 739 | }); 740 | 741 | this._site.requestRemote(); 742 | } 743 | 744 | 745 | /** 746 | * @returns {Boolean} true if a plugin runs on a dedicated thread 747 | * (subprocess in Node.js or a subworker in browser) and therefore 748 | * will not hang up on the infinite loop in the untrusted code 749 | */ 750 | DynamicPlugin.prototype.hasDedicatedThread = 751 | Plugin.prototype.hasDedicatedThread = function() { 752 | return this._connection.hasDedicatedThread(); 753 | } 754 | 755 | 756 | /** 757 | * Disconnects the plugin immideately 758 | */ 759 | DynamicPlugin.prototype.disconnect = 760 | Plugin.prototype.disconnect = function() { 761 | this._connection.disconnect(); 762 | this._disconnect.emit(); 763 | } 764 | 765 | 766 | /** 767 | * Saves the provided function as a handler for the connection 768 | * failure Whenable event 769 | * 770 | * @param {Function} handler to be issued upon disconnect 771 | */ 772 | DynamicPlugin.prototype.whenFailed = 773 | Plugin.prototype.whenFailed = function(handler) { 774 | this._fail.whenEmitted(handler); 775 | } 776 | 777 | 778 | /** 779 | * Saves the provided function as a handler for the connection 780 | * success Whenable event 781 | * 782 | * @param {Function} handler to be issued upon connection 783 | */ 784 | DynamicPlugin.prototype.whenConnected = 785 | Plugin.prototype.whenConnected = function(handler) { 786 | this._connect.whenEmitted(handler); 787 | } 788 | 789 | 790 | /** 791 | * Saves the provided function as a handler for the connection 792 | * failure Whenable event 793 | * 794 | * @param {Function} handler to be issued upon connection failure 795 | */ 796 | DynamicPlugin.prototype.whenDisconnected = 797 | Plugin.prototype.whenDisconnected = function(handler) { 798 | this._disconnect.whenEmitted(handler); 799 | } 800 | 801 | 802 | 803 | exports.Plugin = Plugin; 804 | exports.DynamicPlugin = DynamicPlugin; 805 | 806 | })); 807 | 808 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Dmitry Prokashev ", 3 | "name": "jailed", 4 | "description": "execute untrusted code with custom permissions", 5 | "version": "0.3.1", 6 | "keywords": [ 7 | "jailed", 8 | "isomorphic", 9 | "sandbox", 10 | "export", 11 | "eval", 12 | "plugin", 13 | "untrusted", 14 | "restricted", 15 | "privilliges", 16 | "access", 17 | "security", 18 | "thread", 19 | "worker", 20 | "process", 21 | "messaging" 22 | ], 23 | "repository": { 24 | "type": "git", 25 | "url": "git://github.com/asvd/jailed.git" 26 | }, 27 | "browser": 28 | { 29 | "fs": false, 30 | "child_process": false 31 | }, 32 | "main": "lib/jailed.js", 33 | "dependencies": {}, 34 | "devDependencies": {}, 35 | "optionalDependencies": {}, 36 | "engines": { 37 | "node": "*" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/lighttest.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a set of Helios Kernel modules merged with helios-merge 3 | * 4 | * http://asvd.github.io/helios-kernel 5 | * http://github.com/asvd/helios-merge 6 | * 7 | * The comment related to this code normally preceeds the main module 8 | * (following the last here, according to the dependency order) 9 | */ 10 | 11 | 12 | // base.js 13 | 14 | /** 15 | * @fileoverview Lighttest library base object definition 16 | */ 17 | (function(){ 18 | lighttest = {}; 19 | })(); 20 | 21 | 22 | 23 | // platform.js 24 | 25 | /** 26 | * @fileoverview Platform-dependent routines for the Lighttest library 27 | */ 28 | (function(){ 29 | lighttest._platform = {}; 30 | lighttest._platform._initialized = false; 31 | /** 32 | * Initializes Lighttest upon the first launch of the tests 33 | */ 34 | lighttest._platform.init = function () { 35 | if (!lighttest._platform._initialized) { 36 | var __is__node__ = ((typeof process !== 'undefined') && 37 | (!process.browser) && 38 | (process.release.name.search(/node|io.js/) !== -1)); 39 | if (__is__node__) { 40 | lighttest._platform._initNode(); 41 | } else { 42 | lighttest._platform._initWeb(); 43 | } 44 | lighttest._platform._initialized = true; 45 | } 46 | }; 47 | /** 48 | * Initializes Lighttest for the web-based environment 49 | */ 50 | lighttest._platform._initWeb = function () { 51 | var target = document.getElementById('lighttest') || document.getElementsByTagName('body').item(0); 52 | target.style.margin = 0; 53 | var style1 = { 54 | backgroundColor: 'rgb(2,14,15)', 55 | width: '100%', 56 | height: '100%', 57 | overflow: 'auto', 58 | position: 'absolute' 59 | }; 60 | var div1 = document.createElement('div'); 61 | for (var i in style1) { 62 | div1.style[i] = style1[i]; 63 | } 64 | target.appendChild(div1); 65 | var style2 = { 66 | color: 'rgb(173,196,190)', 67 | paddingBottom: '5px', 68 | paddingLeft: '5px', 69 | fontFamily: 'monospace', 70 | fontSize: '8pt', 71 | position: 'absolute' 72 | }; 73 | var div2 = document.createElement('div'); 74 | for (i in style2) { 75 | div2.style[i] = style2[i]; 76 | } 77 | div1.appendChild(div2); 78 | 79 | if (typeof console.group == 'undefined') { 80 | console.group = function(text) { 81 | console.log('[ GROUP START ]: ' + text); 82 | } 83 | } 84 | 85 | 86 | if (typeof console.groupEnd == 'undefined') { 87 | console.groupEnd = function() { 88 | console.log('[ GROUP END ]'); 89 | console.log(' '); 90 | } 91 | } 92 | 93 | 94 | lighttest._platform.print = function (text) { 95 | div2.innerHTML += text.replace(/\ /g, ' '); 96 | }; 97 | lighttest._platform._printPlain = function (text) { 98 | div2.innerHTML += text; 99 | }; 100 | var styleRed = 'text-shadow : 0px 0px 6px #FD4E7F; color: #F13D35;'; 101 | lighttest._platform.printRed = function (text) { 102 | lighttest._platform._printPlain('' + text + ''); 103 | }; 104 | var styleGreen = 'text-shadow : 0px 0px 8px #50A39C; color: #56D670;'; 105 | lighttest._platform.printGreen = function (text) { 106 | lighttest._platform._printPlain('' + text + ''); 107 | }; 108 | var styleBlue = 'text-shadow: 0px 0px 8px #507EA3; color: #58AEC9;'; 109 | lighttest._platform.printBlue = function (text) { 110 | lighttest._platform._printPlain('' + text + ''); 111 | }; 112 | lighttest._platform.printWhite = function (text) { 113 | lighttest._platform._printPlain(text); 114 | }; 115 | lighttest._platform.printLine = function () { 116 | div2.innerHTML += '
'; 117 | console.groupEnd(); 118 | setTimeout(function () { 119 | div1.scrollTop = div1.scrollHeight; 120 | }, 1); 121 | }; 122 | lighttest._platform.printTestLabel = function(text) { 123 | lighttest._platform.printWhite(text + ' '); 124 | console.group(text); 125 | // console.group("%c" + '' + text, "font-weight:bold;"); 126 | } 127 | lighttest._platform.printPass = function() { 128 | lighttest._platform.printGreen('PASS '); 129 | console.log('%c' + 'PASS', 'color: #36B650;font-weight:bold'); 130 | } 131 | lighttest._platform.printFail = function() { 132 | lighttest._platform.printRed('FAIL '); 133 | console.error('%c' + 'FAIL', 'color: #F13D35;font-weight:bold'); 134 | } 135 | lighttest._platform.reset = function () { 136 | div2.innerHTML = ''; 137 | }; 138 | lighttest._platform.exit = function (code) { 139 | }; 140 | }; 141 | /** 142 | * Initializes Lighttest for the Node.js-based environment 143 | */ 144 | lighttest._platform._initNode = function () { 145 | var red = '\x1B[31m'; 146 | var green = '\x1B[32m'; 147 | var bold = '\x1b[1m'; 148 | var blue = '\x1B[36m'; 149 | var reset = '\x1B[0m'; 150 | lighttest._platform.print = function (val) { 151 | process.stdout.write(val); 152 | }; 153 | lighttest._platform.printRed = function (text) { 154 | lighttest._platform.print(red + bold + text + reset); 155 | }; 156 | lighttest._platform.printGreen = function (text) { 157 | lighttest._platform.print(green + bold + text + reset); 158 | }; 159 | lighttest._platform.printBlue = function (text) { 160 | lighttest._platform.print(blue + text + reset); 161 | }; 162 | lighttest._platform.printWhite = function (text) { 163 | lighttest._platform.print(bold + text + reset); 164 | }; 165 | lighttest._platform.printLine = function () { 166 | console.log(); 167 | }; 168 | lighttest._platform.printTestLabel = function(text) { 169 | lighttest._platform.printWhite(text + ' '); 170 | } 171 | lighttest._platform.printPass = function() { 172 | lighttest._platform.printGreen('PASS '); 173 | } 174 | lighttest._platform.printFail = function() { 175 | lighttest._platform.printRed('FAIL '); 176 | console.log(''); 177 | } 178 | lighttest._platform.reset = function (code) { 179 | }; 180 | lighttest._platform.exit = function (code) { 181 | process.exit(code); 182 | }; 183 | // prevents from crashing on exceptions 184 | process.on('uncaughtException', function (err) { 185 | console.log(); 186 | console.error(err); 187 | }); 188 | }; 189 | })(); 190 | 191 | 192 | 193 | // lighttest.js 194 | 195 | /** 196 | * @fileoverview Lighttest - a clear testing environment 197 | * 198 | * @version 0.1.3 199 | * 200 | * Copyright (c) 2014 asvd 201 | * 202 | * Lighttest library is licensed under the MIT license, 203 | * see http://github.com/asvd/lighttest 204 | */ 205 | (function(){ 206 | lighttest._state = 'nothing'; 207 | lighttest._pendingTests = null; 208 | lighttest._pendingCallback = null; 209 | /** 210 | * Wraps the given code so that in case of exception it will fail 211 | * the test (instead of breaking the stack) 212 | * 213 | * @param {Function} method to wrap 214 | * 215 | * @returns {Function} wrapped method 216 | */ 217 | lighttest.protect = function (method) { 218 | return function () { 219 | try { 220 | method.apply(this, arguments); 221 | } catch (e) { 222 | lighttest.check(false); 223 | lighttest.done(); 224 | throw e; 225 | } 226 | }; 227 | }; 228 | /** 229 | * Runs the given set of tests 230 | * 231 | * @param {Array} tests list of tests to execute 232 | * @param {Function} callback to run after the tests 233 | */ 234 | lighttest.start = function (tests, callback) { 235 | switch (lighttest._state) { 236 | case 'nothing': 237 | case 'paused': 238 | // (re)start 239 | lighttest._platform.init(); 240 | lighttest._platform.reset(); 241 | lighttest._testsFailed = 0; 242 | lighttest._currentTestIdx = 0; 243 | lighttest._state = 'running'; 244 | lighttest._callback = callback || null; 245 | lighttest._tests = []; 246 | for (var label in tests) { 247 | if (tests.hasOwnProperty(label)) { 248 | lighttest._tests.push({ 249 | label: label, 250 | method: lighttest.protect(tests[label]) 251 | }); 252 | } 253 | } 254 | lighttest._next(); 255 | break; 256 | case 'running': 257 | case 'interrupting': 258 | // switching to restart even in case of requested pause 259 | // (restart is stronger) 260 | lighttest._state = 'interrupting'; 261 | lighttest._pendingTests = tests; 262 | lighttest._pendingCallback = callback; 263 | break; 264 | } 265 | }; 266 | /** 267 | * (Un)pauses the tests execution (waiting until the currently 268 | * running test is completed) 269 | */ 270 | lighttest.pause = function () { 271 | switch (lighttest._state) { 272 | case 'nothing': 273 | case 'interrupting': 274 | break; 275 | case 'running': 276 | // pausing 277 | lighttest._state = 'interrupting'; 278 | lighttest._pendingTests = null; 279 | lighttest._pendingCallback = null; 280 | break; 281 | case 'paused': 282 | // unpausing 283 | lighttest._state = 'running'; 284 | lighttest._next(); 285 | break; 286 | } 287 | }; 288 | /** 289 | * Checks the given value against being true, logs the result for 290 | * the currently running test 291 | * 292 | * @param {Boolean} value to check 293 | */ 294 | lighttest.check = function (value) { 295 | if (value) { 296 | lighttest._platform.printPass(); 297 | } else { 298 | lighttest._platform.printFail(); 299 | lighttest._currentFailed = true; 300 | } 301 | }; 302 | /** 303 | * Called by the test body when finished, launches the next test 304 | */ 305 | lighttest.done = function () { 306 | // let pause() called after done() time to perform 307 | setTimeout(lighttest._done, 10); 308 | }; 309 | /** 310 | * Launches the next test 311 | */ 312 | lighttest._done = function () { 313 | if (lighttest._currentFailed) { 314 | lighttest._testsFailed++; 315 | } 316 | lighttest._currentTestIdx++; 317 | switch (lighttest._state) { 318 | case 'paused': 319 | case 'nothing': 320 | // tests not running 321 | break; 322 | case 'running': 323 | // normal case, prevent stack growth 324 | setTimeout(lighttest._next, 0); 325 | break; 326 | case 'interrupting': 327 | if (lighttest._pendingTests) { 328 | // restart requested 329 | lighttest._state = 'nothing'; 330 | var tests = lighttest._pendingTests; 331 | var callback = lighttest._pendingCallback; 332 | lighttest._pendingTests = null; 333 | lighttest._pendingCallback = null; 334 | lighttest.start(tests, callback); 335 | } else { 336 | // pause requested 337 | lighttest._state = 'paused'; 338 | lighttest._platform.printLine(); 339 | lighttest._platform.printLine(); 340 | lighttest._platform.printBlue('// paused'); 341 | lighttest._platform.printLine(); 342 | } 343 | break; 344 | } 345 | }; 346 | lighttest._genPfx = function(num) { 347 | var testsnum = lighttest._tests.length; 348 | var len = (''+testsnum).length; 349 | var result = ('00000000'+(num+1)).substr(-len); 350 | result = ''+result + ' '; 351 | return result; 352 | } 353 | /** 354 | * Proceeds to the next test 355 | */ 356 | lighttest._next = function () { 357 | var idx = lighttest._currentTestIdx; 358 | if (idx == lighttest._tests.length) { 359 | lighttest._finalize(); 360 | } else { 361 | lighttest._platform.printLine(); 362 | lighttest._currentFailed = false; 363 | var test = lighttest._tests[idx]; 364 | lighttest._platform.printTestLabel(lighttest._genPfx(idx) + test.label); 365 | setTimeout(test.method, 0); 366 | } 367 | }; 368 | /** 369 | * Finalizes testing after all tests completed 370 | */ 371 | lighttest._finalize = function () { 372 | var failed = lighttest._testsFailed; 373 | var total = lighttest._tests.length; 374 | lighttest._platform.printLine(); 375 | lighttest._platform.printLine(); 376 | if (failed) { 377 | lighttest._platform.print(failed + ' of ' + total + ' tests '); 378 | lighttest._platform.printRed('FAILED'); 379 | } else { 380 | lighttest._platform.print(total + ' tests '); 381 | lighttest._platform.printGreen('PASSED'); 382 | } 383 | lighttest._platform.printLine(); 384 | lighttest._platform.printLine(); 385 | lighttest._state = 'nothing'; 386 | if (lighttest._callback) { 387 | lighttest._callback(failed); 388 | } 389 | lighttest._platform.exit(failed); 390 | }; 391 | })(); 392 | 393 | 394 | -------------------------------------------------------------------------------- /tests/stage01/plugin1.js: -------------------------------------------------------------------------------- 1 | 2 | application.remote.callMe(); 3 | -------------------------------------------------------------------------------- /tests/stage02/plugin2.js: -------------------------------------------------------------------------------- 1 | var cb = function(result) { 2 | application.remote.report(result); 3 | } 4 | 5 | application.remote.square(2, cb); -------------------------------------------------------------------------------- /tests/stage03/plugin3.js: -------------------------------------------------------------------------------- 1 | 2 | var api = { 3 | square : function(val, cb) { 4 | cb(val*val); 5 | } 6 | }; 7 | 8 | application.setInterface(api); 9 | -------------------------------------------------------------------------------- /tests/stage04/plugin4.js: -------------------------------------------------------------------------------- 1 | var api = { 2 | squareDelayed : function(val, cb) { 3 | var cb1 = function() { 4 | cb(val*val); 5 | } 6 | 7 | application.remote.wait(cb1); 8 | } 9 | }; 10 | 11 | application.setInterface(api); 12 | -------------------------------------------------------------------------------- /tests/stage05/plugin5.js: -------------------------------------------------------------------------------- 1 | var waitCalled = false; 2 | 3 | var api = { 4 | wait : function(cb) { 5 | waitCalled = true; 6 | setTimeout(cb,1000); 7 | } 8 | }; 9 | 10 | 11 | application.setInterface(api); 12 | 13 | var init = function() { 14 | var val = 2; 15 | var cb = function(result) { 16 | application.remote.report(result, waitCalled); 17 | } 18 | 19 | application.remote.squareDelayed(val, cb) 20 | } 21 | 22 | application.whenConnected(init); -------------------------------------------------------------------------------- /tests/stage06/plugin6_1.js: -------------------------------------------------------------------------------- 1 | application.setInterface({square: function(val, cb){ cb(val*val); } }); -------------------------------------------------------------------------------- /tests/stage06/plugin6_2.js: -------------------------------------------------------------------------------- 1 | application.setInterface({square: function(val, cb){ cb(val*val); } }); 2 | -------------------------------------------------------------------------------- /tests/stage07/plugin7.js: -------------------------------------------------------------------------------- 1 | var api = { 2 | square : function(val, cb) {cb(val*val);} 3 | }; 4 | 5 | 6 | application.setInterface(api); 7 | -------------------------------------------------------------------------------- /tests/stage08/plugin8.js: -------------------------------------------------------------------------------- 1 | 2 | var val1 = 2; 3 | 4 | var cb1 = function(result) { 5 | application.remote.check(result == val1*val1); 6 | 7 | setTimeout(step2, 1000); 8 | } 9 | 10 | var step2 = function() { 11 | var val2 = 5; 12 | 13 | var cb2 = function(result) { 14 | application.remote.check(result == val2*val2); 15 | 16 | setTimeout(function() { application.remote.done(); }, 1000); 17 | } 18 | 19 | application.remote.square(val2, cb2); 20 | } 21 | 22 | application.remote.square(val1, cb1); -------------------------------------------------------------------------------- /tests/stage09/plugin9.js: -------------------------------------------------------------------------------- 1 | var finalize = function() { 2 | application.remote.done(); 3 | } 4 | 5 | application.remote.checkAttempt(finalize); -------------------------------------------------------------------------------- /tests/stage10/plugin10.js: -------------------------------------------------------------------------------- 1 | 2 | var cb1 = function(result) { 3 | var cb2 = function() { 4 | application.remote.done(); 5 | } 6 | 7 | application.remote.report(result, cb2); 8 | } 9 | 10 | 11 | application.remote.getNum(cb1); -------------------------------------------------------------------------------- /tests/stage11/plugin11.js: -------------------------------------------------------------------------------- 1 | var api = { 2 | square : function(val, cb) { cb(val*val); }, 3 | killYourself : function() { application.disconnect(); } 4 | } 5 | 6 | application.setInterface(api); 7 | 8 | -------------------------------------------------------------------------------- /tests/stage11/plugin11_1.js: -------------------------------------------------------------------------------- 1 | application.disconnect(); -------------------------------------------------------------------------------- /tests/stage12/plugin12.js: -------------------------------------------------------------------------------- 1 | var api = { 2 | square: function(val, cb) { 3 | cb(val*val); 4 | } 5 | } 6 | 7 | application.setInterface(api); -------------------------------------------------------------------------------- /tests/stage13/plugin13.js: -------------------------------------------------------------------------------- 1 | 2 | var step1 = function() { 3 | var sCb = function() { 4 | application.remote.report(true, step2); 5 | } 6 | 7 | var fCb = function() { 8 | application.remote.report(false, step2); 9 | } 10 | 11 | application.remote.callMeBack(true, sCb, fCb); 12 | } 13 | 14 | 15 | var step2 = function() { 16 | var sCb = function() { 17 | application.remote.report(false, step3); 18 | } 19 | 20 | var fCb = function() { 21 | application.remote.report(true, step3); 22 | } 23 | 24 | application.remote.callMeBack(false, sCb, fCb); 25 | } 26 | 27 | 28 | var step3 = function() { 29 | application.remote.done(); 30 | } 31 | 32 | 33 | application.whenConnected(step1); -------------------------------------------------------------------------------- /tests/stage15/plugin15_bad.js: -------------------------------------------------------------------------------- 1 | 2 | var api = { 3 | infinite: function(cb) { 4 | while (true) {}; 5 | cb(); 6 | } 7 | } 8 | 9 | application.setInterface(api); 10 | -------------------------------------------------------------------------------- /tests/stage15/plugin15_good.js: -------------------------------------------------------------------------------- 1 | 2 | var api = { 3 | square : function(val, cb) { 4 | cb(val*val); 5 | } 6 | } 7 | 8 | application.setInterface(api); -------------------------------------------------------------------------------- /tests/stage16/plugin16.js: -------------------------------------------------------------------------------- 1 | 2 | var step1 = function() { 3 | var _proc = (typeof process != 'undefined' && 4 | process && 5 | typeof process.version != 'undefined'); 6 | 7 | var _require = (typeof require != 'undefined' && !!require); 8 | 9 | application.remote.check(!_proc && !_require, step2); 10 | } 11 | 12 | var step2 = function() { 13 | application.remote.done(); 14 | } 15 | 16 | 17 | 18 | application.whenConnected(step1); 19 | -------------------------------------------------------------------------------- /tests/stage17/plugin17.js: -------------------------------------------------------------------------------- 1 | auaa } u((uu& -------------------------------------------------------------------------------- /tests/stage19/plugin19.js: -------------------------------------------------------------------------------- 1 | 2 | var api = { 3 | square : function(val, cb) { 4 | cb(val*val); 5 | }, 6 | broken : function(cb) { 7 | somethingWrong(); 8 | cb(); 9 | }, 10 | brokenDelayed : function(cb) { 11 | setTimeout(cb, 500); 12 | somethingWrong(); 13 | } 14 | } 15 | 16 | application.setInterface(api); 17 | -------------------------------------------------------------------------------- /tests/stage20/plugin20.js: -------------------------------------------------------------------------------- 1 | 2 | var step1 = function() { 3 | application.remote.check(true, step1_1); 4 | } 5 | 6 | var step1_1 = function() { 7 | var val = 6; 8 | var cb = function(result) { 9 | application.remote.check(result == val*val, step2); 10 | } 11 | 12 | application.remote.square(val, cb); 13 | } 14 | 15 | 16 | var step2 = function() { 17 | var cb = function() { 18 | clearTimeout(timeout); 19 | application.remote.check(true, step3); 20 | } 21 | 22 | var fCb = function() { 23 | var finalize = function() { 24 | application.remote.done(); 25 | } 26 | 27 | application.remote.check(false, finalize); 28 | } 29 | 30 | 31 | application.remote.brokenDelayed(cb); 32 | 33 | timeout = setTimeout(fCb, 1000); 34 | } 35 | 36 | 37 | var step3 = function() { 38 | var finalize = function() { 39 | application.remote.done(); 40 | } 41 | 42 | application.remote.check(true, finalize); 43 | } 44 | 45 | 46 | var fail = function() { 47 | clearTimeout(timeout); 48 | var finalize = function() { 49 | application.remote.done(); 50 | } 51 | 52 | application.remote.check(false, finalize); 53 | } 54 | 55 | application.remote.broken(fail); 56 | 57 | var timeout = setTimeout(step1, 1000); 58 | 59 | -------------------------------------------------------------------------------- /tests/stage21/plugin21.js: -------------------------------------------------------------------------------- 1 | 2 | var api = { 3 | square : function(val, cb) { 4 | cb(val*val); 5 | }, 6 | cubeDelayed : function(val, cb) { 7 | setTimeout( 8 | function() { 9 | cb(val*val*val); 10 | }, 1000 11 | ); 12 | } 13 | } 14 | 15 | application.setInterface(api); -------------------------------------------------------------------------------- /tests/stage22/plugin22.js: -------------------------------------------------------------------------------- 1 | var squareFinished = false; 2 | var cubeFinished = false; 3 | 4 | 5 | var valCube = 6; 6 | 7 | 8 | var cbCube = function(result) { 9 | cubeFinished = true; 10 | application.remote.check(result == valCube*valCube*valCube, finalize); 11 | } 12 | 13 | application.remote.cubeDelayed(valCube, cbCube); 14 | 15 | var valSquare = 8; 16 | var cbSquare = function(result) { 17 | squareFinished = true; 18 | application.remote.check(result == valSquare*valSquare, finalize); 19 | } 20 | 21 | application.remote.square(valSquare, cbSquare); 22 | 23 | 24 | var finalize = function() { 25 | if (squareFinished && cubeFinished) { 26 | application.remote.check( 27 | true, 28 | function() { 29 | application.remote.done(); 30 | } 31 | ); 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /tests/stage23/plugin23.js: -------------------------------------------------------------------------------- 1 | 2 | var api = { 3 | callback: function(num, cb0, cb1) { 4 | if (num == 0) { 5 | cb0(); 6 | } else { 7 | cb1(); 8 | } 9 | } 10 | } 11 | 12 | application.setInterface(api); -------------------------------------------------------------------------------- /tests/stage24/plugin24.js: -------------------------------------------------------------------------------- 1 | 2 | var step1 = function() { 3 | var cb0 = function() { 4 | application.remote.check(false, step2); 5 | } 6 | 7 | var cb1 = function() { 8 | application.remote.check(true, step2); 9 | } 10 | 11 | application.remote.callback(1, cb0, cb1); 12 | } 13 | 14 | var step2 = function() { 15 | var cb0 = function() { 16 | application.remote.check(true, finalize); 17 | } 18 | 19 | 20 | var cb1 = function() { 21 | application.remote.check(false, finalize); 22 | } 23 | 24 | application.remote.callback(0, cb0, cb1); 25 | } 26 | 27 | var finalize = function() { 28 | application.remote.done(); 29 | } 30 | 31 | 32 | application.whenConnected(step1); 33 | 34 | -------------------------------------------------------------------------------- /tests/stage25/plugin25.js: -------------------------------------------------------------------------------- 1 | 2 | var api = { 3 | wait: function(cb) { 4 | setTimeout(cb, 1000); 5 | } 6 | } 7 | 8 | application.setInterface(api); 9 | 10 | 11 | var step1 = function() { 12 | var val = 21; 13 | var cb = function(result) { 14 | application.remote.check(result == val*val, finalize); 15 | } 16 | application.remote.squareDelayed(val, cb); 17 | } 18 | 19 | var finalize = function() { 20 | application.remote.done(); 21 | } 22 | 23 | application.whenConnected(step1); 24 | 25 | -------------------------------------------------------------------------------- /tests/stage26/plugin26.js: -------------------------------------------------------------------------------- 1 | var api = { 2 | squareDelayed: function(val, cb) { 3 | application.remote.wait( 4 | function() { 5 | cb(val*val); 6 | } 7 | ); 8 | } 9 | }; 10 | 11 | application.setInterface(api); -------------------------------------------------------------------------------- /tests/stage27/plugin27.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | var init = function() { 4 | var notYetCalled = true; 5 | var cb = function() { 6 | application.remote.check(notYetCalled); 7 | notYetCalled = false; 8 | } 9 | 10 | application.remote.callme(cb, cb); 11 | } 12 | 13 | application.whenConnected(init); 14 | -------------------------------------------------------------------------------- /tests/stage28/plugin28.js: -------------------------------------------------------------------------------- 1 | var api = { 2 | callme : function(cb0, cb1) { 3 | var finalize = function() { 4 | application.remote.done(); 5 | } 6 | 7 | setTimeout(finalize, 1000); 8 | 9 | cb0(); 10 | 11 | try { 12 | cb1(); 13 | application.remote.check(false); 14 | } catch(e) { 15 | application.remote.check(true); 16 | throw e; 17 | } 18 | } 19 | }; 20 | 21 | application.setInterface(api); -------------------------------------------------------------------------------- /tests/stage29/plugin29.js: -------------------------------------------------------------------------------- 1 | 2 | var api = { 3 | brokenDelayed : function(cb) { 4 | setTimeout( 5 | function() { 6 | somethingWrong(); 7 | }, 1000 8 | ); 9 | 10 | setTimeout( 11 | function() { 12 | cb(); 13 | }, 2000 14 | ); 15 | 16 | 17 | } 18 | }; 19 | 20 | application.setInterface(api); 21 | 22 | 23 | /* 24 | 25 | setTimeout( 26 | function() { 27 | somethingWrongHere(); 28 | }, 29 | 1000 30 | ); 31 | 32 | */ 33 | 34 | -------------------------------------------------------------------------------- /tests/stage30/plugin30.js: -------------------------------------------------------------------------------- 1 | 2 | application.whenConnected('something'); -------------------------------------------------------------------------------- /tests/stage31/plugin31.js: -------------------------------------------------------------------------------- 1 | var connected = false; 2 | 3 | var tryConnect = function() { 4 | application.whenConnected(connect); 5 | } 6 | 7 | var connect = function() { 8 | connected = true; 9 | } 10 | 11 | var checkConnect = function() { 12 | application.remote.check(connected, finalize); 13 | } 14 | 15 | var finalize = function() { 16 | application.remote.done(); 17 | } 18 | 19 | setTimeout(tryConnect, 300); 20 | setTimeout(checkConnect, 600); 21 | -------------------------------------------------------------------------------- /tests/stage32/plugin32.js: -------------------------------------------------------------------------------- 1 | var beforeConnect1Finished = 0; 2 | var beforeConnect1 = function() { 3 | beforeConnect1Finished++; 4 | finalize(); 5 | } 6 | 7 | var beforeConnect2Finished = 0; 8 | var beforeConnect2 = function() { 9 | beforeConnect2Finished++; 10 | finalize(); 11 | } 12 | 13 | 14 | var afterConnect1Finished = 0; 15 | var afterConnect1 = function() { 16 | afterConnect1Finished++; 17 | finalize(); 18 | } 19 | 20 | var afterConnect2Finished = 0; 21 | var afterConnect2 = function() { 22 | afterConnect2Finished++; 23 | finalize(); 24 | } 25 | 26 | 27 | var finalize = function() { 28 | /* 29 | console.log('HAAA'); 30 | console.log(beforeConnect1Finished ); 31 | console.log(beforeConnect2Finished ); 32 | console.log(afterConnect1Finished ); 33 | console.log(afterConnect2Finished ); 34 | */ 35 | if ( 36 | beforeConnect1Finished == 1 && 37 | beforeConnect2Finished == 1 && 38 | afterConnect1Finished == 1 && 39 | afterConnect2Finished == 1 40 | ) { 41 | application.remote.check( 42 | beforeConnect1Finished == 1 && 43 | beforeConnect2Finished == 1 && 44 | afterConnect1Finished == 1 && 45 | afterConnect2Finished == 1, 46 | function() { 47 | application.remote.finished(); 48 | } 49 | ); 50 | } 51 | } 52 | 53 | var connect = function() { 54 | setTimeout(afterConnect, 300); 55 | } 56 | 57 | var afterConnect = function() { 58 | application.whenConnected(afterConnect1); 59 | application.whenConnected(afterConnect2); 60 | } 61 | 62 | application.whenConnected(beforeConnect1); 63 | application.whenConnected(beforeConnect2); 64 | application.whenConnected(connect); 65 | -------------------------------------------------------------------------------- /tests/tests.js: -------------------------------------------------------------------------------- 1 | var __is__node__ = ((typeof process !== 'undefined') && 2 | (!process.browser) && 3 | (process.release.name.search(/node|io.js/) !== -1)); 4 | var currentPath; 5 | 6 | var whenFailed = function(){ 7 | lighttest.check(false); 8 | lighttest.done(); 9 | } 10 | 11 | if (__is__node__) { 12 | currentPath = __dirname + '/'; 13 | } else { 14 | var scripts = document.getElementsByTagName('script'); 15 | currentPath = scripts[scripts.length-1].src 16 | .split('?')[0] 17 | .split('/') 18 | .slice(0, -1) 19 | .join('/')+'/'; 20 | } 21 | 22 | 23 | var tests = { 24 | 25 | 'Initialization': 26 | function() { 27 | lighttest.check( 28 | jailed && 29 | jailed.Plugin && 30 | jailed.DynamicPlugin 31 | ); 32 | 33 | // lighttest.check(false); 34 | 35 | lighttest.done(); 36 | }, 37 | 38 | 'Static plugin': 39 | function() { 40 | var api = { 41 | callMe : lighttest.protect(function() { 42 | plugin.disconnect(); 43 | lighttest.check(true); 44 | lighttest.done(); 45 | }) 46 | }; 47 | 48 | var path = currentPath + 'stage01/plugin1.js'; 49 | var plugin = new jailed.Plugin(path, api); 50 | plugin.whenFailed(whenFailed); 51 | }, 52 | 53 | 54 | 'Dynamic plugin': 55 | function() { 56 | var api = { 57 | callMe : lighttest.protect(function() { 58 | plugin.disconnect(); 59 | lighttest.check(true); 60 | lighttest.done(); 61 | }) 62 | }; 63 | 64 | var code = 'application.remote.callMe();'; 65 | var plugin = new jailed.DynamicPlugin(code, api); 66 | plugin.whenFailed(whenFailed); 67 | }, 68 | 69 | 70 | 'Application API': 71 | function() { 72 | var api = { 73 | square : lighttest.protect(function(val, cb) { 74 | cb(val*val); 75 | }), 76 | report : lighttest.protect(function(result) { 77 | plugin.disconnect(); 78 | lighttest.check(result == 4); 79 | lighttest.done(); 80 | }) 81 | }; 82 | 83 | var path = currentPath + 'stage02/plugin2.js'; 84 | var plugin = new jailed.Plugin(path, api); 85 | plugin.whenFailed(whenFailed); 86 | }, 87 | 88 | 89 | 'Plugin API': 90 | function() { 91 | var init = lighttest.protect(function() { 92 | var val = 2; 93 | 94 | var cb = lighttest.protect(function(result) { 95 | plugin.disconnect(); 96 | lighttest.check(result == val*val); 97 | lighttest.done(); 98 | }); 99 | 100 | plugin.remote.square(val, cb); 101 | }); 102 | 103 | var plugin = new jailed.Plugin(currentPath + 'stage03/plugin3.js'); 104 | plugin.whenFailed(whenFailed); 105 | plugin.whenConnected(init); 106 | }, 107 | 108 | 109 | 'Bidirectional communication initiated by the application': 110 | function() { 111 | var path = currentPath + 'stage04/plugin4.js'; 112 | var waitCalled = false; 113 | 114 | var api = { 115 | wait : lighttest.protect(function(cb) { 116 | waitCalled = true; 117 | setTimeout(cb, 1000); 118 | }) 119 | }; 120 | 121 | var init = lighttest.protect(function() { 122 | var val = 2; 123 | 124 | var cb = lighttest.protect(function(result) { 125 | plugin.disconnect(); 126 | lighttest.check(waitCalled); 127 | lighttest.check(result == val*val); 128 | lighttest.done(); 129 | }); 130 | 131 | plugin.remote.squareDelayed(val, cb); 132 | }); 133 | 134 | var plugin = new jailed.Plugin(path, api); 135 | plugin.whenFailed(whenFailed); 136 | plugin.whenConnected(init); 137 | }, 138 | 139 | 140 | 'Bidirectional communication initiated by the plugin': 141 | function() { 142 | var path = currentPath + 'stage05/plugin5.js'; 143 | 144 | var api = { 145 | squareDelayed : lighttest.protect(function(val, cb) { 146 | var cb1 = lighttest.protect(function() { 147 | cb(val*val); 148 | }); 149 | 150 | plugin.remote.wait(cb1); 151 | }), 152 | report : lighttest.protect(function(result, waitCalled) { 153 | plugin.disconnect(); 154 | lighttest.check(result == 4); 155 | lighttest.check(waitCalled); 156 | lighttest.done(); 157 | }) 158 | }; 159 | 160 | var plugin = new jailed.Plugin(path, api); 161 | plugin.whenFailed(whenFailed); 162 | }, 163 | 164 | 165 | 'Loading several plugins at once': 166 | function() { 167 | var path1 = currentPath + 'stage06/plugin6_1.js'; 168 | var path2 = currentPath + 'stage06/plugin6_2.js'; 169 | 170 | var replied1 = false; 171 | var replied2 = false; 172 | 173 | var finalize = lighttest.protect(function() { 174 | if (replied1 && replied2) { 175 | lighttest.check(true); 176 | lighttest.done(); 177 | } 178 | }); 179 | 180 | var init1 = lighttest.protect(function() { 181 | var val = 2; 182 | var cb = lighttest.protect(function(result) { 183 | replied1 = true; 184 | plugin1.disconnect(); 185 | lighttest.check(result == val*val); 186 | finalize(); 187 | }); 188 | 189 | plugin1.remote.square(val, cb); 190 | }); 191 | 192 | var init2 = lighttest.protect(function() { 193 | var val = 3; 194 | var cb = function(result) { 195 | replied2 = true; 196 | plugin2.disconnect(); 197 | lighttest.check(result == val*val); 198 | finalize(); 199 | } 200 | 201 | plugin2.remote.square(val, cb); 202 | }); 203 | 204 | var plugin1 = new jailed.Plugin(path1); 205 | var plugin2 = new jailed.Plugin(path2); 206 | 207 | plugin1.whenFailed(whenFailed); 208 | plugin2.whenFailed(whenFailed); 209 | 210 | plugin1.whenConnected(init1); 211 | plugin2.whenConnected(init2); 212 | }, 213 | 214 | 215 | 'Using the plugin during a period': 216 | function() { 217 | var path = currentPath + 'stage07/plugin7.js'; 218 | 219 | var init = lighttest.protect(function() { 220 | var val1 = 2; 221 | var cb1 = lighttest.protect(function(result) { 222 | lighttest.check(result == val1*val1); 223 | 224 | setTimeout(step2, 1000); 225 | }); 226 | 227 | var step2 = lighttest.protect(function(){ 228 | var val2 = 3; 229 | var cb2 = lighttest.protect(function(result) { 230 | plugin.disconnect(); 231 | lighttest.check(result == val2*val2); 232 | lighttest.done(); 233 | }); 234 | 235 | plugin.remote.square(val2, cb2); 236 | }); 237 | 238 | plugin.remote.square(val1, cb1); 239 | }); 240 | 241 | var plugin = new jailed.Plugin(path); 242 | plugin.whenFailed(whenFailed); 243 | plugin.whenConnected(init); 244 | }, 245 | 246 | 247 | 'Using the application during a period': 248 | function() { 249 | var path = currentPath + 'stage08/plugin8.js'; 250 | 251 | var api = { 252 | square: lighttest.protect(function(val, cb){ 253 | cb(val*val); 254 | }), 255 | check : lighttest.protect(function(result){ 256 | lighttest.check(result); 257 | }), 258 | done : lighttest.protect(function() { 259 | plugin.disconnect(); 260 | lighttest.check(true); 261 | lighttest.done(); 262 | }) 263 | } 264 | 265 | var plugin = new jailed.Plugin(path, api); 266 | plugin.whenFailed(whenFailed); 267 | }, 268 | 269 | 270 | 'Using the plugin several times': 271 | function() { 272 | var path = currentPath + 'stage09/plugin9.js'; 273 | var attempt = 1; 274 | var api1 = { 275 | checkAttempt : lighttest.protect(function(cb) { 276 | lighttest.check(attempt == 1); 277 | cb(); 278 | }), 279 | done : lighttest.protect(function() { 280 | plugin1.disconnect(); 281 | lighttest.check(true); 282 | step2(); 283 | }) 284 | } 285 | 286 | var step2 = lighttest.protect(function() { 287 | attempt = 2; 288 | 289 | var api2 = { 290 | checkAttempt : lighttest.protect(function(cb) { 291 | lighttest.check(attempt == 2); 292 | cb(); 293 | }), 294 | done : lighttest.protect(function() { 295 | plugin2.disconnect(); 296 | lighttest.check(true); 297 | lighttest.done(); 298 | }) 299 | } 300 | 301 | var plugin2 = new jailed.Plugin(path, api2); 302 | plugin2.whenFailed(whenFailed); 303 | }); 304 | 305 | var plugin1 = new jailed.Plugin(path, api1); 306 | plugin1.whenFailed(whenFailed); 307 | }, 308 | 309 | 310 | 311 | 'Two plugins with the same source but different interface': 312 | function() { 313 | var path = currentPath + 'stage10/plugin10.js'; 314 | 315 | var done1 = false; 316 | var done2 = false; 317 | 318 | var api1 = { 319 | getNum : lighttest.protect(function(cb){ 320 | cb(1); 321 | }), 322 | report : lighttest.protect(function(result, cb) { 323 | lighttest.check(result == 1); 324 | cb(); 325 | }), 326 | done : lighttest.protect(function() { 327 | done1 = true; 328 | finalize(); 329 | }) 330 | }; 331 | 332 | 333 | var api2 = { 334 | getNum : lighttest.protect(function(cb){ 335 | cb(2); 336 | }), 337 | report : lighttest.protect(function(result, cb) { 338 | lighttest.check(result == 2); 339 | cb(); 340 | }), 341 | done : lighttest.protect(function() { 342 | done2 = true; 343 | finalize(); 344 | }) 345 | }; 346 | 347 | var finalize = lighttest.protect(function() { 348 | if (done1 && done2) { 349 | plugin1.disconnect(); 350 | plugin2.disconnect(); 351 | lighttest.check(true); 352 | lighttest.done(); 353 | } 354 | }); 355 | 356 | var plugin1 = new jailed.Plugin(path, api1); 357 | var plugin2 = new jailed.Plugin(path, api2); 358 | plugin1.whenFailed(whenFailed); 359 | plugin2.whenFailed(whenFailed); 360 | }, 361 | 362 | 363 | 364 | 'Plugin disconnected right after creation': 365 | function() { 366 | 367 | var fail = lighttest.protect( 368 | function() { 369 | plugin.disconnect(); 370 | lighttest.check(false); 371 | lighttest.done(); 372 | } 373 | ); 374 | 375 | var disconnected = false; 376 | var disconnect = lighttest.protect( 377 | function() { 378 | disconnected = true; 379 | lighttest.check(true); 380 | } 381 | ); 382 | 383 | var finalize = lighttest.protect( 384 | function() { 385 | lighttest.check(disconnected); 386 | lighttest.done(); 387 | } 388 | ); 389 | 390 | var path = currentPath + 'stage11/plugin11.js'; 391 | var plugin = new jailed.Plugin(path); 392 | plugin.whenConnected(fail); 393 | plugin.whenDisconnected(disconnect); 394 | plugin.whenFailed(whenFailed); 395 | plugin.disconnect(); 396 | setTimeout(finalize, 1000); 397 | }, 398 | 399 | 400 | 401 | 'Plugin disconnected after connection': 402 | function() { 403 | 404 | var fail = lighttest.protect( 405 | function() { 406 | plugin.disconnect(); 407 | lighttest.check(false); 408 | lighttest.done(); 409 | } 410 | ); 411 | 412 | var connect = lighttest.protect( 413 | function() { 414 | lighttest.check(true); 415 | plugin.disconnect(); 416 | setTimeout(finalize, 1000); 417 | } 418 | ); 419 | 420 | var disconnected = false; 421 | var disconnect = lighttest.protect( 422 | function() { 423 | disconnected = true; 424 | lighttest.check(true); 425 | } 426 | ); 427 | 428 | var finalize = lighttest.protect( 429 | function() { 430 | lighttest.check(disconnected); 431 | lighttest.done(); 432 | } 433 | ); 434 | 435 | var path = currentPath + 'stage11/plugin11.js'; 436 | var plugin = new jailed.Plugin(path); 437 | plugin.whenConnected(connect); 438 | plugin.whenDisconnected(disconnect); 439 | plugin.whenFailed(whenFailed); 440 | 441 | }, 442 | 443 | 444 | 'Plugin disconnected by its method': 445 | function() { 446 | var path = currentPath + 'stage11/plugin11.js'; 447 | 448 | var connected = lighttest.protect(function() { 449 | lighttest.check(true); 450 | 451 | var val = 2; 452 | var cb = lighttest.protect(function(result) { 453 | lighttest.check(result == val*val); 454 | setTimeout( 455 | lighttest.protect(function() { 456 | plugin.remote.killYourself(); 457 | }), 1000 458 | ); 459 | }); 460 | 461 | plugin.remote.square(val, cb); 462 | }); 463 | 464 | var disconnected = lighttest.protect(function() { 465 | lighttest.check(true); 466 | lighttest.done(); 467 | }); 468 | 469 | var plugin = new jailed.Plugin(path); 470 | plugin.whenConnected(connected); 471 | plugin.whenDisconnected(disconnected); 472 | plugin.whenFailed(whenFailed); 473 | }, 474 | 475 | 476 | 'Plugin disconnected during initialization': 477 | function() { 478 | var path = currentPath + 'stage11/plugin11_1.js'; 479 | 480 | var failed = function() { 481 | plugin.disconnect(); 482 | lighttest.check(false); 483 | lighttest.done(); 484 | } 485 | 486 | var disconnected = function() { 487 | lighttest.check(true); 488 | lighttest.done(); 489 | } 490 | 491 | var plugin = new jailed.Plugin(path); 492 | plugin.whenConnected(failed); 493 | plugin.whenFailed(failed); 494 | plugin.whenDisconnected(disconnected); 495 | }, 496 | 497 | 498 | 499 | 'Mixed plugin loading sequence': 500 | function() { 501 | var path = currentPath + 'stage12/plugin12.js'; 502 | var plugin1, plugin2; 503 | 504 | var step1 = lighttest.protect(function() { 505 | var val = 2; 506 | var cb = lighttest.protect(function(result) { 507 | lighttest.check(result == val*val); 508 | step2(); 509 | }); 510 | 511 | plugin1.remote.square(val, cb); 512 | }); 513 | 514 | var step2 = lighttest.protect(function() { 515 | var connected2 = lighttest.protect(function() { 516 | var val = 7; 517 | var cb = lighttest.protect(function(result) { 518 | lighttest.check(result == val*val); 519 | step3(); 520 | }); 521 | 522 | plugin2.remote.square(val, cb); 523 | }); 524 | 525 | plugin2 = new jailed.Plugin(path); 526 | plugin2.whenConnected(connected2); 527 | plugin2.whenFailed(whenFailed); 528 | }); 529 | 530 | var step3 = lighttest.protect(function() { 531 | plugin1.disconnect(); 532 | setTimeout(step4, 1000); 533 | }); 534 | 535 | var step4 = lighttest.protect(function() { 536 | var val = 11; 537 | var cb = lighttest.protect(function(result) { 538 | lighttest.check(result == val*val); 539 | finalize(); 540 | }); 541 | 542 | plugin2.remote.square(val, cb); 543 | }); 544 | 545 | var finalize = lighttest.protect(function() { 546 | plugin2.disconnect(); 547 | lighttest.done(); 548 | }); 549 | 550 | plugin1 = new jailed.Plugin(path); 551 | plugin1.whenConnected(step1); 552 | plugin1.whenFailed(whenFailed); 553 | }, 554 | 555 | 556 | 'Executing function with several callbacks': 557 | function() { 558 | var path = currentPath + 'stage13/plugin13.js'; 559 | 560 | var api = { 561 | callMeBack : lighttest.protect(function(success, sCb, fCb) { 562 | if (success) { 563 | sCb(); 564 | } else { 565 | fCb(); 566 | } 567 | }), 568 | report : lighttest.protect(function(result, cb) { 569 | lighttest.check(result); 570 | cb(); 571 | }), 572 | done : lighttest.protect(function() { 573 | plugin.disconnect(); 574 | lighttest.check(true); 575 | lighttest.done(); 576 | }) 577 | } 578 | 579 | var plugin = new jailed.Plugin(path, api); 580 | plugin.whenFailed(whenFailed); 581 | }, 582 | 583 | 584 | 585 | 586 | 587 | 'Remote plugin': 588 | function() { 589 | var path = 'https://asvd.github.io/jailed/tests/plugin14.js'; 590 | 591 | var api = { 592 | square: lighttest.protect(function(val, cb) { 593 | cb(val*val); 594 | }), 595 | check: lighttest.protect(function(result, cb) { 596 | lighttest.check(result); 597 | cb(); 598 | }), 599 | done: lighttest.protect(function() { 600 | plugin.disconnect(); 601 | lighttest.done(); 602 | }) 603 | } 604 | 605 | var plugin = new jailed.Plugin(path, api); 606 | plugin.whenFailed(whenFailed); 607 | }, 608 | 609 | 610 | 611 | 612 | 'Plugin with infinite loop': 613 | function() { 614 | var pathBad = currentPath + 'stage15/plugin15_bad.js'; 615 | var pathGood = currentPath + 'stage15/plugin15_good.js'; 616 | 617 | var pluginGood; 618 | var pluginBad; 619 | 620 | var step1 = lighttest.protect( 621 | function() { 622 | lighttest.check(true); 623 | 624 | if (pluginBad.hasDedicatedThread()) { 625 | var cb = lighttest.protect( 626 | function() { 627 | // should never be called 628 | lighttest.check(false); 629 | pluginBad.disconnect(); 630 | lighttest.done(); 631 | } 632 | ); 633 | 634 | pluginBad.remote.infinite(cb); 635 | 636 | setTimeout(step2, 2000); 637 | } else { 638 | // thread not obtained because of the browser 639 | pluginBad.disconnect(); 640 | lighttest.done(); 641 | } 642 | } 643 | ); 644 | 645 | var step2 = lighttest.protect( 646 | function() { 647 | pluginBad.disconnect(); 648 | setTimeout(step3, 1000); 649 | } 650 | ); 651 | 652 | var step3 = lighttest.protect( 653 | function() { 654 | lighttest.check(true); 655 | 656 | pluginBad = new jailed.Plugin(pathBad); 657 | pluginBad.whenConnected(step4); 658 | pluginBad.whenFailed(whenFailed); 659 | } 660 | ); 661 | 662 | var step4 = lighttest.protect( 663 | function() { 664 | var cb = lighttest.protect( 665 | function() { 666 | // should never be called 667 | lighttest.check(false); 668 | pluginBad.disconnect(); 669 | lighttest.done(); 670 | } 671 | ); 672 | 673 | pluginBad.remote.infinite(cb); 674 | 675 | setTimeout(step5, 2000); 676 | } 677 | ); 678 | 679 | var step5 = lighttest.protect( 680 | function() { 681 | lighttest.check(true); 682 | 683 | pluginGood = new jailed.Plugin(pathGood); 684 | pluginGood.whenConnected(step6); 685 | pluginGood.whenFailed(whenFailed); 686 | } 687 | ); 688 | 689 | var step6 = lighttest.protect( 690 | function() { 691 | var val = 8; 692 | var cb = lighttest.protect(function(result) { 693 | lighttest.check(result == val*val); 694 | pluginGood.disconnect(); 695 | pluginBad.disconnect(); 696 | lighttest.done(); 697 | }); 698 | 699 | pluginGood.remote.square(val, cb); 700 | } 701 | ); 702 | 703 | pluginBad = new jailed.Plugin(pathBad); 704 | pluginBad.whenConnected(step1); 705 | pluginBad.whenFailed(whenFailed); 706 | }, 707 | 708 | 709 | 'Permission restriction': 710 | function() { 711 | var path = currentPath + 'stage16/plugin16.js'; 712 | 713 | var api = { 714 | check : lighttest.protect( 715 | function(result, cb) { 716 | lighttest.check(result); 717 | cb(); 718 | } 719 | ), 720 | done : lighttest.protect( 721 | function() { 722 | plugin.disconnect(); 723 | lighttest.check(true); 724 | lighttest.done(); 725 | } 726 | ) 727 | }; 728 | 729 | var plugin = new jailed.Plugin(path, api); 730 | plugin.whenFailed(whenFailed); 731 | }, 732 | 733 | 734 | 735 | 736 | 'Broken plugin': 737 | function() { 738 | var path = currentPath + 'stage17/plugin17.js'; 739 | 740 | var plugin = new jailed.Plugin(path); 741 | 742 | var fail = lighttest.protect( 743 | function() { 744 | plugin.disconnect(); 745 | lighttest.check(false); 746 | lighttest.done(); 747 | } 748 | ); 749 | 750 | plugin.whenConnected(fail); 751 | 752 | plugin.whenFailed( 753 | lighttest.protect( 754 | function() { 755 | lighttest.check(true); 756 | lighttest.done(); 757 | } 758 | ) 759 | ); 760 | }, 761 | 762 | 763 | 764 | 'Broken dynamic plugin': 765 | function() { 766 | var code = 'u'; 767 | // var code = 'auaa } u((uu&'; 768 | 769 | var plugin = new jailed.DynamicPlugin(code); 770 | 771 | var connect = lighttest.protect( 772 | function() { 773 | plugin.disconnect(); 774 | lighttest.check(false); 775 | lighttest.done(); 776 | } 777 | ); 778 | 779 | var disconnected = false; 780 | var disconnect = lighttest.protect( 781 | function() { 782 | lighttest.check(true); 783 | disconnected = true; 784 | } 785 | ); 786 | 787 | var failed = false; 788 | var fail = lighttest.protect( 789 | function() { 790 | lighttest.check(true); 791 | failed = true; 792 | setTimeout(finalize,500); 793 | } 794 | ); 795 | 796 | var finalize = lighttest.protect( 797 | function() { 798 | plugin.disconnect(); 799 | lighttest.check(failed&&disconnected); 800 | lighttest.done(); 801 | } 802 | ); 803 | 804 | plugin.whenConnected(connect); 805 | plugin.whenDisconnected(disconnect); 806 | plugin.whenFailed(fail); 807 | }, 808 | 809 | 810 | 811 | 'Broken remote plugin': 812 | function() { 813 | var path = 'https://asvd.github.io/jailed/tests/plugin18.js'; 814 | 815 | var plugin = new jailed.Plugin(path); 816 | 817 | var connect = lighttest.protect( 818 | function() { 819 | plugin.disconnect(); 820 | lighttest.check(false); 821 | lighttest.done(); 822 | } 823 | ); 824 | 825 | var disconnected = false; 826 | var disconnect = lighttest.protect( 827 | function() { 828 | lighttest.check(true); 829 | disconnected = true; 830 | } 831 | ); 832 | 833 | var failed = false; 834 | var fail = lighttest.protect( 835 | function() { 836 | lighttest.check(true); 837 | failed = true; 838 | setTimeout(finalize,500); 839 | } 840 | ); 841 | 842 | var finalize = lighttest.protect( 843 | function() { 844 | plugin.disconnect(); 845 | lighttest.check(failed&&disconnected); 846 | lighttest.done(); 847 | } 848 | ); 849 | 850 | plugin.whenConnected(connect); 851 | plugin.whenDisconnected(disconnect); 852 | plugin.whenFailed(fail); 853 | }, 854 | 855 | 856 | 857 | 858 | 'Nonexisting plugin': 859 | function() { 860 | var path = currentPath + 'no_such_path.js'; 861 | var plugin = new jailed.Plugin(path); 862 | 863 | var connect = lighttest.protect( 864 | function() { 865 | plugin.disconnect(); 866 | lighttest.check(false); 867 | lighttest.done(); 868 | } 869 | ); 870 | 871 | var disconnected = false; 872 | var disconnect = lighttest.protect( 873 | function() { 874 | lighttest.check(true); 875 | disconnected = true; 876 | } 877 | ); 878 | 879 | var failed = false; 880 | var fail = lighttest.protect( 881 | function() { 882 | lighttest.check(true); 883 | failed = true; 884 | setTimeout(finalize,500); 885 | } 886 | ); 887 | 888 | var finalize = lighttest.protect( 889 | function() { 890 | lighttest.check(failed&&disconnected); 891 | plugin.disconnect(); 892 | lighttest.done(); 893 | } 894 | ); 895 | 896 | plugin.whenConnected(connect); 897 | plugin.whenDisconnected(disconnect); 898 | plugin.whenFailed(fail); 899 | }, 900 | 901 | 902 | 'Nonexisting remote plugin': 903 | function() { 904 | var path = 'https://asvd.github.io/no_such_path.js'; 905 | var plugin = new jailed.Plugin(path); 906 | 907 | var connect = lighttest.protect( 908 | function() { 909 | plugin.disconnect(); 910 | lighttest.check(false); 911 | lighttest.done(); 912 | } 913 | ); 914 | 915 | var disconnected = false; 916 | var disconnect = lighttest.protect( 917 | function() { 918 | lighttest.check(true); 919 | disconnected = true; 920 | } 921 | ); 922 | 923 | var failed = false; 924 | var fail = lighttest.protect( 925 | function() { 926 | lighttest.check(true); 927 | failed = true; 928 | setTimeout(finalize,500); 929 | } 930 | ); 931 | 932 | var finalize = lighttest.protect( 933 | function() { 934 | plugin.disconnect(); 935 | lighttest.check(failed&&disconnected); 936 | lighttest.done(); 937 | } 938 | ); 939 | 940 | plugin.whenConnected(connect); 941 | plugin.whenDisconnected(disconnect); 942 | plugin.whenFailed(fail); 943 | }, 944 | 945 | 946 | 'Broken plugin method': 947 | function() { 948 | var step1 = lighttest.protect( 949 | function() { 950 | var cb = lighttest.protect( 951 | function() { 952 | plugin.disconnect(); 953 | clearTimeout(timeout); 954 | lighttest.check(false); 955 | lighttest.done(); 956 | } 957 | ); 958 | 959 | plugin.remote.broken(cb); 960 | 961 | var timeout = setTimeout(step2, 1000); 962 | } 963 | ); 964 | 965 | var step2 = lighttest.protect( 966 | function() { 967 | var timeout = setTimeout( 968 | lighttest.protect( 969 | function() { 970 | plugin.disconnect(); 971 | lighttest.check(false); 972 | lighttest.done(); 973 | } 974 | ), 975 | 2000 976 | ); 977 | 978 | var cb = lighttest.protect( 979 | function() { 980 | clearTimeout(timeout); 981 | lighttest.check(true); 982 | step3(); 983 | } 984 | ); 985 | 986 | plugin.remote.brokenDelayed(cb); 987 | } 988 | ); 989 | 990 | var step3 = lighttest.protect( 991 | function() { 992 | var cb = lighttest.protect( 993 | function() { 994 | clearTimeout(timeout); 995 | plugin.disconnect(); 996 | lighttest.check(false); 997 | lighttest.done(); 998 | } 999 | ); 1000 | 1001 | plugin.remote.broken(cb); 1002 | var timeout = setTimeout(step4, 500); 1003 | } 1004 | ); 1005 | 1006 | var step4 = lighttest.protect( 1007 | function() { 1008 | var val = 6; 1009 | var cb = lighttest.protect( 1010 | function(result) { 1011 | plugin.disconnect(); 1012 | lighttest.check(result = val*val); 1013 | lighttest.done(); 1014 | } 1015 | ); 1016 | 1017 | plugin.remote.square(val, cb); 1018 | } 1019 | ); 1020 | 1021 | 1022 | var path = currentPath + 'stage19/plugin19.js'; 1023 | var plugin = new jailed.Plugin(path); 1024 | plugin.whenConnected(step1); 1025 | plugin.whenFailed(whenFailed); 1026 | }, 1027 | 1028 | 1029 | 'Broken application method': 1030 | function() { 1031 | var api = { 1032 | // intentionally not protected, must fail 1033 | broken: function(cb) { 1034 | somethingWrong(); 1035 | cb(); 1036 | }, 1037 | 1038 | // intentionally not protected, must fail 1039 | brokenDelayed: function(cb) { 1040 | setTimeout(cb, 500); 1041 | somethingWrong(); 1042 | }, 1043 | 1044 | square: lighttest.protect( 1045 | function(val, cb) { 1046 | cb(val*val); 1047 | } 1048 | ), 1049 | 1050 | check: lighttest.protect( 1051 | function(result, cb){ 1052 | lighttest.check(result); 1053 | cb(); 1054 | } 1055 | ), 1056 | 1057 | done: lighttest.protect( 1058 | function(){ 1059 | plugin.disconnect(); 1060 | lighttest.done(); 1061 | } 1062 | ) 1063 | }; 1064 | 1065 | var path = currentPath + 'stage20/plugin20.js'; 1066 | var plugin = new jailed.Plugin(path, api); 1067 | plugin.whenFailed(whenFailed); 1068 | }, 1069 | 1070 | 1071 | 1072 | 'Several plugin methods execution': 1073 | function() { 1074 | var cubeFinished = false; 1075 | var squareFinished = false; 1076 | 1077 | var step1 = lighttest.protect( 1078 | function() { 1079 | var valCube = 7; 1080 | 1081 | var cbCube = lighttest.protect( 1082 | function(result) { 1083 | lighttest.check(result == valCube*valCube*valCube); 1084 | cubeFinished = true; 1085 | finalize(); 1086 | } 1087 | ); 1088 | 1089 | plugin.remote.cubeDelayed(valCube, cbCube); 1090 | step2(); 1091 | } 1092 | ); 1093 | 1094 | 1095 | 1096 | var step2 = lighttest.protect( 1097 | function() { 1098 | var val = 8; 1099 | 1100 | var cb = lighttest.protect( 1101 | function(result) { 1102 | lighttest.check(result == val*val); 1103 | squareFinished = true; 1104 | finalize(); 1105 | } 1106 | ); 1107 | 1108 | plugin.remote.square(val, cb); 1109 | 1110 | } 1111 | ); 1112 | 1113 | var finalize = lighttest.protect( 1114 | function() { 1115 | if (cubeFinished && squareFinished) { 1116 | plugin.disconnect(); 1117 | lighttest.check(true); 1118 | lighttest.done(); 1119 | } 1120 | } 1121 | ); 1122 | 1123 | var path = currentPath + 'stage21/plugin21.js'; 1124 | var plugin = new jailed.Plugin(path); 1125 | plugin.whenFailed(whenFailed); 1126 | plugin.whenConnected(step1); 1127 | }, 1128 | 1129 | 1130 | 1131 | 'Several application methods execution': 1132 | function() { 1133 | var api = { 1134 | square: lighttest.protect( 1135 | function(val, cb) { 1136 | cb(val*val); 1137 | } 1138 | ), 1139 | cubeDelayed: lighttest.protect( 1140 | function(val, cb) { 1141 | setTimeout( 1142 | lighttest.protect( 1143 | function() { 1144 | cb(val*val*val); 1145 | } 1146 | ), 1000 1147 | ); 1148 | } 1149 | ), 1150 | check: lighttest.protect( 1151 | function(result, cb){ 1152 | lighttest.check(result); 1153 | cb(); 1154 | } 1155 | ), 1156 | 1157 | done: lighttest.protect( 1158 | function(){ 1159 | plugin.disconnect(); 1160 | lighttest.done(); 1161 | } 1162 | ) 1163 | } 1164 | 1165 | var path = currentPath + 'stage22/plugin22.js'; 1166 | var plugin = new jailed.Plugin(path, api); 1167 | plugin.whenFailed(whenFailed); 1168 | }, 1169 | 1170 | 1171 | 'Plugin method with several callbacks': 1172 | function() { 1173 | var step1 = lighttest.protect( 1174 | function() { 1175 | var cb0 = lighttest.protect( 1176 | function() { 1177 | lighttest.check(false); 1178 | step2(); 1179 | } 1180 | ); 1181 | 1182 | var cb1 = lighttest.protect( 1183 | function() { 1184 | lighttest.check(true); 1185 | step2(); 1186 | } 1187 | ); 1188 | 1189 | plugin.remote.callback(1, cb0, cb1); 1190 | } 1191 | ); 1192 | 1193 | var step2 = lighttest.protect( 1194 | function() { 1195 | var cb0 = lighttest.protect( 1196 | function() { 1197 | lighttest.check(true); 1198 | finalize(); 1199 | } 1200 | ); 1201 | 1202 | var cb1 = lighttest.protect( 1203 | function() { 1204 | lighttest.check(false); 1205 | finalize(); 1206 | } 1207 | ); 1208 | 1209 | plugin.remote.callback(0, cb0, cb1); 1210 | } 1211 | ); 1212 | 1213 | var finalize = lighttest.protect( 1214 | function() { 1215 | plugin.disconnect(); 1216 | lighttest.done(); 1217 | } 1218 | ); 1219 | 1220 | var path = currentPath + 'stage23/plugin23.js'; 1221 | var plugin = new jailed.Plugin(path); 1222 | plugin.whenFailed(whenFailed); 1223 | plugin.whenConnected(step1); 1224 | }, 1225 | 1226 | 1227 | 1228 | 'Application method with several callbacks': 1229 | function() { 1230 | var api = { 1231 | callback: lighttest.protect( 1232 | function(num, cb0, cb1) { 1233 | if (num == 0) { 1234 | cb0(); 1235 | } else { 1236 | cb1(); 1237 | } 1238 | } 1239 | ), 1240 | 1241 | check: lighttest.protect( 1242 | function(result, cb){ 1243 | lighttest.check(result); 1244 | cb(); 1245 | } 1246 | ), 1247 | 1248 | done: lighttest.protect( 1249 | function(){ 1250 | plugin.disconnect(); 1251 | lighttest.done(); 1252 | } 1253 | ) 1254 | }; 1255 | 1256 | var path = currentPath + 'stage24/plugin24.js'; 1257 | var plugin = new jailed.Plugin(path, api); 1258 | plugin.whenFailed(whenFailed); 1259 | }, 1260 | 1261 | 1262 | 1263 | 'Two-sided communication, initiated by the plugin': 1264 | function() { 1265 | var api = { 1266 | squareDelayed: lighttest.protect( 1267 | function(val, cb) { 1268 | plugin.remote.wait( 1269 | lighttest.protect( 1270 | function() { 1271 | cb(val*val); 1272 | } 1273 | ) 1274 | ); 1275 | } 1276 | ), 1277 | 1278 | check: lighttest.protect( 1279 | function(result, cb){ 1280 | lighttest.check(result); 1281 | cb(); 1282 | } 1283 | ), 1284 | 1285 | done: lighttest.protect( 1286 | function(){ 1287 | plugin.disconnect(); 1288 | lighttest.done(); 1289 | } 1290 | ) 1291 | }; 1292 | 1293 | var path = currentPath + 'stage25/plugin25.js'; 1294 | var plugin = new jailed.Plugin(path, api); 1295 | plugin.whenFailed(whenFailed); 1296 | }, 1297 | 1298 | 1299 | 1300 | 'Two-sided communication, initiated by the application': 1301 | function() { 1302 | var api = { 1303 | wait: function(cb) { 1304 | setTimeout(cb, 1000); 1305 | } 1306 | }; 1307 | 1308 | var step1 = lighttest.protect( 1309 | function() { 1310 | var val = 54; 1311 | var cb = lighttest.protect( 1312 | function(result) { 1313 | plugin.disconnect(); 1314 | lighttest.check(result == val*val); 1315 | lighttest.done(); 1316 | } 1317 | ); 1318 | 1319 | plugin.remote.squareDelayed(val, cb); 1320 | } 1321 | ); 1322 | 1323 | var path = currentPath + 'stage26/plugin26.js'; 1324 | var plugin = new jailed.Plugin(path, api); 1325 | plugin.whenFailed(whenFailed); 1326 | plugin.whenConnected(step1); 1327 | }, 1328 | 1329 | 1330 | 'Calling plugin callbacks from the same arguments several times': 1331 | function() { 1332 | var api = { 1333 | callme : function(cb0, cb1) { 1334 | var step1 =function() { 1335 | setTimeout(finalize, 1000); 1336 | 1337 | try { 1338 | cb1(); 1339 | lighttest.check(false); 1340 | } catch(e) { 1341 | lighttest.check(true); 1342 | throw e; 1343 | } 1344 | } 1345 | 1346 | setTimeout(step1, 1000); 1347 | 1348 | cb0(); 1349 | 1350 | try { 1351 | cb0(); 1352 | lighttest.check(false); 1353 | } catch(e) { 1354 | lighttest.check(true); 1355 | throw e; 1356 | } 1357 | 1358 | }, 1359 | 1360 | check: lighttest.protect( 1361 | function(result){ 1362 | lighttest.check(result); 1363 | } 1364 | ) 1365 | } 1366 | 1367 | var finalize = lighttest.protect( 1368 | function() { 1369 | plugin.disconnect(); 1370 | lighttest.done(); 1371 | } 1372 | ); 1373 | 1374 | var path = currentPath + 'stage27/plugin27.js'; 1375 | var plugin = new jailed.Plugin(path, api); 1376 | plugin.whenFailed(whenFailed); 1377 | }, 1378 | 1379 | 1380 | 1381 | 'Calling application callbacks from the same arguments several times': 1382 | function() { 1383 | var init = lighttest.protect( 1384 | function() { 1385 | var notYetCalled = true; 1386 | var cb = lighttest.protect( 1387 | function() { 1388 | lighttest.check(notYetCalled); 1389 | notYetCalled = false; 1390 | } 1391 | ); 1392 | 1393 | plugin.remote.callme(cb, cb); 1394 | } 1395 | ); 1396 | 1397 | var api = { 1398 | check: lighttest.protect( 1399 | function(result){ 1400 | lighttest.check(result); 1401 | } 1402 | ), 1403 | 1404 | done: lighttest.protect( 1405 | function(){ 1406 | plugin.disconnect(); 1407 | lighttest.done(); 1408 | } 1409 | ) 1410 | } 1411 | 1412 | var path = currentPath + 'stage28/plugin28.js'; 1413 | var plugin = new jailed.Plugin(path, api); 1414 | plugin.whenFailed(whenFailed); 1415 | plugin.whenConnected(init); 1416 | }, 1417 | 1418 | 1419 | 'Delayed plugin error': 1420 | function() { 1421 | var init = lighttest.protect( 1422 | function() { 1423 | var cb = lighttest.protect( 1424 | function() { 1425 | plugin.disconnect(); 1426 | lighttest.check(true); 1427 | } 1428 | ); 1429 | 1430 | // Node.js child process will exit, 1431 | // Worker will throw, but proceed 1432 | plugin.remote.brokenDelayed(cb); 1433 | } 1434 | ); 1435 | 1436 | var disconnect = lighttest.protect( 1437 | function() { 1438 | lighttest.check(true); 1439 | setTimeout(finalize, 300); 1440 | } 1441 | ); 1442 | 1443 | var finalize = lighttest.protect( 1444 | function() { 1445 | lighttest.done(); 1446 | } 1447 | ); 1448 | 1449 | 1450 | var path = currentPath + 'stage29/plugin29.js'; 1451 | var plugin = new jailed.Plugin(path); 1452 | plugin.whenFailed(whenFailed); 1453 | plugin.whenConnected(init); 1454 | plugin.whenDisconnected(disconnect); 1455 | }, 1456 | 1457 | 1458 | 1459 | 'Subscribing non-functions to events in the application environment': 1460 | function() { 1461 | var plugin = new jailed.DynamicPlugin(''); 1462 | 1463 | var step1 = function() { 1464 | setTimeout(step2, 100); 1465 | try { 1466 | plugin.whenConnected([]); 1467 | lighttest.check(false); 1468 | } catch (e) { 1469 | lighttest.check(true); 1470 | throw e; 1471 | } 1472 | } 1473 | 1474 | var step2 = function() { 1475 | setTimeout(step3, 100); 1476 | try { 1477 | plugin.whenFailed('something'); 1478 | lighttest.check(false); 1479 | } catch (e) { 1480 | lighttest.check(true); 1481 | throw e; 1482 | } 1483 | } 1484 | 1485 | var step3 = function() { 1486 | setTimeout(step4, 100); 1487 | try { 1488 | plugin.whenDisconnected(new Date); 1489 | lighttest.check(false); 1490 | } catch (e) { 1491 | lighttest.check(true); 1492 | throw e; 1493 | } 1494 | } 1495 | 1496 | var step4 = function() { 1497 | plugin.whenConnected(step5); 1498 | } 1499 | 1500 | var step5 = function() { 1501 | plugin.disconnect(); 1502 | lighttest.check(true); 1503 | lighttest.done(); 1504 | } 1505 | 1506 | setTimeout(step1, 100); 1507 | }, 1508 | 1509 | 1510 | 'Subscribing non-functions to events in the plugin environment': 1511 | function() { 1512 | var fail = lighttest.protect( 1513 | function() { 1514 | plugin.disconnect(); 1515 | lighttest.check(true); 1516 | setTimeout(finalize, 300); 1517 | } 1518 | ); 1519 | 1520 | var connect = lighttest.protect( 1521 | function() { 1522 | plugin.disconnect(); 1523 | lighttest.check(false); 1524 | lighttest.done(); 1525 | } 1526 | ); 1527 | 1528 | var disconnected = false; 1529 | var disconnect = lighttest.protect( 1530 | function() { 1531 | lighttest.check(true); 1532 | disconnected = true; 1533 | } 1534 | ); 1535 | 1536 | var finalize = lighttest.protect( 1537 | function() { 1538 | lighttest.check(disconnected); 1539 | lighttest.done(); 1540 | } 1541 | ); 1542 | 1543 | 1544 | var path = currentPath + 'stage30/plugin30.js'; 1545 | var plugin = new jailed.Plugin(path); 1546 | plugin.whenFailed(fail); 1547 | plugin.whenConnected(connect); 1548 | plugin.whenDisconnected(disconnect); 1549 | }, 1550 | 1551 | 1552 | 1553 | 'Delayed event subscription in the application': 1554 | function() { 1555 | var stage1 = lighttest.protect( 1556 | function() { 1557 | var plugin = new jailed.DynamicPlugin(''); 1558 | 1559 | var connectionCompleted = false; 1560 | 1561 | var tryConnect = lighttest.protect( 1562 | function() { 1563 | plugin.whenConnected(connected); 1564 | setTimeout(connectCheck, 4000); 1565 | } 1566 | ); 1567 | 1568 | var connected = function() { 1569 | connectionCompleted = true; 1570 | } 1571 | 1572 | var connectCheck = lighttest.protect( 1573 | function() { 1574 | plugin.disconnect(); 1575 | lighttest.check(connectionCompleted); 1576 | setTimeout(stage2, 300); 1577 | } 1578 | ); 1579 | 1580 | setTimeout(tryConnect, 300); 1581 | } 1582 | ); 1583 | 1584 | 1585 | var stage2 = lighttest.protect( 1586 | function() { 1587 | var plugin = new jailed.DynamicPlugin('uau}'); 1588 | 1589 | var failureCompleted = false; 1590 | 1591 | var tryFailure = lighttest.protect( 1592 | function() { 1593 | plugin.whenFailed(failed); 1594 | setTimeout(failureCheck, 3500); 1595 | } 1596 | ); 1597 | 1598 | var failed = function() { 1599 | failureCompleted = true; 1600 | } 1601 | 1602 | var failureCheck = lighttest.protect( 1603 | function() { 1604 | plugin.disconnect(); 1605 | lighttest.check(failureCompleted); 1606 | setTimeout(stage3, 300); 1607 | } 1608 | ); 1609 | 1610 | setTimeout(tryFailure, 300); 1611 | } 1612 | ); 1613 | 1614 | 1615 | 1616 | var stage3 = lighttest.protect( 1617 | function() { 1618 | var plugin = new jailed.DynamicPlugin('application.disconnect();'); 1619 | 1620 | var disconnectCompleted = false; 1621 | 1622 | var tryDisconnect = lighttest.protect( 1623 | function() { 1624 | plugin.whenDisconnected(disconnected); 1625 | } 1626 | ); 1627 | 1628 | var disconnected = function() { 1629 | disconnectCompleted = true; 1630 | } 1631 | 1632 | var disconnectCheck = lighttest.protect( 1633 | function() { 1634 | plugin.disconnect(); 1635 | lighttest.check(disconnectCompleted); 1636 | lighttest.done(); 1637 | } 1638 | ); 1639 | 1640 | setTimeout(tryDisconnect, 300); 1641 | setTimeout(disconnectCheck, 5000); 1642 | } 1643 | ); 1644 | 1645 | 1646 | stage1(); 1647 | }, 1648 | 1649 | 1650 | 1651 | 1652 | 'Delayed event subscription in the plugin': 1653 | function() { 1654 | var api = { 1655 | check : lighttest.protect(function(result, cb){ 1656 | lighttest.check(result); 1657 | cb(); 1658 | }), 1659 | 1660 | done : lighttest.protect(function() { 1661 | plugin.disconnect(); 1662 | lighttest.check(true); 1663 | lighttest.done(); 1664 | }) 1665 | } 1666 | 1667 | var path = currentPath + 'stage31/plugin31.js'; 1668 | var plugin = new jailed.Plugin(path, api); 1669 | 1670 | }, 1671 | 1672 | 1673 | 1674 | 'Subscibing to Whenable events several times before and after emission': 1675 | function() { 1676 | var pluginFinished = 0; 1677 | var api = { 1678 | check : lighttest.protect(function(result, cb){ 1679 | lighttest.check(result); 1680 | cb(); 1681 | }), 1682 | 1683 | finished : lighttest.protect(function() { 1684 | lighttest.check(true); 1685 | pluginFinished++; 1686 | disconnect(); 1687 | }) 1688 | } 1689 | 1690 | var beforeConnect1Finished = 0; 1691 | var beforeConnect1 = lighttest.protect( 1692 | function() { 1693 | beforeConnect1Finished++; 1694 | lighttest.check(true); 1695 | finalize(); 1696 | } 1697 | ); 1698 | 1699 | var beforeConnect2Finished = 0; 1700 | var beforeConnect2 = lighttest.protect( 1701 | function() { 1702 | beforeConnect2Finished++; 1703 | lighttest.check(true); 1704 | finalize(); 1705 | } 1706 | ); 1707 | 1708 | 1709 | var beforeDisconnect1Finished = 0; 1710 | var beforeDisconnect1 = lighttest.protect( 1711 | function() { 1712 | beforeDisconnect1Finished++; 1713 | lighttest.check(true); 1714 | finalize(); 1715 | } 1716 | ); 1717 | 1718 | var beforeDisconnect2Finished = 0; 1719 | var beforeDisconnect2 = lighttest.protect( 1720 | function() { 1721 | beforeDisconnect2Finished++; 1722 | lighttest.check(true); 1723 | finalize(); 1724 | } 1725 | ); 1726 | 1727 | 1728 | var afterConnect1Finished = 0; 1729 | var afterConnect1 = lighttest.protect( 1730 | function() { 1731 | afterConnect1Finished++; 1732 | lighttest.check(true); 1733 | finalize(); 1734 | } 1735 | ); 1736 | 1737 | var afterConnect2Finished = 0; 1738 | var afterConnect2 = lighttest.protect( 1739 | function() { 1740 | afterConnect2Finished++; 1741 | lighttest.check(true); 1742 | finalize(); 1743 | } 1744 | ); 1745 | 1746 | 1747 | var afterDisconnect1Finished = 0; 1748 | var afterDisconnect1 = lighttest.protect( 1749 | function() { 1750 | afterDisconnect1Finished++; 1751 | lighttest.check(true); 1752 | finalize(); 1753 | } 1754 | ); 1755 | 1756 | var afterDisconnect2Finished = 0; 1757 | var afterDisconnect2 = lighttest.protect( 1758 | function() { 1759 | afterDisconnect2Finished++; 1760 | lighttest.check(true); 1761 | finalize(); 1762 | } 1763 | ); 1764 | 1765 | 1766 | var finalize = lighttest.protect( 1767 | function() { 1768 | if ( 1769 | pluginFinished == 1 && 1770 | beforeConnect1Finished == 1 && 1771 | beforeConnect2Finished == 1 && 1772 | beforeDisconnect1Finished == 1 && 1773 | beforeDisconnect2Finished == 1 && 1774 | afterConnect1Finished == 1 && 1775 | afterConnect2Finished == 1 && 1776 | afterDisconnect1Finished == 1 && 1777 | afterDisconnect2Finished == 1 1778 | ) { 1779 | lighttest.check( 1780 | pluginFinished == 1 && 1781 | beforeConnect1Finished == 1 && 1782 | beforeConnect2Finished == 1 && 1783 | beforeDisconnect1Finished == 1 && 1784 | beforeDisconnect2Finished == 1 && 1785 | afterConnect1Finished == 1 && 1786 | afterConnect2Finished == 1 && 1787 | afterDisconnect1Finished == 1 && 1788 | afterDisconnect2Finished == 1 1789 | ); 1790 | lighttest.done(); 1791 | } 1792 | } 1793 | ); 1794 | 1795 | var disconnect = lighttest.protect( 1796 | function() { 1797 | plugin.disconnect(); 1798 | setTimeout( 1799 | lighttest.protect( 1800 | function() { 1801 | plugin.whenDisconnected(afterDisconnect); 1802 | } 1803 | ), 300 1804 | ); 1805 | } 1806 | ); 1807 | 1808 | 1809 | var afterDisconnect = lighttest.protect( 1810 | function() { 1811 | plugin.whenConnected(afterConnect1); 1812 | plugin.whenConnected(afterConnect2); 1813 | plugin.whenDisconnected(afterDisconnect1); 1814 | plugin.whenDisconnected(afterDisconnect2); 1815 | } 1816 | ); 1817 | 1818 | 1819 | var path = currentPath + 'stage32/plugin32.js'; 1820 | var plugin = new jailed.Plugin(path, api); 1821 | plugin.whenConnected(beforeConnect1); 1822 | plugin.whenConnected(beforeConnect2); 1823 | plugin.whenDisconnected(beforeDisconnect1); 1824 | plugin.whenDisconnected(beforeDisconnect2); 1825 | } 1826 | 1827 | 1828 | }; 1829 | 1830 | 1831 | lighttest.start(tests); 1832 | 1833 | 1834 | -------------------------------------------------------------------------------- /tests/tests_node.js: -------------------------------------------------------------------------------- 1 | require('./lighttest.js'); 2 | jailed = require('../lib/jailed.js'); 3 | require('./tests.js'); 4 | -------------------------------------------------------------------------------- /tests/tests_web.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | API-Sandbox test 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | --------------------------------------------------------------------------------