toolkit/components/telemetry/ThirdPartyCookieProbe.jsm

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

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

mercurial