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-2015 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 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); | 22 "use strict"; |
23 Cu.import("resource://gre/modules/Services.jsm"); | 23 |
| 24 let {XPCOMUtils} = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {}); |
| 25 let {Services} = Cu.import("resource://gre/modules/Services.jsm", {}); |
24 | 26 |
25 let {Utils} = require("utils"); | 27 let {Utils} = require("utils"); |
26 let {Prefs} = require("prefs"); | 28 let {Prefs} = require("prefs"); |
27 let {FilterStorage} = require("filterStorage"); | 29 let {FilterStorage} = require("filterStorage"); |
28 let {BlockingFilter, WhitelistFilter, RegExpFilter} = require("filterClasses"); | 30 let {BlockingFilter, WhitelistFilter, RegExpFilter} = require("filterClasses"); |
29 let {defaultMatcher} = require("matcher"); | 31 let {defaultMatcher} = require("matcher"); |
30 let {objectMouseEventHander} = require("objectTabs"); | 32 let {objectMouseEventHander} = require("objectTabs"); |
31 let {RequestNotifier} = require("requestNotifier"); | 33 let {RequestNotifier} = require("requestNotifier"); |
32 let {ElemHide} = require("elemHide"); | 34 let {ElemHide} = require("elemHide"); |
33 | 35 |
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
79 localizedDescr: {}, | 81 localizedDescr: {}, |
80 | 82 |
81 /** | 83 /** |
82 * Lists the non-visual content types. | 84 * Lists the non-visual content types. |
83 * @type Object | 85 * @type Object |
84 */ | 86 */ |
85 nonVisual: {}, | 87 nonVisual: {}, |
86 | 88 |
87 /** | 89 /** |
88 * Map containing all schemes that should be ignored by content policy. | 90 * Map containing all schemes that should be ignored by content policy. |
89 * @type Object | 91 * @type Set |
90 */ | 92 */ |
91 whitelistSchemes: {}, | 93 whitelistSchemes: new Set(), |
92 | 94 |
93 /** | 95 /** |
94 * Called on module startup, initializes various exported properties. | 96 * Called on module startup, initializes various exported properties. |
95 */ | 97 */ |
96 init: function() | 98 init: function() |
97 { | 99 { |
98 // type constant by type description and type description by type constant | 100 // type constant by type description and type description by type constant |
99 let iface = Ci.nsIContentPolicy; | 101 let iface = Ci.nsIContentPolicy; |
100 for (let typeName of contentTypes) | 102 for (let typeName of contentTypes) |
101 { | 103 { |
(...skipping 25 matching lines...) Expand all Loading... |
127 this.type.POPUP = 0xFFFE; | 129 this.type.POPUP = 0xFFFE; |
128 this.typeDescr[0xFFFE] = "POPUP"; | 130 this.typeDescr[0xFFFE] = "POPUP"; |
129 this.localizedDescr[0xFFFE] = Utils.getString("type_label_popup"); | 131 this.localizedDescr[0xFFFE] = Utils.getString("type_label_popup"); |
130 this.typeMask[0xFFFE] = RegExpFilter.typeMap.POPUP; | 132 this.typeMask[0xFFFE] = RegExpFilter.typeMap.POPUP; |
131 | 133 |
132 for (let type of nonVisualTypes) | 134 for (let type of nonVisualTypes) |
133 this.nonVisual[this.type[type]] = true; | 135 this.nonVisual[this.type[type]] = true; |
134 | 136 |
135 // whitelisted URL schemes | 137 // whitelisted URL schemes |
136 for (let scheme of Prefs.whitelistschemes.toLowerCase().split(" ")) | 138 for (let scheme of Prefs.whitelistschemes.toLowerCase().split(" ")) |
137 this.whitelistSchemes[scheme] = true; | 139 this.whitelistSchemes.add(scheme); |
138 | 140 |
139 // Generate class identifier used to collapse node and register correspondin
g | 141 // Generate class identifier used to collapse node and register correspondin
g |
140 // stylesheet. | 142 // stylesheet. |
141 let offset = "a".charCodeAt(0); | 143 let offset = "a".charCodeAt(0); |
142 for (let i = 0; i < 20; i++) | 144 for (let i = 0; i < 20; i++) |
143 collapsedClass += String.fromCharCode(offset + Math.random() * 26); | 145 collapsedClass += String.fromCharCode(offset + Math.random() * 26); |
144 | 146 |
145 let collapseStyle = Services.io.newURI("data:text/css," + | 147 let collapseStyle = Services.io.newURI("data:text/css," + |
146 encodeURIComponent("." + collapsedClass + | 148 encodeURIComponent("." + collapsedClass + |
147 "{-moz-binding: url(chrome://global/content/bindings/general.xml#foobarb
azdummy) !important;}"), null, null); | 149 "{-moz-binding: url(chrome://global/content/bindings/general.xml#foobarb
azdummy) !important;}"), null, null); |
148 Utils.styleService.loadAndRegisterSheet(collapseStyle, Ci.nsIStyleSheetServi
ce.USER_SHEET); | 150 Utils.styleService.loadAndRegisterSheet(collapseStyle, Ci.nsIStyleSheetServi
ce.USER_SHEET); |
149 onShutdown.add(function() | 151 onShutdown.add(() => |
150 { | 152 { |
151 Utils.styleService.unregisterSheet(collapseStyle, Ci.nsIStyleSheetService.
USER_SHEET); | 153 Utils.styleService.unregisterSheet(collapseStyle, Ci.nsIStyleSheetService.
USER_SHEET); |
152 }); | 154 }); |
153 }, | 155 }, |
154 | 156 |
155 /** | 157 /** |
156 * Checks whether a node should be blocked, hides it if necessary | 158 * Checks whether a node should be blocked, hides it if necessary |
157 * @param wnd {nsIDOMWindow} | 159 * @param wnd {nsIDOMWindow} |
158 * @param node {nsIDOMElement} | 160 * @param node {nsIDOMElement} |
159 * @param contentType {String} | 161 * @param contentType {String} |
(...skipping 138 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
298 return !match || match instanceof WhitelistFilter; | 300 return !match || match instanceof WhitelistFilter; |
299 }, | 301 }, |
300 | 302 |
301 /** | 303 /** |
302 * Checks whether the location's scheme is blockable. | 304 * Checks whether the location's scheme is blockable. |
303 * @param location {nsIURI} | 305 * @param location {nsIURI} |
304 * @return {Boolean} | 306 * @return {Boolean} |
305 */ | 307 */ |
306 isBlockableScheme: function(location) | 308 isBlockableScheme: function(location) |
307 { | 309 { |
308 return !(location.scheme in Policy.whitelistSchemes); | 310 return !this.whitelistSchemes.has(location.scheme); |
309 }, | 311 }, |
310 | 312 |
311 /** | 313 /** |
312 * Checks whether a page is whitelisted. | 314 * Checks whether a page is whitelisted. |
313 * @param {String} url | 315 * @param {String} url |
314 * @param {String} [parentUrl] location of the parent page | 316 * @param {String} [parentUrl] location of the parent page |
315 * @param {String} [sitekey] public key provided on the page | 317 * @param {String} [sitekey] public key provided on the page |
316 * @return {Filter} filter that matched the URL or null if not whitelisted | 318 * @return {Filter} filter that matched the URL or null if not whitelisted |
317 */ | 319 */ |
318 isWhitelisted: function(url, parentUrl, sitekey) | 320 isWhitelisted: function(url, parentUrl, sitekey) |
319 { | 321 { |
320 if (!url) | 322 if (!url) |
321 return null; | 323 return null; |
322 | 324 |
323 // Do not apply exception rules to schemes on our whitelistschemes list. | 325 // Do not apply exception rules to schemes on our whitelistschemes list. |
324 let match = /^([\w\-]+):/.exec(url); | 326 let match = /^([\w\-]+):/.exec(url); |
325 if (match && match[1] in Policy.whitelistSchemes) | 327 if (match && this.whitelistSchemes.has(match[1])) |
326 return null; | 328 return null; |
327 | 329 |
328 if (!parentUrl) | 330 if (!parentUrl) |
329 parentUrl = url; | 331 parentUrl = url; |
330 | 332 |
331 // Ignore fragment identifier | 333 // Ignore fragment identifier |
332 let index = url.indexOf("#"); | 334 let index = url.indexOf("#"); |
333 if (index >= 0) | 335 if (index >= 0) |
334 url = url.substring(0, index); | 336 url = url.substring(0, index); |
335 | 337 |
336 let result = defaultMatcher.matchesAny(url, RegExpFilter.typeMap.DOCUMENT, g
etHostname(parentUrl), false, sitekey); | 338 let result = defaultMatcher.matchesAny(url, RegExpFilter.typeMap.DOCUMENT, g
etHostname(parentUrl), false, sitekey); |
337 return (result instanceof WhitelistFilter ? result : null); | 339 return (result instanceof WhitelistFilter ? result : null); |
338 }, | 340 }, |
339 | 341 |
340 /** | 342 /** |
341 * Checks whether the page loaded in a window is whitelisted for indication in
the UI. | 343 * Checks whether the page loaded in a window is whitelisted for indication in
the UI. |
342 * @param wnd {nsIDOMWindow} | 344 * @param wnd {nsIDOMWindow} |
343 * @return {Filter} matching exception rule or null if not whitelisted | 345 * @return {Filter} matching exception rule or null if not whitelisted |
344 */ | 346 */ |
345 isWindowWhitelisted: function(wnd) | 347 isWindowWhitelisted: function(wnd) |
346 { | 348 { |
347 return Policy.isWhitelisted(getWindowLocation(wnd)); | 349 return this.isWhitelisted(getWindowLocation(wnd)); |
348 }, | 350 }, |
349 | 351 |
350 /** | 352 /** |
351 * Asynchronously re-checks filters for given nodes. | 353 * Asynchronously re-checks filters for given nodes. |
352 * @param {Node[]} nodes | 354 * @param {Node[]} nodes |
353 * @param {RequestEntry} entry | 355 * @param {RequestEntry} entry |
354 */ | 356 */ |
355 refilterNodes: function(nodes, entry) | 357 refilterNodes: function(nodes, entry) |
356 { | 358 { |
357 // Ignore nodes that have been blocked already | 359 // Ignore nodes that have been blocked already |
(...skipping 22 matching lines...) Expand all Loading... |
380 */ | 382 */ |
381 init: function() | 383 init: function() |
382 { | 384 { |
383 let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar); | 385 let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar); |
384 registrar.registerFactory(this.classID, this.classDescription, this.contract
ID, this); | 386 registrar.registerFactory(this.classID, this.classDescription, this.contract
ID, this); |
385 | 387 |
386 let catMan = Utils.categoryManager; | 388 let catMan = Utils.categoryManager; |
387 for (let category of this.xpcom_categories) | 389 for (let category of this.xpcom_categories) |
388 catMan.addCategoryEntry(category, this.contractID, this.contractID, false,
true); | 390 catMan.addCategoryEntry(category, this.contractID, this.contractID, false,
true); |
389 | 391 |
390 // http-on-opening-request is new in Gecko 18, http-on-modify-request can | |
391 // be used in earlier releases. | |
392 let httpTopic = "http-on-opening-request"; | |
393 if (Services.vc.compare(Utils.platformVersion, "18.0") < 0) | |
394 httpTopic = "http-on-modify-request"; | |
395 | |
396 Services.obs.addObserver(this, httpTopic, true); | |
397 Services.obs.addObserver(this, "content-document-global-created", true); | 392 Services.obs.addObserver(this, "content-document-global-created", true); |
398 | 393 |
399 onShutdown.add(function() | 394 onShutdown.add(() => |
400 { | 395 { |
401 // Our category observers should be removed before changing category | |
402 // memberships, just in case. | |
403 Services.obs.removeObserver(this, httpTopic); | |
404 Services.obs.removeObserver(this, "content-document-global-created"); | 396 Services.obs.removeObserver(this, "content-document-global-created"); |
405 | 397 |
406 for (let category of this.xpcom_categories) | 398 for (let category of this.xpcom_categories) |
407 catMan.deleteCategoryEntry(category, this.contractID, false); | 399 catMan.deleteCategoryEntry(category, this.contractID, false); |
408 | 400 |
409 registrar.unregisterFactory(this.classID, this); | 401 registrar.unregisterFactory(this.classID, this); |
410 | 402 }); |
411 this.previousRequest = null; | |
412 }.bind(this)); | |
413 }, | 403 }, |
414 | 404 |
415 // | 405 // |
416 // nsISupports interface implementation | 406 // nsISupports interface implementation |
417 // | 407 // |
418 | 408 |
419 QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPolicy, Ci.nsIObserver, | 409 QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPolicy, Ci.nsIObserver, |
420 Ci.nsIChannelEventSink, Ci.nsIFactory, Ci.nsISupportsWeakReference]), | 410 Ci.nsIChannelEventSink, Ci.nsIFactory, Ci.nsISupportsWeakReference]), |
421 | 411 |
422 // | 412 // |
(...skipping 17 matching lines...) Expand all Loading... |
440 // Ignore whitelisted schemes | 430 // Ignore whitelisted schemes |
441 let location = Utils.unwrapURL(contentLocation); | 431 let location = Utils.unwrapURL(contentLocation); |
442 if (!Policy.isBlockableScheme(location)) | 432 if (!Policy.isBlockableScheme(location)) |
443 return Ci.nsIContentPolicy.ACCEPT; | 433 return Ci.nsIContentPolicy.ACCEPT; |
444 | 434 |
445 // Interpret unknown types as "other" | 435 // Interpret unknown types as "other" |
446 if (!(contentType in Policy.typeDescr)) | 436 if (!(contentType in Policy.typeDescr)) |
447 contentType = Policy.type.OTHER; | 437 contentType = Policy.type.OTHER; |
448 | 438 |
449 let result = Policy.processNode(wnd, node, contentType, location, false); | 439 let result = Policy.processNode(wnd, node, contentType, location, false); |
450 if (result) | |
451 { | |
452 // We didn't block this request so we will probably see it again in | |
453 // http-on-opening-request. Keep it so that we can associate it with the | |
454 // channel there - will be needed in case of redirect. | |
455 this.previousRequest = [location, contentType]; | |
456 } | |
457 return (result ? Ci.nsIContentPolicy.ACCEPT : Ci.nsIContentPolicy.REJECT_REQ
UEST); | 440 return (result ? Ci.nsIContentPolicy.ACCEPT : Ci.nsIContentPolicy.REJECT_REQ
UEST); |
458 }, | 441 }, |
459 | 442 |
460 shouldProcess: function(contentType, contentLocation, requestOrigin, insecNode
, mimeType, extra) | 443 shouldProcess: function(contentType, contentLocation, requestOrigin, insecNode
, mimeType, extra) |
461 { | 444 { |
462 return Ci.nsIContentPolicy.ACCEPT; | 445 return Ci.nsIContentPolicy.ACCEPT; |
463 }, | 446 }, |
464 | 447 |
465 // | 448 // |
466 // nsIObserver interface implementation | 449 // nsIObserver interface implementation |
(...skipping 10 matching lines...) Expand all Loading... |
477 let uri = additional || Utils.makeURI(subject.location.href); | 460 let uri = additional || Utils.makeURI(subject.location.href); |
478 if (!Policy.processNode(subject.opener, subject.opener.document, Policy.
type.POPUP, uri, false)) | 461 if (!Policy.processNode(subject.opener, subject.opener.document, Policy.
type.POPUP, uri, false)) |
479 { | 462 { |
480 subject.stop(); | 463 subject.stop(); |
481 Utils.runAsync(() => subject.close()); | 464 Utils.runAsync(() => subject.close()); |
482 } | 465 } |
483 else if (uri.spec == "about:blank") | 466 else if (uri.spec == "about:blank") |
484 { | 467 { |
485 // An about:blank pop-up most likely means that a load will be | 468 // An about:blank pop-up most likely means that a load will be |
486 // initiated asynchronously. Wait for that. | 469 // initiated asynchronously. Wait for that. |
487 Utils.runAsync(function() | 470 Utils.runAsync(() => |
488 { | 471 { |
489 let channel = subject.QueryInterface(Ci.nsIInterfaceRequestor) | 472 let channel = subject.QueryInterface(Ci.nsIInterfaceRequestor) |
490 .getInterface(Ci.nsIDocShell) | 473 .getInterface(Ci.nsIDocShell) |
491 .QueryInterface(Ci.nsIDocumentLoader) | 474 .QueryInterface(Ci.nsIDocumentLoader) |
492 .documentChannel; | 475 .documentChannel; |
493 if (channel) | 476 if (channel) |
494 this.observe(subject, topic, data, channel.URI); | 477 this.observe(subject, topic, data, channel.URI); |
495 }); | 478 }); |
496 } | 479 } |
497 break; | 480 break; |
498 } | 481 } |
499 case "http-on-opening-request": | |
500 case "http-on-modify-request": | |
501 { | |
502 if (!(subject instanceof Ci.nsIHttpChannel)) | |
503 return; | |
504 | |
505 if (this.previousRequest && subject.URI == this.previousRequest[0] && | |
506 subject instanceof Ci.nsIWritablePropertyBag) | |
507 { | |
508 // We just handled a content policy call for this request - associate | |
509 // the data with the channel so that we can find it in case of a redir
ect. | |
510 subject.setProperty("abpRequestType", this.previousRequest[1]); | |
511 this.previousRequest = null; | |
512 } | |
513 break; | |
514 } | |
515 } | 482 } |
516 }, | 483 }, |
517 | 484 |
518 // | 485 // |
519 // nsIChannelEventSink interface implementation | 486 // nsIChannelEventSink interface implementation |
520 // | 487 // |
521 | 488 |
522 asyncOnChannelRedirect: function(oldChannel, newChannel, flags, callback) | 489 asyncOnChannelRedirect: function(oldChannel, newChannel, flags, callback) |
523 { | 490 { |
524 let result = Cr.NS_OK; | 491 let result = Cr.NS_OK; |
525 try | 492 try |
526 { | 493 { |
527 // Try to retrieve previously stored request data from the channel | 494 // nsILoadInfo.contentPolicyType was introduced in Gecko 35, then |
528 let contentType; | 495 // renamed to nsILoadInfo.externalContentPolicyType in Gecko 44. |
529 if (oldChannel instanceof Ci.nsIWritablePropertyBag) | 496 let loadInfo = oldChannel.loadInfo; |
530 { | 497 let contentType = ("externalContentPolicyType" in loadInfo ? |
531 try | 498 loadInfo.externalContentPolicyType : loadInfo.contentPolicyType); |
532 { | 499 if (!contentType) |
533 contentType = oldChannel.getProperty("abpRequestType"); | |
534 } | |
535 catch(e) | |
536 { | |
537 // No data attached, ignore this redirect | |
538 return; | |
539 } | |
540 } | |
541 | |
542 let newLocation = null; | |
543 try | |
544 { | |
545 newLocation = newChannel.URI; | |
546 } catch(e2) {} | |
547 if (!newLocation) | |
548 return; | 500 return; |
549 | 501 |
550 let wnd = Utils.getRequestWindow(newChannel); | 502 let wnd = Utils.getRequestWindow(newChannel); |
551 if (!wnd) | 503 if (!wnd) |
552 return; | 504 return; |
553 | 505 |
554 if (contentType == Ci.nsIContentPolicy.TYPE_DOCUMENT) | 506 if (contentType == Ci.nsIContentPolicy.TYPE_DOCUMENT) |
555 { | 507 { |
556 if (wnd.history.length <= 1 && wnd.opener) | 508 if (wnd.history.length <= 1 && wnd.opener) |
557 { | 509 { |
558 // Special treatment for pop-up windows | 510 // Special treatment for pop-up windows - this will close the window |
559 this.observe(wnd, "content-document-global-created", null, newLocation
); | 511 // rather than preventing the redirect. Note that we might not have |
| 512 // seen the original channel yet because the redirect happened before |
| 513 // the async code in observe() had a chance to run. |
| 514 this.observe(wnd, "content-document-global-created", null, oldChannel.
URI); |
| 515 this.observe(wnd, "content-document-global-created", null, newChannel.
URI); |
560 } | 516 } |
561 return; | 517 return; |
562 } | 518 } |
563 | 519 |
564 if (!Policy.processNode(wnd, wnd.document, contentType, newLocation, false
)) | 520 if (!Policy.processNode(wnd, wnd.document, contentType, newChannel.URI, fa
lse)) |
565 result = Cr.NS_BINDING_ABORTED; | 521 result = Cr.NS_BINDING_ABORTED; |
566 } | 522 } |
567 catch (e) | 523 catch (e) |
568 { | 524 { |
569 // We shouldn't throw exceptions here - this will prevent the redirect. | 525 // We shouldn't throw exceptions here - this will prevent the redirect. |
570 Cu.reportError(e); | 526 Cu.reportError(e); |
571 } | 527 } |
572 finally | 528 finally |
573 { | 529 { |
574 callback.onRedirectVerifyCallback(result); | 530 callback.onRedirectVerifyCallback(result); |
(...skipping 198 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
773 if (!wnd || wnd.closed) | 729 if (!wnd || wnd.closed) |
774 return; | 730 return; |
775 | 731 |
776 if (entry.type == Policy.type.OBJECT) | 732 if (entry.type == Policy.type.OBJECT) |
777 { | 733 { |
778 node.removeEventListener("mouseover", objectMouseEventHander, true); | 734 node.removeEventListener("mouseover", objectMouseEventHander, true); |
779 node.removeEventListener("mouseout", objectMouseEventHander, true); | 735 node.removeEventListener("mouseout", objectMouseEventHander, true); |
780 } | 736 } |
781 Policy.processNode(wnd, node, entry.type, Utils.makeURI(entry.location), true)
; | 737 Policy.processNode(wnd, node, entry.type, Utils.makeURI(entry.location), true)
; |
782 } | 738 } |
LEFT | RIGHT |