";
23 |
24 | export function createRequestFilters(data) {
25 | if (!data || !data.pattern) {
26 | return [];
27 | }
28 |
29 | if (!data.pattern.allUrls && data.pattern.anyTLD) {
30 | return createAnyTldRequestFilters(data);
31 | }
32 |
33 | return [
34 | {
35 | rule: createRule(data),
36 | urls: createMatchPatterns(data.pattern),
37 | matcher: createRequestMatcher(data.pattern),
38 | types: data.types,
39 | incognito: data.pattern.incognito,
40 | },
41 | ];
42 | }
43 |
44 | export function createRequestMatcher(pattern, hostnamesWithoutSuffix = []) {
45 | const matchers = [];
46 |
47 | if (pattern.includes) {
48 | for (const value of pattern.includes) {
49 | matchers.push(new IncludeMatcher([value]));
50 | }
51 | }
52 |
53 | if (pattern.excludes) {
54 | matchers.push(new ExcludeMatcher(pattern.excludes));
55 | }
56 |
57 | switch (pattern.origin) {
58 | case "same-domain": {
59 | matchers.push(DomainMatcher);
60 | break;
61 | }
62 | case "same-origin": {
63 | matchers.push(OriginMatcher);
64 | break;
65 | }
66 | case "third-party-domain": {
67 | matchers.push(ThirdPartyDomainMatcher);
68 | break;
69 | }
70 | case "third-party-origin": {
71 | matchers.push(ThirdPartyOriginMatcher);
72 | break;
73 | }
74 | }
75 |
76 | if (hostnamesWithoutSuffix.length > 0) {
77 | matchers.push(new HostnamesWithoutSuffixMatcher(hostnamesWithoutSuffix));
78 | }
79 |
80 | return matchers.length > 0 ? new RequestMatcher(matchers) : BaseMatcher;
81 | }
82 |
83 | export function createRule(data) {
84 | switch (data.action) {
85 | case "whitelist":
86 | if (data.log) {
87 | return new LoggedWhitelistRule(data);
88 | }
89 | return new WhitelistRule(data);
90 | case "block":
91 | return new BlockRule(data);
92 | case "redirect":
93 | return new RedirectRule(data);
94 | case "filter":
95 | return new FilterRule(data);
96 | case "secure":
97 | return new SecureRule(data);
98 | default:
99 | throw new Error("Unsupported rule action");
100 | }
101 | }
102 |
103 | /**
104 | * Construct array of match patterns
105 | * @param pattern pattern of request control rule
106 | * @returns {*} array of match patterns
107 | */
108 | export function createMatchPatterns(pattern) {
109 | const urls = [];
110 | const hosts = Array.isArray(pattern.host) ? pattern.host : [pattern.host];
111 | let paths = Array.isArray(pattern.path) ? pattern.path : [pattern.path];
112 |
113 | if (pattern.allUrls) {
114 | return [ALL_URLS];
115 | }
116 |
117 | if (!pattern.path || paths.length <= 0) {
118 | paths = [""];
119 | }
120 |
121 | for (let host of hosts) {
122 | if (isTLDHostPattern(host)) {
123 | host = host.slice(0, -1);
124 | for (const TLD of pattern.topLevelDomains) {
125 | for (const path of paths) {
126 | urls.push(`${pattern.scheme}://${host}${TLD}${prefixPath(path)}`);
127 | }
128 | }
129 | } else {
130 | for (const path of paths) {
131 | urls.push(`${pattern.scheme}://${host}${prefixPath(path)}`);
132 | }
133 | }
134 | }
135 |
136 | return urls;
137 | }
138 |
139 | function createAnyTldRequestFilters(data) {
140 | const filters = [];
141 | const rule = createRule(data);
142 | const hosts = Array.isArray(data.pattern.host) ? data.pattern.host : [data.pattern.host];
143 |
144 | const withoutSuffix = [];
145 | const withSuffix = [];
146 |
147 | hosts.forEach((host) => (isTLDHostPattern(host) ? withoutSuffix : withSuffix).push(host));
148 |
149 | if (withoutSuffix.length > 0) {
150 | filters.push({
151 | rule,
152 | urls: createMatchPatterns({
153 | scheme: data.pattern.scheme,
154 | host: "*",
155 | path: data.pattern.path,
156 | }),
157 | matcher: createRequestMatcher(data.pattern, withoutSuffix),
158 | types: data.types,
159 | incognito: data.pattern.incognito,
160 | });
161 | }
162 |
163 | if (withSuffix.length > 0) {
164 | filters.push({
165 | rule,
166 | urls: createMatchPatterns({
167 | scheme: data.pattern.scheme,
168 | host: withSuffix,
169 | path: data.pattern.path,
170 | }),
171 | matcher: createRequestMatcher(data.pattern),
172 | types: data.types,
173 | incognito: data.pattern.incognito,
174 | });
175 | }
176 |
177 | return filters;
178 | }
179 |
180 | export function isTLDHostPattern(host) {
181 | const hostTLDWildcardPattern = /^(.+)\.\*$/;
182 | return hostTLDWildcardPattern.test(host);
183 | }
184 |
185 | function prefixPath(path) {
186 | return path.startsWith("/") ? path : `/${path}`;
187 | }
188 |
--------------------------------------------------------------------------------
/src/options/rule-list.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | import { newRuleInput } from "./rule-input.js";
6 |
7 | class RuleList extends HTMLElement {
8 | constructor() {
9 | super();
10 | const template = document.getElementById("rule-list");
11 | this.appendChild(template.content.cloneNode(true));
12 |
13 | this.list = this.querySelector("#list");
14 | this.querySelector("#icon").src = this.getAttribute("icon");
15 | this.querySelector("#title").textContent = browser.i18n.getMessage(this.getAttribute("text"));
16 | this.querySelector("#collapse").addEventListener("click", () => this.collapse());
17 | this.querySelector("#select-all").addEventListener("change", (e) => this.onSelectAll(e));
18 |
19 | this.addEventListener("rule-selected", () => this.updateHeader());
20 | this.addEventListener("rule-deleted", (e) => this.onDelete(e));
21 | this.addEventListener("rule-edit-completed", (e) => this.onEditComplete(e));
22 | this.addEventListener("rule-action-changed", (e) => this.onActionChange(e));
23 | this.addEventListener("rule-created", (e) => this.onCreate(e));
24 | this.addEventListener("rule-changed", (e) => this.onchange(e));
25 | this.addEventListener("rule-invalid", (e) => this.onInvalid(e));
26 | }
27 |
28 | get selected() {
29 | return Array.from(this.list.querySelectorAll(".selected"), (selected) => selected.rule);
30 | }
31 |
32 | get size() {
33 | return this.list.childElementCount;
34 | }
35 |
36 | get isEmpty() {
37 | return this.list.childElementCount === 0;
38 | }
39 |
40 | newRule() {
41 | const input = newRuleInput();
42 | this.list.append(input);
43 | this.updateHeader();
44 | this.toggle();
45 | input.setAttribute("new", "new");
46 | input.toggleEdit();
47 | input.scrollIntoView();
48 | input.focus();
49 | }
50 |
51 | add(rule) {
52 | const ruleInput = newRuleInput(rule);
53 | const { title } = ruleInput;
54 |
55 | if (this.size === 0 || this.list.lastElementChild.title.localeCompare(title) < 0) {
56 | this.list.append(ruleInput);
57 | return;
58 | }
59 |
60 | for (const next of this.list.childNodes) {
61 | if (next.title.localeCompare(title) >= 0) {
62 | next.before(ruleInput);
63 | break;
64 | }
65 | }
66 | }
67 |
68 | addCreated(rule) {
69 | this.add(rule);
70 | this.updateHeader();
71 | this.toggle();
72 | }
73 |
74 | addFrom(input) {
75 | this.add(input.rule);
76 | const newInput = this.querySelector(`#rule-${input.rule.uuid}`);
77 | newInput.selected = input.selected;
78 | newInput.toggleSaved();
79 | this.updateHeader();
80 | this.toggle();
81 | }
82 |
83 | toggle() {
84 | this.classList.toggle("d-none", this.size === 0);
85 | }
86 |
87 | collapse() {
88 | this.querySelector("#collapse").classList.toggle("collapsed");
89 | this.list.classList.toggle("collapsed");
90 | }
91 |
92 | removeSelected() {
93 | this.list.querySelectorAll(".selected").forEach((ruleInput) => ruleInput.remove());
94 | this.updateHeader();
95 | this.toggle();
96 | }
97 |
98 | removeAll() {
99 | while (this.list.lastChild) {
100 | this.list.lastChild.remove();
101 | }
102 | }
103 |
104 | edit(uuid) {
105 | const rule = this.querySelector(`#rule-${uuid}`);
106 | if (rule) {
107 | rule.toggleEdit();
108 | rule.scrollIntoView();
109 | }
110 | }
111 |
112 | mark(rules, className) {
113 | rules.forEach((rule) => {
114 | const input = this.querySelector(`#rule-${rule.uuid}`);
115 | if (input) {
116 | input.classList.add(className);
117 | }
118 | });
119 | }
120 |
121 | updateHeader() {
122 | const checkbox = this.querySelector("#select-all");
123 |
124 | if (!this.list.querySelector(".selected")) {
125 | checkbox.checked = false;
126 | checkbox.indeterminate = false;
127 | } else if (!this.list.querySelector(":scope > :not(.selected)")) {
128 | checkbox.checked = true;
129 | checkbox.indeterminate = false;
130 | } else {
131 | checkbox.checked = false;
132 | checkbox.indeterminate = true;
133 | }
134 | this.updateSelectedText();
135 | }
136 |
137 | updateSelectedText() {
138 | const count = this.list.querySelectorAll(".selected").length;
139 | const selectedText = this.querySelector("#selected-text");
140 | selectedText.classList.toggle("d-none", count === 0);
141 | selectedText.textContent = browser.i18n.getMessage("selected_rules_count", [count, this.size]);
142 | }
143 |
144 | onSelectAll(e) {
145 | const { checked } = e.target;
146 | this.list.childNodes.forEach((rule) => {
147 | rule.selected = checked;
148 | });
149 | this.updateSelectedText();
150 | this.dispatchEvent(
151 | new CustomEvent("rule-selected", {
152 | bubbles: true,
153 | composed: true,
154 | })
155 | );
156 | }
157 |
158 | onCreate(e) {
159 | e.target.remove();
160 | this.updateHeader();
161 | this.toggle();
162 | }
163 |
164 | onDelete(e) {
165 | e.target.remove();
166 | this.updateHeader();
167 | this.toggle();
168 | }
169 |
170 | onEditComplete(e) {
171 | const { action } = e.detail;
172 | if (action !== this.id) {
173 | e.target.remove();
174 | this.updateHeader();
175 | this.toggle();
176 | }
177 | }
178 |
179 | onchange(e) {
180 | if (this.id === "new") {
181 | e.stopPropagation();
182 | }
183 | }
184 |
185 | onActionChange(e) {
186 | const { input } = e.detail;
187 | const newInput = newRuleInput(input.rule);
188 |
189 | if (this.id === "new") {
190 | newInput.setAttribute("new", "new");
191 | }
192 |
193 | input.replaceWith(newInput);
194 | newInput.selected = input.selected;
195 | newInput.toggleEdit();
196 | newInput.notifyChangedIfValid();
197 | }
198 |
199 | onInvalid(e) {
200 | const { input } = e.detail;
201 | if (this.id !== "new") {
202 | input.reportValidity();
203 | }
204 | }
205 | }
206 |
207 | customElements.define("rule-list", RuleList);
208 |
--------------------------------------------------------------------------------
/src/options/rule-input.css:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | form .btn:not(button) {
6 | border: none;
7 | padding: 0;
8 | background: none;
9 | color: black;
10 | }
11 |
12 | form .btn > input {
13 | margin-right: 0.5em;
14 | }
15 |
16 | form .card,
17 | form .tags-input,
18 | form .card:focus {
19 | border: 1px solid #ccc;
20 | background: white;
21 | padding: 0.2em;
22 | font-size: 0.9em;
23 | border-radius: 2px;
24 | }
25 |
26 | form select.scheme,
27 | form select.scheme:focus {
28 | padding-top: 0.5em;
29 | padding-bottom: 0.5em;
30 | }
31 |
32 | form .row {
33 | border-spacing: 0;
34 | }
35 |
36 | .row {
37 | margin-bottom: 0.3em !important;
38 | }
39 |
40 | .form-header {
41 | font-weight: bold;
42 | margin-bottom: 0.3em !important;
43 | }
44 |
45 | .rule-select {
46 | margin: 0 0.8rem;
47 | display: flex;
48 | }
49 |
50 | .rule-select > input {
51 | margin: 0;
52 | }
53 |
54 | .disabled:not(.editing) > .rule-header {
55 | color: grey;
56 | }
57 |
58 | .disabled .btn-activate {
59 | background: green;
60 | border-color: green;
61 | color: white;
62 | }
63 |
64 | .rule-header {
65 | border-top: 1px solid #cfcfcf;
66 | line-height: 1.3;
67 | display: flex;
68 | vertical-align: middle;
69 | align-items: center;
70 | padding: 0.1em 0;
71 | }
72 |
73 | .disabled:not(.editing) > .rule-header {
74 | background: #f7f7f7;
75 | }
76 |
77 | .selected:not(.editing) > .rule-header {
78 | background: #e0e9f7;
79 | }
80 |
81 | .rule-header-title {
82 | flex-grow: 1;
83 | text-overflow: ellipsis;
84 | overflow: hidden;
85 | margin-bottom: 0;
86 | }
87 |
88 | .rule-header > .information {
89 | text-align: right;
90 | vertical-align: middle;
91 | }
92 |
93 | .rule-header > .information > .badge {
94 | white-space: nowrap;
95 | overflow: -moz-hidden-unscrollable;
96 | text-overflow: ellipsis;
97 | }
98 |
99 | .editing .rule-header {
100 | border-bottom: 1px solid #f5f5f5;
101 | padding: 0.3em 0;
102 | }
103 |
104 | .editing .rule-header > div:first-child > div {
105 | max-height: 10em;
106 | overflow-y: auto;
107 | }
108 |
109 | .btn-delete {
110 | float: right;
111 | }
112 |
113 | .rule-input-buttons {
114 | padding-top: 0.3em;
115 | padding-bottom: 0.3em;
116 | }
117 |
118 | .rule-input {
119 | padding-top: 0.3em;
120 | }
121 |
122 | .title:not([contenteditable="true"]) {
123 | white-space: nowrap;
124 | text-overflow: ellipsis;
125 | overflow: hidden;
126 | display: inline-block;
127 | max-width: 48em;
128 | }
129 |
130 | .rule-header div[class$="-wrap"] {
131 | display: flex;
132 | align-items: baseline;
133 | user-select: none;
134 | }
135 |
136 | .editing .rule-header div[class$="-wrap"] {
137 | display: block;
138 | }
139 |
140 | .rule-header div[class$="-wrap"] > .badge {
141 | padding: 0;
142 | }
143 |
144 | .rule-header .title[contenteditable="true"],
145 | .rule-header .description[contenteditable="true"],
146 | .rule-header .tag[contenteditable="true"] {
147 | color: #4b4b4b;
148 | }
149 |
150 | .rule-header .title[contenteditable="true"]:hover,
151 | .rule-header .description[contenteditable="true"]:hover,
152 | .rule-header .tag[contenteditable="true"]:hover,
153 | .rule-header .title[contenteditable="true"]:focus,
154 | .rule-header .description[contenteditable="true"]:focus,
155 | .rule-header .tag[contenteditable="true"]:focus {
156 | color: black;
157 | }
158 |
159 | .header-info-wrap {
160 | display: flex;
161 | align-items: center;
162 | text-align: end;
163 | }
164 |
165 | .col-trim-parameters {
166 | display: flex;
167 | }
168 |
169 | .col-trim-parameters > :first-child {
170 | flex: 1;
171 | }
172 |
173 | .more-types {
174 | margin: 0 !important;
175 | }
176 |
177 | .redirectUrl {
178 | padding: 0.4em !important;
179 | box-sizing: border-box;
180 | }
181 |
182 | .tags-input {
183 | margin-bottom: 0.3em !important;
184 | }
185 |
186 | @media (min-width: 35em) {
187 | .rule-header-buttons {
188 | min-width: 10em;
189 | text-align: center;
190 | }
191 |
192 | .title:hover {
193 | text-decoration: underline;
194 | cursor: pointer;
195 | }
196 |
197 | .editing .title:hover {
198 | text-decoration: initial;
199 | cursor: initial;
200 | }
201 |
202 | .new:not(.editing) > .rule-header,
203 | .saved:not(.editing) > .rule-header {
204 | border-left: 2px solid green;
205 | }
206 |
207 | .error > .rule-header {
208 | border-left: 2px solid red;
209 | }
210 |
211 | .merged:not(.editing) > .rule-header {
212 | border-left: 2px solid orange;
213 | }
214 |
215 | .form-group-pattern {
216 | display: flex;
217 | margin: 0;
218 | flex-flow: row wrap;
219 | }
220 |
221 | .form-group-pattern > div {
222 | flex-grow: 1;
223 | }
224 |
225 | #tlds-form,
226 | .form-group-pattern > div {
227 | margin-right: 1em;
228 | }
229 |
230 | .form-group-pattern > div:first-child {
231 | flex-grow: 0;
232 | }
233 |
234 | .form-wrap {
235 | display: flex;
236 | align-items: center;
237 | }
238 |
239 | .form-wrap > div:first-child {
240 | flex-grow: 1;
241 | }
242 |
243 | .form-wrap .any-wrap {
244 | min-width: 6em;
245 | white-space: nowrap;
246 | display: flex;
247 | }
248 |
249 | .url-wrap:not(.d-none) + div {
250 | margin-top: 2em !important;
251 | align-self: flex-start;
252 | }
253 | }
254 |
255 | @media (max-width: 35em) {
256 | .editing > .rule-header div[class$="-wrap"] {
257 | display: flex;
258 | align-items: flex-start;
259 | flex-direction: column;
260 | margin-bottom: 0.6em !important;
261 | }
262 |
263 | .editing .header-info-wrap,
264 | .rule-select {
265 | display: none !important;
266 | }
267 |
268 | .information::before {
269 | width: 8px;
270 | height: 8px;
271 | border-radius: 50%;
272 | display: inline-block;
273 | margin: 0 0.3em;
274 | }
275 |
276 | .new:not(.editing) .information::before,
277 | .saved:not(.editing) .information::before {
278 | background: green;
279 | content: "";
280 | }
281 |
282 | .error .information::before {
283 | background: red;
284 | content: "";
285 | }
286 |
287 | .merged:not(.editing) .information::before {
288 | background: orange;
289 | content: "";
290 | }
291 |
292 | .rule-header {
293 | line-height: 1.5;
294 | flex-direction: column;
295 | align-items: initial;
296 | }
297 |
298 | .header-info-wrap {
299 | justify-content: space-between;
300 | }
301 |
302 | .selected > .rule-header {
303 | background: #f8f7ff;
304 | }
305 |
306 | .rule-input {
307 | padding: 0;
308 | }
309 | }
310 |
--------------------------------------------------------------------------------
/_locales/zh_CN/manual.wiki:
--------------------------------------------------------------------------------
1 | = Request Control 手册 =
2 |
3 | == Request Control 规则 ==
4 |
5 | Request Control 规则由[[#匹配模式|匹配模式]]、[[#类型|类型]]和[[#动作|动作]] 构成。
6 |
7 | 一个请求如果与非禁用规则的匹配模式和类型相匹配,将根据该规则拦截并采取相应动作。
8 |
9 | === 匹配模式 ===
10 |
11 | 匹配模式用来筛选出匹配[[#协议|协议]]、[[#主机|主机]]和[[#路径|路径]],以及可选的[[#包括和排除|包括和排除]]匹配模式的请求。
12 |
13 | ==== 协议 ====
14 |
15 | 支持的协议为 http 和 https。
16 |
17 | {|
18 | | http
19 | | 匹配 http 协议的请求。
20 | |-
21 | | https
22 | | 匹配 https 协议的请求。
23 | |-
24 | | http/https
25 | | 同时匹配 http 和 https 协议的请求。
26 | |}
27 |
28 | ==== 主机 ====
29 |
30 | 主机匹配可以通过下列方式匹配请求的 URL 中的主机(host)。
31 |
32 | {|
33 | | www.example.com
34 | | 匹配完整主机。
35 | |
36 | |-
37 | | *.example.com
38 | | 匹配指定的主机以及它的所有子域名。
39 | | 将会匹配 example.com 的所有子域名,例如 '''www'''.example.com 和 '''good'''.example.com
40 | |-
41 | | www.example.*
42 | | 匹配符合顶级域名列表的指定主机。 (可以配合子域名匹配)
43 | | 需将所需的顶级域名写入到顶级域名列表框(例如 ''com''、''org'')。
44 | |-
45 | | *
46 | | 匹配任何主机。
47 | |
48 | |}
49 |
50 | ==== 路径 ====
51 |
52 | 路径匹配可以是通配符 "*" 和所有 URL 路径中允许的字符的任意组合。通配符 "*" 能够匹配路径的任意部分,且可以出现多次。
53 |
54 | 下面是一些使用路径匹配的示例。
55 |
56 | {|
57 | | *
58 | | 匹配任意路径。
59 | |-
60 | | path/a/b/
61 | | 匹配特定路径 "path/a/b/"。
62 | |-
63 | | *b*
64 | | 匹配所有包含 "b" 的路径,只要路径中出现就匹配。
65 | |-
66 | |
67 | | 匹配空路径。
68 | |}
69 |
70 | === 类型 ===
71 |
72 | 类型代表请求的资源种类。可以匹配一或多种类型,也可以匹配任意类型。下面列出了所有可能的类型。
73 |
74 | {|
75 | ! 类型
76 | ! 细节
77 | |-
78 | | 文档
79 | | 代表浏览器标签页中的顶级 DOM 文档。(main frame)
80 | |-
81 | | 子文档
82 | | 代表附属于其他文档的 DOM 文档(sub frame)
83 | |-
84 | | 样式表
85 | | 代表样式表(.css 文件)。
86 | |-
87 | | 脚本
88 | | 代表可执行脚本(例如 JavaScript)。
89 | |-
90 | | 图像
91 | | 代表图像(例如 <img> 元素载入的内容)。
92 | |-
93 | | 对象
94 | | 代表对象(<object> 和 <embed> 元素的内容)。
95 | |-
96 | | 插件
97 | | 代表由插件发起的请求。(object_subrequest)
98 | |-
99 | | XMLHttpRequest
100 | | 代表 XMLHttpRequest 请求。
101 | |-
102 | | XSLT
103 | | 代表可扩展样式表转换语言(英文:Extensible Stylesheet Language Transformations,缩写为 XSLT)文档。
104 | |-
105 | | Ping
106 | | 代表因点击带有 ping 属性的 <a> 元素时发出的请求。只有 about:config 中 browser.send_pings 属性被启用时才会发出(默认为禁用)。
107 | |-
108 | | Beacon
109 | | 代表信标([https://developer.mozilla.org/en-US/docs/Web/API/Beacon_API Beacon])请求。
110 | |-
111 | | XML DTD
112 | | 代表被 XML 文档载入的 DTD 文件。
113 | |-
114 | | Font
115 | | 代表通过 CSS @font-face 规则引入的字体。
116 | |-
117 | | Media
118 | | 代表视频或音频。
119 | |-
120 | | WebSocket
121 | | 代表 [https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API WebSocket] 请求。
122 | |-
123 | | CSP Report
124 | | 代表内容安全策略([https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP Content Security Policy])报告。
125 | |-
126 | | Imageset
127 | | 代表加载带 srcset 属性的 <img> 或 <picture> 元素的内容的请求。
128 | |-
129 | | Web Manifest
130 | | 代表加载 Web manifest 的请求。
131 | |-
132 | | Other
133 | | 代表未被归类为上述任何类型的请求。
134 | |}
135 |
136 | === 动作 ===
137 |
138 | ; [[File:/icons/icon-filter.svg|16px]] 过滤
139 | : 跳过 URL 重定向,并/或移除 URL 查询参数。
140 | ; [[File:/icons/icon-redirect.svg|16px]] 重定向
141 | : 将请求重定向至手动配置的目标 URL。
142 | ; [[File:/icons/icon-secure.svg|16px]] 加密
143 | : 将不安全的 HTTP 请求升级为 HTTPS 请求。
144 | ; [[File:/icons/icon-block.svg|16px]] 拦截
145 | : 在请求被发出之前取消请求。
146 | ; [[File:/icons/icon-whitelist.svg|16px]] 白名单
147 | : 白名单并可选地在日志中记录请求。
148 |
149 | == 其他 URL 匹配方式 ==
150 |
151 | 以下匹配器扩展了 [https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest/RequestFilter WebRequest API] 的匹配功能。
152 |
153 | ==== 包括和排除 ====
154 |
155 | 通过支持通配符 "?" 和 "*"(其中 "?" 匹配任何单个字符,而 "*" 匹配零或多个字符)的字符串匹配,来过滤匹配的请求。也可通过在匹配模式前后加 "/" 字符来使用正则表达式。
156 |
157 | 包括和排除不区分大小写,而[[#主机|主机]]和[[#路径|路径]]区分大小写。
158 |
159 | 以下是使用包括和排除模式匹配的示例:
160 |
161 | {|
162 | | login
163 | | 匹配所有包括 "login" 的 URL。
164 | |-
165 | | log?n
166 | | 匹配包括例如 "login" 或者 "logon" 等等的 URL。
167 | |-
168 | | a*b
169 | | 匹配先后出现 "a" 和 "b" 的 URL。
170 | |-
171 | | /[?&]a=\d+(&|$)/
172 | | 匹配包含参数 "a" 的 URL,该参数的值为数字。
173 | |}
174 |
175 | === Match by origin ===
176 |
177 | 按照跨域情况过滤匹配的规则。
178 |
179 | {|
180 | | Any
181 | | 匹配任意跨域情况的请求。
182 | |-
183 | | Same domain
184 | | 匹配同域名的请求。
185 | |-
186 | | Same origin
187 | | 匹配同源的请求。按照[https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy 同源策略]判定。
188 | |-
189 | | Third party domain
190 | | 匹配第三方域名的请求。
191 | |-
192 | | Third party origin
193 | | 匹配第三方源的请求。
194 | |}
195 |
196 | == Rule priorities ==
197 |
198 | # 白名单规则
199 | # 拦截规则
200 | # 加密规则
201 | # 重定向规则
202 | # 过滤规则
203 |
204 | == 去除 URL 参数 ==
205 |
206 | 过滤规则支持去除 URL 参数。可以在要去除的 URL 参数的名称中使用通配符 "*" 和 "?",或者正则表达式。
207 |
208 | 下面是去除 URL 参数的示例。
209 |
210 | {|
211 | | utm_source
212 | | 去除 "utm_source" 参数。
213 | |-
214 | | utm_*
215 | | 去除所有以 "utm_" 开头的参数。
216 | |-
217 | | /[0-9]+/
218 | | 去除所有参数名称仅由数字构成的参数。
219 | |}
220 |
221 | === 反转要去除的请求参数选项 ===
222 |
223 | 仅保留参数列表中所定义的参数,移除所有其他参数。
224 |
225 | === 移除全部请求参数选项 ===
226 |
227 | 从匹配的请求中移除所有 URL 查询参数。
228 |
229 | == 使用模式捕获重定向 ==
230 |
231 | 重定向规则支持将请求重定向至一个从原始请求 URL 修改而来的 URL。可以使用参数展开来使用和更改目标 URL 的某些组成部分(命名参数);也可以使用重定向指令,更改目标 URL 的某些组成部分(命名参数),比如只更改原始请求 URL 的端口。
232 |
233 | 这两种方法可以结合使用。将首先解析并应用重定向指令,再轮到参数扩展。
234 |
235 | 可以在重定向指令中使用参数展开。
236 |
237 | === 参数展开 ===
238 |
239 | {parameter}
240 | 访问原始请求 URL 中的某个命名参数。本节末尾列出了可用的命名参数。
241 |
242 | 参数展开支持以下字符串操作语法:
243 |
244 | ==== 替换子字符串 ====
245 |
246 | {parameter/pattern/replacement}
247 |
248 | 将首个匹配 pattern 的子字符串替换为 replacement。
249 |
250 | {parameter//pattern/replacement}
251 |
252 | 将所有匹配 pattern 的子字符串替换为 replacement。
253 |
254 | 匹配模式 pattern 使用正则表达式编写。replacement 中支持使用一些特殊替换,包括引用捕获组(capture group),下面的表格描述了支持的特殊替换。
255 |
256 | {|
257 | | $n
258 | | 插入第 n 个捕获组,从 1 开始计数。
259 | |-
260 | | $`
261 | | 插入被匹配到的子字符串之前的部分。
262 | |-
263 | | $'
264 | | 插入被匹配到的子字符串之后的部分。
265 | |-
266 | | $&
267 | | 插入被匹配到的子字符串。
268 | |-
269 | | $$
270 | | 插入一个 "$" 字符。
271 | |}
272 |
273 | ==== 提取子字符串 ====
274 |
275 | {parameter:offset:length}
276 |
277 | 提取扩展参数的一个部分。偏移量 offset 确定起始位置,从 0 开始计数;如果为负值,则从字符串结尾开始算起。
278 |
279 | ==== 解码和编码 ====
280 |
281 | {parameter|encodingRule}
282 |
283 | 对匹配模式的展开值进行解码或编码操作。
284 |
285 | {|
286 | | encodeURI
287 | | 将值编码为 URI。不编码以下字符:":"、"/"、";",和 "?"。
288 | |-
289 | | decodeURI
290 | | 将值作为 URI 解码。
291 | |-
292 | | encodeURIComponent
293 | | 将值编码为 URI 组件。会编码所有 URI 保留字符。
294 | |-
295 | | decodeURIComponent
296 | | 将值作为 URI 组件解码。
297 | |-
298 | | encodeBase64
299 | | 将值编码为 Base64 字符串。
300 | |-
301 | | decodeBase64
302 | | 将值作为 Base64 字符串解码。
303 | |}
304 |
305 | ==== 合并参数展开操作 ====
306 |
307 | {parameter(manipulation1)|(manipulation2)...|(manipulationN)}
308 | 可以使用管道符 "|" 链式调用多个字符串操作规则。输出结果是被依次操作过的参数值。
309 |
310 | ==== 示例 ====
311 |
312 | {|
313 | | https://{hostname}/new/path
314 | | 复用原始请求中的主机名。
315 | |-
316 | | https://{hostname/([a-z]{2}).*/$1}/new/path
317 | | 提取原始请求中的主机名的一部分并复用。
318 | |-
319 | | https://{hostname::-3|/.co/.com}/new/path
320 | | 去除原始请求中的主机名中的最后 3 个字符,再将其中的第一个 ".co" 替换为 ".com",再复用。
321 | |-
322 | | {search.url|decodeURIComponent}
323 | | 捕获原始请求的查询参数中的 "url" 参数,并将其解码,作为重定向目标。
324 | |}
325 |
326 | === 重定向指令 ===
327 |
328 | [parameter=value]
329 |
330 | 替换原始请求的特定组成部分。本节末尾列出了可用的命名 URL 参数(named URL parameters)。
331 |
332 | [parameter={parameter}]
333 |
334 | 可以通过上述参数扩展语法对重定向指令的值进行参数化(parametrize)。
335 |
336 | ==== 示例 ====
337 |
338 | {|
339 | | [port=8080]
340 | | 将原始请求的端口重定向至 8080。
341 | |-
342 | | [port=8080][hostname=localhost]
343 | | 将原始请求的主机重定向至 localhost:8080。
344 | |-
345 | | [port=8080][hostname=localhost][hash={pathname}]
346 | | 将原始请求的主机重定向至 localhost:8080,并将哈希(hash,#)更改为原始请求的主机名。
347 | |}
348 |
349 | === 命名参数列表 ===
350 |
351 | 下表列出了支持的参数名称以及输出示例。
352 |
353 | 作为输入的示例地址:
354 |
355 | https://www.example.com:8080/some/path?query=value#hash
356 | {|
357 | ! 名称
358 | ! 输出
359 | |-
360 | | protocol
361 | | https:
362 | |-
363 | | hostname
364 | | www.example.com
365 | |-
366 | | port
367 | | :8080
368 | |-
369 | | pathname
370 | | /some/path
371 | |-
372 | | search
373 | | ?query=value
374 | |-
375 | | search.query
376 | | value
377 | |-
378 | | hash
379 | | #hash
380 | |-
381 | | host
382 | | www.example.com:8080
383 | |-
384 | | origin
385 | | https://www.example.com:8080
386 | |-
387 | | href
388 | | https://www.example.com:8080/some/path?query=value#hash
389 | |}
390 |
391 | 此手册页面基于以下 MDN 维基文档中的资料编写,以 [http://creativecommons.org/licenses/by-sa/2.5/ CC-BY-SA 2.5] 协议授权。
392 |
393 | # [https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Match_patterns Match patterns] by [https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Match_patterns$history Mozilla Contributors] is licensed under [http://creativecommons.org/licenses/by-sa/2.5/ CC-BY-SA 2.5].
394 | # [https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/webRequest/ResourceType webRequest.ResourceType] by [https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/webRequest/ResourceType$history Mozilla Contributors] is licensed under [http://creativecommons.org/licenses/by-sa/2.5/ CC-BY-SA 2.5].
395 | # [https://developer.mozilla.org/en-US/docs/Web/API/URL URL] by [https://developer.mozilla.org/en-US/docs/Web/API/URL$history Mozilla Contributors] is licensed under [http://creativecommons.org/licenses/by-sa/2.5/ CC-BY-SA 2.5].
396 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All notable changes to this project will be documented in this file.
3 |
4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6 |
7 | ## [Unreleased]
8 | - Add option to remove toolbar icon counter. #129
9 | - Add support for matching hostnames without suffix (TLD wildcard). #126
10 | - Add private browsing matcher to ignore or only use in private browsing.
11 |
12 | ## [1.15.5] - Jul. 7, 2020
13 | - Fix rule creation after 1.15.3. #131
14 |
15 | ## [1.15.4] - Jul. 3, 2020
16 | - Fix required validation after 1.15.3. #131
17 |
18 | ## [1.15.3] - Jul. 3, 2020
19 | - Fix Redirect rule not being applied when multiple rules were matching a single request. #111
20 | - Fix rule tester with multiple redirect rules.
21 | - Remove backspace deleting added parameters. #128
22 |
23 | ## [1.15.2] - Mar. 28, 2020
24 | - Fix Filter rule not being applied when multiple filter rules were matching a single request. #111
25 |
26 | ## [1.15.1] - Mar. 13, 2020
27 | - Fix popup height when placed to overflow menu. #111
28 | - Fix rule tester regexp escaping. #113
29 | - Fix selected rules exporting.
30 |
31 | ## [1.15.0] - Mar. 11, 2020
32 | - Add Secure action to upgrade HTTP requests to HTTPS.
33 | - Add new default rules: FBCLID stripping rule and FB redirection service rule. #110 by @AreYouLoco
34 | - Fix text color when default color is changed. #112 by @Zocker1999NET
35 | - Fix filter/redirect actions to be applied and logged separately.
36 | - Remove Downloads API dependency for rule export.
37 | - Update icons and badge color.
38 |
39 | ## [1.14.0] - Sep 8, 2019
40 | - Add option to redirect document (update tab) from other requests types. #103
41 | - Fix invalid regexp pattern breaking all other rules. #107
42 | - Keep last record when redirect/filter rule is followed by a server redirection.
43 |
44 | ## [1.13.3] - Aug 22, 2019
45 | - Fix hosts/paths input field overflow issue with long list of values (2nd fix). #104
46 | - Fix updating of input fields for merged rules after import.
47 | - Accept text files on import.
48 |
49 | ## [1.13.2] - Aug 19, 2019
50 | - Fix comma not accepeted in includes/excludes input fields. #105
51 | - Fix hosts/paths input field overflow issue with long list of values. #104
52 |
53 | ## [1.13.1] - Aug 10, 2019
54 | - Fix subdocument redirect regression issue #103
55 | - Fix bad localization key.
56 |
57 | ## [1.13.0] - Aug 5, 2019
58 | - Change query parameter trimming in Filter Rule to not apply to the extracted redirect URL. #99
59 | - Add replace all substring support for Redirect Rule. #101
60 |
61 | ## [1.12.4] - June 29, 2019
62 | - Add workaround for Firefox bug https://bugzilla.mozilla.org/show_bug.cgi?id=1557300. #100
63 | - Other improvements to options (rule delete button, create button / new rule validation fix, type badge)
64 |
65 | ## [1.12.3] - May 12, 2019
66 | - Update style and manual.
67 | - Fix rule edit link in popup.
68 |
69 | ## [1.12.2] - April 28, 2019
70 | - Order rules alphabetically. #90
71 |
72 | ## [1.12.1] - April 27, 2019
73 | - Update style for more compact layout.
74 |
75 | ## [1.12.0] - April 22, 2019
76 | - Add Android support and update layout and style for mobile browsers. #88
77 | - Add speculative request type. #81
78 | - Add support for matching requests by origin. #36
79 | - Close modals on Escape-key. #91
80 | - Fix action buttons disabled issue. #96
81 | - Fix white list rule testing no feedback issue. #92
82 | - Fix percent sign issue with redirect rules. #95
83 |
84 | ## [1.11.1] - September 6, 2018
85 | - Add strict min version requirement for Firefox 60.
86 | - Remove debug console logging #80.
87 |
88 | ## [1.11.0] - August 17, 2018
89 | - Add support for skipping redirection url filtering within same domain. #29
90 |
91 | ## [1.10.1] - August 4, 2018
92 | - Fix excludes/includes with Any URL. #77
93 |
94 | ## [1.10.0] - July 29, 2018
95 | - Add keywords to decode and encode captured patterns for redirect rule #6
96 | - Add includes and excludes pattern support #16 #24 #35
97 | - Add option to control whitelist logging #59
98 | - Add query parameter expansion support for redirect rule #72
99 | - Change to trim unwanted query parameters before inline url parsing #72
100 | - Update active and disabled icons #70
101 | - Fix parsing of redirect instructions with inline brackets #73
102 |
103 | ## [1.9.4] - May 26, 2018
104 | - Fix rule import after 1.9.3 changes.
105 | - Fix showing number of selected rules for new rules.
106 |
107 | ## [1.9.3] - May 24, 2018
108 | - Add alphabetizing patterns. #57
109 | - Add Select / select none input #63
110 | - Add showing number of selected rules #64
111 | - Add UUID for rules. Rules with same UUID will be overwritten when importing rules.
112 | - Fix Rule Tester to escape '?' in path. #65
113 | - Fix redirecting by manipulating 'hostname'. #69
114 |
115 | ## [1.9.2] - May 15, 2018
116 | - Add disabled state icon.
117 | - Fix resolving match patterns with multiple paths and TLDs. #66
118 |
119 | ## [1.9.1] - May 13, 2018
120 | - Fix toolbar icon updating.
121 | - Fix adding two-character long generic domains. #56
122 | - Fix adding comma to trim parameters. #58
123 |
124 | ## [1.9.0] - May 9, 2018
125 | - Add rule tester for testing selected rules against test URL.
126 | - Add support for rule tagging in panel.
127 | - Add rule edit links in panel.
128 | - Add disable/enable button in panel.
129 | - Change Redirect instructions to supports parameter expansion in value.
130 | - Update options style and panel layout.
131 | - Locale: ES Spanish, thanks to @strel at Github!
132 |
133 | ## [1.8.6] - Nov. 26, 2017
134 | - Fix Redirect to static url.
135 | - Fix combining parameter expansion with redirect instructions.
136 | - Add unit tests.
137 |
138 | ## [1.8.5] - Nov. 25, 2017
139 | - Fix query parameter trimming. #50
140 | - Fix icons not showing in rules view.
141 |
142 | ## [1.8.4] - Nov. 18, 2017
143 | - Fix build.
144 |
145 | ## [1.8.3] - Nov. 17, 2017
146 | - Fix regex repetition quantifier not supported in pattern captures. #45 #47
147 | - Fix query parameters trimming on non-standard urls. #48 #40
148 |
149 | ## [1.8.2] - August 6, 2017
150 | - Fix Filter rule to always decode redirection URL.
151 | - Add version in about page.
152 |
153 | ## [1.8.1] - July 22, 2017
154 | - Fix save rule on title/description change.
155 | - Fix any-url host input required validation.
156 | - Add description for default rules.
157 | - Load default rules from file ("/options/default-rules.json").
158 | - Strip paramsTrim pattern from exported rules.
159 |
160 | ## [1.8.0] - July 19, 2017
161 | - Rules are now auto saved on change.
162 | - Rule name and description are editable.
163 | - Add invert URL parameter trim option.
164 | - Add about page.
165 | - Other changes to rule options display.
166 |
167 | ## [1.7.1] - July 1, 2017
168 | - Fix whitelist/block rule request markers.
169 | - Fix migrate script for Firefox versions before 55.
170 |
171 | ## [1.7.0] - June 29, 2017
172 | - Add rules export and import. #8
173 | - Add toolbar button to list details of applied rules on current tab. #19
174 | - Add tabs to options view.
175 | - Fix trim parameter inconsistency: support literal string params and regexp params. #17
176 | - Fix pageAction details bug with block rules. #19
177 | - Fix filter rule redirection url filtering. #20
178 | - Remove url status icon. #19
179 |
180 | ## [1.6.1] - June 21, 2017
181 | - Add i18n support
182 | - Add request details in page action popup
183 | - Fix query parameters trim with valueless params
184 |
185 | ## [1.6.0] - June 13, 2017
186 | - Add support for multiple rule matching for single request.
187 | - Add support for adding multiple hosts and paths for rules.
188 | - Remove 'Include subdomains' checkbox.
189 | - Improve rules options view.
190 |
191 | ## [1.5.0] - June 1, 2017
192 | - URL parameter filtering is now Filter rule specific.
193 | - Redirection cleaning can now be turned off in Filter rule.
194 | - Added wildcard "*" support for url parameter trimming.
195 |
196 | ## [1.4.3] - May 27, 2017
197 | - Fix url parameter filtering for global rules.
198 | - Fix redirection url parsing from query string.
199 | - Fix default google filter rule, include main frame requests.
200 | - Fix filter action handler to only do tab navigation on sub_frame requests.
201 |
202 | ## [1.4.2] - May 7, 2017
203 | - Fix to escape forward slash in replace regex pattern.
204 | - Add toggle rule edit on double click.
205 |
206 | ## [1.4.1] - April 12, 2017
207 | - Change the default type for new rules to be the document type.
208 | - Change any url and any type buttons to be the rightmost on rule panel.
209 | - Fix more than one parameter expansions failing in redirection address.
210 | - Fix undefined rule title when saving a new rule without changing its action.
211 |
212 | ## [1.4.0] - April 10, 2017
213 | - Add support for pattern capturing (parameter expansion) to redirect based on the original request.
214 | - Add support for parameter instructions to redirect based on the original request.
215 | - Change help page to open in a new page.
216 | - Update help and add attributions to the MDN documents.
217 | - Fix missing title for options page.
218 |
219 | ## [1.3.0] - March 27, 2017
220 | - Add whitelist rules support.
221 | - Add pattern support for creating global rules.
222 | - Change option page to open in new tab.
223 | - Fix input validation that allowed incorrect rule saving.
224 |
225 | ## [1.2.3] - March 15, 2017
226 | - Add toggleable edit mode for rules.
227 | - Change tracking URL parameters input option to use one line tags-input.
228 | - Fix to include WebExtension permission for all urls.
229 | - Fix to include applications key with add-on id in manifest.json.
230 |
231 | ## [1.2.2] - October 30, 2016
232 | - Add support for rule based control with actions (filter, block, redirect).
233 | - Add support for request types.
234 | - Add page action for providing user feedback of handled requests.
235 | - Add help page.
236 | - Add "ng" to the TLDs of pre-defined rule for Google.
237 | - Fix subdomain top-level domain confusion.
238 | - Change TLDs from global list to rule based manual list.
239 | - Change add-on name from JustRedirect! to Request Control.
240 | - Change license from MIT to MPL-2.0.
241 | - Enhance options usability to improve rule creation and match pattern definition (uses Bootstrap CSS).
242 |
243 | ## [1.1.0] - October 1, 2016
244 | - Add match pattern for Google search to prevent outgoing search link tracking.
245 | - Add support for creating match patterns for matching different sub domains (e.g. www.google.*).
246 | - Add match pattern validation.
247 | - Add icon for the add-on.
248 | - Fix to prevent enter key from deleting values on inputs.
249 | - Fix updating redirection listeners on options change.
250 | - Fix adding history entries for redirection origin urls.
251 |
252 | ## [1.0.2] - September 24, 2016
253 | - Add out.reddit.com redirection url pattern.
254 | - Add outgoing.prod.mozaws.net pattern.
255 | - Fix query parameter filtering.
256 |
257 | ## [1.0] - September 23, 2016
258 | - Initial release
--------------------------------------------------------------------------------
/_locales/es/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "extensionName": {
3 | "message": "Request Control"
4 | },
5 | "extensionDescription": {
6 | "message": "Define reglas para controlar peticiones HTTP."
7 | },
8 | "extensionManual": {
9 | "message": "/_locales/es/manual.html",
10 | "description": "Cambia para apuntar al manual.html localizado."
11 | },
12 | "title_filter": {
13 | "message": "Filtrado: "
14 | },
15 | "title_block": {
16 | "message": "Bloqueado: "
17 | },
18 | "title_redirect": {
19 | "message": "Redirigido: "
20 | },
21 | "title_whitelist": {
22 | "message": "En lista blanca: "
23 | },
24 | "rule_title_filter": {
25 | "message": "Regla de filtro para $HOST$",
26 | "placeholders": {
27 | "host": {
28 | "content": "$1",
29 | "example": "ejemplo.com"
30 | }
31 | }
32 | },
33 | "rule_title_redirect": {
34 | "message": "Regla de redireccionamiento para $HOST$",
35 | "placeholders": {
36 | "host": {
37 | "content": "$1",
38 | "example": "ejemplo.com"
39 | }
40 | }
41 | },
42 | "rule_title_block": {
43 | "message": "Regla de bloqueo para $HOST$",
44 | "placeholders": {
45 | "host": {
46 | "content": "$1",
47 | "example": "ejemplo.com"
48 | }
49 | }
50 | },
51 | "rule_title_whitelist": {
52 | "message": "Regla de lista blanca para $HOST$",
53 | "placeholders": {
54 | "host": {
55 | "content": "$1",
56 | "example": "ejemplo.com"
57 | }
58 | }
59 | },
60 | "rule_title_new": {
61 | "message": "Nueva regla"
62 | },
63 | "rule_description_new": {
64 | "message": "Crea una nueva regla de control de petición definiendo su patrón, tipo y acción."
65 | },
66 | "rule_title_hosts": {
67 | "message": "$HOSTS$ y otros $NUM$",
68 | "placeholders": {
69 | "hosts": {
70 | "content": "$1",
71 | "example": "servidor1.com, servidor2.com, servidor3.com"
72 | },
73 | "num": {
74 | "content": "$2",
75 | "example": "10"
76 | }
77 | }
78 | },
79 | "rule_description_filter": {
80 | "message": "Filtra el redireccionamiento de URL"
81 | },
82 | "rule_description_block": {
83 | "message": "Bloquea peticiones antes de que se efectúen."
84 | },
85 | "rule_description_redirect": {
86 | "message": "Redirige peticiones a $URL$",
87 | "placeholders": {
88 | "url": {
89 | "content": "$1",
90 | "example": "http://ejemplo.com"
91 | }
92 | }
93 | },
94 | "rule_description_whitelist": {
95 | "message": "Revoca otras reglas y procesa las peticiones con normalidad."
96 | },
97 | "all_urls": {
98 | "message": "Cualquier URL"
99 | },
100 | "activate_false": {
101 | "message": "Deshabilitar"
102 | },
103 | "activate_true": {
104 | "message": "Habilitar"
105 | },
106 | "show_more_false": {
107 | "message": "◂ Menos"
108 | },
109 | "show_more_true": {
110 | "message": "Más ▸"
111 | },
112 | "main_frame": {
113 | "message": "Document"
114 | },
115 | "sub_frame": {
116 | "message": "Sub document"
117 | },
118 | "stylesheet": {
119 | "message": "Stylesheet"
120 | },
121 | "script": {
122 | "message": "Script"
123 | },
124 | "image": {
125 | "message": "Image"
126 | },
127 | "object": {
128 | "message": "Object"
129 | },
130 | "object_subrequest": {
131 | "message": "Plugin"
132 | },
133 | "xmlhttprequest": {
134 | "message": "XMLHttpRequest"
135 | },
136 | "xslt": {
137 | "message": "XSLT"
138 | },
139 | "ping": {
140 | "message": "Ping"
141 | },
142 | "beacon": {
143 | "message": "Beacon"
144 | },
145 | "xml_dtd": {
146 | "message": "XML DTD"
147 | },
148 | "font": {
149 | "message": "Font"
150 | },
151 | "media": {
152 | "message": "Media"
153 | },
154 | "websocket": {
155 | "message": "WebSocket"
156 | },
157 | "csp_report": {
158 | "message": "CSP report"
159 | },
160 | "imageset": {
161 | "message": "Imageset"
162 | },
163 | "web_manifest": {
164 | "message": "Web Manifest"
165 | },
166 | "other": {
167 | "message": "Other"
168 | },
169 | "options_title": {
170 | "message": "Reglas de Request Control"
171 | },
172 | "options_description": {
173 | "message": "Define reglas para controlar peticiones HTTP"
174 | },
175 | "settings_title": {
176 | "message": "Configuración de Request Control"
177 | },
178 | "manual_title": {
179 | "message": "Manual de Request Control"
180 | },
181 | "manual": {
182 | "message": "Manual"
183 | },
184 | "about_title": {
185 | "message": "Acerca de Request Control"
186 | },
187 | "about": {
188 | "message": "Acerca de"
189 | },
190 | "see_manual": {
191 | "message": "Vea el manual."
192 | },
193 | "create_new_rule": {
194 | "message": "Crear nueva regla"
195 | },
196 | "new_rule": {
197 | "message": "Nueva regla"
198 | },
199 | "edit": {
200 | "message": "Editar"
201 | },
202 | "edit_rule": {
203 | "message": "Editar regla"
204 | },
205 | "disable": {
206 | "message": "Deshabilitar"
207 | },
208 | "pattern": {
209 | "message": "Patrón"
210 | },
211 | "scheme": {
212 | "message": "esquema"
213 | },
214 | "host": {
215 | "message": "servidor"
216 | },
217 | "tlds": {
218 | "message": "TLDs"
219 | },
220 | "path": {
221 | "message": "ruta"
222 | },
223 | "top_level_domains": {
224 | "message": "Dominios de primer nivel"
225 | },
226 | "types": {
227 | "message": "Tipos"
228 | },
229 | "any_type": {
230 | "message": "Cualquier tipo"
231 | },
232 | "action": {
233 | "message": "Acción"
234 | },
235 | "filter": {
236 | "message": "Filtrado"
237 | },
238 | "block": {
239 | "message": "Bloqueo"
240 | },
241 | "redirect": {
242 | "message": "Redireccionamiento"
243 | },
244 | "whitelist": {
245 | "message": "Lista blanca"
246 | },
247 | "redirect_to": {
248 | "message": "Redirigir a"
249 | },
250 | "manual_text_redirect": {
251 | "message": "Usar captura del patrón para redirigir en base a la petición original."
252 | },
253 | "filter_url_redirection": {
254 | "message": "Filtrar redireccionamiento de URL"
255 | },
256 | "trim_url_parameters": {
257 | "message": "Recortar parámetros de URL"
258 | },
259 | "trim_all": {
260 | "message": "Recortar todos"
261 | },
262 | "invert_trim": {
263 | "message": "Recortado inverso"
264 | },
265 | "saved": {
266 | "message": "guardado"
267 | },
268 | "save_rule": {
269 | "message": "regla de guardado"
270 | },
271 | "remove": {
272 | "message": "Eliminar"
273 | },
274 | "title_tlds": {
275 | "message": "Nombres de dominio de primer nivel incluidos"
276 | },
277 | "redirect_url": {
278 | "message": "URL de redireccionamiento"
279 | },
280 | "placeholder_trim_parameters": {
281 | "message": "Añadir nombres de parámetro a filtrar"
282 | },
283 | "show_rules": {
284 | "message": "Mostrar reglas"
285 | },
286 | "request_type": {
287 | "message": "Tipo de petición: $TYPE$",
288 | "placeholders": {
289 | "type": {
290 | "content": "$1",
291 | "example": "Document"
292 | }
293 | }
294 | },
295 | "timestamp": {
296 | "message": "Marca de tiempo: $TIME$",
297 | "placeholders": {
298 | "time": {
299 | "content": "$1",
300 | "example": "18:32:21"
301 | }
302 | }
303 | },
304 | "request_url": {
305 | "message": "URL de petición:"
306 | },
307 | "copy_to_clipboard": {
308 | "message": "Copiar al portapapeles"
309 | },
310 | "copied": {
311 | "message": "¡Copiado!"
312 | },
313 | "new_target": {
314 | "message": "Nuevo objetivo:"
315 | },
316 | "rules": {
317 | "message": "Reglas"
318 | },
319 | "settings": {
320 | "message": "Configuración"
321 | },
322 | "back_to_top": {
323 | "message": "Volver arriba"
324 | },
325 | "export-file-name": {
326 | "message": "reglas-de-request-control.json"
327 | },
328 | "export_selected": {
329 | "message": "Exportar seleccionadas"
330 | },
331 | "remove_selected": {
332 | "message": "Eliminar seleccionadas"
333 | },
334 | "test_selected": {
335 | "message": "Testar seleccionadas"
336 | },
337 | "export_rules": {
338 | "message": "Exportar reglas"
339 | },
340 | "export_description": {
341 | "message": "Exporta reglas a un fichero local."
342 | },
343 | "export": {
344 | "message": "Exportar"
345 | },
346 | "import_rules": {
347 | "message": "Importar reglas"
348 | },
349 | "import_description": {
350 | "message": "Importa reglas a un fichero local."
351 | },
352 | "contents": {
353 | "message": "Contenidos"
354 | },
355 | "about_description": {
356 | "message": "Una WebExtension de Firefox para la administración de peticiones HTTP."
357 | },
358 | "faq": {
359 | "message": "Preguntas frecuentes (FAQ)"
360 | },
361 | "contributors": {
362 | "message": "Contribuidores"
363 | },
364 | "source_code": {
365 | "message": "Código fuente"
366 | },
367 | "changelog": {
368 | "message": "Registro de cambios"
369 | },
370 | "license": {
371 | "message": "Licencia"
372 | },
373 | "license_clause": {
374 | "message": "Request Control está licenciado bajo Mozilla Public License v2.0. Hay disponible una copia de la MPL en"
375 | },
376 | "donate": {
377 | "message": "Donar"
378 | },
379 | "browse_file": {
380 | "message": "Examinar..."
381 | },
382 | "version": {
383 | "message": "Versión $VERSION$",
384 | "placeholders": {
385 | "version": {
386 | "content": "$1"
387 | }
388 | }
389 | },
390 | "home_page": {
391 | "message": "Página principal"
392 | },
393 | "name": {
394 | "message": "Nombre:"
395 | },
396 | "description": {
397 | "message": "Descripción:"
398 | },
399 | "set_tag": {
400 | "message": "Añadir etiqueta"
401 | },
402 | "tag": {
403 | "message": "Etiqueta:"
404 | },
405 | "test_url": {
406 | "message": "URL de testeo"
407 | },
408 | "test_selected_rules": {
409 | "message": "Testar reglas seleccionadas"
410 | },
411 | "invalid_test_url": {
412 | "message": "No es una URL de testeo válida"
413 | },
414 | "no_match": {
415 | "message": "Sin coincidencia"
416 | },
417 | "matched_no_change": {
418 | "message": "Coincidente, pero sin cambio"
419 | },
420 | "whitelisted": {
421 | "message": "En lista blanca"
422 | },
423 | "blocked": {
424 | "message": "Bloqueada"
425 | },
426 | "invalid_target_url": {
427 | "message": "URL objetivo no válida: $URL$",
428 | "placeholders": {
429 | "url": {
430 | "content": "$1"
431 | }
432 | }
433 | },
434 | "enable_rules": {
435 | "message": "Habilitar Request Control"
436 | },
437 | "disable_rules": {
438 | "message": "Deshabilitar Request Control"
439 | }
440 | }
441 |
--------------------------------------------------------------------------------
/test/redirect.test.js:
--------------------------------------------------------------------------------
1 | import { RedirectRule } from "../src/main/rules/redirect";
2 |
3 | test("Static redirection url", () => {
4 | const request = "https://www.amazon.com/AmazonBasics-Type-C-USB-Male-Cable/dp/B01GGKYQ02/ref=sr_1_1?s=amazonbasics&srs=10112675011&ie=UTF8&qid=1489067885&sr=8-1&keywords=usb-c";
5 | const target = "https://www.amazon.com/AmazonBasics-Type-C-USB-Male-Cable/dp/B01GGKYQ02/";
6 | const redirectRule = new RedirectRule({ redirectUrl: "https://www.amazon.com/AmazonBasics-Type-C-USB-Male-Cable/dp/B01GGKYQ02/" });
7 | expect(redirectRule.apply(request)).toBe(target);
8 | });
9 |
10 | test("Pattern expansion - Single", () => {
11 | const request = "https://www.amazon.com/AmazonBasics-Type-C-USB-Male-Cable/dp/B01GGKYQ02/ref=sr_1_1?s=amazonbasics&srs=10112675011&ie=UTF8&qid=1489067885&sr=8-1&keywords=usb-c";
12 | const target = "https://a.b/path/?s=amazonbasics&srs=10112675011&ie=UTF8&qid=1489067885&sr=8-1&keywords=usb-c";
13 | const redirectRule = new RedirectRule({ redirectUrl: "https://a.b/path/{search}" });
14 | expect(redirectRule.apply(request)).toBe(target);
15 | });
16 |
17 | test("Pattern expansion - Multiple", () => {
18 | const request = "https://www.amazon.com/AmazonBasics-Type-C-USB-Male-Cable/dp/B01GGKYQ02/ref=sr_1_1?s=amazonbasics&srs=10112675011&ie=UTF8&qid=1489067885&sr=8-1&keywords=usb-c";
19 | const target = "https://www.amazon.com/AmazonBasics-Type-C-USB-Male-Cable/dp/B01GGKYQ02/ref=sr_1_1?s=amazonbasics&srs=10112675011&ie=UTF8&qid=1489067885&sr=8-1&keywords=usb-c#myhash";
20 | const redirectRule = new RedirectRule({ redirectUrl: "{protocol}//{host}{pathname}{search}#myhash" });
21 | expect(redirectRule.apply(request)).toBe(target);
22 | });
23 |
24 | test("Pattern expansion - Parameter not found", () => {
25 | const request = "https://www.amazon.com/AmazonBasics-Type-C-USB-Male-Cable/dp/B01GGKYQ02/ref=sr_1_1?s=amazonbasics&srs=10112675011&ie=UTF8&qid=1489067885&sr=8-1&keywords=usb-c";
26 | const target = "https://a.b/path/{fail}";
27 | const redirectRule = new RedirectRule({ redirectUrl: target });
28 | expect(redirectRule.apply(request)).toBe(target);
29 | });
30 |
31 | test("Substring replace pattern", () => {
32 | const request = "https://www.amazon.com/AmazonBasics-Type-C-USB-Male-Cable/dp/B01GGKYQ02/ref=sr_1_1?s=amazonbasics&srs=10112675011&ie=UTF8&qid=1489067885&sr=8-1&keywords=usb-c";
33 | const target = "https://www.amazon.com/AmazonBasics-Type-C-USB-Male-Cable/my/path/B01GGKYQ02/ref=sr_1_1?s=amazonbasics&srs=10112675011&ie=UTF8&qid=1489067885&sr=8-1&keywords=usb-c#myhash";
34 | const redirectRule = new RedirectRule({ redirectUrl: "{protocol}//{host}{pathname/dp/my/path}{search}#myhash" });
35 | expect(redirectRule.apply(request)).toBe(target);
36 | });
37 |
38 | test("Substring replace pattern - regexp capture groups", () => {
39 | let request, target, redirectRule;
40 | request = "https://www.amazon.com/AmazonBasics-Type-C-USB-Male-Cable/dp/B01GGKYQ02/ref=sr_1_1?s=amazonbasics&srs=10112675011&ie=UTF8&qid=1489067885&sr=8-1&keywords=usb-c";
41 | target = "https://www.amazon.com/AmazonBasics-Type-C-USB-Male-Cable/foo/B01GGKYQ02/";
42 | redirectRule = new RedirectRule({ redirectUrl: "{href/(.*?)\\/dp\\/(.*?)\\/ref=.*/$1/foo/$2/}" });
43 | expect(redirectRule.apply(request)).toBe(target);
44 | });
45 |
46 | test("Substring replace pattern - regexp replace", () => {
47 | let request, target, redirectRule;
48 | request = "https://www.dropbox.com/s/vm2mh2lkwsug4gt/rick_morty_at.png?dl=0";
49 | target = "https://dl.dropboxusercontent.com/s/vm2mh2lkwsug4gt/rick_morty_at.png";
50 | redirectRule = new RedirectRule({ redirectUrl: "{href/^https?:\\/\\/www\\.dropbox\\.com(\\/s\\/.+\\/.+)\\?dl=\\d$/https://dl.dropboxusercontent.com$1}" });
51 | expect(redirectRule.apply(request)).toBe(target);
52 | });
53 |
54 | test("Substring replace pattern - replace all occurences", () => {
55 | let request, target, redirectRule;
56 | request = "http://foo.com/foo/foo?foo=bar#foo";
57 | target = "http://bar.com/bar/bar?bar=bar#bar";
58 | redirectRule = new RedirectRule({ redirectUrl: "{href//foo/bar}" });
59 | expect(redirectRule.apply(request)).toBe(target);
60 | });
61 |
62 | test("Substring replace pattern - replace backslash", () => {
63 | let request, target, redirectRule;
64 | request = "http:\\/\\/foo.com\\/foo\\/";
65 | target = "http://foo.com/foo/";
66 | redirectRule = new RedirectRule({ redirectUrl: "{href//\\\\+/}" });
67 | expect(redirectRule.apply(request)).toBe(target);
68 | });
69 |
70 | test("Substring replace pattern - replace all combined", () => {
71 | let request, target, redirectRule;
72 | request = "http://track.steadyhq.com/track/click/12345678/steadyhq.com?p=eyJzIjoidDJLdmg0NVV4MUJkNFh3N3lrUkR6djRMWlJNIiwidiI6MSwicCI6IntcInVcIjoxMjM0NTY3OCxcInZcIjoxLFwidXJsXCI6XCJodHRwczpcXFwvXFxcL3N0ZWFkeWhxLmNvbVxcXC9wcm9qZWN0XFxcL3Bvc3RzXFxcLzlmYjYwMWU0LWIzMjctNGY2YS01NzljLWU1NGM4NDE1YmY0YlwiLFwiaWRcIjpcIjZmOTNmMTZhYWE0NTQyYjk2M2M2NjEwOGMwZTk4ZjJcIixcInVybF9pZHNcIjpbXCI2OWExOTZiYWIzODNmN2YyZDcxMDQxMjA3MWQ5NjhmNmRiYmVlMDQ4XCJdfSJ9";
73 | target = "https://steadyhq.com/project/posts/9fb601e4-b327-4f6a-579c-e54c8415bf4b";
74 | redirectRule = new RedirectRule({ redirectUrl: "{search.p|decodeBase64|/.*\"(http.*?)\".*/$1|//\\\\+/}" });
75 | expect(redirectRule.apply(request)).toBe(target);
76 | });
77 |
78 | test("Substring replace pattern - regexp repetition quantifiers ", () => {
79 | let request, target, redirectRule;
80 | request = "https://i.imgur.com/cijC2a2l.jpg";
81 | target = "https://i.imgur.com/cijC2a2.jpg";
82 | redirectRule = new RedirectRule({ redirectUrl: "{origin}{pathname/l\\.([a-zA-Z]{3,4})$/.$1}{search}{hash}" });
83 | expect(redirectRule.apply(request)).toBe(target);
84 |
85 | request = "https://example.com/?1234";
86 | target = "https://example.com/";
87 | redirectRule = new RedirectRule({ redirectUrl: "{origin}{pathname}{search/(?:[?&]\\d{4,}?(?=$|[?#])|([?&])\\d{4,}?[?&])/$1}{hash}" });
88 | expect(redirectRule.apply(request)).toBe(target);
89 | });
90 |
91 | test("Substring extraction pattern", () => {
92 | let request, target, redirectRule;
93 | request = "https://foo.bar/path";
94 | target = "http://bar.com/some/new/path";
95 | redirectRule = new RedirectRule({ redirectUrl: "{protocol:0:4}://{host:-3}.com/some/new/path" });
96 | expect(redirectRule.apply(request)).toBe(target);
97 | });
98 |
99 | test("String manipulations combined", () => {
100 | let request, target, redirectRule;
101 | request = "http://foo.bar.co.uk/path";
102 | target = "https://bar.com/some/new/path";
103 | redirectRule = new RedirectRule({ redirectUrl: "https://{host::-3|/\\.co$/.com|:4}/some/new/path" });
104 | expect(redirectRule.apply(request)).toBe(target);
105 | });
106 |
107 | test("String manipulations combined - Bad manipulation", () => {
108 | let request, target, redirectRule;
109 | request = "http://foo.bar.co.uk/path";
110 | target = "https://foo.bar.com/some/new/path";
111 | redirectRule = new RedirectRule({ redirectUrl: "https://{host::-3|/\\.co$/.com|fail}/some/new/path" });
112 | expect(redirectRule.apply(request)).toBe(target);
113 | });
114 |
115 | test("Redirect instructions - single", () => {
116 | let request, target, redirectRule;
117 | request = "http://foo.bar.co.uk/path";
118 | target = "http://foo.bar.co.uk:8080/path";
119 | redirectRule = new RedirectRule({ redirectUrl: "[port=8080]" });
120 | expect(redirectRule.apply(request)).toBe(target);
121 | });
122 |
123 | test("Redirect instructions - multiple", () => {
124 | let request, target, redirectRule;
125 | request = "http://foo.bar.co.uk/path";
126 | target = "http://localhost:8080/path";
127 | redirectRule = new RedirectRule({ redirectUrl: "[host=localhost][port=8080]" });
128 | expect(redirectRule.apply(request)).toBe(target);
129 | });
130 |
131 | test("Redirect instructions - combined", () => {
132 | let request, target, redirectRule;
133 | request = "http://foo.bar.co.uk/path";
134 | target = "https://bar.com:1234/some/new/path?foo=bar#foobar";
135 | redirectRule = new RedirectRule({ redirectUrl: "ht[port=1234]tps://{host::-3|/\\.co$/.com|:4}/some/new/path[hash=foobar][search=foo=bar]" });
136 | expect(redirectRule.apply(request)).toBe(target);
137 | });
138 |
139 | test("Redirect instructions - combined 2", () => {
140 | let request, target, redirectRule;
141 | request = "http://foo.com/path";
142 | target = "https://bar.com/path#myhash=com";
143 | redirectRule = new RedirectRule({ redirectUrl: "[protocol=https][hash=myhash={host:-3}][host={host/foo/bar}]" });
144 | expect(redirectRule.apply(request)).toBe(target);
145 | });
146 |
147 | test("Redirect instructions - hostname", () => {
148 | let request, target, redirectRule;
149 | request = "https://en.m.wikipedia.org/wiki/Main_Page";
150 | target = "https://en.wikipedia.org/wiki/Main_Page";
151 | redirectRule = new RedirectRule({ redirectUrl: "[hostname={hostname/\\.m\\./.}]" });
152 | expect(redirectRule.apply(request)).toBe(target);
153 | });
154 |
155 | test("Redirect instructions - hostname replace pattern", () => {
156 | let request, target, redirectRule;
157 | request = "https://en.m.wikipedia.org/wiki/Main_Page";
158 | target = "https://en.wikipedia.org/wiki/Main_Page";
159 | redirectRule = new RedirectRule({ redirectUrl: "[hostname={hostname/\\.[mi]\\./.}]" });
160 | expect(redirectRule.apply(request)).toBe(target);
161 | });
162 |
163 | test("Capture Search Parameter - found", () => {
164 | const request = "http://go.redirectingat.com/?xs=1&id=xxxxxxx&sref=http%3A%2F%2Fwww.vulture.com%2F2018%2F05%2Fthe-end-of-nature-at-storm-king-art-center-in-new-york.html&xcust=xxxxxxxx&url=https%3A%2F%2Fen.wikipedia.org%2Fwiki%2FNathaniel_Parker_Willis";
165 | const target = "https://en.wikipedia.org/wiki/Nathaniel_Parker_Willis";
166 | const redirectRule = new RedirectRule({ redirectUrl: "{search.url|decodeURIComponent}" });
167 | expect(redirectRule.apply(request)).toBe(target);
168 | });
169 |
170 | test("Set Search Parameter", () => {
171 | const request = "http://example.com/?query=none";
172 | const target = "http://example.com/?query=set";
173 | const redirectRule = new RedirectRule({ redirectUrl: "[search.query=set]" });
174 | expect(redirectRule.apply(request)).toBe(target);
175 | });
176 |
177 | test("Set Search Parameter - encode", () => {
178 | const request = "http://example.com/?query=none";
179 | const target = "http://example.com/?query=http%3A%2F%2Fexample.com";
180 | const redirectRule = new RedirectRule({ redirectUrl: "[search.query={origin|encodeURIComponent}]" });
181 | expect(redirectRule.apply(request)).toBe(target);
182 | });
183 |
184 | test("Capture Search Parameter - not found", () => {
185 | const request = "http://go.redirectingat.com/?xs=1&id=xxxxxxx&sref=http%3A%2F%2Fwww.vulture.com%2F2018%2F05%2Fthe-end-of-nature-at-storm-king-art-center-in-new-york.html&xcust=xxxxxxxx&url=https%3A%2F%2Fen.wikipedia.org%2Fwiki%2FNathaniel_Parker_Willis";
186 | const redirectRule = new RedirectRule({ redirectUrl: "{search.foo|decodeURIComponent}" });
187 | expect(redirectRule.apply(request)).toBe("");
188 | });
189 |
--------------------------------------------------------------------------------
/test/filter.test.js:
--------------------------------------------------------------------------------
1 | import { FilterRule } from "../src/main/rules/filter";
2 | import { parseInlineUrl, trimQueryParameters } from "../src/main/url";
3 | import { createRegexpPattern } from "../src/util/regexp";
4 |
5 | test("Filter inline url redirection", () => {
6 | const request = "http://foo.com/click?p=240631&a=2314955&g=21407340&url=http%3A%2F%2Fbar.com%2Fkodin-elektroniikka%2Fintel-core-i7-8700k-3-7-ghz-12mb-socket-1151-p41787528";
7 | const target = "http://bar.com/kodin-elektroniikka/intel-core-i7-8700k-3-7-ghz-12mb-socket-1151-p41787528";
8 | expect(new FilterRule().apply(request)).toBe(target);
9 | });
10 |
11 | test("Skip inline url filtering", () => {
12 | const request = "http://foo.com/click?p=240631&a=2314955&g=21407340&url=http%3A%2F%2Fbar.com%2Fkodin-elektroniikka%2Fintel-core-i7-8700k-3-7-ghz-12mb-socket-1151-p41787528%3Futm_source%3Dmuropaketti%26utm_medium%3Dcpc%26utm_campaign%3Dmuropaketti";
13 | const target = "http://foo.com/click?p=240631&a=2314955&g=21407340";
14 | expect(new FilterRule({
15 | paramsFilter: { values: ["url"] },
16 | skipRedirectionFilter: true
17 | }).apply(request)).toBe(target);
18 | });
19 |
20 | test("Skip inline url filtering on same domain", () => {
21 | const request = "http://foo.com/click?p=240631&a=2314955&g=21407340&url=http%3A%2F%2Ffoo.com%2Fkodin-elektroniikka%2Fintel-core-i7-8700k-3-7-ghz-12mb-socket-1151-p41787528%3Futm_source%3Dmuropaketti%26utm_medium%3Dcpc%26utm_campaign%3Dmuropaketti";
22 | expect(new FilterRule({
23 | skipOnSameDomain: true
24 | }).apply(request)).toBe(request);
25 | });
26 |
27 | test("Filter inline url redirection - trim query params before", () => {
28 | const request = "http://foo.com/click?p=240631&a=2314955&g=21407340&url=http%3A%2F%2Ffoo.com%2Fkodin-elektroniikka%2Fintel-core-i7-8700k-3-7-ghz-12mb-socket-1151-p41787528%3Futm_source%3Dmuropaketti%26utm_medium%3Dcpc%26utm_campaign%3Dmuropaketti";
29 | const target = "http://foo.com/click?p=240631&a=2314955&g=21407340";
30 | expect(new FilterRule({
31 | paramsFilter: { values: ["url"] }
32 | }).apply(request)).toBe(target);
33 | });
34 |
35 | test("Filter inline url redirection - query params trimming not applied on parsed inline url", () => {
36 | const request = "http://foo.com/?adobeRef=62716f6088c511e987842211e560f7e80001&sdtid=13131712&sdop=1&sdpid=128047819&sdfid=9&sdfib=1&lno=1&trd=https%20play%20google%20com%20store%20app%20&pv=&au=&u2=https%3A%2F%2Fplay.google.com%2Fstore%2Fapps%2Fdetails%3Fid%3Dcom.pockettrend.neomonsters";
37 | const target = "https://play.google.com/store/apps/details?id=com.pockettrend.neomonsters";
38 | expect(new FilterRule({
39 | paramsFilter: {
40 | values: ["u2"],
41 | invert: true
42 | }
43 | }).apply(request)).toBe(target);
44 | });
45 |
46 | test("Trim query parameters", () => {
47 | let request, target, filterRule;
48 |
49 | filterRule = new FilterRule({ paramsFilter: { values: ["utm_*", "feature", "parameter"] } });
50 | request = "https://www.youtube.com/watch?v=yWtFGtIlzyQ&feature=em-uploademail?parameter&utm_source&key=value?utm_medium=abc¶meter&utm_term?key=value&utm_medium=abc";
51 | target = "https://www.youtube.com/watch?v=yWtFGtIlzyQ?key=value?key=value";
52 | expect(filterRule.apply(request)).toBe(target);
53 |
54 | filterRule = new FilterRule({ paramsFilter: { values: ["utm_source", "utm_medium"] } });
55 | request = "https://www.ghacks.net/2017/04/30/firefox-nightly-marks-legacy-add-ons/?utm_source=feedburner&utm_medium=feed";
56 | target = "https://www.ghacks.net/2017/04/30/firefox-nightly-marks-legacy-add-ons/";
57 | expect(filterRule.apply(request)).toBe(target);
58 |
59 | filterRule = new FilterRule({
60 | paramsFilter: {
61 | values: ["ws_ab_test", "btsid", "algo_expid", "algo_pvid"]
62 | }
63 | });
64 | request = "https://www.aliexpress.com/item/Xiaomi-Mini-Router-2-4GHz-5GHz-Dual-Band-Max-1167Mbps-Support-Wifi-802-11ac-Xiaomi-Mi/32773978417.html?ws_ab_test=searchweb0_0,searchweb201602_3_10152_10065_10151_10068_436_10136_10137_10157_10060_10138_10155_10062_10156_10154_10056_10055_10054_10059_10099_10103_10102_10096_10169_10147_10052_10053_10142_10107_10050_10051_9985_10084_10083_10080_10082_10081_10110_10111_10112_10113_10114_10181_10183_10182_10078_10079_10073_10070_10123-9985,searchweb201603_2,ppcSwitch_5&btsid=3f9443f8-38ad-472c-b6a6-00b8a3db74a3&algo_expid=8f505cf2-0671-4c52-b976-d7a3169da8bc-6&algo_pvid=8f505cf2-0671-4c52-b976-d7a3169da8bc";
65 | target = "https://www.aliexpress.com/item/Xiaomi-Mini-Router-2-4GHz-5GHz-Dual-Band-Max-1167Mbps-Support-Wifi-802-11ac-Xiaomi-Mi/32773978417.html";
66 | expect(filterRule.apply(request)).toBe(target);
67 |
68 | filterRule = new FilterRule({ paramsFilter: { values: ["sid"] } });
69 | request = "http://forums.mozillazine.org/viewtopic.php?sid=6fa91cda58212e7e869dc2022b9e6217&f=48&t=1920191";
70 | target = "http://forums.mozillazine.org/viewtopic.php?f=48&t=1920191";
71 | expect(filterRule.apply(request)).toBe(target);
72 | request = "http://forums.mozillazine.org/viewtopic.php?f=48&sid=6fa91cda58212e7e869dc2022b9e6217&t=1920191";
73 | expect(filterRule.apply(request)).toBe(target);
74 | request = "http://forums.mozillazine.org/viewtopic.php?f=48&t=1920191&sid=6fa91cda58212e7e869dc2022b9e6217";
75 | expect(filterRule.apply(request)).toBe(target);
76 | });
77 |
78 | test("Trim query parameters (inverted)", () => {
79 | const request = "https://www.youtube.com/watch?v=yWtFGtIlzyQ&feature=em-uploademail?parameter&utm_source&key=value?utm_medium=abc¶meter&utm_term?key=value&utm_medium=abc";
80 | const target = "https://www.youtube.com/watch?feature=em-uploademail?parameter&utm_source?utm_medium=abc¶meter&utm_term?utm_medium=abc";
81 | expect(new FilterRule({
82 | paramsFilter: {
83 | values: ["utm_*", "feature", "parameter"],
84 | invert: true
85 | }
86 | }).apply(request)).toBe(target);
87 | });
88 |
89 | test("Remove all query parameters", () => {
90 | const request = "https://www.youtube.com/watch?v=yWtFGtIlzyQ&feature=em-uploademail#hash";
91 | const target = "https://www.youtube.com/watch#hash";
92 | expect(new FilterRule({ trimAllParams: true }).apply(request)).toBe(target);
93 | });
94 |
95 | test("Filter inline url redirection - trim parameters before inline url parsing", () => {
96 | const request = "http://go.redirectingat.com/?xs=1&id=xxxxxxx&sref=http%3A%2F%2Fwww.vulture.com%2F2018%2F05%2Fthe-end-of-nature-at-storm-king-art-center-in-new-york.html&xcust=xxxxxxxx&url=https%3A%2F%2Fen.wikipedia.org%2Fwiki%2FNathaniel_Parker_Willis";
97 | const target = "https://en.wikipedia.org/wiki/Nathaniel_Parker_Willis";
98 | expect(new FilterRule({ paramsFilter: { values: ["sref"] } }).apply(request)).toBe(target);
99 | });
100 |
101 | test("Inline url parsing", () => {
102 | expect(
103 | parseInlineUrl("https://steamcommunity.com/linkfilter/?url=https://addons.mozilla.org/")
104 | ).toBe("https://addons.mozilla.org/");
105 | expect(
106 | parseInlineUrl("https://outgoing.prod.mozaws.net/v1/ca408bc92003166eec54f20e68d7c771ae749b005b72d054ada33f0ef261367d/https%3A//github.com/tumpio/requestcontrol")
107 | ).toBe("https://github.com/tumpio/requestcontrol");
108 | expect(
109 | parseInlineUrl("http://www.deviantart.com/users/outgoing?http://foobar2000.org")
110 | ).toBe("http://foobar2000.org");
111 | expect(
112 | parseInlineUrl("https://www.site2.com/chrome/?i-would-rather-use-firefox=https%3A%2F%2Fwww.mozilla.org/")
113 | ).toBe("https://www.mozilla.org/");
114 | expect(parseInlineUrl("https://site.com/away.php?to=https://github.com&cc_key=")).toBe("https://github.com");
115 | expect(
116 | parseInlineUrl("https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&ved=0ahUKEwiGvaeL1-HTAhUBP5oKHfDoDqQQFggrMAA&url=https%3A%2F%2Faddons.mozilla.org%2F&usg=AFQjCNGoTdPVJJYDmaDkKoFSpuasv6HVCg&cad=rjt")
117 | ).toBe("https://addons.mozilla.org/");
118 | expect(
119 | parseInlineUrl("https://l.facebook.com/l.php?u=https%3A%2F%2Fwww.fsf.org%2Fcampaigns%2F&h=ATP1kf98S0FxqErjoW8VmdSllIp4veuH2_m1jl69sEEeLzUXbkNXrVnzRMp65r5vf21LJGTgJwR2b66m97zYJoXx951n-pr4ruS1osMvT2c9ITsplpPU37RlSqJsSgba&s=1")
120 | ).toBe("https://www.fsf.org/campaigns/");
121 | expect(
122 | parseInlineUrl("https://out.reddit.com/t3_5pq7qd?url=https%3A%2F%2Finternethealthreport.org%2Fv01%2F&token=AQAAZV6JWHBBnIcVjV1wvxVg5gKyCQQSdUhGIvuEUmdPZhxhm8kH&app_name=reddit.com")
123 | ).toBe("https://internethealthreport.org/v01/");
124 | expect(
125 | parseInlineUrl("http://site3.com/?r=https%3A%2F%2Fwww.yr.no%2Fplace%2FNorway%2FNordland%2FBr%C3%B8nn%C3%B8y%2FBr%C3%B8nn%C3%B8ysund%2Fhour_by_hour.html?key=ms&ww=51802")
126 | ).toBe(
127 | "https://www.yr.no/place/Norway/Nordland/Brønnøy/Brønnøysund/hour_by_hour.html"
128 | );
129 | expect(
130 | parseInlineUrl("http://www.deviantart.com/users/outgoing?https://scontent.ftpa1-1.fna.fbcdn.net/v/t1.0-9/19437615_10154946431942669_5896185388243732024_n.jpg?oh=f7eb69d10ee9217944c18955d3a631ad&oe=5A0F78B4")
131 | ).toBe(
132 | "https://scontent.ftpa1-1.fna.fbcdn.net/v/t1.0-9/19437615_10154946431942669_5896185388243732024_n.jpg?oh=f7eb69d10ee9217944c18955d3a631ad&oe=5A0F78B4"
133 | );
134 | expect(
135 | parseInlineUrl("http://site.com/?r=https&foo=bar%3A%2F%2Fwww.yr.no%2Fplace%2FNorway%2FNordland%2FBr%C3%B8nn%C3%B8y%2FBr%C3%B8nn%C3%B8ysund%2Fhour_by_hour.html?key=ms&ww=51802")
136 | ).toBe(null);
137 | expect(parseInlineUrl("http://site.com/?r=www.example.com")).toBe(null);
138 | });
139 |
140 | test("Query parameter trimming", () => {
141 | expect(trimQueryParameters(
142 | "http://site.com/?parameter&utm_source&key=value?utm_medium=abc¶meter&utm_term?key=value&utm_medium=abc",
143 | createRegexpPattern(["utm_source", "utm_medium", "utm_term", "utm_content", "utm_campaign",
144 | "utm_reader", "utm_place"])
145 | )).toBe("http://site.com/?parameter&key=value?parameter?key=value");
146 | expect(trimQueryParameters(
147 | "http://site.com/?parameter&utm_source&key=value?utm_medium=abc¶meter&utm_term?key=value&utm_medium=abc",
148 | createRegexpPattern(["utm_medium", "utm_term", "utm_content", "utm_campaign",
149 | "utm_reader", "utm_place"])
150 | )).toBe("http://site.com/?parameter&utm_source&key=value?parameter?key=value");
151 | expect(trimQueryParameters(
152 | "http://site.com/??utm_source¶meter&utm_source&key=value???utm_medium=abc¶meter&utm_term?key=value&utm_medium=abc",
153 | createRegexpPattern(["utm_source", "utm_medium", "utm_term", "utm_content", "utm_campaign",
154 | "utm_reader", "utm_place"])
155 | )).toBe("http://site.com/??parameter&key=value???parameter?key=value");
156 | expect(trimQueryParameters(
157 | "http://site.com/??utm_source¶meter&utm_source&key=value???utm_medium=abc¶meter&utm_term?key=value&utm_medium=abc",
158 | createRegexpPattern(["u?m_*"])
159 | )).toBe("http://site.com/??parameter&key=value???parameter?key=value");
160 | expect(trimQueryParameters(
161 | "http://site.com/?parameter&utm_source&key=value?utm_medium=abc¶meter&utm_term?key=value&utm_medium=abc",
162 | createRegexpPattern(["utm_source", "utm_medium", "utm_term", "utm_content", "utm_campaign",
163 | "utm_reader", "utm_place"]), true
164 | )).toBe("http://site.com/?utm_source?utm_medium=abc&utm_term?utm_medium=abc");
165 | expect(trimQueryParameters(
166 | "http://site.com/?parameter&utm_source&key=value?utm_medium=abc¶meter&utm_term?key=value&utm_medium=abc",
167 | createRegexpPattern(["/[parmetr]+/"]), true
168 | )).toBe("http://site.com/?parameter?parameter");
169 | expect(trimQueryParameters(
170 | "http://site.com/?parameter&utm_source&key=value?utm_medium=abc¶meter&utm_term?key=value&utm_medium=abc",
171 | createRegexpPattern(["/..._.{5,}/"]), false
172 | )).toBe("http://site.com/?parameter&key=value?parameter&utm_term?key=value");
173 | expect(trimQueryParameters(
174 | "http://site.com/?",
175 | createRegexpPattern(["?"]), true
176 | )).toBe("http://site.com/?");
177 | });
178 |
--------------------------------------------------------------------------------