| LEFT | RIGHT | 
|    1 /* |    1 /* | 
|    2  * This file is part of Adblock Plus <https://adblockplus.org/>, |    2  * This file is part of Adblock Plus <https://adblockplus.org/>, | 
|    3  * Copyright (C) 2006-2015 Eyeo GmbH |    3  * Copyright (C) 2006-2016 Eyeo GmbH | 
|    4  * |    4  * | 
|    5  * Adblock Plus is free software: you can redistribute it and/or modify |    5  * Adblock Plus is free software: you can redistribute it and/or modify | 
|    6  * it under the terms of the GNU General Public License version 3 as |    6  * it under the terms of the GNU General Public License version 3 as | 
|    7  * published by the Free Software Foundation. |    7  * published by the Free Software Foundation. | 
|    8  * |    8  * | 
|    9  * Adblock Plus is distributed in the hope that it will be useful, |    9  * Adblock Plus is distributed in the hope that it will be useful, | 
|   10  * but WITHOUT ANY WARRANTY; without even the implied warranty of |   10  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|   11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the |   11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
|   12  * GNU General Public License for more details. |   12  * GNU General Public License for more details. | 
|   13  * |   13  * | 
|   14  * You should have received a copy of the GNU General Public License |   14  * You should have received a copy of the GNU General Public License | 
|   15  * along with Adblock Plus.  If not, see <http://www.gnu.org/licenses/>. |   15  * along with Adblock Plus.  If not, see <http://www.gnu.org/licenses/>. | 
|   16  */ |   16  */ | 
|   17  |   17  | 
|   18 /** |   18 /** | 
|   19  * @fileOverview Content policy implementation, responsible for blocking things. |   19  * @fileOverview Content policy implementation, responsible for blocking things. | 
|   20  */ |   20  */ | 
|   21  |   21  | 
|   22 "use strict"; |   22 "use strict"; | 
|   23  |   23  | 
|   24 let {XPCOMUtils} = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {}); |   24 let {XPCOMUtils} = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {}); | 
|   25 let {Services} = Cu.import("resource://gre/modules/Services.jsm", {}); |   25 let {Services} = Cu.import("resource://gre/modules/Services.jsm", {}); | 
|   26 let {PrivateBrowsingUtils} = Cu.import("resource://gre/modules/PrivateBrowsingUt
     ils.jsm", {}); |  | 
|   27  |   26  | 
|   28 let {Utils} = require("utils"); |   27 let {Utils} = require("utils"); | 
 |   28 let {port} = require("messaging"); | 
|   29 let {Prefs} = require("prefs"); |   29 let {Prefs} = require("prefs"); | 
|   30 let {FilterStorage} = require("filterStorage"); |   30 let {FilterStorage} = require("filterStorage"); | 
|   31 let {BlockingFilter, WhitelistFilter, RegExpFilter} = require("filterClasses"); |   31 let {BlockingFilter, WhitelistFilter, RegExpFilter} = require("filterClasses"); | 
|   32 let {defaultMatcher} = require("matcher"); |   32 let {defaultMatcher} = require("matcher"); | 
|   33 let {objectMouseEventHander} = require("objectTabs"); |  | 
|   34 let {RequestNotifier} = require("requestNotifier"); |  | 
|   35 let {ElemHide} = require("elemHide"); |   33 let {ElemHide} = require("elemHide"); | 
|   36  |  | 
|   37 /** |  | 
|   38  * Randomly generated class name, to be applied to collapsed nodes. |  | 
|   39  */ |  | 
|   40 let collapsedClass = ""; |  | 
|   41  |  | 
|   42 /** |  | 
|   43  * Maps numerical content type IDs to strings. |  | 
|   44  * @type Map |  | 
|   45  */ |  | 
|   46 let types = new Map(); |  | 
|   47  |   34  | 
|   48 /** |   35 /** | 
|   49  * Public policy checking functions and auxiliary objects |   36  * Public policy checking functions and auxiliary objects | 
|   50  * @class |   37  * @class | 
|   51  */ |   38  */ | 
|   52 var Policy = exports.Policy = |   39 var Policy = exports.Policy = | 
|   53 { |   40 { | 
|   54   /** |   41   /** | 
|   55    * Set of explicitly supported content types |   42    * Map of content types reported by Firefox to the respecitve content types | 
|   56    * @type Set |   43    * used by Adblock Plus. Other content types are simply mapped to OTHER. | 
|   57    */ |   44    * @type Map.<string,string> | 
|   58   contentTypes: new Set([ |   45    */ | 
|   59     "OTHER", "SCRIPT", "IMAGE", "STYLESHEET", "OBJECT", "SUBDOCUMENT", "DOCUMENT
     ", |   46   contentTypes: new Map(function* () | 
|   60     "XMLHTTPREQUEST", "OBJECT_SUBREQUEST", "FONT", "MEDIA", "ELEMHIDE", "POPUP", |   47   { | 
|   61     "GENERICHIDE", "GENERICBLOCK" |   48     // Treat navigator.sendBeacon() the same as <a ping>, | 
|   62   ]), |   49     // it's essentially the same concept - merely generalized. | 
 |   50     yield ["BEACON", "PING"]; | 
 |   51  | 
 |   52     // Treat <img srcset> and <picture> the same as other images. | 
 |   53     yield ["IMAGESET", "IMAGE"]; | 
 |   54  | 
 |   55     // Treat fetch() the same as XMLHttpRequest, | 
 |   56     // it's essentially the same - merely a more modern API. | 
 |   57     yield ["FETCH", "XMLHTTPREQUEST"]; | 
 |   58  | 
 |   59     // Everything else is mapped to itself | 
 |   60     for (let contentType of ["OTHER", "SCRIPT", "IMAGE", "STYLESHEET", "OBJECT", | 
 |   61                              "SUBDOCUMENT", "DOCUMENT", "XMLHTTPREQUEST", | 
 |   62                              "OBJECT_SUBREQUEST", "FONT", "MEDIA", "PING", | 
 |   63                              "ELEMHIDE", "POPUP", "GENERICHIDE", "GENERICBLOCK"]
     ) | 
 |   64       yield [contentType, contentType]; | 
 |   65   }()), | 
|   63  |   66  | 
|   64   /** |   67   /** | 
|   65    * Set of content types that aren't associated with a visual document area |   68    * Set of content types that aren't associated with a visual document area | 
|   66    * @type Set |   69    * @type Set.<string> | 
|   67    */ |   70    */ | 
|   68   nonVisualTypes: new Set([ |   71   nonVisualTypes: new Set([ | 
|   69     "SCRIPT", "STYLESHEET", "XMLHTTPREQUEST", "OBJECT_SUBREQUEST", "FONT", |   72     "SCRIPT", "STYLESHEET", "XMLHTTPREQUEST", "OBJECT_SUBREQUEST", "FONT", | 
|   70     "ELEMHIDE", "POPUP", "GENERICHIDE", "GENERICBLOCK" |   73     "PING", "ELEMHIDE", "POPUP", "GENERICHIDE", "GENERICBLOCK" | 
|   71   ]), |   74   ]), | 
|   72  |   75  | 
|   73   /** |   76   /** | 
|   74    * Map containing all schemes that should be ignored by content policy. |   77    * Map containing all schemes that should be ignored by content policy. | 
|   75    * @type Set |   78    * @type Set.<string> | 
|   76    */ |   79    */ | 
|   77   whitelistSchemes: new Set(), |   80   whitelistSchemes: new Set(), | 
|   78  |   81  | 
|   79   /** |   82   /** | 
|   80    * Called on module startup, initializes various exported properties. |   83    * Called on module startup, initializes various exported properties. | 
|   81    */ |   84    */ | 
|   82   init: function() |   85   init: function() | 
|   83   { |   86   { | 
|   84     // whitelisted URL schemes |   87     // whitelisted URL schemes | 
|   85     for (let scheme of Prefs.whitelistschemes.toLowerCase().split(" ")) |   88     for (let scheme of Prefs.whitelistschemes.toLowerCase().split(" ")) | 
|   86       this.whitelistSchemes.add(scheme); |   89       this.whitelistSchemes.add(scheme); | 
|   87  |   90  | 
|   88     // Generate class identifier used to collapse node and register correspondin
     g |   91     port.on("shouldAllow", (message, sender) => this.shouldAllow(message)); | 
|   89     // stylesheet. |   92  | 
 |   93     // Generate class identifier used to collapse nodes and register | 
 |   94     // corresponding stylesheet. | 
 |   95     let collapsedClass = ""; | 
|   90     let offset = "a".charCodeAt(0); |   96     let offset = "a".charCodeAt(0); | 
|   91     for (let i = 0; i < 20; i++) |   97     for (let i = 0; i < 20; i++) | 
|   92       collapsedClass +=  String.fromCharCode(offset + Math.random() * 26); |   98       collapsedClass +=  String.fromCharCode(offset + Math.random() * 26); | 
 |   99     port.on("getCollapsedClass", (message, sender) => collapsedClass); | 
|   93  |  100  | 
|   94     let collapseStyle = Services.io.newURI("data:text/css," + |  101     let collapseStyle = Services.io.newURI("data:text/css," + | 
|   95         encodeURIComponent("." + collapsedClass + |  102         encodeURIComponent("." + collapsedClass + | 
|   96         "{-moz-binding: url(chrome://global/content/bindings/general.xml#foobarb
     azdummy) !important;}"), null, null); |  103         "{-moz-binding: url(chrome://global/content/bindings/general.xml#foobarb
     azdummy) !important;}"), null, null); | 
|   97     Utils.styleService.loadAndRegisterSheet(collapseStyle, Ci.nsIStyleSheetServi
     ce.USER_SHEET); |  104     Utils.styleService.loadAndRegisterSheet(collapseStyle, Ci.nsIStyleSheetServi
     ce.USER_SHEET); | 
|   98     onShutdown.add(() => |  105     onShutdown.add(() => | 
|   99     { |  106     { | 
|  100       Utils.styleService.unregisterSheet(collapseStyle, Ci.nsIStyleSheetService.
     USER_SHEET); |  107       Utils.styleService.unregisterSheet(collapseStyle, Ci.nsIStyleSheetService.
     USER_SHEET); | 
|  101     }); |  108     }); | 
|  102   }, |  109   }, | 
|  103  |  110  | 
|  104   /** |  111   /** | 
|  105    * Checks whether a node should be blocked, hides it if necessary |  112    * Checks whether a node should be blocked, hides it if necessary | 
|  106    * @param wnd {nsIDOMWindow} |  113    * @param {Object} data  request data | 
|  107    * @param node {nsIDOMElement} |  114    * @param {String} data.contentType | 
|  108    * @param contentType {String} |  115    * @param {String} data.location  location of the request, filter key if conte
     ntType is ELEMHIDE | 
|  109    * @param location {String} |  116    * @param {Object[]} data.frames | 
|  110    * @param collapse {Boolean} true to force hiding of the node |  117    * @param {Boolean} data.isPrivate  true if the request belongs to a private b
     rowsing window | 
|  111    * @return {Boolean} false if the node should be blocked |  118    * @return {Object} An object containing properties allow, collapse and hits | 
|  112    */ |  119    *                  indicating how this request should be handled. | 
|  113   processNode: function(wnd, node, contentType, location, collapse) |  120    */ | 
|  114   { |  121   shouldAllow: function({contentType, location, frames, isPrivate}) | 
|  115     let topWnd = wnd.top; |  122   { | 
|  116     if (!topWnd || !topWnd.location || !topWnd.location.href) |  123     let hits = []; | 
|  117       return true; |  124  | 
 |  125     function addHit(frameIndex, contentType, docDomain, thirdParty, location, fi
     lter) | 
 |  126     { | 
 |  127       if (filter && !isPrivate) | 
 |  128         FilterStorage.increaseHitCount(filter, docDomain, thirdParty); | 
 |  129  | 
 |  130       hits.push({ | 
 |  131         frameIndex, contentType, docDomain, thirdParty, location, | 
 |  132         filter: filter ? filter.text : null, | 
 |  133         filterType: filter ? filter.type : null | 
 |  134       }); | 
 |  135     } | 
 |  136  | 
 |  137     function response(allow, collapse) | 
 |  138     { | 
 |  139       return {allow, collapse, hits}; | 
 |  140     } | 
|  118  |  141  | 
|  119     // Ignore whitelisted schemes |  142     // Ignore whitelisted schemes | 
|  120     if (!this.isBlockableScheme(location)) |  143     if (!this.isBlockableScheme(location)) | 
|  121       return true; |  144       return response(true, false); | 
|  122  |  145  | 
|  123     // Interpret unknown types as "other" |  146     // Interpret unknown types as "other" | 
|  124     if (!this.contentTypes.has(contentType)) |  147     contentType = this.contentTypes.get(contentType) || "OTHER"; | 
|  125       contentType = "OTHER"; |  148  | 
|  126  |  149     let nogeneric = false; | 
|  127     let originWindow = Utils.getOriginWindow(wnd); |  150     if (Prefs.enabled) | 
|  128     let wndLocation = originWindow.location.href; |  151     { | 
 |  152       let whitelistHit = | 
 |  153           this.isFrameWhitelisted(frames, contentType == "ELEMHIDE"); | 
 |  154       if (whitelistHit) | 
 |  155       { | 
 |  156         let [frameIndex, matchType, docDomain, thirdParty, location, filter] = w
     hitelistHit; | 
 |  157         addHit(frameIndex, matchType, docDomain, thirdParty, location, filter); | 
 |  158         if (matchType == "DOCUMENT" || matchType == "ELEMHIDE") | 
 |  159           return response(true, false); | 
 |  160         else | 
 |  161           nogeneric = true; | 
 |  162       } | 
 |  163     } | 
 |  164  | 
 |  165     let match = null; | 
 |  166     let wndLocation = frames[0].location; | 
|  129     let docDomain = getHostname(wndLocation); |  167     let docDomain = getHostname(wndLocation); | 
|  130     let match = null; |  168     let [sitekey, sitekeyFrame] = getSitekey(frames); | 
|  131     let [sitekey, sitekeyWnd] = getSitekey(wnd); |  169     if (contentType == "ELEMHIDE") | 
|  132     let nogeneric = false; |  170     { | 
|  133  |  171       match = ElemHide.getFilterByKey(location); | 
|  134     function cleanWindowLocation(wnd) |  | 
|  135     { |  | 
|  136       let url = getWindowLocation(wnd); |  | 
|  137       let index = url.indexOf("#"); |  | 
|  138       if (index >= 0) |  | 
|  139         url = url.substring(0, index); |  | 
|  140  |  | 
|  141       return url; |  | 
|  142     } |  | 
|  143  |  | 
|  144     function addHit(match) |  | 
|  145     { |  | 
|  146       if (!PrivateBrowsingUtils.isContentWindowPrivate(wnd)) |  | 
|  147         FilterStorage.increaseHitCount(match, wnd); |  | 
|  148     } |  | 
|  149  |  | 
|  150     if (!match && Prefs.enabled) |  | 
|  151     { |  | 
|  152       let testWnd = wnd; |  | 
|  153       let testSitekey = sitekey; |  | 
|  154       let testSitekeyWnd = sitekeyWnd; |  | 
|  155       let parentWndLocation = cleanWindowLocation(testWnd); |  | 
|  156       while (true) |  | 
|  157       { |  | 
|  158         let testWndLocation = parentWndLocation; |  | 
|  159         parentWndLocation = (testWnd == testWnd.parent ? testWndLocation : clean
     WindowLocation(testWnd.parent)); |  | 
|  160         let parentDocDomain = getHostname(parentWndLocation); |  | 
|  161  |  | 
|  162         let typeMap = RegExpFilter.typeMap.DOCUMENT; |  | 
|  163         if (contentType == "ELEMHIDE") |  | 
|  164           typeMap = typeMap | RegExpFilter.typeMap.ELEMHIDE; |  | 
|  165         let whitelistMatch = defaultMatcher.matchesAny(testWndLocation, typeMap,
      parentDocDomain, false, testSitekey); |  | 
|  166         if (whitelistMatch instanceof WhitelistFilter) |  | 
|  167         { |  | 
|  168           addHit(whitelistMatch); |  | 
|  169           RequestNotifier.addNodeData(testWnd.document, topWnd, |  | 
|  170             (whitelistMatch.contentType & RegExpFilter.typeMap.DOCUMENT) ? "DOCU
     MENT" : "ELEMHIDE", |  | 
|  171             parentDocDomain, false, testWndLocation, whitelistMatch); |  | 
|  172           return true; |  | 
|  173         } |  | 
|  174  |  | 
|  175         let genericType = (contentType == "ELEMHIDE" ? "GENERICHIDE" : "GENERICB
     LOCK"); |  | 
|  176         let nogenericMatch = defaultMatcher.matchesAny(testWndLocation, |  | 
|  177             RegExpFilter.typeMap[genericType], parentDocDomain, false, testSitek
     ey); |  | 
|  178         if (nogenericMatch instanceof WhitelistFilter) |  | 
|  179         { |  | 
|  180           nogeneric = true; |  | 
|  181  |  | 
|  182           addHit(nogenericMatch); |  | 
|  183           RequestNotifier.addNodeData(testWnd.document, topWnd, genericType, |  | 
|  184                                       parentDocDomain, false, testWndLocation, |  | 
|  185                                       nogenericMatch); |  | 
|  186         } |  | 
|  187  |  | 
|  188         if (testWnd.parent == testWnd) |  | 
|  189           break; |  | 
|  190  |  | 
|  191         if (testWnd == testSitekeyWnd) |  | 
|  192           [testSitekey, testSitekeyWnd] = getSitekey(testWnd.parent); |  | 
|  193         testWnd = testWnd.parent; |  | 
|  194       } |  | 
|  195     } |  | 
|  196  |  | 
|  197     if (!match && contentType == "ELEMHIDE") |  | 
|  198     { |  | 
|  199       match = location; |  | 
|  200       location = match.text.replace(/^.*?#/, '#'); |  172       location = match.text.replace(/^.*?#/, '#'); | 
|  201  |  173  | 
|  202       if (!match.isActiveOnDomain(docDomain)) |  174       if (!match.isActiveOnDomain(docDomain)) | 
|  203         return true; |  175         return response(true, false); | 
|  204  |  176  | 
|  205       let exception = ElemHide.getException(match, docDomain); |  177       let exception = ElemHide.getException(match, docDomain); | 
|  206       if (exception) |  178       if (exception) | 
|  207       { |  179       { | 
|  208         addHit(exception); |  180         addHit(null, contentType, docDomain, false, location, exception); | 
|  209         RequestNotifier.addNodeData(node, topWnd, contentType, docDomain, false,
      location, exception); |  181         return response(true, false); | 
|  210         return true; |  | 
|  211       } |  182       } | 
|  212  |  183  | 
|  213       if (nogeneric && match.isGeneric()) |  184       if (nogeneric && match.isGeneric()) | 
|  214         return true; |  185         return response(true, false); | 
|  215     } |  186     } | 
|  216  |  187  | 
|  217     let thirdParty = (contentType == "ELEMHIDE" ? false : isThirdParty(location,
      docDomain)); |  188     let thirdParty = (contentType == "ELEMHIDE" ? false : isThirdParty(location,
      docDomain)); | 
|  218  |  189     let collapse = false; | 
|  219     if (!match && Prefs.enabled && RegExpFilter.typeMap.hasOwnProperty(contentTy
     pe)) |  190     if (!match && Prefs.enabled && RegExpFilter.typeMap.hasOwnProperty(contentTy
     pe)) | 
|  220     { |  191     { | 
|  221       match = defaultMatcher.matchesAny(location, RegExpFilter.typeMap[contentTy
     pe], |  192       match = defaultMatcher.matchesAny(location, RegExpFilter.typeMap[contentTy
     pe], | 
|  222                                         docDomain, thirdParty, sitekey, nogeneri
     c); |  193                                         docDomain, thirdParty, sitekey, nogeneri
     c); | 
|  223       if (match instanceof BlockingFilter && node.ownerDocument && !this.nonVisu
     alTypes.has(contentType)) |  194       if (match instanceof BlockingFilter && !this.nonVisualTypes.has(contentTyp
     e)) | 
|  224       { |  195         collapse = (match.collapse != null ? match.collapse : !Prefs.fastcollaps
     e); | 
|  225         let prefCollapse = (match.collapse != null ? match.collapse : !Prefs.fas
     tcollapse); |  196     } | 
|  226         if (collapse || prefCollapse) |  197     addHit(null, contentType, docDomain, thirdParty, location, match); | 
|  227           schedulePostProcess(node); |  198  | 
|  228       } |  199     return response(!match || match instanceof WhitelistFilter, collapse); | 
|  229  |  | 
|  230       // Track mouse events for objects |  | 
|  231       if (!match && contentType == "OBJECT" && node.nodeType == Ci.nsIDOMNode.EL
     EMENT_NODE) |  | 
|  232       { |  | 
|  233         node.addEventListener("mouseover", objectMouseEventHander, true); |  | 
|  234         node.addEventListener("mouseout", objectMouseEventHander, true); |  | 
|  235       } |  | 
|  236     } |  | 
|  237  |  | 
|  238     // Store node data |  | 
|  239     RequestNotifier.addNodeData(node, topWnd, contentType, docDomain, thirdParty
     , location, match); |  | 
|  240     if (match) |  | 
|  241       addHit(match); |  | 
|  242  |  | 
|  243     return !match || match instanceof WhitelistFilter; |  | 
|  244   }, |  200   }, | 
|  245  |  201  | 
|  246   /** |  202   /** | 
|  247    * Checks whether the location's scheme is blockable. |  203    * Checks whether the location's scheme is blockable. | 
|  248    * @param location  {nsIURI|String} |  204    * @param location  {nsIURI|String} | 
|  249    * @return {Boolean} |  205    * @return {Boolean} | 
|  250    */ |  206    */ | 
|  251   isBlockableScheme: function(location) |  207   isBlockableScheme: function(location) | 
|  252   { |  208   { | 
|  253     let scheme; |  209     let scheme; | 
|  254     if (typeof location == "string") |  210     if (typeof location == "string") | 
|  255     { |  211     { | 
|  256       let match = /^([\w\-]+):/.exec(location); |  212       let match = /^([\w\-]+):/.exec(location); | 
|  257       scheme = match ? match[1] : null; |  213       scheme = match ? match[1] : null; | 
|  258     } |  214     } | 
|  259     else |  215     else | 
|  260       scheme = location.scheme; |  216       scheme = location.scheme; | 
|  261     return !this.whitelistSchemes.has(scheme); |  217     return !this.whitelistSchemes.has(scheme); | 
|  262   }, |  218   }, | 
|  263  |  219  | 
|  264   /** |  220   /** | 
|  265    * Checks whether a page is whitelisted. |  221    * Checks whether a top-level window is whitelisted. | 
|  266    * @param {String} url |  222    * @param {String} url | 
|  267    * @param {String} [parentUrl] location of the parent page |  223    *    URL of the document loaded into the window | 
|  268    * @param {String} [sitekey] public key provided on the page |  224    * @return {?WhitelistFilter} | 
|  269    * @return {Filter} filter that matched the URL or null if not whitelisted |  225    *    exception rule that matched the URL if any | 
|  270    */ |  226    */ | 
|  271   isWhitelisted: function(url, parentUrl, sitekey) |  227   isWhitelisted: function(url) | 
|  272   { |  228   { | 
|  273     if (!url) |  229     if (!url) | 
|  274       return null; |  230       return null; | 
|  275  |  231  | 
|  276     // Do not apply exception rules to schemes on our whitelistschemes list. |  232     // Do not apply exception rules to schemes on our whitelistschemes list. | 
|  277     if (!this.isBlockableScheme(url)) |  233     if (!this.isBlockableScheme(url)) | 
|  278       return null; |  234       return null; | 
|  279  |  | 
|  280     if (!parentUrl) |  | 
|  281       parentUrl = url; |  | 
|  282  |  235  | 
|  283     // Ignore fragment identifier |  236     // Ignore fragment identifier | 
|  284     let index = url.indexOf("#"); |  237     let index = url.indexOf("#"); | 
|  285     if (index >= 0) |  238     if (index >= 0) | 
|  286       url = url.substring(0, index); |  239       url = url.substring(0, index); | 
|  287  |  240  | 
|  288     let result = defaultMatcher.matchesAny(url, RegExpFilter.typeMap.DOCUMENT, g
     etHostname(parentUrl), false, sitekey); |  241     let result = defaultMatcher.matchesAny(url, RegExpFilter.typeMap.DOCUMENT, | 
 |  242         getHostname(url), false, null); | 
|  289     return (result instanceof WhitelistFilter ? result : null); |  243     return (result instanceof WhitelistFilter ? result : null); | 
|  290   }, |  244   }, | 
|  291  |  245  | 
|  292   /** |  246   /** | 
|  293    * Checks whether the page loaded in a window is whitelisted for indication in
      the UI. |  247    * Checks whether a frame is whitelisted. | 
|  294    * @param wnd {nsIDOMWindow} |  248    * @param {Array} frames | 
|  295    * @return {Filter} matching exception rule or null if not whitelisted |  249    *    frame structure as returned by getFrames() in child/utils module. | 
|  296    */ |  250    * @param {boolean} isElemHide | 
|  297   isWindowWhitelisted: function(wnd) |  251    *    true if element hiding whitelisting should be considered | 
|  298   { |  252    * @return {?Array} | 
|  299     return this.isWhitelisted(getWindowLocation(wnd)); |  253    *    An array with the hit parameters: frameIndex, contentType, docDomain, | 
|  300   }, |  254    *    thirdParty, location, filter. Note that the filter could be a | 
|  301  |  255    *    genericblock/generichide exception rule. If nothing matched null is | 
|  302   /** |  256    *    returned. | 
|  303    * Asynchronously re-checks filters for given nodes. |  257    */ | 
|  304    * @param {Node[]} nodes |  258   isFrameWhitelisted: function(frames, isElemHide) | 
 |  259   { | 
 |  260     let [sitekey, sitekeyFrame] = getSitekey(frames); | 
 |  261     let nogenericHit = null; | 
 |  262  | 
 |  263     let typeMap = RegExpFilter.typeMap.DOCUMENT; | 
 |  264     if (isElemHide) | 
 |  265       typeMap = typeMap | RegExpFilter.typeMap.ELEMHIDE; | 
 |  266     let genericType = (isElemHide ? "GENERICHIDE" : "GENERICBLOCK"); | 
 |  267  | 
 |  268     for (let i = 0; i < frames.length; i++) | 
 |  269     { | 
 |  270       let frame = frames[i]; | 
 |  271       let wndLocation = frame.location; | 
 |  272       let parentWndLocation = frames[Math.min(i + 1, frames.length - 1)].locatio
     n; | 
 |  273       let parentDocDomain = getHostname(parentWndLocation); | 
 |  274  | 
 |  275       let match = defaultMatcher.matchesAny(wndLocation, typeMap, parentDocDomai
     n, false, sitekey); | 
 |  276       if (match instanceof WhitelistFilter) | 
 |  277       { | 
 |  278         let whitelistType = (whitelistMatch.contentType & RegExpFilter.typeMap.D
     OCUMENT) ? "DOCUMENT" : "ELEMHIDE"; | 
 |  279         return [i, whitelistType, parentDocDomain, false, wndLocation, match]; | 
 |  280       } | 
 |  281  | 
 |  282       if (!nogenericHit) | 
 |  283       { | 
 |  284         match = defaultMatcher.matchesAny(wndLocation, | 
 |  285             RegExpFilter.typeMap[genericType], parentDocDomain, false, sitekey); | 
 |  286         if (match instanceof WhitelistFilter) | 
 |  287           nogenericHit = [i, genericType, parentDocDomain, false, wndLocation, m
     atch]; | 
 |  288       } | 
 |  289  | 
 |  290       if (frame == sitekeyFrame) | 
 |  291         [sitekey, sitekeyFrame] = getSitekey(frames.slice(i + 1)); | 
 |  292     } | 
 |  293  | 
 |  294     return nogenericHit; | 
 |  295   }, | 
 |  296  | 
 |  297   /** | 
 |  298    * Deletes nodes that were previously stored with a | 
 |  299    * RequestNotifier.storeNodesForEntries() call or similar. | 
 |  300    * @param {string} id  unique ID of the nodes | 
 |  301    */ | 
 |  302   deleteNodes: function(id) | 
 |  303   { | 
 |  304     port.emit("deleteNodes", id); | 
 |  305   }, | 
 |  306  | 
 |  307   /** | 
 |  308    * Asynchronously re-checks filters for nodes given by an ID previously | 
 |  309    * returned by a RequestNotifier.storeNodesForEntries() call or similar. | 
 |  310    * @param {string} id  unique ID of the nodes | 
|  305    * @param {RequestEntry} entry |  311    * @param {RequestEntry} entry | 
|  306    */ |  312    */ | 
|  307   refilterNodes: function(nodes, entry) |  313   refilterNodes: function(id, entry) | 
|  308   { |  314   { | 
|  309     // Ignore nodes that have been blocked already |  315     port.emit("refilterNodes", { | 
|  310     if (entry.filter && !(entry.filter instanceof WhitelistFilter)) |  316       nodesID: id, | 
|  311       return; |  317       entry: entry | 
|  312  |  318     }); | 
|  313     for (let node of nodes) |  | 
|  314       Utils.runAsync(() => refilterNode(node, entry)); |  | 
|  315   } |  319   } | 
|  316 }; |  320 }; | 
|  317 Policy.init(); |  321 Policy.init(); | 
|  318  |  | 
|  319 /** |  | 
|  320  * Actual nsIContentPolicy and nsIChannelEventSink implementation |  | 
|  321  * @class |  | 
|  322  */ |  | 
|  323 var PolicyImplementation = |  | 
|  324 { |  | 
|  325   classDescription: "Adblock Plus content policy", |  | 
|  326   classID: Components.ID("cfeaabe6-1dd1-11b2-a0c6-cb5c268894c9"), |  | 
|  327   contractID: "@adblockplus.org/abp/policy;1", |  | 
|  328   xpcom_categories: ["content-policy", "net-channel-event-sinks"], |  | 
|  329  |  | 
|  330   /** |  | 
|  331    * Registers the content policy on startup. |  | 
|  332    */ |  | 
|  333   init: function() |  | 
|  334   { |  | 
|  335     // Populate types map |  | 
|  336     let iface = Ci.nsIContentPolicy; |  | 
|  337     for (let name in iface) |  | 
|  338       if (name.indexOf("TYPE_") == 0 && name != "TYPE_DATAREQUEST") |  | 
|  339         types.set(iface[name], name.substr(5)); |  | 
|  340  |  | 
|  341     let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar); |  | 
|  342     registrar.registerFactory(this.classID, this.classDescription, this.contract
     ID, this); |  | 
|  343  |  | 
|  344     let catMan = Utils.categoryManager; |  | 
|  345     for (let category of this.xpcom_categories) |  | 
|  346       catMan.addCategoryEntry(category, this.contractID, this.contractID, false,
      true); |  | 
|  347  |  | 
|  348     Services.obs.addObserver(this, "content-document-global-created", true); |  | 
|  349  |  | 
|  350     onShutdown.add(() => |  | 
|  351     { |  | 
|  352       Services.obs.removeObserver(this, "content-document-global-created"); |  | 
|  353  |  | 
|  354       for (let category of this.xpcom_categories) |  | 
|  355         catMan.deleteCategoryEntry(category, this.contractID, false); |  | 
|  356  |  | 
|  357       registrar.unregisterFactory(this.classID, this); |  | 
|  358     }); |  | 
|  359   }, |  | 
|  360  |  | 
|  361   // |  | 
|  362   // nsISupports interface implementation |  | 
|  363   // |  | 
|  364  |  | 
|  365   QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPolicy, Ci.nsIObserver, |  | 
|  366     Ci.nsIChannelEventSink, Ci.nsIFactory, Ci.nsISupportsWeakReference]), |  | 
|  367  |  | 
|  368   // |  | 
|  369   // nsIContentPolicy interface implementation |  | 
|  370   // |  | 
|  371  |  | 
|  372   shouldLoad: function(contentType, contentLocation, requestOrigin, node, mimeTy
     peGuess, extra) |  | 
