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-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 /** @module filterValidation */ | 18 /** @module filterValidation */ |
19 | 19 |
20 let {Filter, InvalidFilter, ElemHideBase} = require("filterClasses"); | 20 let {Filter, InvalidFilter, ElemHideBase} = require("filterClasses"); |
21 | 21 |
| 22 /** |
| 23 * An error returned by |
| 24 * {@link module:filterValidation.parseFilter parseFilter()} or |
| 25 * {@link module:filterValidation.parseFilters parseFilters()} |
| 26 * indicating that a given filter cannot be parsed, |
| 27 * contains an invalid CSS selector or is a filter list header. |
| 28 * |
| 29 * @constructor |
| 30 */ |
| 31 function FilterParsingError(type, details) |
| 32 { |
| 33 /** |
| 34 * Indicates why the filter is rejected. Possible choices: |
| 35 * "invalid-filter", "invalid-css-selector", "unexpected-filter-list-header" |
| 36 * |
| 37 * @type {string} |
| 38 */ |
| 39 this.type = type; |
| 40 |
| 41 if (details) |
| 42 { |
| 43 if ("reason" in details) |
| 44 this.reason = details.reason; |
| 45 if ("selector" in details) |
| 46 this.selector = details.selector; |
| 47 } |
| 48 } |
| 49 FilterParsingError.prototype = { |
| 50 /** |
| 51 * The line number the error occurred on if |
| 52 * {@link module:filterValidation.parseFilters parseFilters()} |
| 53 * were used. Or null if the error was returned by |
| 54 * {@link module:filterValidation.parseFilter parseFilter()}. |
| 55 * |
| 56 * @type {?number} |
| 57 */ |
| 58 lineno: null, |
| 59 |
| 60 /** |
| 61 * Returns a detailed translated error message. |
| 62 * |
| 63 * @return {string} |
| 64 */ |
| 65 toString: function() |
| 66 { |
| 67 let message = this.reason || ext.i18n.getMessage( |
| 68 this.type.replace(/-/g, "_"), |
| 69 "selector" in this ? "'" + this.selector + "'" : null |
| 70 ); |
| 71 |
| 72 if (this.lineno) |
| 73 message = ext.i18n.getMessage("line", this.lineno.toLocaleString()) + ": "
+ message; |
| 74 |
| 75 return message; |
| 76 } |
| 77 }; |
| 78 |
22 function isValidCSSSelector(selector) | 79 function isValidCSSSelector(selector) |
23 { | 80 { |
24 let style = document.createElement("style"); | 81 let style = document.createElement("style"); |
25 document.documentElement.appendChild(style); | 82 document.documentElement.appendChild(style); |
26 let sheet = style.sheet; | 83 let sheet = style.sheet; |
27 document.documentElement.removeChild(style); | 84 document.documentElement.removeChild(style); |
28 | 85 |
29 try | 86 try |
30 { | 87 { |
31 document.querySelector(selector); | 88 document.querySelector(selector); |
32 sheet.insertRule(selector + "{}", 0); | 89 sheet.insertRule(selector + "{}", 0); |
33 } | 90 } |
34 catch (e) | 91 catch (e) |
35 { | 92 { |
36 return false; | 93 return false; |
37 } | 94 } |
38 return true; | 95 return true; |
39 } | 96 } |
40 | 97 |
41 /** | 98 /** |
42 * @typedef ParsedFilter | 99 * @typedef ParsedFilter |
43 * @property {?Filter} [filter] The parsed filter if it is valid. Or null if | 100 * @property {?Filter} [filter] The parsed filter if it is valid. |
44 * the given string is empty or a filter list head
er. | 101 * Or null if the given string is empty. |
45 * @property {string} [error] An error message indicated that the filter cann
ot | 102 * @property {FilterParsingError} [error] See {@link module:filterValidation~Fi
lterParsingError FilterParsingError} |
46 * be parsed or contains an invalid CSS selector. | 103 * |
47 */ | 104 */ |
48 | 105 |
49 let parseFilter = | 106 let parseFilter = |
50 /** | 107 /** |
51 * Parses and validates a filter given by the user. | 108 * Parses and validates a filter given by the user. |
52 * | 109 * |
53 * @param {string} text | 110 * @param {string} text |
54 * @param {Boolean} [ignoreHeaders=false] If true, {filter: null} is | |
55 returned instead an error | |
56 for filter list headers. | |
57 * @return {ParsedFilter} | 111 * @return {ParsedFilter} |
58 */ | 112 */ |
59 exports.parseFilter = function(text, ignoreHeaders) | 113 exports.parseFilter = function(text) |
60 { | 114 { |
61 let filter = null; | 115 let filter = null; |
62 text = Filter.normalize(text); | 116 text = Filter.normalize(text); |
63 | 117 |
64 if (text) | 118 if (text) |
65 { | 119 { |
66 if (text[0] != "[") | 120 if (text[0] == "[") |
67 { | 121 return {error: new FilterParsingError("unexpected-filter-list-header")}; |
68 filter = Filter.fromText(text); | |
69 | 122 |
70 if (filter instanceof InvalidFilter) | 123 filter = Filter.fromText(text); |
71 return {error: filter.reason}; | |
72 | 124 |
73 if (filter instanceof ElemHideBase && !isValidCSSSelector(filter.selector)
) | 125 if (filter instanceof InvalidFilter) |
74 return {error: ext.i18n.getMessage("invalid_css_selector", "'" + filter.
selector + "'")}; | 126 return {error: new FilterParsingError("invalid-filter", {reason: filter.re
ason})}; |
75 } | 127 |
76 else if (!ignoreHeaders) | 128 if (filter instanceof ElemHideBase && !isValidCSSSelector(filter.selector)) |
77 { | 129 return {error: new FilterParsingError("invalid-css-selector", {selector: f
ilter.selector})}; |
78 return {error: ext.i18n.getMessage("unexpected_filter_list_header")}; | |
79 } | |
80 } | 130 } |
81 | 131 |
82 return {filter: filter}; | 132 return {filter: filter}; |
83 }; | 133 }; |
84 | 134 |
85 /** | 135 /** |
86 * @typedef ParsedFilters | 136 * @typedef ParsedFilters |
87 * @property {Filter[]} [filters] The parsed filters if all of them are valid. | 137 * @property {Filter[]} filters The parsed result without invalid filters. |
88 * @property {string} [error] An error message indicated that any filter ca
nnot | 138 * @property {FilterParsingError[]} errors See {@link module:filterValidation~F
ilterParsingError FilterParsingError} |
89 * be parsed or contains an invalid CSS selector
. | |
90 */ | 139 */ |
91 | 140 |
92 /** | 141 /** |
93 * Parses and validates a newline-separated list of filters given by the user. | 142 * Parses and validates a newline-separated list of filters given by the user. |
94 * | 143 * |
95 * @param {string} text | 144 * @param {string} text |
96 * @param {Boolean} [ignoreHeaders=false] If true, filter list headers | |
97 * will be stripped instead of | |
98 * returning an error. | |
99 * @return {ParsedFilters} | 145 * @return {ParsedFilters} |
100 */ | 146 */ |
101 exports.parseFilters = function(text, ignoreHeaders) | 147 exports.parseFilters = function(text) |
102 { | 148 { |
103 let lines = text.split("\n"); | 149 let lines = text.split("\n"); |
104 let filters = []; | 150 let filters = []; |
| 151 let errors = []; |
105 | 152 |
106 for (let i = 0; i < lines.length; i++) | 153 for (let i = 0; i < lines.length; i++) |
107 { | 154 { |
108 let {filter, error} = parseFilter(lines[i], ignoreHeaders); | 155 let {filter, error} = parseFilter(lines[i]); |
109 | |
110 if (error) | |
111 return {error: ext.i18n.getMessage("line", (i + 1).toString()) + ": " + er
ror}; | |
112 | 156 |
113 if (filter) | 157 if (filter) |
114 filters.push(filter); | 158 filters.push(filter); |
| 159 |
| 160 if (error) |
| 161 { |
| 162 error.lineno = i + 1; |
| 163 errors.push(error); |
| 164 } |
115 } | 165 } |
116 | 166 |
117 return {filters: filters}; | 167 return {filters: filters, errors: errors}; |
118 }; | 168 }; |
OLD | NEW |