Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Side by Side Diff: lib/contentPolicy.js

Issue 29329336: Issue 3208 - Inject content policy implementation into all processes (Closed)
Patch Set: Created Oct. 22, 2015, 9:03 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff | Download patch
OLDNEW
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
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 }
OLDNEW

Powered by Google App Engine
This is Rietveld