gecko-dev/browser/components/sessionstore/SessionCookies.sys.mjs
Alexandru Marc 5ea2ca1ea1 Backed out 5 changesets (bug 1923663, bug 1922193) for causing cookie parsing crashes.
Backed out changeset f70a3f184cef (bug 1923663)
Backed out changeset a7114c76e107 (bug 1923663)
Backed out changeset 01c2f8a4572f (bug 1922193)
Backed out changeset 6ca20b512745 (bug 1922193)
Backed out changeset fb747408813c (bug 1922193)
2024-11-08 11:43:06 +02:00

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),
});
},
};