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/. */
7 ChromeUtils.defineESModuleGetters(lazy, {
8 assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
9 error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
10 pprint: "chrome://remote/content/shared/Format.sys.mjs",
13 const IPV4_PORT_EXPR = /:\d+$/;
15 const SAMESITE_MAP = new Map([
16 ["None", Ci.nsICookie.SAMESITE_NONE],
17 ["Lax", Ci.nsICookie.SAMESITE_LAX],
18 ["Strict", Ci.nsICookie.SAMESITE_STRICT],
22 export const cookie = {
23 manager: Services.cookies,
29 * @returns {Object<string, (number|boolean|string)>}
33 * Unmarshal a JSON Object to a cookie representation.
35 * Effectively this will run validation checks on ``json``, which
36 * will produce the errors expected by WebDriver if the input is
39 * @param {Object<string, (number | boolean | string)>} json
40 * Cookie to be deserialised. ``name`` and ``value`` are required
41 * fields which must be strings. The ``path`` and ``domain`` fields
42 * are optional, but must be a string if provided. The ``secure``,
43 * and ``httpOnly`` are similarly optional, but must be booleans.
44 * Likewise, the ``expiry`` field is optional but must be
48 * Valid cookie object.
50 * @throws {InvalidArgumentError}
51 * If any of the properties are invalid.
53 cookie.fromJSON = function (json) {
56 lazy.assert.object(json, lazy.pprint`Expected cookie object, got ${json}`);
58 newCookie.name = lazy.assert.string(json.name, "Cookie name must be string");
59 newCookie.value = lazy.assert.string(
61 "Cookie value must be string"
64 if (typeof json.path != "undefined") {
65 newCookie.path = lazy.assert.string(
67 "Cookie path must be string"
70 if (typeof json.domain != "undefined") {
71 newCookie.domain = lazy.assert.string(
73 "Cookie domain must be string"
76 if (typeof json.secure != "undefined") {
77 newCookie.secure = lazy.assert.boolean(
79 "Cookie secure flag must be boolean"
82 if (typeof json.httpOnly != "undefined") {
83 newCookie.httpOnly = lazy.assert.boolean(
85 "Cookie httpOnly flag must be boolean"
88 if (typeof json.expiry != "undefined") {
89 newCookie.expiry = lazy.assert.positiveInteger(
91 "Cookie expiry must be a positive integer"
94 if (typeof json.sameSite != "undefined") {
95 newCookie.sameSite = lazy.assert.in(
97 Array.from(SAMESITE_MAP.keys()),
98 "Cookie SameSite flag must be one of None, Lax, or Strict"
106 * Insert cookie to the cookie store.
108 * @param {Cookie} newCookie
110 * @param {object} options
111 * @param {string=} options.restrictToHost
112 * Perform test that ``newCookie``'s domain matches this.
113 * @param {string=} options.protocol
114 * The protocol of the caller. It can be `http:` or `https:`.
116 * @throws {TypeError}
117 * If ``name``, ``value``, or ``domain`` are not present and
118 * of the correct type.
119 * @throws {InvalidCookieDomainError}
120 * If ``restrictToHost`` is set and ``newCookie``'s domain does
122 * @throws {UnableToSetCookieError}
123 * If an error occurred while trying to save the cookie.
125 cookie.add = function (
127 { restrictToHost = null, protocol = null } = {}
129 lazy.assert.string(newCookie.name, "Cookie name must be string");
130 lazy.assert.string(newCookie.value, "Cookie value must be string");
132 if (typeof newCookie.path == "undefined") {
133 newCookie.path = "/";
136 let hostOnly = false;
137 if (typeof newCookie.domain == "undefined") {
139 newCookie.domain = restrictToHost;
141 lazy.assert.string(newCookie.domain, "Cookie domain must be string");
142 if (newCookie.domain.substring(0, 1) === ".") {
143 newCookie.domain = newCookie.domain.substring(1);
146 if (typeof newCookie.secure == "undefined") {
147 newCookie.secure = false;
149 if (typeof newCookie.httpOnly == "undefined") {
150 newCookie.httpOnly = false;
152 if (typeof newCookie.expiry == "undefined") {
153 // The XPCOM interface requires the expiry field even for session cookies.
154 newCookie.expiry = Number.MAX_SAFE_INTEGER;
155 newCookie.session = true;
157 newCookie.session = false;
159 newCookie.sameSite = SAMESITE_MAP.get(newCookie.sameSite || "None");
161 let isIpAddress = false;
163 Services.eTLD.getPublicSuffixFromHost(newCookie.domain);
166 case Cr.NS_ERROR_HOST_IS_IP_ADDRESS:
170 throw new lazy.error.InvalidCookieDomainError(newCookie.domain);
174 if (!hostOnly && !isIpAddress) {
175 // only store this as a domain cookie if the domain was specified in the
176 // request and it wasn't an IP address.
177 newCookie.domain = "." + newCookie.domain;
180 if (restrictToHost) {
182 !restrictToHost.endsWith(newCookie.domain) &&
183 "." + restrictToHost !== newCookie.domain &&
184 restrictToHost !== newCookie.domain
186 throw new lazy.error.InvalidCookieDomainError(
187 `Cookies may only be set ` +
188 `for the current domain (${restrictToHost})`
193 let schemeType = Ci.nsICookie.SCHEME_UNSET;
196 schemeType = Ci.nsICookie.SCHEME_HTTP;
199 schemeType = Ci.nsICookie.SCHEME_HTTPS;
202 // Any other protocol that is supported by the cookie service.
206 // remove port from domain, if present.
207 // unfortunately this catches IPv6 addresses by mistake
209 newCookie.domain = newCookie.domain.replace(IPV4_PORT_EXPR, "");
221 {} /* origin attributes */,
226 throw new lazy.error.UnableToSetCookieError(e);
231 * Remove cookie from the cookie store.
233 * @param {Cookie} toDelete
236 cookie.remove = function (toDelete) {
237 cookie.manager.remove(
241 {} /* originAttributes */
246 * Iterates over the cookies for the current ``host``. You may
247 * optionally filter for specific paths on that ``host`` by specifying
248 * a path in ``currentPath``.
250 * @param {string} host
251 * Hostname to retrieve cookies for.
252 * @param {string=} [currentPath="/"] currentPath
253 * Optionally filter the cookies for ``host`` for the specific path.
254 * Defaults to ``/``, meaning all cookies for ``host`` are included.
256 * @returns {Iterable.<Cookie>}
259 cookie.iter = function* (host, currentPath = "/") {
260 lazy.assert.string(host, "host must be string");
261 lazy.assert.string(currentPath, "currentPath must be string");
263 const isForCurrentPath = path => currentPath.includes(path);
265 let cookies = cookie.manager.getCookiesFromHost(host, {});
266 for (let cookie of cookies) {
267 // take the hostname and progressively shorten
271 (cookie.host == "." + hostname || cookie.host == hostname) &&
272 isForCurrentPath(cookie.path)
279 secure: cookie.isSecure,
280 httpOnly: cookie.isHttpOnly,
283 if (!cookie.isSession) {
284 data.expiry = cookie.expiry;
287 data.sameSite = [...SAMESITE_MAP].find(
288 ([, value]) => cookie.sameSite === value
293 hostname = hostname.replace(/^.*?\./, "");
294 } while (hostname.includes("."));