summaryrefslogtreecommitdiff
path: root/data/extensions/jsr@javascriptrestrictor/code_builders.js
diff options
context:
space:
mode:
authorRuben Rodriguez <ruben@trisquel.info>2022-09-08 20:18:54 -0400
committerRuben Rodriguez <ruben@trisquel.info>2022-09-08 20:18:54 -0400
commit5da28b0f8771834ae208d61431d632875e9f8e7d (patch)
tree688ecaff26197bad8abde617b4947b11d617309e /data/extensions/jsr@javascriptrestrictor/code_builders.js
parent4a87716686104266a9cccc2d83cc249e312f3673 (diff)
Updated extensions:
* Upgraded Privacy Redirect to 1.1.49 and configured to use the 10 most reliable invidious instances * Removed ViewTube * Added torproxy@icecat.gnu based on 'Proxy toggle' extension * Added jShelter 0.11.1 * Upgraded LibreJS to 7.21.0 * Upgraded HTTPS Everywhere to 2021.7.13 * Upgraded SubmitMe to 1.9
Diffstat (limited to 'data/extensions/jsr@javascriptrestrictor/code_builders.js')
-rw-r--r--data/extensions/jsr@javascriptrestrictor/code_builders.js664
1 files changed, 664 insertions, 0 deletions
diff --git a/data/extensions/jsr@javascriptrestrictor/code_builders.js b/data/extensions/jsr@javascriptrestrictor/code_builders.js
new file mode 100644
index 0000000..8173c43
--- /dev/null
+++ b/data/extensions/jsr@javascriptrestrictor/code_builders.js
@@ -0,0 +1,664 @@
+/** \file
+ * \brief Functions that build code that modifies JS evironment provided to page scripts
+ *
+ * \author Copyright (C) 2019 Libor Polcak
+ * \author Copyright (C) 2021 Giorgio Maone
+ * \author Copyright (C) 2022 Marek Salon
+ *
+ * \license SPDX-License-Identifier: GPL-3.0-or-later
+ */
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+/**
+ * Create IIFE to wrap the code in closure
+ */
+function enclose_wrapping(code, ...args) {
+ return `try{(function(...args) {${code}})(${args});} catch (e) {console.error(e)}`;
+}
+
+/**
+ * Create wrapping that might be IIFE or a function that is immediately called and also available
+ * for future.
+ */
+function enclose_wrapping2(code, name, params, call_with_window) {
+ if (name === undefined) {
+ return enclose_wrapping(code);
+ }
+ return `function ${name}(${params}) {${code}}
+ ${name}(${call_with_window ? "window" : ""});`
+}
+
+/**
+ * Create code containing call of API counting function.
+ */
+function create_counter_call(wrapper, type) {
+ let {parent_object, parent_object_property} = wrapper;
+ let updateCount = `${parent_object}.${parent_object_property}`;
+
+ if ("update_count" in wrapper) {
+ if (typeof wrapper.update_count === "string") updateCount = wrapper.update_count;
+ }
+
+ return updateCount ? `if (fp_enabled && fp_${type}_count < 1000) {
+ updateCount(${JSON.stringify(updateCount)}, "${type}", args.map(x => JSON.stringify(x)));
+ fp_${type}_count += 1;
+ }` : "";
+}
+
+/**
+ * This function create code (as string) that creates code that can be used to inject (or overwrite)
+ * a function in the page context.
+ */
+function define_page_context_function(wrapper) {
+ let {parent_object, parent_object_property, original_function, replace_original_function} = wrapper;
+ if (replace_original_function) {
+ let lastDot = original_function.lastIndexOf(".");
+ parent_object = original_function.substring(0, lastDot);
+ parent_object_property = original_function.substring(lastDot + 1);
+ }
+ let originalF = original_function || `${parent_object}.${parent_object_property}`;
+ let code = `
+ let originalF = ${originalF};
+ var fp_call_count = 0;
+ let replacementF = function(${wrapper.wrapping_function_args}) {
+ try {
+ let args = Array.prototype.slice.apply(arguments);
+ ${create_counter_call(wrapper, "call")}
+ }
+ catch (e) { /* No action: let the wrapper continue uninterupted. TODO: let the user decide? */ }`
+
+ // if apply_if condition is present, we need to wrap for FPD anyhow
+ if (wrapper.apply_if !== undefined) {
+ code += `
+ if (${wrapper.apply_if}) {
+ ${wrapper.wrapping_function_body}
+ }
+ else {
+ return originalF.call(this, ${wrapper.wrapping_function_args});
+ }`
+ }
+ else {
+ code += `${wrapper.wrapping_function_body}`
+ }
+
+ code += `
+ };
+ if (WrapHelper.XRAY) {
+ let innerF = replacementF;
+ replacementF = function(...args) {
+
+ // prepare callbacks
+ args = args.map(a => typeof a === "function" ? WrapHelper.pageAPI(a) : a);
+
+ let ret = WrapHelper.forPage(innerF.call(this, ...args));
+ if (ret) {
+ if (ret instanceof xrayWindow.Promise || ret instanceof WrapHelper.unX(xrayWindow).Promise) {
+ ret = Promise.resolve(ret);
+ }
+ try {
+ ret = WrapHelper.unX(ret);
+ } catch (e) {}
+ }
+ return ret;
+ }
+ }
+ exportFunction(replacementF, ${parent_object}, {defineAs: '${parent_object_property}'});
+ ${wrapper.post_replacement_code || ''}`
+
+ return enclose_wrapping2(code, wrapper.wrapping_code_function_name, wrapper.wrapping_code_function_params, wrapper.wrapping_code_function_call_window);
+}
+
+/**
+ * This function creates code that assigns an already defined function to given property.
+ */
+function generate_assign_function_code(code_spec_obj) {
+ return `exportFunction(${code_spec_obj.export_function_name},
+ ${code_spec_obj.parent_object},
+ {defineAs: '${code_spec_obj.parent_object_property}'});
+ `;
+}
+
+/**
+ * This function wraps object properties using WrapHelper.defineProperties().
+ */
+function generate_object_properties(code_spec_obj, fpd_only) {
+ var code = `
+ if (!("${code_spec_obj.parent_object_property}" in ${code_spec_obj.parent_object})) {
+ // Do not wrap an object that is not defined, e.g. because it is experimental feature.
+ // This should reduce fingerprintability.
+ return;
+ }
+ `;
+ for (let assign of code_spec_obj.wrapped_objects || []) {
+ code += `var ${assign.wrapped_name} = window.${assign.original_name};`;
+ }
+ code += `
+ {
+ let obj = ${code_spec_obj.parent_object};
+ let prop = "${code_spec_obj.parent_object_property}";
+ let descriptor = Object.getOwnPropertyDescriptor(obj, prop);
+ if (!descriptor) {
+ // let's traverse the prototype chain in search of this property
+ for (let proto = Object.getPrototypeOf(obj); proto; proto = Object.getPrototypeOf(obj)) {
+ if (descriptor = Object.getOwnPropertyDescriptor(proto, prop)) {
+ obj = WrapHelper.unX(obj);
+ break;
+ }
+ }
+ if (!descriptor) descriptor = {
+ // Originally not a descriptor, fallback
+ enumerable: true,
+ configurable: true,
+ };
+ }
+ `
+ for (let wrap_spec of code_spec_obj.wrapped_properties) {
+ // variable name used for distinguishing between different original properties of the same wrapper
+ var original_property = `originalP_${wrap_spec.property_name}`;
+
+ var counting_wrapper = `
+ function(...args) {
+ ${create_counter_call(code_spec_obj, wrap_spec.property_name)}
+
+ // checks type of underlying wrapper/definition and returns it (no changes to semantics)
+ if (typeof (${fpd_only ? original_property : wrap_spec.property_value}) === 'function') {
+ return (${fpd_only ? original_property : wrap_spec.property_value}).bind(this)(...args);
+ }
+ else {
+ return (${fpd_only ? original_property : wrap_spec.property_value});
+ }
+ }
+ `;
+
+ if (fpd_only) {
+ code += `var ${original_property} = descriptor["${wrap_spec.property_name}"];`;
+ }
+
+ code += `
+ originalPDF = descriptor["${wrap_spec.property_name}"];
+ var fp_${wrap_spec.property_name}_count = 0;
+ replacementPD = ${counting_wrapper};
+ descriptor["${wrap_spec.property_name}"] = replacementPD;
+ `;
+ }
+ code += `WrapHelper.defineProperty(${code_spec_obj.parent_object},
+ "${code_spec_obj.parent_object_property}", descriptor);
+ }`;
+ return code;
+}
+
+/**
+ * This function removes a property.
+ */
+function generate_delete_properties(code_spec_obj) {
+ var code = `
+ `;
+ for (prop of code_spec_obj.delete_properties) {
+ code += `
+ if ("${prop}" in ${code_spec_obj.parent_object}) {
+ // Delete only properties that are available.
+ // The if should be safe to be deleted but it can possibly reduce fingerprintability
+ WrapHelper.defineProperty(
+ ${code_spec_obj.parent_object},
+ "${prop}", {get: undefined, set: undefined, configurable: false, enumerable: false}
+ );
+ }
+ `
+ }
+ return code;
+}
+
+/**
+ * This function generates code that makes an assignment.
+ */
+function generate_assignement(code_spec_obj) {
+ return `${code_spec_obj.parent_object}.${code_spec_obj.parent_object_property} = ${code_spec_obj.value};`
+}
+
+/**
+ * This function builds the wrapping code.
+ */
+var build_code = function(wrapper, ...args) {
+ let post_wrapping_functions = {
+ function_define: define_page_context_function,
+ function_export: generate_assign_function_code,
+ object_properties: generate_object_properties,
+ delete_properties: generate_delete_properties,
+ assign: generate_assignement,
+ };
+
+ let target = `${wrapper.parent_object}.${wrapper.parent_object_property}`;
+ let code = "";
+
+ {
+ // Do not wrap an object that is not defined, e.g. because it is experimental feature.
+ // This should reduce fingerprintability.
+ let objPath = [], undefChecks = [];
+ for (leaf of target.split('.')) {
+ undefChecks.push(
+ objPath.length ? `!("${leaf}" in ${objPath.join('.')})` // avoids e.g. Event.prototype.timeStamp from throwing "Illegal invocation"
+ : `typeof ${leaf} === "undefined"`
+ );
+ objPath.push(leaf);
+ }
+
+ code += `
+ try {
+ if (${undefChecks.join(" || ")}) return;
+ } catch (e) {
+ return;
+ }`;
+ }
+
+ for (let {original_name = target, wrapped_name, callable_name} of wrapper.wrapped_objects || []) {
+ if (original_name !== target) {
+ code += `
+ if (typeof ${original_name} === undefined) return;
+ `;
+ }
+ if (wrapped_name) {
+ code += `var ${wrapped_name} = window.${original_name};`;
+ }
+ if (callable_name) {
+ code += `var ${callable_name} = WrapHelper.pageAPI(window.${original_name});`;
+ }
+ }
+ code += `
+ ${wrapper.helping_code || ''}`;
+
+ if (wrapper.wrapping_function_body){
+ code += `${define_page_context_function(wrapper)}`;
+ }
+
+ let build_post_normal = () => {
+ if (wrapper["post_wrapping_code"] !== undefined) {
+ for (code_spec of wrapper["post_wrapping_code"]) {
+ if (code_spec.apply_if !== undefined) {
+ code += `if (${code_spec.apply_if}) {`
+ }
+ code += post_wrapping_functions[code_spec.code_type](code_spec);
+ if (code_spec.apply_if !== undefined) {
+ code += "}";
+ }
+ // if not wrapped because of apply_if condition in post wrapping object, still needs to be wrapped for FPD
+ if (code_spec.apply_if !== undefined && code_spec.code_type == "object_properties") {
+ code += "else {" + generate_object_properties(code_spec, true) + "}";
+ }
+ }
+ }
+ }
+
+ let build_post_fpd = () => {
+ if (wrapper["post_wrapping_code"] !== undefined) {
+ for (code_spec of wrapper["post_wrapping_code"]) {
+ // if not wrapped because of apply_if condition in post wrapping object, still needs to be wrapped for FPD
+ if (code_spec.apply_if !== undefined && code_spec.code_type == "object_properties") {
+ code += generate_object_properties(code_spec, true);
+ }
+ }
+ }
+ }
+
+ // if apply_if is present in main wrapper object and contains post wrapping code -> wrap for FPD only if condition is FALSE
+ if (wrapper.apply_if !== undefined) {
+ code += `if (${wrapper.apply_if}) {`
+ build_post_normal();
+ code += `} else {`
+ build_post_fpd();
+ code += `}`
+ }
+ else {
+ build_post_normal();
+ }
+
+ if (wrapper["wrapper_prototype"] !== undefined) {
+ let source = wrapper.wrapper_prototype;
+ code += `if (${target.prototype} !== ${source.prototype}) { // prevent cyclic __proto__ errors on Proxy
+ Object.setPrototypeOf(${target}, ${source});
+ }`;
+ }
+ code += `
+ if (${wrapper.freeze}) {
+ Object.freeze(${wrapper.parent_object}.${wrapper.parent_object_property});
+ }
+ `;
+
+ return enclose_wrapping(code, ...args);
+};
+
+/**
+ * Transform wrapping arrays into injectable code.
+ */
+function wrap_code(wrappers) {
+ if (wrappers.length === 0) {
+ return; // Nothing to wrap
+ }
+
+ let build = (wrapper) => {
+ try {
+ return build_code(build_wrapping_code[wrapper[0]], wrapper.slice(1));
+ } catch (e) {
+ console.error(e);
+ return "";
+ }
+ };
+
+ let fpd_placeholder = "\n\n// FPD_S\n\n// FPD_E"
+ return generate_code(joinWrappingCode(wrappers.map(x => build(x))) + fpd_placeholder);
+}
+
+/**
+ * Join array of wrapping codes into single string.
+ */
+let joinWrappingCode = code => {
+ return code.join("\n").replace(/\bObject\.(create|definePropert)/g, "WrapHelper.$1");
+}
+
+/**
+ * Append wrapped codes to NSCL helpers and create injectable code.
+ */
+function generate_code(wrapped_code) {
+ let code = (w => {
+
+ // cross-wrapper globals
+ let xrayWindow = window; // the "privileged" xray window wrapper in Firefox
+ {
+ let {port} = env;
+ function updateCount(wrapperName, wrapperType, wrapperArgs) {
+ port.postMessage({
+ wrapperName,
+ wrapperType,
+ wrapperArgs
+ });
+ }
+ }
+ let WrapHelper; // xray boundary helper
+ {
+ const XRAY = (xrayWindow.top !== unwrappedWindow.top && typeof XPCNativeWrapper !== "undefined");
+ let privilegedToPage = new WeakMap();
+ let pageReady = new WeakSet();
+
+ let promise = obj => obj.then(r => forPage(r));
+
+ forPage = obj => {
+ if (typeof obj !== "object" && typeof obj !== "function" || obj === null
+ || pageReady.has(obj)) return obj;
+ if (privilegedToPage.has(obj)) return privilegedToPage.get(obj); // keep clone identity
+ let ret = obj; // fallback
+ if (XRAY) {
+ if (obj instanceof xrayWindow.Promise) {
+ return promise(obj);
+ }
+ if (obj instanceof unX(xrayWindow).Promise) {
+ return new xrayWindow.Promise((resolve, reject) => {
+ unX(xrayWindow).Promise.prototype.then.call(obj,
+ forPage(r => {
+ if (r.wrappedJSObject && r.wrappedJSObject === unX(r)) {
+ r = unX(r)
+ } else r = forPage(r);
+ resolve(r);
+ }
+ ), forPage(e => reject(e)))
+ });
+ }
+ try {
+ if (obj.wrappedJSObject && obj.wrappedJSObject === unX(obj)) {
+ return obj;
+ }
+ } catch (e) {}
+ try {
+ ret = cloneInto(obj, unX(xrayWindow), {cloneFunctions: true, wrapReflectors: true});
+ } catch (e) {
+ // can't be cloned: must be a Proxy
+ }
+ } else {
+ // Chromium: just use patchWindow's exportFunction() to make our wrappers look like native functions
+ if (typeof obj === "function") {
+ ret = exportFunction(obj, unX(xrayWindow));
+ }
+ }
+ pageReady.add(ret);
+ privilegedToPage.set(obj, ret);
+ return ret;
+ }
+
+ let fixProp = (d, prop, obj) => {
+ for (let accessor of ["set", "get"]) {
+ if (typeof d[accessor] === "function") {
+ let f = d[accessor];
+ d[accessor] = exportFunction(d[accessor], obj, {defineAs: `${accessor} ${prop}`});
+ }
+ }
+ if (typeof d.value === "object") d.value = forPage(d.value);
+ return d;
+ };
+ let OriginalProxy = unwrappedWindow.Proxy;
+ let Proxy = OriginalProxy;
+ let pageAPI, unX;
+ if (XRAY) {
+
+ unX = o => XPCNativeWrapper.unwrap(o);
+
+ // automatically export Proxy constructor parameters
+ let proxyConstructorHandler = forPage({
+ construct(targetConstructor, args) {
+ let [target, handler] = unX(args);
+ let selfProxy = !!(target === WrapHelper.Proxy && handler.construct);
+ if (selfProxy) {
+ let {construct} = handler;
+ handler.construct = (target, args) => {
+ let proxy = construct(target, unX(args));
+ pageReady.add(proxy);
+ return proxy;
+ }
+ }
+
+ target = forPage(target);
+ handler = forPage(handler);
+ let proxy = new targetConstructor(target, handler);
+ pageReady.add(proxy);
+ return proxy;
+ },
+ });
+ Proxy = new OriginalProxy(OriginalProxy, proxyConstructorHandler);
+ let then;
+ let apiHandler = {
+ apply(target, thisArg, args) {
+ let pa = unX(args);
+ for (let j = pa.length; j-- > 0;) {
+ let a = pa[j];
+ if (a && unX(a) === a) {
+ pa[j] = forPage(a);
+ } else if (typeof a === "function") {
+ pa[j] = new Proxy(a, apiHandler);
+ }
+ }
+ let ret = target.apply(thisArg, pa);
+ if (ret) {
+ if (ret instanceof xrayWindow.Promise) {
+ then = then || (then = new Proxy(xrayWindow.Promise.prototype.then, apiHandler));
+ if (ret.wrappedJSObject) {
+ let p = unX(ret);
+ if (p === ret.wrappedJSObject) {
+ p.then = then
+ ret = p;
+ }
+ }
+ } else {
+ ret = forPage(ret);
+ }
+ }
+ return ret;
+ }
+ };
+
+
+
+ pageAPI = f => {
+ if (typeof f !== "function") return f;
+ return new Proxy(f, apiHandler);
+ }
+ } else {
+ pageAPI = unX = f => f;
+ }
+
+ let overlay;
+ {
+ let overlayProtos = new WeakMap();
+ let overlayObjects = new WeakMap();
+ overlay = (obj, data) => {
+ obj = unX(obj);
+ let proto = obj.__proto__;
+ let proxiedProps = overlayProtos.get(proto);
+ if (!proxiedProps) overlayProtos.set(proto, proxiedProps = {});
+ let props = Object.getOwnPropertyDescriptors(data);
+ for (let p in props) {
+ if (p in proxiedProps) continue;
+ for (let rootProto = proto; ;) {
+ let protoProps = Object.getOwnPropertyDescriptors(rootProto);
+ let protoProp = protoProps[p];
+ if (!protoProp) {
+ rootProto = rootProto.__proto__;
+ if (rootProto) continue;
+ }
+ if (protoProp) {
+ let original;
+ if (protoProp.get) {
+ let getterHandler = forPage({
+ apply(target, thisArg, args) {
+ let obj = unX(thisArg);
+ if (overlayObjects.has(obj)) {
+ let data = overlayObjects.get(obj);
+ return forPage(data[p]);
+ }
+ return target.apply(thisArg, args);
+ }
+ });
+ let original = protoProp.get;
+ protoProp.get = new Proxy(protoProp.get, getterHandler);
+ } else if (typeof protoProp.value === "function") {
+ original = protoProp.value;
+ let methodHandler = forPage({
+ apply(target, thisArg, args) {
+ let obj = unX(thisArg);
+ if (overlayObjects.has(obj)) {
+ let data = overlayObjects.get(obj);
+ return forPage(data[p].apply(thisArg, args));
+ }
+ return target.apply(thisArg, args);
+ }
+ });
+ protoProp.value = new Proxy(protoProp.value, methodHandler);
+ } else {
+ protoProp = null;
+ }
+ if (protoProp) {
+ Reflect.defineProperty(rootProto, p, protoProp);
+ proxiedProps[p] = {rootProto, original, protoProp};
+ break;
+ }
+ }
+ Reflect.defineProperty(obj, p, forPage(props[p]));
+ break;
+ }
+ }
+ overlayObjects.set(obj, data);
+ return obj;
+ }
+ }
+ let createObj = unX(xrayWindow).Object.create;
+ WrapHelper = {
+ XRAY, // boolean, are we in a xray environment (i.e. on Firefox)?
+ shared: {}, // shared storage object for in inter-wrapper coordination
+
+ // WrapHelper.forPage() can be used by "complex" proxies to explicitly
+ // prepare an object/function created in Firefox's sandboxed content script environment
+ // to be consumed/called from the page context, and to make replacements for native
+ // objects and functions provided by the wrappers look as much native as possible.
+ // in most cases, however, this gets automated by the code builders replacing
+ // Object methods found in the wrapper sources with their WrapHelper counterparts
+ // and by proxying "callable_name" functions through WrapHelper.pageAPI().
+ forPage,
+ _forPage: x => x, // dummy for easily testing out the preparation
+ isForPage: obj => pageReady.has(obj),
+ unX, // safely waives xray wrappers
+ // xray-aware Object creation helpers, mostly used transparently by the code builders
+ defineProperty(obj, prop, descriptor, ...args) {
+ obj = unX(obj);
+ return Object.defineProperty(obj, prop, fixProp(descriptor, prop, obj), ...args);
+ },
+ defineProperties(obj, descriptors, ...args) {
+ obj = unX(obj);
+ for (let [prop, d] of Object.entries(descriptors)) {
+ descriptors[prop] = fixProp(d, prop, obj);
+ }
+ return Object.defineProperties(obj, descriptors, ...args);
+ },
+ create(proto, descriptors) {
+ let unwrappedProto = unX(proto);
+ let obj = unX(createObj(unwrappedProto));
+ try {
+ if (proto && !obj.__proto__) {
+ obj = Object.create(XPCNativeWrapper(proto));
+ }
+ } catch (e) {
+ // access denied to obj.__proto__, wrappers mismatch
+ obj = forPage(Object.create(proto));
+ }
+ return descriptors ? this.defineProperties(obj, descriptors) && obj : obj;
+ },
+
+ // WrapHelper.overlay(obj, data)
+ // Proxies the prototype of the obj object in order to return the properties of the data object
+ // as if they were native properties (e.g. as if they were returned by getters on the prototype chain,
+ // rather than defined on the instance).
+ // This allows spoofing some native objects data in a less detectable / fingerprintable way than using
+ // Object.defineProperty(). See wrappingS-MCS.js for an example.
+ overlay,
+ // WrapHelper.pageAPI(f)
+ // Proxies the function/method f so that arguments and return values, and especially callbacks and
+ // Promise objects, are recursively managed in order to transparently marshal objects back
+ // and forth Firefox's sandbox for extensions and the page scripts.
+ pageAPI,
+ // the original Proxy constructor
+ OriginalProxy,
+ // our xray-aware proxied Proxy constructor
+ Proxy,
+ };
+ Object.freeze(WrapHelper);
+ }
+
+ with(unwrappedWindow) {
+ let window = unwrappedWindow;
+ let {Proxy} = WrapHelper;
+ let {Promise, Object, Array, JSON} = xrayWindow;
+
+ // add flag variable that determines whether messages should be sent
+ let fp_enabled = false;
+
+ try {
+ // WRAPPERS //
+ } finally {
+ // cleanup environment if necessary
+ }
+
+ // after injection code completed, allow messages (calls from wrappers won't be counted)
+ fp_enabled = true;
+ }
+ }).toString().replace('// WRAPPERS //', wrapped_code)
+
+ return `(${code})();`;
+}
+