summaryrefslogtreecommitdiff
path: root/data/extensions/uBlock0@raymondhill.net/js/resources/json-edit.js
diff options
context:
space:
mode:
Diffstat (limited to 'data/extensions/uBlock0@raymondhill.net/js/resources/json-edit.js')
-rw-r--r--data/extensions/uBlock0@raymondhill.net/js/resources/json-edit.js1097
1 files changed, 1097 insertions, 0 deletions
diff --git a/data/extensions/uBlock0@raymondhill.net/js/resources/json-edit.js b/data/extensions/uBlock0@raymondhill.net/js/resources/json-edit.js
new file mode 100644
index 0000000..f8ea191
--- /dev/null
+++ b/data/extensions/uBlock0@raymondhill.net/js/resources/json-edit.js
@@ -0,0 +1,1097 @@
+/*******************************************************************************
+
+ uBlock Origin - a comprehensive, efficient content blocker
+ Copyright (C) 2019-present Raymond Hill
+
+ 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 {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+
+*/
+
+import {
+ matchObjectPropertiesFn,
+ parsePropertiesToMatchFn,
+} from './utils.js';
+
+import { JSONPath } from './shared.js';
+import { proxyApplyFn } from './proxy-apply.js';
+import { registerScriptlet } from './base.js';
+import { safeSelf } from './safe-self.js';
+
+/******************************************************************************/
+/******************************************************************************/
+
+function editOutboundObjectFn(
+ trusted = false,
+ propChain = '',
+ jsonq = '',
+) {
+ if ( propChain === '' ) { return; }
+ const safe = safeSelf();
+ const logPrefix = safe.makeLogPrefix(
+ `${trusted ? 'trusted-' : ''}edit-outbound-object`,
+ propChain,
+ jsonq
+ );
+ const jsonp = JSONPath.create(jsonq);
+ if ( jsonp.valid === false || jsonp.value !== undefined && trusted !== true ) {
+ return safe.uboLog(logPrefix, 'Bad JSONPath query');
+ }
+ proxyApplyFn(propChain, function(context) {
+ const obj = context.reflect();
+ if ( jsonp.apply(obj) === 0 ) { return obj; }
+ safe.uboLog(logPrefix, 'Edited');
+ if ( safe.logLevel > 1 ) {
+ safe.uboLog(logPrefix, `After edit:\n${safe.JSON_stringify(obj, null, 2)}`);
+ }
+ return obj;
+ });
+}
+registerScriptlet(editOutboundObjectFn, {
+ name: 'edit-outbound-object.fn',
+ dependencies: [
+ JSONPath,
+ proxyApplyFn,
+ safeSelf,
+ ],
+});
+
+/******************************************************************************/
+/**
+ * @scriptlet edit-outbound-object-.js
+ *
+ * @description
+ * Prune properties from an object returned by a specific method.
+ * Properties can only be removed.
+ *
+ * @param propChain
+ * Property chain of the method to trap.
+ *
+ * @param jsonq
+ * A uBO-flavored JSONPath query.
+ *
+ * */
+
+function editOutboundObject(propChain = '', jsonq = '') {
+ editOutboundObjectFn(false, propChain, jsonq);
+}
+registerScriptlet(editOutboundObject, {
+ name: 'edit-outbound-object.js',
+ dependencies: [
+ editOutboundObjectFn,
+ ],
+});
+
+/******************************************************************************/
+/**
+ * @scriptlet trusted-edit-outbound-object.js
+ *
+ * @description
+ * Edit properties of an object returned by a specific method.
+ * Properties can be assigned new values.
+ *
+ * @param propChain
+ * Property chain of the method to trap.
+ *
+* @param jsonq
+ * A uBO-flavored JSONPath query.
+ *
+ * */
+
+function trustedEditOutboundObject(propChain = '', jsonq = '') {
+ editOutboundObjectFn(true, propChain, jsonq);
+}
+registerScriptlet(trustedEditOutboundObject, {
+ name: 'trusted-edit-outbound-object.js',
+ requiresTrust: true,
+ dependencies: [
+ editOutboundObjectFn,
+ ],
+});
+
+/******************************************************************************/
+/******************************************************************************/
+/**
+ * @scriptlet json-edit.js
+ *
+ * @description
+ * Edit object generated through JSON.parse().
+ * Properties can only be removed.
+ *
+ * @param jsonq
+ * A uBO-flavored JSONPath query.
+ *
+ * */
+
+function jsonEdit(jsonq = '') {
+ editOutboundObjectFn(false, 'JSON.parse', jsonq);
+}
+registerScriptlet(jsonEdit, {
+ name: 'json-edit.js',
+ dependencies: [
+ editOutboundObjectFn,
+ ],
+});
+
+/******************************************************************************/
+/**
+ * @scriptlet trusted-json-edit.js
+ *
+ * @description
+ * Edit object generated through JSON.parse().
+ * Properties can be assigned new values.
+ *
+ * @param jsonq
+ * A uBO-flavored JSONPath query.
+ *
+ * */
+
+function trustedJsonEdit(jsonq = '') {
+ editOutboundObjectFn(true, 'JSON.parse', jsonq);
+}
+registerScriptlet(trustedJsonEdit, {
+ name: 'trusted-json-edit.js',
+ requiresTrust: true,
+ dependencies: [
+ editOutboundObjectFn,
+ ],
+});
+
+/******************************************************************************/
+/******************************************************************************/
+
+function editInboundObjectFn(
+ trusted = false,
+ propChain = '',
+ argPosRaw = '',
+ jsonq = '',
+) {
+ if ( propChain === '' ) { return; }
+ const safe = safeSelf();
+ const logPrefix = safe.makeLogPrefix(
+ `${trusted ? 'trusted-' : ''}edit-inbound-object`,
+ propChain,
+ jsonq
+ );
+ const jsonp = JSONPath.create(jsonq);
+ if ( jsonp.valid === false || jsonp.value !== undefined && trusted !== true ) {
+ return safe.uboLog(logPrefix, 'Bad JSONPath query');
+ }
+ const argPos = parseInt(argPosRaw, 10);
+ if ( isNaN(argPos) ) { return; }
+ const getArgPos = args => {
+ if ( argPos >= 0 ) {
+ if ( args.length <= argPos ) { return; }
+ return argPos;
+ }
+ if ( args.length < -argPos ) { return; }
+ return args.length + argPos;
+ };
+ const editObj = obj => {
+ let clone;
+ try {
+ clone = safe.JSON_parse(safe.JSON_stringify(obj));
+ } catch {
+ }
+ if ( typeof clone !== 'object' || clone === null ) { return; }
+ if ( jsonp.apply(clone) === 0 ) { return; }
+ safe.uboLog(logPrefix, 'Edited');
+ if ( safe.logLevel > 1 ) {
+ safe.uboLog(logPrefix, `After edit:\n${safe.JSON_stringify(clone, null, 2)}`);
+ }
+ return clone;
+ };
+ proxyApplyFn(propChain, function(context) {
+ const i = getArgPos(context.args);
+ if ( i !== undefined ) {
+ const obj = editObj(context.args[i]);
+ if ( obj ) {
+ context.args[i] = obj;
+ }
+ }
+ return context.reflect();
+ });
+}
+registerScriptlet(editInboundObjectFn, {
+ name: 'edit-inbound-object.fn',
+ dependencies: [
+ JSONPath,
+ proxyApplyFn,
+ safeSelf,
+ ],
+});
+
+/******************************************************************************/
+/**
+ * @scriptlet edit-inbound-object.js
+ *
+ * @description
+ * Prune properties from an object passed as argument to a specific method.
+ * Properties can only be removed.
+ *
+ * @param propChain
+ * Property chain of the method to trap.
+ *
+ * @param argPos
+ * 0-based position of the argument. Use negative integer for position relative
+ * to the end.
+ *
+ * @param jsonq
+ * A uBO-flavored JSONPath query.
+ *
+ * */
+
+function editInboundObject(propChain = '', argPos = '', jsonq = '') {
+ editInboundObjectFn(false, propChain, argPos, jsonq);
+}
+registerScriptlet(editInboundObject, {
+ name: 'edit-inbound-object.js',
+ dependencies: [
+ editInboundObjectFn,
+ ],
+});
+
+/******************************************************************************/
+/**
+ * @scriptlet trusted-edit-inbound-object.js
+ *
+ * @description
+ * Edit properties of an object passed as argument to a specific method.
+ * Properties can be assigned new values.
+ *
+ * @param propChain
+ * Property chain of the method to trap.
+ *
+ * @param argPos
+ * 0-based position of the argument. Use negative integer for position relative
+ * to the end.
+ *
+ * @param jsonq
+ * A uBO-flavored JSONPath query.
+ *
+ * */
+
+function trustedEditInboundObject(propChain = '', argPos = '', jsonq = '') {
+ editInboundObjectFn(true, propChain, argPos, jsonq);
+}
+registerScriptlet(trustedEditInboundObject, {
+ name: 'trusted-edit-inbound-object.js',
+ requiresTrust: true,
+ dependencies: [
+ editInboundObjectFn,
+ ],
+});
+
+/******************************************************************************/
+/******************************************************************************/
+
+function jsonEditXhrResponseFn(trusted, jsonq = '') {
+ const safe = safeSelf();
+ const logPrefix = safe.makeLogPrefix(
+ `${trusted ? 'trusted-' : ''}json-edit-xhr-response`,
+ jsonq
+ );
+ const xhrInstances = new WeakMap();
+ const jsonp = JSONPath.create(jsonq);
+ if ( jsonp.valid === false || jsonp.value !== undefined && trusted !== true ) {
+ return safe.uboLog(logPrefix, 'Bad JSONPath query');
+ }
+ const extraArgs = safe.getExtraArgs(Array.from(arguments), 2);
+ const propNeedles = parsePropertiesToMatchFn(extraArgs.propsToMatch, 'url');
+ self.XMLHttpRequest = class extends self.XMLHttpRequest {
+ open(method, url, ...args) {
+ const xhrDetails = { method, url };
+ const matched = propNeedles.size === 0 ||
+ matchObjectPropertiesFn(propNeedles, xhrDetails);
+ if ( matched ) {
+ if ( safe.logLevel > 1 && Array.isArray(matched) ) {
+ safe.uboLog(logPrefix, `Matched "propsToMatch":\n\t${matched.join('\n\t')}`);
+ }
+ xhrInstances.set(this, xhrDetails);
+ }
+ return super.open(method, url, ...args);
+ }
+ get response() {
+ const innerResponse = super.response;
+ const xhrDetails = xhrInstances.get(this);
+ if ( xhrDetails === undefined ) { return innerResponse; }
+ const responseLength = typeof innerResponse === 'string'
+ ? innerResponse.length
+ : undefined;
+ if ( xhrDetails.lastResponseLength !== responseLength ) {
+ xhrDetails.response = undefined;
+ xhrDetails.lastResponseLength = responseLength;
+ }
+ if ( xhrDetails.response !== undefined ) {
+ return xhrDetails.response;
+ }
+ let obj;
+ if ( typeof innerResponse === 'object' ) {
+ obj = innerResponse;
+ } else if ( typeof innerResponse === 'string' ) {
+ try { obj = safe.JSON_parse(innerResponse); } catch { }
+ }
+ if ( typeof obj !== 'object' || obj === null || jsonp.apply(obj) === 0 ) {
+ return (xhrDetails.response = innerResponse);
+ }
+ safe.uboLog(logPrefix, 'Edited');
+ const outerResponse = typeof innerResponse === 'string'
+ ? JSONPath.toJSON(obj, safe.JSON_stringify)
+ : obj;
+ return (xhrDetails.response = outerResponse);
+ }
+ get responseText() {
+ const response = this.response;
+ return typeof response !== 'string'
+ ? super.responseText
+ : response;
+ }
+ };
+}
+registerScriptlet(jsonEditXhrResponseFn, {
+ name: 'json-edit-xhr-response.fn',
+ dependencies: [
+ JSONPath,
+ matchObjectPropertiesFn,
+ parsePropertiesToMatchFn,
+ safeSelf,
+ ],
+});
+
+/******************************************************************************/
+/**
+ * @scriptlet json-edit-xhr-response.js
+ *
+ * @description
+ * Edit the object fetched through a XHR instance.
+ * Properties can only be removed.
+ *
+ * @param jsonq
+ * A uBO-flavored JSONPath query.
+ *
+ * @param [propsToMatch, value]
+ * An optional vararg detailing the arguments to match when xhr.open() is
+ * called.
+ *
+ * */
+
+function jsonEditXhrResponse(jsonq = '', ...args) {
+ jsonEditXhrResponseFn(false, jsonq, ...args);
+}
+registerScriptlet(jsonEditXhrResponse, {
+ name: 'json-edit-xhr-response.js',
+ dependencies: [
+ jsonEditXhrResponseFn,
+ ],
+});
+
+/******************************************************************************/
+/**
+ * @scriptlet trusted-json-edit-xhr-response.js
+ *
+ * @description
+ * Edit the object fetched through a XHR instance.
+ * Properties can be assigned new values.
+ *
+ * @param jsonq
+ * A uBO-flavored JSONPath query.
+ *
+ * @param [propsToMatch, value]
+ * An optional vararg detailing the arguments to match when xhr.open() is
+ * called.
+ *
+ * */
+
+function trustedJsonEditXhrResponse(jsonq = '', ...args) {
+ jsonEditXhrResponseFn(true, jsonq, ...args);
+}
+registerScriptlet(trustedJsonEditXhrResponse, {
+ name: 'trusted-json-edit-xhr-response.js',
+ requiresTrust: true,
+ dependencies: [
+ jsonEditXhrResponseFn,
+ ],
+});
+
+/******************************************************************************/
+/******************************************************************************/
+
+function jsonEditXhrRequestFn(trusted, jsonq = '') {
+ const safe = safeSelf();
+ const logPrefix = safe.makeLogPrefix(
+ `${trusted ? 'trusted-' : ''}json-edit-xhr-request`,
+ jsonq
+ );
+ const xhrInstances = new WeakMap();
+ const jsonp = JSONPath.create(jsonq);
+ if ( jsonp.valid === false || jsonp.value !== undefined && trusted !== true ) {
+ return safe.uboLog(logPrefix, 'Bad JSONPath query');
+ }
+ const extraArgs = safe.getExtraArgs(Array.from(arguments), 2);
+ const propNeedles = parsePropertiesToMatchFn(extraArgs.propsToMatch, 'url');
+ self.XMLHttpRequest = class extends self.XMLHttpRequest {
+ open(method, url, ...args) {
+ const xhrDetails = { method, url };
+ const matched = propNeedles.size === 0 ||
+ matchObjectPropertiesFn(propNeedles, xhrDetails);
+ if ( matched ) {
+ if ( safe.logLevel > 1 && Array.isArray(matched) ) {
+ safe.uboLog(logPrefix, `Matched "propsToMatch":\n\t${matched.join('\n\t')}`);
+ }
+ xhrInstances.set(this, xhrDetails);
+ }
+ return super.open(method, url, ...args);
+ }
+ send(body) {
+ const xhrDetails = xhrInstances.get(this);
+ if ( xhrDetails ) {
+ body = this.#filterBody(body) || body;
+ }
+ super.send(body);
+ }
+ #filterBody(body) {
+ if ( typeof body !== 'string' ) { return; }
+ let data;
+ try { data = safe.JSON_parse(body); }
+ catch { }
+ if ( data instanceof Object === false ) { return; }
+ const n = jsonp.apply(data);
+ if ( n === 0 ) { return; }
+ body = safe.JSON_stringify(data);
+ safe.uboLog(logPrefix, 'Edited');
+ if ( safe.logLevel > 1 ) {
+ safe.uboLog(logPrefix, `After edit:\n${body}`);
+ }
+ return body;
+ }
+ };
+}
+registerScriptlet(jsonEditXhrRequestFn, {
+ name: 'json-edit-xhr-request.fn',
+ dependencies: [
+ JSONPath,
+ matchObjectPropertiesFn,
+ parsePropertiesToMatchFn,
+ safeSelf,
+ ],
+});
+
+/******************************************************************************/
+/**
+ * @scriptlet json-edit-xhr-request.js
+ *
+ * @description
+ * Edit the object sent as the body in a XHR instance.
+ * Properties can only be removed.
+ *
+ * @param jsonq
+ * A uBO-flavored JSONPath query.
+ *
+ * @param [propsToMatch, value]
+ * An optional vararg detailing the arguments to match when xhr.open() is
+ * called.
+ *
+ * */
+
+function jsonEditXhrRequest(jsonq = '', ...args) {
+ jsonEditXhrRequestFn(false, jsonq, ...args);
+}
+registerScriptlet(jsonEditXhrRequest, {
+ name: 'json-edit-xhr-request.js',
+ dependencies: [
+ jsonEditXhrRequestFn,
+ ],
+});
+
+/******************************************************************************/
+/**
+ * @scriptlet trusted-json-edit-xhr-request.js
+ *
+ * @description
+ * Edit the object sent as the body in a XHR instance.
+ * Properties can be assigned new values.
+ *
+ * @param jsonq
+ * A uBO-flavored JSONPath query.
+ *
+ * @param [propsToMatch, value]
+ * An optional vararg detailing the arguments to match when xhr.open() is
+ * called.
+ *
+ * */
+
+function trustedJsonEditXhrRequest(jsonq = '', ...args) {
+ jsonEditXhrRequestFn(true, jsonq, ...args);
+}
+registerScriptlet(trustedJsonEditXhrRequest, {
+ name: 'trusted-json-edit-xhr-request.js',
+ requiresTrust: true,
+ dependencies: [
+ jsonEditXhrRequestFn,
+ ],
+});
+
+/******************************************************************************/
+/******************************************************************************/
+
+function jsonEditFetchResponseFn(trusted, jsonq = '') {
+ const safe = safeSelf();
+ const logPrefix = safe.makeLogPrefix(
+ `${trusted ? 'trusted-' : ''}json-edit-fetch-response`,
+ jsonq
+ );
+ const jsonp = JSONPath.create(jsonq);
+ if ( jsonp.valid === false || jsonp.value !== undefined && trusted !== true ) {
+ return safe.uboLog(logPrefix, 'Bad JSONPath query');
+ }
+ const extraArgs = safe.getExtraArgs(Array.from(arguments), 2);
+ const propNeedles = parsePropertiesToMatchFn(extraArgs.propsToMatch, 'url');
+ proxyApplyFn('fetch', function(context) {
+ const args = context.callArgs;
+ const fetchPromise = context.reflect();
+ if ( propNeedles.size !== 0 ) {
+ const objs = [ args[0] instanceof Object ? args[0] : { url: args[0] } ];
+ if ( objs[0] instanceof Request ) {
+ try {
+ objs[0] = safe.Request_clone.call(objs[0]);
+ } catch(ex) {
+ safe.uboErr(logPrefix, 'Error:', ex);
+ }
+ }
+ if ( args[1] instanceof Object ) {
+ objs.push(args[1]);
+ }
+ const matched = matchObjectPropertiesFn(propNeedles, ...objs);
+ if ( matched === undefined ) { return fetchPromise; }
+ if ( safe.logLevel > 1 ) {
+ safe.uboLog(logPrefix, `Matched "propsToMatch":\n\t${matched.join('\n\t')}`);
+ }
+ }
+ return fetchPromise.then(responseBefore => {
+ const response = responseBefore.clone();
+ return response.json().then(obj => {
+ if ( typeof obj !== 'object' ) { return responseBefore; }
+ if ( jsonp.apply(obj) === 0 ) { return responseBefore; }
+ safe.uboLog(logPrefix, 'Edited');
+ const responseAfter = Response.json(obj, {
+ status: responseBefore.status,
+ statusText: responseBefore.statusText,
+ headers: responseBefore.headers,
+ });
+ Object.defineProperties(responseAfter, {
+ ok: { value: responseBefore.ok },
+ redirected: { value: responseBefore.redirected },
+ type: { value: responseBefore.type },
+ url: { value: responseBefore.url },
+ });
+ return responseAfter;
+ }).catch(reason => {
+ safe.uboErr(logPrefix, 'Error:', reason);
+ return responseBefore;
+ });
+ }).catch(reason => {
+ safe.uboErr(logPrefix, 'Error:', reason);
+ return fetchPromise;
+ });
+ });
+}
+registerScriptlet(jsonEditFetchResponseFn, {
+ name: 'json-edit-fetch-response.fn',
+ dependencies: [
+ JSONPath,
+ matchObjectPropertiesFn,
+ parsePropertiesToMatchFn,
+ proxyApplyFn,
+ safeSelf,
+ ],
+});
+
+/******************************************************************************/
+/**
+ * @scriptlet json-edit-fetch-response.js
+ *
+ * @description
+ * Edit the object fetched through the fetch API.
+ * Properties can only be removed.
+ *
+ * @param jsonq
+ * A uBO-flavored JSONPath query.
+ *
+ * @param [propsToMatch, value]
+ * An optional vararg detailing the arguments to match when xhr.open() is
+ * called.
+ *
+ * */
+
+function jsonEditFetchResponse(jsonq = '', ...args) {
+ jsonEditFetchResponseFn(false, jsonq, ...args);
+}
+registerScriptlet(jsonEditFetchResponse, {
+ name: 'json-edit-fetch-response.js',
+ dependencies: [
+ jsonEditFetchResponseFn,
+ ],
+});
+
+/******************************************************************************/
+/**
+ * @scriptlet trusted-json-edit-fetch-response.js
+ *
+ * @description
+ * Edit the object fetched through the fetch API. The trusted version allows
+ * Properties can be assigned new values.
+ *
+ * @param jsonq
+ * A uBO-flavored JSONPath query.
+ *
+ * @param [propsToMatch, value]
+ * An optional vararg detailing the arguments to match when xhr.open() is
+ * called.
+ *
+ * */
+
+function trustedJsonEditFetchResponse(jsonq = '', ...args) {
+ jsonEditFetchResponseFn(true, jsonq, ...args);
+}
+registerScriptlet(trustedJsonEditFetchResponse, {
+ name: 'trusted-json-edit-fetch-response.js',
+ requiresTrust: true,
+ dependencies: [
+ jsonEditFetchResponseFn,
+ ],
+});
+
+/******************************************************************************/
+/******************************************************************************/
+
+function jsonEditFetchRequestFn(trusted, jsonq = '') {
+ const safe = safeSelf();
+ const logPrefix = safe.makeLogPrefix(
+ `${trusted ? 'trusted-' : ''}json-edit-fetch-request`,
+ jsonq
+ );
+ const jsonp = JSONPath.create(jsonq);
+ if ( jsonp.valid === false || jsonp.value !== undefined && trusted !== true ) {
+ return safe.uboLog(logPrefix, 'Bad JSONPath query');
+ }
+ const extraArgs = safe.getExtraArgs(Array.from(arguments), 2);
+ const propNeedles = parsePropertiesToMatchFn(extraArgs.propsToMatch, 'url');
+ const filterBody = body => {
+ if ( typeof body !== 'string' ) { return; }
+ let data;
+ try { data = safe.JSON_parse(body); }
+ catch { }
+ if ( data instanceof Object === false ) { return; }
+ const n = jsonp.apply(data);
+ if ( n === 0 ) { return; }
+ return safe.JSON_stringify(data);
+ }
+ const proxyHandler = context => {
+ const args = context.callArgs;
+ const [ resource, options ] = args;
+ const bodyBefore = options?.body;
+ if ( Boolean(bodyBefore) === false ) { return context.reflect(); }
+ const bodyAfter = filterBody(bodyBefore);
+ if ( bodyAfter === undefined || bodyAfter === bodyBefore ) {
+ return context.reflect();
+ }
+ if ( propNeedles.size !== 0 ) {
+ const objs = [
+ resource instanceof Object ? resource : { url: `${resource}` }
+ ];
+ if ( objs[0] instanceof Request ) {
+ try {
+ objs[0] = safe.Request_clone.call(objs[0]);
+ } catch(ex) {
+ safe.uboErr(logPrefix, 'Error:', ex);
+ }
+ }
+ const matched = matchObjectPropertiesFn(propNeedles, ...objs);
+ if ( matched === undefined ) { return context.reflect(); }
+ if ( safe.logLevel > 1 ) {
+ safe.uboLog(logPrefix, `Matched "propsToMatch":\n\t${matched.join('\n\t')}`);
+ }
+ }
+ safe.uboLog(logPrefix, 'Edited');
+ if ( safe.logLevel > 1 ) {
+ safe.uboLog(logPrefix, `After edit:\n${bodyAfter}`);
+ }
+ options.body = bodyAfter;
+ return context.reflect();
+ };
+ proxyApplyFn('fetch', proxyHandler);
+ proxyApplyFn('Request', proxyHandler);
+}
+registerScriptlet(jsonEditFetchRequestFn, {
+ name: 'json-edit-fetch-request.fn',
+ dependencies: [
+ JSONPath,
+ matchObjectPropertiesFn,
+ parsePropertiesToMatchFn,
+ proxyApplyFn,
+ safeSelf,
+ ],
+});
+
+/******************************************************************************/
+/**
+ * @scriptlet json-edit-fetch-request.js
+ *
+ * @description
+ * Edit the request body sent through the fetch API.
+ * Properties can only be removed.
+ *
+ * @param jsonq
+ * A uBO-flavored JSONPath query.
+ *
+ * @param [propsToMatch, value]
+ * An optional vararg detailing the arguments to match when fetch() is called.
+ *
+ * */
+
+function jsonEditFetchRequest(jsonq = '', ...args) {
+ jsonEditFetchRequestFn(false, jsonq, ...args);
+}
+registerScriptlet(jsonEditFetchRequest, {
+ name: 'json-edit-fetch-request.js',
+ dependencies: [
+ jsonEditFetchRequestFn,
+ ],
+});
+
+/******************************************************************************/
+/**
+ * @scriptlet trusted-json-edit-fetch-request.js
+ *
+ * @description
+ * Edit the request body sent through the fetch API.
+ * Properties can be assigned new values.
+ *
+ * @param jsonq
+ * A uBO-flavored JSONPath query.
+ *
+ * @param [propsToMatch, value]
+ * An optional vararg detailing the arguments to match when fetch() is called.
+ *
+ * */
+
+function trustedJsonEditFetchRequest(jsonq = '', ...args) {
+ jsonEditFetchRequestFn(true, jsonq, ...args);
+}
+registerScriptlet(trustedJsonEditFetchRequest, {
+ name: 'trusted-json-edit-fetch-request.js',
+ requiresTrust: true,
+ dependencies: [
+ jsonEditFetchRequestFn,
+ ],
+});
+
+/******************************************************************************/
+/******************************************************************************/
+
+function jsonlEditFn(jsonp, text = '') {
+ const safe = safeSelf();
+ const lineSeparator = /\r?\n/.exec(text)?.[0] || '\n';
+ const linesBefore = text.split('\n');
+ const linesAfter = [];
+ for ( const lineBefore of linesBefore ) {
+ let obj;
+ try { obj = safe.JSON_parse(lineBefore); } catch { }
+ if ( typeof obj !== 'object' || obj === null ) {
+ linesAfter.push(lineBefore);
+ continue;
+ }
+ if ( jsonp.apply(obj) === 0 ) {
+ linesAfter.push(lineBefore);
+ continue;
+ }
+ const lineAfter = safe.JSON_stringify(obj);
+ linesAfter.push(lineAfter);
+ }
+ return linesAfter.join(lineSeparator);
+}
+registerScriptlet(jsonlEditFn, {
+ name: 'jsonl-edit.fn',
+ dependencies: [
+ JSONPath,
+ safeSelf,
+ ],
+});
+
+/******************************************************************************/
+
+function jsonlEditXhrResponseFn(trusted, jsonq = '') {
+ const safe = safeSelf();
+ const logPrefix = safe.makeLogPrefix(
+ `${trusted ? 'trusted-' : ''}jsonl-edit-xhr-response`,
+ jsonq
+ );
+ const xhrInstances = new WeakMap();
+ const jsonp = JSONPath.create(jsonq);
+ if ( jsonp.valid === false || jsonp.value !== undefined && trusted !== true ) {
+ return safe.uboLog(logPrefix, 'Bad JSONPath query');
+ }
+ const extraArgs = safe.getExtraArgs(Array.from(arguments), 2);
+ const propNeedles = parsePropertiesToMatchFn(extraArgs.propsToMatch, 'url');
+ self.XMLHttpRequest = class extends self.XMLHttpRequest {
+ open(method, url, ...args) {
+ const xhrDetails = { method, url };
+ const matched = propNeedles.size === 0 ||
+ matchObjectPropertiesFn(propNeedles, xhrDetails);
+ if ( matched ) {
+ if ( safe.logLevel > 1 && Array.isArray(matched) ) {
+ safe.uboLog(logPrefix, `Matched "propsToMatch":\n\t${matched.join('\n\t')}`);
+ }
+ xhrInstances.set(this, xhrDetails);
+ }
+ return super.open(method, url, ...args);
+ }
+ get response() {
+ const innerResponse = super.response;
+ const xhrDetails = xhrInstances.get(this);
+ if ( xhrDetails === undefined ) {
+ return innerResponse;
+ }
+ const responseLength = typeof innerResponse === 'string'
+ ? innerResponse.length
+ : undefined;
+ if ( xhrDetails.lastResponseLength !== responseLength ) {
+ xhrDetails.response = undefined;
+ xhrDetails.lastResponseLength = responseLength;
+ }
+ if ( xhrDetails.response !== undefined ) {
+ return xhrDetails.response;
+ }
+ if ( typeof innerResponse !== 'string' ) {
+ return (xhrDetails.response = innerResponse);
+ }
+ const outerResponse = jsonlEditFn(jsonp, innerResponse);
+ if ( outerResponse !== innerResponse ) {
+ safe.uboLog(logPrefix, 'Pruned');
+ }
+ return (xhrDetails.response = outerResponse);
+ }
+ get responseText() {
+ const response = this.response;
+ return typeof response !== 'string'
+ ? super.responseText
+ : response;
+ }
+ };
+}
+registerScriptlet(jsonlEditXhrResponseFn, {
+ name: 'jsonl-edit-xhr-response.fn',
+ dependencies: [
+ JSONPath,
+ jsonlEditFn,
+ matchObjectPropertiesFn,
+ parsePropertiesToMatchFn,
+ safeSelf,
+ ],
+});
+
+/******************************************************************************/
+/**
+ * @scriptlet jsonl-edit-xhr-response.js
+ *
+ * @description
+ * Edit the objects found in a JSONL resource fetched through a XHR instance.
+ * Properties can only be removed.
+ *
+ * @param jsonq
+ * A uBO-flavored JSONPath query.
+ *
+ * @param [propsToMatch, value]
+ * An optional vararg detailing the arguments to match when xhr.open() is
+ * called.
+ *
+ * */
+
+function jsonlEditXhrResponse(jsonq = '', ...args) {
+ jsonlEditXhrResponseFn(false, jsonq, ...args);
+}
+registerScriptlet(jsonlEditXhrResponse, {
+ name: 'jsonl-edit-xhr-response.js',
+ dependencies: [
+ jsonlEditXhrResponseFn,
+ ],
+});
+
+/******************************************************************************/
+/**
+ * @scriptlet trusted-jsonl-edit-xhr-response.js
+ *
+ * @description
+ * Edit the objects found in a JSONL resource fetched through a XHR instance.
+ * Properties can be assigned new values.
+ *
+ * @param jsonq
+ * A uBO-flavored JSONPath query.
+ *
+ * @param [propsToMatch, value]
+ * An optional vararg detailing the arguments to match when xhr.open() is
+ * called.
+ *
+ * */
+
+function trustedJsonlEditXhrResponse(jsonq = '', ...args) {
+ jsonlEditXhrResponseFn(true, jsonq, ...args);
+}
+registerScriptlet(trustedJsonlEditXhrResponse, {
+ name: 'trusted-jsonl-edit-xhr-response.js',
+ requiresTrust: true,
+ dependencies: [
+ jsonlEditXhrResponseFn,
+ ],
+});
+
+/******************************************************************************/
+/******************************************************************************/
+
+function jsonlEditFetchResponseFn(trusted, jsonq = '') {
+ const safe = safeSelf();
+ const logPrefix = safe.makeLogPrefix(
+ `${trusted ? 'trusted-' : ''}jsonl-edit-fetch-response`,
+ jsonq
+ );
+ const jsonp = JSONPath.create(jsonq);
+ if ( jsonp.valid === false || jsonp.value !== undefined && trusted !== true ) {
+ return safe.uboLog(logPrefix, 'Bad JSONPath query');
+ }
+ const extraArgs = safe.getExtraArgs(Array.from(arguments), 2);
+ const propNeedles = parsePropertiesToMatchFn(extraArgs.propsToMatch, 'url');
+ const logall = jsonq === '';
+ proxyApplyFn('fetch', function(context) {
+ const args = context.callArgs;
+ const fetchPromise = context.reflect();
+ if ( propNeedles.size !== 0 ) {
+ const objs = [ args[0] instanceof Object ? args[0] : { url: args[0] } ];
+ if ( objs[0] instanceof Request ) {
+ try {
+ objs[0] = safe.Request_clone.call(objs[0]);
+ } catch(ex) {
+ safe.uboErr(logPrefix, 'Error:', ex);
+ }
+ }
+ if ( args[1] instanceof Object ) {
+ objs.push(args[1]);
+ }
+ const matched = matchObjectPropertiesFn(propNeedles, ...objs);
+ if ( matched === undefined ) { return fetchPromise; }
+ if ( safe.logLevel > 1 ) {
+ safe.uboLog(logPrefix, `Matched "propsToMatch":\n\t${matched.join('\n\t')}`);
+ }
+ }
+ return fetchPromise.then(responseBefore => {
+ const response = responseBefore.clone();
+ return response.text().then(textBefore => {
+ if ( typeof textBefore !== 'string' ) { return textBefore; }
+ if ( logall ) {
+ safe.uboLog(logPrefix, textBefore);
+ return responseBefore;
+ }
+ const textAfter = jsonlEditFn(jsonp, textBefore);
+ if ( textAfter === textBefore ) { return responseBefore; }
+ safe.uboLog(logPrefix, 'Pruned');
+ const responseAfter = new Response(textAfter, {
+ status: responseBefore.status,
+ statusText: responseBefore.statusText,
+ headers: responseBefore.headers,
+ });
+ Object.defineProperties(responseAfter, {
+ ok: { value: responseBefore.ok },
+ redirected: { value: responseBefore.redirected },
+ type: { value: responseBefore.type },
+ url: { value: responseBefore.url },
+ });
+ return responseAfter;
+ }).catch(reason => {
+ safe.uboErr(logPrefix, 'Error:', reason);
+ return responseBefore;
+ });
+ }).catch(reason => {
+ safe.uboErr(logPrefix, 'Error:', reason);
+ return fetchPromise;
+ });
+ });
+}
+registerScriptlet(jsonlEditFetchResponseFn, {
+ name: 'jsonl-edit-fetch-response.fn',
+ dependencies: [
+ JSONPath,
+ jsonlEditFn,
+ matchObjectPropertiesFn,
+ parsePropertiesToMatchFn,
+ proxyApplyFn,
+ safeSelf,
+ ],
+});
+
+/******************************************************************************/
+/**
+ * @scriptlet jsonl-edit-fetch-response.js
+ *
+ * @description
+ * Edit the objects found in a JSONL resource fetched through the fetch API.
+ * Properties can only be removed.
+ *
+ * @param jsonq
+ * A uBO-flavored JSONPath query.
+ *
+ * @param [propsToMatch, value]
+ * An optional vararg detailing the arguments to match when xhr.open() is
+ * called.
+ *
+ * */
+
+function jsonlEditFetchResponse(jsonq = '', ...args) {
+ jsonlEditFetchResponseFn(false, jsonq, ...args);
+}
+registerScriptlet(jsonlEditFetchResponse, {
+ name: 'jsonl-edit-fetch-response.js',
+ dependencies: [
+ jsonlEditFetchResponseFn,
+ ],
+});
+
+/******************************************************************************/
+/**
+ * @scriptlet trusted-jsonl-edit-fetch-response.js
+ *
+ * @description
+ * Edit the objects found in a JSONL resource fetched through the fetch API.
+ * Properties can be assigned new values.
+ *
+ * @param jsonq
+ * A uBO-flavored JSONPath query.
+ *
+ * @param [propsToMatch, value]
+ * An optional vararg detailing the arguments to match when xhr.open() is
+ * called.
+ *
+ * */
+
+function trustedJsonlEditFetchResponse(jsonq = '', ...args) {
+ jsonlEditFetchResponseFn(true, jsonq, ...args);
+}
+registerScriptlet(trustedJsonlEditFetchResponse, {
+ name: 'trusted-jsonl-edit-fetch-response.js',
+ requiresTrust: true,
+ dependencies: [
+ jsonlEditFetchResponseFn,
+ ],
+});
+
+/******************************************************************************/