browser/components/sessionstore/src/SessionCookies.jsm

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     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 file,
     3  * You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 "use strict";
     7 this.EXPORTED_SYMBOLS = ["SessionCookies"];
     9 const Cu = Components.utils;
    10 const Ci = Components.interfaces;
    12 Cu.import("resource://gre/modules/Services.jsm", this);
    13 Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
    15 XPCOMUtils.defineLazyModuleGetter(this, "Utils",
    16   "resource:///modules/sessionstore/Utils.jsm");
    17 XPCOMUtils.defineLazyModuleGetter(this, "PrivacyLevel",
    18   "resource:///modules/sessionstore/PrivacyLevel.jsm");
    20 // MAX_EXPIRY should be 2^63-1, but JavaScript can't handle that precision.
    21 const MAX_EXPIRY = Math.pow(2, 62);
    23 /**
    24  * The external API implemented by the SessionCookies module.
    25  */
    26 this.SessionCookies = Object.freeze({
    27   update: function (windows) {
    28     SessionCookiesInternal.update(windows);
    29   },
    31   getHostsForWindow: function (window, checkPrivacy = false) {
    32     return SessionCookiesInternal.getHostsForWindow(window, checkPrivacy);
    33   }
    34 });
    36 /**
    37  * The internal API.
    38  */
    39 let SessionCookiesInternal = {
    40   /**
    41    * Stores whether we're initialized, yet.
    42    */
    43   _initialized: false,
    45   /**
    46    * Retrieve the list of all hosts contained in the given windows' session
    47    * history entries (per window) and collect the associated cookies for those
    48    * hosts, if any. The given state object is being modified.
    49    *
    50    * @param windows
    51    *        Array of window state objects.
    52    *        [{ tabs: [...], cookies: [...] }, ...]
    53    */
    54   update: function (windows) {
    55     this._ensureInitialized();
    57     for (let window of windows) {
    58       let cookies = [];
    60       // Collect all hosts for the current window.
    61       let hosts = this.getHostsForWindow(window, true);
    63       for (let host of Object.keys(hosts)) {
    64         let isPinned = hosts[host];
    66         for (let cookie of CookieStore.getCookiesForHost(host)) {
    67           // _getCookiesForHost() will only return hosts with the right privacy
    68           // rules, so there is no need to do anything special with this call
    69           // to PrivacyLevel.canSave().
    70           if (PrivacyLevel.canSave({isHttps: cookie.secure, isPinned: isPinned})) {
    71             cookies.push(cookie);
    72           }
    73         }
    74       }
    76       // Don't include/keep empty cookie sections.
    77       if (cookies.length) {
    78         window.cookies = cookies;
    79       } else if ("cookies" in window) {
    80         delete window.cookies;
    81       }
    82     }
    83   },
    85   /**
    86    * Returns a map of all hosts for a given window that we might want to
    87    * collect cookies for.
    88    *
    89    * @param window
    90    *        A window state object containing tabs with history entries.
    91    * @param checkPrivacy (bool)
    92    *        Whether to check the privacy level for each host.
    93    * @return {object} A map of hosts for a given window state object. The keys
    94    *                  will be hosts, the values are boolean and determine
    95    *                  whether we will use the deferred privacy level when
    96    *                  checking how much data to save on quitting.
    97    */
    98   getHostsForWindow: function (window, checkPrivacy = false) {
    99     let hosts = {};
   101     for (let tab of window.tabs) {
   102       for (let entry of tab.entries) {
   103         this._extractHostsFromEntry(entry, hosts, checkPrivacy, tab.pinned);
   104       }
   105     }
   107     return hosts;
   108   },
   110   /**
   111    * Handles observers notifications that are sent whenever cookies are added,
   112    * changed, or removed. Ensures that the storage is updated accordingly.
   113    */
   114   observe: function (subject, topic, data) {
   115     switch (data) {
   116       case "added":
   117       case "changed":
   118         this._updateCookie(subject);
   119         break;
   120       case "deleted":
   121         this._removeCookie(subject);
   122         break;
   123       case "cleared":
   124         CookieStore.clear();
   125         break;
   126       case "batch-deleted":
   127         this._removeCookies(subject);
   128         break;
   129       case "reload":
   130         CookieStore.clear();
   131         this._reloadCookies();
   132         break;
   133       default:
   134         throw new Error("Unhandled cookie-changed notification.");
   135     }
   136   },
   138   /**
   139    * If called for the first time in a session, iterates all cookies in the
   140    * cookies service and puts them into the store if they're session cookies.
   141    */
   142   _ensureInitialized: function () {
   143     if (!this._initialized) {
   144       this._reloadCookies();
   145       this._initialized = true;
   146       Services.obs.addObserver(this, "cookie-changed", false);
   147     }
   148   },
   150   /**
   151    * Fill a given map with hosts found in the given entry's session history and
   152    * any child entries.
   153    *
   154    * @param entry
   155    *        the history entry, serialized
   156    * @param hosts
   157    *        the hash that will be used to store hosts eg, { hostname: true }
   158    * @param checkPrivacy
   159    *        should we check the privacy level for https
   160    * @param isPinned
   161    *        is the entry we're evaluating for a pinned tab; used only if
   162    *        checkPrivacy
   163    */
   164   _extractHostsFromEntry: function (entry, hosts, checkPrivacy, isPinned) {
   165     let host = entry._host;
   166     let scheme = entry._scheme;
   168     // If host & scheme aren't defined, then we are likely here in the startup
   169     // process via _splitCookiesFromWindow. In that case, we'll turn entry.url
   170     // into an nsIURI and get host/scheme from that. This will throw for about:
   171     // urls in which case we don't need to do anything.
   172     if (!host && !scheme) {
   173       try {
   174         let uri = Utils.makeURI(entry.url);
   175         host = uri.host;
   176         scheme = uri.scheme;
   177         this._extractHostsFromHostScheme(host, scheme, hosts, checkPrivacy, isPinned);
   178       }
   179       catch (ex) { }
   180     }
   182     if (entry.children) {
   183       for (let child of entry.children) {
   184         this._extractHostsFromEntry(child, hosts, checkPrivacy, isPinned);
   185       }
   186     }
   187   },
   189   /**
   190    * Add a given host to a given map of hosts if the privacy level allows
   191    * saving cookie data for it.
   192    *
   193    * @param host
   194    *        the host of a uri (usually via nsIURI.host)
   195    * @param scheme
   196    *        the scheme of a uri (usually via nsIURI.scheme)
   197    * @param hosts
   198    *        the hash that will be used to store hosts eg, { hostname: true }
   199    * @param checkPrivacy
   200    *        should we check the privacy level for https
   201    * @param isPinned
   202    *        is the entry we're evaluating for a pinned tab; used only if
   203    *        checkPrivacy
   204    */
   205   _extractHostsFromHostScheme:
   206     function (host, scheme, hosts, checkPrivacy, isPinned) {
   207     // host and scheme may not be set (for about: urls for example), in which
   208     // case testing scheme will be sufficient.
   209     if (/https?/.test(scheme) && !hosts[host] &&
   210         (!checkPrivacy ||
   211          PrivacyLevel.canSave({isHttps: scheme == "https", isPinned: isPinned}))) {
   212       // By setting this to true or false, we can determine when looking at
   213       // the host in update() if we should check for privacy.
   214       hosts[host] = isPinned;
   215     } else if (scheme == "file") {
   216       hosts[host] = true;
   217     }
   218   },
   220   /**
   221    * Updates or adds a given cookie to the store.
   222    */
   223   _updateCookie: function (cookie) {
   224     cookie.QueryInterface(Ci.nsICookie2);
   226     if (cookie.isSession) {
   227       CookieStore.set(cookie);
   228     }
   229   },
   231   /**
   232    * Removes a given cookie from the store.
   233    */
   234   _removeCookie: function (cookie) {
   235     cookie.QueryInterface(Ci.nsICookie2);
   237     if (cookie.isSession) {
   238       CookieStore.delete(cookie);
   239     }
   240   },
   242   /**
   243    * Removes a given list of cookies from the store.
   244    */
   245   _removeCookies: function (cookies) {
   246     for (let i = 0; i < cookies.length; i++) {
   247       this._removeCookie(cookies.queryElementAt(i, Ci.nsICookie2));
   248     }
   249   },
   251   /**
   252    * Iterates all cookies in the cookies service and puts them into the store
   253    * if they're session cookies.
   254    */
   255   _reloadCookies: function () {
   256     let iter = Services.cookies.enumerator;
   257     while (iter.hasMoreElements()) {
   258       this._updateCookie(iter.getNext());
   259     }
   260   }
   261 };
   263 /**
   264  * The internal cookie storage that keeps track of every active session cookie.
   265  * These are stored using maps per host, path, and cookie name.
   266  */
   267 let CookieStore = {
   268   /**
   269    * The internal structure holding all known cookies.
   270    *
   271    * Host =>
   272    *  Path =>
   273    *    Name => {path: "/", name: "sessionid", secure: true}
   274    *
   275    * Maps are used for storage but the data structure is equivalent to this:
   276    *
   277    * this._hosts = {
   278    *   "www.mozilla.org": {
   279    *     "/": {
   280    *       "username": {name: "username", value: "my_name_is", etc...},
   281    *       "sessionid": {name: "sessionid", value: "1fdb3a", etc...}
   282    *     }
   283    *   },
   284    *   "tbpl.mozilla.org": {
   285    *     "/path": {
   286    *       "cookiename": {name: "cookiename", value: "value", etc...}
   287    *     }
   288    *   }
   289    * };
   290    */
   291   _hosts: new Map(),
   293   /**
   294    * Returns the list of stored session cookies for a given host.
   295    *
   296    * @param host
   297    *        A string containing the host name we want to get cookies for.
   298    */
   299   getCookiesForHost: function (host) {
   300     if (!this._hosts.has(host)) {
   301       return [];
   302     }
   304     let cookies = [];
   306     for (let pathToNamesMap of this._hosts.get(host).values()) {
   307       cookies.push(...pathToNamesMap.values());
   308     }
   310     return cookies;
   311   },
   313   /**
   314    * Stores a given cookie.
   315    *
   316    * @param cookie
   317    *        The nsICookie2 object to add to the storage.
   318    */
   319   set: function (cookie) {
   320     let jscookie = {host: cookie.host, value: cookie.value};
   322     // Only add properties with non-default values to save a few bytes.
   323     if (cookie.path) {
   324       jscookie.path = cookie.path;
   325     }
   327     if (cookie.name) {
   328       jscookie.name = cookie.name;
   329     }
   331     if (cookie.isSecure) {
   332       jscookie.secure = true;
   333     }
   335     if (cookie.isHttpOnly) {
   336       jscookie.httponly = true;
   337     }
   339     if (cookie.expiry < MAX_EXPIRY) {
   340       jscookie.expiry = cookie.expiry;
   341     }
   343     this._ensureMap(cookie).set(cookie.name, jscookie);
   344   },
   346   /**
   347    * Removes a given cookie.
   348    *
   349    * @param cookie
   350    *        The nsICookie2 object to be removed from storage.
   351    */
   352   delete: function (cookie) {
   353     this._ensureMap(cookie).delete(cookie.name);
   354   },
   356   /**
   357    * Removes all cookies.
   358    */
   359   clear: function () {
   360     this._hosts.clear();
   361   },
   363   /**
   364    * Creates all maps necessary to store a given cookie.
   365    *
   366    * @param cookie
   367    *        The nsICookie2 object to create maps for.
   368    *
   369    * @return The newly created Map instance mapping cookie names to
   370    *         internal jscookies, in the given path of the given host.
   371    */
   372   _ensureMap: function (cookie) {
   373     if (!this._hosts.has(cookie.host)) {
   374       this._hosts.set(cookie.host, new Map());
   375     }
   377     let pathToNamesMap = this._hosts.get(cookie.host);
   379     if (!pathToNamesMap.has(cookie.path)) {
   380       pathToNamesMap.set(cookie.path, new Map());
   381     }
   383     return pathToNamesMap.get(cookie.path);
   384   }
   385 };

mercurial