source: frontend/node_modules/tough-cookie/lib/cookie.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 12 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 50.2 KB
Line 
1/*!
2 * Copyright (c) 2015-2020, Salesforce.com, Inc.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * 3. Neither the name of Salesforce.com nor the names of its contributors may
16 * be used to endorse or promote products derived from this software without
17 * specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31"use strict";
32const punycode = require("punycode/");
33const urlParse = require("url-parse");
34const pubsuffix = require("./pubsuffix-psl");
35const Store = require("./store").Store;
36const MemoryCookieStore = require("./memstore").MemoryCookieStore;
37const pathMatch = require("./pathMatch").pathMatch;
38const validators = require("./validators.js");
39const VERSION = require("./version");
40const { fromCallback } = require("universalify");
41const { getCustomInspectSymbol } = require("./utilHelper");
42
43// From RFC6265 S4.1.1
44// note that it excludes \x3B ";"
45const COOKIE_OCTETS = /^[\x21\x23-\x2B\x2D-\x3A\x3C-\x5B\x5D-\x7E]+$/;
46
47const CONTROL_CHARS = /[\x00-\x1F]/;
48
49// From Chromium // '\r', '\n' and '\0' should be treated as a terminator in
50// the "relaxed" mode, see:
51// https://github.com/ChromiumWebApps/chromium/blob/b3d3b4da8bb94c1b2e061600df106d590fda3620/net/cookies/parsed_cookie.cc#L60
52const TERMINATORS = ["\n", "\r", "\0"];
53
54// RFC6265 S4.1.1 defines path value as 'any CHAR except CTLs or ";"'
55// Note ';' is \x3B
56const PATH_VALUE = /[\x20-\x3A\x3C-\x7E]+/;
57
58// date-time parsing constants (RFC6265 S5.1.1)
59
60const DATE_DELIM = /[\x09\x20-\x2F\x3B-\x40\x5B-\x60\x7B-\x7E]/;
61
62const MONTH_TO_NUM = {
63 jan: 0,
64 feb: 1,
65 mar: 2,
66 apr: 3,
67 may: 4,
68 jun: 5,
69 jul: 6,
70 aug: 7,
71 sep: 8,
72 oct: 9,
73 nov: 10,
74 dec: 11
75};
76
77const MAX_TIME = 2147483647000; // 31-bit max
78const MIN_TIME = 0; // 31-bit min
79const SAME_SITE_CONTEXT_VAL_ERR =
80 'Invalid sameSiteContext option for getCookies(); expected one of "strict", "lax", or "none"';
81
82function checkSameSiteContext(value) {
83 validators.validate(validators.isNonEmptyString(value), value);
84 const context = String(value).toLowerCase();
85 if (context === "none" || context === "lax" || context === "strict") {
86 return context;
87 } else {
88 return null;
89 }
90}
91
92const PrefixSecurityEnum = Object.freeze({
93 SILENT: "silent",
94 STRICT: "strict",
95 DISABLED: "unsafe-disabled"
96});
97
98// Dumped from ip-regex@4.0.0, with the following changes:
99// * all capturing groups converted to non-capturing -- "(?:)"
100// * support for IPv6 Scoped Literal ("%eth1") removed
101// * lowercase hexadecimal only
102const IP_REGEX_LOWERCASE = /(?:^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}$)|(?:^(?:(?:[a-f\d]{1,4}:){7}(?:[a-f\d]{1,4}|:)|(?:[a-f\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|:[a-f\d]{1,4}|:)|(?:[a-f\d]{1,4}:){5}(?::(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,2}|:)|(?:[a-f\d]{1,4}:){4}(?:(?::[a-f\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,3}|:)|(?:[a-f\d]{1,4}:){3}(?:(?::[a-f\d]{1,4}){0,2}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,4}|:)|(?:[a-f\d]{1,4}:){2}(?:(?::[a-f\d]{1,4}){0,3}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,5}|:)|(?:[a-f\d]{1,4}:){1}(?:(?::[a-f\d]{1,4}){0,4}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,6}|:)|(?::(?:(?::[a-f\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,7}|:)))$)/;
103const IP_V6_REGEX = `
104\\[?(?:
105(?:[a-fA-F\\d]{1,4}:){7}(?:[a-fA-F\\d]{1,4}|:)|
106(?:[a-fA-F\\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|:[a-fA-F\\d]{1,4}|:)|
107(?:[a-fA-F\\d]{1,4}:){5}(?::(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(?::[a-fA-F\\d]{1,4}){1,2}|:)|
108(?:[a-fA-F\\d]{1,4}:){4}(?:(?::[a-fA-F\\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(?::[a-fA-F\\d]{1,4}){1,3}|:)|
109(?:[a-fA-F\\d]{1,4}:){3}(?:(?::[a-fA-F\\d]{1,4}){0,2}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(?::[a-fA-F\\d]{1,4}){1,4}|:)|
110(?:[a-fA-F\\d]{1,4}:){2}(?:(?::[a-fA-F\\d]{1,4}){0,3}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(?::[a-fA-F\\d]{1,4}){1,5}|:)|
111(?:[a-fA-F\\d]{1,4}:){1}(?:(?::[a-fA-F\\d]{1,4}){0,4}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(?::[a-fA-F\\d]{1,4}){1,6}|:)|
112(?::(?:(?::[a-fA-F\\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(?::[a-fA-F\\d]{1,4}){1,7}|:))
113)(?:%[0-9a-zA-Z]{1,})?\\]?
114`
115 .replace(/\s*\/\/.*$/gm, "")
116 .replace(/\n/g, "")
117 .trim();
118const IP_V6_REGEX_OBJECT = new RegExp(`^${IP_V6_REGEX}$`);
119
120/*
121 * Parses a Natural number (i.e., non-negative integer) with either the
122 * <min>*<max>DIGIT ( non-digit *OCTET )
123 * or
124 * <min>*<max>DIGIT
125 * grammar (RFC6265 S5.1.1).
126 *
127 * The "trailingOK" boolean controls if the grammar accepts a
128 * "( non-digit *OCTET )" trailer.
129 */
130function parseDigits(token, minDigits, maxDigits, trailingOK) {
131 let count = 0;
132 while (count < token.length) {
133 const c = token.charCodeAt(count);
134 // "non-digit = %x00-2F / %x3A-FF"
135 if (c <= 0x2f || c >= 0x3a) {
136 break;
137 }
138 count++;
139 }
140
141 // constrain to a minimum and maximum number of digits.
142 if (count < minDigits || count > maxDigits) {
143 return null;
144 }
145
146 if (!trailingOK && count != token.length) {
147 return null;
148 }
149
150 return parseInt(token.substr(0, count), 10);
151}
152
153function parseTime(token) {
154 const parts = token.split(":");
155 const result = [0, 0, 0];
156
157 /* RF6256 S5.1.1:
158 * time = hms-time ( non-digit *OCTET )
159 * hms-time = time-field ":" time-field ":" time-field
160 * time-field = 1*2DIGIT
161 */
162
163 if (parts.length !== 3) {
164 return null;
165 }
166
167 for (let i = 0; i < 3; i++) {
168 // "time-field" must be strictly "1*2DIGIT", HOWEVER, "hms-time" can be
169 // followed by "( non-digit *OCTET )" so therefore the last time-field can
170 // have a trailer
171 const trailingOK = i == 2;
172 const num = parseDigits(parts[i], 1, 2, trailingOK);
173 if (num === null) {
174 return null;
175 }
176 result[i] = num;
177 }
178
179 return result;
180}
181
182function parseMonth(token) {
183 token = String(token)
184 .substr(0, 3)
185 .toLowerCase();
186 const num = MONTH_TO_NUM[token];
187 return num >= 0 ? num : null;
188}
189
190/*
191 * RFC6265 S5.1.1 date parser (see RFC for full grammar)
192 */
193function parseDate(str) {
194 if (!str) {
195 return;
196 }
197
198 /* RFC6265 S5.1.1:
199 * 2. Process each date-token sequentially in the order the date-tokens
200 * appear in the cookie-date
201 */
202 const tokens = str.split(DATE_DELIM);
203 if (!tokens) {
204 return;
205 }
206
207 let hour = null;
208 let minute = null;
209 let second = null;
210 let dayOfMonth = null;
211 let month = null;
212 let year = null;
213
214 for (let i = 0; i < tokens.length; i++) {
215 const token = tokens[i].trim();
216 if (!token.length) {
217 continue;
218 }
219
220 let result;
221
222 /* 2.1. If the found-time flag is not set and the token matches the time
223 * production, set the found-time flag and set the hour- value,
224 * minute-value, and second-value to the numbers denoted by the digits in
225 * the date-token, respectively. Skip the remaining sub-steps and continue
226 * to the next date-token.
227 */
228 if (second === null) {
229 result = parseTime(token);
230 if (result) {
231 hour = result[0];
232 minute = result[1];
233 second = result[2];
234 continue;
235 }
236 }
237
238 /* 2.2. If the found-day-of-month flag is not set and the date-token matches
239 * the day-of-month production, set the found-day-of- month flag and set
240 * the day-of-month-value to the number denoted by the date-token. Skip
241 * the remaining sub-steps and continue to the next date-token.
242 */
243 if (dayOfMonth === null) {
244 // "day-of-month = 1*2DIGIT ( non-digit *OCTET )"
245 result = parseDigits(token, 1, 2, true);
246 if (result !== null) {
247 dayOfMonth = result;
248 continue;
249 }
250 }
251
252 /* 2.3. If the found-month flag is not set and the date-token matches the
253 * month production, set the found-month flag and set the month-value to
254 * the month denoted by the date-token. Skip the remaining sub-steps and
255 * continue to the next date-token.
256 */
257 if (month === null) {
258 result = parseMonth(token);
259 if (result !== null) {
260 month = result;
261 continue;
262 }
263 }
264
265 /* 2.4. If the found-year flag is not set and the date-token matches the
266 * year production, set the found-year flag and set the year-value to the
267 * number denoted by the date-token. Skip the remaining sub-steps and
268 * continue to the next date-token.
269 */
270 if (year === null) {
271 // "year = 2*4DIGIT ( non-digit *OCTET )"
272 result = parseDigits(token, 2, 4, true);
273 if (result !== null) {
274 year = result;
275 /* From S5.1.1:
276 * 3. If the year-value is greater than or equal to 70 and less
277 * than or equal to 99, increment the year-value by 1900.
278 * 4. If the year-value is greater than or equal to 0 and less
279 * than or equal to 69, increment the year-value by 2000.
280 */
281 if (year >= 70 && year <= 99) {
282 year += 1900;
283 } else if (year >= 0 && year <= 69) {
284 year += 2000;
285 }
286 }
287 }
288 }
289
290 /* RFC 6265 S5.1.1
291 * "5. Abort these steps and fail to parse the cookie-date if:
292 * * at least one of the found-day-of-month, found-month, found-
293 * year, or found-time flags is not set,
294 * * the day-of-month-value is less than 1 or greater than 31,
295 * * the year-value is less than 1601,
296 * * the hour-value is greater than 23,
297 * * the minute-value is greater than 59, or
298 * * the second-value is greater than 59.
299 * (Note that leap seconds cannot be represented in this syntax.)"
300 *
301 * So, in order as above:
302 */
303 if (
304 dayOfMonth === null ||
305 month === null ||
306 year === null ||
307 second === null ||
308 dayOfMonth < 1 ||
309 dayOfMonth > 31 ||
310 year < 1601 ||
311 hour > 23 ||
312 minute > 59 ||
313 second > 59
314 ) {
315 return;
316 }
317
318 return new Date(Date.UTC(year, month, dayOfMonth, hour, minute, second));
319}
320
321function formatDate(date) {
322 validators.validate(validators.isDate(date), date);
323 return date.toUTCString();
324}
325
326// S5.1.2 Canonicalized Host Names
327function canonicalDomain(str) {
328 if (str == null) {
329 return null;
330 }
331 str = str.trim().replace(/^\./, ""); // S4.1.2.3 & S5.2.3: ignore leading .
332
333 if (IP_V6_REGEX_OBJECT.test(str)) {
334 str = str.replace("[", "").replace("]", "");
335 }
336
337 // convert to IDN if any non-ASCII characters
338 if (punycode && /[^\u0001-\u007f]/.test(str)) {
339 str = punycode.toASCII(str);
340 }
341
342 return str.toLowerCase();
343}
344
345// S5.1.3 Domain Matching
346function domainMatch(str, domStr, canonicalize) {
347 if (str == null || domStr == null) {
348 return null;
349 }
350 if (canonicalize !== false) {
351 str = canonicalDomain(str);
352 domStr = canonicalDomain(domStr);
353 }
354
355 /*
356 * S5.1.3:
357 * "A string domain-matches a given domain string if at least one of the
358 * following conditions hold:"
359 *
360 * " o The domain string and the string are identical. (Note that both the
361 * domain string and the string will have been canonicalized to lower case at
362 * this point)"
363 */
364 if (str == domStr) {
365 return true;
366 }
367
368 /* " o All of the following [three] conditions hold:" */
369
370 /* "* The domain string is a suffix of the string" */
371 const idx = str.lastIndexOf(domStr);
372 if (idx <= 0) {
373 return false; // it's a non-match (-1) or prefix (0)
374 }
375
376 // next, check it's a proper suffix
377 // e.g., "a.b.c".indexOf("b.c") === 2
378 // 5 === 3+2
379 if (str.length !== domStr.length + idx) {
380 return false; // it's not a suffix
381 }
382
383 /* " * The last character of the string that is not included in the
384 * domain string is a %x2E (".") character." */
385 if (str.substr(idx - 1, 1) !== ".") {
386 return false; // doesn't align on "."
387 }
388
389 /* " * The string is a host name (i.e., not an IP address)." */
390 if (IP_REGEX_LOWERCASE.test(str)) {
391 return false; // it's an IP address
392 }
393
394 return true;
395}
396
397// RFC6265 S5.1.4 Paths and Path-Match
398
399/*
400 * "The user agent MUST use an algorithm equivalent to the following algorithm
401 * to compute the default-path of a cookie:"
402 *
403 * Assumption: the path (and not query part or absolute uri) is passed in.
404 */
405function defaultPath(path) {
406 // "2. If the uri-path is empty or if the first character of the uri-path is not
407 // a %x2F ("/") character, output %x2F ("/") and skip the remaining steps.
408 if (!path || path.substr(0, 1) !== "/") {
409 return "/";
410 }
411
412 // "3. If the uri-path contains no more than one %x2F ("/") character, output
413 // %x2F ("/") and skip the remaining step."
414 if (path === "/") {
415 return path;
416 }
417
418 const rightSlash = path.lastIndexOf("/");
419 if (rightSlash === 0) {
420 return "/";
421 }
422
423 // "4. Output the characters of the uri-path from the first character up to,
424 // but not including, the right-most %x2F ("/")."
425 return path.slice(0, rightSlash);
426}
427
428function trimTerminator(str) {
429 if (validators.isEmptyString(str)) return str;
430 for (let t = 0; t < TERMINATORS.length; t++) {
431 const terminatorIdx = str.indexOf(TERMINATORS[t]);
432 if (terminatorIdx !== -1) {
433 str = str.substr(0, terminatorIdx);
434 }
435 }
436
437 return str;
438}
439
440function parseCookiePair(cookiePair, looseMode) {
441 cookiePair = trimTerminator(cookiePair);
442 validators.validate(validators.isString(cookiePair), cookiePair);
443
444 let firstEq = cookiePair.indexOf("=");
445 if (looseMode) {
446 if (firstEq === 0) {
447 // '=' is immediately at start
448 cookiePair = cookiePair.substr(1);
449 firstEq = cookiePair.indexOf("="); // might still need to split on '='
450 }
451 } else {
452 // non-loose mode
453 if (firstEq <= 0) {
454 // no '=' or is at start
455 return; // needs to have non-empty "cookie-name"
456 }
457 }
458
459 let cookieName, cookieValue;
460 if (firstEq <= 0) {
461 cookieName = "";
462 cookieValue = cookiePair.trim();
463 } else {
464 cookieName = cookiePair.substr(0, firstEq).trim();
465 cookieValue = cookiePair.substr(firstEq + 1).trim();
466 }
467
468 if (CONTROL_CHARS.test(cookieName) || CONTROL_CHARS.test(cookieValue)) {
469 return;
470 }
471
472 const c = new Cookie();
473 c.key = cookieName;
474 c.value = cookieValue;
475 return c;
476}
477
478function parse(str, options) {
479 if (!options || typeof options !== "object") {
480 options = {};
481 }
482
483 if (validators.isEmptyString(str) || !validators.isString(str)) {
484 return null;
485 }
486
487 str = str.trim();
488
489 // We use a regex to parse the "name-value-pair" part of S5.2
490 const firstSemi = str.indexOf(";"); // S5.2 step 1
491 const cookiePair = firstSemi === -1 ? str : str.substr(0, firstSemi);
492 const c = parseCookiePair(cookiePair, !!options.loose);
493 if (!c) {
494 return;
495 }
496
497 if (firstSemi === -1) {
498 return c;
499 }
500
501 // S5.2.3 "unparsed-attributes consist of the remainder of the set-cookie-string
502 // (including the %x3B (";") in question)." plus later on in the same section
503 // "discard the first ";" and trim".
504 const unparsed = str.slice(firstSemi + 1).trim();
505
506 // "If the unparsed-attributes string is empty, skip the rest of these
507 // steps."
508 if (unparsed.length === 0) {
509 return c;
510 }
511
512 /*
513 * S5.2 says that when looping over the items "[p]rocess the attribute-name
514 * and attribute-value according to the requirements in the following
515 * subsections" for every item. Plus, for many of the individual attributes
516 * in S5.3 it says to use the "attribute-value of the last attribute in the
517 * cookie-attribute-list". Therefore, in this implementation, we overwrite
518 * the previous value.
519 */
520 const cookie_avs = unparsed.split(";");
521 while (cookie_avs.length) {
522 const av = cookie_avs.shift().trim();
523 if (av.length === 0) {
524 // happens if ";;" appears
525 continue;
526 }
527 const av_sep = av.indexOf("=");
528 let av_key, av_value;
529
530 if (av_sep === -1) {
531 av_key = av;
532 av_value = null;
533 } else {
534 av_key = av.substr(0, av_sep);
535 av_value = av.substr(av_sep + 1);
536 }
537
538 av_key = av_key.trim().toLowerCase();
539
540 if (av_value) {
541 av_value = av_value.trim();
542 }
543
544 switch (av_key) {
545 case "expires": // S5.2.1
546 if (av_value) {
547 const exp = parseDate(av_value);
548 // "If the attribute-value failed to parse as a cookie date, ignore the
549 // cookie-av."
550 if (exp) {
551 // over and underflow not realistically a concern: V8's getTime() seems to
552 // store something larger than a 32-bit time_t (even with 32-bit node)
553 c.expires = exp;
554 }
555 }
556 break;
557
558 case "max-age": // S5.2.2
559 if (av_value) {
560 // "If the first character of the attribute-value is not a DIGIT or a "-"
561 // character ...[or]... If the remainder of attribute-value contains a
562 // non-DIGIT character, ignore the cookie-av."
563 if (/^-?[0-9]+$/.test(av_value)) {
564 const delta = parseInt(av_value, 10);
565 // "If delta-seconds is less than or equal to zero (0), let expiry-time
566 // be the earliest representable date and time."
567 c.setMaxAge(delta);
568 }
569 }
570 break;
571
572 case "domain": // S5.2.3
573 // "If the attribute-value is empty, the behavior is undefined. However,
574 // the user agent SHOULD ignore the cookie-av entirely."
575 if (av_value) {
576 // S5.2.3 "Let cookie-domain be the attribute-value without the leading %x2E
577 // (".") character."
578 const domain = av_value.trim().replace(/^\./, "");
579 if (domain) {
580 // "Convert the cookie-domain to lower case."
581 c.domain = domain.toLowerCase();
582 }
583 }
584 break;
585
586 case "path": // S5.2.4
587 /*
588 * "If the attribute-value is empty or if the first character of the
589 * attribute-value is not %x2F ("/"):
590 * Let cookie-path be the default-path.
591 * Otherwise:
592 * Let cookie-path be the attribute-value."
593 *
594 * We'll represent the default-path as null since it depends on the
595 * context of the parsing.
596 */
597 c.path = av_value && av_value[0] === "/" ? av_value : null;
598 break;
599
600 case "secure": // S5.2.5
601 /*
602 * "If the attribute-name case-insensitively matches the string "Secure",
603 * the user agent MUST append an attribute to the cookie-attribute-list
604 * with an attribute-name of Secure and an empty attribute-value."
605 */
606 c.secure = true;
607 break;
608
609 case "httponly": // S5.2.6 -- effectively the same as 'secure'
610 c.httpOnly = true;
611 break;
612
613 case "samesite": // RFC6265bis-02 S5.3.7
614 const enforcement = av_value ? av_value.toLowerCase() : "";
615 switch (enforcement) {
616 case "strict":
617 c.sameSite = "strict";
618 break;
619 case "lax":
620 c.sameSite = "lax";
621 break;
622 case "none":
623 c.sameSite = "none";
624 break;
625 default:
626 c.sameSite = undefined;
627 break;
628 }
629 break;
630
631 default:
632 c.extensions = c.extensions || [];
633 c.extensions.push(av);
634 break;
635 }
636 }
637
638 return c;
639}
640
641/**
642 * If the cookie-name begins with a case-sensitive match for the
643 * string "__Secure-", abort these steps and ignore the cookie
644 * entirely unless the cookie's secure-only-flag is true.
645 * @param cookie
646 * @returns boolean
647 */
648function isSecurePrefixConditionMet(cookie) {
649 validators.validate(validators.isObject(cookie), cookie);
650 return !cookie.key.startsWith("__Secure-") || cookie.secure;
651}
652
653/**
654 * If the cookie-name begins with a case-sensitive match for the
655 * string "__Host-", abort these steps and ignore the cookie
656 * entirely unless the cookie meets all the following criteria:
657 * 1. The cookie's secure-only-flag is true.
658 * 2. The cookie's host-only-flag is true.
659 * 3. The cookie-attribute-list contains an attribute with an
660 * attribute-name of "Path", and the cookie's path is "/".
661 * @param cookie
662 * @returns boolean
663 */
664function isHostPrefixConditionMet(cookie) {
665 validators.validate(validators.isObject(cookie));
666 return (
667 !cookie.key.startsWith("__Host-") ||
668 (cookie.secure &&
669 cookie.hostOnly &&
670 cookie.path != null &&
671 cookie.path === "/")
672 );
673}
674
675// avoid the V8 deoptimization monster!
676function jsonParse(str) {
677 let obj;
678 try {
679 obj = JSON.parse(str);
680 } catch (e) {
681 return e;
682 }
683 return obj;
684}
685
686function fromJSON(str) {
687 if (!str || validators.isEmptyString(str)) {
688 return null;
689 }
690
691 let obj;
692 if (typeof str === "string") {
693 obj = jsonParse(str);
694 if (obj instanceof Error) {
695 return null;
696 }
697 } else {
698 // assume it's an Object
699 obj = str;
700 }
701
702 const c = new Cookie();
703 for (let i = 0; i < Cookie.serializableProperties.length; i++) {
704 const prop = Cookie.serializableProperties[i];
705 if (obj[prop] === undefined || obj[prop] === cookieDefaults[prop]) {
706 continue; // leave as prototype default
707 }
708
709 if (prop === "expires" || prop === "creation" || prop === "lastAccessed") {
710 if (obj[prop] === null) {
711 c[prop] = null;
712 } else {
713 c[prop] = obj[prop] == "Infinity" ? "Infinity" : new Date(obj[prop]);
714 }
715 } else {
716 c[prop] = obj[prop];
717 }
718 }
719
720 return c;
721}
722
723/* Section 5.4 part 2:
724 * "* Cookies with longer paths are listed before cookies with
725 * shorter paths.
726 *
727 * * Among cookies that have equal-length path fields, cookies with
728 * earlier creation-times are listed before cookies with later
729 * creation-times."
730 */
731
732function cookieCompare(a, b) {
733 validators.validate(validators.isObject(a), a);
734 validators.validate(validators.isObject(b), b);
735 let cmp = 0;
736
737 // descending for length: b CMP a
738 const aPathLen = a.path ? a.path.length : 0;
739 const bPathLen = b.path ? b.path.length : 0;
740 cmp = bPathLen - aPathLen;
741 if (cmp !== 0) {
742 return cmp;
743 }
744
745 // ascending for time: a CMP b
746 const aTime = a.creation ? a.creation.getTime() : MAX_TIME;
747 const bTime = b.creation ? b.creation.getTime() : MAX_TIME;
748 cmp = aTime - bTime;
749 if (cmp !== 0) {
750 return cmp;
751 }
752
753 // break ties for the same millisecond (precision of JavaScript's clock)
754 cmp = a.creationIndex - b.creationIndex;
755
756 return cmp;
757}
758
759// Gives the permutation of all possible pathMatch()es of a given path. The
760// array is in longest-to-shortest order. Handy for indexing.
761function permutePath(path) {
762 validators.validate(validators.isString(path));
763 if (path === "/") {
764 return ["/"];
765 }
766 const permutations = [path];
767 while (path.length > 1) {
768 const lindex = path.lastIndexOf("/");
769 if (lindex === 0) {
770 break;
771 }
772 path = path.substr(0, lindex);
773 permutations.push(path);
774 }
775 permutations.push("/");
776 return permutations;
777}
778
779function getCookieContext(url) {
780 if (url instanceof Object) {
781 return url;
782 }
783 // NOTE: decodeURI will throw on malformed URIs (see GH-32).
784 // Therefore, we will just skip decoding for such URIs.
785 try {
786 url = decodeURI(url);
787 } catch (err) {
788 // Silently swallow error
789 }
790
791 return urlParse(url);
792}
793
794const cookieDefaults = {
795 // the order in which the RFC has them:
796 key: "",
797 value: "",
798 expires: "Infinity",
799 maxAge: null,
800 domain: null,
801 path: null,
802 secure: false,
803 httpOnly: false,
804 extensions: null,
805 // set by the CookieJar:
806 hostOnly: null,
807 pathIsDefault: null,
808 creation: null,
809 lastAccessed: null,
810 sameSite: undefined
811};
812
813class Cookie {
814 constructor(options = {}) {
815 const customInspectSymbol = getCustomInspectSymbol();
816 if (customInspectSymbol) {
817 this[customInspectSymbol] = this.inspect;
818 }
819
820 Object.assign(this, cookieDefaults, options);
821 this.creation = this.creation || new Date();
822
823 // used to break creation ties in cookieCompare():
824 Object.defineProperty(this, "creationIndex", {
825 configurable: false,
826 enumerable: false, // important for assert.deepEqual checks
827 writable: true,
828 value: ++Cookie.cookiesCreated
829 });
830 }
831
832 inspect() {
833 const now = Date.now();
834 const hostOnly = this.hostOnly != null ? this.hostOnly : "?";
835 const createAge = this.creation
836 ? `${now - this.creation.getTime()}ms`
837 : "?";
838 const accessAge = this.lastAccessed
839 ? `${now - this.lastAccessed.getTime()}ms`
840 : "?";
841 return `Cookie="${this.toString()}; hostOnly=${hostOnly}; aAge=${accessAge}; cAge=${createAge}"`;
842 }
843
844 toJSON() {
845 const obj = {};
846
847 for (const prop of Cookie.serializableProperties) {
848 if (this[prop] === cookieDefaults[prop]) {
849 continue; // leave as prototype default
850 }
851
852 if (
853 prop === "expires" ||
854 prop === "creation" ||
855 prop === "lastAccessed"
856 ) {
857 if (this[prop] === null) {
858 obj[prop] = null;
859 } else {
860 obj[prop] =
861 this[prop] == "Infinity" // intentionally not ===
862 ? "Infinity"
863 : this[prop].toISOString();
864 }
865 } else if (prop === "maxAge") {
866 if (this[prop] !== null) {
867 // again, intentionally not ===
868 obj[prop] =
869 this[prop] == Infinity || this[prop] == -Infinity
870 ? this[prop].toString()
871 : this[prop];
872 }
873 } else {
874 if (this[prop] !== cookieDefaults[prop]) {
875 obj[prop] = this[prop];
876 }
877 }
878 }
879
880 return obj;
881 }
882
883 clone() {
884 return fromJSON(this.toJSON());
885 }
886
887 validate() {
888 if (!COOKIE_OCTETS.test(this.value)) {
889 return false;
890 }
891 if (
892 this.expires != Infinity &&
893 !(this.expires instanceof Date) &&
894 !parseDate(this.expires)
895 ) {
896 return false;
897 }
898 if (this.maxAge != null && this.maxAge <= 0) {
899 return false; // "Max-Age=" non-zero-digit *DIGIT
900 }
901 if (this.path != null && !PATH_VALUE.test(this.path)) {
902 return false;
903 }
904
905 const cdomain = this.cdomain();
906 if (cdomain) {
907 if (cdomain.match(/\.$/)) {
908 return false; // S4.1.2.3 suggests that this is bad. domainMatch() tests confirm this
909 }
910 const suffix = pubsuffix.getPublicSuffix(cdomain);
911 if (suffix == null) {
912 // it's a public suffix
913 return false;
914 }
915 }
916 return true;
917 }
918
919 setExpires(exp) {
920 if (exp instanceof Date) {
921 this.expires = exp;
922 } else {
923 this.expires = parseDate(exp) || "Infinity";
924 }
925 }
926
927 setMaxAge(age) {
928 if (age === Infinity || age === -Infinity) {
929 this.maxAge = age.toString(); // so JSON.stringify() works
930 } else {
931 this.maxAge = age;
932 }
933 }
934
935 cookieString() {
936 let val = this.value;
937 if (val == null) {
938 val = "";
939 }
940 if (this.key === "") {
941 return val;
942 }
943 return `${this.key}=${val}`;
944 }
945
946 // gives Set-Cookie header format
947 toString() {
948 let str = this.cookieString();
949
950 if (this.expires != Infinity) {
951 if (this.expires instanceof Date) {
952 str += `; Expires=${formatDate(this.expires)}`;
953 } else {
954 str += `; Expires=${this.expires}`;
955 }
956 }
957
958 if (this.maxAge != null && this.maxAge != Infinity) {
959 str += `; Max-Age=${this.maxAge}`;
960 }
961
962 if (this.domain && !this.hostOnly) {
963 str += `; Domain=${this.domain}`;
964 }
965 if (this.path) {
966 str += `; Path=${this.path}`;
967 }
968
969 if (this.secure) {
970 str += "; Secure";
971 }
972 if (this.httpOnly) {
973 str += "; HttpOnly";
974 }
975 if (this.sameSite && this.sameSite !== "none") {
976 const ssCanon = Cookie.sameSiteCanonical[this.sameSite.toLowerCase()];
977 str += `; SameSite=${ssCanon ? ssCanon : this.sameSite}`;
978 }
979 if (this.extensions) {
980 this.extensions.forEach(ext => {
981 str += `; ${ext}`;
982 });
983 }
984
985 return str;
986 }
987
988 // TTL() partially replaces the "expiry-time" parts of S5.3 step 3 (setCookie()
989 // elsewhere)
990 // S5.3 says to give the "latest representable date" for which we use Infinity
991 // For "expired" we use 0
992 TTL(now) {
993 /* RFC6265 S4.1.2.2 If a cookie has both the Max-Age and the Expires
994 * attribute, the Max-Age attribute has precedence and controls the
995 * expiration date of the cookie.
996 * (Concurs with S5.3 step 3)
997 */
998 if (this.maxAge != null) {
999 return this.maxAge <= 0 ? 0 : this.maxAge * 1000;
1000 }
1001
1002 let expires = this.expires;
1003 if (expires != Infinity) {
1004 if (!(expires instanceof Date)) {
1005 expires = parseDate(expires) || Infinity;
1006 }
1007
1008 if (expires == Infinity) {
1009 return Infinity;
1010 }
1011
1012 return expires.getTime() - (now || Date.now());
1013 }
1014
1015 return Infinity;
1016 }
1017
1018 // expiryTime() replaces the "expiry-time" parts of S5.3 step 3 (setCookie()
1019 // elsewhere)
1020 expiryTime(now) {
1021 if (this.maxAge != null) {
1022 const relativeTo = now || this.creation || new Date();
1023 const age = this.maxAge <= 0 ? -Infinity : this.maxAge * 1000;
1024 return relativeTo.getTime() + age;
1025 }
1026
1027 if (this.expires == Infinity) {
1028 return Infinity;
1029 }
1030 return this.expires.getTime();
1031 }
1032
1033 // expiryDate() replaces the "expiry-time" parts of S5.3 step 3 (setCookie()
1034 // elsewhere), except it returns a Date
1035 expiryDate(now) {
1036 const millisec = this.expiryTime(now);
1037 if (millisec == Infinity) {
1038 return new Date(MAX_TIME);
1039 } else if (millisec == -Infinity) {
1040 return new Date(MIN_TIME);
1041 } else {
1042 return new Date(millisec);
1043 }
1044 }
1045
1046 // This replaces the "persistent-flag" parts of S5.3 step 3
1047 isPersistent() {
1048 return this.maxAge != null || this.expires != Infinity;
1049 }
1050
1051 // Mostly S5.1.2 and S5.2.3:
1052 canonicalizedDomain() {
1053 if (this.domain == null) {
1054 return null;
1055 }
1056 return canonicalDomain(this.domain);
1057 }
1058
1059 cdomain() {
1060 return this.canonicalizedDomain();
1061 }
1062}
1063
1064Cookie.cookiesCreated = 0;
1065Cookie.parse = parse;
1066Cookie.fromJSON = fromJSON;
1067Cookie.serializableProperties = Object.keys(cookieDefaults);
1068Cookie.sameSiteLevel = {
1069 strict: 3,
1070 lax: 2,
1071 none: 1
1072};
1073
1074Cookie.sameSiteCanonical = {
1075 strict: "Strict",
1076 lax: "Lax"
1077};
1078
1079function getNormalizedPrefixSecurity(prefixSecurity) {
1080 if (prefixSecurity != null) {
1081 const normalizedPrefixSecurity = prefixSecurity.toLowerCase();
1082 /* The three supported options */
1083 switch (normalizedPrefixSecurity) {
1084 case PrefixSecurityEnum.STRICT:
1085 case PrefixSecurityEnum.SILENT:
1086 case PrefixSecurityEnum.DISABLED:
1087 return normalizedPrefixSecurity;
1088 }
1089 }
1090 /* Default is SILENT */
1091 return PrefixSecurityEnum.SILENT;
1092}
1093
1094class CookieJar {
1095 constructor(store, options = { rejectPublicSuffixes: true }) {
1096 if (typeof options === "boolean") {
1097 options = { rejectPublicSuffixes: options };
1098 }
1099 validators.validate(validators.isObject(options), options);
1100 this.rejectPublicSuffixes = options.rejectPublicSuffixes;
1101 this.enableLooseMode = !!options.looseMode;
1102 this.allowSpecialUseDomain =
1103 typeof options.allowSpecialUseDomain === "boolean"
1104 ? options.allowSpecialUseDomain
1105 : true;
1106 this.store = store || new MemoryCookieStore();
1107 this.prefixSecurity = getNormalizedPrefixSecurity(options.prefixSecurity);
1108 this._cloneSync = syncWrap("clone");
1109 this._importCookiesSync = syncWrap("_importCookies");
1110 this.getCookiesSync = syncWrap("getCookies");
1111 this.getCookieStringSync = syncWrap("getCookieString");
1112 this.getSetCookieStringsSync = syncWrap("getSetCookieStrings");
1113 this.removeAllCookiesSync = syncWrap("removeAllCookies");
1114 this.setCookieSync = syncWrap("setCookie");
1115 this.serializeSync = syncWrap("serialize");
1116 }
1117
1118 setCookie(cookie, url, options, cb) {
1119 validators.validate(validators.isUrlStringOrObject(url), cb, options);
1120
1121 let err;
1122
1123 if (validators.isFunction(url)) {
1124 cb = url;
1125 return cb(new Error("No URL was specified"));
1126 }
1127
1128 const context = getCookieContext(url);
1129 if (validators.isFunction(options)) {
1130 cb = options;
1131 options = {};
1132 }
1133
1134 validators.validate(validators.isFunction(cb), cb);
1135
1136 if (
1137 !validators.isNonEmptyString(cookie) &&
1138 !validators.isObject(cookie) &&
1139 cookie instanceof String &&
1140 cookie.length == 0
1141 ) {
1142 return cb(null);
1143 }
1144
1145 const host = canonicalDomain(context.hostname);
1146 const loose = options.loose || this.enableLooseMode;
1147
1148 let sameSiteContext = null;
1149 if (options.sameSiteContext) {
1150 sameSiteContext = checkSameSiteContext(options.sameSiteContext);
1151 if (!sameSiteContext) {
1152 return cb(new Error(SAME_SITE_CONTEXT_VAL_ERR));
1153 }
1154 }
1155
1156 // S5.3 step 1
1157 if (typeof cookie === "string" || cookie instanceof String) {
1158 cookie = Cookie.parse(cookie, { loose: loose });
1159 if (!cookie) {
1160 err = new Error("Cookie failed to parse");
1161 return cb(options.ignoreError ? null : err);
1162 }
1163 } else if (!(cookie instanceof Cookie)) {
1164 // If you're seeing this error, and are passing in a Cookie object,
1165 // it *might* be a Cookie object from another loaded version of tough-cookie.
1166 err = new Error(
1167 "First argument to setCookie must be a Cookie object or string"
1168 );
1169 return cb(options.ignoreError ? null : err);
1170 }
1171
1172 // S5.3 step 2
1173 const now = options.now || new Date(); // will assign later to save effort in the face of errors
1174
1175 // S5.3 step 3: NOOP; persistent-flag and expiry-time is handled by getCookie()
1176
1177 // S5.3 step 4: NOOP; domain is null by default
1178
1179 // S5.3 step 5: public suffixes
1180 if (this.rejectPublicSuffixes && cookie.domain) {
1181 const suffix = pubsuffix.getPublicSuffix(cookie.cdomain(), {
1182 allowSpecialUseDomain: this.allowSpecialUseDomain,
1183 ignoreError: options.ignoreError
1184 });
1185 if (suffix == null && !IP_V6_REGEX_OBJECT.test(cookie.domain)) {
1186 // e.g. "com"
1187 err = new Error("Cookie has domain set to a public suffix");
1188 return cb(options.ignoreError ? null : err);
1189 }
1190 }
1191
1192 // S5.3 step 6:
1193 if (cookie.domain) {
1194 if (!domainMatch(host, cookie.cdomain(), false)) {
1195 err = new Error(
1196 `Cookie not in this host's domain. Cookie:${cookie.cdomain()} Request:${host}`
1197 );
1198 return cb(options.ignoreError ? null : err);
1199 }
1200
1201 if (cookie.hostOnly == null) {
1202 // don't reset if already set
1203 cookie.hostOnly = false;
1204 }
1205 } else {
1206 cookie.hostOnly = true;
1207 cookie.domain = host;
1208 }
1209
1210 //S5.2.4 If the attribute-value is empty or if the first character of the
1211 //attribute-value is not %x2F ("/"):
1212 //Let cookie-path be the default-path.
1213 if (!cookie.path || cookie.path[0] !== "/") {
1214 cookie.path = defaultPath(context.pathname);
1215 cookie.pathIsDefault = true;
1216 }
1217
1218 // S5.3 step 8: NOOP; secure attribute
1219 // S5.3 step 9: NOOP; httpOnly attribute
1220
1221 // S5.3 step 10
1222 if (options.http === false && cookie.httpOnly) {
1223 err = new Error("Cookie is HttpOnly and this isn't an HTTP API");
1224 return cb(options.ignoreError ? null : err);
1225 }
1226
1227 // 6252bis-02 S5.4 Step 13 & 14:
1228 if (
1229 cookie.sameSite !== "none" &&
1230 cookie.sameSite !== undefined &&
1231 sameSiteContext
1232 ) {
1233 // "If the cookie's "same-site-flag" is not "None", and the cookie
1234 // is being set from a context whose "site for cookies" is not an
1235 // exact match for request-uri's host's registered domain, then
1236 // abort these steps and ignore the newly created cookie entirely."
1237 if (sameSiteContext === "none") {
1238 err = new Error(
1239 "Cookie is SameSite but this is a cross-origin request"
1240 );
1241 return cb(options.ignoreError ? null : err);
1242 }
1243 }
1244
1245 /* 6265bis-02 S5.4 Steps 15 & 16 */
1246 const ignoreErrorForPrefixSecurity =
1247 this.prefixSecurity === PrefixSecurityEnum.SILENT;
1248 const prefixSecurityDisabled =
1249 this.prefixSecurity === PrefixSecurityEnum.DISABLED;
1250 /* If prefix checking is not disabled ...*/
1251 if (!prefixSecurityDisabled) {
1252 let errorFound = false;
1253 let errorMsg;
1254 /* Check secure prefix condition */
1255 if (!isSecurePrefixConditionMet(cookie)) {
1256 errorFound = true;
1257 errorMsg = "Cookie has __Secure prefix but Secure attribute is not set";
1258 } else if (!isHostPrefixConditionMet(cookie)) {
1259 /* Check host prefix condition */
1260 errorFound = true;
1261 errorMsg =
1262 "Cookie has __Host prefix but either Secure or HostOnly attribute is not set or Path is not '/'";
1263 }
1264 if (errorFound) {
1265 return cb(
1266 options.ignoreError || ignoreErrorForPrefixSecurity
1267 ? null
1268 : new Error(errorMsg)
1269 );
1270 }
1271 }
1272
1273 const store = this.store;
1274
1275 if (!store.updateCookie) {
1276 store.updateCookie = function(oldCookie, newCookie, cb) {
1277 this.putCookie(newCookie, cb);
1278 };
1279 }
1280
1281 function withCookie(err, oldCookie) {
1282 if (err) {
1283 return cb(err);
1284 }
1285
1286 const next = function(err) {
1287 if (err) {
1288 return cb(err);
1289 } else {
1290 cb(null, cookie);
1291 }
1292 };
1293
1294 if (oldCookie) {
1295 // S5.3 step 11 - "If the cookie store contains a cookie with the same name,
1296 // domain, and path as the newly created cookie:"
1297 if (options.http === false && oldCookie.httpOnly) {
1298 // step 11.2
1299 err = new Error("old Cookie is HttpOnly and this isn't an HTTP API");
1300 return cb(options.ignoreError ? null : err);
1301 }
1302 cookie.creation = oldCookie.creation; // step 11.3
1303 cookie.creationIndex = oldCookie.creationIndex; // preserve tie-breaker
1304 cookie.lastAccessed = now;
1305 // Step 11.4 (delete cookie) is implied by just setting the new one:
1306 store.updateCookie(oldCookie, cookie, next); // step 12
1307 } else {
1308 cookie.creation = cookie.lastAccessed = now;
1309 store.putCookie(cookie, next); // step 12
1310 }
1311 }
1312
1313 store.findCookie(cookie.domain, cookie.path, cookie.key, withCookie);
1314 }
1315
1316 // RFC6365 S5.4
1317 getCookies(url, options, cb) {
1318 validators.validate(validators.isUrlStringOrObject(url), cb, url);
1319
1320 const context = getCookieContext(url);
1321 if (validators.isFunction(options)) {
1322 cb = options;
1323 options = {};
1324 }
1325 validators.validate(validators.isObject(options), cb, options);
1326 validators.validate(validators.isFunction(cb), cb);
1327
1328 const host = canonicalDomain(context.hostname);
1329 const path = context.pathname || "/";
1330
1331 let secure = options.secure;
1332 if (
1333 secure == null &&
1334 context.protocol &&
1335 (context.protocol == "https:" || context.protocol == "wss:")
1336 ) {
1337 secure = true;
1338 }
1339
1340 let sameSiteLevel = 0;
1341 if (options.sameSiteContext) {
1342 const sameSiteContext = checkSameSiteContext(options.sameSiteContext);
1343 sameSiteLevel = Cookie.sameSiteLevel[sameSiteContext];
1344 if (!sameSiteLevel) {
1345 return cb(new Error(SAME_SITE_CONTEXT_VAL_ERR));
1346 }
1347 }
1348
1349 let http = options.http;
1350 if (http == null) {
1351 http = true;
1352 }
1353
1354 const now = options.now || Date.now();
1355 const expireCheck = options.expire !== false;
1356 const allPaths = !!options.allPaths;
1357 const store = this.store;
1358
1359 function matchingCookie(c) {
1360 // "Either:
1361 // The cookie's host-only-flag is true and the canonicalized
1362 // request-host is identical to the cookie's domain.
1363 // Or:
1364 // The cookie's host-only-flag is false and the canonicalized
1365 // request-host domain-matches the cookie's domain."
1366 if (c.hostOnly) {
1367 if (c.domain != host) {
1368 return false;
1369 }
1370 } else {
1371 if (!domainMatch(host, c.domain, false)) {
1372 return false;
1373 }
1374 }
1375
1376 // "The request-uri's path path-matches the cookie's path."
1377 if (!allPaths && !pathMatch(path, c.path)) {
1378 return false;
1379 }
1380
1381 // "If the cookie's secure-only-flag is true, then the request-uri's
1382 // scheme must denote a "secure" protocol"
1383 if (c.secure && !secure) {
1384 return false;
1385 }
1386
1387 // "If the cookie's http-only-flag is true, then exclude the cookie if the
1388 // cookie-string is being generated for a "non-HTTP" API"
1389 if (c.httpOnly && !http) {
1390 return false;
1391 }
1392
1393 // RFC6265bis-02 S5.3.7
1394 if (sameSiteLevel) {
1395 const cookieLevel = Cookie.sameSiteLevel[c.sameSite || "none"];
1396 if (cookieLevel > sameSiteLevel) {
1397 // only allow cookies at or below the request level
1398 return false;
1399 }
1400 }
1401
1402 // deferred from S5.3
1403 // non-RFC: allow retention of expired cookies by choice
1404 if (expireCheck && c.expiryTime() <= now) {
1405 store.removeCookie(c.domain, c.path, c.key, () => {}); // result ignored
1406 return false;
1407 }
1408
1409 return true;
1410 }
1411
1412 store.findCookies(
1413 host,
1414 allPaths ? null : path,
1415 this.allowSpecialUseDomain,
1416 (err, cookies) => {
1417 if (err) {
1418 return cb(err);
1419 }
1420
1421 cookies = cookies.filter(matchingCookie);
1422
1423 // sorting of S5.4 part 2
1424 if (options.sort !== false) {
1425 cookies = cookies.sort(cookieCompare);
1426 }
1427
1428 // S5.4 part 3
1429 const now = new Date();
1430 for (const cookie of cookies) {
1431 cookie.lastAccessed = now;
1432 }
1433 // TODO persist lastAccessed
1434
1435 cb(null, cookies);
1436 }
1437 );
1438 }
1439
1440 getCookieString(...args) {
1441 const cb = args.pop();
1442 validators.validate(validators.isFunction(cb), cb);
1443 const next = function(err, cookies) {
1444 if (err) {
1445 cb(err);
1446 } else {
1447 cb(
1448 null,
1449 cookies
1450 .sort(cookieCompare)
1451 .map(c => c.cookieString())
1452 .join("; ")
1453 );
1454 }
1455 };
1456 args.push(next);
1457 this.getCookies.apply(this, args);
1458 }
1459
1460 getSetCookieStrings(...args) {
1461 const cb = args.pop();
1462 validators.validate(validators.isFunction(cb), cb);
1463 const next = function(err, cookies) {
1464 if (err) {
1465 cb(err);
1466 } else {
1467 cb(
1468 null,
1469 cookies.map(c => {
1470 return c.toString();
1471 })
1472 );
1473 }
1474 };
1475 args.push(next);
1476 this.getCookies.apply(this, args);
1477 }
1478
1479 serialize(cb) {
1480 validators.validate(validators.isFunction(cb), cb);
1481 let type = this.store.constructor.name;
1482 if (validators.isObject(type)) {
1483 type = null;
1484 }
1485
1486 // update README.md "Serialization Format" if you change this, please!
1487 const serialized = {
1488 // The version of tough-cookie that serialized this jar. Generally a good
1489 // practice since future versions can make data import decisions based on
1490 // known past behavior. When/if this matters, use `semver`.
1491 version: `tough-cookie@${VERSION}`,
1492
1493 // add the store type, to make humans happy:
1494 storeType: type,
1495
1496 // CookieJar configuration:
1497 rejectPublicSuffixes: !!this.rejectPublicSuffixes,
1498 enableLooseMode: !!this.enableLooseMode,
1499 allowSpecialUseDomain: !!this.allowSpecialUseDomain,
1500 prefixSecurity: getNormalizedPrefixSecurity(this.prefixSecurity),
1501
1502 // this gets filled from getAllCookies:
1503 cookies: []
1504 };
1505
1506 if (
1507 !(
1508 this.store.getAllCookies &&
1509 typeof this.store.getAllCookies === "function"
1510 )
1511 ) {
1512 return cb(
1513 new Error(
1514 "store does not support getAllCookies and cannot be serialized"
1515 )
1516 );
1517 }
1518
1519 this.store.getAllCookies((err, cookies) => {
1520 if (err) {
1521 return cb(err);
1522 }
1523
1524 serialized.cookies = cookies.map(cookie => {
1525 // convert to serialized 'raw' cookies
1526 cookie = cookie instanceof Cookie ? cookie.toJSON() : cookie;
1527
1528 // Remove the index so new ones get assigned during deserialization
1529 delete cookie.creationIndex;
1530
1531 return cookie;
1532 });
1533
1534 return cb(null, serialized);
1535 });
1536 }
1537
1538 toJSON() {
1539 return this.serializeSync();
1540 }
1541
1542 // use the class method CookieJar.deserialize instead of calling this directly
1543 _importCookies(serialized, cb) {
1544 let cookies = serialized.cookies;
1545 if (!cookies || !Array.isArray(cookies)) {
1546 return cb(new Error("serialized jar has no cookies array"));
1547 }
1548 cookies = cookies.slice(); // do not modify the original
1549
1550 const putNext = err => {
1551 if (err) {
1552 return cb(err);
1553 }
1554
1555 if (!cookies.length) {
1556 return cb(err, this);
1557 }
1558
1559 let cookie;
1560 try {
1561 cookie = fromJSON(cookies.shift());
1562 } catch (e) {
1563 return cb(e);
1564 }
1565
1566 if (cookie === null) {
1567 return putNext(null); // skip this cookie
1568 }
1569
1570 this.store.putCookie(cookie, putNext);
1571 };
1572
1573 putNext();
1574 }
1575
1576 clone(newStore, cb) {
1577 if (arguments.length === 1) {
1578 cb = newStore;
1579 newStore = null;
1580 }
1581
1582 this.serialize((err, serialized) => {
1583 if (err) {
1584 return cb(err);
1585 }
1586 CookieJar.deserialize(serialized, newStore, cb);
1587 });
1588 }
1589
1590 cloneSync(newStore) {
1591 if (arguments.length === 0) {
1592 return this._cloneSync();
1593 }
1594 if (!newStore.synchronous) {
1595 throw new Error(
1596 "CookieJar clone destination store is not synchronous; use async API instead."
1597 );
1598 }
1599 return this._cloneSync(newStore);
1600 }
1601
1602 removeAllCookies(cb) {
1603 validators.validate(validators.isFunction(cb), cb);
1604 const store = this.store;
1605
1606 // Check that the store implements its own removeAllCookies(). The default
1607 // implementation in Store will immediately call the callback with a "not
1608 // implemented" Error.
1609 if (
1610 typeof store.removeAllCookies === "function" &&
1611 store.removeAllCookies !== Store.prototype.removeAllCookies
1612 ) {
1613 return store.removeAllCookies(cb);
1614 }
1615
1616 store.getAllCookies((err, cookies) => {
1617 if (err) {
1618 return cb(err);
1619 }
1620
1621 if (cookies.length === 0) {
1622 return cb(null);
1623 }
1624
1625 let completedCount = 0;
1626 const removeErrors = [];
1627
1628 function removeCookieCb(removeErr) {
1629 if (removeErr) {
1630 removeErrors.push(removeErr);
1631 }
1632
1633 completedCount++;
1634
1635 if (completedCount === cookies.length) {
1636 return cb(removeErrors.length ? removeErrors[0] : null);
1637 }
1638 }
1639
1640 cookies.forEach(cookie => {
1641 store.removeCookie(
1642 cookie.domain,
1643 cookie.path,
1644 cookie.key,
1645 removeCookieCb
1646 );
1647 });
1648 });
1649 }
1650
1651 static deserialize(strOrObj, store, cb) {
1652 if (arguments.length !== 3) {
1653 // store is optional
1654 cb = store;
1655 store = null;
1656 }
1657 validators.validate(validators.isFunction(cb), cb);
1658
1659 let serialized;
1660 if (typeof strOrObj === "string") {
1661 serialized = jsonParse(strOrObj);
1662 if (serialized instanceof Error) {
1663 return cb(serialized);
1664 }
1665 } else {
1666 serialized = strOrObj;
1667 }
1668
1669 const jar = new CookieJar(store, {
1670 rejectPublicSuffixes: serialized.rejectPublicSuffixes,
1671 looseMode: serialized.enableLooseMode,
1672 allowSpecialUseDomain: serialized.allowSpecialUseDomain,
1673 prefixSecurity: serialized.prefixSecurity
1674 });
1675 jar._importCookies(serialized, err => {
1676 if (err) {
1677 return cb(err);
1678 }
1679 cb(null, jar);
1680 });
1681 }
1682
1683 static deserializeSync(strOrObj, store) {
1684 const serialized =
1685 typeof strOrObj === "string" ? JSON.parse(strOrObj) : strOrObj;
1686 const jar = new CookieJar(store, {
1687 rejectPublicSuffixes: serialized.rejectPublicSuffixes,
1688 looseMode: serialized.enableLooseMode
1689 });
1690
1691 // catch this mistake early:
1692 if (!jar.store.synchronous) {
1693 throw new Error(
1694 "CookieJar store is not synchronous; use async API instead."
1695 );
1696 }
1697
1698 jar._importCookiesSync(serialized);
1699 return jar;
1700 }
1701}
1702CookieJar.fromJSON = CookieJar.deserializeSync;
1703
1704[
1705 "_importCookies",
1706 "clone",
1707 "getCookies",
1708 "getCookieString",
1709 "getSetCookieStrings",
1710 "removeAllCookies",
1711 "serialize",
1712 "setCookie"
1713].forEach(name => {
1714 CookieJar.prototype[name] = fromCallback(CookieJar.prototype[name]);
1715});
1716CookieJar.deserialize = fromCallback(CookieJar.deserialize);
1717
1718// Use a closure to provide a true imperative API for synchronous stores.
1719function syncWrap(method) {
1720 return function(...args) {
1721 if (!this.store.synchronous) {
1722 throw new Error(
1723 "CookieJar store is not synchronous; use async API instead."
1724 );
1725 }
1726
1727 let syncErr, syncResult;
1728 this[method](...args, (err, result) => {
1729 syncErr = err;
1730 syncResult = result;
1731 });
1732
1733 if (syncErr) {
1734 throw syncErr;
1735 }
1736 return syncResult;
1737 };
1738}
1739
1740exports.version = VERSION;
1741exports.CookieJar = CookieJar;
1742exports.Cookie = Cookie;
1743exports.Store = Store;
1744exports.MemoryCookieStore = MemoryCookieStore;
1745exports.parseDate = parseDate;
1746exports.formatDate = formatDate;
1747exports.parse = parse;
1748exports.fromJSON = fromJSON;
1749exports.domainMatch = domainMatch;
1750exports.defaultPath = defaultPath;
1751exports.pathMatch = pathMatch;
1752exports.getPublicSuffix = pubsuffix.getPublicSuffix;
1753exports.cookieCompare = cookieCompare;
1754exports.permuteDomain = require("./permuteDomain").permuteDomain;
1755exports.permutePath = permutePath;
1756exports.canonicalDomain = canonicalDomain;
1757exports.PrefixSecurityEnum = PrefixSecurityEnum;
1758exports.ParameterError = validators.ParameterError;
Note: See TracBrowser for help on using the repository browser.