| 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 |