Left: | ||
Right: |
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 /** | 18 /** |
19 * @fileOverview Content policy implementation, responsible for blocking things. | 19 * @fileOverview Content policy implementation, responsible for blocking things. |
20 */ | 20 */ |
21 | 21 |
22 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); | 22 let {Services} = Cu.import("resource://gre/modules/Services.jsm", {}); |
23 Cu.import("resource://gre/modules/Services.jsm"); | |
24 | 23 |
25 let {Utils} = require("utils"); | 24 let {Utils} = require("utils"); |
26 let {Prefs} = require("prefs"); | 25 let {Prefs} = require("prefs"); |
27 let {FilterStorage} = require("filterStorage"); | 26 let {FilterStorage} = require("filterStorage"); |
28 let {BlockingFilter, WhitelistFilter, RegExpFilter} = require("filterClasses"); | 27 let {BlockingFilter, WhitelistFilter, RegExpFilter} = require("filterClasses"); |
29 let {defaultMatcher} = require("matcher"); | 28 let {defaultMatcher} = require("matcher"); |
30 let {objectMouseEventHander} = require("objectTabs"); | |
31 let {RequestNotifier} = require("requestNotifier"); | |
32 let {ElemHide} = require("elemHide"); | 29 let {ElemHide} = require("elemHide"); |
33 | 30 |
34 /** | 31 /** |
35 * List of explicitly supported content types | 32 * List of explicitly supported content types |
36 * @type string[] | 33 * @type string[] |
37 */ | 34 */ |
38 let contentTypes = ["OTHER", "SCRIPT", "IMAGE", "STYLESHEET", "OBJECT", "SUBDOCU MENT", "DOCUMENT", "XMLHTTPREQUEST", "OBJECT_SUBREQUEST", "FONT", "MEDIA"]; | 35 let contentTypes = new Set([ |
36 "OTHER", "SCRIPT", "IMAGE", "STYLESHEET", "OBJECT", "SUBDOCUMENT", "DOCUMENT", | |
37 "XMLHTTPREQUEST", "OBJECT_SUBREQUEST", "FONT", "MEDIA", "ELEMHIDE", "POPUP", | |
38 "GENERICHIDE", "GENERICBLOCK" | |
39 ]); | |
39 | 40 |
40 /** | 41 /** |
41 * List of content types that aren't associated with a visual document area | 42 * List of content types that aren't associated with a visual document area |
42 * @type string[] | 43 * @type string[] |
43 */ | 44 */ |
44 let nonVisualTypes = ["SCRIPT", "STYLESHEET", "XMLHTTPREQUEST", "OBJECT_SUBREQUE ST", "FONT"]; | 45 let nonVisualTypes = new Set([ |
45 | 46 "SCRIPT", "STYLESHEET", "XMLHTTPREQUEST", "OBJECT_SUBREQUEST", "FONT", |
46 /** | 47 "ELEMHIDE", "POPUP", "GENERICHIDE", "GENERICBLOCK" |
47 * Randomly generated class name, to be applied to collapsed nodes. | 48 ]); |
48 */ | |
49 let collapsedClass = ""; | |
50 | 49 |
51 /** | 50 /** |
52 * Public policy checking functions and auxiliary objects | 51 * Public policy checking functions and auxiliary objects |
53 * @class | 52 * @class |
54 */ | 53 */ |
55 var Policy = exports.Policy = | 54 var Policy = exports.Policy = |
56 { | 55 { |
57 /** | 56 /** |
58 * Map of content type identifiers by their name. | |
59 * @type Object | |
60 */ | |
61 type: {}, | |
62 | |
63 /** | |
64 * Map of content type names by their identifiers (reverse of type map). | |
65 * @type Object | |
66 */ | |
67 typeDescr: {}, | |
68 | |
69 /** | |
70 * Map of numerical content types with their corresponding masks | |
71 * @type Object | |
72 */ | |
73 typeMask: {}, | |
74 | |
75 /** | |
76 * Map of localized content type names by their identifiers. | 57 * Map of localized content type names by their identifiers. |
77 * @type Object | 58 * @type Map |
78 */ | 59 */ |
79 localizedDescr: {}, | 60 localizedDescr: new Map(), |
80 | |
81 /** | |
82 * Lists the non-visual content types. | |
83 * @type Object | |
84 */ | |
85 nonVisual: {}, | |
Wladimir Palant
2015/10/22 21:51:13
I removed that member - it isn't necessary if nonV
| |
86 | 61 |
87 /** | 62 /** |
88 * Map containing all schemes that should be ignored by content policy. | 63 * Map containing all schemes that should be ignored by content policy. |
89 * @type Object | 64 * @type Object |
90 */ | 65 */ |
91 whitelistSchemes: {}, | 66 whitelistSchemes: new Set(), |
92 | 67 |
93 /** | 68 /** |
94 * Called on module startup, initializes various exported properties. | 69 * Called on module startup, initializes various exported properties. |
95 */ | 70 */ |
96 init: function() | 71 init: function() |
97 { | 72 { |
98 // type constant by type description and type description by type constant | 73 // type constant by type description and type description by type constant |
99 let iface = Ci.nsIContentPolicy; | |
100 for (let typeName of contentTypes) | 74 for (let typeName of contentTypes) |
101 { | 75 this.localizedDescr.set(typeName, Utils.getString("type_label_" + typeName .toLowerCase())); |
102 if ("TYPE_" + typeName in iface) | |
103 { | |
104 let id = iface["TYPE_" + typeName]; | |
105 this.type[typeName] = id; | |
106 this.typeDescr[id] = typeName; | |
107 this.localizedDescr[id] = Utils.getString("type_label_" + typeName.toLow erCase()); | |
108 this.typeMask[id] = RegExpFilter.typeMap[typeName]; | |
109 } | |
110 } | |
111 | |
112 this.type.ELEMHIDE = 0xFFFD; | |
113 this.typeDescr[0xFFFD] = "ELEMHIDE"; | |
114 this.localizedDescr[0xFFFD] = Utils.getString("type_label_elemhide"); | |
115 this.typeMask[0xFFFD] = RegExpFilter.typeMap.ELEMHIDE; | |
116 | |
117 this.type.POPUP = 0xFFFE; | |
118 this.typeDescr[0xFFFE] = "POPUP"; | |
119 this.localizedDescr[0xFFFE] = Utils.getString("type_label_popup"); | |
120 this.typeMask[0xFFFE] = RegExpFilter.typeMap.POPUP; | |
121 | |
122 for (let type of nonVisualTypes) | |
123 this.nonVisual[this.type[type]] = true; | |
124 | 76 |
125 // whitelisted URL schemes | 77 // whitelisted URL schemes |
126 for (let scheme of Prefs.whitelistschemes.toLowerCase().split(" ")) | 78 for (let scheme of Prefs.whitelistschemes.toLowerCase().split(" ")) |
127 this.whitelistSchemes[scheme] = true; | 79 this.whitelistSchemes.add(scheme); |
80 | |
81 onShutdown.add(function() | |
82 { | |
83 Utils.styleService.unregisterSheet(collapseStyle, Ci.nsIStyleSheetService. USER_SHEET); | |
84 }); | |
85 | |
86 let messageManager = Cc["@mozilla.org/parentprocessmessagemanager;1"] | |
87 .getService(Ci.nsIMessageListenerManager) | |
88 .QueryInterface(Ci.nsIMessageBroadcaster); | |
89 let handler = (message => JSON.stringify(this.shouldLoad(message.data))); | |
90 messageManager.addMessageListener("AdblockPlus:ShouldLoad", handler); | |
91 onShutdown.add(() => messageManager.removeMessageListener("AdblockPlus:Shoul dLoad", handler)); | |
128 | 92 |
129 // Generate class identifier used to collapse node and register correspondin g | 93 // Generate class identifier used to collapse node and register correspondin g |
130 // stylesheet. | 94 // stylesheet. |
95 let collapsedClass = ""; | |
131 let offset = "a".charCodeAt(0); | 96 let offset = "a".charCodeAt(0); |
132 for (let i = 0; i < 20; i++) | 97 for (let i = 0; i < 20; i++) |
133 collapsedClass += String.fromCharCode(offset + Math.random() * 26); | 98 collapsedClass += String.fromCharCode(offset + Math.random() * 26); |
99 messageManager.broadcastAsyncMessage("AdblockPlus:CollapsedClass", collapsed Class); | |
134 | 100 |
135 let collapseStyle = Services.io.newURI("data:text/css," + | 101 let collapseStyle = Services.io.newURI("data:text/css," + |
136 encodeURIComponent("." + collapsedClass + | 102 encodeURIComponent("." + collapsedClass + |
137 "{-moz-binding: url(chrome://global/content/bindings/general.xml#foobarb azdummy) !important;}"), null, null); | 103 "{-moz-binding: url(chrome://global/content/bindings/general.xml#foobarb azdummy) !important;}"), null, null); |
138 Utils.styleService.loadAndRegisterSheet(collapseStyle, Ci.nsIStyleSheetServi ce.USER_SHEET); | 104 Utils.styleService.loadAndRegisterSheet(collapseStyle, Ci.nsIStyleSheetServi ce.USER_SHEET); |
139 onShutdown.add(function() | |
140 { | |
141 Utils.styleService.unregisterSheet(collapseStyle, Ci.nsIStyleSheetService. USER_SHEET); | |
142 }); | |
143 }, | 105 }, |
144 | 106 |
145 /** | 107 /** |
146 * Checks whether a node should be blocked, hides it if necessary | 108 * Checks whether a particular request should be blocked. |
147 * @param wnd {nsIDOMWindow} | |
148 * @param node {nsIDOMElement} | |
149 * @param contentType {String} | 109 * @param contentType {String} |
150 * @param location {nsIURI} | 110 * @param location {String} |
151 * @param collapse {Boolean} true to force hiding of the node | 111 * @param frames {Object[]} |
152 * @return {Boolean} false if the node should be blocked | 112 * @return {Object} An object containing properties block, collapse and hits |
153 */ | 113 * indicating how this request should be handled. |
154 processNode: function(wnd, node, contentType, location, collapse) | 114 */ |
155 { | 115 shouldLoad: function({contentType, location, frames, isPrivate}) |
156 let topWnd = wnd.top; | 116 { |
157 if (!topWnd || !topWnd.location || !topWnd.location.href) | 117 let wndLocation = frames[0].location; |
158 return true; | |
159 | |
160 let originWindow = Utils.getOriginWindow(wnd); | |
161 let wndLocation = originWindow.location.href; | |
162 let docDomain = getHostname(wndLocation); | 118 let docDomain = getHostname(wndLocation); |
163 let match = null; | 119 let match = null; |
164 let [sitekey, sitekeyWnd] = getSitekey(wnd); | 120 let [sitekey, sitekeyFrame] = getSitekey(frames); |
165 let nogeneric = false; | 121 let nogeneric = false; |
166 | 122 |
123 let hits = []; | |
124 | |
125 function response(block, collapse) | |
126 { | |
127 block = !!block; | |
128 return {block, collapse, hits}; | |
129 } | |
130 | |
131 if (!contentTypes.has(contentType)) | |
132 contentType = "OTHER"; | |
Wladimir Palant
2015/10/22 21:51:13
This check was in PolicyImplementation.shouldLoad(
| |
133 | |
167 if (!match && Prefs.enabled) | 134 if (!match && Prefs.enabled) |
168 { | 135 { |
169 let testWnd = wnd; | |
170 let testSitekey = sitekey; | 136 let testSitekey = sitekey; |
171 let testSitekeyWnd = sitekeyWnd; | 137 let testSitekeyFrame = sitekeyFrame; |
172 let parentWndLocation = getWindowLocation(testWnd); | 138 for (let i = 0; i < frames.length; i++) |
173 while (true) | |
174 { | 139 { |
175 let testWndLocation = parentWndLocation; | 140 let frame = frames[i]; |
176 parentWndLocation = (testWnd == testWnd.parent ? testWndLocation : getWi ndowLocation(testWnd.parent)); | 141 let testWndLocation = frame.location; |
177 match = Policy.isWhitelisted(testWndLocation, parentWndLocation, testSit ekey); | 142 let parentWndLocation = frames[Math.min(i + 1, frames.length - 1)].locat ion; |
143 let parentDocDomain = getHostname(parentWndLocation); | |
144 match = this.isWhitelisted(testWndLocation, parentWndLocation, testSitek ey); | |
178 | 145 |
179 if (match instanceof WhitelistFilter) | 146 if (match instanceof WhitelistFilter) |
180 { | 147 { |
181 FilterStorage.increaseHitCount(match, wnd); | 148 if (!isPrivate) |
182 RequestNotifier.addNodeData(testWnd.document, topWnd, Policy.type.DOCU MENT, getHostname(parentWndLocation), false, testWndLocation, match); | 149 FilterStorage.increaseHitCount(match); |
183 return true; | 150 |
151 hits.push({ | |
152 frameIndex: i, | |
153 contentType: "DOCUMENT", | |
154 docDomain: parentDocDomain, | |
155 thirdParty: false, | |
156 location: testWndLocation, | |
157 match: match.text | |
158 }); | |
159 return response(false, false); | |
184 } | 160 } |
185 | 161 |
186 let genericType = (contentType == Policy.type.ELEMHIDE ? | 162 if (contentType == "ELEMHIDE") |
187 RegExpFilter.typeMap.GENERICHIDE : | 163 { |
188 RegExpFilter.typeMap.GENERICBLOCK); | 164 match = defaultMatcher.matchesAny(frame.location, RegExpFilter.typeMap .ELEMHIDE, parentDocDomain, false, testSitekey); |
189 let parentDocDomain = getHostname(parentWndLocation); | 165 if (match instanceof WhitelistFilter) |
166 { | |
167 if (!isPrivate) | |
168 FilterStorage.increaseHitCount(match); | |
169 hits.push({ | |
170 frameIndex: i, | |
171 contentType: "ELEMHIDE", | |
172 docDomain: parentDocDomain, | |
173 thirdParty: false, | |
174 location: testWndLocation, | |
175 match: match.text | |
176 }); | |
177 return response(false, false); | |
178 } | |
179 } | |
Wladimir Palant
2015/10/22 21:51:13
This block was further down originally, having a s
| |
180 | |
181 let genericType = (contentType == "ELEMHIDE" ? | |
182 "GENERICHIDE" : | |
183 "GENERICBLOCK"); | |
190 let nogenericMatch = defaultMatcher.matchesAny( | 184 let nogenericMatch = defaultMatcher.matchesAny( |
191 testWndLocation, genericType, parentDocDomain, false, testSitekey | 185 testWndLocation, RegExpFilter.typeMap[genericType], parentDocDomain, f alse, testSitekey |
192 ); | 186 ); |
193 if (nogenericMatch instanceof WhitelistFilter) | 187 if (nogenericMatch instanceof WhitelistFilter) |
194 { | 188 { |
195 nogeneric = true; | 189 nogeneric = true; |
196 | 190 |
197 FilterStorage.increaseHitCount(nogenericMatch, wnd); | 191 if (!isPrivate) |
198 RequestNotifier.addNodeData(testWnd.document, topWnd, contentType, | 192 FilterStorage.increaseHitCount(nogenericMatch); |
199 parentDocDomain, false, testWndLocation, | 193 hits.push({ |
200 nogenericMatch); | 194 frameIndex: i, |
195 contentType: genericType, | |
Wladimir Palant
2015/10/22 21:51:14
Note that this parameter changed: originally the h
| |
196 docDomain: parentDocDomain, | |
197 thirdParty: false, | |
198 location: testWndLocation, | |
199 match: nogenericMatch.text | |
200 }); | |
201 } | 201 } |
202 | 202 |
203 if (testWnd.parent == testWnd) | 203 if (frame == testSitekeyFrame) |
204 break; | 204 [testSitekey, testSitekeyFrame] = getSitekey(frames.slice(i + 1)); |
205 | |
206 if (testWnd == testSitekeyWnd) | |
207 [testSitekey, testSitekeyWnd] = getSitekey(testWnd.parent); | |
208 testWnd = testWnd.parent; | |
209 } | 205 } |
210 } | 206 } |
211 | 207 |
212 // Data loaded by plugins should be attached to the document | 208 if (!match && contentType == "ELEMHIDE") |
213 if (contentType == Policy.type.OBJECT_SUBREQUEST && node instanceof Ci.nsIDO MElement) | 209 { |
214 node = node.ownerDocument; | |
215 | |
216 // Fix type for objects misrepresented as frames or images | |
217 if (contentType != Policy.type.OBJECT && (node instanceof Ci.nsIDOMHTMLObjec tElement || node instanceof Ci.nsIDOMHTMLEmbedElement)) | |
218 contentType = Policy.type.OBJECT; | |
Wladimir Palant
2015/10/22 21:51:13
The two checks above moved to PolicyImplementation
| |
219 | |
220 let locationText = location.spec; | |
221 if (!match && contentType == Policy.type.ELEMHIDE) | |
222 { | |
223 let testWnd = wnd; | |
224 let parentWndLocation = getWindowLocation(testWnd); | |
225 while (true) | |
226 { | |
227 let testWndLocation = parentWndLocation; | |
228 parentWndLocation = (testWnd == testWnd.parent ? testWndLocation : getWi ndowLocation(testWnd.parent)); | |
229 let parentDocDomain = getHostname(parentWndLocation); | |
230 match = defaultMatcher.matchesAny(testWndLocation, RegExpFilter.typeMap. ELEMHIDE, parentDocDomain, false, sitekey); | |
231 if (match instanceof WhitelistFilter) | |
232 { | |
233 FilterStorage.increaseHitCount(match, wnd); | |
234 RequestNotifier.addNodeData(testWnd.document, topWnd, contentType, par entDocDomain, false, testWndLocation, match); | |
235 return true; | |
236 } | |
237 | |
238 if (testWnd.parent == testWnd) | |
239 break; | |
240 else | |
241 testWnd = testWnd.parent; | |
242 } | |
243 | |
244 match = location; | 210 match = location; |
245 locationText = match.text.replace(/^.*?#/, '#'); | 211 location = match.text.replace(/^.*?#/, '#'); |
246 location = locationText; | 212 |
247 | 213 if (!match.isActiveOnDomain(docDomain) || (nogeneric && match.isGeneric()) ) |
Wladimir Palant
2015/10/22 21:51:14
The nogeneric part of this check was further down
| |
248 if (!match.isActiveOnDomain(docDomain)) | 214 return response(false, false); |
249 return true; | |
250 | 215 |
251 let exception = ElemHide.getException(match, docDomain); | 216 let exception = ElemHide.getException(match, docDomain); |
252 if (exception) | 217 if (exception) |
253 { | 218 { |
254 FilterStorage.increaseHitCount(exception, wnd); | 219 if (!isPrivate) |
255 RequestNotifier.addNodeData(node, topWnd, contentType, docDomain, false, locationText, exception); | 220 FilterStorage.increaseHitCount(exception); |
256 return true; | 221 hits.push({ |
222 frameIndex: -1, | |
223 contentType, | |
224 docDomain, | |
225 thirdParty: false, | |
226 location, | |
227 match: match.text | |
228 }); | |
229 return response(false, false); | |
257 } | 230 } |
258 | 231 } |
259 if (nogeneric && match.isGeneric()) | 232 |
260 return true; | 233 let thirdParty = (contentType == "ELEMHIDE" ? false : isThirdParty(location, docDomain)); |
261 } | 234 |
262 | 235 let collapse = false; |
263 let thirdParty = (contentType == Policy.type.ELEMHIDE ? false : isThirdParty (location, docDomain)); | 236 if (!match && Prefs.enabled && RegExpFilter.typeMap.hasOwnProperty(contentTy pe)) |
264 | 237 { |
265 if (!match && Prefs.enabled && contentType in Policy.typeMask) | 238 match = defaultMatcher.matchesAny(location, RegExpFilter.typeMap[contentTy pe], |
266 { | |
267 match = defaultMatcher.matchesAny(locationText, Policy.typeMask[contentTyp e], | |
268 docDomain, thirdParty, sitekey, nogeneri c); | 239 docDomain, thirdParty, sitekey, nogeneri c); |
269 if (match instanceof BlockingFilter && node.ownerDocument && !(contentType in Policy.nonVisual)) | 240 if (match instanceof BlockingFilter && !nonVisualTypes.has(contentType)) |
270 { | 241 { |
271 let prefCollapse = (match.collapse != null ? match.collapse : !Prefs.fas tcollapse); | 242 let prefCollapse = (match.collapse != null ? match.collapse : !Prefs.fas tcollapse); |
272 if (collapse || prefCollapse) | 243 if (collapse || prefCollapse) |
273 schedulePostProcess(node); | 244 collapse = true; |
274 } | 245 } |
275 | |
276 // Track mouse events for objects | |
277 if (!match && contentType == Policy.type.OBJECT && node.nodeType == Ci.nsI DOMNode.ELEMENT_NODE) | |
278 { | |
279 node.addEventListener("mouseover", objectMouseEventHander, true); | |
280 node.addEventListener("mouseout", objectMouseEventHander, true); | |
281 } | |
Wladimir Palant
2015/10/22 21:51:14
This part moved to checkRequest() in the child.
| |
282 } | 246 } |
283 | 247 |
284 // Store node data | 248 // Store node data |
285 RequestNotifier.addNodeData(node, topWnd, contentType, docDomain, thirdParty , locationText, match); | 249 if (match && !isPrivate) |
286 if (match) | 250 FilterStorage.increaseHitCount(match); |
287 FilterStorage.increaseHitCount(match, wnd); | 251 hits.push({ |
288 | 252 frameIndex: -1, |
289 return !match || match instanceof WhitelistFilter; | 253 contentType, |
254 docDomain, | |
255 thirdParty, | |
256 location, | |
257 match: match && match.text | |
258 }); | |
259 | |
260 return response(match && !(match instanceof WhitelistFilter), collapse); | |
290 }, | 261 }, |
291 | 262 |
292 /** | 263 /** |
293 * Checks whether the location's scheme is blockable. | 264 * Checks whether the location's scheme is blockable. |
294 * @param location {nsIURI} | 265 * @param location {nsIURI} |
295 * @return {Boolean} | 266 * @return {Boolean} |
296 */ | 267 */ |
297 isBlockableScheme: function(location) | 268 isBlockableScheme: function(location) |
298 { | 269 { |
299 return !(location.scheme in Policy.whitelistSchemes); | 270 return !this.whitelistSchemes.has(location.scheme); |
300 }, | 271 }, |
301 | 272 |
302 /** | 273 /** |
303 * Checks whether a page is whitelisted. | 274 * Checks whether a page is whitelisted. |
304 * @param {String} url | 275 * @param {String} url |
305 * @param {String} [parentUrl] location of the parent page | 276 * @param {String} [parentUrl] location of the parent page |
306 * @param {String} [sitekey] public key provided on the page | 277 * @param {String} [sitekey] public key provided on the page |
307 * @return {Filter} filter that matched the URL or null if not whitelisted | 278 * @return {Filter} filter that matched the URL or null if not whitelisted |
308 */ | 279 */ |
309 isWhitelisted: function(url, parentUrl, sitekey) | 280 isWhitelisted: function(url, parentUrl, sitekey) |
310 { | 281 { |
311 if (!url) | 282 if (!url) |
312 return null; | 283 return null; |
313 | 284 |
314 // Do not apply exception rules to schemes on our whitelistschemes list. | 285 // Do not apply exception rules to schemes on our whitelistschemes list. |
315 let match = /^([\w\-]+):/.exec(url); | 286 let match = /^([\w\-]+):/.exec(url); |
316 if (match && match[1] in Policy.whitelistSchemes) | 287 if (match && this.whitelistSchemes.has(match[1])) |
317 return null; | 288 return null; |
318 | 289 |
319 if (!parentUrl) | 290 if (!parentUrl) |
320 parentUrl = url; | 291 parentUrl = url; |
321 | 292 |
322 // Ignore fragment identifier | 293 // Ignore fragment identifier |
323 let index = url.indexOf("#"); | 294 let index = url.indexOf("#"); |
324 if (index >= 0) | 295 if (index >= 0) |
325 url = url.substring(0, index); | 296 url = url.substring(0, index); |
326 | 297 |
327 let result = defaultMatcher.matchesAny(url, RegExpFilter.typeMap.DOCUMENT, g etHostname(parentUrl), false, sitekey); | 298 let result = defaultMatcher.matchesAny(url, RegExpFilter.typeMap.DOCUMENT, g etHostname(parentUrl), false, sitekey); |
328 return (result instanceof WhitelistFilter ? result : null); | 299 return (result instanceof WhitelistFilter ? result : null); |
329 }, | 300 }, |
330 | 301 |
331 /** | 302 /** |
332 * Checks whether the page loaded in a window is whitelisted for indication in the UI. | 303 * Checks whether the page loaded in a window is whitelisted for indication in the UI. |
333 * @param wnd {nsIDOMWindow} | 304 * @param wnd {nsIDOMWindow} |
334 * @return {Filter} matching exception rule or null if not whitelisted | 305 * @return {Filter} matching exception rule or null if not whitelisted |
335 */ | 306 */ |
336 isWindowWhitelisted: function(wnd) | 307 isWindowWhitelisted: function(wnd) |
337 { | 308 { |
338 return Policy.isWhitelisted(getWindowLocation(wnd)); | 309 return this.isWhitelisted(getWindowLocation(wnd)); |
339 }, | 310 }, |
340 | 311 |
341 /** | 312 /** |
342 * Asynchronously re-checks filters for given nodes. | 313 * Asynchronously re-checks filters for given nodes. |
343 * @param {Node[]} nodes | 314 * @param {Node[]} nodes |
344 * @param {RequestEntry} entry | 315 * @param {RequestEntry} entry |
345 */ | 316 */ |
346 refilterNodes: function(nodes, entry) | 317 refilterNodes: function(nodes, entry) |
Wladimir Palant
2015/10/22 21:51:14
This functionality is intentionally left broken, f
| |
347 { | 318 { |
348 // Ignore nodes that have been blocked already | 319 // Ignore nodes that have been blocked already |
349 if (entry.filter && !(entry.filter instanceof WhitelistFilter)) | 320 if (entry.filter && !(entry.filter instanceof WhitelistFilter)) |
350 return; | 321 return; |
351 | 322 |
352 for (let node of nodes) | 323 for (let node of nodes) |
353 Utils.runAsync(() => refilterNode(node, entry)); | 324 Utils.runAsync(() => refilterNode(node, entry)); |
354 } | 325 } |
355 }; | 326 }; |
356 Policy.init(); | 327 Policy.init(); |
357 | 328 |
358 /** | 329 /** |
359 * Actual nsIContentPolicy and nsIChannelEventSink implementation | |
360 * @class | |
361 */ | |
362 var PolicyImplementation = | |
363 { | |
364 classDescription: "Adblock Plus content policy", | |
365 classID: Components.ID("cfeaabe6-1dd1-11b2-a0c6-cb5c268894c9"), | |
366 contractID: "@adblockplus.org/abp/policy;1", | |
367 xpcom_categories: ["content-policy", "net-channel-event-sinks"], | |
368 | |
369 /** | |
370 * Registers the content policy on startup. | |
371 */ | |
372 init: function() | |
373 { | |
374 let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar); | |
375 try | |
376 { | |
377 registrar.registerFactory(this.classID, this.classDescription, this.contra ctID, this); | |
378 } | |
379 catch (e) | |
380 { | |
381 // See bug 924340 - it might be too early to init now, the old version | |
382 // we are replacing didn't finish removing itself yet. | |
383 if (e.result == Cr.NS_ERROR_FACTORY_EXISTS) | |
384 { | |
385 Utils.runAsync(this.init.bind(this)); | |
386 return; | |
387 } | |
388 | |
389 Cu.reportError(e); | |
390 } | |
391 | |
392 let catMan = Utils.categoryManager; | |
393 for (let category of this.xpcom_categories) | |
394 catMan.addCategoryEntry(category, this.contractID, this.contractID, false, true); | |
395 | |
396 // http-on-opening-request is new in Gecko 18, http-on-modify-request can | |
397 // be used in earlier releases. | |
398 let httpTopic = "http-on-opening-request"; | |
399 if (Services.vc.compare(Utils.platformVersion, "18.0") < 0) | |
400 httpTopic = "http-on-modify-request"; | |
401 | |
402 Services.obs.addObserver(this, httpTopic, true); | |
403 Services.obs.addObserver(this, "content-document-global-created", true); | |
404 Services.obs.addObserver(this, "xpcom-category-entry-removed", true); | |
405 Services.obs.addObserver(this, "xpcom-category-cleared", true); | |
406 | |
407 onShutdown.add(function() | |
408 { | |
409 // Our category observers should be removed before changing category | |
410 // memberships, just in case. | |
411 Services.obs.removeObserver(this, httpTopic); | |
412 Services.obs.removeObserver(this, "content-document-global-created"); | |
413 Services.obs.removeObserver(this, "xpcom-category-entry-removed"); | |
414 Services.obs.removeObserver(this, "xpcom-category-cleared"); | |
415 | |
416 for (let category of this.xpcom_categories) | |
417 catMan.deleteCategoryEntry(category, this.contractID, false); | |
418 | |
419 // This needs to run asynchronously, see bug 753687 | |
420 Utils.runAsync(function() | |
421 { | |
422 registrar.unregisterFactory(this.classID, this); | |
423 }.bind(this)); | |
424 | |
425 this.previousRequest = null; | |
426 }.bind(this)); | |
427 }, | |
428 | |
429 // | |
430 // nsISupports interface implementation | |
431 // | |
432 | |
433 QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPolicy, Ci.nsIObserver, | |
434 Ci.nsIChannelEventSink, Ci.nsIFactory, Ci.nsISupportsWeakReference]), | |
435 | |
436 // | |
437 // nsIContentPolicy interface implementation | |
438 // | |
439 | |
440 shouldLoad: function(contentType, contentLocation, requestOrigin, node, mimeTy peGuess, extra) | |
441 { | |
442 // Ignore requests without context and top-level documents | |
443 if (!node || contentType == Policy.type.DOCUMENT) | |
444 return Ci.nsIContentPolicy.ACCEPT; | |
445 | |
446 // Ignore standalone objects | |
447 if (contentType == Policy.type.OBJECT && node.ownerDocument && !/^text\/|[+\ /]xml$/.test(node.ownerDocument.contentType)) | |
448 return Ci.nsIContentPolicy.ACCEPT; | |
449 | |
450 let wnd = Utils.getWindow(node); | |
451 if (!wnd) | |
452 return Ci.nsIContentPolicy.ACCEPT; | |
453 | |
454 // Ignore whitelisted schemes | |
455 let location = Utils.unwrapURL(contentLocation); | |
456 if (!Policy.isBlockableScheme(location)) | |
457 return Ci.nsIContentPolicy.ACCEPT; | |
458 | |
459 // Interpret unknown types as "other" | |
460 if (!(contentType in Policy.typeDescr)) | |
461 contentType = Policy.type.OTHER; | |
462 | |
463 let result = Policy.processNode(wnd, node, contentType, location, false); | |
464 if (result) | |
465 { | |
466 // We didn't block this request so we will probably see it again in | |
467 // http-on-opening-request. Keep it so that we can associate it with the | |
468 // channel there - will be needed in case of redirect. | |
469 this.previousRequest = [location, contentType]; | |
470 } | |
471 return (result ? Ci.nsIContentPolicy.ACCEPT : Ci.nsIContentPolicy.REJECT_REQ UEST); | |
472 }, | |
473 | |
474 shouldProcess: function(contentType, contentLocation, requestOrigin, insecNode , mimeType, extra) | |
475 { | |
476 return Ci.nsIContentPolicy.ACCEPT; | |
477 }, | |
478 | |
479 // | |
480 // nsIObserver interface implementation | |
481 // | |
482 observe: function(subject, topic, data, additional) | |
483 { | |
484 switch (topic) | |
485 { | |
486 case "content-document-global-created": | |
487 { | |
488 if (!(subject instanceof Ci.nsIDOMWindow) || !subject.opener) | |
489 return; | |
490 | |
491 let uri = additional || Utils.makeURI(subject.location.href); | |
492 if (!Policy.processNode(subject.opener, subject.opener.document, Policy. type.POPUP, uri, false)) | |
493 { | |
494 subject.stop(); | |
495 Utils.runAsync(() => subject.close()); | |
496 } | |
497 else if (uri.spec == "about:blank") | |
498 { | |
499 // An about:blank pop-up most likely means that a load will be | |
500 // initiated synchronously. Set a flag for our "http-on-opening-reques t" | |
501 // handler. | |
502 this.expectingPopupLoad = true; | |
503 Utils.runAsync(function() | |
504 { | |
505 this.expectingPopupLoad = false; | |
506 }); | |
507 } | |
508 break; | |
509 } | |
510 case "http-on-opening-request": | |
511 case "http-on-modify-request": | |
512 { | |
513 if (!(subject instanceof Ci.nsIHttpChannel)) | |
514 return; | |
515 | |
516 if (this.previousRequest && subject.URI == this.previousRequest[0] && | |
517 subject instanceof Ci.nsIWritablePropertyBag) | |
518 { | |
519 // We just handled a content policy call for this request - associate | |
520 // the data with the channel so that we can find it in case of a redir ect. | |
521 subject.setProperty("abpRequestType", this.previousRequest[1]); | |
522 this.previousRequest = null; | |
523 } | |
524 | |
525 if (this.expectingPopupLoad) | |
526 { | |
527 let wnd = Utils.getRequestWindow(subject); | |
528 if (wnd && wnd.opener && wnd.location.href == "about:blank") | |
529 { | |
530 this.observe(wnd, "content-document-global-created", null, subject.U RI); | |
531 if (subject instanceof Ci.nsIWritablePropertyBag) | |
532 subject.setProperty("abpRequestType", Policy.type.POPUP); | |
533 } | |
534 } | |
535 | |
536 break; | |
537 } | |
538 case "xpcom-category-entry-removed": | |
539 case "xpcom-category-cleared": | |
540 { | |
541 let category = data; | |
542 if (this.xpcom_categories.indexOf(category) < 0) | |
543 return; | |
544 | |
545 if (topic == "xpcom-category-entry-removed" && | |
546 subject instanceof Ci.nsISupportsCString && | |
547 subject.data != this.contractID) | |
548 { | |
549 return; | |
550 } | |
551 | |
552 // Our category entry was removed, make sure to add it back | |
553 let catMan = Utils.categoryManager; | |
554 catMan.addCategoryEntry(category, this.contractID, this.contractID, fals e, true); | |
555 break; | |
556 } | |
557 } | |
558 }, | |
559 | |
560 // | |
561 // nsIChannelEventSink interface implementation | |
562 // | |
563 | |
564 asyncOnChannelRedirect: function(oldChannel, newChannel, flags, callback) | |
565 { | |
566 let result = Cr.NS_OK; | |
567 try | |
568 { | |
569 // Try to retrieve previously stored request data from the channel | |
570 let contentType; | |
571 if (oldChannel instanceof Ci.nsIWritablePropertyBag) | |
572 { | |
573 try | |
574 { | |
575 contentType = oldChannel.getProperty("abpRequestType"); | |
576 } | |
577 catch(e) | |
578 { | |
579 // No data attached, ignore this redirect | |
580 return; | |
581 } | |
582 } | |
583 | |
584 let newLocation = null; | |
585 try | |
586 { | |
587 newLocation = newChannel.URI; | |
588 } catch(e2) {} | |
589 if (!newLocation) | |
590 return; | |
591 | |
592 let wnd = Utils.getRequestWindow(newChannel); | |
593 if (!wnd) | |
594 return; | |
595 | |
596 if (contentType == Policy.type.SUBDOCUMENT && wnd.parent == wnd.top && wnd .opener) | |
597 { | |
598 // This is a window opened in a new tab miscategorized as frame load, | |
599 // see bug 467514. Get the frame as context to be at least consistent. | |
600 wnd = wnd.opener; | |
601 } | |
602 | |
603 if (contentType == Policy.type.POPUP && wnd.opener) | |
604 { | |
605 // Popups are initiated by their opener, not their own window. | |
606 wnd = wnd.opener; | |
607 } | |
608 | |
609 if (!Policy.processNode(wnd, wnd.document, contentType, newLocation, false )) | |
610 result = Cr.NS_BINDING_ABORTED; | |
611 } | |
612 catch (e) | |
613 { | |
614 // We shouldn't throw exceptions here - this will prevent the redirect. | |
615 Cu.reportError(e); | |
616 } | |
617 finally | |
618 { | |
619 callback.onRedirectVerifyCallback(result); | |
620 } | |
621 }, | |
622 | |
623 // | |
624 // nsIFactory interface implementation | |
625 // | |
626 | |
627 createInstance: function(outer, iid) | |
628 { | |
629 if (outer) | |
630 throw Cr.NS_ERROR_NO_AGGREGATION; | |
631 return this.QueryInterface(iid); | |
632 } | |
633 }; | |
634 PolicyImplementation.init(); | |
635 | |
636 /** | |
637 * Nodes scheduled for post-processing (might be null). | |
638 * @type Node[] | |
639 */ | |
640 let scheduledNodes = null; | |
641 | |
642 /** | |
643 * Schedules a node for post-processing. | |
644 */ | |
645 function schedulePostProcess(/**Element*/ node) | |
646 { | |
647 if (scheduledNodes) | |
648 scheduledNodes.push(node); | |
649 else | |
650 { | |
651 scheduledNodes = [node]; | |
652 Utils.runAsync(postProcessNodes); | |
653 } | |
654 } | |
655 | |
656 /** | |
657 * Processes nodes scheduled for post-processing (typically hides them). | |
658 */ | |
659 function postProcessNodes() | |
660 { | |
661 let nodes = scheduledNodes; | |
662 scheduledNodes = null; | |
663 | |
664 for (let node of nodes) | |
665 { | |
666 // adjust frameset's cols/rows for frames | |
667 let parentNode = node.parentNode; | |
668 if (parentNode && parentNode instanceof Ci.nsIDOMHTMLFrameSetElement) | |
669 { | |
670 let hasCols = (parentNode.cols && parentNode.cols.indexOf(",") > 0); | |
671 let hasRows = (parentNode.rows && parentNode.rows.indexOf(",") > 0); | |
672 if ((hasCols || hasRows) && !(hasCols && hasRows)) | |
673 { | |
674 let index = -1; | |
675 for (let frame = node; frame; frame = frame.previousSibling) | |
676 if (frame instanceof Ci.nsIDOMHTMLFrameElement || frame instanceof Ci. nsIDOMHTMLFrameSetElement) | |
677 index++; | |
678 | |
679 let property = (hasCols ? "cols" : "rows"); | |
680 let weights = parentNode[property].split(","); | |
681 weights[index] = "0"; | |
682 parentNode[property] = weights.join(","); | |
683 } | |
684 } | |
685 else | |
686 node.classList.add(collapsedClass); | |
687 } | |
688 } | |
689 | |
690 /** | |
691 * Extracts the hostname from a URL (might return null). | 330 * Extracts the hostname from a URL (might return null). |
692 */ | 331 */ |
693 function getHostname(/**String*/ url) /**String*/ | 332 function getHostname(/**String*/ url) /**String*/ |
694 { | 333 { |
695 try | 334 try |
696 { | 335 { |
697 return Utils.unwrapURL(url).host; | 336 return Utils.unwrapURL(url).host; |
698 } | 337 } |
699 catch(e) | 338 catch(e) |
700 { | 339 { |
701 return null; | 340 return null; |
702 } | 341 } |
703 } | 342 } |
704 | 343 |
705 /** | 344 /** |
706 * Retrieves the sitekey of a window. | 345 * Retrieves the sitekey of a window. |
707 */ | 346 */ |
708 function getSitekey(wnd) | 347 function getSitekey(frames) |
709 { | 348 { |
710 let sitekey = null; | 349 for (let frame of frames) |
350 { | |
351 if (frame.sitekey && frame.sitekey.indexOf("_") >= 0) | |
352 { | |
353 let [key, signature] = frame.sitekey.split("_", 2); | |
354 key = key.replace(/=/g, ""); | |
711 | 355 |
712 while (true) | 356 // Website specifies a key but is the signature valid? |
713 { | 357 let uri = Services.io.newURI(frame.location, null, null); |
714 if (wnd.document && wnd.document.documentElement) | 358 let host = uri.asciiHost; |
715 { | 359 if (uri.port > 0) |
716 let keydata = wnd.document.documentElement.getAttribute("data-adblockkey") ; | 360 host += ":" + uri.port; |
717 if (keydata && keydata.indexOf("_") >= 0) | 361 let params = [ |
718 { | 362 uri.path.replace(/#.*/, ""), // REQUEST_URI |
719 let [key, signature] = keydata.split("_", 2); | 363 host, // HTTP_HOST |
720 key = key.replace(/=/g, ""); | 364 Utils.httpProtocol.userAgent // HTTP_USER_AGENT |
721 | 365 ]; |
722 // Website specifies a key but is the signature valid? | 366 if (Utils.verifySignature(key, signature, params.join("\0"))) |
723 let uri = Services.io.newURI(getWindowLocation(wnd), null, null); | 367 return [key, frame]; |
724 let host = uri.asciiHost; | |
725 if (uri.port > 0) | |
726 host += ":" + uri.port; | |
727 let params = [ | |
728 uri.path.replace(/#.*/, ""), // REQUEST_URI | |
729 host, // HTTP_HOST | |
730 Utils.httpProtocol.userAgent // HTTP_USER_AGENT | |
731 ]; | |
732 if (Utils.verifySignature(key, signature, params.join("\0"))) | |
733 return [key, wnd]; | |
734 } | |
735 } | 368 } |
736 | |
737 if (wnd === wnd.parent) | |
738 break; | |
739 | |
740 wnd = wnd.parent; | |
741 } | 369 } |
742 | 370 |
743 return [sitekey, wnd]; | 371 return [null, null]; |
Wladimir Palant
2015/10/22 21:51:14
Note that the original code was returning sitekey
| |
744 } | 372 } |
745 | 373 |
746 /** | 374 /** |
747 * Retrieves the location of a window. | 375 * Retrieves the location of a window. |
748 * @param wnd {nsIDOMWindow} | 376 * @param wnd {nsIDOMWindow} |
749 * @return {String} window location or null on failure | 377 * @return {String} window location or null on failure |
750 */ | 378 */ |
751 function getWindowLocation(wnd) | 379 function getWindowLocation(wnd) |
752 { | 380 { |
753 if ("name" in wnd && wnd.name == "messagepane") | 381 if ("name" in wnd && wnd.name == "messagepane") |
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
818 if (!wnd || wnd.closed) | 446 if (!wnd || wnd.closed) |
819 return; | 447 return; |
820 | 448 |
821 if (entry.type == Policy.type.OBJECT) | 449 if (entry.type == Policy.type.OBJECT) |
822 { | 450 { |
823 node.removeEventListener("mouseover", objectMouseEventHander, true); | 451 node.removeEventListener("mouseover", objectMouseEventHander, true); |
824 node.removeEventListener("mouseout", objectMouseEventHander, true); | 452 node.removeEventListener("mouseout", objectMouseEventHander, true); |
825 } | 453 } |
826 Policy.processNode(wnd, node, entry.type, Utils.makeURI(entry.location), true) ; | 454 Policy.processNode(wnd, node, entry.type, Utils.makeURI(entry.location), true) ; |
827 } | 455 } |
OLD | NEW |