OLD | NEW |
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 606 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
617 * Defines whether the filter should distinguish between lower and upper case | 617 * Defines whether the filter should distinguish between lower and upper case |
618 * letters | 618 * letters |
619 * @param {string} [domains] | 619 * @param {string} [domains] |
620 * Domains that the filter is restricted to, e.g. "foo.com|bar.com|~baz.com" | 620 * Domains that the filter is restricted to, e.g. "foo.com|bar.com|~baz.com" |
621 * @param {boolean} [thirdParty] | 621 * @param {boolean} [thirdParty] |
622 * Defines whether the filter should apply to third-party or first-party | 622 * Defines whether the filter should apply to third-party or first-party |
623 * content only | 623 * content only |
624 * @param {string} [sitekeys] | 624 * @param {string} [sitekeys] |
625 * Public keys of websites that this filter should apply to | 625 * Public keys of websites that this filter should apply to |
626 * @param {?string} [rewrite] | 626 * @param {?string} [rewrite] |
627 * The (optional) rule specifying how to rewrite the URL. See | |
628 * RegExpFilter.prototype.rewrite. | |
629 * @param {?string} [resourceName] | |
630 * The name of the internal resource to which to rewrite the | 627 * The name of the internal resource to which to rewrite the |
631 * URL. e.g. if the value of the <code>rewrite</code> parameter is | 628 * URL. e.g. if the value of the <code>$rewrite</code> option is |
632 * <code>abp-resource:blank-html</code>, this should be | 629 * <code>abp-resource:blank-html</code>, this should be |
633 * <code>blank-html</code>. | 630 * <code>blank-html</code>. |
634 * @constructor | 631 * @constructor |
635 * @augments ActiveFilter | 632 * @augments ActiveFilter |
636 */ | 633 */ |
637 function RegExpFilter(text, regexpSource, contentType, matchCase, domains, | 634 function RegExpFilter(text, regexpSource, contentType, matchCase, domains, |
638 thirdParty, sitekeys, rewrite, resourceName) | 635 thirdParty, sitekeys, rewrite) |
639 { | 636 { |
640 ActiveFilter.call(this, text, domains); | 637 ActiveFilter.call(this, text, domains); |
641 | 638 |
642 if (contentType != null) | 639 if (contentType != null) |
643 this.contentType = contentType; | 640 this.contentType = contentType; |
644 if (matchCase) | 641 if (matchCase) |
645 this.matchCase = matchCase; | 642 this.matchCase = matchCase; |
646 if (thirdParty != null) | 643 if (thirdParty != null) |
647 this.thirdParty = thirdParty; | 644 this.thirdParty = thirdParty; |
648 if (sitekeys != null) | 645 if (sitekeys != null) |
649 this.sitekeySource = sitekeys; | 646 this.sitekeySource = sitekeys; |
650 if (rewrite != null) | 647 if (rewrite != null) |
651 this.rewrite = rewrite; | 648 this.rewrite = rewrite; |
652 if (resourceName) | |
653 this.resourceName = resourceName; | |
654 | 649 |
655 if (!this.matchCase) | 650 if (!this.matchCase) |
656 regexpSource = regexpSource.toLowerCase(); | 651 regexpSource = regexpSource.toLowerCase(); |
657 | 652 |
658 if (regexpSource.length >= 2 && | 653 if (regexpSource.length >= 2 && |
659 regexpSource[0] == "/" && | 654 regexpSource[0] == "/" && |
660 regexpSource[regexpSource.length - 1] == "/") | 655 regexpSource[regexpSource.length - 1] == "/") |
661 { | 656 { |
662 // The filter is a regular expression - convert it immediately to | 657 // The filter is a regular expression - convert it immediately to |
663 // catch syntax errors | 658 // catch syntax errors |
664 let regexp = new RegExp(regexpSource.substring(1, regexpSource.length - 1)); | 659 let regexp = new RegExp(regexpSource.substring(1, regexpSource.length - 1)); |
665 Object.defineProperty(this, "regexp", {value: regexp}); | 660 Object.defineProperty(this, "regexp", {value: regexp}); |
666 } | 661 } |
667 else | 662 else |
668 { | 663 { |
669 // Patterns like /foo/bar/* exist so that they are not treated as regular | 664 // Patterns like /foo/bar/* exist so that they are not treated as regular |
670 // expressions. We drop any superfluous wildcards here so our optimizations | 665 // expressions. We drop any superfluous wildcards here so our optimizations |
671 // can kick in. | 666 // can kick in. |
672 if (this.rewrite == null || this.resourceName) | 667 regexpSource = regexpSource.replace(/^\*+/, "").replace(/\*+$/, ""); |
673 regexpSource = regexpSource.replace(/^\*+/, "").replace(/\*+$/, ""); | |
674 | 668 |
675 // No need to convert this filter to regular expression yet, do it on demand | 669 // No need to convert this filter to regular expression yet, do it on demand |
676 this.pattern = regexpSource; | 670 this.pattern = regexpSource; |
677 } | 671 } |
678 } | 672 } |
679 exports.RegExpFilter = RegExpFilter; | 673 exports.RegExpFilter = RegExpFilter; |
680 | 674 |
681 RegExpFilter.prototype = extend(ActiveFilter, { | 675 RegExpFilter.prototype = extend(ActiveFilter, { |
682 /** | 676 /** |
683 * Number of filters contained, will always be 1 (required to | 677 * Number of filters contained, will always be 1 (required to |
(...skipping 15 matching lines...) Expand all Loading... |
699 */ | 693 */ |
700 pattern: null, | 694 pattern: null, |
701 /** | 695 /** |
702 * Regular expression to be used when testing against this filter | 696 * Regular expression to be used when testing against this filter |
703 * @type {RegExp} | 697 * @type {RegExp} |
704 */ | 698 */ |
705 get regexp() | 699 get regexp() |
706 { | 700 { |
707 let value = null; | 701 let value = null; |
708 | 702 |
709 let {pattern, rewrite, resourceName} = this; | 703 let {pattern} = this; |
710 if ((rewrite != null && !resourceName) || !isLiteralPattern(pattern)) | 704 if (!isLiteralPattern(pattern)) |
711 value = new RegExp(filterToRegExp(pattern, rewrite != null)); | 705 value = new RegExp(filterToRegExp(pattern)); |
712 | 706 |
713 Object.defineProperty(this, "regexp", {value}); | 707 Object.defineProperty(this, "regexp", {value}); |
714 return value; | 708 return value; |
715 }, | 709 }, |
716 /** | 710 /** |
717 * Content types the filter applies to, combination of values from | 711 * Content types the filter applies to, combination of values from |
718 * RegExpFilter.typeMap | 712 * RegExpFilter.typeMap |
719 * @type {number} | 713 * @type {number} |
720 */ | 714 */ |
721 contentType: 0x7FFFFFFF, | 715 contentType: 0x7FFFFFFF, |
(...skipping 29 matching lines...) Expand all Loading... |
751 this.sitekeySource = null; | 745 this.sitekeySource = null; |
752 } | 746 } |
753 | 747 |
754 Object.defineProperty( | 748 Object.defineProperty( |
755 this, "sitekeys", {value: sitekeys, enumerable: true} | 749 this, "sitekeys", {value: sitekeys, enumerable: true} |
756 ); | 750 ); |
757 return this.sitekeys; | 751 return this.sitekeys; |
758 }, | 752 }, |
759 | 753 |
760 /** | 754 /** |
761 * The rule specifying how to rewrite the URL. | |
762 * The syntax is similar to the one of String.prototype.replace(). | |
763 * @type {?string} | |
764 */ | |
765 rewrite: null, | |
766 | |
767 /** | |
768 * The name of the internal resource to which to rewrite the | 755 * The name of the internal resource to which to rewrite the |
769 * URL. e.g. if the value of the <code>rewrite</code> property is | 756 * URL. e.g. if the value of the <code>$rewrite</code> option is |
770 * <code>abp-resource:blank-html</code>, this should be | 757 * <code>abp-resource:blank-html</code>, this should be |
771 * <code>blank-html</code>. | 758 * <code>blank-html</code>. |
772 * @type {?string} | 759 * @type {?string} |
773 */ | 760 */ |
774 resourceName: null, | 761 rewrite: null, |
775 | 762 |
776 /** | 763 /** |
777 * Tests whether the URL request matches this filter | 764 * Tests whether the URL request matches this filter |
778 * @param {URLRequest} request URL request to be tested | 765 * @param {URLRequest} request URL request to be tested |
779 * @param {number} typeMask bitmask of content / request types to match | 766 * @param {number} typeMask bitmask of content / request types to match |
780 * @param {string} [sitekey] public key provided by the document | 767 * @param {string} [sitekey] public key provided by the document |
781 * @return {boolean} true in case of a match | 768 * @return {boolean} true in case of a match |
782 */ | 769 */ |
783 matches(request, typeMask, sitekey) | 770 matches(request, typeMask, sitekey) |
784 { | 771 { |
(...skipping 112 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
897 } | 884 } |
898 | 885 |
899 let contentType = null; | 886 let contentType = null; |
900 let matchCase = null; | 887 let matchCase = null; |
901 let domains = null; | 888 let domains = null; |
902 let sitekeys = null; | 889 let sitekeys = null; |
903 let thirdParty = null; | 890 let thirdParty = null; |
904 let collapse = null; | 891 let collapse = null; |
905 let csp = null; | 892 let csp = null; |
906 let rewrite = null; | 893 let rewrite = null; |
907 let resourceName = null; | |
908 let options; | 894 let options; |
909 let match = text.includes("$") ? Filter.optionsRegExp.exec(text) : null; | 895 let match = text.includes("$") ? Filter.optionsRegExp.exec(text) : null; |
910 if (match) | 896 if (match) |
911 { | 897 { |
912 options = match[1].split(","); | 898 options = match[1].split(","); |
913 text = match.input.substring(0, match.index); | 899 text = match.input.substring(0, match.index); |
914 for (let option of options) | 900 for (let option of options) |
915 { | 901 { |
916 let value = null; | 902 let value = null; |
917 let separatorIndex = option.indexOf("="); | 903 let separatorIndex = option.indexOf("="); |
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
965 collapse = !inverse; | 951 collapse = !inverse; |
966 break; | 952 break; |
967 case "sitekey": | 953 case "sitekey": |
968 if (!value) | 954 if (!value) |
969 return new InvalidFilter(origText, "filter_unknown_option"); | 955 return new InvalidFilter(origText, "filter_unknown_option"); |
970 sitekeys = value.toUpperCase(); | 956 sitekeys = value.toUpperCase(); |
971 break; | 957 break; |
972 case "rewrite": | 958 case "rewrite": |
973 if (value == null) | 959 if (value == null) |
974 return new InvalidFilter(origText, "filter_unknown_option"); | 960 return new InvalidFilter(origText, "filter_unknown_option"); |
975 rewrite = value; | 961 if (!value.startsWith("abp-resource:")) |
976 if (value.startsWith("abp-resource:")) | 962 return new InvalidFilter(origText, "filter_invalid_rewrite"); |
977 resourceName = value.substring("abp-resource:".length); | 963 rewrite = value.substring("abp-resource:".length); |
978 break; | 964 break; |
979 default: | 965 default: |
980 return new InvalidFilter(origText, "filter_unknown_option"); | 966 return new InvalidFilter(origText, "filter_unknown_option"); |
981 } | 967 } |
982 } | 968 } |
983 } | 969 } |
984 } | 970 } |
985 | 971 |
986 // For security reasons, never match $rewrite filters | |
987 // against requests that might load any code to be executed. | |
988 // Unless it is to an internal resource. | |
989 if (rewrite != null && !resourceName) | |
990 { | |
991 if (contentType == null) | |
992 ({contentType} = RegExpFilter.prototype); | |
993 contentType &= ~(RegExpFilter.typeMap.SCRIPT | | |
994 RegExpFilter.typeMap.SUBDOCUMENT | | |
995 RegExpFilter.typeMap.OBJECT | | |
996 RegExpFilter.typeMap.OBJECT_SUBREQUEST); | |
997 } | |
998 | |
999 try | 972 try |
1000 { | 973 { |
1001 if (blocking) | 974 if (blocking) |
1002 { | 975 { |
1003 if (csp && Filter.invalidCSPRegExp.test(csp)) | 976 if (csp && Filter.invalidCSPRegExp.test(csp)) |
1004 return new InvalidFilter(origText, "filter_invalid_csp"); | 977 return new InvalidFilter(origText, "filter_invalid_csp"); |
1005 | 978 |
1006 if (resourceName) | 979 if (rewrite) |
1007 { | 980 { |
1008 if (text[0] == "|" && text[1] == "|") | 981 if (text[0] == "|" && text[1] == "|") |
1009 { | 982 { |
1010 if (!domains && thirdParty != false) | 983 if (!domains && thirdParty != false) |
1011 return new InvalidFilter(origText, "filter_invalid_rewrite"); | 984 return new InvalidFilter(origText, "filter_invalid_rewrite"); |
1012 } | 985 } |
1013 else if (text[0] == "*") | 986 else if (text[0] == "*") |
1014 { | 987 { |
1015 if (!domains) | 988 if (!domains) |
1016 return new InvalidFilter(origText, "filter_invalid_rewrite"); | 989 return new InvalidFilter(origText, "filter_invalid_rewrite"); |
1017 } | 990 } |
1018 else | 991 else |
1019 { | 992 { |
1020 return new InvalidFilter(origText, "filter_invalid_rewrite"); | 993 return new InvalidFilter(origText, "filter_invalid_rewrite"); |
1021 } | 994 } |
1022 } | 995 } |
1023 | 996 |
1024 return new BlockingFilter(origText, text, contentType, matchCase, domains, | 997 return new BlockingFilter(origText, text, contentType, matchCase, domains, |
1025 thirdParty, sitekeys, rewrite, resourceName, | 998 thirdParty, sitekeys, rewrite, |
1026 collapse, csp); | 999 collapse, csp); |
1027 } | 1000 } |
1028 return new WhitelistFilter(origText, text, contentType, matchCase, domains, | 1001 return new WhitelistFilter(origText, text, contentType, matchCase, domains, |
1029 thirdParty, sitekeys); | 1002 thirdParty, sitekeys); |
1030 } | 1003 } |
1031 catch (e) | 1004 catch (e) |
1032 { | 1005 { |
1033 return new InvalidFilter(origText, "filter_invalid_regexp"); | 1006 return new InvalidFilter(origText, "filter_invalid_regexp"); |
1034 } | 1007 } |
1035 }; | 1008 }; |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1076 /** | 1049 /** |
1077 * Class for blocking filters | 1050 * Class for blocking filters |
1078 * @param {string} text see {@link Filter Filter()} | 1051 * @param {string} text see {@link Filter Filter()} |
1079 * @param {string} regexpSource see {@link RegExpFilter RegExpFilter()} | 1052 * @param {string} regexpSource see {@link RegExpFilter RegExpFilter()} |
1080 * @param {number} [contentType] see {@link RegExpFilter RegExpFilter()} | 1053 * @param {number} [contentType] see {@link RegExpFilter RegExpFilter()} |
1081 * @param {boolean} [matchCase] see {@link RegExpFilter RegExpFilter()} | 1054 * @param {boolean} [matchCase] see {@link RegExpFilter RegExpFilter()} |
1082 * @param {string} [domains] see {@link RegExpFilter RegExpFilter()} | 1055 * @param {string} [domains] see {@link RegExpFilter RegExpFilter()} |
1083 * @param {boolean} [thirdParty] see {@link RegExpFilter RegExpFilter()} | 1056 * @param {boolean} [thirdParty] see {@link RegExpFilter RegExpFilter()} |
1084 * @param {string} [sitekeys] see {@link RegExpFilter RegExpFilter()} | 1057 * @param {string} [sitekeys] see {@link RegExpFilter RegExpFilter()} |
1085 * @param {?string} [rewrite] | 1058 * @param {?string} [rewrite] |
1086 * The (optional) rule specifying how to rewrite the URL. See | |
1087 * RegExpFilter.prototype.rewrite. | |
1088 * @param {?string} [resourceName] | |
1089 * The name of the internal resource to which to rewrite the | 1059 * The name of the internal resource to which to rewrite the |
1090 * URL. e.g. if the value of the <code>rewrite</code> parameter is | 1060 * URL. e.g. if the value of the <code>$rewrite</code> option is |
1091 * <code>abp-resource:blank-html</code>, this should be | 1061 * <code>abp-resource:blank-html</code>, this should be |
1092 * <code>blank-html</code>. | 1062 * <code>blank-html</code>. |
1093 * @param {boolean} [collapse] | 1063 * @param {boolean} [collapse] |
1094 * defines whether the filter should collapse blocked content, can be null | 1064 * defines whether the filter should collapse blocked content, can be null |
1095 * @param {string} [csp] | 1065 * @param {string} [csp] |
1096 * Content Security Policy to inject when the filter matches | 1066 * Content Security Policy to inject when the filter matches |
1097 * @constructor | 1067 * @constructor |
1098 * @augments RegExpFilter | 1068 * @augments RegExpFilter |
1099 */ | 1069 */ |
1100 function BlockingFilter(text, regexpSource, contentType, matchCase, domains, | 1070 function BlockingFilter(text, regexpSource, contentType, matchCase, domains, |
1101 thirdParty, sitekeys, rewrite, resourceName, | 1071 thirdParty, sitekeys, rewrite, |
1102 collapse, csp) | 1072 collapse, csp) |
1103 { | 1073 { |
1104 RegExpFilter.call(this, text, regexpSource, contentType, matchCase, domains, | 1074 RegExpFilter.call(this, text, regexpSource, contentType, matchCase, domains, |
1105 thirdParty, sitekeys, rewrite, resourceName); | 1075 thirdParty, sitekeys, rewrite); |
1106 | 1076 |
1107 if (collapse != null) | 1077 if (collapse != null) |
1108 this.collapse = collapse; | 1078 this.collapse = collapse; |
1109 | 1079 |
1110 if (csp != null) | 1080 if (csp != null) |
1111 this.csp = csp; | 1081 this.csp = csp; |
1112 } | 1082 } |
1113 exports.BlockingFilter = BlockingFilter; | 1083 exports.BlockingFilter = BlockingFilter; |
1114 | 1084 |
1115 BlockingFilter.prototype = extend(RegExpFilter, { | 1085 BlockingFilter.prototype = extend(RegExpFilter, { |
(...skipping 12 matching lines...) Expand all Loading... |
1128 */ | 1098 */ |
1129 csp: null, | 1099 csp: null, |
1130 | 1100 |
1131 /** | 1101 /** |
1132 * Rewrites an URL. | 1102 * Rewrites an URL. |
1133 * @param {string} url the URL to rewrite | 1103 * @param {string} url the URL to rewrite |
1134 * @return {string} the rewritten URL, or the original in case of failure | 1104 * @return {string} the rewritten URL, or the original in case of failure |
1135 */ | 1105 */ |
1136 rewriteUrl(url) | 1106 rewriteUrl(url) |
1137 { | 1107 { |
1138 if (this.resourceName) | 1108 return resourceMap.get(this.rewrite) || url; |
1139 return resourceMap.get(this.resourceName) || url; | |
1140 | |
1141 try | |
1142 { | |
1143 let rewrittenUrl = new URL(url.replace(this.regexp, this.rewrite), url); | |
1144 if (rewrittenUrl.origin == new URL(url).origin) | |
1145 return rewrittenUrl.href; | |
1146 } | |
1147 catch (e) | |
1148 { | |
1149 } | |
1150 | |
1151 return url; | |
1152 } | 1109 } |
1153 }); | 1110 }); |
1154 | 1111 |
1155 /** | 1112 /** |
1156 * Class for whitelist filters | 1113 * Class for whitelist filters |
1157 * @param {string} text see {@link Filter Filter()} | 1114 * @param {string} text see {@link Filter Filter()} |
1158 * @param {string} regexpSource see {@link RegExpFilter RegExpFilter()} | 1115 * @param {string} regexpSource see {@link RegExpFilter RegExpFilter()} |
1159 * @param {number} [contentType] see {@link RegExpFilter RegExpFilter()} | 1116 * @param {number} [contentType] see {@link RegExpFilter RegExpFilter()} |
1160 * @param {boolean} [matchCase] see {@link RegExpFilter RegExpFilter()} | 1117 * @param {boolean} [matchCase] see {@link RegExpFilter RegExpFilter()} |
1161 * @param {string} [domains] see {@link RegExpFilter RegExpFilter()} | 1118 * @param {string} [domains] see {@link RegExpFilter RegExpFilter()} |
(...skipping 193 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1355 | 1312 |
1356 /** | 1313 /** |
1357 * Script that should be executed | 1314 * Script that should be executed |
1358 * @type {string} | 1315 * @type {string} |
1359 */ | 1316 */ |
1360 get script() | 1317 get script() |
1361 { | 1318 { |
1362 return this.body; | 1319 return this.body; |
1363 } | 1320 } |
1364 }); | 1321 }); |
OLD | NEW |