Skip to content

Commit 3152896

Browse files
committed
Add json-prune-xhr-response and trusted-replace-xhr-response scriptlets
As discussed with filter list maintainers. Related issue: uBlockOrigin/uBlock-issues#2743
1 parent eeafae1 commit 3152896

File tree

1 file changed

+208
-2
lines changed

1 file changed

+208
-2
lines changed

assets/resources/scriptlets.js

Lines changed: 208 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1291,8 +1291,8 @@ function jsonPruneFetchResponse(
12911291
rawNeedlePaths,
12921292
{ matchAll: true },
12931293
extraArgs
1294-
);
1295-
if ( typeof objAfter !== 'object' ) { return responseBefore; }
1294+
);
1295+
if ( typeof objAfter !== 'object' ) { return responseBefore; }
12961296
const responseAfter = Response.json(objAfter, {
12971297
status: responseBefore.status,
12981298
statusText: responseBefore.statusText,
@@ -1321,6 +1321,113 @@ function jsonPruneFetchResponse(
13211321

13221322
/******************************************************************************/
13231323

1324+
builtinScriptlets.push({
1325+
name: 'json-prune-xhr-response.js',
1326+
fn: jsonPruneXhrResponse,
1327+
dependencies: [
1328+
'match-object-properties.fn',
1329+
'object-prune.fn',
1330+
'parse-properties-to-match.fn',
1331+
'safe-self.fn',
1332+
'should-log.fn',
1333+
],
1334+
});
1335+
function jsonPruneXhrResponse(
1336+
rawPrunePaths = '',
1337+
rawNeedlePaths = ''
1338+
) {
1339+
const safe = safeSelf();
1340+
const xhrInstances = new WeakMap();
1341+
const extraArgs = safe.getExtraArgs(Array.from(arguments), 2);
1342+
const logLevel = shouldLog({ log: rawPrunePaths === '' || extraArgs.log, });
1343+
const log = logLevel ? ((...args) => { safe.uboLog(...args); }) : (( ) => { });
1344+
const propNeedles = parsePropertiesToMatch(extraArgs.propsToMatch, 'url');
1345+
self.XMLHttpRequest = class extends self.XMLHttpRequest {
1346+
open(method, url, ...args) {
1347+
const outerXhr = this;
1348+
const xhrDetails = { method, url, args: [ ...args ] };
1349+
let outcome = 'match';
1350+
if ( propNeedles.size !== 0 ) {
1351+
if ( matchObjectProperties(propNeedles, { method, url }) === false ) {
1352+
outcome = 'nomatch';
1353+
}
1354+
}
1355+
if ( outcome === logLevel || outcome === 'all' ) {
1356+
log(`xhr.open(${method}, ${url}, ${args.join(', ')})`);
1357+
}
1358+
if ( outcome === 'match' ) {
1359+
xhrInstances.set(outerXhr, xhrDetails);
1360+
}
1361+
return super.open(method, url, ...args);
1362+
}
1363+
send(...args) {
1364+
const outerXhr = this;
1365+
const xhrDetails = xhrInstances.get(outerXhr);
1366+
if ( xhrDetails === undefined ) {
1367+
return super.send(...args);
1368+
}
1369+
switch ( outerXhr.responseType ) {
1370+
case '':
1371+
case 'json':
1372+
case 'text':
1373+
break;
1374+
default:
1375+
return super.send(...args);
1376+
}
1377+
const innerXhr = new safe.XMLHttpRequest();
1378+
innerXhr.open(xhrDetails.method, xhrDetails.url, ...xhrDetails.args);
1379+
innerXhr.responseType = outerXhr.responseType;
1380+
innerXhr.onloadend = function() {
1381+
let objBefore;
1382+
switch ( outerXhr.responseType ) {
1383+
case '':
1384+
case 'text':
1385+
try {
1386+
objBefore = safe.jsonParse(innerXhr.responseText);
1387+
} catch(ex) {
1388+
}
1389+
break;
1390+
case 'json':
1391+
objBefore = innerXhr.response;
1392+
break;
1393+
default:
1394+
break;
1395+
}
1396+
let objAfter;
1397+
if ( typeof objBefore === 'object' ) {
1398+
objAfter = objectPrune(
1399+
objBefore,
1400+
rawPrunePaths,
1401+
rawNeedlePaths,
1402+
{ matchAll: true },
1403+
extraArgs
1404+
);
1405+
}
1406+
let response = objAfter || objBefore;
1407+
let responseText = '';
1408+
if ( typeof response === 'object' && outerXhr.responseType !== 'json' ) {
1409+
response = responseText = safe.jsonStringify(response);
1410+
}
1411+
Object.defineProperties(outerXhr, {
1412+
readyState: { value: 4 },
1413+
response: { value: response },
1414+
responseText: { value: responseText },
1415+
responseXML: { value: null },
1416+
responseURL: { value: innerXhr.url },
1417+
status: { value: innerXhr.status },
1418+
statusText: { value: innerXhr.statusText },
1419+
});
1420+
outerXhr.dispatchEvent(new Event('readystatechange'));
1421+
outerXhr.dispatchEvent(new Event('load'));
1422+
outerXhr.dispatchEvent(new Event('loadend'));
1423+
};
1424+
innerXhr.send(...args);
1425+
}
1426+
};
1427+
}
1428+
1429+
/******************************************************************************/
1430+
13241431
// There is still code out there which uses `eval` in lieu of `JSON.parse`.
13251432

13261433
builtinScriptlets.push({
@@ -3622,3 +3729,102 @@ function trustedReplaceFetchResponse(
36223729
}
36233730

36243731
/******************************************************************************/
3732+
3733+
builtinScriptlets.push({
3734+
name: 'trusted-replace-xhr-response.js',
3735+
fn: trustedReplaceXhrResponse,
3736+
dependencies: [
3737+
'match-object-properties.fn',
3738+
'parse-properties-to-match.fn',
3739+
'safe-self.fn',
3740+
'should-log.fn',
3741+
],
3742+
});
3743+
function trustedReplaceXhrResponse(
3744+
pattern = '',
3745+
replacement = '',
3746+
propsToMatch = ''
3747+
) {
3748+
const safe = safeSelf();
3749+
const xhrInstances = new WeakMap();
3750+
const extraArgs = safe.getExtraArgs(Array.from(arguments), 3);
3751+
const logLevel = shouldLog({
3752+
log: pattern === '' && 'all' || extraArgs.log,
3753+
});
3754+
const log = logLevel ? ((...args) => { safe.uboLog(...args); }) : (( ) => { });
3755+
if ( pattern === '*' ) { pattern = '.*'; }
3756+
const rePattern = safe.patternToRegex(pattern);
3757+
const propNeedles = parsePropertiesToMatch(propsToMatch, 'url');
3758+
self.XMLHttpRequest = class extends self.XMLHttpRequest {
3759+
open(method, url, ...args) {
3760+
const outerXhr = this;
3761+
const xhrDetails = { method, url, args: [ ...args ] };
3762+
let outcome = 'match';
3763+
if ( propNeedles.size !== 0 ) {
3764+
if ( matchObjectProperties(propNeedles, { method, url }) === false ) {
3765+
outcome = 'nomatch';
3766+
}
3767+
}
3768+
if ( outcome === logLevel || outcome === 'all' ) {
3769+
log(`xhr.open(${method}, ${url}, ${args.join(', ')})`);
3770+
}
3771+
if ( outcome === 'match' ) {
3772+
xhrInstances.set(outerXhr, xhrDetails);
3773+
}
3774+
return super.open(method, url, ...args);
3775+
}
3776+
send(...args) {
3777+
const outerXhr = this;
3778+
const xhrDetails = xhrInstances.get(outerXhr);
3779+
if ( xhrDetails === undefined ) {
3780+
return super.send(...args);
3781+
}
3782+
switch ( outerXhr.responseType ) {
3783+
case '':
3784+
case 'json':
3785+
case 'text':
3786+
break;
3787+
default:
3788+
return super.send(...args);
3789+
}
3790+
const innerXhr = new safe.XMLHttpRequest();
3791+
innerXhr.open(xhrDetails.method, xhrDetails.url, ...xhrDetails.args);
3792+
innerXhr.responseType = outerXhr.responseType;
3793+
innerXhr.onloadend = function() {
3794+
const textBefore = innerXhr.responseText;
3795+
const textAfter = textBefore.replace(rePattern, replacement);
3796+
const outcome = textAfter !== textBefore ? 'match' : 'nomatch';
3797+
if ( outcome === logLevel || logLevel === 'all' ) {
3798+
log(
3799+
`trusted-replace-xhr-response (${outcome})`,
3800+
`\n\tpattern: ${pattern}`,
3801+
`\n\treplacement: ${replacement}`,
3802+
);
3803+
}
3804+
let response = innerXhr.responseText;
3805+
if ( outerXhr.responseType === 'json' ) {
3806+
try {
3807+
response = safe.jsonParse(innerXhr.responseText);
3808+
} catch(ex) {
3809+
response = innerXhr.response;
3810+
}
3811+
}
3812+
Object.defineProperties(outerXhr, {
3813+
readyState: { value: 4 },
3814+
response: { value: response },
3815+
responseText: { value: textAfter },
3816+
responseXML: { value: null },
3817+
responseURL: { value: innerXhr.url },
3818+
status: { value: innerXhr.status },
3819+
statusText: { value: innerXhr.statusText },
3820+
});
3821+
outerXhr.dispatchEvent(new Event('readystatechange'));
3822+
outerXhr.dispatchEvent(new Event('load'));
3823+
outerXhr.dispatchEvent(new Event('loadend'));
3824+
};
3825+
innerXhr.send(...args);
3826+
}
3827+
};
3828+
}
3829+
3830+
/******************************************************************************/

0 commit comments

Comments
 (0)