├── .github └── workflows │ └── main.yml ├── .gitignore ├── LICENSE ├── README.md ├── action.yml ├── dist ├── bridge.js ├── contextify.js ├── events.js ├── fixasync.js ├── index.js ├── licenses.txt ├── sandbox.js ├── setup-node-sandbox.js └── setup-sandbox.js ├── how-to-build.md ├── index.js ├── package-lock.json └── package.json /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | on: [push] 2 | 3 | jobs: 4 | delete-workflow-runs: 5 | runs-on: ubuntu-latest 6 | name: A demo job to delete workflow runs 7 | steps: 8 | - name: Checkout 9 | uses: actions/checkout@v2 10 | 11 | - name: Delete workflow runs 12 | uses: ./ # Uses an action in the root directory 13 | id: delete-old-workflow-runs 14 | with: 15 | repository: MajorScruffy/delete-old-workflow-runs 16 | workflow: ".github/workflows/main.yml" 17 | # older-than-seconds: 3600 18 | # created-before: "2021-12-08T16:34:00Z" 19 | # actor: test@test.com 20 | # branch: main 21 | # event: push 22 | # event: completed 23 | # what-if: true 24 | env: 25 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | 84 | # Gatsby files 85 | .cache/ 86 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 87 | # https://nextjs.org/blog/next-9-1#public-directory-support 88 | # public 89 | 90 | # vuepress build output 91 | .vuepress/dist 92 | 93 | # Serverless directories 94 | .serverless/ 95 | 96 | # FuseBox cache 97 | .fusebox/ 98 | 99 | # DynamoDB Local files 100 | .dynamodb/ 101 | 102 | # TernJS port file 103 | .tern-port 104 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Stefan Mihai Stanescu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # delete-old-workflow-runs 2 | A GitHub Action used to delete workflow runs from a repository. 3 | 4 | Behind the scenes it uses [Octokit request-action](https://github.com/octokit/request-action) to call the GitHub API, so you'll need to add the GITHUB_TOKEN as an environment variable: 5 | ``` 6 | env: 7 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 8 | ``` 9 | 10 | ## Example usage 11 | ``` 12 | steps: 13 | - name: Delete workflow runs 14 | uses: MajorScruffy/delete-old-workflow-runs@v0.2.0 15 | with: 16 | repository: MajorScruffy/delete-old-workflow-runs # replace this with your own repository 17 | older-than-seconds: 86400 # remove all workflow runs older than 1 day 18 | env: 19 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 20 | ``` 21 | 22 | See also the [Demo workflow](.github/workflows/main.yml). 23 | 24 | ## Inputs 25 | 26 | ### `repository` **required** 27 | This is the repository for which to delete workflow runs. Should be in the format `{user}/{repository}` 28 | 29 | ### `workflow` 30 | The path to the workflow's .yml file. For example `.github/workflows/main.yml`. Use this parameter in case you have multiple workflows in the same repository, but you only want to delete the workflow runs for a single workflow. 31 | 32 | ### `older-than-seconds` 33 | Use this parameter to delete only the workflow runs that are older than the given number of seconds. If this parameter is set, the `created-before` parameter will be ignored. 34 | 35 | ### `created-before` 36 | Use this parameter to delete only the workflow runs that were created before the given date in ISO 8601 format. For example, `2021-12-08T16:34:00Z`. This parameter is ignored if `older-than-seconds` is set. 37 | 38 | ### `actor` 39 | Delete only the workflow runs for the given GitHub user. This is the e-mail address of the user who pushed the code. 40 | 41 | ### `branch` 42 | Delete only the workflow runs on the given branch. 43 | 44 | ### `event` 45 | Delete only the workflow runs triggered by the given event type. For example, push, pull_request or issue. 46 | 47 | ### `status` 48 | Delete only the workflow runs with the give status. Can be one of queued, in_progress, or completed. 49 | 50 | ### `what-if` 51 | Set to true to preview the changes made by this action without deleting any workflow runs. Defaults to false. 52 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: Delete old workflow runs 2 | description: Delete workflow runs that match the given filters 3 | inputs: 4 | repository: 5 | description: The repository for which to delete workflow runs. 6 | required: true 7 | workflow: 8 | description: The path to the workflow's .yml file. 9 | required: false 10 | older-than-seconds: 11 | description: Consider only the workflows older than the given number of seconds. 12 | required: false 13 | created-before: 14 | description: Consider only the workflows created before this date for deletion. 15 | required: false 16 | actor: 17 | description: Returns someone's workflow runs. Use the login for the user who created the push associated with the check suite or workflow run. 18 | required: false 19 | branch: 20 | description: Returns workflow runs associated with a branch. Use the name of the branch of the push. 21 | required: false 22 | event: 23 | description: Returns workflow run triggered by the event you specify. For example, push, pull_request or issue. 24 | required: false 25 | status: 26 | description: Returns workflow runs with the given status. Can be one of queued, in_progress, or completed. 27 | required: false 28 | what-if: 29 | description: Set to true to preview the changes made by this action without deleting any workflow runs. 30 | required: false 31 | default: false 32 | runs: 33 | using: node16 34 | main: "dist/index.js" 35 | branding: 36 | icon: "archive" 37 | color: "blue" 38 | -------------------------------------------------------------------------------- /dist/bridge.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * __ ___ ____ _ _ ___ _ _ ____ 5 | * \ \ / / \ | _ \| \ | |_ _| \ | |/ ___| 6 | * \ \ /\ / / _ \ | |_) | \| || || \| | | _ 7 | * \ V V / ___ \| _ <| |\ || || |\ | |_| | 8 | * \_/\_/_/ \_\_| \_\_| \_|___|_| \_|\____| 9 | * 10 | * This file is critical for vm2. It implements the bridge between the host and the sandbox. 11 | * If you do not know exactly what you are doing, you should NOT edit this file. 12 | * 13 | * The file is loaded in the host and sandbox to handle objects in both directions. 14 | * This is done to ensure that RangeErrors are from the correct context. 15 | * The boundary between the sandbox and host might throw RangeErrors from both contexts. 16 | * Therefore, thisFromOther and friends can handle objects from both domains. 17 | * 18 | * Method parameters have comments to tell from which context they came. 19 | * 20 | */ 21 | 22 | const globalsList = [ 23 | 'Number', 24 | 'String', 25 | 'Boolean', 26 | 'Date', 27 | 'RegExp', 28 | 'Map', 29 | 'WeakMap', 30 | 'Set', 31 | 'WeakSet', 32 | 'Promise', 33 | 'Function' 34 | ]; 35 | 36 | const errorsList = [ 37 | 'RangeError', 38 | 'ReferenceError', 39 | 'SyntaxError', 40 | 'TypeError', 41 | 'EvalError', 42 | 'URIError', 43 | 'Error' 44 | ]; 45 | 46 | const OPNA = 'Operation not allowed on contextified object.'; 47 | 48 | const thisGlobalPrototypes = { 49 | __proto__: null, 50 | Object: Object.prototype, 51 | Array: Array.prototype 52 | }; 53 | 54 | for (let i = 0; i < globalsList.length; i++) { 55 | const key = globalsList[i]; 56 | const g = global[key]; 57 | if (g) thisGlobalPrototypes[key] = g.prototype; 58 | } 59 | 60 | for (let i = 0; i < errorsList.length; i++) { 61 | const key = errorsList[i]; 62 | const g = global[key]; 63 | if (g) thisGlobalPrototypes[key] = g.prototype; 64 | } 65 | 66 | const { 67 | getPrototypeOf: thisReflectGetPrototypeOf, 68 | setPrototypeOf: thisReflectSetPrototypeOf, 69 | defineProperty: thisReflectDefineProperty, 70 | deleteProperty: thisReflectDeleteProperty, 71 | getOwnPropertyDescriptor: thisReflectGetOwnPropertyDescriptor, 72 | isExtensible: thisReflectIsExtensible, 73 | preventExtensions: thisReflectPreventExtensions, 74 | apply: thisReflectApply, 75 | construct: thisReflectConstruct, 76 | set: thisReflectSet, 77 | get: thisReflectGet, 78 | has: thisReflectHas, 79 | ownKeys: thisReflectOwnKeys, 80 | enumerate: thisReflectEnumerate, 81 | } = Reflect; 82 | 83 | const thisObject = Object; 84 | const { 85 | freeze: thisObjectFreeze, 86 | prototype: thisObjectPrototype 87 | } = thisObject; 88 | const thisObjectHasOwnProperty = thisObjectPrototype.hasOwnProperty; 89 | const ThisProxy = Proxy; 90 | const ThisWeakMap = WeakMap; 91 | const { 92 | get: thisWeakMapGet, 93 | set: thisWeakMapSet 94 | } = ThisWeakMap.prototype; 95 | const ThisMap = Map; 96 | const thisMapGet = ThisMap.prototype.get; 97 | const thisMapSet = ThisMap.prototype.set; 98 | const thisFunction = Function; 99 | const thisFunctionBind = thisFunction.prototype.bind; 100 | const thisArrayIsArray = Array.isArray; 101 | const thisErrorCaptureStackTrace = Error.captureStackTrace; 102 | 103 | const thisSymbolToString = Symbol.prototype.toString; 104 | const thisSymbolToStringTag = Symbol.toStringTag; 105 | const thisSymbolIterator = Symbol.iterator; 106 | const thisSymbolNodeJSUtilInspectCustom = Symbol.for('nodejs.util.inspect.custom'); 107 | 108 | /** 109 | * VMError. 110 | * 111 | * @public 112 | * @extends {Error} 113 | */ 114 | class VMError extends Error { 115 | 116 | /** 117 | * Create VMError instance. 118 | * 119 | * @public 120 | * @param {string} message - Error message. 121 | * @param {string} code - Error code. 122 | */ 123 | constructor(message, code) { 124 | super(message); 125 | 126 | this.name = 'VMError'; 127 | this.code = code; 128 | 129 | thisErrorCaptureStackTrace(this, this.constructor); 130 | } 131 | } 132 | 133 | thisGlobalPrototypes['VMError'] = VMError.prototype; 134 | 135 | function thisUnexpected() { 136 | return new VMError('Unexpected'); 137 | } 138 | 139 | if (!thisReflectSetPrototypeOf(exports, null)) throw thisUnexpected(); 140 | 141 | function thisSafeGetOwnPropertyDescriptor(obj, key) { 142 | const desc = thisReflectGetOwnPropertyDescriptor(obj, key); 143 | if (!desc) return desc; 144 | if (!thisReflectSetPrototypeOf(desc, null)) throw thisUnexpected(); 145 | return desc; 146 | } 147 | 148 | function thisThrowCallerCalleeArgumentsAccess(key) { 149 | 'use strict'; 150 | thisThrowCallerCalleeArgumentsAccess[key]; 151 | return thisUnexpected(); 152 | } 153 | 154 | function thisIdMapping(factory, other) { 155 | return other; 156 | } 157 | 158 | const thisThrowOnKeyAccessHandler = thisObjectFreeze({ 159 | __proto__: null, 160 | get(target, key, receiver) { 161 | if (typeof key === 'symbol') { 162 | key = thisReflectApply(thisSymbolToString, key, []); 163 | } 164 | throw new VMError(`Unexpected access to key '${key}'`); 165 | } 166 | }); 167 | 168 | const emptyForzenObject = thisObjectFreeze({ 169 | __proto__: null 170 | }); 171 | 172 | const thisThrowOnKeyAccess = new ThisProxy(emptyForzenObject, thisThrowOnKeyAccessHandler); 173 | 174 | function SafeBase() {} 175 | 176 | if (!thisReflectDefineProperty(SafeBase, 'prototype', { 177 | __proto__: null, 178 | value: thisThrowOnKeyAccess 179 | })) throw thisUnexpected(); 180 | 181 | function SHARED_FUNCTION() {} 182 | 183 | const TEST_PROXY_HANDLER = thisObjectFreeze({ 184 | __proto__: thisThrowOnKeyAccess, 185 | construct() { 186 | return this; 187 | } 188 | }); 189 | 190 | function thisIsConstructor(obj) { 191 | // Note: obj@any(unsafe) 192 | const Func = new ThisProxy(obj, TEST_PROXY_HANDLER); 193 | try { 194 | // eslint-disable-next-line no-new 195 | new Func(); 196 | return true; 197 | } catch (e) { 198 | return false; 199 | } 200 | } 201 | 202 | function thisCreateTargetObject(obj, proto) { 203 | // Note: obj@any(unsafe) proto@any(unsafe) returns@this(unsafe) throws@this(unsafe) 204 | let base; 205 | if (typeof obj === 'function') { 206 | if (thisIsConstructor(obj)) { 207 | // Bind the function since bound functions do not have a prototype property. 208 | base = thisReflectApply(thisFunctionBind, SHARED_FUNCTION, [null]); 209 | } else { 210 | base = () => {}; 211 | } 212 | } else if (thisArrayIsArray(obj)) { 213 | base = []; 214 | } else { 215 | return {__proto__: proto}; 216 | } 217 | if (!thisReflectSetPrototypeOf(base, proto)) throw thisUnexpected(); 218 | return base; 219 | } 220 | 221 | function createBridge(otherInit, registerProxy) { 222 | 223 | const mappingOtherToThis = new ThisWeakMap(); 224 | const protoMappings = new ThisMap(); 225 | const protoName = new ThisMap(); 226 | 227 | function thisAddProtoMapping(proto, other, name) { 228 | // Note: proto@this(unsafe) other@other(unsafe) name@this(unsafe) throws@this(unsafe) 229 | thisReflectApply(thisMapSet, protoMappings, [proto, thisIdMapping]); 230 | thisReflectApply(thisMapSet, protoMappings, [other, 231 | (factory, object) => thisProxyOther(factory, object, proto)]); 232 | if (name) thisReflectApply(thisMapSet, protoName, [proto, name]); 233 | } 234 | 235 | function thisAddProtoMappingFactory(protoFactory, other, name) { 236 | // Note: protoFactory@this(unsafe) other@other(unsafe) name@this(unsafe) throws@this(unsafe) 237 | let proto; 238 | thisReflectApply(thisMapSet, protoMappings, [other, 239 | (factory, object) => { 240 | if (!proto) { 241 | proto = protoFactory(); 242 | thisReflectApply(thisMapSet, protoMappings, [proto, thisIdMapping]); 243 | if (name) thisReflectApply(thisMapSet, protoName, [proto, name]); 244 | } 245 | return thisProxyOther(factory, object, proto); 246 | }]); 247 | } 248 | 249 | const result = { 250 | __proto__: null, 251 | globalPrototypes: thisGlobalPrototypes, 252 | safeGetOwnPropertyDescriptor: thisSafeGetOwnPropertyDescriptor, 253 | fromArguments: thisFromOtherArguments, 254 | from: thisFromOther, 255 | fromWithFactory: thisFromOtherWithFactory, 256 | ensureThis: thisEnsureThis, 257 | mapping: mappingOtherToThis, 258 | connect: thisConnect, 259 | reflectSet: thisReflectSet, 260 | reflectGet: thisReflectGet, 261 | reflectDefineProperty: thisReflectDefineProperty, 262 | reflectDeleteProperty: thisReflectDeleteProperty, 263 | reflectApply: thisReflectApply, 264 | reflectConstruct: thisReflectConstruct, 265 | reflectHas: thisReflectHas, 266 | reflectOwnKeys: thisReflectOwnKeys, 267 | reflectEnumerate: thisReflectEnumerate, 268 | reflectGetPrototypeOf: thisReflectGetPrototypeOf, 269 | reflectIsExtensible: thisReflectIsExtensible, 270 | reflectPreventExtensions: thisReflectPreventExtensions, 271 | objectHasOwnProperty: thisObjectHasOwnProperty, 272 | weakMapSet: thisWeakMapSet, 273 | addProtoMapping: thisAddProtoMapping, 274 | addProtoMappingFactory: thisAddProtoMappingFactory, 275 | defaultFactory, 276 | protectedFactory, 277 | readonlyFactory, 278 | VMError 279 | }; 280 | 281 | const isHost = typeof otherInit !== 'object'; 282 | 283 | if (isHost) { 284 | otherInit = otherInit(result, registerProxy); 285 | } 286 | 287 | result.other = otherInit; 288 | 289 | const { 290 | globalPrototypes: otherGlobalPrototypes, 291 | safeGetOwnPropertyDescriptor: otherSafeGetOwnPropertyDescriptor, 292 | fromArguments: otherFromThisArguments, 293 | from: otherFromThis, 294 | mapping: mappingThisToOther, 295 | reflectSet: otherReflectSet, 296 | reflectGet: otherReflectGet, 297 | reflectDefineProperty: otherReflectDefineProperty, 298 | reflectDeleteProperty: otherReflectDeleteProperty, 299 | reflectApply: otherReflectApply, 300 | reflectConstruct: otherReflectConstruct, 301 | reflectHas: otherReflectHas, 302 | reflectOwnKeys: otherReflectOwnKeys, 303 | reflectEnumerate: otherReflectEnumerate, 304 | reflectGetPrototypeOf: otherReflectGetPrototypeOf, 305 | reflectIsExtensible: otherReflectIsExtensible, 306 | reflectPreventExtensions: otherReflectPreventExtensions, 307 | objectHasOwnProperty: otherObjectHasOwnProperty, 308 | weakMapSet: otherWeakMapSet 309 | } = otherInit; 310 | 311 | function thisOtherHasOwnProperty(object, key) { 312 | // Note: object@other(safe) key@prim throws@this(unsafe) 313 | try { 314 | return otherReflectApply(otherObjectHasOwnProperty, object, [key]) === true; 315 | } catch (e) { // @other(unsafe) 316 | throw thisFromOtherForThrow(e); 317 | } 318 | } 319 | 320 | function thisDefaultGet(handler, object, key, desc) { 321 | // Note: object@other(unsafe) key@prim desc@other(safe) 322 | let ret; // @other(unsafe) 323 | if (desc.get || desc.set) { 324 | const getter = desc.get; 325 | if (!getter) return undefined; 326 | try { 327 | ret = otherReflectApply(getter, object, [key]); 328 | } catch (e) { 329 | throw thisFromOtherForThrow(e); 330 | } 331 | } else { 332 | ret = desc.value; 333 | } 334 | return handler.fromOtherWithContext(ret); 335 | } 336 | 337 | function otherFromThisIfAvailable(to, from, key) { 338 | // Note: to@other(safe) from@this(safe) key@prim throws@this(unsafe) 339 | if (!thisReflectApply(thisObjectHasOwnProperty, from, [key])) return false; 340 | try { 341 | to[key] = otherFromThis(from[key]); 342 | } catch (e) { // @other(unsafe) 343 | throw thisFromOtherForThrow(e); 344 | } 345 | return true; 346 | } 347 | 348 | class BaseHandler extends SafeBase { 349 | 350 | constructor(object) { 351 | // Note: object@other(unsafe) throws@this(unsafe) 352 | super(); 353 | this.objectWrapper = () => object; 354 | } 355 | 356 | getObject() { 357 | return this.objectWrapper(); 358 | } 359 | 360 | getFactory() { 361 | return defaultFactory; 362 | } 363 | 364 | fromOtherWithContext(other) { 365 | // Note: other@other(unsafe) throws@this(unsafe) 366 | return thisFromOtherWithFactory(this.getFactory(), other); 367 | } 368 | 369 | doPreventExtensions(target, object, factory) { 370 | // Note: target@this(unsafe) object@other(unsafe) throws@this(unsafe) 371 | let keys; // @other(safe-array-of-prim) 372 | try { 373 | keys = otherReflectOwnKeys(object); 374 | } catch (e) { // @other(unsafe) 375 | throw thisFromOtherForThrow(e); 376 | } 377 | for (let i = 0; i < keys.length; i++) { 378 | const key = keys[i]; // @prim 379 | let desc; 380 | try { 381 | desc = otherSafeGetOwnPropertyDescriptor(object, key); 382 | } catch (e) { // @other(unsafe) 383 | throw thisFromOtherForThrow(e); 384 | } 385 | if (!desc) continue; 386 | if (!desc.configurable) { 387 | const current = thisSafeGetOwnPropertyDescriptor(target, key); 388 | if (current && !current.configurable) continue; 389 | if (desc.get || desc.set) { 390 | desc.get = this.fromOtherWithContext(desc.get); 391 | desc.set = this.fromOtherWithContext(desc.set); 392 | } else if (typeof object === 'function' && (key === 'caller' || key === 'callee' || key === 'arguments')) { 393 | desc.value = null; 394 | } else { 395 | desc.value = this.fromOtherWithContext(desc.value); 396 | } 397 | } else { 398 | if (desc.get || desc.set) { 399 | desc = { 400 | __proto__: null, 401 | configurable: true, 402 | enumerable: desc.enumerable, 403 | writable: true, 404 | value: null 405 | }; 406 | } else { 407 | desc.value = null; 408 | } 409 | } 410 | if (!thisReflectDefineProperty(target, key, desc)) throw thisUnexpected(); 411 | } 412 | if (!thisReflectPreventExtensions(target)) throw thisUnexpected(); 413 | } 414 | 415 | get(target, key, receiver) { 416 | // Note: target@this(unsafe) key@prim receiver@this(unsafe) throws@this(unsafe) 417 | const object = this.getObject(); // @other(unsafe) 418 | switch (key) { 419 | case 'constructor': { 420 | const desc = otherSafeGetOwnPropertyDescriptor(object, key); 421 | if (desc) return thisDefaultGet(this, object, key, desc); 422 | const proto = thisReflectGetPrototypeOf(target); 423 | return proto === null ? undefined : proto.constructor; 424 | } 425 | case '__proto__': { 426 | const desc = otherSafeGetOwnPropertyDescriptor(object, key); 427 | if (desc) return thisDefaultGet(this, object, key, desc); 428 | return thisReflectGetPrototypeOf(target); 429 | } 430 | case thisSymbolToStringTag: 431 | if (!thisOtherHasOwnProperty(object, thisSymbolToStringTag)) { 432 | const proto = thisReflectGetPrototypeOf(target); 433 | const name = thisReflectApply(thisMapGet, protoName, [proto]); 434 | if (name) return name; 435 | } 436 | break; 437 | case 'arguments': 438 | case 'caller': 439 | case 'callee': 440 | if (typeof object === 'function' && thisOtherHasOwnProperty(object, key)) { 441 | throw thisThrowCallerCalleeArgumentsAccess(key); 442 | } 443 | break; 444 | } 445 | let ret; // @other(unsafe) 446 | try { 447 | ret = otherReflectGet(object, key); 448 | } catch (e) { // @other(unsafe) 449 | throw thisFromOtherForThrow(e); 450 | } 451 | return this.fromOtherWithContext(ret); 452 | } 453 | 454 | set(target, key, value, receiver) { 455 | // Note: target@this(unsafe) key@prim value@this(unsafe) receiver@this(unsafe) throws@this(unsafe) 456 | const object = this.getObject(); // @other(unsafe) 457 | if (key === '__proto__' && !thisOtherHasOwnProperty(object, key)) { 458 | return this.setPrototypeOf(target, value); 459 | } 460 | try { 461 | value = otherFromThis(value); 462 | return otherReflectSet(object, key, value) === true; 463 | } catch (e) { // @other(unsafe) 464 | throw thisFromOtherForThrow(e); 465 | } 466 | } 467 | 468 | getPrototypeOf(target) { 469 | // Note: target@this(unsafe) 470 | return thisReflectGetPrototypeOf(target); 471 | } 472 | 473 | setPrototypeOf(target, value) { 474 | // Note: target@this(unsafe) throws@this(unsafe) 475 | throw new VMError(OPNA); 476 | } 477 | 478 | apply(target, context, args) { 479 | // Note: target@this(unsafe) context@this(unsafe) args@this(safe-array) throws@this(unsafe) 480 | const object = this.getObject(); // @other(unsafe) 481 | let ret; // @other(unsafe) 482 | try { 483 | context = otherFromThis(context); 484 | args = otherFromThisArguments(args); 485 | ret = otherReflectApply(object, context, args); 486 | } catch (e) { // @other(unsafe) 487 | throw thisFromOtherForThrow(e); 488 | } 489 | return thisFromOther(ret); 490 | } 491 | 492 | construct(target, args, newTarget) { 493 | // Note: target@this(unsafe) args@this(safe-array) newTarget@this(unsafe) throws@this(unsafe) 494 | const object = this.getObject(); // @other(unsafe) 495 | let ret; // @other(unsafe) 496 | try { 497 | args = otherFromThisArguments(args); 498 | ret = otherReflectConstruct(object, args); 499 | } catch (e) { // @other(unsafe) 500 | throw thisFromOtherForThrow(e); 501 | } 502 | return thisFromOtherWithFactory(this.getFactory(), ret, thisFromOther(object)); 503 | } 504 | 505 | getOwnPropertyDescriptorDesc(target, prop, desc) { 506 | // Note: target@this(unsafe) prop@prim desc@other{safe} throws@this(unsafe) 507 | const object = this.getObject(); // @other(unsafe) 508 | if (desc && typeof object === 'function' && (prop === 'arguments' || prop === 'caller' || prop === 'callee')) desc.value = null; 509 | return desc; 510 | } 511 | 512 | getOwnPropertyDescriptor(target, prop) { 513 | // Note: target@this(unsafe) prop@prim throws@this(unsafe) 514 | const object = this.getObject(); // @other(unsafe) 515 | let desc; // @other(safe) 516 | try { 517 | desc = otherSafeGetOwnPropertyDescriptor(object, prop); 518 | } catch (e) { // @other(unsafe) 519 | throw thisFromOtherForThrow(e); 520 | } 521 | 522 | desc = this.getOwnPropertyDescriptorDesc(target, prop, desc); 523 | 524 | if (!desc) return undefined; 525 | 526 | let thisDesc; 527 | if (desc.get || desc.set) { 528 | thisDesc = { 529 | __proto__: null, 530 | get: this.fromOtherWithContext(desc.get), 531 | set: this.fromOtherWithContext(desc.set), 532 | enumerable: desc.enumerable === true, 533 | configurable: desc.configurable === true 534 | }; 535 | } else { 536 | thisDesc = { 537 | __proto__: null, 538 | value: this.fromOtherWithContext(desc.value), 539 | writable: desc.writable === true, 540 | enumerable: desc.enumerable === true, 541 | configurable: desc.configurable === true 542 | }; 543 | } 544 | if (!thisDesc.configurable) { 545 | const oldDesc = thisSafeGetOwnPropertyDescriptor(target, prop); 546 | if (!oldDesc || oldDesc.configurable || oldDesc.writable !== thisDesc.writable) { 547 | if (!thisReflectDefineProperty(target, prop, thisDesc)) throw thisUnexpected(); 548 | } 549 | } 550 | return thisDesc; 551 | } 552 | 553 | definePropertyDesc(target, prop, desc) { 554 | // Note: target@this(unsafe) prop@prim desc@this(safe) throws@this(unsafe) 555 | return desc; 556 | } 557 | 558 | defineProperty(target, prop, desc) { 559 | // Note: target@this(unsafe) prop@prim desc@this(unsafe) throws@this(unsafe) 560 | const object = this.getObject(); // @other(unsafe) 561 | if (!thisReflectSetPrototypeOf(desc, null)) throw thisUnexpected(); 562 | 563 | desc = this.definePropertyDesc(target, prop, desc); 564 | 565 | if (!desc) return false; 566 | 567 | let otherDesc = {__proto__: null}; 568 | let hasFunc = true; 569 | let hasValue = true; 570 | let hasBasic = true; 571 | hasFunc &= otherFromThisIfAvailable(otherDesc, desc, 'get'); 572 | hasFunc &= otherFromThisIfAvailable(otherDesc, desc, 'set'); 573 | hasValue &= otherFromThisIfAvailable(otherDesc, desc, 'value'); 574 | hasValue &= otherFromThisIfAvailable(otherDesc, desc, 'writable'); 575 | hasBasic &= otherFromThisIfAvailable(otherDesc, desc, 'enumerable'); 576 | hasBasic &= otherFromThisIfAvailable(otherDesc, desc, 'configurable'); 577 | 578 | try { 579 | if (!otherReflectDefineProperty(object, prop, otherDesc)) return false; 580 | if (otherDesc.configurable !== true && (!hasBasic || !(hasFunc || hasValue))) { 581 | otherDesc = otherSafeGetOwnPropertyDescriptor(object, prop); 582 | } 583 | } catch (e) { // @other(unsafe) 584 | throw thisFromOtherForThrow(e); 585 | } 586 | 587 | if (!otherDesc.configurable) { 588 | let thisDesc; 589 | if (otherDesc.get || otherDesc.set) { 590 | thisDesc = { 591 | __proto__: null, 592 | get: this.fromOtherWithContext(otherDesc.get), 593 | set: this.fromOtherWithContext(otherDesc.set), 594 | enumerable: otherDesc.enumerable, 595 | configurable: otherDesc.configurable 596 | }; 597 | } else { 598 | thisDesc = { 599 | __proto__: null, 600 | value: this.fromOtherWithContext(otherDesc.value), 601 | writable: otherDesc.writable, 602 | enumerable: otherDesc.enumerable, 603 | configurable: otherDesc.configurable 604 | }; 605 | } 606 | if (!thisReflectDefineProperty(target, prop, thisDesc)) throw thisUnexpected(); 607 | } 608 | return true; 609 | } 610 | 611 | deleteProperty(target, prop) { 612 | // Note: target@this(unsafe) prop@prim throws@this(unsafe) 613 | const object = this.getObject(); // @other(unsafe) 614 | try { 615 | return otherReflectDeleteProperty(object, prop) === true; 616 | } catch (e) { // @other(unsafe) 617 | throw thisFromOtherForThrow(e); 618 | } 619 | } 620 | 621 | has(target, key) { 622 | // Note: target@this(unsafe) key@prim throws@this(unsafe) 623 | const object = this.getObject(); // @other(unsafe) 624 | try { 625 | return otherReflectHas(object, key) === true; 626 | } catch (e) { // @other(unsafe) 627 | throw thisFromOtherForThrow(e); 628 | } 629 | } 630 | 631 | isExtensible(target) { 632 | // Note: target@this(unsafe) throws@this(unsafe) 633 | const object = this.getObject(); // @other(unsafe) 634 | try { 635 | if (otherReflectIsExtensible(object)) return true; 636 | } catch (e) { // @other(unsafe) 637 | throw thisFromOtherForThrow(e); 638 | } 639 | if (thisReflectIsExtensible(target)) { 640 | this.doPreventExtensions(target, object, this); 641 | } 642 | return false; 643 | } 644 | 645 | ownKeys(target) { 646 | // Note: target@this(unsafe) throws@this(unsafe) 647 | const object = this.getObject(); // @other(unsafe) 648 | let res; // @other(unsafe) 649 | try { 650 | res = otherReflectOwnKeys(object); 651 | } catch (e) { // @other(unsafe) 652 | throw thisFromOtherForThrow(e); 653 | } 654 | return thisFromOther(res); 655 | } 656 | 657 | preventExtensions(target) { 658 | // Note: target@this(unsafe) throws@this(unsafe) 659 | const object = this.getObject(); // @other(unsafe) 660 | try { 661 | if (!otherReflectPreventExtensions(object)) return false; 662 | } catch (e) { // @other(unsafe) 663 | throw thisFromOtherForThrow(e); 664 | } 665 | if (thisReflectIsExtensible(target)) { 666 | this.doPreventExtensions(target, object, this); 667 | } 668 | return true; 669 | } 670 | 671 | enumerate(target) { 672 | // Note: target@this(unsafe) throws@this(unsafe) 673 | const object = this.getObject(); // @other(unsafe) 674 | let res; // @other(unsafe) 675 | try { 676 | res = otherReflectEnumerate(object); 677 | } catch (e) { // @other(unsafe) 678 | throw thisFromOtherForThrow(e); 679 | } 680 | return this.fromOtherWithContext(res); 681 | } 682 | 683 | } 684 | 685 | BaseHandler.prototype[thisSymbolNodeJSUtilInspectCustom] = undefined; 686 | BaseHandler.prototype[thisSymbolToStringTag] = 'VM2 Wrapper'; 687 | BaseHandler.prototype[thisSymbolIterator] = undefined; 688 | 689 | function defaultFactory(object) { 690 | // Note: other@other(unsafe) returns@this(unsafe) throws@this(unsafe) 691 | return new BaseHandler(object); 692 | } 693 | 694 | class ProtectedHandler extends BaseHandler { 695 | 696 | getFactory() { 697 | return protectedFactory; 698 | } 699 | 700 | set(target, key, value, receiver) { 701 | // Note: target@this(unsafe) key@prim value@this(unsafe) receiver@this(unsafe) throws@this(unsafe) 702 | if (typeof value === 'function') { 703 | return thisReflectDefineProperty(receiver, key, { 704 | __proto__: null, 705 | value: value, 706 | writable: true, 707 | enumerable: true, 708 | configurable: true 709 | }) === true; 710 | } 711 | return super.set(target, key, value, receiver); 712 | } 713 | 714 | definePropertyDesc(target, prop, desc) { 715 | // Note: target@this(unsafe) prop@prim desc@this(safe) throws@this(unsafe) 716 | if (desc && (desc.set || desc.get || typeof desc.value === 'function')) return undefined; 717 | return desc; 718 | } 719 | 720 | } 721 | 722 | function protectedFactory(object) { 723 | // Note: other@other(unsafe) returns@this(unsafe) throws@this(unsafe) 724 | return new ProtectedHandler(object); 725 | } 726 | 727 | class ReadOnlyHandler extends BaseHandler { 728 | 729 | getFactory() { 730 | return readonlyFactory; 731 | } 732 | 733 | set(target, key, value, receiver) { 734 | // Note: target@this(unsafe) key@prim value@this(unsafe) receiver@this(unsafe) throws@this(unsafe) 735 | return thisReflectDefineProperty(receiver, key, { 736 | __proto__: null, 737 | value: value, 738 | writable: true, 739 | enumerable: true, 740 | configurable: true 741 | }); 742 | } 743 | 744 | setPrototypeOf(target, value) { 745 | // Note: target@this(unsafe) throws@this(unsafe) 746 | return false; 747 | } 748 | 749 | defineProperty(target, prop, desc) { 750 | // Note: target@this(unsafe) prop@prim desc@this(unsafe) throws@this(unsafe) 751 | return false; 752 | } 753 | 754 | deleteProperty(target, prop) { 755 | // Note: target@this(unsafe) prop@prim throws@this(unsafe) 756 | return false; 757 | } 758 | 759 | isExtensible(target) { 760 | // Note: target@this(unsafe) throws@this(unsafe) 761 | return false; 762 | } 763 | 764 | preventExtensions(target) { 765 | // Note: target@this(unsafe) throws@this(unsafe) 766 | return false; 767 | } 768 | 769 | } 770 | 771 | function readonlyFactory(object) { 772 | // Note: other@other(unsafe) returns@this(unsafe) throws@this(unsafe) 773 | return new ReadOnlyHandler(object); 774 | } 775 | 776 | class ReadOnlyMockHandler extends ReadOnlyHandler { 777 | 778 | constructor(object, mock) { 779 | // Note: object@other(unsafe) mock:this(unsafe) throws@this(unsafe) 780 | super(object); 781 | this.mock = mock; 782 | } 783 | 784 | get(target, key, receiver) { 785 | // Note: target@this(unsafe) key@prim receiver@this(unsafe) throws@this(unsafe) 786 | const object = this.getObject(); // @other(unsafe) 787 | const mock = this.mock; 788 | if (thisReflectApply(thisObjectHasOwnProperty, mock, key) && !thisOtherHasOwnProperty(object, key)) { 789 | return mock[key]; 790 | } 791 | return super.get(target, key, receiver); 792 | } 793 | 794 | } 795 | 796 | function thisFromOther(other) { 797 | // Note: other@other(unsafe) returns@this(unsafe) throws@this(unsafe) 798 | return thisFromOtherWithFactory(defaultFactory, other); 799 | } 800 | 801 | function thisProxyOther(factory, other, proto) { 802 | const target = thisCreateTargetObject(other, proto); 803 | const handler = factory(other); 804 | const proxy = new ThisProxy(target, handler); 805 | try { 806 | otherReflectApply(otherWeakMapSet, mappingThisToOther, [proxy, other]); 807 | registerProxy(proxy, handler); 808 | } catch (e) { 809 | throw new VMError('Unexpected error'); 810 | } 811 | if (!isHost) { 812 | thisReflectApply(thisWeakMapSet, mappingOtherToThis, [other, proxy]); 813 | return proxy; 814 | } 815 | const proxy2 = new ThisProxy(proxy, emptyForzenObject); 816 | try { 817 | otherReflectApply(otherWeakMapSet, mappingThisToOther, [proxy2, other]); 818 | registerProxy(proxy2, handler); 819 | } catch (e) { 820 | throw new VMError('Unexpected error'); 821 | } 822 | thisReflectApply(thisWeakMapSet, mappingOtherToThis, [other, proxy2]); 823 | return proxy2; 824 | } 825 | 826 | function thisEnsureThis(other) { 827 | const type = typeof other; 828 | switch (type) { 829 | case 'object': 830 | if (other === null) { 831 | return null; 832 | } 833 | // fallthrough 834 | case 'function': 835 | let proto = thisReflectGetPrototypeOf(other); 836 | if (!proto) { 837 | return other; 838 | } 839 | while (proto) { 840 | const mapping = thisReflectApply(thisMapGet, protoMappings, [proto]); 841 | if (mapping) { 842 | const mapped = thisReflectApply(thisWeakMapGet, mappingOtherToThis, [other]); 843 | if (mapped) return mapped; 844 | return mapping(defaultFactory, other); 845 | } 846 | proto = thisReflectGetPrototypeOf(proto); 847 | } 848 | return other; 849 | case 'undefined': 850 | case 'string': 851 | case 'number': 852 | case 'boolean': 853 | case 'symbol': 854 | case 'bigint': 855 | return other; 856 | 857 | default: // new, unknown types can be dangerous 858 | throw new VMError(`Unknown type '${type}'`); 859 | } 860 | } 861 | 862 | function thisFromOtherForThrow(other) { 863 | for (let loop = 0; loop < 10; loop++) { 864 | const type = typeof other; 865 | switch (type) { 866 | case 'object': 867 | if (other === null) { 868 | return null; 869 | } 870 | // fallthrough 871 | case 'function': 872 | const mapped = thisReflectApply(thisWeakMapGet, mappingOtherToThis, [other]); 873 | if (mapped) return mapped; 874 | let proto; 875 | try { 876 | proto = otherReflectGetPrototypeOf(other); 877 | } catch (e) { // @other(unsafe) 878 | other = e; 879 | break; 880 | } 881 | if (!proto) { 882 | return thisProxyOther(defaultFactory, other, null); 883 | } 884 | for (;;) { 885 | const mapping = thisReflectApply(thisMapGet, protoMappings, [proto]); 886 | if (mapping) return mapping(defaultFactory, other); 887 | try { 888 | proto = otherReflectGetPrototypeOf(proto); 889 | } catch (e) { // @other(unsafe) 890 | other = e; 891 | break; 892 | } 893 | if (!proto) return thisProxyOther(defaultFactory, other, thisObjectPrototype); 894 | } 895 | break; 896 | case 'undefined': 897 | case 'string': 898 | case 'number': 899 | case 'boolean': 900 | case 'symbol': 901 | case 'bigint': 902 | return other; 903 | 904 | default: // new, unknown types can be dangerous 905 | throw new VMError(`Unknown type '${type}'`); 906 | } 907 | } 908 | throw new VMError('Exception recursion depth'); 909 | } 910 | 911 | function thisFromOtherWithFactory(factory, other, proto) { 912 | const type = typeof other; 913 | switch (type) { 914 | case 'object': 915 | if (other === null) { 916 | return null; 917 | } 918 | // fallthrough 919 | case 'function': 920 | const mapped = thisReflectApply(thisWeakMapGet, mappingOtherToThis, [other]); 921 | if (mapped) return mapped; 922 | if (proto) { 923 | return thisProxyOther(factory, other, proto); 924 | } 925 | try { 926 | proto = otherReflectGetPrototypeOf(other); 927 | } catch (e) { // @other(unsafe) 928 | throw thisFromOtherForThrow(e); 929 | } 930 | if (!proto) { 931 | return thisProxyOther(factory, other, null); 932 | } 933 | do { 934 | const mapping = thisReflectApply(thisMapGet, protoMappings, [proto]); 935 | if (mapping) return mapping(factory, other); 936 | try { 937 | proto = otherReflectGetPrototypeOf(proto); 938 | } catch (e) { // @other(unsafe) 939 | throw thisFromOtherForThrow(e); 940 | } 941 | } while (proto); 942 | return thisProxyOther(factory, other, thisObjectPrototype); 943 | case 'undefined': 944 | case 'string': 945 | case 'number': 946 | case 'boolean': 947 | case 'symbol': 948 | case 'bigint': 949 | return other; 950 | 951 | default: // new, unknown types can be dangerous 952 | throw new VMError(`Unknown type '${type}'`); 953 | } 954 | } 955 | 956 | function thisFromOtherArguments(args) { 957 | // Note: args@other(safe-array) returns@this(safe-array) throws@this(unsafe) 958 | const arr = []; 959 | for (let i = 0; i < args.length; i++) { 960 | const value = thisFromOther(args[i]); 961 | thisReflectDefineProperty(arr, i, { 962 | __proto__: null, 963 | value: value, 964 | writable: true, 965 | enumerable: true, 966 | configurable: true 967 | }); 968 | } 969 | return arr; 970 | } 971 | 972 | function thisConnect(obj, other) { 973 | // Note: obj@this(unsafe) other@other(unsafe) throws@this(unsafe) 974 | try { 975 | otherReflectApply(otherWeakMapSet, mappingThisToOther, [obj, other]); 976 | } catch (e) { 977 | throw new VMError('Unexpected error'); 978 | } 979 | thisReflectApply(thisWeakMapSet, mappingOtherToThis, [other, obj]); 980 | } 981 | 982 | thisAddProtoMapping(thisGlobalPrototypes.Object, otherGlobalPrototypes.Object); 983 | thisAddProtoMapping(thisGlobalPrototypes.Array, otherGlobalPrototypes.Array); 984 | 985 | for (let i = 0; i < globalsList.length; i++) { 986 | const key = globalsList[i]; 987 | const tp = thisGlobalPrototypes[key]; 988 | const op = otherGlobalPrototypes[key]; 989 | if (tp && op) thisAddProtoMapping(tp, op, key); 990 | } 991 | 992 | for (let i = 0; i < errorsList.length; i++) { 993 | const key = errorsList[i]; 994 | const tp = thisGlobalPrototypes[key]; 995 | const op = otherGlobalPrototypes[key]; 996 | if (tp && op) thisAddProtoMapping(tp, op, 'Error'); 997 | } 998 | 999 | thisAddProtoMapping(thisGlobalPrototypes.VMError, otherGlobalPrototypes.VMError, 'Error'); 1000 | 1001 | result.BaseHandler = BaseHandler; 1002 | result.ProtectedHandler = ProtectedHandler; 1003 | result.ReadOnlyHandler = ReadOnlyHandler; 1004 | result.ReadOnlyMockHandler = ReadOnlyMockHandler; 1005 | 1006 | return result; 1007 | } 1008 | 1009 | exports.createBridge = createBridge; 1010 | exports.VMError = VMError; 1011 | -------------------------------------------------------------------------------- /dist/contextify.js: -------------------------------------------------------------------------------- 1 | /* global host */ 2 | /* eslint-disable block-spacing, no-multi-spaces, brace-style, no-array-constructor, new-cap, no-use-before-define */ 3 | 4 | 'use strict'; 5 | 6 | // eslint-disable-next-line no-invalid-this, no-shadow 7 | const global = this; 8 | 9 | const local = host.Object.create(null); 10 | local.Object = Object; 11 | local.Array = Array; 12 | local.Reflect = host.Object.create(null); 13 | local.Reflect.ownKeys = Reflect.ownKeys; 14 | local.Reflect.enumerate = Reflect.enumerate; 15 | local.Reflect.getPrototypeOf = Reflect.getPrototypeOf; 16 | local.Reflect.construct = Reflect.construct; 17 | local.Reflect.apply = Reflect.apply; 18 | local.Reflect.set = Reflect.set; 19 | local.Reflect.deleteProperty = Reflect.deleteProperty; 20 | local.Reflect.has = Reflect.has; 21 | local.Reflect.defineProperty = Reflect.defineProperty; 22 | local.Reflect.setPrototypeOf = Reflect.setPrototypeOf; 23 | local.Reflect.isExtensible = Reflect.isExtensible; 24 | local.Reflect.preventExtensions = Reflect.preventExtensions; 25 | local.Reflect.getOwnPropertyDescriptor = Reflect.getOwnPropertyDescriptor; 26 | 27 | function uncurryThis(func) { 28 | return (thiz, args) => local.Reflect.apply(func, thiz, args); 29 | } 30 | 31 | const FunctionBind = uncurryThis(Function.prototype.bind); 32 | 33 | // global is originally prototype of host.Object so it can be used to climb up from the sandbox. 34 | Object.setPrototypeOf(global, Object.prototype); 35 | 36 | Object.defineProperties(global, { 37 | global: {value: global}, 38 | GLOBAL: {value: global}, 39 | root: {value: global}, 40 | isVM: {value: true} 41 | }); 42 | 43 | const DEBUG = false; 44 | const OPNA = 'Operation not allowed on contextified object.'; 45 | const captureStackTrace = Error.captureStackTrace; 46 | 47 | const RETURN_FALSE = () => false; 48 | 49 | const FROZEN_TRAPS = { 50 | __proto__: null, 51 | set(target, key, value, receiver) { 52 | return local.Reflect.defineProperty(receiver, key, { 53 | __proto__: null, 54 | value: value, 55 | writable: true, 56 | enumerable: true, 57 | configurable: true 58 | }); 59 | }, 60 | setPrototypeOf: RETURN_FALSE, 61 | defineProperty: RETURN_FALSE, 62 | deleteProperty: RETURN_FALSE, 63 | isExtensible: RETURN_FALSE, 64 | preventExtensions: RETURN_FALSE 65 | }; 66 | 67 | // Map of contextified objects to original objects 68 | const Contextified = new host.WeakMap(); 69 | const Decontextified = new host.WeakMap(); 70 | 71 | // We can't use host's hasInstance method 72 | const ObjectHasInstance = uncurryThis(local.Object[Symbol.hasInstance]); 73 | function instanceOf(value, construct) { 74 | try { 75 | return ObjectHasInstance(construct, [value]); 76 | } catch (ex) { 77 | // Never pass the handled exception through! 78 | throw new VMError('Unable to perform instanceOf check.'); 79 | // This exception actually never get to the user. It only instructs the caller to return null because we wasn't able to perform instanceOf check. 80 | } 81 | } 82 | 83 | const SHARED_OBJECT = {__proto__: null}; 84 | function SHARED_FUNCTION() {} 85 | 86 | function createBaseObject(obj) { 87 | let base; 88 | if (typeof obj === 'function') { 89 | try { 90 | // eslint-disable-next-line no-new 91 | new new host.Proxy(obj, { 92 | __proto__: null, 93 | construct() { 94 | return this; 95 | } 96 | })(); 97 | // Bind the function since bound functions do not have a prototype property. 98 | base = FunctionBind(SHARED_FUNCTION, [null]); 99 | } catch (e) { 100 | base = () => {}; 101 | } 102 | } else if (host.Array.isArray(obj)) { 103 | base = []; 104 | } else { 105 | return {__proto__: null}; 106 | } 107 | if (!local.Reflect.setPrototypeOf(base, null)) { 108 | // Should not happen 109 | return null; 110 | } 111 | return base; 112 | } 113 | 114 | /** 115 | * VMError definition. 116 | */ 117 | 118 | class VMError extends Error { 119 | constructor(message, code) { 120 | super(message); 121 | 122 | this.name = 'VMError'; 123 | this.code = code; 124 | 125 | captureStackTrace(this, this.constructor); 126 | } 127 | } 128 | 129 | global.VMError = VMError; 130 | 131 | /* 132 | * This function will throw a TypeError for accessing properties 133 | * on a strict mode function 134 | */ 135 | function throwCallerCalleeArgumentsAccess(key) { 136 | 'use strict'; 137 | throwCallerCalleeArgumentsAccess[key]; 138 | return new VMError('Unreachable'); 139 | } 140 | 141 | function unexpected() { 142 | throw new VMError('Should not happen'); 143 | } 144 | 145 | function doPreventExtensions(target, object, doProxy) { 146 | const keys = local.Reflect.ownKeys(object); 147 | for (let i = 0; i < keys.length; i++) { 148 | const key = keys[i]; 149 | let desc = local.Reflect.getOwnPropertyDescriptor(object, key); 150 | if (!desc) continue; 151 | if (!local.Reflect.setPrototypeOf(desc, null)) unexpected(); 152 | if (!desc.configurable) { 153 | const current = local.Reflect.getOwnPropertyDescriptor(target, key); 154 | if (current && !current.configurable) continue; 155 | if (desc.get || desc.set) { 156 | desc.get = doProxy(desc.get); 157 | desc.set = doProxy(desc.set); 158 | } else { 159 | desc.value = doProxy(desc.value); 160 | } 161 | } else { 162 | if (desc.get || desc.set) { 163 | desc = { 164 | __proto__: null, 165 | configurable: true, 166 | enumerable: desc.enumerable, 167 | writable: true, 168 | value: null 169 | }; 170 | } else { 171 | desc.value = null; 172 | } 173 | } 174 | if (!local.Reflect.defineProperty(target, key, desc)) unexpected(); 175 | } 176 | if (!local.Reflect.preventExtensions(target)) unexpected(); 177 | } 178 | 179 | /** 180 | * Decontextify. 181 | */ 182 | 183 | const Decontextify = host.Object.create(null); 184 | Decontextify.proxies = new host.WeakMap(); 185 | 186 | Decontextify.arguments = args => { 187 | if (!host.Array.isArray(args)) return new host.Array(); 188 | 189 | try { 190 | const arr = new host.Array(); 191 | for (let i = 0, l = args.length; i < l; i++) arr[i] = Decontextify.value(args[i]); 192 | return arr; 193 | } catch (e) { 194 | // Never pass the handled exception through! 195 | return new host.Array(); 196 | } 197 | }; 198 | Decontextify.instance = (instance, klass, deepTraps, flags, toStringTag) => { 199 | if (typeof instance === 'function') return Decontextify.function(instance); 200 | 201 | // We must not use normal object because there's a chance object already contains malicious code in the prototype 202 | const base = host.Object.create(null); 203 | 204 | base.get = (target, key, receiver) => { 205 | try { 206 | if (key === 'vmProxyTarget' && DEBUG) return instance; 207 | if (key === 'isVMProxy') return true; 208 | if (key === 'constructor') return klass; 209 | if (key === '__proto__') return klass.prototype; 210 | } catch (e) { 211 | // Never pass the handled exception through! This block can't throw an exception under normal conditions. 212 | return null; 213 | } 214 | 215 | if (key === '__defineGetter__') return host.Object.prototype.__defineGetter__; 216 | if (key === '__defineSetter__') return host.Object.prototype.__defineSetter__; 217 | if (key === '__lookupGetter__') return host.Object.prototype.__lookupGetter__; 218 | if (key === '__lookupSetter__') return host.Object.prototype.__lookupSetter__; 219 | if (key === host.Symbol.toStringTag && toStringTag) return toStringTag; 220 | 221 | try { 222 | return Decontextify.value(instance[key], null, deepTraps, flags); 223 | } catch (e) { 224 | throw Decontextify.value(e); 225 | } 226 | }; 227 | base.getPrototypeOf = (target) => { 228 | return klass && klass.prototype; 229 | }; 230 | 231 | return Decontextify.object(instance, base, deepTraps, flags); 232 | }; 233 | Decontextify.function = (fnc, traps, deepTraps, flags, mock) => { 234 | // We must not use normal object because there's a chance object already contains malicious code in the prototype 235 | const base = host.Object.create(null); 236 | // eslint-disable-next-line prefer-const 237 | let proxy; 238 | 239 | base.apply = (target, context, args) => { 240 | context = Contextify.value(context); 241 | 242 | // Set context of all arguments to vm's context. 243 | args = Contextify.arguments(args); 244 | 245 | try { 246 | return Decontextify.value(fnc.apply(context, args)); 247 | } catch (e) { 248 | throw Decontextify.value(e); 249 | } 250 | }; 251 | base.construct = (target, args, newTarget) => { 252 | args = Contextify.arguments(args); 253 | 254 | try { 255 | return Decontextify.instance(new fnc(...args), proxy, deepTraps, flags); 256 | } catch (e) { 257 | throw Decontextify.value(e); 258 | } 259 | }; 260 | base.get = (target, key, receiver) => { 261 | try { 262 | if (key === 'vmProxyTarget' && DEBUG) return fnc; 263 | if (key === 'isVMProxy') return true; 264 | if (mock && host.Object.prototype.hasOwnProperty.call(mock, key)) return mock[key]; 265 | if (key === 'constructor') return host.Function; 266 | if (key === '__proto__') return host.Function.prototype; 267 | } catch (e) { 268 | // Never pass the handled exception through! This block can't throw an exception under normal conditions. 269 | return null; 270 | } 271 | 272 | if (key === '__defineGetter__') return host.Object.prototype.__defineGetter__; 273 | if (key === '__defineSetter__') return host.Object.prototype.__defineSetter__; 274 | if (key === '__lookupGetter__') return host.Object.prototype.__lookupGetter__; 275 | if (key === '__lookupSetter__') return host.Object.prototype.__lookupSetter__; 276 | 277 | try { 278 | return Decontextify.value(fnc[key], null, deepTraps, flags); 279 | } catch (e) { 280 | throw Decontextify.value(e); 281 | } 282 | }; 283 | base.getPrototypeOf = (target) => { 284 | return host.Function.prototype; 285 | }; 286 | 287 | proxy = Decontextify.object(fnc, host.Object.assign(base, traps), deepTraps); 288 | return proxy; 289 | }; 290 | Decontextify.object = (object, traps, deepTraps, flags, mock) => { 291 | // We must not use normal object because there's a chance object already contains malicious code in the prototype 292 | const base = host.Object.create(null); 293 | 294 | base.get = (target, key, receiver) => { 295 | try { 296 | if (key === 'vmProxyTarget' && DEBUG) return object; 297 | if (key === 'isVMProxy') return true; 298 | if (mock && host.Object.prototype.hasOwnProperty.call(mock, key)) return mock[key]; 299 | if (key === 'constructor') return host.Object; 300 | if (key === '__proto__') return host.Object.prototype; 301 | } catch (e) { 302 | // Never pass the handled exception through! This block can't throw an exception under normal conditions. 303 | return null; 304 | } 305 | 306 | if (key === '__defineGetter__') return host.Object.prototype.__defineGetter__; 307 | if (key === '__defineSetter__') return host.Object.prototype.__defineSetter__; 308 | if (key === '__lookupGetter__') return host.Object.prototype.__lookupGetter__; 309 | if (key === '__lookupSetter__') return host.Object.prototype.__lookupSetter__; 310 | 311 | try { 312 | return Decontextify.value(object[key], null, deepTraps, flags); 313 | } catch (e) { 314 | throw Decontextify.value(e); 315 | } 316 | }; 317 | base.set = (target, key, value, receiver) => { 318 | value = Contextify.value(value); 319 | 320 | try { 321 | return local.Reflect.set(object, key, value); 322 | } catch (e) { 323 | throw Decontextify.value(e); 324 | } 325 | }; 326 | base.getOwnPropertyDescriptor = (target, prop) => { 327 | let def; 328 | 329 | try { 330 | def = host.Object.getOwnPropertyDescriptor(object, prop); 331 | } catch (e) { 332 | throw Decontextify.value(e); 333 | } 334 | 335 | // Following code prevents V8 to throw 336 | // TypeError: 'getOwnPropertyDescriptor' on proxy: trap reported non-configurability for property '' 337 | // which is either non-existant or configurable in the proxy target 338 | 339 | let desc; 340 | if (!def) { 341 | return undefined; 342 | } else if (def.get || def.set) { 343 | desc = { 344 | __proto__: null, 345 | get: Decontextify.value(def.get) || undefined, 346 | set: Decontextify.value(def.set) || undefined, 347 | enumerable: def.enumerable === true, 348 | configurable: def.configurable === true 349 | }; 350 | } else { 351 | desc = { 352 | __proto__: null, 353 | value: Decontextify.value(def.value), 354 | writable: def.writable === true, 355 | enumerable: def.enumerable === true, 356 | configurable: def.configurable === true 357 | }; 358 | } 359 | if (!desc.configurable) { 360 | try { 361 | def = host.Object.getOwnPropertyDescriptor(target, prop); 362 | if (!def || def.configurable || def.writable !== desc.writable) { 363 | local.Reflect.defineProperty(target, prop, desc); 364 | } 365 | } catch (e) { 366 | // Should not happen. 367 | } 368 | } 369 | return desc; 370 | }; 371 | base.defineProperty = (target, key, descriptor) => { 372 | let success = false; 373 | try { 374 | success = local.Reflect.setPrototypeOf(descriptor, null); 375 | } catch (e) { 376 | // Should not happen 377 | } 378 | if (!success) return false; 379 | // There's a chance accessing a property throws an error so we must not access them 380 | // in try catch to prevent contextifying local objects. 381 | 382 | const propertyDescriptor = host.Object.create(null); 383 | if (descriptor.get || descriptor.set) { 384 | propertyDescriptor.get = Contextify.value(descriptor.get, null, deepTraps, flags) || undefined; 385 | propertyDescriptor.set = Contextify.value(descriptor.set, null, deepTraps, flags) || undefined; 386 | propertyDescriptor.enumerable = descriptor.enumerable === true; 387 | propertyDescriptor.configurable = descriptor.configurable === true; 388 | } else { 389 | propertyDescriptor.value = Contextify.value(descriptor.value, null, deepTraps, flags); 390 | propertyDescriptor.writable = descriptor.writable === true; 391 | propertyDescriptor.enumerable = descriptor.enumerable === true; 392 | propertyDescriptor.configurable = descriptor.configurable === true; 393 | } 394 | 395 | try { 396 | success = local.Reflect.defineProperty(object, key, propertyDescriptor); 397 | } catch (e) { 398 | throw Decontextify.value(e); 399 | } 400 | if (success && !descriptor.configurable) { 401 | try { 402 | local.Reflect.defineProperty(target, key, descriptor); 403 | } catch (e) { 404 | // This should not happen. 405 | return false; 406 | } 407 | } 408 | return success; 409 | }; 410 | base.deleteProperty = (target, prop) => { 411 | try { 412 | return Decontextify.value(local.Reflect.deleteProperty(object, prop)); 413 | } catch (e) { 414 | throw Decontextify.value(e); 415 | } 416 | }; 417 | base.getPrototypeOf = (target) => { 418 | return host.Object.prototype; 419 | }; 420 | base.setPrototypeOf = (target) => { 421 | throw new host.Error(OPNA); 422 | }; 423 | base.has = (target, key) => { 424 | try { 425 | return Decontextify.value(local.Reflect.has(object, key)); 426 | } catch (e) { 427 | throw Decontextify.value(e); 428 | } 429 | }; 430 | base.isExtensible = target => { 431 | let result; 432 | try { 433 | result = local.Reflect.isExtensible(object); 434 | } catch (e) { 435 | throw Decontextify.value(e); 436 | } 437 | if (!result) { 438 | try { 439 | if (local.Reflect.isExtensible(target)) { 440 | doPreventExtensions(target, object, obj => Contextify.value(obj, null, deepTraps, flags)); 441 | } 442 | } catch (e) { 443 | // Should not happen 444 | } 445 | } 446 | return result; 447 | }; 448 | base.ownKeys = target => { 449 | try { 450 | return Decontextify.value(local.Reflect.ownKeys(object)); 451 | } catch (e) { 452 | throw Decontextify.value(e); 453 | } 454 | }; 455 | base.preventExtensions = target => { 456 | let success; 457 | try { 458 | success = local.Reflect.preventExtensions(object); 459 | } catch (e) { 460 | throw Decontextify.value(e); 461 | } 462 | if (success) { 463 | try { 464 | if (local.Reflect.isExtensible(target)) { 465 | doPreventExtensions(target, object, obj => Contextify.value(obj, null, deepTraps, flags)); 466 | } 467 | } catch (e) { 468 | // Should not happen 469 | } 470 | } 471 | return success; 472 | }; 473 | base.enumerate = target => { 474 | try { 475 | return Decontextify.value(local.Reflect.enumerate(object)); 476 | } catch (e) { 477 | throw Decontextify.value(e); 478 | } 479 | }; 480 | 481 | host.Object.assign(base, traps, deepTraps); 482 | 483 | let shallow; 484 | if (host.Array.isArray(object)) { 485 | const origGet = base.get; 486 | shallow = { 487 | __proto__: null, 488 | ownKeys: base.ownKeys, 489 | // TODO this get will call getOwnPropertyDescriptor of target all the time. 490 | get: origGet 491 | }; 492 | base.ownKeys = target => { 493 | try { 494 | const keys = local.Reflect.ownKeys(object); 495 | // Do this hack so that console.log(decontextify([1,2,3])) doesn't write the properties twice 496 | // a la [1,2,3,'0':1,'1':2,'2':3] 497 | return Decontextify.value(keys.filter(key=>typeof key!=='string' || !key.match(/^\d+$/))); 498 | } catch (e) { 499 | throw Decontextify.value(e); 500 | } 501 | }; 502 | base.get = (target, key, receiver) => { 503 | if (key === host.Symbol.toStringTag) return; 504 | return origGet(target, key, receiver); 505 | }; 506 | } else { 507 | shallow = SHARED_OBJECT; 508 | } 509 | 510 | const proxy = new host.Proxy(createBaseObject(object), base); 511 | Decontextified.set(proxy, object); 512 | // We need two proxies since nodes inspect just removes one. 513 | const proxy2 = new host.Proxy(proxy, shallow); 514 | Decontextify.proxies.set(object, proxy2); 515 | Decontextified.set(proxy2, object); 516 | return proxy2; 517 | }; 518 | Decontextify.value = (value, traps, deepTraps, flags, mock) => { 519 | try { 520 | if (Contextified.has(value)) { 521 | // Contextified object has returned back from vm 522 | return Contextified.get(value); 523 | } else if (Decontextify.proxies.has(value)) { 524 | // Decontextified proxy already exists, reuse 525 | return Decontextify.proxies.get(value); 526 | } 527 | 528 | switch (typeof value) { 529 | case 'object': 530 | if (value === null) { 531 | return null; 532 | } else if (instanceOf(value, Number)) { return Decontextify.instance(value, host.Number, deepTraps, flags, 'Number'); 533 | } else if (instanceOf(value, String)) { return Decontextify.instance(value, host.String, deepTraps, flags, 'String'); 534 | } else if (instanceOf(value, Boolean)) { return Decontextify.instance(value, host.Boolean, deepTraps, flags, 'Boolean'); 535 | } else if (instanceOf(value, Date)) { return Decontextify.instance(value, host.Date, deepTraps, flags, 'Date'); 536 | } else if (instanceOf(value, RangeError)) { return Decontextify.instance(value, host.RangeError, deepTraps, flags, 'Error'); 537 | } else if (instanceOf(value, ReferenceError)) { return Decontextify.instance(value, host.ReferenceError, deepTraps, flags, 'Error'); 538 | } else if (instanceOf(value, SyntaxError)) { return Decontextify.instance(value, host.SyntaxError, deepTraps, flags, 'Error'); 539 | } else if (instanceOf(value, TypeError)) { return Decontextify.instance(value, host.TypeError, deepTraps, flags, 'Error'); 540 | } else if (instanceOf(value, VMError)) { return Decontextify.instance(value, host.VMError, deepTraps, flags, 'Error'); 541 | } else if (instanceOf(value, EvalError)) { return Decontextify.instance(value, host.EvalError, deepTraps, flags, 'Error'); 542 | } else if (instanceOf(value, URIError)) { return Decontextify.instance(value, host.URIError, deepTraps, flags, 'Error'); 543 | } else if (instanceOf(value, Error)) { return Decontextify.instance(value, host.Error, deepTraps, flags, 'Error'); 544 | } else if (instanceOf(value, Array)) { return Decontextify.instance(value, host.Array, deepTraps, flags, 'Array'); 545 | } else if (instanceOf(value, RegExp)) { return Decontextify.instance(value, host.RegExp, deepTraps, flags, 'RegExp'); 546 | } else if (instanceOf(value, Map)) { return Decontextify.instance(value, host.Map, deepTraps, flags, 'Map'); 547 | } else if (instanceOf(value, WeakMap)) { return Decontextify.instance(value, host.WeakMap, deepTraps, flags, 'WeakMap'); 548 | } else if (instanceOf(value, Set)) { return Decontextify.instance(value, host.Set, deepTraps, flags, 'Set'); 549 | } else if (instanceOf(value, WeakSet)) { return Decontextify.instance(value, host.WeakSet, deepTraps, flags, 'WeakSet'); 550 | } else if (typeof Promise === 'function' && instanceOf(value, Promise)) { 551 | return Decontextify.instance(value, host.Promise, deepTraps, flags, 'Promise'); 552 | } else if (local.Reflect.getPrototypeOf(value) === null) { 553 | return Decontextify.instance(value, null, deepTraps, flags); 554 | } else { 555 | return Decontextify.object(value, traps, deepTraps, flags, mock); 556 | } 557 | case 'function': 558 | return Decontextify.function(value, traps, deepTraps, flags, mock); 559 | 560 | case 'undefined': 561 | return undefined; 562 | 563 | default: // string, number, boolean, symbol 564 | return value; 565 | } 566 | } catch (ex) { 567 | // Never pass the handled exception through! This block can't throw an exception under normal conditions. 568 | return null; 569 | } 570 | }; 571 | 572 | /** 573 | * Contextify. 574 | */ 575 | 576 | const Contextify = host.Object.create(null); 577 | Contextify.proxies = new host.WeakMap(); 578 | 579 | Contextify.arguments = args => { 580 | if (!host.Array.isArray(args)) return new local.Array(); 581 | 582 | try { 583 | const arr = new local.Array(); 584 | for (let i = 0, l = args.length; i < l; i++) arr[i] = Contextify.value(args[i]); 585 | return arr; 586 | } catch (e) { 587 | // Never pass the handled exception through! 588 | return new local.Array(); 589 | } 590 | }; 591 | Contextify.instance = (instance, klass, deepTraps, flags, toStringTag) => { 592 | if (typeof instance === 'function') return Contextify.function(instance); 593 | 594 | // We must not use normal object because there's a chance object already contains malicious code in the prototype 595 | const base = host.Object.create(null); 596 | 597 | base.get = (target, key, receiver) => { 598 | try { 599 | if (key === 'vmProxyTarget' && DEBUG) return instance; 600 | if (key === 'isVMProxy') return true; 601 | if (key === 'constructor') return klass; 602 | if (key === '__proto__') return klass.prototype; 603 | } catch (e) { 604 | // Never pass the handled exception through! This block can't throw an exception under normal conditions. 605 | return null; 606 | } 607 | 608 | if (key === '__defineGetter__') return local.Object.prototype.__defineGetter__; 609 | if (key === '__defineSetter__') return local.Object.prototype.__defineSetter__; 610 | if (key === '__lookupGetter__') return local.Object.prototype.__lookupGetter__; 611 | if (key === '__lookupSetter__') return local.Object.prototype.__lookupSetter__; 612 | if (key === host.Symbol.toStringTag && toStringTag) return toStringTag; 613 | 614 | try { 615 | return Contextify.value(host.Reflect.get(instance, key), null, deepTraps, flags); 616 | } catch (e) { 617 | throw Contextify.value(e); 618 | } 619 | }; 620 | base.getPrototypeOf = (target) => { 621 | return klass && klass.prototype; 622 | }; 623 | 624 | return Contextify.object(instance, base, deepTraps, flags); 625 | }; 626 | Contextify.function = (fnc, traps, deepTraps, flags, mock) => { 627 | // We must not use normal object because there's a chance object already contains malicious code in the prototype 628 | const base = host.Object.create(null); 629 | // eslint-disable-next-line prefer-const 630 | let proxy; 631 | 632 | base.apply = (target, context, args) => { 633 | // Fixes buffer unsafe allocation for node v6/7 634 | if (host.version < 8 && fnc === host.Buffer && 'number' === typeof args[0]) { 635 | args[0] = new local.Array(args[0]).fill(0); 636 | } 637 | 638 | context = Decontextify.value(context); 639 | 640 | // Set context of all arguments to host's context. 641 | args = Decontextify.arguments(args); 642 | 643 | try { 644 | return Contextify.value(fnc.apply(context, args)); 645 | } catch (e) { 646 | throw Contextify.value(e); 647 | } 648 | }; 649 | base.construct = (target, args, newTarget) => { 650 | // Fixes buffer unsafe allocation for node v6/7 651 | if (host.version < 8 && fnc === host.Buffer && 'number' === typeof args[0]) { 652 | args[0] = new local.Array(args[0]).fill(0); 653 | } 654 | 655 | args = Decontextify.arguments(args); 656 | 657 | try { 658 | return Contextify.instance(new fnc(...args), proxy, deepTraps, flags); 659 | } catch (e) { 660 | throw Contextify.value(e); 661 | } 662 | }; 663 | base.get = (target, key, receiver) => { 664 | try { 665 | if (key === 'vmProxyTarget' && DEBUG) return fnc; 666 | if (key === 'isVMProxy') return true; 667 | if (mock && host.Object.prototype.hasOwnProperty.call(mock, key)) return mock[key]; 668 | if (key === 'constructor') return Function; 669 | if (key === '__proto__') return Function.prototype; 670 | } catch (e) { 671 | // Never pass the handled exception through! This block can't throw an exception under normal conditions. 672 | return null; 673 | } 674 | 675 | if (key === '__defineGetter__') return local.Object.prototype.__defineGetter__; 676 | if (key === '__defineSetter__') return local.Object.prototype.__defineSetter__; 677 | if (key === '__lookupGetter__') return local.Object.prototype.__lookupGetter__; 678 | if (key === '__lookupSetter__') return local.Object.prototype.__lookupSetter__; 679 | 680 | if (key === 'caller' || key === 'callee' || key === 'arguments') throw throwCallerCalleeArgumentsAccess(key); 681 | 682 | try { 683 | return Contextify.value(host.Reflect.get(fnc, key), null, deepTraps, flags); 684 | } catch (e) { 685 | throw Contextify.value(e); 686 | } 687 | }; 688 | base.getPrototypeOf = (target) => { 689 | return Function.prototype; 690 | }; 691 | 692 | proxy = Contextify.object(fnc, host.Object.assign(base, traps), deepTraps); 693 | return proxy; 694 | }; 695 | Contextify.object = (object, traps, deepTraps, flags, mock) => { 696 | // We must not use normal object because there's a chance object already contains malicious code in the prototype 697 | const base = host.Object.create(null); 698 | 699 | base.get = (target, key, receiver) => { 700 | try { 701 | if (key === 'vmProxyTarget' && DEBUG) return object; 702 | if (key === 'isVMProxy') return true; 703 | if (mock && host.Object.prototype.hasOwnProperty.call(mock, key)) return mock[key]; 704 | if (key === 'constructor') return Object; 705 | if (key === '__proto__') return Object.prototype; 706 | } catch (e) { 707 | // Never pass the handled exception through! This block can't throw an exception under normal conditions. 708 | return null; 709 | } 710 | 711 | if (key === '__defineGetter__') return local.Object.prototype.__defineGetter__; 712 | if (key === '__defineSetter__') return local.Object.prototype.__defineSetter__; 713 | if (key === '__lookupGetter__') return local.Object.prototype.__lookupGetter__; 714 | if (key === '__lookupSetter__') return local.Object.prototype.__lookupSetter__; 715 | 716 | try { 717 | return Contextify.value(host.Reflect.get(object, key), null, deepTraps, flags); 718 | } catch (e) { 719 | throw Contextify.value(e); 720 | } 721 | }; 722 | base.set = (target, key, value, receiver) => { 723 | if (key === '__proto__') return false; 724 | if (flags && flags.protected && typeof value === 'function') return false; 725 | 726 | value = Decontextify.value(value); 727 | 728 | try { 729 | return host.Reflect.set(object, key, value); 730 | } catch (e) { 731 | throw Contextify.value(e); 732 | } 733 | }; 734 | base.getOwnPropertyDescriptor = (target, prop) => { 735 | let def; 736 | 737 | try { 738 | def = host.Object.getOwnPropertyDescriptor(object, prop); 739 | } catch (e) { 740 | throw Contextify.value(e); 741 | } 742 | 743 | // Following code prevents V8 to throw 744 | // TypeError: 'getOwnPropertyDescriptor' on proxy: trap reported non-configurability for property '' 745 | // which is either non-existant or configurable in the proxy target 746 | 747 | let desc; 748 | if (!def) { 749 | return undefined; 750 | } else if (def.get || def.set) { 751 | desc = { 752 | __proto__: null, 753 | get: Contextify.value(def.get, null, deepTraps, flags) || undefined, 754 | set: Contextify.value(def.set, null, deepTraps, flags) || undefined, 755 | enumerable: def.enumerable === true, 756 | configurable: def.configurable === true 757 | }; 758 | } else { 759 | desc = { 760 | __proto__: null, 761 | value: Contextify.value(def.value, null, deepTraps, flags), 762 | writable: def.writable === true, 763 | enumerable: def.enumerable === true, 764 | configurable: def.configurable === true 765 | }; 766 | } 767 | if (!desc.configurable) { 768 | try { 769 | def = host.Object.getOwnPropertyDescriptor(target, prop); 770 | if (!def || def.configurable || def.writable !== desc.writable) { 771 | local.Reflect.defineProperty(target, prop, desc); 772 | } 773 | } catch (e) { 774 | // Should not happen. 775 | } 776 | } 777 | return desc; 778 | }; 779 | base.defineProperty = (target, key, descriptor) => { 780 | let success = false; 781 | try { 782 | success = local.Reflect.setPrototypeOf(descriptor, null); 783 | } catch (e) { 784 | // Should not happen 785 | } 786 | if (!success) return false; 787 | // There's a chance accessing a property throws an error so we must not access them 788 | // in try catch to prevent contextifying local objects. 789 | 790 | const descGet = descriptor.get; 791 | const descSet = descriptor.set; 792 | const descValue = descriptor.value; 793 | 794 | if (flags && flags.protected) { 795 | if (descGet || descSet || typeof descValue === 'function') return false; 796 | } 797 | 798 | const propertyDescriptor = host.Object.create(null); 799 | if (descGet || descSet) { 800 | propertyDescriptor.get = Decontextify.value(descGet, null, deepTraps, flags) || undefined; 801 | propertyDescriptor.set = Decontextify.value(descSet, null, deepTraps, flags) || undefined; 802 | propertyDescriptor.enumerable = descriptor.enumerable === true; 803 | propertyDescriptor.configurable = descriptor.configurable === true; 804 | } else { 805 | propertyDescriptor.value = Decontextify.value(descValue, null, deepTraps, flags); 806 | propertyDescriptor.writable = descriptor.writable === true; 807 | propertyDescriptor.enumerable = descriptor.enumerable === true; 808 | propertyDescriptor.configurable = descriptor.configurable === true; 809 | } 810 | 811 | try { 812 | success = host.Reflect.defineProperty(object, key, propertyDescriptor); 813 | } catch (e) { 814 | throw Contextify.value(e); 815 | } 816 | if (success && !descriptor.configurable) { 817 | try { 818 | local.Reflect.defineProperty(target, key, descriptor); 819 | } catch (e) { 820 | // This should not happen. 821 | return false; 822 | } 823 | } 824 | return success; 825 | }; 826 | base.deleteProperty = (target, prop) => { 827 | try { 828 | return Contextify.value(host.Reflect.deleteProperty(object, prop)); 829 | } catch (e) { 830 | throw Contextify.value(e); 831 | } 832 | }; 833 | base.getPrototypeOf = (target) => { 834 | return local.Object.prototype; 835 | }; 836 | base.setPrototypeOf = (target) => { 837 | throw new VMError(OPNA); 838 | }; 839 | base.has = (target, key) => { 840 | try { 841 | return Contextify.value(host.Reflect.has(object, key)); 842 | } catch (e) { 843 | throw Contextify.value(e); 844 | } 845 | }; 846 | base.isExtensible = target => { 847 | let result; 848 | try { 849 | result = host.Reflect.isExtensible(object); 850 | } catch (e) { 851 | throw Contextify.value(e); 852 | } 853 | if (!result) { 854 | try { 855 | if (local.Reflect.isExtensible(target)) { 856 | doPreventExtensions(target, object, obj => Decontextify.value(obj, null, deepTraps, flags)); 857 | } 858 | } catch (e) { 859 | // Should not happen 860 | } 861 | } 862 | return result; 863 | }; 864 | base.ownKeys = target => { 865 | try { 866 | return Contextify.value(host.Reflect.ownKeys(object)); 867 | } catch (e) { 868 | throw Contextify.value(e); 869 | } 870 | }; 871 | base.preventExtensions = target => { 872 | let success; 873 | try { 874 | success = local.Reflect.preventExtensions(object); 875 | } catch (e) { 876 | throw Contextify.value(e); 877 | } 878 | if (success) { 879 | try { 880 | if (local.Reflect.isExtensible(target)) { 881 | doPreventExtensions(target, object, obj => Decontextify.value(obj, null, deepTraps, flags)); 882 | } 883 | } catch (e) { 884 | // Should not happen 885 | } 886 | } 887 | return success; 888 | }; 889 | base.enumerate = target => { 890 | try { 891 | return Contextify.value(host.Reflect.enumerate(object)); 892 | } catch (e) { 893 | throw Contextify.value(e); 894 | } 895 | }; 896 | 897 | const proxy = new host.Proxy(createBaseObject(object), host.Object.assign(base, traps, deepTraps)); 898 | Contextify.proxies.set(object, proxy); 899 | Contextified.set(proxy, object); 900 | return proxy; 901 | }; 902 | Contextify.value = (value, traps, deepTraps, flags, mock) => { 903 | try { 904 | if (Decontextified.has(value)) { 905 | // Decontextified object has returned back to vm 906 | return Decontextified.get(value); 907 | } else if (Contextify.proxies.has(value)) { 908 | // Contextified proxy already exists, reuse 909 | return Contextify.proxies.get(value); 910 | } 911 | 912 | switch (typeof value) { 913 | case 'object': 914 | if (value === null) { 915 | return null; 916 | } else if (instanceOf(value, host.Number)) { return Contextify.instance(value, Number, deepTraps, flags, 'Number'); 917 | } else if (instanceOf(value, host.String)) { return Contextify.instance(value, String, deepTraps, flags, 'String'); 918 | } else if (instanceOf(value, host.Boolean)) { return Contextify.instance(value, Boolean, deepTraps, flags, 'Boolean'); 919 | } else if (instanceOf(value, host.Date)) { return Contextify.instance(value, Date, deepTraps, flags, 'Date'); 920 | } else if (instanceOf(value, host.RangeError)) { return Contextify.instance(value, RangeError, deepTraps, flags, 'Error'); 921 | } else if (instanceOf(value, host.ReferenceError)) { return Contextify.instance(value, ReferenceError, deepTraps, flags, 'Error'); 922 | } else if (instanceOf(value, host.SyntaxError)) { return Contextify.instance(value, SyntaxError, deepTraps, flags, 'Error'); 923 | } else if (instanceOf(value, host.TypeError)) { return Contextify.instance(value, TypeError, deepTraps, flags, 'Error'); 924 | } else if (instanceOf(value, host.VMError)) { return Contextify.instance(value, VMError, deepTraps, flags, 'Error'); 925 | } else if (instanceOf(value, host.EvalError)) { return Contextify.instance(value, EvalError, deepTraps, flags, 'Error'); 926 | } else if (instanceOf(value, host.URIError)) { return Contextify.instance(value, URIError, deepTraps, flags, 'Error'); 927 | } else if (instanceOf(value, host.Error)) { return Contextify.instance(value, Error, deepTraps, flags, 'Error'); 928 | } else if (instanceOf(value, host.Array)) { return Contextify.instance(value, Array, deepTraps, flags, 'Array'); 929 | } else if (instanceOf(value, host.RegExp)) { return Contextify.instance(value, RegExp, deepTraps, flags, 'RegExp'); 930 | } else if (instanceOf(value, host.Map)) { return Contextify.instance(value, Map, deepTraps, flags, 'Map'); 931 | } else if (instanceOf(value, host.WeakMap)) { return Contextify.instance(value, WeakMap, deepTraps, flags, 'WeakMap'); 932 | } else if (instanceOf(value, host.Set)) { return Contextify.instance(value, Set, deepTraps, flags, 'Set'); 933 | } else if (instanceOf(value, host.WeakSet)) { return Contextify.instance(value, WeakSet, deepTraps, flags, 'WeakSet'); 934 | } else if (typeof Promise === 'function' && instanceOf(value, host.Promise)) { 935 | return Contextify.instance(value, Promise, deepTraps, flags, 'Promise'); 936 | } else if (instanceOf(value, host.Buffer)) { return Contextify.instance(value, LocalBuffer, deepTraps, flags, 'Uint8Array'); 937 | } else if (host.Reflect.getPrototypeOf(value) === null) { 938 | return Contextify.instance(value, null, deepTraps, flags); 939 | } else { 940 | return Contextify.object(value, traps, deepTraps, flags, mock); 941 | } 942 | case 'function': 943 | return Contextify.function(value, traps, deepTraps, flags, mock); 944 | 945 | case 'undefined': 946 | return undefined; 947 | 948 | default: // string, number, boolean, symbol 949 | return value; 950 | } 951 | } catch (ex) { 952 | // Never pass the handled exception through! This block can't throw an exception under normal conditions. 953 | return null; 954 | } 955 | }; 956 | Contextify.setGlobal = (name, value) => { 957 | const prop = Contextify.value(name); 958 | try { 959 | global[prop] = Contextify.value(value); 960 | } catch (e) { 961 | throw Decontextify.value(e); 962 | } 963 | }; 964 | Contextify.getGlobal = (name) => { 965 | const prop = Contextify.value(name); 966 | try { 967 | return Decontextify.value(global[prop]); 968 | } catch (e) { 969 | throw Decontextify.value(e); 970 | } 971 | }; 972 | Contextify.readonly = (value, mock) => { 973 | return Contextify.value(value, null, FROZEN_TRAPS, null, mock); 974 | }; 975 | Contextify.protected = (value, mock) => { 976 | return Contextify.value(value, null, null, {protected: true}, mock); 977 | }; 978 | Contextify.connect = (outer, inner) => { 979 | Decontextified.set(outer, inner); 980 | Contextified.set(inner, outer); 981 | }; 982 | Contextify.makeModule = ()=>({exports: {}}); 983 | Contextify.isVMProxy = (obj) => Decontextified.has(obj); 984 | 985 | const BufferMock = host.Object.create(null); 986 | BufferMock.allocUnsafe = function allocUnsafe(size) { 987 | return this.alloc(size); 988 | }; 989 | BufferMock.allocUnsafeSlow = function allocUnsafeSlow(size) { 990 | return this.alloc(size); 991 | }; 992 | const BufferOverride = host.Object.create(null); 993 | BufferOverride.inspect = function inspect(recurseTimes, ctx) { 994 | // Mimic old behavior, could throw but didn't pass a test. 995 | const max = host.INSPECT_MAX_BYTES; 996 | const actualMax = Math.min(max, this.length); 997 | const remaining = this.length - max; 998 | let str = this.hexSlice(0, actualMax).replace(/(.{2})/g, '$1 ').trim(); 999 | if (remaining > 0) str += ` ... ${remaining} more byte${remaining > 1 ? 's' : ''}`; 1000 | return `<${this.constructor.name} ${str}>`; 1001 | }; 1002 | const LocalBuffer = global.Buffer = Contextify.readonly(host.Buffer, BufferMock); 1003 | Contextify.connect(host.Buffer.prototype.inspect, BufferOverride.inspect); 1004 | Contextify.connect(host.Function.prototype.bind, Function.prototype.bind); 1005 | 1006 | const oldPrepareStackTraceDesc = Reflect.getOwnPropertyDescriptor(Error, 'prepareStackTrace'); 1007 | 1008 | let currentPrepareStackTrace = Error.prepareStackTrace; 1009 | const wrappedPrepareStackTrace = new host.WeakMap(); 1010 | if (typeof currentPrepareStackTrace === 'function') { 1011 | wrappedPrepareStackTrace.set(currentPrepareStackTrace, currentPrepareStackTrace); 1012 | } 1013 | 1014 | let OriginalCallSite; 1015 | Error.prepareStackTrace = (e, sst) => { 1016 | OriginalCallSite = sst[0].constructor; 1017 | }; 1018 | new Error().stack; 1019 | if (typeof OriginalCallSite === 'function') { 1020 | Error.prepareStackTrace = undefined; 1021 | 1022 | function makeCallSiteGetters(list) { 1023 | const callSiteGetters = []; 1024 | for (let i=0; i { 1031 | return local.Reflect.apply(func, thiz, []); 1032 | } 1033 | }; 1034 | } 1035 | return callSiteGetters; 1036 | } 1037 | 1038 | function applyCallSiteGetters(callSite, getters) { 1039 | const properties = {__proto__: null}; 1040 | for (let i=0; i { 1110 | if (host.Array.isArray(sst)) { 1111 | for (let i=0; i ReflectApply(func, thiz, args); 51 | } 52 | 53 | const ArrayPrototypeIndexOf = uncurryThis(Array.prototype.indexOf); 54 | const ArrayPrototypeJoin = uncurryThis(Array.prototype.join); 55 | const ArrayPrototypeSlice = uncurryThis(Array.prototype.slice); 56 | const ArrayPrototypeSplice = uncurryThis(Array.prototype.splice); 57 | const ArrayPrototypeUnshift = uncurryThis(Array.prototype.unshift); 58 | 59 | const kRejection = SymbolFor('nodejs.rejection'); 60 | 61 | function inspect(obj) { 62 | return typeof obj === 'symbol' ? obj.toString() : `${obj}`; 63 | } 64 | 65 | function spliceOne(list, index) { 66 | for (; index + 1 < list.length; index++) 67 | list[index] = list[index + 1]; 68 | list.pop(); 69 | } 70 | 71 | function assert(what, message) { 72 | if (!what) throw new Error(message); 73 | } 74 | 75 | function E(key, msg, Base) { 76 | return function NodeError(...args) { 77 | const error = new Base(); 78 | const message = ReflectApply(msg, error, args); 79 | ObjectDefineProperties(error, { 80 | message: { 81 | value: message, 82 | enumerable: false, 83 | writable: true, 84 | configurable: true, 85 | }, 86 | toString: { 87 | value() { 88 | return `${this.name} [${key}]: ${this.message}`; 89 | }, 90 | enumerable: false, 91 | writable: true, 92 | configurable: true, 93 | }, 94 | }); 95 | error.code = key; 96 | return error; 97 | }; 98 | } 99 | 100 | 101 | const ERR_INVALID_ARG_TYPE = E('ERR_INVALID_ARG_TYPE', 102 | (name, expected, actual) => { 103 | assert(typeof name === 'string', "'name' must be a string"); 104 | if (!ArrayIsArray(expected)) { 105 | expected = [expected]; 106 | } 107 | 108 | let msg = 'The '; 109 | if (StringPrototypeEndsWith(name, ' argument')) { 110 | // For cases like 'first argument' 111 | msg += `${name} `; 112 | } else { 113 | const type = StringPrototypeIncludes(name, '.') ? 'property' : 'argument'; 114 | msg += `"${name}" ${type} `; 115 | } 116 | msg += 'must be '; 117 | 118 | const types = []; 119 | const instances = []; 120 | const other = []; 121 | 122 | for (const value of expected) { 123 | assert(typeof value === 'string', 124 | 'All expected entries have to be of type string'); 125 | if (ArrayPrototypeIncludes(kTypes, value)) { 126 | ArrayPrototypePush(types, StringPrototypeToLowerCase(value)); 127 | } else if (RegExpPrototypeTest(classRegExp, value)) { 128 | ArrayPrototypePush(instances, value); 129 | } else { 130 | assert(value !== 'object', 131 | 'The value "object" should be written as "Object"'); 132 | ArrayPrototypePush(other, value); 133 | } 134 | } 135 | 136 | // Special handle `object` in case other instances are allowed to outline 137 | // the differences between each other. 138 | if (instances.length > 0) { 139 | const pos = ArrayPrototypeIndexOf(types, 'object'); 140 | if (pos !== -1) { 141 | ArrayPrototypeSplice(types, pos, 1); 142 | ArrayPrototypePush(instances, 'Object'); 143 | } 144 | } 145 | 146 | if (types.length > 0) { 147 | if (types.length > 2) { 148 | const last = ArrayPrototypePop(types); 149 | msg += `one of type ${ArrayPrototypeJoin(types, ', ')}, or ${last}`; 150 | } else if (types.length === 2) { 151 | msg += `one of type ${types[0]} or ${types[1]}`; 152 | } else { 153 | msg += `of type ${types[0]}`; 154 | } 155 | if (instances.length > 0 || other.length > 0) 156 | msg += ' or '; 157 | } 158 | 159 | if (instances.length > 0) { 160 | if (instances.length > 2) { 161 | const last = ArrayPrototypePop(instances); 162 | msg += 163 | `an instance of ${ArrayPrototypeJoin(instances, ', ')}, or ${last}`; 164 | } else { 165 | msg += `an instance of ${instances[0]}`; 166 | if (instances.length === 2) { 167 | msg += ` or ${instances[1]}`; 168 | } 169 | } 170 | if (other.length > 0) 171 | msg += ' or '; 172 | } 173 | 174 | if (other.length > 0) { 175 | if (other.length > 2) { 176 | const last = ArrayPrototypePop(other); 177 | msg += `one of ${ArrayPrototypeJoin(other, ', ')}, or ${last}`; 178 | } else if (other.length === 2) { 179 | msg += `one of ${other[0]} or ${other[1]}`; 180 | } else { 181 | if (StringPrototypeToLowerCase(other[0]) !== other[0]) 182 | msg += 'an '; 183 | msg += `${other[0]}`; 184 | } 185 | } 186 | 187 | if (actual == null) { 188 | msg += `. Received ${actual}`; 189 | } else if (typeof actual === 'function' && actual.name) { 190 | msg += `. Received function ${actual.name}`; 191 | } else if (typeof actual === 'object') { 192 | if (actual.constructor && actual.constructor.name) { 193 | msg += `. Received an instance of ${actual.constructor.name}`; 194 | } else { 195 | const inspected = inspect(actual, { depth: -1 }); 196 | msg += `. Received ${inspected}`; 197 | } 198 | } else { 199 | let inspected = inspect(actual, { colors: false }); 200 | if (inspected.length > 25) 201 | inspected = `${StringPrototypeSlice(inspected, 0, 25)}...`; 202 | msg += `. Received type ${typeof actual} (${inspected})`; 203 | } 204 | return msg; 205 | }, TypeError); 206 | 207 | const ERR_INVALID_THIS = E('ERR_INVALID_THIS', s => `Value of "this" must be of type ${s}`, TypeError); 208 | 209 | const ERR_OUT_OF_RANGE = E('ERR_OUT_OF_RANGE', 210 | (str, range, input, replaceDefaultBoolean = false) => { 211 | assert(range, 'Missing "range" argument'); 212 | let msg = replaceDefaultBoolean ? str : 213 | `The value of "${str}" is out of range.`; 214 | const received = inspect(input); 215 | msg += ` It must be ${range}. Received ${received}`; 216 | return msg; 217 | }, RangeError); 218 | 219 | const ERR_UNHANDLED_ERROR = E('ERR_UNHANDLED_ERROR', 220 | err => { 221 | const msg = 'Unhandled error.'; 222 | if (err === undefined) return msg; 223 | return `${msg} (${err})`; 224 | }, Error); 225 | 226 | function validateBoolean(value, name) { 227 | if (typeof value !== 'boolean') 228 | throw new ERR_INVALID_ARG_TYPE(name, 'boolean', value); 229 | } 230 | 231 | function validateFunction(value, name) { 232 | if (typeof value !== 'function') 233 | throw new ERR_INVALID_ARG_TYPE(name, 'Function', value); 234 | } 235 | 236 | function validateString(value, name) { 237 | if (typeof value !== 'string') 238 | throw new ERR_INVALID_ARG_TYPE(name, 'string', value); 239 | } 240 | 241 | function nc(cond, e) { 242 | return cond === undefined || cond === null ? e : cond; 243 | } 244 | 245 | function oc(base, key) { 246 | return base === undefined || base === null ? undefined : base[key]; 247 | } 248 | 249 | const kCapture = Symbol('kCapture'); 250 | const kErrorMonitor = host.kErrorMonitor || Symbol('events.errorMonitor'); 251 | const kMaxEventTargetListeners = Symbol('events.maxEventTargetListeners'); 252 | const kMaxEventTargetListenersWarned = 253 | Symbol('events.maxEventTargetListenersWarned'); 254 | 255 | const kIsEventTarget = SymbolFor('nodejs.event_target'); 256 | 257 | function isEventTarget(obj) { 258 | return oc(oc(obj, 'constructor'), kIsEventTarget); 259 | } 260 | 261 | /** 262 | * Creates a new `EventEmitter` instance. 263 | * @param {{ captureRejections?: boolean; }} [opts] 264 | * @constructs {EventEmitter} 265 | */ 266 | function EventEmitter(opts) { 267 | EventEmitter.init.call(this, opts); 268 | } 269 | module.exports = EventEmitter; 270 | if (host.once) module.exports.once = host.once; 271 | if (host.on) module.exports.on = host.on; 272 | if (host.getEventListeners) module.exports.getEventListeners = host.getEventListeners; 273 | // Backwards-compat with node 0.10.x 274 | EventEmitter.EventEmitter = EventEmitter; 275 | 276 | EventEmitter.usingDomains = false; 277 | 278 | EventEmitter.captureRejectionSymbol = kRejection; 279 | ObjectDefineProperty(EventEmitter, 'captureRejections', { 280 | get() { 281 | return EventEmitter.prototype[kCapture]; 282 | }, 283 | set(value) { 284 | validateBoolean(value, 'EventEmitter.captureRejections'); 285 | 286 | EventEmitter.prototype[kCapture] = value; 287 | }, 288 | enumerable: true 289 | }); 290 | 291 | if (host.EventEmitterReferencingAsyncResource) { 292 | const kAsyncResource = Symbol('kAsyncResource'); 293 | const EventEmitterReferencingAsyncResource = host.EventEmitterReferencingAsyncResource; 294 | 295 | class EventEmitterAsyncResource extends EventEmitter { 296 | /** 297 | * @param {{ 298 | * name?: string, 299 | * triggerAsyncId?: number, 300 | * requireManualDestroy?: boolean, 301 | * }} [options] 302 | */ 303 | constructor(options = undefined) { 304 | let name; 305 | if (typeof options === 'string') { 306 | name = options; 307 | options = undefined; 308 | } else { 309 | if (new.target === EventEmitterAsyncResource) { 310 | validateString(oc(options, 'name'), 'options.name'); 311 | } 312 | name = oc(options, 'name') || new.target.name; 313 | } 314 | super(options); 315 | 316 | this[kAsyncResource] = 317 | new EventEmitterReferencingAsyncResource(this, name, options); 318 | } 319 | 320 | /** 321 | * @param {symbol,string} event 322 | * @param {...any} args 323 | * @returns {boolean} 324 | */ 325 | emit(event, ...args) { 326 | if (this[kAsyncResource] === undefined) 327 | throw new ERR_INVALID_THIS('EventEmitterAsyncResource'); 328 | const { asyncResource } = this; 329 | ArrayPrototypeUnshift(args, super.emit, this, event); 330 | return ReflectApply(asyncResource.runInAsyncScope, asyncResource, 331 | args); 332 | } 333 | 334 | /** 335 | * @returns {void} 336 | */ 337 | emitDestroy() { 338 | if (this[kAsyncResource] === undefined) 339 | throw new ERR_INVALID_THIS('EventEmitterAsyncResource'); 340 | this.asyncResource.emitDestroy(); 341 | } 342 | 343 | /** 344 | * @type {number} 345 | */ 346 | get asyncId() { 347 | if (this[kAsyncResource] === undefined) 348 | throw new ERR_INVALID_THIS('EventEmitterAsyncResource'); 349 | return this.asyncResource.asyncId(); 350 | } 351 | 352 | /** 353 | * @type {number} 354 | */ 355 | get triggerAsyncId() { 356 | if (this[kAsyncResource] === undefined) 357 | throw new ERR_INVALID_THIS('EventEmitterAsyncResource'); 358 | return this.asyncResource.triggerAsyncId(); 359 | } 360 | 361 | /** 362 | * @type {EventEmitterReferencingAsyncResource} 363 | */ 364 | get asyncResource() { 365 | if (this[kAsyncResource] === undefined) 366 | throw new ERR_INVALID_THIS('EventEmitterAsyncResource'); 367 | return this[kAsyncResource]; 368 | } 369 | } 370 | EventEmitter.EventEmitterAsyncResource = EventEmitterAsyncResource; 371 | } 372 | 373 | EventEmitter.errorMonitor = kErrorMonitor; 374 | 375 | // The default for captureRejections is false 376 | ObjectDefineProperty(EventEmitter.prototype, kCapture, { 377 | value: false, 378 | writable: true, 379 | enumerable: false 380 | }); 381 | 382 | EventEmitter.prototype._events = undefined; 383 | EventEmitter.prototype._eventsCount = 0; 384 | EventEmitter.prototype._maxListeners = undefined; 385 | 386 | // By default EventEmitters will print a warning if more than 10 listeners are 387 | // added to it. This is a useful default which helps finding memory leaks. 388 | let defaultMaxListeners = 10; 389 | 390 | function checkListener(listener) { 391 | validateFunction(listener, 'listener'); 392 | } 393 | 394 | ObjectDefineProperty(EventEmitter, 'defaultMaxListeners', { 395 | enumerable: true, 396 | get: function() { 397 | return defaultMaxListeners; 398 | }, 399 | set: function(arg) { 400 | if (typeof arg !== 'number' || arg < 0 || NumberIsNaN(arg)) { 401 | throw new ERR_OUT_OF_RANGE('defaultMaxListeners', 402 | 'a non-negative number', 403 | arg); 404 | } 405 | defaultMaxListeners = arg; 406 | } 407 | }); 408 | 409 | ObjectDefineProperties(EventEmitter, { 410 | kMaxEventTargetListeners: { 411 | value: kMaxEventTargetListeners, 412 | enumerable: false, 413 | configurable: false, 414 | writable: false, 415 | }, 416 | kMaxEventTargetListenersWarned: { 417 | value: kMaxEventTargetListenersWarned, 418 | enumerable: false, 419 | configurable: false, 420 | writable: false, 421 | } 422 | }); 423 | 424 | /** 425 | * Sets the max listeners. 426 | * @param {number} n 427 | * @param {EventTarget[] | EventEmitter[]} [eventTargets] 428 | * @returns {void} 429 | */ 430 | EventEmitter.setMaxListeners = 431 | function(n = defaultMaxListeners, ...eventTargets) { 432 | if (typeof n !== 'number' || n < 0 || NumberIsNaN(n)) 433 | throw new ERR_OUT_OF_RANGE('n', 'a non-negative number', n); 434 | if (eventTargets.length === 0) { 435 | defaultMaxListeners = n; 436 | } else { 437 | for (let i = 0; i < eventTargets.length; i++) { 438 | const target = eventTargets[i]; 439 | if (isEventTarget(target)) { 440 | target[kMaxEventTargetListeners] = n; 441 | target[kMaxEventTargetListenersWarned] = false; 442 | } else if (typeof target.setMaxListeners === 'function') { 443 | target.setMaxListeners(n); 444 | } else { 445 | throw new ERR_INVALID_ARG_TYPE( 446 | 'eventTargets', 447 | ['EventEmitter', 'EventTarget'], 448 | target); 449 | } 450 | } 451 | } 452 | }; 453 | 454 | // If you're updating this function definition, please also update any 455 | // re-definitions, such as the one in the Domain module (lib/domain.js). 456 | EventEmitter.init = function(opts) { 457 | 458 | if (this._events === undefined || 459 | this._events === ObjectGetPrototypeOf(this)._events) { 460 | this._events = ObjectCreate(null); 461 | this._eventsCount = 0; 462 | } 463 | 464 | this._maxListeners = this._maxListeners || undefined; 465 | 466 | 467 | if (oc(opts, 'captureRejections')) { 468 | validateBoolean(opts.captureRejections, 'options.captureRejections'); 469 | this[kCapture] = Boolean(opts.captureRejections); 470 | } else { 471 | // Assigning the kCapture property directly saves an expensive 472 | // prototype lookup in a very sensitive hot path. 473 | this[kCapture] = EventEmitter.prototype[kCapture]; 474 | } 475 | }; 476 | 477 | function addCatch(that, promise, type, args) { 478 | if (!that[kCapture]) { 479 | return; 480 | } 481 | 482 | // Handle Promises/A+ spec, then could be a getter 483 | // that throws on second use. 484 | try { 485 | const then = promise.then; 486 | 487 | if (typeof then === 'function') { 488 | then.call(promise, undefined, function(err) { 489 | // The callback is called with nextTick to avoid a follow-up 490 | // rejection from this promise. 491 | process.nextTick(emitUnhandledRejectionOrErr, that, err, type, args); 492 | }); 493 | } 494 | } catch (err) { 495 | that.emit('error', err); 496 | } 497 | } 498 | 499 | function emitUnhandledRejectionOrErr(ee, err, type, args) { 500 | if (typeof ee[kRejection] === 'function') { 501 | ee[kRejection](err, type, ...args); 502 | } else { 503 | // We have to disable the capture rejections mechanism, otherwise 504 | // we might end up in an infinite loop. 505 | const prev = ee[kCapture]; 506 | 507 | // If the error handler throws, it is not catchable and it 508 | // will end up in 'uncaughtException'. We restore the previous 509 | // value of kCapture in case the uncaughtException is present 510 | // and the exception is handled. 511 | try { 512 | ee[kCapture] = false; 513 | ee.emit('error', err); 514 | } finally { 515 | ee[kCapture] = prev; 516 | } 517 | } 518 | } 519 | 520 | /** 521 | * Increases the max listeners of the event emitter. 522 | * @param {number} n 523 | * @returns {EventEmitter} 524 | */ 525 | EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) { 526 | if (typeof n !== 'number' || n < 0 || NumberIsNaN(n)) { 527 | throw new ERR_OUT_OF_RANGE('n', 'a non-negative number', n); 528 | } 529 | this._maxListeners = n; 530 | return this; 531 | }; 532 | 533 | function _getMaxListeners(that) { 534 | if (that._maxListeners === undefined) 535 | return EventEmitter.defaultMaxListeners; 536 | return that._maxListeners; 537 | } 538 | 539 | /** 540 | * Returns the current max listener value for the event emitter. 541 | * @returns {number} 542 | */ 543 | EventEmitter.prototype.getMaxListeners = function getMaxListeners() { 544 | return _getMaxListeners(this); 545 | }; 546 | 547 | /** 548 | * Synchronously calls each of the listeners registered 549 | * for the event. 550 | * @param {string | symbol} type 551 | * @param {...any} [args] 552 | * @returns {boolean} 553 | */ 554 | EventEmitter.prototype.emit = function emit(type, ...args) { 555 | let doError = (type === 'error'); 556 | 557 | const events = this._events; 558 | if (events !== undefined) { 559 | if (doError && events[kErrorMonitor] !== undefined) 560 | this.emit(kErrorMonitor, ...args); 561 | doError = (doError && events.error === undefined); 562 | } else if (!doError) 563 | return false; 564 | 565 | // If there is no 'error' event listener then throw. 566 | if (doError) { 567 | let er; 568 | if (args.length > 0) 569 | er = args[0]; 570 | if (er instanceof Error) { 571 | try { 572 | const capture = {}; 573 | ErrorCaptureStackTrace(capture, EventEmitter.prototype.emit); 574 | } catch (e) {} 575 | 576 | // Note: The comments on the `throw` lines are intentional, they show 577 | // up in Node's output if this results in an unhandled exception. 578 | throw er; // Unhandled 'error' event 579 | } 580 | 581 | let stringifiedEr; 582 | try { 583 | stringifiedEr = inspect(er); 584 | } catch (e) { 585 | stringifiedEr = er; 586 | } 587 | 588 | // At least give some kind of context to the user 589 | const err = new ERR_UNHANDLED_ERROR(stringifiedEr); 590 | err.context = er; 591 | throw err; // Unhandled 'error' event 592 | } 593 | 594 | const handler = events[type]; 595 | 596 | if (handler === undefined) 597 | return false; 598 | 599 | if (typeof handler === 'function') { 600 | const result = handler.apply(this, args); 601 | 602 | // We check if result is undefined first because that 603 | // is the most common case so we do not pay any perf 604 | // penalty 605 | if (result !== undefined && result !== null) { 606 | addCatch(this, result, type, args); 607 | } 608 | } else { 609 | const len = handler.length; 610 | const listeners = arrayClone(handler); 611 | for (let i = 0; i < len; ++i) { 612 | const result = listeners[i].apply(this, args); 613 | 614 | // We check if result is undefined first because that 615 | // is the most common case so we do not pay any perf 616 | // penalty. 617 | // This code is duplicated because extracting it away 618 | // would make it non-inlineable. 619 | if (result !== undefined && result !== null) { 620 | addCatch(this, result, type, args); 621 | } 622 | } 623 | } 624 | 625 | return true; 626 | }; 627 | 628 | function _addListener(target, type, listener, prepend) { 629 | let m; 630 | let events; 631 | let existing; 632 | 633 | checkListener(listener); 634 | 635 | events = target._events; 636 | if (events === undefined) { 637 | events = target._events = ObjectCreate(null); 638 | target._eventsCount = 0; 639 | } else { 640 | // To avoid recursion in the case that type === "newListener"! Before 641 | // adding it to the listeners, first emit "newListener". 642 | if (events.newListener !== undefined) { 643 | target.emit('newListener', type, 644 | nc(listener.listener, listener)); 645 | 646 | // Re-assign `events` because a newListener handler could have caused the 647 | // this._events to be assigned to a new object 648 | events = target._events; 649 | } 650 | existing = events[type]; 651 | } 652 | 653 | if (existing === undefined) { 654 | // Optimize the case of one listener. Don't need the extra array object. 655 | events[type] = listener; 656 | ++target._eventsCount; 657 | } else { 658 | if (typeof existing === 'function') { 659 | // Adding the second element, need to change to array. 660 | existing = events[type] = 661 | prepend ? [listener, existing] : [existing, listener]; 662 | // If we've already got an array, just append. 663 | } else if (prepend) { 664 | existing.unshift(listener); 665 | } else { 666 | existing.push(listener); 667 | } 668 | 669 | // Check for listener leak 670 | m = _getMaxListeners(target); 671 | if (m > 0 && existing.length > m && !existing.warned) { 672 | existing.warned = true; 673 | // No error code for this since it is a Warning 674 | // eslint-disable-next-line no-restricted-syntax 675 | const w = new Error('Possible EventEmitter memory leak detected. ' + 676 | `${existing.length} ${String(type)} listeners ` + 677 | `added to ${inspect(target, { depth: -1 })}. Use ` + 678 | 'emitter.setMaxListeners() to increase limit'); 679 | w.name = 'MaxListenersExceededWarning'; 680 | w.emitter = target; 681 | w.type = type; 682 | w.count = existing.length; 683 | process.emitWarning(w); 684 | } 685 | } 686 | 687 | return target; 688 | } 689 | 690 | /** 691 | * Adds a listener to the event emitter. 692 | * @param {string | symbol} type 693 | * @param {Function} listener 694 | * @returns {EventEmitter} 695 | */ 696 | EventEmitter.prototype.addListener = function addListener(type, listener) { 697 | return _addListener(this, type, listener, false); 698 | }; 699 | 700 | EventEmitter.prototype.on = EventEmitter.prototype.addListener; 701 | 702 | /** 703 | * Adds the `listener` function to the beginning of 704 | * the listeners array. 705 | * @param {string | symbol} type 706 | * @param {Function} listener 707 | * @returns {EventEmitter} 708 | */ 709 | EventEmitter.prototype.prependListener = 710 | function prependListener(type, listener) { 711 | return _addListener(this, type, listener, true); 712 | }; 713 | 714 | function onceWrapper() { 715 | if (!this.fired) { 716 | this.target.removeListener(this.type, this.wrapFn); 717 | this.fired = true; 718 | if (arguments.length === 0) 719 | return this.listener.call(this.target); 720 | return this.listener.apply(this.target, arguments); 721 | } 722 | } 723 | 724 | function _onceWrap(target, type, listener) { 725 | const state = { fired: false, wrapFn: undefined, target, type, listener }; 726 | const wrapped = onceWrapper.bind(state); 727 | wrapped.listener = listener; 728 | state.wrapFn = wrapped; 729 | return wrapped; 730 | } 731 | 732 | /** 733 | * Adds a one-time `listener` function to the event emitter. 734 | * @param {string | symbol} type 735 | * @param {Function} listener 736 | * @returns {EventEmitter} 737 | */ 738 | EventEmitter.prototype.once = function once(type, listener) { 739 | checkListener(listener); 740 | 741 | this.on(type, _onceWrap(this, type, listener)); 742 | return this; 743 | }; 744 | 745 | /** 746 | * Adds a one-time `listener` function to the beginning of 747 | * the listeners array. 748 | * @param {string | symbol} type 749 | * @param {Function} listener 750 | * @returns {EventEmitter} 751 | */ 752 | EventEmitter.prototype.prependOnceListener = 753 | function prependOnceListener(type, listener) { 754 | checkListener(listener); 755 | 756 | this.prependListener(type, _onceWrap(this, type, listener)); 757 | return this; 758 | }; 759 | 760 | 761 | /** 762 | * Removes the specified `listener` from the listeners array. 763 | * @param {string | symbol} type 764 | * @param {Function} listener 765 | * @returns {EventEmitter} 766 | */ 767 | EventEmitter.prototype.removeListener = 768 | function removeListener(type, listener) { 769 | checkListener(listener); 770 | 771 | const events = this._events; 772 | if (events === undefined) 773 | return this; 774 | 775 | const list = events[type]; 776 | if (list === undefined) 777 | return this; 778 | 779 | if (list === listener || list.listener === listener) { 780 | if (--this._eventsCount === 0) 781 | this._events = ObjectCreate(null); 782 | else { 783 | delete events[type]; 784 | if (events.removeListener) 785 | this.emit('removeListener', type, list.listener || listener); 786 | } 787 | } else if (typeof list !== 'function') { 788 | let position = -1; 789 | 790 | for (let i = list.length - 1; i >= 0; i--) { 791 | if (list[i] === listener || list[i].listener === listener) { 792 | position = i; 793 | break; 794 | } 795 | } 796 | 797 | if (position < 0) 798 | return this; 799 | 800 | if (position === 0) 801 | list.shift(); 802 | else { 803 | spliceOne(list, position); 804 | } 805 | 806 | if (list.length === 1) 807 | events[type] = list[0]; 808 | 809 | if (events.removeListener !== undefined) 810 | this.emit('removeListener', type, listener); 811 | } 812 | 813 | return this; 814 | }; 815 | 816 | EventEmitter.prototype.off = EventEmitter.prototype.removeListener; 817 | 818 | /** 819 | * Removes all listeners from the event emitter. (Only 820 | * removes listeners for a specific event name if specified 821 | * as `type`). 822 | * @param {string | symbol} [type] 823 | * @returns {EventEmitter} 824 | */ 825 | EventEmitter.prototype.removeAllListeners = 826 | function removeAllListeners(type) { 827 | const events = this._events; 828 | if (events === undefined) 829 | return this; 830 | 831 | // Not listening for removeListener, no need to emit 832 | if (events.removeListener === undefined) { 833 | if (arguments.length === 0) { 834 | this._events = ObjectCreate(null); 835 | this._eventsCount = 0; 836 | } else if (events[type] !== undefined) { 837 | if (--this._eventsCount === 0) 838 | this._events = ObjectCreate(null); 839 | else 840 | delete events[type]; 841 | } 842 | return this; 843 | } 844 | 845 | // Emit removeListener for all listeners on all events 846 | if (arguments.length === 0) { 847 | for (const key of ReflectOwnKeys(events)) { 848 | if (key === 'removeListener') continue; 849 | this.removeAllListeners(key); 850 | } 851 | this.removeAllListeners('removeListener'); 852 | this._events = ObjectCreate(null); 853 | this._eventsCount = 0; 854 | return this; 855 | } 856 | 857 | const listeners = events[type]; 858 | 859 | if (typeof listeners === 'function') { 860 | this.removeListener(type, listeners); 861 | } else if (listeners !== undefined) { 862 | // LIFO order 863 | for (let i = listeners.length - 1; i >= 0; i--) { 864 | this.removeListener(type, listeners[i]); 865 | } 866 | } 867 | 868 | return this; 869 | }; 870 | 871 | function _listeners(target, type, unwrap) { 872 | const events = target._events; 873 | 874 | if (events === undefined) 875 | return []; 876 | 877 | const evlistener = events[type]; 878 | if (evlistener === undefined) 879 | return []; 880 | 881 | if (typeof evlistener === 'function') 882 | return unwrap ? [evlistener.listener || evlistener] : [evlistener]; 883 | 884 | return unwrap ? 885 | unwrapListeners(evlistener) : arrayClone(evlistener); 886 | } 887 | 888 | /** 889 | * Returns a copy of the array of listeners for the event name 890 | * specified as `type`. 891 | * @param {string | symbol} type 892 | * @returns {Function[]} 893 | */ 894 | EventEmitter.prototype.listeners = function listeners(type) { 895 | return _listeners(this, type, true); 896 | }; 897 | 898 | /** 899 | * Returns a copy of the array of listeners and wrappers for 900 | * the event name specified as `type`. 901 | * @param {string | symbol} type 902 | * @returns {Function[]} 903 | */ 904 | EventEmitter.prototype.rawListeners = function rawListeners(type) { 905 | return _listeners(this, type, false); 906 | }; 907 | 908 | /** 909 | * Returns the number of listeners listening to the event name 910 | * specified as `type`. 911 | * @deprecated since v3.2.0 912 | * @param {EventEmitter} emitter 913 | * @param {string | symbol} type 914 | * @returns {number} 915 | */ 916 | EventEmitter.listenerCount = function(emitter, type) { 917 | if (typeof emitter.listenerCount === 'function') { 918 | return emitter.listenerCount(type); 919 | } 920 | return emitter.listenerCount(type); 921 | }; 922 | 923 | EventEmitter.prototype.listenerCount = listenerCount; 924 | 925 | /** 926 | * Returns the number of listeners listening to event name 927 | * specified as `type`. 928 | * @param {string | symbol} type 929 | * @returns {number} 930 | */ 931 | function listenerCount(type) { 932 | const events = this._events; 933 | 934 | if (events !== undefined) { 935 | const evlistener = events[type]; 936 | 937 | if (typeof evlistener === 'function') { 938 | return 1; 939 | } else if (evlistener !== undefined) { 940 | return evlistener.length; 941 | } 942 | } 943 | 944 | return 0; 945 | } 946 | 947 | /** 948 | * Returns an array listing the events for which 949 | * the emitter has registered listeners. 950 | * @returns {any[]} 951 | */ 952 | EventEmitter.prototype.eventNames = function eventNames() { 953 | return this._eventsCount > 0 ? ReflectOwnKeys(this._events) : []; 954 | }; 955 | 956 | function arrayClone(arr) { 957 | // At least since V8 8.3, this implementation is faster than the previous 958 | // which always used a simple for-loop 959 | switch (arr.length) { 960 | case 2: return [arr[0], arr[1]]; 961 | case 3: return [arr[0], arr[1], arr[2]]; 962 | case 4: return [arr[0], arr[1], arr[2], arr[3]]; 963 | case 5: return [arr[0], arr[1], arr[2], arr[3], arr[4]]; 964 | case 6: return [arr[0], arr[1], arr[2], arr[3], arr[4], arr[5]]; 965 | } 966 | return ArrayPrototypeSlice(arr); 967 | } 968 | 969 | function unwrapListeners(arr) { 970 | const ret = arrayClone(arr); 971 | for (let i = 0; i < ret.length; ++i) { 972 | const orig = ret[i].listener; 973 | if (typeof orig === 'function') 974 | ret[i] = orig; 975 | } 976 | return ret; 977 | } 978 | -------------------------------------------------------------------------------- /dist/fixasync.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // eslint-disable-next-line no-invalid-this, no-shadow 4 | const {GeneratorFunction, AsyncFunction, AsyncGeneratorFunction, global, internal, host, hook} = this; 5 | const {Contextify, Decontextify} = internal; 6 | // eslint-disable-next-line no-shadow 7 | const {Function, eval: eval_, Promise, Object, Reflect} = global; 8 | const {getOwnPropertyDescriptor, defineProperty, assign} = Object; 9 | const {apply: rApply, construct: rConstruct} = Reflect; 10 | 11 | const FunctionHandler = { 12 | __proto__: null, 13 | apply(target, thiz, args) { 14 | const type = this.type; 15 | args = Decontextify.arguments(args); 16 | try { 17 | args = Contextify.value(hook(type, args)); 18 | } catch (e) { 19 | throw Contextify.value(e); 20 | } 21 | return rApply(target, thiz, args); 22 | }, 23 | construct(target, args, newTarget) { 24 | const type = this.type; 25 | args = Decontextify.arguments(args); 26 | try { 27 | args = Contextify.value(hook(type, args)); 28 | } catch (e) { 29 | throw Contextify.value(e); 30 | } 31 | return rConstruct(target, args, newTarget); 32 | } 33 | }; 34 | 35 | function makeCheckFunction(type) { 36 | return assign({ 37 | __proto__: null, 38 | type 39 | }, FunctionHandler); 40 | } 41 | 42 | function override(obj, prop, value) { 43 | const desc = getOwnPropertyDescriptor(obj, prop); 44 | desc.value = value; 45 | defineProperty(obj, prop, desc); 46 | } 47 | 48 | const proxiedFunction = new host.Proxy(Function, makeCheckFunction('function')); 49 | override(Function.prototype, 'constructor', proxiedFunction); 50 | if (GeneratorFunction) { 51 | Object.setPrototypeOf(GeneratorFunction, proxiedFunction); 52 | override(GeneratorFunction.prototype, 'constructor', new host.Proxy(GeneratorFunction, makeCheckFunction('generator_function'))); 53 | } 54 | if (AsyncFunction) { 55 | Object.setPrototypeOf(AsyncFunction, proxiedFunction); 56 | override(AsyncFunction.prototype, 'constructor', new host.Proxy(AsyncFunction, makeCheckFunction('async_function'))); 57 | } 58 | if (AsyncGeneratorFunction) { 59 | Object.setPrototypeOf(AsyncGeneratorFunction, proxiedFunction); 60 | override(AsyncGeneratorFunction.prototype, 'constructor', new host.Proxy(AsyncGeneratorFunction, makeCheckFunction('async_generator_function'))); 61 | } 62 | 63 | global.Function = proxiedFunction; 64 | global.eval = new host.Proxy(eval_, makeCheckFunction('eval')); 65 | 66 | if (Promise) { 67 | 68 | Promise.prototype.then = new host.Proxy(Promise.prototype.then, makeCheckFunction('promise_then')); 69 | // This seems not to work, and will produce 70 | // UnhandledPromiseRejectionWarning: TypeError: Method Promise.prototype.then called on incompatible receiver [object Object]. 71 | // This is likely caused since the host.Promise.prototype.then cannot use the VM Proxy object. 72 | // Contextify.connect(host.Promise.prototype.then, Promise.prototype.then); 73 | 74 | if (Promise.prototype.finally) { 75 | Promise.prototype.finally = new host.Proxy(Promise.prototype.finally, makeCheckFunction('promise_finally')); 76 | // Contextify.connect(host.Promise.prototype.finally, Promise.prototype.finally); 77 | } 78 | if (Promise.prototype.catch) { 79 | Promise.prototype.catch = new host.Proxy(Promise.prototype.catch, makeCheckFunction('promise_catch')); 80 | // Contextify.connect(host.Promise.prototype.catch, Promise.prototype.catch); 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /dist/sandbox.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-shadow, no-invalid-this */ 2 | /* global vm, host, Contextify, Decontextify, VMError, options */ 3 | 4 | 'use strict'; 5 | 6 | const {Script} = host.require('vm'); 7 | const fs = host.require('fs'); 8 | const pa = host.require('path'); 9 | 10 | const BUILTIN_MODULES = host.process.binding('natives'); 11 | const parseJSON = JSON.parse; 12 | const importModuleDynamically = () => { 13 | // We can't throw an error object here because since vm.Script doesn't store a context, we can't properly contextify that error object. 14 | // eslint-disable-next-line no-throw-literal 15 | throw 'Dynamic imports are not allowed.'; 16 | }; 17 | 18 | /** 19 | * @param {Object} host Hosts's internal objects. 20 | */ 21 | 22 | return ((vm, host) => { 23 | 'use strict'; 24 | 25 | const global = this; 26 | 27 | const TIMERS = new host.WeakMap(); // Contains map of timers created inside sandbox 28 | const BUILTINS = {__proto__: null}; 29 | const CACHE = {__proto__: null}; 30 | const EXTENSIONS = { 31 | __proto__: null, 32 | ['.json'](module, filename) { 33 | try { 34 | const code = fs.readFileSync(filename, 'utf8'); 35 | module.exports = parseJSON(code); 36 | } catch (e) { 37 | throw Contextify.value(e); 38 | } 39 | }, 40 | ['.node'](module, filename) { 41 | if (vm.options.require.context === 'sandbox') throw new VMError('Native modules can be required only with context set to \'host\'.'); 42 | 43 | try { 44 | module.exports = Contextify.readonly(host.require(filename)); 45 | } catch (e) { 46 | throw Contextify.value(e); 47 | } 48 | } 49 | }; 50 | 51 | for (let i = 0; i < vm.options.sourceExtensions.length; i++) { 52 | const ext = vm.options.sourceExtensions[i]; 53 | 54 | EXTENSIONS['.' + ext] = (module, filename, dirname) => { 55 | if (vm.options.require.context !== 'sandbox') { 56 | try { 57 | module.exports = Contextify.readonly(host.require(filename)); 58 | } catch (e) { 59 | throw Contextify.value(e); 60 | } 61 | } else { 62 | let script; 63 | 64 | try { 65 | // Load module 66 | let contents = fs.readFileSync(filename, 'utf8'); 67 | contents = vm._compiler(contents, filename); 68 | 69 | const code = host.STRICT_MODULE_PREFIX + contents + host.MODULE_SUFFIX; 70 | 71 | const ccode = vm._hook('run', [code]); 72 | 73 | // Precompile script 74 | script = new Script(ccode, { 75 | __proto__: null, 76 | filename: filename || 'vm.js', 77 | displayErrors: false, 78 | importModuleDynamically 79 | }); 80 | 81 | } catch (ex) { 82 | throw Contextify.value(ex); 83 | } 84 | 85 | const closure = script.runInContext(global, { 86 | __proto__: null, 87 | filename: filename || 'vm.js', 88 | displayErrors: false, 89 | importModuleDynamically 90 | }); 91 | 92 | // run the script 93 | closure(module.exports, module.require, module, filename, dirname); 94 | } 95 | }; 96 | } 97 | 98 | const _parseExternalOptions = (options) => { 99 | if (host.Array.isArray(options)) { 100 | return { 101 | __proto__: null, 102 | external: options, 103 | transitive: false 104 | }; 105 | } 106 | 107 | return { 108 | __proto__: null, 109 | external: options.modules, 110 | transitive: options.transitive 111 | }; 112 | }; 113 | 114 | /** 115 | * Resolve filename. 116 | */ 117 | 118 | const _resolveFilename = (path) => { 119 | if (!path) return null; 120 | let hasPackageJson; 121 | try { 122 | path = pa.resolve(path); 123 | 124 | const exists = fs.existsSync(path); 125 | const isdir = exists ? fs.statSync(path).isDirectory() : false; 126 | 127 | // direct file match 128 | if (exists && !isdir) return path; 129 | 130 | // load as file 131 | 132 | for (let i = 0; i < vm.options.sourceExtensions.length; i++) { 133 | const ext = vm.options.sourceExtensions[i]; 134 | if (fs.existsSync(`${path}.${ext}`)) return `${path}.${ext}`; 135 | } 136 | if (fs.existsSync(`${path}.json`)) return `${path}.json`; 137 | if (fs.existsSync(`${path}.node`)) return `${path}.node`; 138 | 139 | // load as module 140 | 141 | hasPackageJson = fs.existsSync(`${path}/package.json`); 142 | } catch (e) { 143 | throw Contextify.value(e); 144 | } 145 | 146 | if (hasPackageJson) { 147 | let pkg; 148 | try { 149 | pkg = fs.readFileSync(`${path}/package.json`, 'utf8'); 150 | } catch (e) { 151 | throw Contextify.value(e); 152 | } 153 | try { 154 | pkg = parseJSON(pkg); 155 | } catch (ex) { 156 | throw new VMError(`Module '${path}' has invalid package.json`, 'EMODULEINVALID'); 157 | } 158 | 159 | let main; 160 | if (pkg && pkg.main) { 161 | main = _resolveFilename(`${path}/${pkg.main}`); 162 | if (!main) main = _resolveFilename(`${path}/index`); 163 | } else { 164 | main = _resolveFilename(`${path}/index`); 165 | } 166 | 167 | return main; 168 | } 169 | 170 | // load as directory 171 | 172 | try { 173 | for (let i = 0; i < vm.options.sourceExtensions.length; i++) { 174 | const ext = vm.options.sourceExtensions[i]; 175 | if (fs.existsSync(`${path}/index.${ext}`)) return `${path}/index.${ext}`; 176 | } 177 | 178 | if (fs.existsSync(`${path}/index.json`)) return `${path}/index.json`; 179 | if (fs.existsSync(`${path}/index.node`)) return `${path}/index.node`; 180 | } catch (e) { 181 | throw Contextify.value(e); 182 | } 183 | 184 | return null; 185 | }; 186 | 187 | /** 188 | * Builtin require. 189 | */ 190 | 191 | const _requireBuiltin = (moduleName) => { 192 | if (moduleName === 'buffer') return ({Buffer}); 193 | if (BUILTINS[moduleName]) return BUILTINS[moduleName].exports; // Only compiled builtins are stored here 194 | 195 | if (moduleName === 'util') { 196 | return Contextify.readonly(host.require(moduleName), { 197 | // Allows VM context to use util.inherits 198 | __proto__: null, 199 | inherits: (ctor, superCtor) => { 200 | ctor.super_ = superCtor; 201 | Object.setPrototypeOf(ctor.prototype, superCtor.prototype); 202 | } 203 | }); 204 | } 205 | 206 | if (moduleName === 'events' || moduleName === 'internal/errors') { 207 | let script; 208 | try { 209 | script = new Script(`(function (exports, require, module, process, internalBinding) { 210 | 'use strict'; 211 | const primordials = global; 212 | ${BUILTIN_MODULES[moduleName]} 213 | \n 214 | });`, { 215 | filename: `${moduleName}.vm.js` 216 | }); 217 | 218 | } catch (e) { 219 | throw Contextify.value(e); 220 | } 221 | 222 | // setup module scope 223 | const module = BUILTINS[moduleName] = { 224 | exports: {}, 225 | require: _requireBuiltin 226 | }; 227 | 228 | // run script 229 | try { 230 | // FIXME binding should be contextified 231 | script.runInContext(global)(module.exports, module.require, module, host.process, host.process.binding); 232 | } catch (e) { 233 | // e could be from inside or outside of sandbox 234 | throw new VMError(`Error loading '${moduleName}'`); 235 | } 236 | return module.exports; 237 | } 238 | 239 | return Contextify.readonly(host.require(moduleName)); 240 | }; 241 | 242 | /** 243 | * Prepare require. 244 | */ 245 | 246 | const _prepareRequire = (currentDirname, parentAllowsTransitive = false) => { 247 | const _require = moduleName => { 248 | let requireObj; 249 | try { 250 | const optionsObj = vm.options; 251 | if (optionsObj.nesting && moduleName === 'vm2') return {VM: Contextify.readonly(host.VM), NodeVM: Contextify.readonly(host.NodeVM)}; 252 | requireObj = optionsObj.require; 253 | } catch (e) { 254 | throw Contextify.value(e); 255 | } 256 | 257 | if (!requireObj) throw new VMError(`Access denied to require '${moduleName}'`, 'EDENIED'); 258 | if (moduleName == null) throw new VMError("Module '' not found.", 'ENOTFOUND'); 259 | if (typeof moduleName !== 'string') throw new VMError(`Invalid module name '${moduleName}'`, 'EINVALIDNAME'); 260 | 261 | let filename; 262 | let allowRequireTransitive = false; 263 | 264 | // Mock? 265 | 266 | try { 267 | const {mock} = requireObj; 268 | if (mock) { 269 | const mockModule = mock[moduleName]; 270 | if (mockModule) { 271 | return Contextify.readonly(mockModule); 272 | } 273 | } 274 | } catch (e) { 275 | throw Contextify.value(e); 276 | } 277 | 278 | // Builtin? 279 | 280 | if (BUILTIN_MODULES[moduleName]) { 281 | let allowed; 282 | try { 283 | const builtinObj = requireObj.builtin; 284 | if (host.Array.isArray(builtinObj)) { 285 | if (builtinObj.indexOf('*') >= 0) { 286 | allowed = builtinObj.indexOf(`-${moduleName}`) === -1; 287 | } else { 288 | allowed = builtinObj.indexOf(moduleName) >= 0; 289 | } 290 | } else if (builtinObj) { 291 | allowed = builtinObj[moduleName]; 292 | } else { 293 | allowed = false; 294 | } 295 | } catch (e) { 296 | throw Contextify.value(e); 297 | } 298 | if (!allowed) throw new VMError(`Access denied to require '${moduleName}'`, 'EDENIED'); 299 | 300 | return _requireBuiltin(moduleName); 301 | } 302 | 303 | // External? 304 | 305 | let externalObj; 306 | try { 307 | externalObj = requireObj.external; 308 | } catch (e) { 309 | throw Contextify.value(e); 310 | } 311 | 312 | if (!externalObj) throw new VMError(`Access denied to require '${moduleName}'`, 'EDENIED'); 313 | 314 | if (/^(\.|\.\/|\.\.\/)/.exec(moduleName)) { 315 | // Module is relative file, e.g. ./script.js or ../script.js 316 | 317 | if (!currentDirname) throw new VMError('You must specify script path to load relative modules.', 'ENOPATH'); 318 | 319 | filename = _resolveFilename(`${currentDirname}/${moduleName}`); 320 | } else if (/^(\/|\\|[a-zA-Z]:\\)/.exec(moduleName)) { 321 | // Module is absolute file, e.g. /script.js or //server/script.js or C:\script.js 322 | 323 | filename = _resolveFilename(moduleName); 324 | } else { 325 | // Check node_modules in path 326 | 327 | if (!currentDirname) throw new VMError('You must specify script path to load relative modules.', 'ENOPATH'); 328 | 329 | if (typeof externalObj === 'object') { 330 | let isWhitelisted; 331 | try { 332 | const { external, transitive } = _parseExternalOptions(externalObj); 333 | 334 | isWhitelisted = external.some(ext => host.helpers.match(ext, moduleName)) || (transitive && parentAllowsTransitive); 335 | } catch (e) { 336 | throw Contextify.value(e); 337 | } 338 | if (!isWhitelisted) { 339 | throw new VMError(`The module '${moduleName}' is not whitelisted in VM.`, 'EDENIED'); 340 | } 341 | 342 | allowRequireTransitive = true; 343 | } 344 | 345 | // FIXME the paths array has side effects 346 | const paths = currentDirname.split(pa.sep); 347 | 348 | while (paths.length) { 349 | const path = paths.join(pa.sep); 350 | 351 | // console.log moduleName, "#{path}#{pa.sep}node_modules#{pa.sep}#{moduleName}" 352 | 353 | filename = _resolveFilename(`${path}${pa.sep}node_modules${pa.sep}${moduleName}`); 354 | if (filename) break; 355 | 356 | paths.pop(); 357 | } 358 | } 359 | 360 | if (!filename) { 361 | let resolveFunc; 362 | try { 363 | resolveFunc = requireObj.resolve; 364 | } catch (e) { 365 | throw Contextify.value(e); 366 | } 367 | if (resolveFunc) { 368 | let resolved; 369 | try { 370 | resolved = requireObj.resolve(moduleName, currentDirname); 371 | } catch (e) { 372 | throw Contextify.value(e); 373 | } 374 | filename = _resolveFilename(resolved); 375 | } 376 | } 377 | if (!filename) throw new VMError(`Cannot find module '${moduleName}'`, 'ENOTFOUND'); 378 | 379 | // return cache whenever possible 380 | if (CACHE[filename]) return CACHE[filename].exports; 381 | 382 | const dirname = pa.dirname(filename); 383 | const extname = pa.extname(filename); 384 | 385 | let allowedModule = true; 386 | try { 387 | const rootObj = requireObj.root; 388 | if (rootObj) { 389 | const rootPaths = host.Array.isArray(rootObj) ? rootObj : host.Array.of(rootObj); 390 | allowedModule = rootPaths.some(path => host.String.prototype.startsWith.call(dirname, pa.resolve(path))); 391 | } 392 | } catch (e) { 393 | throw Contextify.value(e); 394 | } 395 | 396 | if (!allowedModule) { 397 | throw new VMError(`Module '${moduleName}' is not allowed to be required. The path is outside the border!`, 'EDENIED'); 398 | } 399 | 400 | const module = CACHE[filename] = { 401 | filename, 402 | exports: {}, 403 | require: _prepareRequire(dirname, allowRequireTransitive) 404 | }; 405 | 406 | // lookup extensions 407 | if (EXTENSIONS[extname]) { 408 | EXTENSIONS[extname](module, filename, dirname); 409 | return module.exports; 410 | } 411 | 412 | throw new VMError(`Failed to load '${moduleName}': Unknown type.`, 'ELOADFAIL'); 413 | }; 414 | 415 | return _require; 416 | }; 417 | 418 | /** 419 | * Prepare sandbox. 420 | */ 421 | 422 | // This is a function and not an arrow function, since the original is also a function 423 | global.setTimeout = function setTimeout(callback, delay, ...args) { 424 | if (typeof callback !== 'function') throw new TypeError('"callback" argument must be a function'); 425 | let tmr; 426 | try { 427 | tmr = host.setTimeout(Decontextify.value(() => { 428 | // FIXME ...args has side effects 429 | callback(...args); 430 | }), Decontextify.value(delay)); 431 | } catch (e) { 432 | throw Contextify.value(e); 433 | } 434 | const local = Contextify.value(tmr); 435 | 436 | TIMERS.set(local, tmr); 437 | return local; 438 | }; 439 | 440 | global.setInterval = function setInterval(callback, interval, ...args) { 441 | if (typeof callback !== 'function') throw new TypeError('"callback" argument must be a function'); 442 | let tmr; 443 | try { 444 | tmr = host.setInterval(Decontextify.value(() => { 445 | // FIXME ...args has side effects 446 | callback(...args); 447 | }), Decontextify.value(interval)); 448 | } catch (e) { 449 | throw Contextify.value(e); 450 | } 451 | 452 | const local = Contextify.value(tmr); 453 | 454 | TIMERS.set(local, tmr); 455 | return local; 456 | }; 457 | 458 | global.setImmediate = function setImmediate(callback, ...args) { 459 | if (typeof callback !== 'function') throw new TypeError('"callback" argument must be a function'); 460 | let tmr; 461 | try { 462 | tmr = host.setImmediate(Decontextify.value(() => { 463 | // FIXME ...args has side effects 464 | callback(...args); 465 | })); 466 | } catch (e) { 467 | throw Contextify.value(e); 468 | } 469 | 470 | const local = Contextify.value(tmr); 471 | 472 | TIMERS.set(local, tmr); 473 | return local; 474 | }; 475 | 476 | global.clearTimeout = function clearTimeout(local) { 477 | try { 478 | host.clearTimeout(TIMERS.get(local)); 479 | } catch (e) { 480 | throw Contextify.value(e); 481 | } 482 | }; 483 | 484 | global.clearInterval = function clearInterval(local) { 485 | try { 486 | host.clearInterval(TIMERS.get(local)); 487 | } catch (e) { 488 | throw Contextify.value(e); 489 | } 490 | }; 491 | 492 | global.clearImmediate = function clearImmediate(local) { 493 | try { 494 | host.clearImmediate(TIMERS.get(local)); 495 | } catch (e) { 496 | throw Contextify.value(e); 497 | } 498 | }; 499 | 500 | function addListener(name, handler) { 501 | if (name !== 'beforeExit' && name !== 'exit') { 502 | throw new Error(`Access denied to listen for '${name}' event.`); 503 | } 504 | 505 | try { 506 | host.process.on(name, Decontextify.value(handler)); 507 | } catch (e) { 508 | throw Contextify.value(e); 509 | } 510 | 511 | return this; 512 | } 513 | 514 | const {argv: optionArgv, env: optionsEnv} = options; 515 | 516 | // FIXME wrong class structure 517 | global.process = { 518 | argv: optionArgv !== undefined ? Contextify.value(optionArgv) : [], 519 | title: host.process.title, 520 | version: host.process.version, 521 | versions: Contextify.readonly(host.process.versions), 522 | arch: host.process.arch, 523 | platform: host.process.platform, 524 | env: optionsEnv !== undefined ? Contextify.value(optionsEnv) : {}, 525 | pid: host.process.pid, 526 | features: Contextify.readonly(host.process.features), 527 | nextTick: function nextTick(callback, ...args) { 528 | if (typeof callback !== 'function') { 529 | throw new Error('Callback must be a function.'); 530 | } 531 | 532 | try { 533 | host.process.nextTick(Decontextify.value(() => { 534 | // FIXME ...args has side effects 535 | callback(...args); 536 | })); 537 | } catch (e) { 538 | throw Contextify.value(e); 539 | } 540 | }, 541 | hrtime: function hrtime(time) { 542 | try { 543 | return Contextify.value(host.process.hrtime(Decontextify.value(time))); 544 | } catch (e) { 545 | throw Contextify.value(e); 546 | } 547 | }, 548 | cwd: function cwd() { 549 | try { 550 | return Contextify.value(host.process.cwd()); 551 | } catch (e) { 552 | throw Contextify.value(e); 553 | } 554 | }, 555 | addListener, 556 | on: addListener, 557 | 558 | once: function once(name, handler) { 559 | if (name !== 'beforeExit' && name !== 'exit') { 560 | throw new Error(`Access denied to listen for '${name}' event.`); 561 | } 562 | 563 | try { 564 | host.process.once(name, Decontextify.value(handler)); 565 | } catch (e) { 566 | throw Contextify.value(e); 567 | } 568 | 569 | return this; 570 | }, 571 | 572 | listeners: function listeners(name) { 573 | if (name !== 'beforeExit' && name !== 'exit') { 574 | // Maybe add ({__proto__:null})[name] to throw when name fails in https://tc39.es/ecma262/#sec-topropertykey. 575 | return []; 576 | } 577 | 578 | // Filter out listeners, which were not created in this sandbox 579 | try { 580 | return Contextify.value(host.process.listeners(name).filter(listener => Contextify.isVMProxy(listener))); 581 | } catch (e) { 582 | throw Contextify.value(e); 583 | } 584 | }, 585 | 586 | removeListener: function removeListener(name, handler) { 587 | if (name !== 'beforeExit' && name !== 'exit') { 588 | return this; 589 | } 590 | 591 | try { 592 | host.process.removeListener(name, Decontextify.value(handler)); 593 | } catch (e) { 594 | throw Contextify.value(e); 595 | } 596 | 597 | return this; 598 | }, 599 | 600 | umask: function umask() { 601 | if (arguments.length) { 602 | throw new Error('Access denied to set umask.'); 603 | } 604 | 605 | try { 606 | return Contextify.value(host.process.umask()); 607 | } catch (e) { 608 | throw Contextify.value(e); 609 | } 610 | } 611 | }; 612 | 613 | if (vm.options.console === 'inherit') { 614 | global.console = Contextify.readonly(host.console); 615 | } else if (vm.options.console === 'redirect') { 616 | global.console = { 617 | debug(...args) { 618 | try { 619 | // FIXME ...args has side effects 620 | vm.emit('console.debug', ...Decontextify.arguments(args)); 621 | } catch (e) { 622 | throw Contextify.value(e); 623 | } 624 | }, 625 | log(...args) { 626 | try { 627 | // FIXME ...args has side effects 628 | vm.emit('console.log', ...Decontextify.arguments(args)); 629 | } catch (e) { 630 | throw Contextify.value(e); 631 | } 632 | }, 633 | info(...args) { 634 | try { 635 | // FIXME ...args has side effects 636 | vm.emit('console.info', ...Decontextify.arguments(args)); 637 | } catch (e) { 638 | throw Contextify.value(e); 639 | } 640 | }, 641 | warn(...args) { 642 | try { 643 | // FIXME ...args has side effects 644 | vm.emit('console.warn', ...Decontextify.arguments(args)); 645 | } catch (e) { 646 | throw Contextify.value(e); 647 | } 648 | }, 649 | error(...args) { 650 | try { 651 | // FIXME ...args has side effects 652 | vm.emit('console.error', ...Decontextify.arguments(args)); 653 | } catch (e) { 654 | throw Contextify.value(e); 655 | } 656 | }, 657 | dir(...args) { 658 | try { 659 | vm.emit('console.dir', ...Decontextify.arguments(args)); 660 | } catch (e) { 661 | throw Contextify.value(e); 662 | } 663 | }, 664 | time() {}, 665 | timeEnd() {}, 666 | trace(...args) { 667 | try { 668 | // FIXME ...args has side effects 669 | vm.emit('console.trace', ...Decontextify.arguments(args)); 670 | } catch (e) { 671 | throw Contextify.value(e); 672 | } 673 | } 674 | }; 675 | } 676 | 677 | /* 678 | Return contextified require. 679 | */ 680 | 681 | return _prepareRequire; 682 | })(vm, host); 683 | -------------------------------------------------------------------------------- /dist/setup-node-sandbox.js: -------------------------------------------------------------------------------- 1 | /* global host, data, VMError */ 2 | 3 | 'use strict'; 4 | 5 | const LocalError = Error; 6 | const LocalTypeError = TypeError; 7 | const LocalWeakMap = WeakMap; 8 | 9 | const { 10 | apply: localReflectApply, 11 | defineProperty: localReflectDefineProperty 12 | } = Reflect; 13 | 14 | const { 15 | set: localWeakMapSet, 16 | get: localWeakMapGet 17 | } = LocalWeakMap.prototype; 18 | 19 | const { 20 | isArray: localArrayIsArray 21 | } = Array; 22 | 23 | function uncurryThis(func) { 24 | return (thiz, ...args) => localReflectApply(func, thiz, args); 25 | } 26 | 27 | const localArrayPrototypeSlice = uncurryThis(Array.prototype.slice); 28 | const localArrayPrototypeIncludes = uncurryThis(Array.prototype.includes); 29 | const localArrayPrototypePush = uncurryThis(Array.prototype.push); 30 | const localArrayPrototypeIndexOf = uncurryThis(Array.prototype.indexOf); 31 | const localArrayPrototypeSplice = uncurryThis(Array.prototype.splice); 32 | const localStringPrototypeStartsWith = uncurryThis(String.prototype.startsWith); 33 | const localStringPrototypeSlice = uncurryThis(String.prototype.slice); 34 | const localStringPrototypeIndexOf = uncurryThis(String.prototype.indexOf); 35 | 36 | const { 37 | argv: optionArgv, 38 | env: optionEnv, 39 | console: optionConsole, 40 | vm, 41 | resolver, 42 | extensions 43 | } = data; 44 | 45 | function ensureSandboxArray(a) { 46 | return localArrayPrototypeSlice(a); 47 | } 48 | 49 | const globalPaths = ensureSandboxArray(resolver.globalPaths); 50 | 51 | class Module { 52 | 53 | constructor(id, path, parent) { 54 | this.id = id; 55 | this.filename = id; 56 | this.path = path; 57 | this.parent = parent; 58 | this.loaded = false; 59 | this.paths = path ? ensureSandboxArray(resolver.genLookupPaths(path)) : []; 60 | this.children = []; 61 | this.exports = {}; 62 | } 63 | 64 | _updateChildren(child, isNew) { 65 | const children = this.children; 66 | if (children && (isNew || !localArrayPrototypeIncludes(children, child))) { 67 | localArrayPrototypePush(children, child); 68 | } 69 | } 70 | 71 | require(id) { 72 | return requireImpl(this, id, false); 73 | } 74 | 75 | } 76 | 77 | const originalRequire = Module.prototype.require; 78 | const cacheBuiltins = {__proto__: null}; 79 | 80 | function requireImpl(mod, id, direct) { 81 | if (direct && mod.require !== originalRequire) { 82 | return mod.require(id); 83 | } 84 | const filename = resolver.resolve(mod, id, undefined, Module._extensions, direct); 85 | if (localStringPrototypeStartsWith(filename, 'node:')) { 86 | id = localStringPrototypeSlice(filename, 5); 87 | let nmod = cacheBuiltins[id]; 88 | if (!nmod) { 89 | nmod = resolver.loadBuiltinModule(vm, id); 90 | if (!nmod) throw new VMError(`Cannot find module '${filename}'`, 'ENOTFOUND'); 91 | cacheBuiltins[id] = nmod; 92 | } 93 | return nmod; 94 | } 95 | 96 | const cachedModule = Module._cache[filename]; 97 | if (cachedModule !== undefined) { 98 | mod._updateChildren(cachedModule, false); 99 | return cachedModule.exports; 100 | } 101 | 102 | let nmod = cacheBuiltins[id]; 103 | if (nmod) return nmod; 104 | nmod = resolver.loadBuiltinModule(vm, id); 105 | if (nmod) { 106 | cacheBuiltins[id] = nmod; 107 | return nmod; 108 | } 109 | 110 | const path = resolver.pathDirname(filename); 111 | const module = new Module(filename, path, mod); 112 | resolver.registerModule(module, filename, path, mod, direct); 113 | mod._updateChildren(module, true); 114 | try { 115 | Module._cache[filename] = module; 116 | const handler = findBestExtensionHandler(filename); 117 | handler(module, filename); 118 | module.loaded = true; 119 | } catch (e) { 120 | delete Module._cache[filename]; 121 | const children = mod.children; 122 | if (localArrayIsArray(children)) { 123 | const index = localArrayPrototypeIndexOf(children, module); 124 | if (index !== -1) { 125 | localArrayPrototypeSplice(children, index, 1); 126 | } 127 | } 128 | throw e; 129 | } 130 | 131 | return module.exports; 132 | } 133 | 134 | Module.builtinModules = ensureSandboxArray(resolver.getBuiltinModulesList()); 135 | Module.globalPaths = globalPaths; 136 | Module._extensions = {__proto__: null}; 137 | Module._cache = {__proto__: null}; 138 | 139 | { 140 | const keys = Object.getOwnPropertyNames(extensions); 141 | for (let i = 0; i < keys.length; i++) { 142 | const key = keys[i]; 143 | const handler = extensions[key]; 144 | Module._extensions[key] = (mod, filename) => handler(mod, filename); 145 | } 146 | } 147 | 148 | function findBestExtensionHandler(filename) { 149 | const name = resolver.pathBasename(filename); 150 | for (let i = 0; (i = localStringPrototypeIndexOf(name, '.', i + 1)) !== -1;) { 151 | const ext = localStringPrototypeSlice(name, i); 152 | const handler = Module._extensions[ext]; 153 | if (handler) return handler; 154 | } 155 | const js = Module._extensions['.js']; 156 | if (js) return js; 157 | const keys = Object.getOwnPropertyNames(Module._extensions); 158 | if (keys.length === 0) throw new VMError(`Failed to load '${filename}': Unknown type.`, 'ELOADFAIL'); 159 | return Module._extensions[keys[0]]; 160 | } 161 | 162 | function createRequireForModule(mod) { 163 | // eslint-disable-next-line no-shadow 164 | function require(id) { 165 | return requireImpl(mod, id, true); 166 | } 167 | function resolve(id, options) { 168 | return resolver.resolve(mod, id, options, Module._extensions, true); 169 | } 170 | require.resolve = resolve; 171 | function paths(id) { 172 | return ensureSandboxArray(resolver.lookupPaths(mod, id)); 173 | } 174 | resolve.paths = paths; 175 | 176 | require.extensions = Module._extensions; 177 | 178 | require.cache = Module._cache; 179 | 180 | return require; 181 | } 182 | 183 | /** 184 | * Prepare sandbox. 185 | */ 186 | 187 | const TIMERS = new LocalWeakMap(); 188 | 189 | class Timeout { 190 | } 191 | 192 | class Interval { 193 | } 194 | 195 | class Immediate { 196 | } 197 | 198 | function clearTimer(timer) { 199 | const obj = localReflectApply(localWeakMapGet, TIMERS, [timer]); 200 | if (obj) { 201 | obj.clear(obj.value); 202 | } 203 | } 204 | 205 | // This is a function and not an arrow function, since the original is also a function 206 | // eslint-disable-next-line no-shadow 207 | global.setTimeout = function setTimeout(callback, delay, ...args) { 208 | if (typeof callback !== 'function') throw new LocalTypeError('"callback" argument must be a function'); 209 | const obj = new Timeout(callback, args); 210 | const cb = () => { 211 | localReflectApply(callback, null, args); 212 | }; 213 | const tmr = host.setTimeout(cb, delay); 214 | 215 | const ref = { 216 | __proto__: null, 217 | clear: host.clearTimeout, 218 | value: tmr 219 | }; 220 | 221 | localReflectApply(localWeakMapSet, TIMERS, [obj, ref]); 222 | return obj; 223 | }; 224 | 225 | // eslint-disable-next-line no-shadow 226 | global.setInterval = function setInterval(callback, interval, ...args) { 227 | if (typeof callback !== 'function') throw new LocalTypeError('"callback" argument must be a function'); 228 | const obj = new Interval(); 229 | const cb = () => { 230 | localReflectApply(callback, null, args); 231 | }; 232 | const tmr = host.setInterval(cb, interval); 233 | 234 | const ref = { 235 | __proto__: null, 236 | clear: host.clearInterval, 237 | value: tmr 238 | }; 239 | 240 | localReflectApply(localWeakMapSet, TIMERS, [obj, ref]); 241 | return obj; 242 | }; 243 | 244 | // eslint-disable-next-line no-shadow 245 | global.setImmediate = function setImmediate(callback, ...args) { 246 | if (typeof callback !== 'function') throw new LocalTypeError('"callback" argument must be a function'); 247 | const obj = new Immediate(); 248 | const cb = () => { 249 | localReflectApply(callback, null, args); 250 | }; 251 | const tmr = host.setImmediate(cb); 252 | 253 | const ref = { 254 | __proto__: null, 255 | clear: host.clearImmediate, 256 | value: tmr 257 | }; 258 | 259 | localReflectApply(localWeakMapSet, TIMERS, [obj, ref]); 260 | return obj; 261 | }; 262 | 263 | // eslint-disable-next-line no-shadow 264 | global.clearTimeout = function clearTimeout(timeout) { 265 | clearTimer(timeout); 266 | }; 267 | 268 | // eslint-disable-next-line no-shadow 269 | global.clearInterval = function clearInterval(interval) { 270 | clearTimer(interval); 271 | }; 272 | 273 | // eslint-disable-next-line no-shadow 274 | global.clearImmediate = function clearImmediate(immediate) { 275 | clearTimer(immediate); 276 | }; 277 | 278 | const localProcess = host.process; 279 | 280 | function vmEmitArgs(event, args) { 281 | const allargs = [event]; 282 | for (let i = 0; i < args.length; i++) { 283 | if (!localReflectDefineProperty(allargs, i + 1, { 284 | __proto__: null, 285 | value: args[i], 286 | writable: true, 287 | enumerable: true, 288 | configurable: true 289 | })) throw new LocalError('Unexpected'); 290 | } 291 | return localReflectApply(vm.emit, vm, allargs); 292 | } 293 | 294 | const LISTENERS = new LocalWeakMap(); 295 | const LISTENER_HANDLER = new LocalWeakMap(); 296 | 297 | /** 298 | * 299 | * @param {*} name 300 | * @param {*} handler 301 | * @this process 302 | * @return {this} 303 | */ 304 | function addListener(name, handler) { 305 | if (name !== 'beforeExit' && name !== 'exit') { 306 | throw new LocalError(`Access denied to listen for '${name}' event.`); 307 | } 308 | 309 | let cb = localReflectApply(localWeakMapGet, LISTENERS, [handler]); 310 | if (!cb) { 311 | cb = () => { 312 | handler(); 313 | }; 314 | localReflectApply(localWeakMapSet, LISTENER_HANDLER, [cb, handler]); 315 | localReflectApply(localWeakMapSet, LISTENERS, [handler, cb]); 316 | } 317 | 318 | localProcess.on(name, cb); 319 | 320 | return this; 321 | } 322 | 323 | /** 324 | * 325 | * @this process 326 | * @return {this} 327 | */ 328 | // eslint-disable-next-line no-shadow 329 | function process() { 330 | return this; 331 | } 332 | 333 | const baseUptime = localProcess.uptime(); 334 | 335 | // FIXME wrong class structure 336 | global.process = { 337 | __proto__: process.prototype, 338 | argv: optionArgv !== undefined ? optionArgv : [], 339 | title: localProcess.title, 340 | version: localProcess.version, 341 | versions: localProcess.versions, 342 | arch: localProcess.arch, 343 | platform: localProcess.platform, 344 | env: optionEnv !== undefined ? optionEnv : {}, 345 | pid: localProcess.pid, 346 | features: localProcess.features, 347 | nextTick: function nextTick(callback, ...args) { 348 | if (typeof callback !== 'function') { 349 | throw new LocalError('Callback must be a function.'); 350 | } 351 | 352 | localProcess.nextTick(()=>{ 353 | localReflectApply(callback, null, args); 354 | }); 355 | }, 356 | hrtime: function hrtime(time) { 357 | return localProcess.hrtime(time); 358 | }, 359 | uptime: function uptime() { 360 | return localProcess.uptime() - baseUptime; 361 | }, 362 | cwd: function cwd() { 363 | return localProcess.cwd(); 364 | }, 365 | addListener, 366 | on: addListener, 367 | 368 | once: function once(name, handler) { 369 | if (name !== 'beforeExit' && name !== 'exit') { 370 | throw new LocalError(`Access denied to listen for '${name}' event.`); 371 | } 372 | 373 | let triggered = false; 374 | const cb = () => { 375 | if (triggered) return; 376 | triggered = true; 377 | localProcess.removeListener(name, cb); 378 | handler(); 379 | }; 380 | localReflectApply(localWeakMapSet, LISTENER_HANDLER, [cb, handler]); 381 | 382 | localProcess.on(name, cb); 383 | 384 | return this; 385 | }, 386 | 387 | listeners: function listeners(name) { 388 | if (name !== 'beforeExit' && name !== 'exit') { 389 | // Maybe add ({__proto__:null})[name] to throw when name fails in https://tc39.es/ecma262/#sec-topropertykey. 390 | return []; 391 | } 392 | 393 | // Filter out listeners, which were not created in this sandbox 394 | const all = localProcess.listeners(name); 395 | const filtered = []; 396 | let j = 0; 397 | for (let i = 0; i < all.length; i++) { 398 | const h = localReflectApply(localWeakMapGet, LISTENER_HANDLER, [all[i]]); 399 | if (h) { 400 | if (!localReflectDefineProperty(filtered, j, { 401 | __proto__: null, 402 | value: h, 403 | writable: true, 404 | enumerable: true, 405 | configurable: true 406 | })) throw new LocalError('Unexpected'); 407 | j++; 408 | } 409 | } 410 | return filtered; 411 | }, 412 | 413 | removeListener: function removeListener(name, handler) { 414 | if (name !== 'beforeExit' && name !== 'exit') { 415 | return this; 416 | } 417 | 418 | const cb = localReflectApply(localWeakMapGet, LISTENERS, [handler]); 419 | if (cb) localProcess.removeListener(name, cb); 420 | 421 | return this; 422 | }, 423 | 424 | umask: function umask() { 425 | if (arguments.length) { 426 | throw new LocalError('Access denied to set umask.'); 427 | } 428 | 429 | return localProcess.umask(); 430 | } 431 | }; 432 | 433 | if (optionConsole === 'inherit') { 434 | global.console = host.console; 435 | } else if (optionConsole === 'redirect') { 436 | global.console = { 437 | debug(...args) { 438 | vmEmitArgs('console.debug', args); 439 | }, 440 | log(...args) { 441 | vmEmitArgs('console.log', args); 442 | }, 443 | info(...args) { 444 | vmEmitArgs('console.info', args); 445 | }, 446 | warn(...args) { 447 | vmEmitArgs('console.warn', args); 448 | }, 449 | error(...args) { 450 | vmEmitArgs('console.error', args); 451 | }, 452 | dir(...args) { 453 | vmEmitArgs('console.dir', args); 454 | }, 455 | time() {}, 456 | timeEnd() {}, 457 | trace(...args) { 458 | vmEmitArgs('console.trace', args); 459 | } 460 | }; 461 | } 462 | 463 | return { 464 | __proto__: null, 465 | Module, 466 | jsonParse: JSON.parse, 467 | createRequireForModule, 468 | requireImpl 469 | }; 470 | -------------------------------------------------------------------------------- /dist/setup-sandbox.js: -------------------------------------------------------------------------------- 1 | /* global host, bridge, data, context */ 2 | 3 | 'use strict'; 4 | 5 | const { 6 | Object: localObject, 7 | Array: localArray, 8 | Error: LocalError, 9 | Reflect: localReflect, 10 | Proxy: LocalProxy, 11 | WeakMap: LocalWeakMap, 12 | Function: localFunction, 13 | Promise: localPromise, 14 | eval: localEval 15 | } = global; 16 | 17 | const { 18 | freeze: localObjectFreeze 19 | } = localObject; 20 | 21 | const { 22 | getPrototypeOf: localReflectGetPrototypeOf, 23 | apply: localReflectApply, 24 | deleteProperty: localReflectDeleteProperty, 25 | has: localReflectHas, 26 | defineProperty: localReflectDefineProperty, 27 | setPrototypeOf: localReflectSetPrototypeOf, 28 | getOwnPropertyDescriptor: localReflectGetOwnPropertyDescriptor 29 | } = localReflect; 30 | 31 | const { 32 | isArray: localArrayIsArray 33 | } = localArray; 34 | 35 | const { 36 | ensureThis, 37 | ReadOnlyHandler, 38 | from, 39 | fromWithFactory, 40 | readonlyFactory, 41 | connect, 42 | addProtoMapping, 43 | VMError, 44 | ReadOnlyMockHandler 45 | } = bridge; 46 | 47 | const { 48 | allowAsync, 49 | GeneratorFunction, 50 | AsyncFunction, 51 | AsyncGeneratorFunction 52 | } = data; 53 | 54 | const { 55 | get: localWeakMapGet, 56 | set: localWeakMapSet 57 | } = LocalWeakMap.prototype; 58 | 59 | function localUnexpected() { 60 | return new VMError('Should not happen'); 61 | } 62 | 63 | // global is originally prototype of host.Object so it can be used to climb up from the sandbox. 64 | if (!localReflectSetPrototypeOf(context, localObject.prototype)) throw localUnexpected(); 65 | 66 | Object.defineProperties(global, { 67 | global: {value: global, writable: true, configurable: true, enumerable: true}, 68 | globalThis: {value: global, writable: true, configurable: true}, 69 | GLOBAL: {value: global, writable: true, configurable: true}, 70 | root: {value: global, writable: true, configurable: true}, 71 | Error: {value: LocalError} 72 | }); 73 | 74 | if (!localReflectDefineProperty(global, 'VMError', { 75 | __proto__: null, 76 | value: VMError, 77 | writable: true, 78 | enumerable: false, 79 | configurable: true 80 | })) throw localUnexpected(); 81 | 82 | // Fixes buffer unsafe allocation 83 | /* eslint-disable no-use-before-define */ 84 | class BufferHandler extends ReadOnlyHandler { 85 | 86 | apply(target, thiz, args) { 87 | if (args.length > 0 && typeof args[0] === 'number') { 88 | return LocalBuffer.alloc(args[0]); 89 | } 90 | return localReflectApply(LocalBuffer.from, LocalBuffer, args); 91 | } 92 | 93 | construct(target, args, newTarget) { 94 | if (args.length > 0 && typeof args[0] === 'number') { 95 | return LocalBuffer.alloc(args[0]); 96 | } 97 | return localReflectApply(LocalBuffer.from, LocalBuffer, args); 98 | } 99 | 100 | } 101 | /* eslint-enable no-use-before-define */ 102 | 103 | const LocalBuffer = fromWithFactory(obj => new BufferHandler(obj), host.Buffer); 104 | 105 | 106 | if (!localReflectDefineProperty(global, 'Buffer', { 107 | __proto__: null, 108 | value: LocalBuffer, 109 | writable: true, 110 | enumerable: false, 111 | configurable: true 112 | })) throw localUnexpected(); 113 | 114 | addProtoMapping(LocalBuffer.prototype, host.Buffer.prototype, 'Uint8Array'); 115 | 116 | /** 117 | * 118 | * @param {*} size Size of new buffer 119 | * @this LocalBuffer 120 | * @return {LocalBuffer} 121 | */ 122 | function allocUnsafe(size) { 123 | return LocalBuffer.alloc(size); 124 | } 125 | 126 | connect(allocUnsafe, host.Buffer.allocUnsafe); 127 | 128 | /** 129 | * 130 | * @param {*} size Size of new buffer 131 | * @this LocalBuffer 132 | * @return {LocalBuffer} 133 | */ 134 | function allocUnsafeSlow(size) { 135 | return LocalBuffer.alloc(size); 136 | } 137 | 138 | connect(allocUnsafeSlow, host.Buffer.allocUnsafeSlow); 139 | 140 | /** 141 | * Replacement for Buffer inspect 142 | * 143 | * @param {*} recurseTimes 144 | * @param {*} ctx 145 | * @this LocalBuffer 146 | * @return {string} 147 | */ 148 | function inspect(recurseTimes, ctx) { 149 | // Mimic old behavior, could throw but didn't pass a test. 150 | const max = host.INSPECT_MAX_BYTES; 151 | const actualMax = Math.min(max, this.length); 152 | const remaining = this.length - max; 153 | let str = this.hexSlice(0, actualMax).replace(/(.{2})/g, '$1 ').trim(); 154 | if (remaining > 0) str += ` ... ${remaining} more byte${remaining > 1 ? 's' : ''}`; 155 | return `<${this.constructor.name} ${str}>`; 156 | } 157 | 158 | connect(inspect, host.Buffer.prototype.inspect); 159 | 160 | connect(localFunction.prototype.bind, host.Function.prototype.bind); 161 | 162 | connect(localObject.prototype.__defineGetter__, host.Object.prototype.__defineGetter__); 163 | connect(localObject.prototype.__defineSetter__, host.Object.prototype.__defineSetter__); 164 | connect(localObject.prototype.__lookupGetter__, host.Object.prototype.__lookupGetter__); 165 | connect(localObject.prototype.__lookupSetter__, host.Object.prototype.__lookupSetter__); 166 | 167 | /* 168 | * PrepareStackTrace sanitization 169 | */ 170 | 171 | const oldPrepareStackTraceDesc = localReflectGetOwnPropertyDescriptor(LocalError, 'prepareStackTrace'); 172 | 173 | let currentPrepareStackTrace = LocalError.prepareStackTrace; 174 | const wrappedPrepareStackTrace = new LocalWeakMap(); 175 | if (typeof currentPrepareStackTrace === 'function') { 176 | wrappedPrepareStackTrace.set(currentPrepareStackTrace, currentPrepareStackTrace); 177 | } 178 | 179 | let OriginalCallSite; 180 | LocalError.prepareStackTrace = (e, sst) => { 181 | OriginalCallSite = sst[0].constructor; 182 | }; 183 | new LocalError().stack; 184 | if (typeof OriginalCallSite === 'function') { 185 | LocalError.prepareStackTrace = undefined; 186 | 187 | function makeCallSiteGetters(list) { 188 | const callSiteGetters = []; 189 | for (let i=0; i { 196 | return localReflectApply(func, thiz, []); 197 | } 198 | }; 199 | } 200 | return callSiteGetters; 201 | } 202 | 203 | function applyCallSiteGetters(thiz, callSite, getters) { 204 | for (let i=0; i { 279 | if (localArrayIsArray(sst)) { 280 | for (let i=0; i < sst.length; i++) { 281 | const cs = sst[i]; 282 | if (typeof cs === 'object' && localReflectGetPrototypeOf(cs) === OriginalCallSite.prototype) { 283 | sst[i] = new CallSite(cs); 284 | } 285 | } 286 | } 287 | return value(error, sst); 288 | }; 289 | localReflectApply(localWeakMapSet, wrappedPrepareStackTrace, [value, newWrapped]); 290 | localReflectApply(localWeakMapSet, wrappedPrepareStackTrace, [newWrapped, newWrapped]); 291 | currentPrepareStackTrace = newWrapped; 292 | } 293 | })) throw localUnexpected(); 294 | } else if (oldPrepareStackTraceDesc) { 295 | localReflectDefineProperty(LocalError, 'prepareStackTrace', oldPrepareStackTraceDesc); 296 | } else { 297 | localReflectDeleteProperty(LocalError, 'prepareStackTrace'); 298 | } 299 | 300 | /* 301 | * Exception sanitization 302 | */ 303 | 304 | const withProxy = localObjectFreeze({ 305 | __proto__: null, 306 | has(target, key) { 307 | if (key === host.INTERNAL_STATE_NAME) return false; 308 | return localReflectHas(target, key); 309 | } 310 | }); 311 | 312 | const interanState = localObjectFreeze({ 313 | __proto__: null, 314 | wrapWith(x) { 315 | if (x === null || x === undefined) return x; 316 | return new LocalProxy(localObject(x), withProxy); 317 | }, 318 | handleException: ensureThis, 319 | import(what) { 320 | throw new VMError('Dynamic Import not supported'); 321 | } 322 | }); 323 | 324 | if (!localReflectDefineProperty(global, host.INTERNAL_STATE_NAME, { 325 | __proto__: null, 326 | configurable: false, 327 | enumerable: false, 328 | writable: false, 329 | value: interanState 330 | })) throw localUnexpected(); 331 | 332 | /* 333 | * Eval sanitization 334 | */ 335 | 336 | function throwAsync() { 337 | return new VMError('Async not available'); 338 | } 339 | 340 | function makeFunction(inputArgs, isAsync, isGenerator) { 341 | const lastArgs = inputArgs.length - 1; 342 | let code = lastArgs >= 0 ? `${inputArgs[lastArgs]}` : ''; 343 | let args = lastArgs > 0 ? `${inputArgs[0]}` : ''; 344 | for (let i = 1; i < lastArgs; i++) { 345 | args += `,${inputArgs[i]}`; 346 | } 347 | try { 348 | code = host.transformAndCheck(args, code, isAsync, isGenerator, allowAsync); 349 | } catch (e) { 350 | throw bridge.from(e); 351 | } 352 | return localEval(code); 353 | } 354 | 355 | const FunctionHandler = { 356 | __proto__: null, 357 | apply(target, thiz, args) { 358 | return makeFunction(args, this.isAsync, this.isGenerator); 359 | }, 360 | construct(target, args, newTarget) { 361 | return makeFunction(args, this.isAsync, this.isGenerator); 362 | } 363 | }; 364 | 365 | const EvalHandler = { 366 | __proto__: null, 367 | apply(target, thiz, args) { 368 | if (args.length === 0) return undefined; 369 | let code = `${args[0]}`; 370 | try { 371 | code = host.transformAndCheck(null, code, false, false, allowAsync); 372 | } catch (e) { 373 | throw bridge.from(e); 374 | } 375 | return localEval(code); 376 | } 377 | }; 378 | 379 | const AsyncErrorHandler = { 380 | __proto__: null, 381 | apply(target, thiz, args) { 382 | throw throwAsync(); 383 | }, 384 | construct(target, args, newTarget) { 385 | throw throwAsync(); 386 | } 387 | }; 388 | 389 | function makeCheckFunction(isAsync, isGenerator) { 390 | if (isAsync && !allowAsync) return AsyncErrorHandler; 391 | return { 392 | __proto__: FunctionHandler, 393 | isAsync, 394 | isGenerator 395 | }; 396 | } 397 | 398 | function overrideWithProxy(obj, prop, value, handler) { 399 | const proxy = new LocalProxy(value, handler); 400 | if (!localReflectDefineProperty(obj, prop, {__proto__: null, value: proxy})) throw localUnexpected(); 401 | return proxy; 402 | } 403 | 404 | const proxiedFunction = overrideWithProxy(localFunction.prototype, 'constructor', localFunction, makeCheckFunction(false, false)); 405 | if (GeneratorFunction) { 406 | if (!localReflectSetPrototypeOf(GeneratorFunction, proxiedFunction)) throw localUnexpected(); 407 | overrideWithProxy(GeneratorFunction.prototype, 'constructor', GeneratorFunction, makeCheckFunction(false, true)); 408 | } 409 | if (AsyncFunction) { 410 | if (!localReflectSetPrototypeOf(AsyncFunction, proxiedFunction)) throw localUnexpected(); 411 | overrideWithProxy(AsyncFunction.prototype, 'constructor', AsyncFunction, makeCheckFunction(true, false)); 412 | } 413 | if (AsyncGeneratorFunction) { 414 | if (!localReflectSetPrototypeOf(AsyncGeneratorFunction, proxiedFunction)) throw localUnexpected(); 415 | overrideWithProxy(AsyncGeneratorFunction.prototype, 'constructor', AsyncGeneratorFunction, makeCheckFunction(true, true)); 416 | } 417 | 418 | global.Function = proxiedFunction; 419 | global.eval = new LocalProxy(localEval, EvalHandler); 420 | 421 | /* 422 | * Promise sanitization 423 | */ 424 | 425 | if (localPromise && !allowAsync) { 426 | 427 | const PromisePrototype = localPromise.prototype; 428 | 429 | overrideWithProxy(PromisePrototype, 'then', PromisePrototype.then, AsyncErrorHandler); 430 | // This seems not to work, and will produce 431 | // UnhandledPromiseRejectionWarning: TypeError: Method Promise.prototype.then called on incompatible receiver [object Object]. 432 | // This is likely caused since the host.Promise.prototype.then cannot use the VM Proxy object. 433 | // Contextify.connect(host.Promise.prototype.then, Promise.prototype.then); 434 | 435 | if (PromisePrototype.finally) { 436 | overrideWithProxy(PromisePrototype, 'finally', PromisePrototype.finally, AsyncErrorHandler); 437 | // Contextify.connect(host.Promise.prototype.finally, Promise.prototype.finally); 438 | } 439 | if (Promise.prototype.catch) { 440 | overrideWithProxy(PromisePrototype, 'catch', PromisePrototype.catch, AsyncErrorHandler); 441 | // Contextify.connect(host.Promise.prototype.catch, Promise.prototype.catch); 442 | } 443 | 444 | } 445 | 446 | function readonly(other, mock) { 447 | // Note: other@other(unsafe) mock@other(unsafe) returns@this(unsafe) throws@this(unsafe) 448 | if (!mock) return fromWithFactory(readonlyFactory, other); 449 | const tmock = from(mock); 450 | return fromWithFactory(obj=>new ReadOnlyMockHandler(obj, tmock), other); 451 | } 452 | 453 | return { 454 | __proto__: null, 455 | readonly, 456 | global 457 | }; 458 | -------------------------------------------------------------------------------- /how-to-build.md: -------------------------------------------------------------------------------- 1 | ## Build 2 | 3 | Before pushing and tagging follow these steps: 4 | 5 | 1. Install `vercel/ncc` by running this command in your terminal. `npm i -g @vercel/ncc` 6 | 7 | 2. Compile your `index.js` file. `ncc build index.js --license licenses.txt` 8 | 9 | More more information, go [here](https://docs.github.com/en/actions/creating-actions/creating-a-javascript-action#commit-tag-and-push-your-action-to-github) 10 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const { inspect } = require("util"); 2 | const core = require("@actions/core"); 3 | const { Octokit } = require("@octokit/action"); 4 | 5 | main(); 6 | 7 | async function main() { 8 | try { 9 | const octokit = new Octokit(); 10 | 11 | let parameters = {}; 12 | parameters.per_page = 100; 13 | parameters.page = 1; 14 | 15 | const repository = core.getInput("repository"); 16 | const ownerAndRepo = repository.split("/"); 17 | if(ownerAndRepo.length !== 2){ 18 | throw new Error(`The repository input parameter '${repository}' is not in the format {owner}/{repo}.`); 19 | } 20 | 21 | parameters.owner = ownerAndRepo[0]; 22 | parameters.repo = ownerAndRepo[1]; 23 | 24 | if(!parameters.owner){ 25 | throw new Error(`Owner cannot be empty. Make sure the repository input parameter '${repository}' is in the format {owner}/{repo}.`); 26 | } 27 | 28 | if(!parameters.repo){ 29 | throw new Error(`Repository cannot be empty. Make sure the repository input parameter '${repository}' is in the format {owner}/{repo}.`); 30 | } 31 | 32 | const workflow = core.getInput("workflow"); 33 | const olderThanSeconds = core.getInput("older-than-seconds"); 34 | const createdBefore = core.getInput("created-before"); 35 | const actor = core.getInput("actor"); 36 | const branch = core.getInput("branch"); 37 | const event = core.getInput("event"); 38 | const status = core.getInput("status"); 39 | const whatIf = core.getInput("what-if"); 40 | 41 | core.info(`Applying filters:`); 42 | 43 | if(!!workflow){ 44 | core.info(`workflow: ${workflow}`); 45 | parameters.workflow_id = workflow; 46 | } 47 | 48 | if(!!olderThanSeconds){ 49 | core.info(`older-than-seconds: ${olderThanSeconds}`); 50 | parameters.created = `<=${new Date(Date.now() - olderThanSeconds * 1000).toISOString()}`; 51 | } 52 | else if(!!createdBefore){ 53 | let createdBeforeDate = new Date(createdBefore); 54 | core.info(`created-before: ${createdBeforeDate}`); 55 | parameters.created = `<=${createdBeforeDate.toISOString()}`; 56 | } 57 | 58 | if(!!actor){ 59 | parameters.actor = actor; 60 | core.info(`actor: ${actor}`); 61 | } 62 | 63 | if(!!branch){ 64 | parameters.branch = branch; 65 | core.info(`branch: ${branch}`); 66 | } 67 | 68 | if(!!event){ 69 | parameters.event = event; 70 | core.info(`event: ${event}`); 71 | } 72 | 73 | if(!!status){ 74 | parameters.status = status; 75 | core.info(`status: ${status}`); 76 | } 77 | 78 | if(whatIf !== "false"){ 79 | if(whatIf !== "true"){ 80 | core.warning(`Invalid value "${whatIf}" for what-if. Should be either "true" or "false". Defaulting to "true".`); 81 | } 82 | 83 | core.info(`Running in what-if mode. The following workflow runs would be deleted if what-if was set to "false":`); 84 | } 85 | 86 | for(;;) { 87 | let response; 88 | 89 | if(!!workflow){ 90 | response = await octokit.actions.listWorkflowRuns(parameters); 91 | } 92 | else{ 93 | response = await octokit.actions.listWorkflowRunsForRepo(parameters); 94 | } 95 | 96 | if(response.data.workflow_runs <= 0){ 97 | break; 98 | } 99 | 100 | let deletedFromCurrentPage = false; 101 | for (const workflowRun of response.data.workflow_runs) { 102 | const title = workflowRun.head_commit.message.split("\n")[0] 103 | const workflowRunLog = `${workflowRun.id} created at ${workflowRun.created_at}. Title: "${title}", Author: ${workflowRun.head_commit.author.name} - ${workflowRun.head_commit.author.email}, Branch: ${workflowRun.head_branch}, Workflow: ${workflowRun.name}`; 104 | 105 | if(process.env.GITHUB_RUN_ID == workflowRun.id){ 106 | core.info(`Skipping current workflow run ${workflowRun.id}.`); 107 | 108 | continue; 109 | } 110 | 111 | if(whatIf !== "false"){ 112 | core.info(`Workflow run ${workflowRunLog}`); 113 | 114 | continue; 115 | } 116 | else{ 117 | core.info(`Deleting workflow run ${workflowRunLog}...`); 118 | } 119 | 120 | let deleteParameters = { 121 | owner: parameters.owner, 122 | repo: parameters.repo, 123 | run_id: workflowRun.id 124 | }; 125 | 126 | try { 127 | let { status } = await octokit.actions.deleteWorkflowRun(deleteParameters); 128 | 129 | if(status == 204){ 130 | core.info(`Deleted workflow run ${workflowRun.id}.`); 131 | deletedFromCurrentPage = true; 132 | } else{ 133 | core.warning(`Something went wrong while deleting workflow "${title}" with ID:${workflowRun.id}. Status code: ${status}`); 134 | } 135 | } catch (error) { 136 | core.info(inspect(error)); 137 | } 138 | } 139 | 140 | if(deletedFromCurrentPage && response.data.total_count < parameters.per_page){ 141 | break; 142 | } 143 | 144 | if(whatIf !== "false" || !deletedFromCurrentPage){ 145 | parameters.page += 1; 146 | } 147 | } 148 | } catch (error) { 149 | core.info(inspect(error)); 150 | core.setFailed(error.message); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "delete-old-workflow-runs", 3 | "version": "1.0.0", 4 | "description": "A GitHub Action used to delete workflow runs from a repository.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/MajorScruffy/delete-workflow-runs.git" 12 | }, 13 | "keywords": [], 14 | "author": "", 15 | "license": "ISC", 16 | "bugs": { 17 | "url": "https://github.com/MajorScruffy/delete-workflow-runs/issues" 18 | }, 19 | "homepage": "https://github.com/MajorScruffy/delete-workflow-runs#readme", 20 | "dependencies": { 21 | "@actions/core": "^1.6.0", 22 | "@octokit/action": "^3.18.0" 23 | } 24 | } 25 | --------------------------------------------------------------------------------