|  373   { |  | 
|  374     // Ignore requests without context and top-level documents |  | 
|  375     if (!node || contentType == Ci.nsIContentPolicy.TYPE_DOCUMENT) |  | 
|  376       return Ci.nsIContentPolicy.ACCEPT; |  | 
|  377  |  | 
|  378     // Ignore standalone objects |  | 
|  379     if (contentType == Ci.nsIContentPolicy.TYPE_OBJECT && node.ownerDocument && 
     !/^text\/|[+\/]xml$/.test(node.ownerDocument.contentType)) |  | 
|  380       return Ci.nsIContentPolicy.ACCEPT; |  | 
|  381  |  | 
|  382     let wnd = Utils.getWindow(node); |  | 
|  383     if (!wnd) |  | 
|  384       return Ci.nsIContentPolicy.ACCEPT; |  | 
|  385  |  | 
|  386     // Data loaded by plugins should be associated with the document |  | 
|  387     if (contentType == Ci.nsIContentPolicy.TYPE_OBJECT_SUBREQUEST && node instan
     ceof Ci.nsIDOMElement) |  | 
|  388       node = node.ownerDocument; |  | 
|  389  |  | 
|  390     // Fix type for objects misrepresented as frames or images |  | 
|  391     if (contentType != Ci.nsIContentPolicy.TYPE_OBJECT && (node instanceof Ci.ns
     IDOMHTMLObjectElement || node instanceof Ci.nsIDOMHTMLEmbedElement)) |  | 
|  392       contentType = Ci.nsIContentPolicy.TYPE_OBJECT; |  | 
|  393  |  | 
|  394     let location = Utils.unwrapURL(contentLocation); |  | 
|  395     let result = Policy.processNode(wnd, node, types.get(contentType), location.
     spec, false); |  | 
|  396     return (result ? Ci.nsIContentPolicy.ACCEPT : Ci.nsIContentPolicy.REJECT_REQ
     UEST); |  | 
|  397   }, |  | 
|  398  |  | 
|  399   shouldProcess: function(contentType, contentLocation, requestOrigin, insecNode
     , mimeType, extra) |  | 
