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

Side by Side Diff: include.preload.js

Issue 29347034: Issue 1727 - Prevent circumvention via WebSocket (Closed)
Patch Set: Addressed feedback Created June 29, 2016, 1:38 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
« no previous file with comments | « no previous file | lib/requestBlocker.js » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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-2016 Eyeo GmbH 3 * Copyright (C) 2006-2016 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
(...skipping 331 matching lines...) Expand 10 before | Expand all | Expand 10 after
342 var observer = new MutationObserver(function() 342 var observer = new MutationObserver(function()
343 { 343 {
344 if (style.parentNode != parentNode) 344 if (style.parentNode != parentNode)
345 parentNode.appendChild(style); 345 parentNode.appendChild(style);
346 }); 346 });
347 347
348 observer.observe(parentNode, {childList: true}); 348 observer.observe(parentNode, {childList: true});
349 return observer; 349 return observer;
350 } 350 }
351 351
352 function protectStyleSheet(document, style) 352 function protectStyleSheet(document, style, script)
353 { 353 {
354 var id = Math.random().toString(36).substr(2) 354 var id = Math.random().toString(36).substr(2);
355 style.id = id; 355 style.id = id;
356 356
357 var code = [ 357 var code = [
358 "(function()", 358 "(function()",
359 "{", 359 "{",
360 ' var style = document.getElementById("' + id + '") ||', 360 ' var style = document.getElementById("' + id + '") ||',
361 ' document.documentElement.shadowRoot.getElementById("' + id + '");', 361 ' document.documentElement.shadowRoot.getElementById("' + id + '");',
362 ' style.removeAttribute("id");' 362 ' style.removeAttribute("id");'
363 ]; 363 ];
364 364
(...skipping 15 matching lines...) Expand all
380 " " + origin + " = function(index)", 380 " " + origin + " = function(index)",
381 " {", 381 " {",
382 " if (this != style.sheet)", 382 " if (this != style.sheet)",
383 " " + method + ".call(this, index);", 383 " " + method + ".call(this, index);",
384 " }"); 384 " }");
385 } 385 }
386 } 386 }
387 387
388 code.push("})();"); 388 code.push("})();");
389 389
390 var script = document.createElement("script"); 390 script.textContent += code.join("\n");
391 script.async = false; 391 }
392 script.textContent = code.join("\n"); 392
393 document.documentElement.appendChild(script); 393 // Neither Chrome[1] nor Safari allow us to intercept WebSockets, and therefore
394 document.documentElement.removeChild(script); 394 // some ad networks are misusing them as a way to serve adverts and circumvent
395 // us. As a workaround we wrap WebSocket, closing connections that would have
396 // otherwise been blocked.
397 // [1] - https://bugs.chromium.org/p/chromium/issues/detail?id=129353
398 function wrapWebSocket(script)
399 {
400 if (typeof WebSocket == "undefined")
401 return;
402
403 var eventName = "abpws-" + Math.random().toString().substr(2);
Sebastian Noack 2016/07/01 17:03:39 Instead of duplicating this logic, perhaps just as
kzar 2016/07/06 16:39:19 Done.
404
405 document.addEventListener(eventName, function(event)
406 {
407 ext.backgroundPage.sendMessage({
408 type: "websocket-request",
409 url: event.detail.url
410 }, function (block)
411 {
412 document.dispatchEvent(
413 new CustomEvent(eventName + "-" + event.detail.url, {detail: block})
414 );
415 });
416 });
417
418 function wrapper(eventName)
419 {
420 var originalWebSocket = WebSocket;
421 var readyStates = {
422 CLOSED: {value: 3, enumerable: true},
423 CLOSING: {value: 2, enumerable: true},
424 OPEN: {value: 1, enumerable: true},
425 CONNECTING: {value: 0, enumerable: true}
426 };
427
428 WebSocket = function(url, protocol)
429 {
430 var blocked = false;
431 var websocket;
Sebastian Noack 2016/07/01 17:03:39 Since you access this variable before it might get
kzar 2016/07/06 16:39:19 Done.
432 var websocketProperties = {
Sebastian Noack 2016/07/01 17:03:39 It seems you don't need to hard-code that properti
kzar 2016/07/06 16:39:19 Done.
433 attributes: {
434 url: url,
435 protocol: protocol,
436 readyState: readyStates.CONNECTING,
437 bufferedAmount: 0,
438 extensions: "",
439 binaryType: "blob",
440 onopen: null,
441 onerror: null,
442 onclose: null,
443 onmessage: null
444 },
445 methods: ["close", "send", "addEventListener", "removeEventListener"]
446 };
447 var properties = Object.create(null);
448 var queue = [];
449
450 for (var key in websocketProperties.attributes)
451 {
452 properties[key] = function(key, defaultValue)
453 {
454 return {
455 enumerable: true,
Sebastian Noack 2016/07/01 17:03:40 Instead hard-coding metadata like "enumerable" you
kzar 2016/07/06 16:39:19 Done.
456 get: function()
457 {
458 if (blocked && key == "readyState")
459 return readyStates.CLOSED;
460 return websocket ? websocket[key] : defaultValue;
461 },
462 set: function(value)
463 {
464 if (websocket)
465 return websocket[key] = value;
Sebastian Noack 2016/07/01 17:03:40 Why do you return in a setter?
kzar 2016/07/06 16:39:20 Because the return value for an assignment is usua
Sebastian Noack 2016/08/08 16:42:53 An assignment operation returns it's right-hand va
kzar 2016/08/08 18:19:05 Ah, I didn't know that!
466 else
467 {
468 queue.push(["set", key, value]);
469 return value;
470 }
471 }
472 };
473 }(key, websocketProperties.attributes[key]);
474 }
475 for (var i = 0; i < websocketProperties.methods.length; i += 1)
476 {
477 var method = websocketProperties.methods[i];
478 properties[method] = (function(method)
479 {
480 return {
481 enumerable: true,
482 value: function()
483 {
484 if (websocket)
485 websocket[method].apply(websocket, arguments);
486 else
487 queue.push(["call", method, arguments]);
488 }
489 };
490 }(method));
491 }
492 Object.defineProperties(this, properties);
Sebastian Noack 2016/07/01 17:03:40 Note that those properties (in Chrome at least) ar
kzar 2016/07/06 16:39:20 Done.
493
494 function processQueue()
495 {
496 for (var i = 0; i < queue.length; i += 1)
497 {
498 var action = queue[i][0];
499 var key = queue[i][1];
500 var value = queue[i][2];
501 switch (action)
502 {
503 case "set":
504 websocket[key] = value;
505 break;
506 case "call":
507 websocket[key].apply(websocket, value);
508 break;
509 }
510 }
511 queue = undefined;
512 }
513
514 var incomingEventName = eventName + "-" + url;
515 function listener(event)
516 {
517 blocked = event.detail;
518 if (blocked)
519 queue = undefined;
520 else
521 {
522 websocket = new originalWebSocket(url, protocol);
523 processQueue();
524 }
525
526 document.removeEventListener(incomingEventName, listener);
527 }
528 document.addEventListener(incomingEventName, listener);
529
530 document.dispatchEvent(new CustomEvent(eventName, {
531 detail: {url: url, protocol: protocol}
532 }));
533 };
534 WebSocket.prototype = Object.create(window.EventTarget.prototype, readyState s);
Sebastian Noack 2016/07/01 17:03:40 Can't you simply reuse the original parent prototy
kzar 2016/07/06 16:39:20 Done. (Good point as it is different in Safari.)
535 Object.defineProperties(WebSocket, readyStates);
536 }
537
538 script.textContent += "(" + wrapper.toString() + ")(\"" + eventName + "\");";
Sebastian Noack 2016/07/01 17:03:40 This is inconsistent with how we generate the code
kzar 2016/07/06 16:39:19 Done, however wasn't sure how to test the code abo
395 } 539 }
396 540
397 function init(document) 541 function init(document)
398 { 542 {
399 var shadow = null; 543 var shadow = null;
400 var style = null; 544 var style = null;
401 var observer = null; 545 var observer = null;
402 var tracer = null; 546 var tracer = null;
403 547
548 // Scripts to be injected into the page, executed at the end of initialization
549 var script = document.createElement("script");
550 script.async = false;
551
552 wrapWebSocket(script);
553
404 function getPropertyFilters(callback) 554 function getPropertyFilters(callback)
405 { 555 {
406 ext.backgroundPage.sendMessage({ 556 ext.backgroundPage.sendMessage({
407 type: "filters.get", 557 type: "filters.get",
408 what: "cssproperties" 558 what: "cssproperties"
409 }, callback); 559 }, callback);
410 } 560 }
411 var propertyFilters = new CSSPropertyFilters(window, getPropertyFilters, 561 var propertyFilters = new CSSPropertyFilters(window, getPropertyFilters,
412 addElemHideSelectors); 562 addElemHideSelectors);
413 563
(...skipping 27 matching lines...) Expand all
441 (shadow || document.head || document.documentElement).appendChild(style); 591 (shadow || document.head || document.documentElement).appendChild(style);
442 592
443 // It can happen that the frame already navigated to a different 593 // It can happen that the frame already navigated to a different
444 // document while we were waiting for the background page to respond. 594 // document while we were waiting for the background page to respond.
445 // In that case the sheet property will stay null, after addind the 595 // In that case the sheet property will stay null, after addind the
446 // <style> element to the shadow DOM. 596 // <style> element to the shadow DOM.
447 if (!style.sheet) 597 if (!style.sheet)
448 return; 598 return;
449 599
450 observer = reinjectStyleSheetWhenRemoved(document, style); 600 observer = reinjectStyleSheetWhenRemoved(document, style);
451 protectStyleSheet(document, style); 601 if (script)
Sebastian Noack 2016/07/01 17:03:39 Sorry, I didn't notice that this a different code
kzar 2016/07/06 16:39:19 Done.
602 protectStyleSheet(document, style, script);
452 } 603 }
453 604
454 // If using shadow DOM, we have to add the ::content pseudo-element 605 // If using shadow DOM, we have to add the ::content pseudo-element
455 // before each selector, in order to match elements within the 606 // before each selector, in order to match elements within the
456 // insertion point. 607 // insertion point.
457 if (shadow) 608 if (shadow)
458 { 609 {
459 var preparedSelectors = []; 610 var preparedSelectors = [];
460 for (var i = 0; i < selectors.length; i++) 611 for (var i = 0; i < selectors.length; i++)
461 { 612 {
(...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after
556 if (!contentWindow.collapsing) 707 if (!contentWindow.collapsing)
557 Array.prototype.forEach.call( 708 Array.prototype.forEach.call(
558 contentDocument.querySelectorAll(Object.keys(typeMap).join(",")), 709 contentDocument.querySelectorAll(Object.keys(typeMap).join(",")),
559 checkCollapse 710 checkCollapse
560 ); 711 );
561 } 712 }
562 } 713 }
563 } 714 }
564 }, true); 715 }, true);
565 716
717 document.documentElement.appendChild(script);
718 document.documentElement.removeChild(script);
719 script = undefined;
720
566 return updateStylesheet; 721 return updateStylesheet;
567 } 722 }
568 723
569 if (document instanceof HTMLDocument) 724 if (document instanceof HTMLDocument)
570 { 725 {
571 checkSitekey(); 726 checkSitekey();
572 window.updateStylesheet = init(document); 727 window.updateStylesheet = init(document);
573 } 728 }
OLDNEW
« no previous file with comments | « no previous file | lib/requestBlocker.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld