forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			303 lines
		
	
	
	
		
			7.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			303 lines
		
	
	
	
		
			7.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 | |
|  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| const lazy = {};
 | |
| 
 | |
| ChromeUtils.defineESModuleGetters(lazy, {
 | |
|   PrivacyLevel: "resource://gre/modules/sessionstore/PrivacyLevel.sys.mjs",
 | |
| });
 | |
| 
 | |
| const MAX_EXPIRY = Number.MAX_SAFE_INTEGER;
 | |
| 
 | |
| /**
 | |
|  * The external API implemented by the SessionCookies module.
 | |
|  */
 | |
| export var SessionCookies = Object.freeze({
 | |
|   collect() {
 | |
|     return SessionCookiesInternal.collect();
 | |
|   },
 | |
| 
 | |
|   restore(cookies) {
 | |
|     SessionCookiesInternal.restore(cookies);
 | |
|   },
 | |
| });
 | |
| 
 | |
| /**
 | |
|  * The internal API.
 | |
|  */
 | |
| var SessionCookiesInternal = {
 | |
|   /**
 | |
|    * Stores whether we're initialized, yet.
 | |
|    */
 | |
|   _initialized: false,
 | |
| 
 | |
|   /**
 | |
|    * Retrieve an array of all stored session cookies.
 | |
|    */
 | |
|   collect() {
 | |
|     this._ensureInitialized();
 | |
|     return CookieStore.toArray();
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Restores a given list of session cookies.
 | |
|    */
 | |
|   restore(cookies) {
 | |
|     for (let cookie of cookies) {
 | |
|       let expiry = "expiry" in cookie ? cookie.expiry : MAX_EXPIRY;
 | |
|       let exists = false;
 | |
|       try {
 | |
|         exists = Services.cookies.cookieExists(
 | |
|           cookie.host,
 | |
|           cookie.path || "",
 | |
|           cookie.name || "",
 | |
|           cookie.originAttributes || {}
 | |
|         );
 | |
|       } catch (ex) {
 | |
|         console.error(
 | |
|           `CookieService::CookieExists failed with error '${ex}' for '${JSON.stringify(
 | |
|             cookie
 | |
|           )}'.`
 | |
|         );
 | |
|       }
 | |
|       if (!exists) {
 | |
|         try {
 | |
|           Services.cookies.add(
 | |
|             cookie.host,
 | |
|             cookie.path || "",
 | |
|             cookie.name || "",
 | |
|             cookie.value,
 | |
|             !!cookie.secure,
 | |
|             !!cookie.httponly,
 | |
|             /* isSession = */ true,
 | |
|             expiry,
 | |
|             cookie.originAttributes || {},
 | |
|             cookie.sameSite || Ci.nsICookie.SAMESITE_NONE,
 | |
|             cookie.schemeMap || Ci.nsICookie.SCHEME_HTTPS
 | |
|           );
 | |
|         } catch (ex) {
 | |
|           console.error(
 | |
|             `CookieService::Add failed with error '${ex}' for cookie ${JSON.stringify(
 | |
|               cookie
 | |
|             )}.`
 | |
|           );
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Handles observers notifications that are sent whenever cookies are added,
 | |
|    * changed, or removed. Ensures that the storage is updated accordingly.
 | |
|    */
 | |
|   observe(subject) {
 | |
|     let notification = subject.QueryInterface(Ci.nsICookieNotification);
 | |
| 
 | |
|     let {
 | |
|       COOKIE_DELETED,
 | |
|       COOKIE_ADDED,
 | |
|       COOKIE_CHANGED,
 | |
|       ALL_COOKIES_CLEARED,
 | |
|       COOKIES_BATCH_DELETED,
 | |
|     } = Ci.nsICookieNotification;
 | |
| 
 | |
|     switch (notification.action) {
 | |
|       case COOKIE_ADDED:
 | |
|         this._addCookie(notification.cookie);
 | |
|         break;
 | |
|       case COOKIE_CHANGED:
 | |
|         this._updateCookie(notification.cookie);
 | |
|         break;
 | |
|       case COOKIE_DELETED:
 | |
|         this._removeCookie(notification.cookie);
 | |
|         break;
 | |
|       case ALL_COOKIES_CLEARED:
 | |
|         CookieStore.clear();
 | |
|         break;
 | |
|       case COOKIES_BATCH_DELETED:
 | |
|         this._removeCookies(notification.batchDeletedCookies);
 | |
|         break;
 | |
|       default:
 | |
|         throw new Error("Unhandled session-cookie-changed notification.");
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * If called for the first time in a session, iterates all cookies in the
 | |
|    * cookies service and puts them into the store if they're session cookies.
 | |
|    */
 | |
|   _ensureInitialized() {
 | |
|     if (this._initialized) {
 | |
|       return;
 | |
|     }
 | |
|     this._reloadCookies();
 | |
|     this._initialized = true;
 | |
|     Services.obs.addObserver(this, "session-cookie-changed");
 | |
| 
 | |
|     // Listen for privacy level changes to reload cookies when needed.
 | |
|     Services.prefs.addObserver("browser.sessionstore.privacy_level", () => {
 | |
|       this._reloadCookies();
 | |
|     });
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Adds a given cookie to the store.
 | |
|    */
 | |
|   _addCookie(cookie) {
 | |
|     cookie.QueryInterface(Ci.nsICookie);
 | |
| 
 | |
|     // Store only session cookies, obey the privacy level.
 | |
|     if (cookie.isSession && lazy.PrivacyLevel.canSave(cookie.isSecure)) {
 | |
|       CookieStore.add(cookie);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Updates a given cookie.
 | |
|    */
 | |
|   _updateCookie(cookie) {
 | |
|     cookie.QueryInterface(Ci.nsICookie);
 | |
| 
 | |
|     // Store only session cookies, obey the privacy level.
 | |
|     if (cookie.isSession && lazy.PrivacyLevel.canSave(cookie.isSecure)) {
 | |
|       CookieStore.add(cookie);
 | |
|     } else {
 | |
|       CookieStore.delete(cookie);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Removes a given cookie from the store.
 | |
|    */
 | |
|   _removeCookie(cookie) {
 | |
|     cookie.QueryInterface(Ci.nsICookie);
 | |
| 
 | |
|     if (cookie.isSession) {
 | |
|       CookieStore.delete(cookie);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Removes a given list of cookies from the store.
 | |
|    */
 | |
|   _removeCookies(cookies) {
 | |
|     for (let i = 0; i < cookies.length; i++) {
 | |
|       this._removeCookie(cookies.queryElementAt(i, Ci.nsICookie));
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Iterates all cookies in the cookies service and puts them into the store
 | |
|    * if they're session cookies. Obeys the user's chosen privacy level.
 | |
|    */
 | |
|   _reloadCookies() {
 | |
|     CookieStore.clear();
 | |
| 
 | |
|     // Bail out if we're not supposed to store cookies at all.
 | |
|     if (!lazy.PrivacyLevel.canSave(false)) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     for (let cookie of Services.cookies.sessionCookies) {
 | |
|       this._addCookie(cookie);
 | |
|     }
 | |
|   },
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * The internal storage that keeps track of session cookies.
 | |
|  */
 | |
| var CookieStore = {
 | |
|   /**
 | |
|    * The internal map holding all known session cookies.
 | |
|    */
 | |
|   _entries: new Map(),
 | |
| 
 | |
|   /**
 | |
|    * Stores a given cookie.
 | |
|    *
 | |
|    * @param cookie
 | |
|    *        The nsICookie object to add to the storage.
 | |
|    */
 | |
|   add(cookie) {
 | |
|     let jscookie = { host: cookie.host, value: cookie.value };
 | |
| 
 | |
|     // Only add properties with non-default values to save a few bytes.
 | |
|     if (cookie.path) {
 | |
|       jscookie.path = cookie.path;
 | |
|     }
 | |
| 
 | |
|     if (cookie.name) {
 | |
|       jscookie.name = cookie.name;
 | |
|     }
 | |
| 
 | |
|     if (cookie.isSecure) {
 | |
|       jscookie.secure = true;
 | |
|     }
 | |
| 
 | |
|     if (cookie.isHttpOnly) {
 | |
|       jscookie.httponly = true;
 | |
|     }
 | |
| 
 | |
|     if (cookie.expiry < MAX_EXPIRY) {
 | |
|       jscookie.expiry = cookie.expiry;
 | |
|     }
 | |
| 
 | |
|     if (cookie.originAttributes) {
 | |
|       jscookie.originAttributes = cookie.originAttributes;
 | |
|     }
 | |
| 
 | |
|     if (cookie.sameSite) {
 | |
|       jscookie.sameSite = cookie.sameSite;
 | |
|     }
 | |
| 
 | |
|     if (cookie.schemeMap) {
 | |
|       jscookie.schemeMap = cookie.schemeMap;
 | |
|     }
 | |
| 
 | |
|     this._entries.set(this._getKeyForCookie(cookie), jscookie);
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Removes a given cookie.
 | |
|    *
 | |
|    * @param cookie
 | |
|    *        The nsICookie object to be removed from storage.
 | |
|    */
 | |
|   delete(cookie) {
 | |
|     this._entries.delete(this._getKeyForCookie(cookie));
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Removes all cookies.
 | |
|    */
 | |
|   clear() {
 | |
|     this._entries.clear();
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Return all cookies as an array.
 | |
|    */
 | |
|   toArray() {
 | |
|     return [...this._entries.values()];
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Returns the key needed to properly store and identify a given cookie.
 | |
|    * A cookie is uniquely identified by the combination of its host, name,
 | |
|    * path, and originAttributes properties.
 | |
|    *
 | |
|    * @param cookie
 | |
|    *        The nsICookie object to compute a key for.
 | |
|    * @return string
 | |
|    */
 | |
|   _getKeyForCookie(cookie) {
 | |
|     return JSON.stringify({
 | |
|       host: cookie.host,
 | |
|       name: cookie.name,
 | |
|       path: cookie.path,
 | |
|       attr: ChromeUtils.originAttributesToSuffix(cookie.originAttributes),
 | |
|     });
 | |
|   },
 | |
| };
 | 
