diff options
author | Nayeem Rahman <nayeemrmn99@gmail.com> | 2021-10-13 18:04:44 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-10-13 13:04:44 -0400 |
commit | 7a22df9b7641274b2a83ce53845215d17cfda2c8 (patch) | |
tree | 6cf99f74682635a0128c5cf79f5f7d3ecfbc85f1 /runtime/js/10_permissions.js | |
parent | 43a63530acb16e57cbb190eacedbd097c536a775 (diff) |
fix(runtime/ops/worker_host): move permission arg parsing to Rust (#12297)
Diffstat (limited to 'runtime/js/10_permissions.js')
-rw-r--r-- | runtime/js/10_permissions.js | 253 |
1 files changed, 253 insertions, 0 deletions
diff --git a/runtime/js/10_permissions.js b/runtime/js/10_permissions.js new file mode 100644 index 000000000..a6884aab9 --- /dev/null +++ b/runtime/js/10_permissions.js @@ -0,0 +1,253 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. +"use strict"; + +((window) => { + const { + Event, + EventTarget, + Deno: { core }, + __bootstrap: { webUtil: { illegalConstructorKey } }, + } = window; + const { pathFromURL } = window.__bootstrap.util; + const { + ArrayIsArray, + ArrayPrototypeIncludes, + ArrayPrototypeMap, + ArrayPrototypeSlice, + Map, + MapPrototypeGet, + MapPrototypeHas, + MapPrototypeSet, + FunctionPrototypeCall, + PromiseResolve, + PromiseReject, + SymbolFor, + TypeError, + } = window.__bootstrap.primordials; + + /** + * @typedef StatusCacheValue + * @property {PermissionState} state + * @property {PermissionStatus} status + */ + + /** @type {ReadonlyArray<"read" | "write" | "net" | "env" | "run" | "ffi" | "hrtime">} */ + const permissionNames = [ + "read", + "write", + "net", + "env", + "run", + "ffi", + "hrtime", + ]; + + /** + * @param {Deno.PermissionDescriptor} desc + * @returns {Deno.PermissionState} + */ + function opQuery(desc) { + return core.opSync("op_query_permission", desc); + } + + /** + * @param {Deno.PermissionDescriptor} desc + * @returns {Deno.PermissionState} + */ + function opRevoke(desc) { + return core.opSync("op_revoke_permission", desc); + } + + /** + * @param {Deno.PermissionDescriptor} desc + * @returns {Deno.PermissionState} + */ + function opRequest(desc) { + return core.opSync("op_request_permission", desc); + } + + class PermissionStatus extends EventTarget { + /** @type {{ state: Deno.PermissionState }} */ + #state; + + /** @type {((this: PermissionStatus, event: Event) => any) | null} */ + onchange = null; + + /** @returns {Deno.PermissionState} */ + get state() { + return this.#state.state; + } + + /** + * @param {{ state: Deno.PermissionState }} state + * @param {unknown} key + */ + constructor(state = null, key = null) { + if (key != illegalConstructorKey) { + throw new TypeError("Illegal constructor."); + } + super(); + this.#state = state; + } + + /** + * @param {Event} event + * @returns {boolean} + */ + dispatchEvent(event) { + let dispatched = super.dispatchEvent(event); + if (dispatched && this.onchange) { + FunctionPrototypeCall(this.onchange, this, event); + dispatched = !event.defaultPrevented; + } + return dispatched; + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ state: this.state, onchange: this.onchange }) + }`; + } + } + + /** @type {Map<string, StatusCacheValue>} */ + const statusCache = new Map(); + + /** + * @param {Deno.PermissionDescriptor} desc + * @param {Deno.PermissionState} state + * @returns {PermissionStatus} + */ + function cache(desc, state) { + let { name: key } = desc; + if ((desc.name === "read" || desc.name === "write") && "path" in desc) { + key += `-${desc.path}`; + } else if (desc.name === "net" && desc.host) { + key += `-${desc.host}`; + } + if (MapPrototypeHas(statusCache, key)) { + const status = MapPrototypeGet(statusCache, key); + if (status.state !== state) { + status.state = state; + status.status.dispatchEvent(new Event("change", { cancelable: false })); + } + return status.status; + } + /** @type {{ state: Deno.PermissionState; status?: PermissionStatus }} */ + const status = { state }; + status.status = new PermissionStatus(status, illegalConstructorKey); + MapPrototypeSet(statusCache, key, status); + return status.status; + } + + /** + * @param {unknown} desc + * @returns {desc is Deno.PermissionDescriptor} + */ + function isValidDescriptor(desc) { + return desc && desc !== null && + ArrayPrototypeIncludes(permissionNames, desc.name); + } + + class Permissions { + constructor(key = null) { + if (key != illegalConstructorKey) { + throw new TypeError("Illegal constructor."); + } + } + + query(desc) { + if (!isValidDescriptor(desc)) { + return PromiseReject( + new TypeError( + `The provided value "${desc.name}" is not a valid permission name.`, + ), + ); + } + + if ( + desc.name === "read" || desc.name === "write" || desc.name === "ffi" + ) { + desc.path = pathFromURL(desc.path); + } else if (desc.name === "run") { + desc.command = pathFromURL(desc.command); + } + + const state = opQuery(desc); + return PromiseResolve(cache(desc, state)); + } + + revoke(desc) { + if (!isValidDescriptor(desc)) { + return PromiseReject( + new TypeError( + `The provided value "${desc.name}" is not a valid permission name.`, + ), + ); + } + + if (desc.name === "read" || desc.name === "write") { + desc.path = pathFromURL(desc.path); + } else if (desc.name === "run") { + desc.command = pathFromURL(desc.command); + } + + const state = opRevoke(desc); + return PromiseResolve(cache(desc, state)); + } + + request(desc) { + if (!isValidDescriptor(desc)) { + return PromiseReject( + new TypeError( + `The provided value "${desc.name}" is not a valid permission name.`, + ), + ); + } + + if (desc.name === "read" || desc.name === "write") { + desc.path = pathFromURL(desc.path); + } else if (desc.name === "run") { + desc.command = pathFromURL(desc.command); + } + + const state = opRequest(desc); + return PromiseResolve(cache(desc, state)); + } + } + + const permissions = new Permissions(illegalConstructorKey); + + /** Converts all file URLs in FS allowlists to paths. */ + function serializePermissions(permissions) { + if (typeof permissions == "object" && permissions != null) { + const serializedPermissions = {}; + for (const key of ["read", "write", "run", "ffi"]) { + if (ArrayIsArray(permissions[key])) { + serializedPermissions[key] = ArrayPrototypeMap( + permissions[key], + (path) => pathFromURL(path), + ); + } else { + serializedPermissions[key] = permissions[key]; + } + } + for (const key of ["env", "hrtime", "net"]) { + if (ArrayIsArray(permissions[key])) { + serializedPermissions[key] = ArrayPrototypeSlice(permissions[key]); + } else { + serializedPermissions[key] = permissions[key]; + } + } + return serializedPermissions; + } + return permissions; + } + + window.__bootstrap.permissions = { + serializePermissions, + permissions, + Permissions, + PermissionStatus, + }; +})(this); |