tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

SessionCookies.sys.mjs (8177B)


      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/. */
      4 
      5 const lazy = {};
      6 
      7 ChromeUtils.defineESModuleGetters(lazy, {
      8  PrivacyLevel: "resource://gre/modules/sessionstore/PrivacyLevel.sys.mjs",
      9 });
     10 
     11 const MAX_EXPIRY = Number.MAX_SAFE_INTEGER;
     12 
     13 /**
     14 * The external API implemented by the SessionCookies module.
     15 */
     16 export var SessionCookies = Object.freeze({
     17  collect() {
     18    return SessionCookiesInternal.collect();
     19  },
     20 
     21  restore(cookies) {
     22    SessionCookiesInternal.restore(cookies);
     23  },
     24 });
     25 
     26 /**
     27 * The internal API.
     28 */
     29 var SessionCookiesInternal = {
     30  /**
     31   * Stores whether we're initialized, yet.
     32   */
     33  _initialized: false,
     34 
     35  /**
     36   * Retrieve an array of all stored session cookies.
     37   */
     38  collect() {
     39    this._ensureInitialized();
     40    return CookieStore.toArray();
     41  },
     42 
     43  /**
     44   * Restores a given list of session cookies.
     45   */
     46  restore(cookies) {
     47    for (let cookie of cookies) {
     48      let expiry = "expiry" in cookie ? cookie.expiry : MAX_EXPIRY;
     49      let exists = false;
     50      try {
     51        exists = Services.cookies.cookieExists(
     52          cookie.host,
     53          cookie.path || "",
     54          cookie.name || "",
     55          cookie.originAttributes || {}
     56        );
     57      } catch (ex) {
     58        console.error(
     59          `CookieService::CookieExists failed with error '${ex}' for '${JSON.stringify(
     60            cookie
     61          )}'.`
     62        );
     63      }
     64      if (!exists) {
     65        // Enforces isPartitioned if the partitionKey is set. We need to do this
     66        // because the session store didn't store the isPartitioned flag.
     67        // Otherwise, we'd end up setting partitioned cookies without
     68        // isPartitioned flag.
     69        let isPartitioned =
     70          cookie.isPartitioned ||
     71          cookie.originAttributes?.partitionKey?.length > 0;
     72 
     73        try {
     74          const cv = Services.cookies.add(
     75            cookie.host,
     76            cookie.path || "",
     77            cookie.name || "",
     78            cookie.value,
     79            !!cookie.secure,
     80            !!cookie.httponly,
     81            /* isSession = */ true,
     82            expiry,
     83            cookie.originAttributes || {},
     84            // If sameSite is undefined, we are migrating from a pre bug 1955685 session).
     85            cookie.sameSite === undefined
     86              ? Ci.nsICookie.SAMESITE_NONE
     87              : cookie.sameSite,
     88            cookie.schemeMap || Ci.nsICookie.SCHEME_HTTPS,
     89            isPartitioned
     90          );
     91          if (cv.result !== Ci.nsICookieValidation.eOK) {
     92            console.error(
     93              `CookieService::Add failed with error '${cv.result}' for cookie ${JSON.stringify(
     94                cookie
     95              )}.`
     96            );
     97          }
     98        } catch (ex) {
     99          console.error(
    100            `CookieService::Add failed with error '${ex}' for cookie ${JSON.stringify(
    101              cookie
    102            )}.`
    103          );
    104        }
    105      }
    106    }
    107  },
    108 
    109  /**
    110   * Handles observers notifications that are sent whenever cookies are added,
    111   * changed, or removed. Ensures that the storage is updated accordingly.
    112   */
    113  observe(subject) {
    114    let notification = subject.QueryInterface(Ci.nsICookieNotification);
    115 
    116    let {
    117      COOKIE_DELETED,
    118      COOKIE_ADDED,
    119      COOKIE_CHANGED,
    120      ALL_COOKIES_CLEARED,
    121      COOKIES_BATCH_DELETED,
    122    } = Ci.nsICookieNotification;
    123 
    124    switch (notification.action) {
    125      case COOKIE_ADDED:
    126        this._addCookie(notification.cookie);
    127        break;
    128      case COOKIE_CHANGED:
    129        this._updateCookie(notification.cookie);
    130        break;
    131      case COOKIE_DELETED:
    132        this._removeCookie(notification.cookie);
    133        break;
    134      case ALL_COOKIES_CLEARED:
    135        CookieStore.clear();
    136        break;
    137      case COOKIES_BATCH_DELETED:
    138        this._removeCookies(notification.batchDeletedCookies);
    139        break;
    140      default:
    141        throw new Error("Unhandled session-cookie-changed notification.");
    142    }
    143  },
    144 
    145  /**
    146   * If called for the first time in a session, iterates all cookies in the
    147   * cookies service and puts them into the store if they're session cookies.
    148   */
    149  _ensureInitialized() {
    150    if (this._initialized) {
    151      return;
    152    }
    153    this._reloadCookies();
    154    this._initialized = true;
    155    Services.obs.addObserver(this, "session-cookie-changed");
    156 
    157    // Listen for privacy level changes to reload cookies when needed.
    158    Services.prefs.addObserver("browser.sessionstore.privacy_level", () => {
    159      this._reloadCookies();
    160    });
    161  },
    162 
    163  /**
    164   * Adds a given cookie to the store.
    165   */
    166  _addCookie(cookie) {
    167    cookie.QueryInterface(Ci.nsICookie);
    168 
    169    // Store only session cookies, obey the privacy level.
    170    if (cookie.isSession && lazy.PrivacyLevel.canSave(cookie.isSecure)) {
    171      CookieStore.add(cookie);
    172    }
    173  },
    174 
    175  /**
    176   * Updates a given cookie.
    177   */
    178  _updateCookie(cookie) {
    179    cookie.QueryInterface(Ci.nsICookie);
    180 
    181    // Store only session cookies, obey the privacy level.
    182    if (cookie.isSession && lazy.PrivacyLevel.canSave(cookie.isSecure)) {
    183      CookieStore.add(cookie);
    184    } else {
    185      CookieStore.delete(cookie);
    186    }
    187  },
    188 
    189  /**
    190   * Removes a given cookie from the store.
    191   */
    192  _removeCookie(cookie) {
    193    cookie.QueryInterface(Ci.nsICookie);
    194 
    195    if (cookie.isSession) {
    196      CookieStore.delete(cookie);
    197    }
    198  },
    199 
    200  /**
    201   * Removes a given list of cookies from the store.
    202   */
    203  _removeCookies(cookies) {
    204    for (let i = 0; i < cookies.length; i++) {
    205      this._removeCookie(cookies.queryElementAt(i, Ci.nsICookie));
    206    }
    207  },
    208 
    209  /**
    210   * Iterates all cookies in the cookies service and puts them into the store
    211   * if they're session cookies. Obeys the user's chosen privacy level.
    212   */
    213  _reloadCookies() {
    214    CookieStore.clear();
    215 
    216    // Bail out if we're not supposed to store cookies at all.
    217    if (!lazy.PrivacyLevel.canSave(false)) {
    218      return;
    219    }
    220 
    221    for (let cookie of Services.cookies.sessionCookies) {
    222      this._addCookie(cookie);
    223    }
    224  },
    225 };
    226 
    227 /**
    228 * The internal storage that keeps track of session cookies.
    229 */
    230 var CookieStore = {
    231  /**
    232   * The internal map holding all known session cookies.
    233   */
    234  _entries: new Map(),
    235 
    236  /**
    237   * Stores a given cookie.
    238   *
    239   * @param cookie
    240   *        The nsICookie object to add to the storage.
    241   */
    242  add(cookie) {
    243    let jscookie = { host: cookie.host, value: cookie.value };
    244 
    245    // Only add properties with non-default values to save a few bytes.
    246    if (cookie.path) {
    247      jscookie.path = cookie.path;
    248    }
    249 
    250    if (cookie.name) {
    251      jscookie.name = cookie.name;
    252    }
    253 
    254    if (cookie.isSecure) {
    255      jscookie.secure = true;
    256    }
    257 
    258    if (cookie.isHttpOnly) {
    259      jscookie.httponly = true;
    260    }
    261 
    262    if (cookie.expiry < MAX_EXPIRY) {
    263      jscookie.expiry = cookie.expiry;
    264    }
    265 
    266    if (cookie.originAttributes) {
    267      jscookie.originAttributes = cookie.originAttributes;
    268    }
    269 
    270    jscookie.sameSite = cookie.sameSite;
    271 
    272    if (cookie.schemeMap) {
    273      jscookie.schemeMap = cookie.schemeMap;
    274    }
    275 
    276    if (cookie.isPartitioned) {
    277      jscookie.isPartitioned = true;
    278    }
    279 
    280    this._entries.set(this._getKeyForCookie(cookie), jscookie);
    281  },
    282 
    283  /**
    284   * Removes a given cookie.
    285   *
    286   * @param cookie
    287   *        The nsICookie object to be removed from storage.
    288   */
    289  delete(cookie) {
    290    this._entries.delete(this._getKeyForCookie(cookie));
    291  },
    292 
    293  /**
    294   * Removes all cookies.
    295   */
    296  clear() {
    297    this._entries.clear();
    298  },
    299 
    300  /**
    301   * Return all cookies as an array.
    302   */
    303  toArray() {
    304    return [...this._entries.values()];
    305  },
    306 
    307  /**
    308   * Returns the key needed to properly store and identify a given cookie.
    309   * A cookie is uniquely identified by the combination of its host, name,
    310   * path, and originAttributes properties.
    311   *
    312   * @param cookie
    313   *        The nsICookie object to compute a key for.
    314   * @return string
    315   */
    316  _getKeyForCookie(cookie) {
    317    return JSON.stringify({
    318      host: cookie.host,
    319      name: cookie.name,
    320      path: cookie.path,
    321      attr: ChromeUtils.originAttributesToSuffix(cookie.originAttributes),
    322    });
    323  },
    324 };