summaryrefslogtreecommitdiff
path: root/data/extensions/uBlock0@raymondhill.net/js/resources/json-prune.js
diff options
context:
space:
mode:
Diffstat (limited to 'data/extensions/uBlock0@raymondhill.net/js/resources/json-prune.js')
-rw-r--r--data/extensions/uBlock0@raymondhill.net/js/resources/json-prune.js275
1 files changed, 275 insertions, 0 deletions
diff --git a/data/extensions/uBlock0@raymondhill.net/js/resources/json-prune.js b/data/extensions/uBlock0@raymondhill.net/js/resources/json-prune.js
new file mode 100644
index 0000000..08284e3
--- /dev/null
+++ b/data/extensions/uBlock0@raymondhill.net/js/resources/json-prune.js
@@ -0,0 +1,275 @@
+/*******************************************************************************
+
+ 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 { objectPruneFn } from './object-prune.js';
+import { proxyApplyFn } from './proxy-apply.js';
+import { registerScriptlet } from './base.js';
+import { safeSelf } from './safe-self.js';
+
+/******************************************************************************/
+
+function jsonPrune(
+ rawPrunePaths = '',
+ rawNeedlePaths = '',
+ stackNeedle = ''
+) {
+ const safe = safeSelf();
+ const logPrefix = safe.makeLogPrefix('json-prune', rawPrunePaths, rawNeedlePaths, stackNeedle);
+ const stackNeedleDetails = safe.initPattern(stackNeedle, { canNegate: true });
+ const extraArgs = safe.getExtraArgs(Array.from(arguments), 3);
+ JSON.parse = new Proxy(JSON.parse, {
+ apply: function(target, thisArg, args) {
+ const objBefore = Reflect.apply(target, thisArg, args);
+ if ( rawPrunePaths === '' ) {
+ safe.uboLog(logPrefix, safe.JSON_stringify(objBefore, null, 2));
+ }
+ const objAfter = objectPruneFn(
+ objBefore,
+ rawPrunePaths,
+ rawNeedlePaths,
+ stackNeedleDetails,
+ extraArgs
+ );
+ if ( objAfter === undefined ) { return objBefore; }
+ safe.uboLog(logPrefix, 'Pruned');
+ if ( safe.logLevel > 1 ) {
+ safe.uboLog(logPrefix, `After pruning:\n${safe.JSON_stringify(objAfter, null, 2)}`);
+ }
+ return objAfter;
+ },
+ });
+}
+registerScriptlet(jsonPrune, {
+ name: 'json-prune.js',
+ dependencies: [
+ objectPruneFn,
+ safeSelf,
+ ],
+});
+
+/******************************************************************************/
+
+function jsonPruneFetchResponse(
+ rawPrunePaths = '',
+ rawNeedlePaths = ''
+) {
+ const safe = safeSelf();
+ const logPrefix = safe.makeLogPrefix('json-prune-fetch-response', rawPrunePaths, rawNeedlePaths);
+ const extraArgs = safe.getExtraArgs(Array.from(arguments), 2);
+ const propNeedles = parsePropertiesToMatchFn(extraArgs.propsToMatch, 'url');
+ const stackNeedle = safe.initPattern(extraArgs.stackToMatch || '', { canNegate: true });
+ const logall = rawPrunePaths === '';
+ const applyHandler = function(target, thisArg, args) {
+ const fetchPromise = Reflect.apply(target, thisArg, args);
+ 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(objBefore => {
+ if ( typeof objBefore !== 'object' ) { return responseBefore; }
+ if ( logall ) {
+ safe.uboLog(logPrefix, safe.JSON_stringify(objBefore, null, 2));
+ return responseBefore;
+ }
+ const objAfter = objectPruneFn(
+ objBefore,
+ rawPrunePaths,
+ rawNeedlePaths,
+ stackNeedle,
+ extraArgs
+ );
+ if ( typeof objAfter !== 'object' ) { return responseBefore; }
+ safe.uboLog(logPrefix, 'Pruned');
+ const responseAfter = Response.json(objAfter, {
+ 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;
+ });
+ };
+ self.fetch = new Proxy(self.fetch, {
+ apply: applyHandler
+ });
+}
+registerScriptlet(jsonPruneFetchResponse, {
+ name: 'json-prune-fetch-response.js',
+ dependencies: [
+ matchObjectPropertiesFn,
+ objectPruneFn,
+ parsePropertiesToMatchFn,
+ safeSelf,
+ ],
+});
+
+/******************************************************************************/
+
+function jsonPruneXhrResponse(
+ rawPrunePaths = '',
+ rawNeedlePaths = ''
+) {
+ const safe = safeSelf();
+ const logPrefix = safe.makeLogPrefix('json-prune-xhr-response', rawPrunePaths, rawNeedlePaths);
+ const xhrInstances = new WeakMap();
+ const extraArgs = safe.getExtraArgs(Array.from(arguments), 2);
+ const propNeedles = parsePropertiesToMatchFn(extraArgs.propsToMatch, 'url');
+ const stackNeedle = safe.initPattern(extraArgs.stackToMatch || '', { canNegate: true });
+ self.XMLHttpRequest = class extends self.XMLHttpRequest {
+ open(method, url, ...args) {
+ const xhrDetails = { method, url };
+ let outcome = 'match';
+ if ( propNeedles.size !== 0 ) {
+ if ( matchObjectPropertiesFn(propNeedles, xhrDetails) === undefined ) {
+ outcome = 'nomatch';
+ }
+ }
+ if ( outcome === 'match' ) {
+ if ( safe.logLevel > 1 ) {
+ safe.uboLog(logPrefix, `Matched optional "propsToMatch", "${extraArgs.propsToMatch}"`);
+ }
+ 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 objBefore;
+ if ( typeof innerResponse === 'object' ) {
+ objBefore = innerResponse;
+ } else if ( typeof innerResponse === 'string' ) {
+ try {
+ objBefore = safe.JSON_parse(innerResponse);
+ } catch {
+ }
+ }
+ if ( typeof objBefore !== 'object' ) {
+ return (xhrDetails.response = innerResponse);
+ }
+ const objAfter = objectPruneFn(
+ objBefore,
+ rawPrunePaths,
+ rawNeedlePaths,
+ stackNeedle,
+ extraArgs
+ );
+ let outerResponse;
+ if ( typeof objAfter === 'object' ) {
+ outerResponse = typeof innerResponse === 'string'
+ ? safe.JSON_stringify(objAfter)
+ : objAfter;
+ safe.uboLog(logPrefix, 'Pruned');
+ } else {
+ outerResponse = innerResponse;
+ }
+ return (xhrDetails.response = outerResponse);
+ }
+ get responseText() {
+ const response = this.response;
+ return typeof response !== 'string'
+ ? super.responseText
+ : response;
+ }
+ };
+}
+registerScriptlet(jsonPruneXhrResponse, {
+ name: 'json-prune-xhr-response.js',
+ dependencies: [
+ matchObjectPropertiesFn,
+ objectPruneFn,
+ parsePropertiesToMatchFn,
+ safeSelf,
+ ],
+});
+
+/******************************************************************************/
+
+// There is still code out there which uses `eval` in lieu of `JSON.parse`.
+
+function evaldataPrune(
+ rawPrunePaths = '',
+ rawNeedlePaths = ''
+) {
+ proxyApplyFn('eval', function(context) {
+ const before = context.reflect();
+ if ( typeof before !== 'object' ) { return before; }
+ if ( before === null ) { return null; }
+ const after = objectPruneFn(before, rawPrunePaths, rawNeedlePaths);
+ return after || before;
+ });
+}
+registerScriptlet(evaldataPrune, {
+ name: 'evaldata-prune.js',
+ dependencies: [
+ objectPruneFn,
+ proxyApplyFn,
+ ],
+});
+
+/******************************************************************************/