-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Squashed commit of the following: commit 18a5a61 Merge: 93efce6 0976bb9 Author: Slava Leleka <v.leleka@adguard.com> Date: Tue Oct 18 00:57:30 2022 +0300 Merge branch 'feature/AG-16687_01' of ssh://bit.adguard.com:7999/adguard-filters/scriptlets into feature/AG-16687_01 commit 93efce6 Merge: 10ee63d 66b417a Author: Slava Leleka <v.leleka@adguard.com> Date: Tue Oct 18 00:57:12 2022 +0300 merge release/v1.7 into feature/AG-16687_01, resolve conflicts commit 0976bb9 Author: Adam <adam@adguard.com> Date: Fri Oct 14 18:47:47 2022 +0200 Remove unnecessary variable commit 10ee63d Author: Adam <adam@adguard.com> Date: Fri Oct 14 14:51:54 2022 +0200 Return original value if response is not pruned Fix tests commit 0053d0c Author: Adam <adam@adguard.com> Date: Fri Oct 14 11:40:53 2022 +0200 Remove unnecessary conditional statement commit 930dd6a Merge: fe26660 cfa9570 Author: Adam <adam@adguard.com> Date: Thu Oct 13 12:41:31 2022 +0200 Merge branch 'release/v1.7' into feature/AG-16687_01 commit fe26660 Author: Adam <adam@adguard.com> Date: Thu Oct 13 12:40:41 2022 +0200 Add common constant GET_METHOD for tests Remove unnecessary MPD_PATH commit 63c4bef Author: Adam <adam@adguard.com> Date: Wed Oct 12 14:03:21 2022 +0200 Rename url to urlMatchRegexp commit e43bd18 Author: Adam <adam@adguard.com> Date: Wed Oct 12 13:46:21 2022 +0200 Avoid regexp Improve log Add a note about usage without propsToMatch Change realFetch to nativeFetch Do not reassign input variable Add validity of xmlDoc Get rid of try...catch Remove unnecessary spaces in test file Fix endsWith function commit 8185ae3 Author: Adam <adam@adguard.com> Date: Mon Oct 10 14:33:28 2022 +0200 Remove unnecessary hit function Rename shouldLog to shouldPruneResponse commit cf1f380 Author: Adam <adam@adguard.com> Date: Fri Oct 7 17:47:07 2022 +0200 Update description commit f358b8a Author: Adam <adam@adguard.com> Date: Fri Oct 7 14:36:48 2022 +0200 Rename checkIfXML functin to isXML commit c197e0a Author: Adam <adam@adguard.com> Date: Fri Oct 7 14:28:45 2022 +0200 Add ability to log response in a browser console Add tests for logging Use assert.notOk Improve description Remove unnecessary hit function Simplify test file Add removeEventListener() method Improve function name (pruneXML) Add checkIfXML function and comment to it Check if Reflect is supported Add eslint-enable max-len Use indexOf() instead of includes() in tests Add more aliases commit 66b76c0 Author: Adam <adam@adguard.com> Date: Tue Oct 4 10:19:31 2022 +0200 Add new scriptlet - `xml-prune`
- Loading branch information
Showing
7 changed files
with
622 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,217 @@ | ||
import { | ||
hit, | ||
toRegExp, | ||
startsWith, | ||
endsWith, | ||
} from '../helpers/index'; | ||
|
||
/* eslint-disable max-len */ | ||
/** | ||
* @scriptlet xml-prune | ||
* | ||
* @description | ||
* Removes an element from the specified XML. | ||
* | ||
* | ||
* **Syntax** | ||
* ``` | ||
* example.org#%#//scriptlet('xml-prune'[, propsToMatch[, optionalProp[, urlToMatch]]]) | ||
* ``` | ||
* | ||
* - `propsToMatch` - optional, selector of elements which will be removed from XML | ||
* - `optionalProp` - optional, selector of elements that must occur in XML document | ||
* - `urlToMatch` - optional, string or regular expression for matching the request's URL | ||
* > Usage with no arguments will log response payload and URL to browser console; | ||
* which is useful for debugging but prohibited for production filter lists. | ||
* | ||
* **Examples** | ||
* 1. Remove `Period` tag whose `id` contains `-ad-` from all requests | ||
* ``` | ||
* example.org#%#//scriptlet('xml-prune', 'Period[id*="-ad-"]') | ||
* ``` | ||
* | ||
* 2. Remove `Period` tag whose `id` contains `-ad-`, only if XML contains `SegmentTemplate` | ||
* ``` | ||
* example.org#%#//scriptlet('xml-prune', 'Period[id*="-ad-"]', 'SegmentTemplate') | ||
* ``` | ||
* | ||
* 3. Remove `Period` tag whose `id` contains `-ad-`, only if request's URL contains `.mpd` | ||
* ``` | ||
* example.org#%#//scriptlet('xml-prune', 'Period[id*="-ad-"]', '', '.mpd') | ||
* ``` | ||
* | ||
* 4. Call with no arguments will log response payload and URL at the console | ||
* ``` | ||
* example.org#%#//scriptlet('xml-prune') | ||
* ``` | ||
* | ||
* 5. Call with only `urlToMatch` argument will log response payload and URL only for the matched URL | ||
* ``` | ||
* example.org#%#//scriptlet('xml-prune', '', '', '.mpd') | ||
* ``` | ||
*/ | ||
/* eslint-enable max-len */ | ||
|
||
export function xmlPrune(source, propsToRemove, optionalProp = '', urlToMatch) { | ||
// do nothing if browser does not support Reflect, fetch or Proxy (e.g. Internet Explorer) | ||
// https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch | ||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy | ||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect | ||
if (typeof Reflect === 'undefined' | ||
|| typeof fetch === 'undefined' | ||
|| typeof Proxy === 'undefined' | ||
|| typeof Response === 'undefined') { | ||
return; | ||
} | ||
|
||
let shouldPruneResponse = true; | ||
// eslint-disable-next-line no-console | ||
const log = console.log.bind(console); | ||
if (!propsToRemove) { | ||
// If "propsToRemove" is not defined, then response shouldn't be pruned | ||
// but it should be logged in browser console | ||
shouldPruneResponse = false; | ||
} | ||
|
||
const urlMatchRegexp = toRegExp(urlToMatch); | ||
|
||
const isXML = (text) => { | ||
// Check if "text" starts with "<" and check if it ends with ">" | ||
// If so, then it might be an XML file and should be pruned or logged | ||
const trimedText = text.trim(); | ||
if (startsWith(trimedText, '<') && endsWith(trimedText, '>')) { | ||
return true; | ||
} | ||
return false; | ||
}; | ||
|
||
const pruneXML = (text) => { | ||
if (!isXML(text)) { | ||
shouldPruneResponse = false; | ||
return text; | ||
} | ||
const xmlParser = new DOMParser(); | ||
const xmlDoc = xmlParser.parseFromString(text, 'text/xml'); | ||
const errorNode = xmlDoc.querySelector('parsererror'); | ||
if (errorNode) { | ||
return text; | ||
} | ||
if (optionalProp !== '' && xmlDoc.querySelector(optionalProp) === null) { | ||
shouldPruneResponse = false; | ||
return text; | ||
} | ||
const elems = xmlDoc.querySelectorAll(propsToRemove); | ||
if (!elems.length) { | ||
shouldPruneResponse = false; | ||
return text; | ||
} | ||
elems.forEach((elem) => { | ||
elem.remove(); | ||
}); | ||
const serializer = new XMLSerializer(); | ||
text = serializer.serializeToString(xmlDoc); | ||
return text; | ||
}; | ||
|
||
const xhrWrapper = (target, thisArg, args) => { | ||
const xhrURL = args[1]; | ||
if (typeof xhrURL !== 'string' || xhrURL.length === 0) { | ||
return Reflect.apply(target, thisArg, args); | ||
} | ||
if (urlMatchRegexp.test(xhrURL)) { | ||
thisArg.addEventListener('readystatechange', function pruneResponse() { | ||
if (thisArg.readyState === 4) { | ||
const { response } = thisArg; | ||
thisArg.removeEventListener('readystatechange', pruneResponse); | ||
if (!shouldPruneResponse) { | ||
if (isXML(response)) { | ||
log(`XMLHttpRequest.open() URL: ${xhrURL}\nresponse: ${response}`); | ||
} | ||
} else { | ||
const prunedResponseContent = pruneXML(response); | ||
if (shouldPruneResponse) { | ||
Object.defineProperty(thisArg, 'response', { | ||
value: prunedResponseContent, | ||
}); | ||
Object.defineProperty(thisArg, 'responseText', { | ||
value: prunedResponseContent, | ||
}); | ||
hit(source); | ||
} | ||
// In case if response shouldn't be pruned | ||
// pruneXML sets shouldPruneResponse to false | ||
// so it's necessary to set it to true again | ||
// otherwise response will be only logged | ||
shouldPruneResponse = true; | ||
} | ||
} | ||
}); | ||
} | ||
return Reflect.apply(target, thisArg, args); | ||
}; | ||
|
||
const xhrHandler = { | ||
apply: xhrWrapper, | ||
}; | ||
// eslint-disable-next-line max-len | ||
window.XMLHttpRequest.prototype.open = new Proxy(window.XMLHttpRequest.prototype.open, xhrHandler); | ||
|
||
// eslint-disable-next-line compat/compat | ||
const nativeFetch = window.fetch; | ||
|
||
const fetchWrapper = (target, thisArg, args) => { | ||
const fetchURL = args[0]; | ||
if (typeof fetchURL !== 'string' || fetchURL.length === 0) { | ||
return Reflect.apply(target, thisArg, args); | ||
} | ||
if (urlMatchRegexp.test(fetchURL)) { | ||
return nativeFetch.apply(this, args).then((response) => { | ||
return response.text().then((text) => { | ||
if (!shouldPruneResponse) { | ||
if (isXML(text)) { | ||
log(`fetch URL: ${fetchURL}\nresponse text: ${text}`); | ||
} | ||
return Reflect.apply(target, thisArg, args); | ||
} | ||
const prunedText = pruneXML(text); | ||
if (shouldPruneResponse) { | ||
hit(source); | ||
return new Response(prunedText, { | ||
status: response.status, | ||
statusText: response.statusText, | ||
headers: response.headers, | ||
}); | ||
} | ||
// In case if response shouldn't be pruned | ||
// pruneXML sets shouldPruneResponse to false | ||
// so it's necessary to set it to true again | ||
// otherwise response will be only logged | ||
shouldPruneResponse = true; | ||
return Reflect.apply(target, thisArg, args); | ||
}); | ||
}); | ||
} | ||
return Reflect.apply(target, thisArg, args); | ||
}; | ||
|
||
const fetchHandler = { | ||
apply: fetchWrapper, | ||
}; | ||
// eslint-disable-next-line compat/compat | ||
window.fetch = new Proxy(window.fetch, fetchHandler); | ||
} | ||
|
||
xmlPrune.names = [ | ||
'xml-prune', | ||
// aliases are needed for matching the related scriptlet converted into our syntax | ||
'xml-prune.js', | ||
'ubo-xml-prune.js', | ||
'ubo-xml-prune', | ||
]; | ||
|
||
xmlPrune.injections = [ | ||
hit, | ||
toRegExp, | ||
startsWith, | ||
endsWith, | ||
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:_XMLSchema-instance="http://www.w3.org/2001/XMLSchema-instance" _XMLSchema-instance:schemaLocation="urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd" profiles="urn:mpeg:dash:profile:isoff-live:2011" type="static" mediaPresentationDuration="PT49M2.272666664S" minBufferTime="PT2S"> | ||
<BaseURL>https://vod-gcs-cedexis.cbsaavideo.com/intl_vms/2017/02/17/879659075884/609941_cenc_precon_dash/</BaseURL> | ||
<Period id="pre-roll-1-ad-1" duration="PT20.02S"> | ||
<BaseURL>https://dai.google.com/segments/redirect/c/</BaseURL> | ||
</Period> | ||
<Period id="0" duration="PT21M23.282S"> | ||
<AdaptationSet id="0" lang="en" contentType="text" segmentAlignment="true"> | ||
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="subtitle"/> | ||
</AdaptationSet> | ||
</Period> | ||
<Period id="1" duration="PT12M48.768S"> | ||
<AdaptationSet id="0" lang="en" contentType="text" segmentAlignment="true"> | ||
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="subtitle"/> | ||
</AdaptationSet> | ||
</Period> | ||
<Period id="2" duration="PT6M26.385999999S"> | ||
<AdaptationSet id="0" lang="en" contentType="text" segmentAlignment="true"> | ||
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="subtitle"/> | ||
</AdaptationSet> | ||
</Period> | ||
<Period id="3" duration="PT7M12.431999999S"> | ||
<AdaptationSet id="0" lang="en" contentType="text" segmentAlignment="true"> | ||
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="subtitle"/> | ||
</AdaptationSet> | ||
</Period> | ||
<Period id="4" duration="PT51.384666666S"> | ||
<AdaptationSet id="0" lang="en" contentType="text" segmentAlignment="true"> | ||
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="subtitle"/> | ||
</AdaptationSet> | ||
</Period> | ||
</MPD> |
Oops, something went wrong.