|  400   { |  | 
|  401     return Ci.nsIContentPolicy.ACCEPT; |  | 
|  402   }, |  | 
|  403  |  | 
|  404   // |  | 
|  405   // nsIObserver interface implementation |  | 
|  406   // |  | 
|  407   observe: function(subject, topic, data, additional) |  | 
|  408   { |  | 
|  409     switch (topic) |  | 
|  410     { |  | 
|  411       case "content-document-global-created": |  | 
|  412       { |  | 
|  413         if (!(subject instanceof Ci.nsIDOMWindow) || !subject.opener) |  | 
|  414           return; |  | 
|  415  |  | 
|  416         let uri = additional || subject.location.href; |  | 
|  417         if (!Policy.processNode(subject.opener, subject.opener.document, "POPUP"
     , uri, false)) |  | 
|  418         { |  | 
|  419           subject.stop(); |  | 
|  420           Utils.runAsync(() => subject.close()); |  | 
|  421         } |  | 
|  422         else if (uri == "about:blank") |  | 
|  423         { |  | 
|  424           // An about:blank pop-up most likely means that a load will be |  | 
|  425           // initiated asynchronously. Wait for that. |  | 
|  426           Utils.runAsync(() => |  | 
|  427           { |  | 
|  428             let channel = subject.QueryInterface(Ci.nsIInterfaceRequestor) |  | 
|  429                                  .getInterface(Ci.nsIDocShell) |  | 
|  430                                  .QueryInterface(Ci.nsIDocumentLoader) |  | 
|  431                                  .documentChannel; |  | 
|  432             if (channel) |  | 
|  433               this.observe(subject, topic, data, channel.URI.spec); |  | 
|  434           }); |  | 
|  435         } |  | 
|  436         break; |  | 
|  437       } |  | 
|  438     } |  | 
|  439   }, |  | 
|  440  |  | 
|  441   // |  | 
|  442   // nsIChannelEventSink interface implementation |  | 
|  443   // |  | 
|  444  |  | 
|  445   asyncOnChannelRedirect: function(oldChannel, newChannel, flags, callback) |  | 
|  446   { |  | 
|  447     let result = Cr.NS_OK; |  | 
|  448     try |  | 
|  449     { |  | 
|  450       // nsILoadInfo.contentPolicyType was introduced in Gecko 35, then |  | 
|  451       // renamed to nsILoadInfo.externalContentPolicyType in Gecko 44. |  | 
|  452       let loadInfo = oldChannel.loadInfo; |  | 
|  453       let contentType = ("externalContentPolicyType" in loadInfo ? |  | 
|  454           loadInfo.externalContentPolicyType : loadInfo.contentPolicyType); |  | 
|  455       if (!contentType) |  | 
|  456         return; |  | 
|  457  |  | 
|  458       let wnd = Utils.getRequestWindow(newChannel); |  | 
|  459       if (!wnd) |  | 
|  460         return; |  | 
|  461  |  | 
|  462       if (contentType == Ci.nsIContentPolicy.TYPE_DOCUMENT) |  | 
|  463       { |  | 
|  464         if (wnd.history.length <= 1 && wnd.opener) |  | 
|  465         { |  | 
|  466           // Special treatment for pop-up windows - this will close the window |  | 
|  467           // rather than preventing the redirect. Note that we might not have |  | 
|  468           // seen the original channel yet because the redirect happened before |  | 
|  469           // the async code in observe() had a chance to run. |  | 
|  470           this.observe(wnd, "content-document-global-created", null, oldChannel.
     URI.spec); |  | 
|  471           this.observe(wnd, "content-document-global-created", null, newChannel.
     URI.spec); |  | 
|  472         } |  | 
|  473         return; |  | 
|  474       } |  | 
|  475  |  | 
|  476       if (!Policy.processNode(wnd, wnd.document, types.get(contentType), newChan
     nel.URI.spec, false)) |  | 
|  477         result = Cr.NS_BINDING_ABORTED; |  | 
|  478     } |  | 
|  479     catch (e) |  | 
|  480     { |  | 
|  481       // We shouldn't throw exceptions here - this will prevent the redirect. |  | 
|  482       Cu.reportError(e); |  | 
|  483     } |  | 
|  484     finally |  | 
|  485     { |  | 
|  486       callback.onRedirectVerifyCallback(result); |  | 
|  487     } |  | 
|  488   }, |  | 
|  489  |  | 
|  490   // |  | 
|  491   // nsIFactory interface implementation |  | 
|  492   // |  | 
|  493  |  | 
|  494   createInstance: function(outer, iid) |  | 
|  495   { |  | 
|  496     if (outer) |  | 
|  497       throw Cr.NS_ERROR_NO_AGGREGATION; |  | 
|  498     return this.QueryInterface(iid); |  | 
|  499   } |  | 
|  500 }; |  | 
|  501 PolicyImplementation.init(); |  | 
|  502  |  | 
|  503 /** |  | 
|  504  * Nodes scheduled for post-processing (might be null). |  | 
|  505  * @type Node[] |  | 
|  506  */ |  | 
|  507 let scheduledNodes = null; |  | 
|  508  |  | 
|  509 /** |  | 
|  510  * Schedules a node for post-processing. |  | 
|  511  */ |  | 
|  512 function schedulePostProcess(/**Element*/ node) |  | 
|  513 { |  | 
|  514   if (scheduledNodes) |  | 
|  515     scheduledNodes.push(node); |  | 
|  516   else |  | 
|  517   { |  | 
|  518     scheduledNodes = [node]; |  | 
|  519     Utils.runAsync(postProcessNodes); |  | 
|  520   } |  | 
|  521 } |  | 
|  522  |  | 
|  523 /** |  | 
|  524  * Processes nodes scheduled for post-processing (typically hides them). |  | 
|  525  */ |  | 
|  526 function postProcessNodes() |  | 
|  527 { |  | 
|  528   let nodes = scheduledNodes; |  | 
|  529   scheduledNodes = null; |  | 
|  530  |  | 
|  531   for (let node of nodes) |  | 
|  532   { |  | 
|  533     // adjust frameset's cols/rows for frames |  | 
|  534     let parentNode = node.parentNode; |  | 
|  535     if (parentNode && parentNode instanceof Ci.nsIDOMHTMLFrameSetElement) |  | 
|  536     { |  | 
|  537       let hasCols = (parentNode.cols && parentNode.cols.indexOf(",") > 0); |  | 
|  538       let hasRows = (parentNode.rows && parentNode.rows.indexOf(",") > 0); |  | 
|  539       if ((hasCols || hasRows) && !(hasCols && hasRows)) |  | 
|  540       { |  | 
|  541         let index = -1; |  | 
|  542         for (let frame = node; frame; frame = frame.previousSibling) |  | 
|  543           if (frame instanceof Ci.nsIDOMHTMLFrameElement || frame instanceof Ci.
     nsIDOMHTMLFrameSetElement) |  | 
|  544             index++; |  | 
|  545  |  | 
|  546         let property = (hasCols ? "cols" : "rows"); |  | 
|  547         let weights = parentNode[property].split(","); |  | 
|  548         weights[index] = "0"; |  | 
|  549         parentNode[property] = weights.join(","); |  | 
|  550       } |  | 
|  551     } |  | 
|  552     else |  | 
|  553       node.classList.add(collapsedClass); |  | 
|  554   } |  | 
|  555 } |  | 
|  556  |  322  | 
|  557 /** |  323 /** | 
|  558  * Extracts the hostname from a URL (might return null). |  324  * Extracts the hostname from a URL (might return null). | 
|  559  */ |  325  */ | 
|  560 function getHostname(/**String*/ url) /**String*/ |  326 function getHostname(/**String*/ url) /**String*/ | 
|  561 { |  327 { | 
|  562   try |  328   try | 
|  563   { |  329   { | 
|  564     return Utils.unwrapURL(url).host; |  330     return Utils.unwrapURL(url).host; | 
|  565   } |  331   } | 
|  566   catch(e) |  332   catch(e) | 
|  567   { |  333   { | 
|  568     return null; |  334     return null; | 
|  569   } |  335   } | 
|  570 } |  336 } | 
|  571  |  337  | 
|  572 /** |  338 /** | 
|  573  * Retrieves the sitekey of a window. |  339  * Retrieves and validates the sitekey for a frame structure. | 
|  574  */ |  340  */ | 
|  575 function getSitekey(wnd) |  341 function getSitekey(frames) | 
|  576 { |  342 { | 
|  577   let sitekey = null; |  343   for (let frame of frames) | 
|  578  |  344   { | 
|  579   while (true) |  345     if (frame.sitekey && frame.sitekey.indexOf("_") >= 0) | 
|  580   { |  346     { | 
|  581     if (wnd.document && wnd.document.documentElement) |  347       let [key, signature] = frame.sitekey.split("_", 2); | 
|  582     { |  348       key = key.replace(/=/g, ""); | 
|  583       let keydata = wnd.document.documentElement.getAttribute("data-adblockkey")
     ; |  349  | 
|  584       if (keydata && keydata.indexOf("_") >= 0) |  350       // Website specifies a key but is the signature valid? | 
|  585       { |  351       let uri = Services.io.newURI(frame.location, null, null); | 
|  586         let [key, signature] = keydata.split("_", 2); |  352       let host = uri.asciiHost; | 
|  587         key = key.replace(/=/g, ""); |  353       if (uri.port > 0) | 
|  588  |  354         host += ":" + uri.port; | 
|  589         // Website specifies a key but is the signature valid? |  355       let params = [ | 
|  590         let uri = Services.io.newURI(getWindowLocation(wnd), null, null); |  356         uri.path.replace(/#.*/, ""),  // REQUEST_URI | 
|  591         let host = uri.asciiHost; |  357         host,                         // HTTP_HOST | 
|  592         if (uri.port > 0) |  358         Utils.httpProtocol.userAgent  // HTTP_USER_AGENT | 
|  593           host += ":" + uri.port; |  359       ]; | 
|  594         let params = [ |  360       if (Utils.verifySignature(key, signature, params.join("\0"))) | 
|  595           uri.path.replace(/#.*/, ""),  // REQUEST_URI |  361         return [key, frame]; | 
|  596           host,                         // HTTP_HOST |  362     } | 
|  597           Utils.httpProtocol.userAgent  // HTTP_USER_AGENT |  363   } | 
|  598         ]; |  364  | 
|  599         if (Utils.verifySignature(key, signature, params.join("\0"))) |  365   return [null, null]; | 
|  600           return [key, wnd]; |  | 
|  601       } |  | 
|  602     } |  | 
|  603  |  | 
|  604     if (wnd === wnd.parent) |  | 
|  605       break; |  | 
|  606  |  | 
|  607     wnd = wnd.parent; |  | 
|  608   } |  | 
|  609  |  | 
|  610   return [sitekey, wnd]; |  | 
|  611 } |  366 } | 
|  612  |  367  | 
|  613 /** |  368 /** | 
|  614  * Retrieves the location of a window. |  369  * Retrieves the location of a window. | 
|  615  * @param wnd {nsIDOMWindow} |  370  * @param wnd {nsIDOMWindow} | 
|  616  * @return {String} window location or null on failure |  371  * @return {String} window location or null on failure | 
|  617  */ |  372  */ | 
|  618 function getWindowLocation(wnd) |  373 function getWindowLocation(wnd) | 
|  619 { |  374 { | 
|  620   if ("name" in wnd && wnd.name == "messagepane") |  375   if ("name" in wnd && wnd.name == "messagepane") | 
| (...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
|  669   { |  424   { | 
|  670     // EffectiveTLDService throws on IP addresses, just compare the host name |  425     // EffectiveTLDService throws on IP addresses, just compare the host name | 
|  671     let host = ""; |  426     let host = ""; | 
|  672     try |  427     try | 
|  673     { |  428     { | 
|  674       host = uri.host; |  429       host = uri.host; | 
|  675     } catch (e) {} |  430     } catch (e) {} | 
|  676     return host != docDomain; |  431     return host != docDomain; | 
|  677   } |  432   } | 
|  678 } |  433 } | 
|  679  |  | 
|  680 /** |  | 
|  681  * Re-checks filters on an element. |  | 
|  682  */ |  | 
|  683 function refilterNode(/**Node*/ node, /**RequestEntry*/ entry) |  | 
|  684 { |  | 
|  685   let wnd = Utils.getWindow(node); |  | 
|  686   if (!wnd || wnd.closed) |  | 
|  687     return; |  | 
|  688  |  | 
|  689   if (entry.type == "OBJECT") |  | 
|  690   { |  | 
|  691     node.removeEventListener("mouseover", objectMouseEventHander, true); |  | 
|  692     node.removeEventListener("mouseout", objectMouseEventHander, true); |  | 
|  693   } |  | 
|  694   Policy.processNode(wnd, node, entry.type, entry.location, true); |  | 
|  695 } |  | 
| LEFT | RIGHT |