toolkit/components/telemetry/ThirdPartyCookieProbe.jsm

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     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/. */
     5 "use strict";
     7 let Ci = Components.interfaces;
     8 let Cu = Components.utils;
     9 let Cr = Components.results;
    11 Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
    12 Cu.import("resource://gre/modules/Services.jsm", this);
    14 this.EXPORTED_SYMBOLS = ["ThirdPartyCookieProbe"];
    16 const MILLISECONDS_PER_DAY = 1000 * 60 * 60 * 24;
    18 /**
    19  * A probe implementing the measurements detailed at
    20  * https://wiki.mozilla.org/SecurityEngineering/ThirdPartyCookies/Telemetry
    21  *
    22  * This implementation uses only in-memory data.
    23  */
    24 this.ThirdPartyCookieProbe = function() {
    25   /**
    26    * A set of third-party sites that have caused cookies to be
    27    * rejected. These sites are trimmed down to ETLD + 1
    28    * (i.e. "x.y.com" and "z.y.com" are both trimmed down to "y.com",
    29    * "x.y.co.uk" is trimmed down to "y.co.uk").
    30    *
    31    * Used to answer the following question: "For each third-party
    32    * site, how many other first parties embed them and result in
    33    * cookie traffic?" (see
    34    * https://wiki.mozilla.org/SecurityEngineering/ThirdPartyCookies/Telemetry#Breadth
    35    * )
    36    *
    37    * @type Map<string, RejectStats> A mapping from third-party site
    38    * to rejection statistics.
    39    */
    40   this._thirdPartyCookies = new Map();
    41   /**
    42    * Timestamp of the latest call to flush() in milliseconds since the Epoch.
    43    */
    44   this._latestFlush = Date.now();
    45 };
    47 this.ThirdPartyCookieProbe.prototype = {
    48   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
    49   init: function() {
    50     Services.obs.addObserver(this, "profile-before-change", false);
    51     Services.obs.addObserver(this, "third-party-cookie-accepted", false);
    52     Services.obs.addObserver(this, "third-party-cookie-rejected", false);
    53   },
    54   dispose: function() {
    55     Services.obs.removeObserver(this, "profile-before-change");
    56     Services.obs.removeObserver(this, "third-party-cookie-accepted");
    57     Services.obs.removeObserver(this, "third-party-cookie-rejected");
    58   },
    59   /**
    60    * Observe either
    61    * - "profile-before-change" (no meaningful subject or data) - time to flush statistics and unregister; or
    62    * - "third-party-cookie-accepted"/"third-party-cookie-rejected" with
    63    *    subject: the nsIURI of the third-party that attempted to set the cookie;
    64    *    data: a string holding the uri of the page seen by the user.
    65    */
    66   observe: function(docURI, topic, referrer) {
    67     try {
    68       if (topic == "profile-before-change") {
    69         // A final flush, then unregister
    70         this.flush();
    71         this.dispose();
    72       }
    73       if (topic != "third-party-cookie-accepted"
    74           && topic != "third-party-cookie-rejected") {
    75         // Not a third-party cookie
    76         return;
    77       }
    78       // Add host to this._thirdPartyCookies
    79       // Note: nsCookieService passes "?" if the issuer is unknown.  Avoid
    80       //       normalizing in this case since its not a valid URI.
    81       let firstParty = (referrer === "?") ? referrer : normalizeHost(referrer);
    82       let thirdParty = normalizeHost(docURI.QueryInterface(Ci.nsIURI).host);
    83       let data = this._thirdPartyCookies.get(thirdParty);
    84       if (!data) {
    85         data = new RejectStats();
    86         this._thirdPartyCookies.set(thirdParty, data);
    87       }
    88       if (topic == "third-party-cookie-accepted") {
    89         data.addAccepted(firstParty);
    90       } else {
    91         data.addRejected(firstParty);
    92       }
    93     } catch (ex) {
    94       if (ex instanceof Ci.nsIXPCException) {
    95         if (ex.result == Cr.NS_ERROR_HOST_IS_IP_ADDRESS ||
    96             ex.result == Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) {
    97           return;
    98         }
    99       }
   100       // Other errors should not remain silent.
   101       Services.console.logStringMessage("ThirdPartyCookieProbe: Uncaught error " + ex + "\n" + ex.stack);
   102     }
   103   },
   105   /**
   106    * Clear internal data, fill up corresponding histograms.
   107    *
   108    * @param {number} aNow (optional, used for testing purposes only)
   109    * The current instant. Used to make tests time-independent.
   110    */
   111   flush: function(aNow = Date.now()) {
   112     let updays = (aNow - this._latestFlush) / MILLISECONDS_PER_DAY;
   113     if (updays <= 0) {
   114       // Unlikely, but regardless, don't risk division by zero
   115       // or weird stuff.
   116       return;
   117     }
   118     this._latestFlush = aNow;
   119     let acceptedSites = Services.telemetry.getHistogramById("COOKIES_3RDPARTY_NUM_SITES_ACCEPTED");
   120     let rejectedSites = Services.telemetry.getHistogramById("COOKIES_3RDPARTY_NUM_SITES_BLOCKED");
   121     let acceptedRequests = Services.telemetry.getHistogramById("COOKIES_3RDPARTY_NUM_ATTEMPTS_ACCEPTED");
   122     let rejectedRequests = Services.telemetry.getHistogramById("COOKIES_3RDPARTY_NUM_ATTEMPTS_BLOCKED");
   123     for (let [k, data] of this._thirdPartyCookies) {
   124       acceptedSites.add(data.countAcceptedSites / updays);
   125       rejectedSites.add(data.countRejectedSites / updays);
   126       acceptedRequests.add(data.countAcceptedRequests / updays);
   127       rejectedRequests.add(data.countRejectedRequests / updays);
   128     }
   129     this._thirdPartyCookies.clear();
   130   }
   131 };
   133 /**
   134  * Data gathered on cookies that a third party site has attempted to set.
   135  *
   136  * Privacy note: the only data actually sent to the server is the size of
   137  * the sets.
   138  *
   139  * @constructor
   140  */
   141 let RejectStats = function() {
   142   /**
   143    * The set of all sites for which we have accepted third-party cookies.
   144    */
   145   this._acceptedSites = new Set();
   146   /**
   147    * The set of all sites for which we have rejected third-party cookies.
   148    */
   149   this._rejectedSites = new Set();
   150   /**
   151    * Total number of attempts to set a third-party cookie that have
   152    * been accepted. Two accepted attempts on the same site will both
   153    * augment this count.
   154    */
   155   this._acceptedRequests = 0;
   156   /**
   157    * Total number of attempts to set a third-party cookie that have
   158    * been rejected. Two rejected attempts on the same site will both
   159    * augment this count.
   160    */
   161   this._rejectedRequests = 0;
   162 };
   163 RejectStats.prototype = {
   164   addAccepted: function(firstParty) {
   165     this._acceptedSites.add(firstParty);
   166     this._acceptedRequests++;
   167   },
   168   addRejected: function(firstParty) {
   169     this._rejectedSites.add(firstParty);
   170     this._rejectedRequests++;
   171   },
   172   get countAcceptedSites() {
   173     return this._acceptedSites.size;
   174   },
   175   get countRejectedSites() {
   176     return this._rejectedSites.size;
   177   },
   178   get countAcceptedRequests() {
   179     return this._acceptedRequests;
   180   },
   181   get countRejectedRequests() {
   182     return this._rejectedRequests;
   183   }
   184 };
   186 /**
   187  * Normalize a host to its eTLD + 1.
   188  */
   189 function normalizeHost(host) {
   190   return Services.eTLD.getBaseDomainFromHost(host);
   191 };

mercurial