| Left: | ||
| Right: |
| 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-present eyeo GmbH | 3 * Copyright (C) 2006-present 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 |
| (...skipping 368 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 379 domainSource: null, | 379 domainSource: null, |
| 380 | 380 |
| 381 /** | 381 /** |
| 382 * Separator character used in domainSource property, must be | 382 * Separator character used in domainSource property, must be |
| 383 * overridden by subclasses | 383 * overridden by subclasses |
| 384 * @type {string} | 384 * @type {string} |
| 385 */ | 385 */ |
| 386 domainSeparator: null, | 386 domainSeparator: null, |
| 387 | 387 |
| 388 /** | 388 /** |
| 389 * Determines whether the trailing dot in domain names isn't important and | |
| 390 * should be ignored, must be overridden by subclasses. | |
| 391 * @type {boolean} | |
| 392 */ | |
| 393 ignoreTrailingDot: true, | |
| 394 | |
| 395 /** | |
| 396 * Determines whether domainSource is already upper-case, | 389 * Determines whether domainSource is already upper-case, |
| 397 * can be overridden by subclasses. | 390 * can be overridden by subclasses. |
| 398 * @type {boolean} | 391 * @type {boolean} |
| 399 */ | 392 */ |
| 400 domainSourceIsUpperCase: false, | 393 domainSourceIsUpperCase: false, |
| 401 | 394 |
| 402 /** | 395 /** |
| 403 * String specifying the domain that this filter should match on, or map | 396 * Map containing domains that this filter should match on/not match |
| 404 * containing domains that this filter should match on/not match on, or null | 397 * on or null if the filter should match on all domains |
| 405 * if the filter should match on all domains | 398 * @type {?Map.<string,boolean>} |
| 406 * @type {?Map.<string,boolean>|string} | |
| 407 */ | 399 */ |
| 408 get domains() | 400 get domains() |
| 409 { | 401 { |
| 410 // Despite this property being cached, the getter is called | 402 // Despite this property being cached, the getter is called |
| 411 // several times on Safari, due to WebKit bug 132872 | 403 // several times on Safari, due to WebKit bug 132872 |
| 412 let prop = Object.getOwnPropertyDescriptor(this, "domains"); | 404 let prop = Object.getOwnPropertyDescriptor(this, "_domains"); |
| 413 if (prop) | 405 if (prop) |
| 414 return prop.value; | 406 { |
| 407 let {value} = prop; | |
| 408 return typeof value == "string" ? | |
| 409 new Map([[value, true], ["", false]]) : value; | |
| 410 } | |
| 415 | 411 |
| 416 let domains = null; | 412 let domains = null; |
| 417 | 413 |
| 418 if (this.domainSource) | 414 if (this.domainSource) |
| 419 { | 415 { |
| 420 let source = this.domainSource; | 416 let source = this.domainSource; |
| 421 if (!this.domainSourceIsUpperCase) | 417 if (!this.domainSourceIsUpperCase) |
| 422 { | 418 { |
| 423 // RegExpFilter already have uppercase domains | 419 // RegExpFilter already have uppercase domains |
| 424 source = source.toUpperCase(); | 420 source = source.toUpperCase(); |
| 425 } | 421 } |
| 426 let list = source.split(this.domainSeparator); | 422 let list = source.split(this.domainSeparator); |
| 427 if (list.length == 1 && list[0][0] != "~") | 423 if (list.length == 1 && list[0][0] != "~") |
| 428 { | 424 { |
| 429 // Fast track for the common one-domain scenario | 425 // Fast track for the common one-domain scenario |
| 430 if (this.ignoreTrailingDot) | |
| 431 list[0] = list[0].replace(/\.+$/, ""); | |
| 432 domains = list[0]; | 426 domains = list[0]; |
| 433 } | 427 } |
| 434 else | 428 else |
| 435 { | 429 { |
| 436 let hasIncludes = false; | 430 let hasIncludes = false; |
| 437 for (let i = 0; i < list.length; i++) | 431 for (let i = 0; i < list.length; i++) |
| 438 { | 432 { |
| 439 let domain = list[i]; | 433 let domain = list[i]; |
| 440 if (this.ignoreTrailingDot) | |
| 441 domain = domain.replace(/\.+$/, ""); | |
| 442 if (domain == "") | 434 if (domain == "") |
| 443 continue; | 435 continue; |
| 444 | 436 |
| 445 let include; | 437 let include; |
| 446 if (domain[0] == "~") | 438 if (domain[0] == "~") |
| 447 { | 439 { |
| 448 include = false; | 440 include = false; |
| 449 domain = domain.substr(1); | 441 domain = domain.substr(1); |
| 450 } | 442 } |
| 451 else | 443 else |
| 452 { | 444 { |
| 453 include = true; | 445 include = true; |
| 454 hasIncludes = true; | 446 hasIncludes = true; |
| 455 } | 447 } |
| 456 | 448 |
| 457 if (!domains) | 449 if (!domains) |
| 458 domains = new Map(); | 450 domains = new Map(); |
| 459 | 451 |
| 460 domains.set(domain, include); | 452 domains.set(domain, include); |
| 461 } | 453 } |
| 462 if (domains) | 454 if (domains) |
| 463 domains.set("", !hasIncludes); | 455 domains.set("", !hasIncludes); |
| 464 } | 456 } |
| 465 | 457 |
| 466 this.domainSource = null; | 458 this.domainSource = null; |
| 467 } | 459 } |
| 468 | 460 |
| 469 Object.defineProperty(this, "domains", {value: domains, enumerable: true}); | 461 Object.defineProperty(this, "_domains", {value: domains}); |
| 470 return this.domains; | 462 return this.domains; |
| 471 }, | 463 }, |
| 472 | 464 |
| 473 /** | 465 /** |
| 474 * Array containing public keys of websites that this filter should apply to | 466 * Array containing public keys of websites that this filter should apply to |
| 475 * @type {?string[]} | 467 * @type {?string[]} |
| 476 */ | 468 */ |
| 477 sitekeys: null, | 469 sitekeys: null, |
| 478 | 470 |
| 479 /** | 471 /** |
| (...skipping 13 matching lines...) Expand all Loading... | |
| 493 return false; | 485 return false; |
| 494 } | 486 } |
| 495 | 487 |
| 496 // If no domains are set the rule matches everywhere | 488 // If no domains are set the rule matches everywhere |
| 497 if (!this.domains) | 489 if (!this.domains) |
| 498 return true; | 490 return true; |
| 499 | 491 |
| 500 // If the document has no host name, match only if the filter | 492 // If the document has no host name, match only if the filter |
| 501 // isn't restricted to specific domains | 493 // isn't restricted to specific domains |
| 502 if (!docDomain) | 494 if (!docDomain) |
| 503 return typeof this.domains != "string" && this.domains.get(""); | 495 return this.domains.get(""); |
| 504 | 496 |
| 505 if (this.ignoreTrailingDot) | 497 docDomain = docDomain.replace(/\.+$/, "").toUpperCase(); |
| 506 docDomain = docDomain.replace(/\.+$/, ""); | |
| 507 docDomain = docDomain.toUpperCase(); | |
| 508 | |
| 509 if (typeof this.domains == "string") | |
| 510 { | |
| 511 return docDomain == this.domains || | |
| 512 docDomain.endsWith("." + this.domains); | |
| 513 } | |
| 514 | 498 |
| 515 while (true) | 499 while (true) |
| 516 { | 500 { |
| 517 let isDomainIncluded = this.domains.get(docDomain); | 501 let isDomainIncluded = this.domains.get(docDomain); |
| 518 if (typeof isDomainIncluded != "undefined") | 502 if (typeof isDomainIncluded != "undefined") |
| 519 return isDomainIncluded; | 503 return isDomainIncluded; |
| 520 | 504 |
| 521 let nextDot = docDomain.indexOf("."); | 505 let nextDot = docDomain.indexOf("."); |
| 522 if (nextDot < 0) | 506 if (nextDot < 0) |
| 523 break; | 507 break; |
| 524 docDomain = docDomain.substr(nextDot + 1); | 508 docDomain = docDomain.substr(nextDot + 1); |
| 525 } | 509 } |
| 526 return this.domains.get(""); | 510 return this.domains.get(""); |
| 527 }, | 511 }, |
| 528 | 512 |
| 529 /** | 513 /** |
| 530 * Checks whether this filter is active only on a domain and its subdomains. | 514 * Checks whether this filter is active only on a domain and its subdomains. |
| 531 * @param {string} docDomain | 515 * @param {string} docDomain |
| 532 * @return {boolean} | 516 * @return {boolean} |
| 533 */ | 517 */ |
| 534 isActiveOnlyOnDomain(docDomain) | 518 isActiveOnlyOnDomain(docDomain) |
| 535 { | 519 { |
| 536 if (!docDomain || !this.domains || | 520 if (!docDomain || !this.domains || this.domains.get("")) |
| 537 typeof this.domains != "string" && this.domains.get("")) | |
|
sergei
2018/05/28 09:22:07
IMO, perhaps it would be safer to rather check tha
kzar
2018/06/05 16:51:30
Disagree on this one, people that don't understand
Manish Jethani
2018/06/06 11:16:31
This is one of those things that can lead to an en
Manish Jethani
2018/06/06 12:31:18
typeof is typically faster than instanceof, so whe
| |
| 538 { | |
| 539 return false; | 521 return false; |
| 540 } | 522 |
| 541 | 523 docDomain = docDomain.replace(/\.+$/, "").toUpperCase(); |
| 542 if (this.ignoreTrailingDot) | |
| 543 docDomain = docDomain.replace(/\.+$/, ""); | |
| 544 docDomain = docDomain.toUpperCase(); | |
| 545 | |
| 546 if (typeof this.domains == "string") | |
| 547 { | |
| 548 return docDomain == this.domains || | |
| 549 this.domains.endsWith("." + docDomain); | |
| 550 } | |
| 551 | 524 |
| 552 for (let [domain, isIncluded] of this.domains) | 525 for (let [domain, isIncluded] of this.domains) |
| 553 { | 526 { |
| 554 if (isIncluded && domain != docDomain) | 527 if (isIncluded && domain != docDomain) |
| 555 { | 528 { |
| 556 if (domain.length <= docDomain.length) | 529 if (domain.length <= docDomain.length) |
| 557 return false; | 530 return false; |
| 558 | 531 |
| 559 if (!domain.endsWith("." + docDomain)) | 532 if (!domain.endsWith("." + docDomain)) |
| 560 return false; | 533 return false; |
| 561 } | 534 } |
| 562 } | 535 } |
| 563 | 536 |
| 564 return true; | 537 return true; |
| 565 }, | 538 }, |
| 566 | 539 |
| 567 /** | 540 /** |
| 568 * Checks whether this filter is generic or specific | 541 * Checks whether this filter is generic or specific |
| 569 * @return {boolean} | 542 * @return {boolean} |
| 570 */ | 543 */ |
| 571 isGeneric() | 544 isGeneric() |
| 572 { | 545 { |
| 573 return !(this.sitekeys && this.sitekeys.length) && | 546 return !(this.sitekeys && this.sitekeys.length) && |
| 574 (!this.domains || | 547 (!this.domains || this.domains.get("")); |
| 575 typeof this.domains != "string" && this.domains.get("")); | |
| 576 }, | 548 }, |
| 577 | 549 |
| 578 /** | 550 /** |
| 579 * See Filter.serialize() | 551 * See Filter.serialize() |
| 580 * @inheritdoc | 552 * @inheritdoc |
| 581 */ | 553 */ |
| 582 serialize(buffer) | 554 serialize(buffer) |
| 583 { | 555 { |
| 584 if (this._disabled || this._hitCount || this._lastHit) | 556 if (this._disabled || this._hitCount || this._lastHit) |
| 585 { | 557 { |
| (...skipping 93 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 679 { | 651 { |
| 680 // Despite this property being cached, the getter is called | 652 // Despite this property being cached, the getter is called |
| 681 // several times on Safari, due to WebKit bug 132872 | 653 // several times on Safari, due to WebKit bug 132872 |
| 682 let prop = Object.getOwnPropertyDescriptor(this, "regexp"); | 654 let prop = Object.getOwnPropertyDescriptor(this, "regexp"); |
| 683 if (prop) | 655 if (prop) |
| 684 return prop.value; | 656 return prop.value; |
| 685 | 657 |
| 686 let source = Filter.toRegExp(this.regexpSource); | 658 let source = Filter.toRegExp(this.regexpSource); |
| 687 let regexp = new RegExp(source, this.matchCase ? "" : "i"); | 659 let regexp = new RegExp(source, this.matchCase ? "" : "i"); |
| 688 Object.defineProperty(this, "regexp", {value: regexp}); | 660 Object.defineProperty(this, "regexp", {value: regexp}); |
| 689 delete this.regexpSource; | 661 this.regexpSource = null; |
| 690 return regexp; | 662 return regexp; |
| 691 }, | 663 }, |
| 692 /** | 664 /** |
| 693 * Content types the filter applies to, combination of values from | 665 * Content types the filter applies to, combination of values from |
| 694 * RegExpFilter.typeMap | 666 * RegExpFilter.typeMap |
| 695 * @type {number} | 667 * @type {number} |
| 696 */ | 668 */ |
| 697 contentType: 0x7FFFFFFF, | 669 contentType: 0x7FFFFFFF, |
| 698 /** | 670 /** |
| 699 * Defines whether the filter should distinguish between lower and | 671 * Defines whether the filter should distinguish between lower and |
| (...skipping 136 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 836 collapse = false; | 808 collapse = false; |
| 837 else if (option == "SITEKEY" && value) | 809 else if (option == "SITEKEY" && value) |
| 838 sitekeys = value.toUpperCase(); | 810 sitekeys = value.toUpperCase(); |
| 839 else if (option == "REWRITE" && value) | 811 else if (option == "REWRITE" && value) |
| 840 rewrite = value; | 812 rewrite = value; |
| 841 else | 813 else |
| 842 return new InvalidFilter(origText, "filter_unknown_option"); | 814 return new InvalidFilter(origText, "filter_unknown_option"); |
| 843 } | 815 } |
| 844 } | 816 } |
| 845 | 817 |
| 818 // For security reasons, never match $rewrite filters | |
| 819 // against requests that might load any code to be executed. | |
| 820 if (rewrite != null) | |
| 821 { | |
| 822 if (contentType == null) | |
| 823 ({contentType} = RegExpFilter.prototype); | |
| 824 contentType &= ~(RegExpFilter.typeMap.SCRIPT | | |
| 825 RegExpFilter.typeMap.SUBDOCUMENT | | |
| 826 RegExpFilter.typeMap.OBJECT | | |
| 827 RegExpFilter.typeMap.OBJECT_SUBREQUEST); | |
| 828 } | |
| 829 | |
| 846 try | 830 try |
| 847 { | 831 { |
| 848 if (blocking) | 832 if (blocking) |
| 849 { | 833 { |
| 850 if (csp && Filter.invalidCSPRegExp.test(csp)) | 834 if (csp && Filter.invalidCSPRegExp.test(csp)) |
| 851 return new InvalidFilter(origText, "filter_invalid_csp"); | 835 return new InvalidFilter(origText, "filter_invalid_csp"); |
| 852 | 836 |
| 853 return new BlockingFilter(origText, text, contentType, matchCase, domains, | 837 return new BlockingFilter(origText, text, contentType, matchCase, domains, |
| 854 thirdParty, sitekeys, collapse, csp, rewrite); | 838 thirdParty, sitekeys, collapse, csp, rewrite); |
| 855 } | 839 } |
| (...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 919 * BlockingFilter.prototype.rewrite. | 903 * BlockingFilter.prototype.rewrite. |
| 920 * @constructor | 904 * @constructor |
| 921 * @augments RegExpFilter | 905 * @augments RegExpFilter |
| 922 */ | 906 */ |
| 923 function BlockingFilter(text, regexpSource, contentType, matchCase, domains, | 907 function BlockingFilter(text, regexpSource, contentType, matchCase, domains, |
| 924 thirdParty, sitekeys, collapse, csp, rewrite) | 908 thirdParty, sitekeys, collapse, csp, rewrite) |
| 925 { | 909 { |
| 926 RegExpFilter.call(this, text, regexpSource, contentType, matchCase, domains, | 910 RegExpFilter.call(this, text, regexpSource, contentType, matchCase, domains, |
| 927 thirdParty, sitekeys); | 911 thirdParty, sitekeys); |
| 928 | 912 |
| 929 this.collapse = collapse; | 913 if (collapse != null) |
| 930 this.csp = csp; | 914 this.collapse = collapse; |
| 931 this.rewrite = rewrite; | 915 |
| 916 if (csp != null) | |
| 917 this.csp = csp; | |
| 918 | |
| 919 if (rewrite != null) | |
| 920 this.rewrite = rewrite; | |
| 932 } | 921 } |
| 933 exports.BlockingFilter = BlockingFilter; | 922 exports.BlockingFilter = BlockingFilter; |
| 934 | 923 |
| 935 BlockingFilter.prototype = extend(RegExpFilter, { | 924 BlockingFilter.prototype = extend(RegExpFilter, { |
| 936 type: "blocking", | 925 type: "blocking", |
| 937 | 926 |
| 938 /** | 927 /** |
| 939 * Defines whether the filter should collapse blocked content. | 928 * Defines whether the filter should collapse blocked content. |
| 940 * Can be null (use the global preference). | 929 * Can be null (use the global preference). |
| 941 * @type {?boolean} | 930 * @type {?boolean} |
| (...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1007 * restricted to | 996 * restricted to |
| 1008 * @param {string} selector CSS selector for the HTML elements that should be | 997 * @param {string} selector CSS selector for the HTML elements that should be |
| 1009 * hidden | 998 * hidden |
| 1010 * @constructor | 999 * @constructor |
| 1011 * @augments ActiveFilter | 1000 * @augments ActiveFilter |
| 1012 */ | 1001 */ |
| 1013 function ElemHideBase(text, domains, selector) | 1002 function ElemHideBase(text, domains, selector) |
| 1014 { | 1003 { |
| 1015 ActiveFilter.call(this, text, domains || null); | 1004 ActiveFilter.call(this, text, domains || null); |
| 1016 | 1005 |
| 1017 if (domains) | |
| 1018 { | |
| 1019 this.selectorDomains = domains.replace(/,~[^,]+/g, "") | |
| 1020 .replace(/^~[^,]+,?/, "").toLowerCase(); | |
| 1021 } | |
| 1022 | |
| 1023 // Braces are being escaped to prevent CSS rule injection. | 1006 // Braces are being escaped to prevent CSS rule injection. |
| 1024 this.selector = selector.replace("{", "\\7B ").replace("}", "\\7D "); | 1007 this.selector = selector.replace("{", "\\7B ").replace("}", "\\7D "); |
| 1025 } | 1008 } |
| 1026 exports.ElemHideBase = ElemHideBase; | 1009 exports.ElemHideBase = ElemHideBase; |
| 1027 | 1010 |
| 1028 ElemHideBase.prototype = extend(ActiveFilter, { | 1011 ElemHideBase.prototype = extend(ActiveFilter, { |
| 1029 /** | 1012 /** |
| 1030 * @see ActiveFilter.domainSeparator | 1013 * @see ActiveFilter.domainSeparator |
| 1031 */ | 1014 */ |
| 1032 domainSeparator: ",", | 1015 domainSeparator: ",", |
| 1033 | 1016 |
| 1034 /** | |
| 1035 * @see ActiveFilter.ignoreTrailingDot | |
| 1036 */ | |
| 1037 ignoreTrailingDot: false, | |
| 1038 | |
| 1039 /** | |
| 1040 * Host names or domains the filter should be restricted to (can be null for | |
| 1041 * no restriction) | |
| 1042 * @type {?string} | |
| 1043 */ | |
| 1044 selectorDomains: null, | |
| 1045 /** | 1017 /** |
| 1046 * CSS selector for the HTML elements that should be hidden | 1018 * CSS selector for the HTML elements that should be hidden |
| 1047 * @type {string} | 1019 * @type {string} |
| 1048 */ | 1020 */ |
| 1049 selector: null | 1021 selector: null |
| 1050 }); | 1022 }); |
| 1051 | 1023 |
| 1052 /** | 1024 /** |
| 1053 * Creates an element hiding filter from a pre-parsed text representation | 1025 * Creates an element hiding filter from a pre-parsed text representation |
| 1054 * | 1026 * |
| (...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1131 */ | 1103 */ |
| 1132 function ElemHideEmulationFilter(text, domains, selector) | 1104 function ElemHideEmulationFilter(text, domains, selector) |
| 1133 { | 1105 { |
| 1134 ElemHideBase.call(this, text, domains, selector); | 1106 ElemHideBase.call(this, text, domains, selector); |
| 1135 } | 1107 } |
| 1136 exports.ElemHideEmulationFilter = ElemHideEmulationFilter; | 1108 exports.ElemHideEmulationFilter = ElemHideEmulationFilter; |
| 1137 | 1109 |
| 1138 ElemHideEmulationFilter.prototype = extend(ElemHideBase, { | 1110 ElemHideEmulationFilter.prototype = extend(ElemHideBase, { |
| 1139 type: "elemhideemulation" | 1111 type: "elemhideemulation" |
| 1140 }); | 1112 }); |
| LEFT | RIGHT |