mirror of
https://github.com/gorhill/uBlock.git
synced 2026-03-11 09:04:36 +00:00
Add json-edit-related scriptlets
edit-outbound-object-.js @description Prune properties from an object returned by a specific method. Properties can only be removed. @param jsonq A uBO-flavored JSONPath query. trusted-edit-outbound-object.js @description Edit properties from an object returned by a specific method. Properties can be assigned new values. @param jsonq A uBO-flavored JSONPath query. 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. 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. 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. 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.
This commit is contained in:
parent
3a2bb62519
commit
87e0434c90
2 changed files with 327 additions and 12 deletions
|
|
@ -99,6 +99,10 @@ export class JSONPath {
|
|||
const r = this.#compile(query, 0);
|
||||
if ( r === undefined ) { return; }
|
||||
if ( r.i !== query.length ) {
|
||||
if ( query.startsWith('+=', r.i) ) {
|
||||
r.modify = '+';
|
||||
r.i += 1;
|
||||
}
|
||||
if ( query.startsWith('=', r.i) === false ) { return; }
|
||||
try { r.rval = JSON.parse(query.slice(r.i+1)); }
|
||||
catch { return; }
|
||||
|
|
@ -114,7 +118,7 @@ export class JSONPath {
|
|||
}
|
||||
apply(root) {
|
||||
if ( this.valid === false ) { return 0; }
|
||||
const { rval } = this.#compiled;
|
||||
const { modify, rval } = this.#compiled;
|
||||
this.#root = root;
|
||||
const paths = this.#evaluate(this.#compiled.steps, []);
|
||||
const n = paths.length;
|
||||
|
|
@ -122,7 +126,11 @@ export class JSONPath {
|
|||
while ( i-- ) {
|
||||
const { obj, key } = this.#resolvePath(paths[i]);
|
||||
if ( rval !== undefined ) {
|
||||
obj[key] = rval;
|
||||
if ( modify === '+' ) {
|
||||
this.#modifyVal(obj, key, rval);
|
||||
} else {
|
||||
obj[key] = rval;
|
||||
}
|
||||
} else if ( Array.isArray(obj) && typeof key === 'number' ) {
|
||||
obj.splice(key, 1);
|
||||
} else {
|
||||
|
|
@ -450,4 +458,13 @@ export class JSONPath {
|
|||
}
|
||||
if ( outcome ) { return k; }
|
||||
}
|
||||
#modifyVal(obj, key, rval) {
|
||||
const lval = obj[key];
|
||||
if ( rval instanceof Object === false ) { return; }
|
||||
if ( lval instanceof Object === false ) { return; }
|
||||
if ( Array.isArray(lval) ) { return; }
|
||||
for ( const [ k, v ] of Object.entries(rval) ) {
|
||||
lval[k] = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,21 +30,28 @@ import { proxyApplyFn } from './proxy-apply.js';
|
|||
import { registerScriptlet } from './base.js';
|
||||
import { safeSelf } from './safe-self.js';
|
||||
|
||||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
function jsonEditFn(trusted, jsonq = '') {
|
||||
function editOutboundObjectFn(
|
||||
trusted = false,
|
||||
propChain = '',
|
||||
jsonq = '',
|
||||
) {
|
||||
if ( propChain === '' ) { return; }
|
||||
const safe = safeSelf();
|
||||
const logPrefix = safe.makeLogPrefix(
|
||||
`${trusted ? 'trusted-' : ''}json-edit`,
|
||||
`${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('JSON.parse', function(context) {
|
||||
proxyApplyFn(propChain, function(context) {
|
||||
const obj = context.reflect();
|
||||
if ( jsonp.apply(obj) !== 0 ) { return obj; }
|
||||
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)}`);
|
||||
|
|
@ -52,8 +59,8 @@ function jsonEditFn(trusted, jsonq = '') {
|
|||
return obj;
|
||||
});
|
||||
}
|
||||
registerScriptlet(jsonEditFn, {
|
||||
name: 'json-edit.fn',
|
||||
registerScriptlet(editOutboundObjectFn, {
|
||||
name: 'edit-outbound-object.fn',
|
||||
dependencies: [
|
||||
JSONPath,
|
||||
proxyApplyFn,
|
||||
|
|
@ -61,6 +68,54 @@ registerScriptlet(jsonEditFn, {
|
|||
],
|
||||
});
|
||||
|
||||
/******************************************************************************/
|
||||
/**
|
||||
* @scriptlet edit-outbound-object-.js
|
||||
*
|
||||
* @description
|
||||
* Prune properties from an object returned by a specific method.
|
||||
* Properties can only be removed.
|
||||
*
|
||||
* @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 from an object returned by a specific method.
|
||||
* Properties can be assigned new values.
|
||||
*
|
||||
* @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
|
||||
|
|
@ -75,12 +130,12 @@ registerScriptlet(jsonEditFn, {
|
|||
* */
|
||||
|
||||
function jsonEdit(jsonq = '') {
|
||||
jsonEditFn(false, jsonq);
|
||||
editOutboundObjectFn(false, 'JSON.parse', jsonq);
|
||||
}
|
||||
registerScriptlet(jsonEdit, {
|
||||
name: 'json-edit.js',
|
||||
dependencies: [
|
||||
jsonEditFn,
|
||||
editOutboundObjectFn,
|
||||
],
|
||||
});
|
||||
|
||||
|
|
@ -98,13 +153,13 @@ registerScriptlet(jsonEdit, {
|
|||
* */
|
||||
|
||||
function trustedJsonEdit(jsonq = '') {
|
||||
jsonEditFn(true, jsonq);
|
||||
editOutboundObjectFn(true, 'JSON.parse', jsonq);
|
||||
}
|
||||
registerScriptlet(trustedJsonEdit, {
|
||||
name: 'trusted-json-edit.js',
|
||||
requiresTrust: true,
|
||||
dependencies: [
|
||||
jsonEditFn,
|
||||
editOutboundObjectFn,
|
||||
],
|
||||
});
|
||||
|
||||
|
|
@ -242,6 +297,124 @@ registerScriptlet(trustedJsonEditXhrResponse, {
|
|||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
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(
|
||||
|
|
@ -372,6 +545,131 @@ registerScriptlet(trustedJsonEditFetchResponse, {
|
|||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
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';
|
||||
|
|
|
|||
Loading…
Reference in a new issue