 Issue 29452289:
  Issue 5283 - Add support for $websocket and $webrtc  (Closed) 
  Base URL: https://hg.adblockplus.org/abp2blocklist
    
  
    Issue 29452289:
  Issue 5283 - Add support for $websocket and $webrtc  (Closed) 
  Base URL: https://hg.adblockplus.org/abp2blocklist| Index: lib/abp2blocklist.js | 
| =================================================================== | 
| --- a/lib/abp2blocklist.js | 
| +++ b/lib/abp2blocklist.js | 
| @@ -28,16 +28,18 @@ | 
| | typeMap.STYLESHEET | 
| | typeMap.SCRIPT | 
| | typeMap.FONT | 
| | typeMap.MEDIA | 
| | typeMap.POPUP | 
| | typeMap.OBJECT | 
| | typeMap.OBJECT_SUBREQUEST | 
| | typeMap.XMLHTTPREQUEST | 
| + | typeMap.WEBSOCKET | 
| + | typeMap.WEBRTC | 
| | typeMap.PING | 
| | typeMap.SUBDOCUMENT | 
| | typeMap.OTHER); | 
| function parseDomains(domains, included, excluded) | 
| { | 
| for (let domain in domains) | 
| { | 
| @@ -59,16 +61,27 @@ | 
| return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); | 
| } | 
| function matchDomain(domain) | 
| { | 
| return "^https?://([^/:]*\\.)?" + escapeRegExp(domain).toLowerCase() + "[/:]"; | 
| } | 
| +function getURLSchemes(contentType) | 
| +{ | 
| + if (contentType == typeMap.WEBSOCKET) | 
| + return ["wss?://"]; | 
| + | 
| + if (contentType == typeMap.WEBRTC) | 
| + return ["stuns?:", "turns?:"]; | 
| + | 
| + return ["https?://"]; | 
| +} | 
| + | 
| function findSubdomainsInList(domain, list) | 
| { | 
| let subdomains = []; | 
| let suffixLength = domain.length + 1; | 
| for (let name of list) | 
| { | 
| if (name.length > suffixLength && name.slice(-suffixLength) == "." + domain) | 
| @@ -92,35 +105,39 @@ | 
| /** | 
| * Parse the given filter "regexpSource" string. Producing a regular expression, | 
| * extracting the hostname (if any), deciding if the regular expression is safe | 
| * to be converted + matched as lower case and noting if the source contains | 
| * anything after the hostname.) | 
| * | 
| * @param {string} text regexpSource property of a filter | 
| + * @param {string} urlScheme The URL scheme to use in the regular expression | 
| * @returns {object} An object containing a regular expression string, a bool | 
| * indicating if the filter can be safely matched as lower | 
| * case, a hostname string (or undefined) and a bool | 
| * indicating if the source only contains a hostname or not: | 
| * {regexp: "...", | 
| * canSafelyMatchAsLowercase: true/false, | 
| * hostname: "...", | 
| * justHostname: true/false} | 
| */ | 
| -function parseFilterRegexpSource(text) | 
| +function parseFilterRegexpSource(text, urlScheme) | 
| { | 
| let regexp = []; | 
| let lastIndex = text.length - 1; | 
| let hostname; | 
| let hostnameStart = null; | 
| let hostnameFinished = false; | 
| let justHostname = false; | 
| let canSafelyMatchAsLowercase = false; | 
| + if (!urlScheme) | 
| + urlScheme = getURLSchemes()[0]; | 
| + | 
| for (let i = 0; i < text.length; i++) | 
| { | 
| let c = text[i]; | 
| if (hostnameFinished) | 
| justHostname = false; | 
| // If we're currently inside the hostname we have to be careful not to | 
| @@ -161,17 +178,17 @@ | 
| { | 
| regexp.push("$"); | 
| break; | 
| } | 
| if (i == 1 && text[0] == "|") | 
| { | 
| hostnameStart = i + 1; | 
| canSafelyMatchAsLowercase = true; | 
| - regexp.push("https?://([^/]+\\.)?"); | 
| + regexp.push(urlScheme + "([^/]+\\.)?"); | 
| break; | 
| } | 
| regexp.push("\\|"); | 
| break; | 
| case "/": | 
| if (!hostnameFinished && | 
| text.charAt(i-2) == ":" && text.charAt(i-1) == "/") | 
| { | 
| @@ -188,81 +205,97 @@ | 
| default: | 
| if (hostnameFinished && (c >= "a" && c <= "z" || | 
| c >= "A" && c <= "Z")) | 
| canSafelyMatchAsLowercase = false; | 
| regexp.push(c); | 
| } | 
| } | 
| + if (regexp.length == 0 || regexp[0] != "^") | 
| + regexp.unshift("^" + urlScheme + ".*"); | 
| + | 
| return { | 
| regexp: regexp.join(""), | 
| canSafelyMatchAsLowercase: canSafelyMatchAsLowercase, | 
| hostname: hostname, | 
| justHostname: justHostname | 
| }; | 
| } | 
| -function getResourceTypes(filter) | 
| +function getResourceTypes(contentType) | 
| { | 
| let types = []; | 
| - if (filter.contentType & typeMap.IMAGE) | 
| + if (contentType & typeMap.IMAGE) | 
| types.push("image"); | 
| - if (filter.contentType & typeMap.STYLESHEET) | 
| + if (contentType & typeMap.STYLESHEET) | 
| types.push("style-sheet"); | 
| - if (filter.contentType & typeMap.SCRIPT) | 
| + if (contentType & typeMap.SCRIPT) | 
| types.push("script"); | 
| - if (filter.contentType & typeMap.FONT) | 
| + if (contentType & typeMap.FONT) | 
| types.push("font"); | 
| - if (filter.contentType & (typeMap.MEDIA | typeMap.OBJECT)) | 
| + if (contentType & (typeMap.MEDIA | typeMap.OBJECT)) | 
| types.push("media"); | 
| - if (filter.contentType & typeMap.POPUP) | 
| + if (contentType & typeMap.POPUP) | 
| types.push("popup"); | 
| - if (filter.contentType & (typeMap.XMLHTTPREQUEST | | 
| + if (contentType & (typeMap.XMLHTTPREQUEST | | 
| + typeMap.WEBSOCKET | | 
| + typeMap.WEBRTC | | 
| typeMap.OBJECT_SUBREQUEST | | 
| typeMap.PING | | 
| typeMap.OTHER)) | 
| + { | 
| types.push("raw"); | 
| - if (filter.contentType & typeMap.SUBDOCUMENT) | 
| + } | 
| + if (contentType & typeMap.SUBDOCUMENT) | 
| types.push("document"); | 
| return types; | 
| } | 
| function convertFilterAddRules(rules, filter, action, withResourceTypes, | 
| exceptionDomains) | 
| { | 
| - let parsed = parseFilterRegexpSource(filter.regexpSource); | 
| + let contentType = filter.contentType; | 
| + | 
| + // Support WebSocket and WebRTC only if they're the only option. If we try to | 
| + // support them otherwise (e.g. $xmlhttprequest,websocket,webrtc), we end up | 
| + // having to generate multiple rules, which bloats the rule set and is not | 
| + // really necessary in practice. | 
| + if ((contentType & typeMap.WEBSOCKET && contentType != typeMap.WEBSOCKET) || | 
| + (contentType & typeMap.WEBRTC && contentType != typeMap.WEBRTC)) | 
| + { | 
| + contentType &= ~(typeMap.WEBSOCKET | typeMap.WEBRTC); | 
| + } | 
| + | 
| + let urlSchemes = getURLSchemes(contentType); | 
| + let parsed = parseFilterRegexpSource(filter.regexpSource, urlSchemes[0]); | 
| // For the special case of $document whitelisting filters with just a domain | 
| // we can generate an equivalent blocking rule exception using if-domain. | 
| if (filter instanceof filterClasses.WhitelistFilter && | 
| - filter.contentType & typeMap.DOCUMENT && | 
| + contentType & typeMap.DOCUMENT && | 
| parsed.justHostname) | 
| { | 
| rules.push({ | 
| trigger: { | 
| "url-filter": ".*", | 
| "if-domain": ["*" + parsed.hostname] | 
| }, | 
| action: {type: "ignore-previous-rules"} | 
| }); | 
| // If the filter contains other supported options we'll need to generate | 
| // further rules for it, but if not we can simply return now. | 
| - if (!(filter.contentType & whitelistableRequestTypes)) | 
| + if (!(contentType & whitelistableRequestTypes)) | 
| return; | 
| } | 
| let trigger = {"url-filter": parsed.regexp}; | 
| - // Limit rules to HTTP(S) URLs | 
| 
Manish Jethani
2017/05/31 02:51:43
This has been moved into parseFilterRegexpSource.
 | 
| - if (!/^(\^|http)/i.test(trigger["url-filter"])) | 
| - trigger["url-filter"] = "^https?://.*" + trigger["url-filter"]; | 
| - | 
| // For rules containing only a hostname we know that we're matching against | 
| // a lowercase string unless the matchCase option was passed. | 
| if (parsed.canSafelyMatchAsLowercase && !filter.matchCase) | 
| trigger["url-filter"] = trigger["url-filter"].toLowerCase(); | 
| if (parsed.canSafelyMatchAsLowercase || filter.matchCase) | 
| trigger["url-filter-is-case-sensitive"] = true; | 
| @@ -271,17 +304,17 @@ | 
| parseDomains(filter.domains, included, excluded); | 
| if (exceptionDomains) | 
| excluded = excluded.concat(exceptionDomains); | 
| if (withResourceTypes) | 
| { | 
| - trigger["resource-type"] = getResourceTypes(filter); | 
| + trigger["resource-type"] = getResourceTypes(contentType); | 
| if (trigger["resource-type"].length == 0) | 
| return; | 
| } | 
| if (filter.thirdParty != null) | 
| trigger["load-type"] = [filter.thirdParty ? "third-party" : "first-party"]; | 
| @@ -311,16 +344,34 @@ | 
| } | 
| } | 
| else if (excluded.length > 0) | 
| { | 
| trigger["unless-domain"] = excluded.map(name => "*" + name); | 
| } | 
| rules.push({trigger: trigger, action: {type: action}}); | 
| + | 
| + // Generate additional rules for any alternative URL schemes. | 
| + if (urlSchemes.length > 1 && | 
| + trigger["url-filter"].startsWith("^" + urlSchemes[0])) | 
| + { | 
| + // Always make a deep copy of the rule, since rules may have to be | 
| + // manipulated individually at a later stage. | 
| + let stringifiedTrigger = JSON.stringify(trigger); | 
| + | 
| + for (let i = 1; i < urlSchemes.length; i++) | 
| + { | 
| + let altTrigger = Object.assign(JSON.parse(stringifiedTrigger), { | 
| + "url-filter": "^" + urlSchemes[i] + | 
| + trigger["url-filter"].substring(urlSchemes[0].length + 1) | 
| + }); | 
| + rules.push({trigger: altTrigger, action: {type: action}}); | 
| + } | 
| + } | 
| } | 
| function hasNonASCI(obj) | 
| { | 
| if (typeof obj == "string") | 
| { | 
| if (/[^\x00-\x7F]/.test(obj)) | 
| return true; |