Bug 1854550 - pt 12. Allow inlining between mozjemalloc and PHC r=glandium
[gecko.git] / remote / marionette / cookie.sys.mjs
blob117ccc33edefc20c29545c8b897aa6df3d965dfa
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 const lazy = {};
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",
11 });
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],
19 ]);
21 /** @namespace */
22 export const cookie = {
23   manager: Services.cookies,
26 /**
27  * @name Cookie
28  *
29  * @returns {Object<string, (number|boolean|string)>}
30  */
32 /**
33  * Unmarshal a JSON Object to a cookie representation.
34  *
35  * Effectively this will run validation checks on ``json``, which
36  * will produce the errors expected by WebDriver if the input is
37  * not valid.
38  *
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
45  *     unsigned integer.
46  *
47  * @returns {Cookie}
48  *     Valid cookie object.
49  *
50  * @throws {InvalidArgumentError}
51  *     If any of the properties are invalid.
52  */
53 cookie.fromJSON = function (json) {
54   let newCookie = {};
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(
60     json.value,
61     "Cookie value must be string"
62   );
64   if (typeof json.path != "undefined") {
65     newCookie.path = lazy.assert.string(
66       json.path,
67       "Cookie path must be string"
68     );
69   }
70   if (typeof json.domain != "undefined") {
71     newCookie.domain = lazy.assert.string(
72       json.domain,
73       "Cookie domain must be string"
74     );
75   }
76   if (typeof json.secure != "undefined") {
77     newCookie.secure = lazy.assert.boolean(
78       json.secure,
79       "Cookie secure flag must be boolean"
80     );
81   }
82   if (typeof json.httpOnly != "undefined") {
83     newCookie.httpOnly = lazy.assert.boolean(
84       json.httpOnly,
85       "Cookie httpOnly flag must be boolean"
86     );
87   }
88   if (typeof json.expiry != "undefined") {
89     newCookie.expiry = lazy.assert.positiveInteger(
90       json.expiry,
91       "Cookie expiry must be a positive integer"
92     );
93   }
94   if (typeof json.sameSite != "undefined") {
95     newCookie.sameSite = lazy.assert.in(
96       json.sameSite,
97       Array.from(SAMESITE_MAP.keys()),
98       "Cookie SameSite flag must be one of None, Lax, or Strict"
99     );
100   }
102   return newCookie;
106  * Insert cookie to the cookie store.
108  * @param {Cookie} newCookie
109  *     Cookie to add.
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
121  *     not match.
122  * @throws {UnableToSetCookieError}
123  *     If an error occurred while trying to save the cookie.
124  */
125 cookie.add = function (
126   newCookie,
127   { restrictToHost = null, protocol = null } = {}
128 ) {
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 = "/";
134   }
136   let hostOnly = false;
137   if (typeof newCookie.domain == "undefined") {
138     hostOnly = true;
139     newCookie.domain = restrictToHost;
140   }
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);
144   }
146   if (typeof newCookie.secure == "undefined") {
147     newCookie.secure = false;
148   }
149   if (typeof newCookie.httpOnly == "undefined") {
150     newCookie.httpOnly = false;
151   }
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;
156   } else {
157     newCookie.session = false;
158   }
159   newCookie.sameSite = SAMESITE_MAP.get(newCookie.sameSite || "None");
161   let isIpAddress = false;
162   try {
163     Services.eTLD.getPublicSuffixFromHost(newCookie.domain);
164   } catch (e) {
165     switch (e.result) {
166       case Cr.NS_ERROR_HOST_IS_IP_ADDRESS:
167         isIpAddress = true;
168         break;
169       default:
170         throw new lazy.error.InvalidCookieDomainError(newCookie.domain);
171     }
172   }
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;
178   }
180   if (restrictToHost) {
181     if (
182       !restrictToHost.endsWith(newCookie.domain) &&
183       "." + restrictToHost !== newCookie.domain &&
184       restrictToHost !== newCookie.domain
185     ) {
186       throw new lazy.error.InvalidCookieDomainError(
187         `Cookies may only be set ` +
188           `for the current domain (${restrictToHost})`
189       );
190     }
191   }
193   let schemeType = Ci.nsICookie.SCHEME_UNSET;
194   switch (protocol) {
195     case "http:":
196       schemeType = Ci.nsICookie.SCHEME_HTTP;
197       break;
198     case "https:":
199       schemeType = Ci.nsICookie.SCHEME_HTTPS;
200       break;
201     default:
202       // Any other protocol that is supported by the cookie service.
203       break;
204   }
206   // remove port from domain, if present.
207   // unfortunately this catches IPv6 addresses by mistake
208   // TODO: Bug 814416
209   newCookie.domain = newCookie.domain.replace(IPV4_PORT_EXPR, "");
211   try {
212     cookie.manager.add(
213       newCookie.domain,
214       newCookie.path,
215       newCookie.name,
216       newCookie.value,
217       newCookie.secure,
218       newCookie.httpOnly,
219       newCookie.session,
220       newCookie.expiry,
221       {} /* origin attributes */,
222       newCookie.sameSite,
223       schemeType
224     );
225   } catch (e) {
226     throw new lazy.error.UnableToSetCookieError(e);
227   }
231  * Remove cookie from the cookie store.
233  * @param {Cookie} toDelete
234  *     Cookie to remove.
235  */
236 cookie.remove = function (toDelete) {
237   cookie.manager.remove(
238     toDelete.domain,
239     toDelete.name,
240     toDelete.path,
241     {} /* originAttributes */
242   );
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>}
257  *     Iterator.
258  */
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
268     let hostname = host;
269     do {
270       if (
271         (cookie.host == "." + hostname || cookie.host == hostname) &&
272         isForCurrentPath(cookie.path)
273       ) {
274         let data = {
275           name: cookie.name,
276           value: cookie.value,
277           path: cookie.path,
278           domain: cookie.host,
279           secure: cookie.isSecure,
280           httpOnly: cookie.isHttpOnly,
281         };
283         if (!cookie.isSession) {
284           data.expiry = cookie.expiry;
285         }
287         data.sameSite = [...SAMESITE_MAP].find(
288           ([, value]) => cookie.sameSite === value
289         )[0];
291         yield data;
292       }
293       hostname = hostname.replace(/^.*?\./, "");
294     } while (hostname.includes("."));
295   }