tough-cookie 0.10.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +12 -0
- package/.jshintrc +54 -56
- package/.npmignore +1 -0
- package/.travis.yml +2 -1
- package/LICENSE +22 -26
- package/README.md +65 -26
- package/generate-pubsuffix.js +72 -43
- package/lib/cookie.js +370 -164
- package/lib/memstore.js +73 -32
- package/lib/pubsuffix.js +27 -17
- package/lib/store.js +67 -0
- package/package.json +7 -7
- package/test/api_test.js +388 -0
- package/test/cookie_jar_test.js +468 -0
- package/test/cookie_sorting_test.js +90 -0
- package/test/cookie_to_json_test.js +164 -0
- package/test/cookie_to_string_test.js +162 -0
- package/test/date_test.js +79 -0
- package/test/domain_and_path_test.js +201 -0
- package/test/ietf_data/dates/bsd-examples.json +168 -0
- package/test/ietf_data/dates/examples.json +48 -0
- package/test/ietf_data/parser.json +1959 -0
- package/test/ietf_test.js +106 -0
- package/test/lifetime_test.js +97 -0
- package/test/parsing_test.js +356 -0
- package/test/regression_test.js +128 -0
- package/tough-cookie-deps.tsv +6 -0
- package/test.js +0 -1411
package/lib/cookie.js
CHANGED
|
@@ -1,28 +1,38 @@
|
|
|
1
|
-
|
|
2
|
-
* Copyright
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2015, 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.
|
|
9
14
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
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.
|
|
12
18
|
*
|
|
13
|
-
*
|
|
14
|
-
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
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.
|
|
20
30
|
*/
|
|
21
|
-
|
|
22
|
-
/*jshint regexp:false */
|
|
31
|
+
'use strict';
|
|
23
32
|
var net = require('net');
|
|
24
33
|
var urlParse = require('url').parse;
|
|
25
34
|
var pubsuffix = require('./pubsuffix');
|
|
35
|
+
var Store = require('./store').Store;
|
|
26
36
|
|
|
27
37
|
var punycode;
|
|
28
38
|
try {
|
|
@@ -43,7 +53,13 @@ var COOKIE_OCTETS = new RegExp('^'+COOKIE_OCTET.source+'$');
|
|
|
43
53
|
|
|
44
54
|
// The name/key cannot be empty but the value can (S5.2):
|
|
45
55
|
var COOKIE_PAIR_STRICT = new RegExp('^('+TOKEN.source+'+)=("?)('+COOKIE_OCTET.source+'*)\\2$');
|
|
46
|
-
|
|
56
|
+
|
|
57
|
+
// Double quotes are part of the value (see: S4.1.1).
|
|
58
|
+
// '\r', '\n' and '\0' should be treated as a terminator in the "relaxed" mode
|
|
59
|
+
// (see: https://github.com/ChromiumWebApps/chromium/blob/b3d3b4da8bb94c1b2e061600df106d590fda3620/net/cookies/parsed_cookie.cc#L60)
|
|
60
|
+
// '=' and ';' are attribute/values separators
|
|
61
|
+
// (see: https://github.com/ChromiumWebApps/chromium/blob/b3d3b4da8bb94c1b2e061600df106d590fda3620/net/cookies/parsed_cookie.cc#L64)
|
|
62
|
+
var COOKIE_PAIR = /^([^=;]+)\s*=\s*(("?)[^\n\r\0]*\3)/;
|
|
47
63
|
|
|
48
64
|
// RFC6265 S4.1.1 defines extension-av as 'any CHAR except CTLs or ";"'
|
|
49
65
|
// Note ';' is \x3B
|
|
@@ -54,23 +70,13 @@ var PATH_VALUE = NON_CTL_SEMICOLON;
|
|
|
54
70
|
// Used for checking whether or not there is a trailing semi-colon
|
|
55
71
|
var TRAILING_SEMICOLON = /;+$/;
|
|
56
72
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
var DAY_OF_MONTH = /^(0?[1-9]|[12][0-9]|3[01])$/;
|
|
73
|
+
var DAY_OF_MONTH = /^(\d{1,2})[^\d]*$/;
|
|
74
|
+
var TIME = /^(\d{1,2})[^\d]*:(\d{1,2})[^\d]*:(\d{1,2})[^\d]*$/;
|
|
75
|
+
var MONTH = /^(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)/i;
|
|
61
76
|
|
|
62
|
-
/* RFC6265 S5.1.1.5:
|
|
63
|
-
* [fail if]
|
|
64
|
-
* * the hour-value is greater than 23,
|
|
65
|
-
* * the minute-value is greater than 59, or
|
|
66
|
-
* * the second-value is greater than 59.
|
|
67
|
-
*/
|
|
68
|
-
var TIME = /(0?[0-9]|1[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])/;
|
|
69
|
-
var STRICT_TIME = /^(0?[0-9]|1[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$/;
|
|
70
|
-
|
|
71
|
-
var MONTH = /^(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)$/i;
|
|
72
77
|
var MONTH_TO_NUM = {
|
|
73
|
-
jan:0, feb:1, mar:2, apr:3, may:4, jun:5,
|
|
78
|
+
jan:0, feb:1, mar:2, apr:3, may:4, jun:5,
|
|
79
|
+
jul:6, aug:7, sep:8, oct:9, nov:10, dec:11
|
|
74
80
|
};
|
|
75
81
|
var NUM_TO_MONTH = [
|
|
76
82
|
'Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'
|
|
@@ -79,32 +85,44 @@ var NUM_TO_DAY = [
|
|
|
79
85
|
'Sun','Mon','Tue','Wed','Thu','Fri','Sat'
|
|
80
86
|
];
|
|
81
87
|
|
|
82
|
-
var YEAR = /^(
|
|
88
|
+
var YEAR = /^(\d{2}|\d{4})$/; // 2 to 4 digits
|
|
83
89
|
|
|
84
90
|
var MAX_TIME = 2147483647000; // 31-bit max
|
|
85
|
-
var MAX_DATE = new Date(CookieJar.MAX_TIME); // 31-bit max
|
|
86
91
|
var MIN_TIME = 0; // 31-bit min
|
|
87
|
-
|
|
92
|
+
|
|
93
|
+
var cookiesCreated = 0; // Number of cookies created in runtime
|
|
88
94
|
|
|
89
95
|
|
|
90
96
|
// RFC6265 S5.1.1 date parser:
|
|
91
97
|
function parseDate(str,strict) {
|
|
92
|
-
if (!str)
|
|
93
|
-
|
|
98
|
+
if (!str) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
94
101
|
|
|
95
102
|
/* RFC6265 S5.1.1:
|
|
96
103
|
* 2. Process each date-token sequentially in the order the date-tokens
|
|
97
104
|
* appear in the cookie-date
|
|
98
105
|
*/
|
|
99
106
|
var tokens = str.split(DATE_DELIM);
|
|
100
|
-
if (!tokens)
|
|
107
|
+
if (!tokens) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
var hour = null;
|
|
112
|
+
var minutes = null;
|
|
113
|
+
var seconds = null;
|
|
114
|
+
var day = null;
|
|
115
|
+
var month = null;
|
|
116
|
+
var year = null;
|
|
101
117
|
|
|
102
118
|
var date = new Date();
|
|
103
119
|
date.setMilliseconds(0);
|
|
104
120
|
|
|
105
121
|
for (var i=0; i<tokens.length; i++) {
|
|
106
122
|
var token = tokens[i].trim();
|
|
107
|
-
if (!token.length)
|
|
123
|
+
if (!token.length) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
108
126
|
|
|
109
127
|
var result;
|
|
110
128
|
|
|
@@ -114,13 +132,22 @@ function parseDate(str,strict) {
|
|
|
114
132
|
* the date-token, respectively. Skip the remaining sub-steps and continue
|
|
115
133
|
* to the next date-token.
|
|
116
134
|
*/
|
|
117
|
-
if (
|
|
118
|
-
result =
|
|
135
|
+
if (seconds === null) {
|
|
136
|
+
result = TIME.exec(token);
|
|
119
137
|
if (result) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
138
|
+
hour = parseInt(result[1], 10);
|
|
139
|
+
minutes = parseInt(result[2], 10);
|
|
140
|
+
seconds = parseInt(result[3], 10);
|
|
141
|
+
/* RFC6265 S5.1.1.5:
|
|
142
|
+
* [fail if]
|
|
143
|
+
* * the hour-value is greater than 23,
|
|
144
|
+
* * the minute-value is greater than 59, or
|
|
145
|
+
* * the second-value is greater than 59.
|
|
146
|
+
*/
|
|
147
|
+
if(hour > 23 || minutes > 59 || seconds > 59) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
124
151
|
continue;
|
|
125
152
|
}
|
|
126
153
|
}
|
|
@@ -130,11 +157,16 @@ function parseDate(str,strict) {
|
|
|
130
157
|
* the day-of-month-value to the number denoted by the date-token. Skip
|
|
131
158
|
* the remaining sub-steps and continue to the next date-token.
|
|
132
159
|
*/
|
|
133
|
-
if (
|
|
160
|
+
if (day === null) {
|
|
134
161
|
result = DAY_OF_MONTH.exec(token);
|
|
135
162
|
if (result) {
|
|
136
|
-
|
|
137
|
-
|
|
163
|
+
day = parseInt(result, 10);
|
|
164
|
+
/* RFC6265 S5.1.1.5:
|
|
165
|
+
* [fail if] the day-of-month-value is less than 1 or greater than 31
|
|
166
|
+
*/
|
|
167
|
+
if(day < 1 || day > 31) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
138
170
|
continue;
|
|
139
171
|
}
|
|
140
172
|
}
|
|
@@ -144,11 +176,10 @@ function parseDate(str,strict) {
|
|
|
144
176
|
* the month denoted by the date-token. Skip the remaining sub-steps and
|
|
145
177
|
* continue to the next date-token.
|
|
146
178
|
*/
|
|
147
|
-
if (
|
|
179
|
+
if (month === null) {
|
|
148
180
|
result = MONTH.exec(token);
|
|
149
181
|
if (result) {
|
|
150
|
-
|
|
151
|
-
date.setUTCMonth(MONTH_TO_NUM[result[1].toLowerCase()]);
|
|
182
|
+
month = MONTH_TO_NUM[result[1].toLowerCase()];
|
|
152
183
|
continue;
|
|
153
184
|
}
|
|
154
185
|
}
|
|
@@ -158,37 +189,36 @@ function parseDate(str,strict) {
|
|
|
158
189
|
* denoted by the date-token. Skip the remaining sub-steps and continue to
|
|
159
190
|
* the next date-token.
|
|
160
191
|
*/
|
|
161
|
-
if (
|
|
192
|
+
if (year === null) {
|
|
162
193
|
result = YEAR.exec(token);
|
|
163
194
|
if (result) {
|
|
164
|
-
|
|
195
|
+
year = parseInt(result[0], 10);
|
|
165
196
|
/* From S5.1.1:
|
|
166
197
|
* 3. If the year-value is greater than or equal to 70 and less
|
|
167
198
|
* than or equal to 99, increment the year-value by 1900.
|
|
168
199
|
* 4. If the year-value is greater than or equal to 0 and less
|
|
169
200
|
* than or equal to 69, increment the year-value by 2000.
|
|
170
201
|
*/
|
|
171
|
-
if (70 <= year && year <= 99)
|
|
202
|
+
if (70 <= year && year <= 99) {
|
|
172
203
|
year += 1900;
|
|
173
|
-
else if (0 <= year && year <= 69)
|
|
204
|
+
} else if (0 <= year && year <= 69) {
|
|
174
205
|
year += 2000;
|
|
206
|
+
}
|
|
175
207
|
|
|
176
|
-
if (year < 1601)
|
|
208
|
+
if (year < 1601) {
|
|
177
209
|
return; // 5. ... the year-value is less than 1601
|
|
178
|
-
|
|
179
|
-
found_year = true;
|
|
180
|
-
date.setUTCFullYear(year);
|
|
210
|
+
}
|
|
181
211
|
continue;
|
|
182
212
|
}
|
|
183
213
|
}
|
|
184
214
|
}
|
|
185
215
|
|
|
186
|
-
if (
|
|
216
|
+
if (seconds === null || day === null || month === null || year === null) {
|
|
187
217
|
return; // 5. ... at least one of the found-day-of-month, found-month, found-
|
|
188
218
|
// year, or found-time flags is not set,
|
|
189
219
|
}
|
|
190
220
|
|
|
191
|
-
return
|
|
221
|
+
return new Date(Date.UTC(year, month, day, hour, minutes, seconds));
|
|
192
222
|
}
|
|
193
223
|
|
|
194
224
|
function formatDate(date) {
|
|
@@ -203,19 +233,24 @@ function formatDate(date) {
|
|
|
203
233
|
|
|
204
234
|
// S5.1.2 Canonicalized Host Names
|
|
205
235
|
function canonicalDomain(str) {
|
|
206
|
-
if (str == null)
|
|
236
|
+
if (str == null) {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
207
239
|
str = str.trim().replace(/^\./,''); // S4.1.2.3 & S5.2.3: ignore leading .
|
|
208
240
|
|
|
209
241
|
// convert to IDN if any non-ASCII characters
|
|
210
|
-
if (punycode && /[^\u0001-\u007f]/.test(str))
|
|
242
|
+
if (punycode && /[^\u0001-\u007f]/.test(str)) {
|
|
211
243
|
str = punycode.toASCII(str);
|
|
244
|
+
}
|
|
212
245
|
|
|
213
246
|
return str.toLowerCase();
|
|
214
247
|
}
|
|
215
248
|
|
|
216
249
|
// S5.1.3 Domain Matching
|
|
217
250
|
function domainMatch(str, domStr, canonicalize) {
|
|
218
|
-
if (str == null || domStr == null)
|
|
251
|
+
if (str == null || domStr == null) {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
219
254
|
if (canonicalize !== false) {
|
|
220
255
|
str = canonicalDomain(str);
|
|
221
256
|
domStr = canonicalDomain(domStr);
|
|
@@ -226,25 +261,35 @@ function domainMatch(str, domStr, canonicalize) {
|
|
|
226
261
|
* domain string and the string will have been canonicalized to lower case at
|
|
227
262
|
* this point)"
|
|
228
263
|
*/
|
|
229
|
-
if (str == domStr)
|
|
264
|
+
if (str == domStr) {
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
230
267
|
|
|
231
268
|
/* "All of the following [three] conditions hold:" (order adjusted from the RFC) */
|
|
232
269
|
|
|
233
270
|
/* "* The string is a host name (i.e., not an IP address)." */
|
|
234
|
-
if (net.isIP(str))
|
|
271
|
+
if (net.isIP(str)) {
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
235
274
|
|
|
236
275
|
/* "* The domain string is a suffix of the string" */
|
|
237
276
|
var idx = str.indexOf(domStr);
|
|
238
|
-
if (idx <= 0)
|
|
277
|
+
if (idx <= 0) {
|
|
278
|
+
return false; // it's a non-match (-1) or prefix (0)
|
|
279
|
+
}
|
|
239
280
|
|
|
240
281
|
// e.g "a.b.c".indexOf("b.c") === 2
|
|
241
282
|
// 5 === 3+2
|
|
242
|
-
if (str.length !== domStr.length + idx) // it's not a suffix
|
|
283
|
+
if (str.length !== domStr.length + idx) { // it's not a suffix
|
|
243
284
|
return false;
|
|
285
|
+
}
|
|
244
286
|
|
|
245
287
|
/* "* The last character of the string that is not included in the domain
|
|
246
288
|
* string is a %x2E (".") character." */
|
|
247
|
-
if (str.substr(idx-1,1) !== '.')
|
|
289
|
+
if (str.substr(idx-1,1) !== '.') {
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
|
|
248
293
|
return true;
|
|
249
294
|
}
|
|
250
295
|
|
|
@@ -260,14 +305,20 @@ function domainMatch(str, domStr, canonicalize) {
|
|
|
260
305
|
function defaultPath(path) {
|
|
261
306
|
// "2. If the uri-path is empty or if the first character of the uri-path is not
|
|
262
307
|
// a %x2F ("/") character, output %x2F ("/") and skip the remaining steps.
|
|
263
|
-
if (!path || path.substr(0,1) !== "/")
|
|
308
|
+
if (!path || path.substr(0,1) !== "/") {
|
|
309
|
+
return "/";
|
|
310
|
+
}
|
|
264
311
|
|
|
265
312
|
// "3. If the uri-path contains no more than one %x2F ("/") character, output
|
|
266
313
|
// %x2F ("/") and skip the remaining step."
|
|
267
|
-
if (path === "/")
|
|
314
|
+
if (path === "/") {
|
|
315
|
+
return path;
|
|
316
|
+
}
|
|
268
317
|
|
|
269
318
|
var rightSlash = path.lastIndexOf("/");
|
|
270
|
-
if (rightSlash === 0)
|
|
319
|
+
if (rightSlash === 0) {
|
|
320
|
+
return "/";
|
|
321
|
+
}
|
|
271
322
|
|
|
272
323
|
// "4. Output the characters of the uri-path from the first character up to,
|
|
273
324
|
// but not including, the right-most %x2F ("/")."
|
|
@@ -280,21 +331,24 @@ function defaultPath(path) {
|
|
|
280
331
|
*/
|
|
281
332
|
function pathMatch(reqPath,cookiePath) {
|
|
282
333
|
// "o The cookie-path and the request-path are identical."
|
|
283
|
-
if (cookiePath === reqPath)
|
|
334
|
+
if (cookiePath === reqPath) {
|
|
284
335
|
return true;
|
|
336
|
+
}
|
|
285
337
|
|
|
286
338
|
var idx = reqPath.indexOf(cookiePath);
|
|
287
339
|
if (idx === 0) {
|
|
288
340
|
// "o The cookie-path is a prefix of the request-path, and the last
|
|
289
341
|
// character of the cookie-path is %x2F ("/")."
|
|
290
|
-
if (cookiePath.substr(-1) === "/")
|
|
342
|
+
if (cookiePath.substr(-1) === "/") {
|
|
291
343
|
return true;
|
|
344
|
+
}
|
|
292
345
|
|
|
293
346
|
// " o The cookie-path is a prefix of the request-path, and the first
|
|
294
347
|
// character of the request-path that is not included in the cookie- path
|
|
295
348
|
// is a %x2F ("/") character."
|
|
296
|
-
if (reqPath.substr(cookiePath.length,1) === "/")
|
|
349
|
+
if (reqPath.substr(cookiePath.length,1) === "/") {
|
|
297
350
|
return true;
|
|
351
|
+
}
|
|
298
352
|
}
|
|
299
353
|
|
|
300
354
|
return false;
|
|
@@ -307,7 +361,9 @@ function parse(str, strict) {
|
|
|
307
361
|
// If we are not in strict mode we remove the trailing semi-colons.
|
|
308
362
|
var semiColonCheck = TRAILING_SEMICOLON.exec(str);
|
|
309
363
|
if (semiColonCheck) {
|
|
310
|
-
if (strict)
|
|
364
|
+
if (strict) {
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
311
367
|
str = str.slice(0, semiColonCheck.index);
|
|
312
368
|
}
|
|
313
369
|
|
|
@@ -318,13 +374,19 @@ function parse(str, strict) {
|
|
|
318
374
|
|
|
319
375
|
// Rx satisfies the "the name string is empty" and "lacks a %x3D ("=")"
|
|
320
376
|
// constraints as well as trimming any whitespace.
|
|
321
|
-
if (!result)
|
|
377
|
+
if (!result) {
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
322
380
|
|
|
323
381
|
var c = new Cookie();
|
|
324
382
|
c.key = result[1]; // the regexp should trim() already
|
|
325
|
-
c.value = result[3]; // [2] is quotes or empty-string
|
|
383
|
+
c.value = strict ? result[3] : result[2]; // [2] is quotes or empty-string in strict mode
|
|
384
|
+
c.key = c.key.trim();
|
|
385
|
+
c.value = c.value.trim();
|
|
326
386
|
|
|
327
|
-
if (firstSemi === -1)
|
|
387
|
+
if (firstSemi === -1) {
|
|
388
|
+
return c;
|
|
389
|
+
}
|
|
328
390
|
|
|
329
391
|
// S5.2.3 "unparsed-attributes consist of the remainder of the set-cookie-string
|
|
330
392
|
// (including the %x3B (";") in question)." plus later on in the same section
|
|
@@ -333,7 +395,9 @@ function parse(str, strict) {
|
|
|
333
395
|
|
|
334
396
|
// "If the unparsed-attributes string is empty, skip the rest of these
|
|
335
397
|
// steps."
|
|
336
|
-
if (unparsed.length === 0)
|
|
398
|
+
if (unparsed.length === 0) {
|
|
399
|
+
return c;
|
|
400
|
+
}
|
|
337
401
|
|
|
338
402
|
/*
|
|
339
403
|
* S5.2 says that when looping over the items "[p]rocess the attribute-name
|
|
@@ -347,7 +411,9 @@ function parse(str, strict) {
|
|
|
347
411
|
while (cookie_avs.length) {
|
|
348
412
|
var av = cookie_avs.shift();
|
|
349
413
|
|
|
350
|
-
if (strict && !EXTENSION_AV.test(av))
|
|
414
|
+
if (strict && !EXTENSION_AV.test(av)) {
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
351
417
|
|
|
352
418
|
var av_sep = av.indexOf('=');
|
|
353
419
|
var av_key, av_value;
|
|
@@ -360,28 +426,32 @@ function parse(str, strict) {
|
|
|
360
426
|
}
|
|
361
427
|
|
|
362
428
|
av_key = av_key.trim().toLowerCase();
|
|
363
|
-
if (av_value)
|
|
429
|
+
if (av_value) {
|
|
430
|
+
av_value = av_value.trim();
|
|
431
|
+
}
|
|
364
432
|
|
|
365
433
|
switch(av_key) {
|
|
366
434
|
case 'expires': // S5.2.1
|
|
367
|
-
if (!av_value) {
|
|
435
|
+
if (!av_value) {if(strict){return;}else{break;} }
|
|
368
436
|
var exp = parseDate(av_value,strict);
|
|
369
437
|
// "If the attribute-value failed to parse as a cookie date, ignore the
|
|
370
438
|
// cookie-av."
|
|
371
|
-
if (exp == null) { if(strict){return}else{break;} }
|
|
439
|
+
if (exp == null) { if(strict){return;}else{break;} }
|
|
372
440
|
c.expires = exp;
|
|
373
441
|
// over and underflow not realistically a concern: V8's getTime() seems to
|
|
374
442
|
// store something larger than a 32-bit time_t (even with 32-bit node)
|
|
375
443
|
break;
|
|
376
444
|
|
|
377
445
|
case 'max-age': // S5.2.2
|
|
378
|
-
if (!av_value) { if(strict){return}else{break;} }
|
|
446
|
+
if (!av_value) { if(strict){return;}else{break;} }
|
|
379
447
|
// "If the first character of the attribute-value is not a DIGIT or a "-"
|
|
380
448
|
// character ...[or]... If the remainder of attribute-value contains a
|
|
381
449
|
// non-DIGIT character, ignore the cookie-av."
|
|
382
|
-
if (!/^-?[0-9]+$/.test(av_value)) { if(strict){return}else{break;} }
|
|
450
|
+
if (!/^-?[0-9]+$/.test(av_value)) { if(strict){return;}else{break;} }
|
|
383
451
|
var delta = parseInt(av_value,10);
|
|
384
|
-
if (strict && delta <= 0)
|
|
452
|
+
if (strict && delta <= 0) {
|
|
453
|
+
return; // S4.1.1
|
|
454
|
+
}
|
|
385
455
|
// "If delta-seconds is less than or equal to zero (0), let expiry-time
|
|
386
456
|
// be the earliest representable date and time."
|
|
387
457
|
c.setMaxAge(delta);
|
|
@@ -390,11 +460,11 @@ function parse(str, strict) {
|
|
|
390
460
|
case 'domain': // S5.2.3
|
|
391
461
|
// "If the attribute-value is empty, the behavior is undefined. However,
|
|
392
462
|
// the user agent SHOULD ignore the cookie-av entirely."
|
|
393
|
-
if (!av_value) { if(strict){return}else{break;} }
|
|
463
|
+
if (!av_value) { if(strict){return;}else{break;} }
|
|
394
464
|
// S5.2.3 "Let cookie-domain be the attribute-value without the leading %x2E
|
|
395
465
|
// (".") character."
|
|
396
466
|
var domain = av_value.trim().replace(/^\./,'');
|
|
397
|
-
if (!domain) { if(strict){return}else{break;} } // see "is empty" above
|
|
467
|
+
if (!domain) { if(strict){return;}else{break;} } // see "is empty" above
|
|
398
468
|
// "Convert the cookie-domain to lower case."
|
|
399
469
|
c.domain = domain.toLowerCase();
|
|
400
470
|
break;
|
|
@@ -410,7 +480,10 @@ function parse(str, strict) {
|
|
|
410
480
|
* We'll represent the default-path as null since it depends on the
|
|
411
481
|
* context of the parsing.
|
|
412
482
|
*/
|
|
413
|
-
if (!av_value || av_value.substr(0,1) != "/") {
|
|
483
|
+
if (!av_value || av_value.substr(0,1) != "/") {
|
|
484
|
+
c.path = null;
|
|
485
|
+
if(strict){return;}else{break;}
|
|
486
|
+
}
|
|
414
487
|
c.path = av_value;
|
|
415
488
|
break;
|
|
416
489
|
|
|
@@ -420,12 +493,12 @@ function parse(str, strict) {
|
|
|
420
493
|
* the user agent MUST append an attribute to the cookie-attribute-list
|
|
421
494
|
* with an attribute-name of Secure and an empty attribute-value."
|
|
422
495
|
*/
|
|
423
|
-
if (av_value != null) { if(strict){return} }
|
|
496
|
+
if (av_value != null) { if(strict){return;} }
|
|
424
497
|
c.secure = true;
|
|
425
498
|
break;
|
|
426
499
|
|
|
427
500
|
case 'httponly': // S5.2.6 -- effectively the same as 'secure'
|
|
428
|
-
if (av_value != null) { if(strict){return} }
|
|
501
|
+
if (av_value != null) { if(strict){return;} }
|
|
429
502
|
c.httpOnly = true;
|
|
430
503
|
break;
|
|
431
504
|
|
|
@@ -438,11 +511,17 @@ function parse(str, strict) {
|
|
|
438
511
|
|
|
439
512
|
// ensure a default date for sorting:
|
|
440
513
|
c.creation = new Date();
|
|
514
|
+
//NOTE: add runtime index for the cookieCompare() to resolve the situation when Date's precision is not enough .
|
|
515
|
+
//Store initial UTC time as well, so we will be able to determine if we need to fallback to the Date object.
|
|
516
|
+
c._creationRuntimeIdx = ++cookiesCreated;
|
|
517
|
+
c._initialCreationTime = c.creation.getTime();
|
|
441
518
|
return c;
|
|
442
519
|
}
|
|
443
520
|
|
|
444
521
|
function fromJSON(str) {
|
|
445
|
-
if (!str)
|
|
522
|
+
if (!str) {
|
|
523
|
+
return null;
|
|
524
|
+
}
|
|
446
525
|
|
|
447
526
|
var obj;
|
|
448
527
|
try {
|
|
@@ -454,7 +533,9 @@ function fromJSON(str) {
|
|
|
454
533
|
var c = new Cookie();
|
|
455
534
|
for (var i=0; i<numCookieProperties; i++) {
|
|
456
535
|
var prop = cookieProperties[i];
|
|
457
|
-
if (obj[prop] == null)
|
|
536
|
+
if (obj[prop] == null) {
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
458
539
|
if (prop === 'expires' ||
|
|
459
540
|
prop === 'creation' ||
|
|
460
541
|
prop === 'lastAccessed')
|
|
@@ -484,18 +565,33 @@ function fromJSON(str) {
|
|
|
484
565
|
function cookieCompare(a,b) {
|
|
485
566
|
// descending for length: b CMP a
|
|
486
567
|
var deltaLen = (b.path ? b.path.length : 0) - (a.path ? a.path.length : 0);
|
|
487
|
-
if (deltaLen !== 0)
|
|
568
|
+
if (deltaLen !== 0) {
|
|
569
|
+
return deltaLen;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
var aTime = a.creation ? a.creation.getTime() : MAX_TIME;
|
|
573
|
+
var bTime = b.creation ? b.creation.getTime() : MAX_TIME;
|
|
574
|
+
|
|
575
|
+
// NOTE: if creation dates are equal and they were not modified from the outside,
|
|
576
|
+
// then use _creationRuntimeIdx for the comparison.
|
|
577
|
+
if(aTime === bTime && aTime === a._initialCreationTime && bTime === b._initialCreationTime) {
|
|
578
|
+
return a._creationRuntimeIdx - b._creationRuntimeIdx;
|
|
579
|
+
}
|
|
580
|
+
|
|
488
581
|
// ascending for time: a CMP b
|
|
489
|
-
return
|
|
490
|
-
(b.creation ? b.creation.getTime() : MAX_TIME);
|
|
582
|
+
return aTime - bTime;
|
|
491
583
|
}
|
|
492
584
|
|
|
493
585
|
// Gives the permutation of all possible domainMatch()es of a given domain. The
|
|
494
586
|
// array is in shortest-to-longest order. Handy for indexing.
|
|
495
587
|
function permuteDomain(domain) {
|
|
496
588
|
var pubSuf = pubsuffix.getPublicSuffix(domain);
|
|
497
|
-
if (!pubSuf)
|
|
498
|
-
|
|
589
|
+
if (!pubSuf) {
|
|
590
|
+
return null;
|
|
591
|
+
}
|
|
592
|
+
if (pubSuf == domain) {
|
|
593
|
+
return [domain];
|
|
594
|
+
}
|
|
499
595
|
|
|
500
596
|
var prefix = domain.slice(0,-(pubSuf.length+1)); // ".example.com"
|
|
501
597
|
var parts = prefix.split('.').reverse();
|
|
@@ -511,14 +607,18 @@ function permuteDomain(domain) {
|
|
|
511
607
|
// Gives the permutation of all possible pathMatch()es of a given path. The
|
|
512
608
|
// array is in longest-to-shortest order. Handy for indexing.
|
|
513
609
|
function permutePath(path) {
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
610
|
+
if (path === '/') {
|
|
611
|
+
return ['/'];
|
|
612
|
+
}
|
|
613
|
+
if (path.lastIndexOf('/') === path.length-1) {
|
|
517
614
|
path = path.substr(0,path.length-1);
|
|
615
|
+
}
|
|
518
616
|
var permutations = [path];
|
|
519
617
|
while (path.length > 1) {
|
|
520
618
|
var lindex = path.lastIndexOf('/');
|
|
521
|
-
if (lindex === 0)
|
|
619
|
+
if (lindex === 0) {
|
|
620
|
+
break;
|
|
621
|
+
}
|
|
522
622
|
path = path.substr(0,lindex);
|
|
523
623
|
permutations.push(path);
|
|
524
624
|
}
|
|
@@ -526,9 +626,14 @@ function permutePath(path) {
|
|
|
526
626
|
return permutations;
|
|
527
627
|
}
|
|
528
628
|
|
|
629
|
+
function getCookieContext(url) {
|
|
630
|
+
return (url instanceof Object) ? url : urlParse(decodeURI(url));
|
|
631
|
+
}
|
|
529
632
|
|
|
530
633
|
function Cookie (opts) {
|
|
531
|
-
if (typeof opts !== "object")
|
|
634
|
+
if (typeof opts !== "object") {
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
532
637
|
Object.keys(opts).forEach(function (key) {
|
|
533
638
|
if (Cookie.prototype.hasOwnProperty(key)) {
|
|
534
639
|
this[key] = opts[key] || Cookie.prototype[key];
|
|
@@ -555,10 +660,14 @@ Cookie.prototype.extensions = null;
|
|
|
555
660
|
Cookie.prototype.hostOnly = null; // boolean when set
|
|
556
661
|
Cookie.prototype.pathIsDefault = null; // boolean when set
|
|
557
662
|
Cookie.prototype.creation = null; // Date when set; defaulted by Cookie.parse
|
|
663
|
+
Cookie.prototype._initialCreationTime = null; // Used to determine if cookie.creation was modified
|
|
664
|
+
Cookie.prototype._creationRuntimeIdx = null; // Runtime index of the created cookie, used in cookieCompare()
|
|
558
665
|
Cookie.prototype.lastAccessed = null; // Date when set
|
|
559
666
|
|
|
560
667
|
var cookieProperties = Object.freeze(Object.keys(Cookie.prototype).map(function(p) {
|
|
561
|
-
if (p instanceof Function)
|
|
668
|
+
if (p instanceof Function) {
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
562
671
|
return p;
|
|
563
672
|
}));
|
|
564
673
|
var numCookieProperties = cookieProperties.length;
|
|
@@ -573,42 +682,54 @@ Cookie.prototype.inspect = function inspect() {
|
|
|
573
682
|
};
|
|
574
683
|
|
|
575
684
|
Cookie.prototype.validate = function validate() {
|
|
576
|
-
if (!COOKIE_OCTETS.test(this.value))
|
|
685
|
+
if (!COOKIE_OCTETS.test(this.value)) {
|
|
577
686
|
return false;
|
|
578
|
-
|
|
687
|
+
}
|
|
688
|
+
if (this.expires != Infinity && !(this.expires instanceof Date) && !parseDate(this.expires,true)) {
|
|
579
689
|
return false;
|
|
580
|
-
|
|
690
|
+
}
|
|
691
|
+
if (this.maxAge != null && this.maxAge <= 0) {
|
|
581
692
|
return false; // "Max-Age=" non-zero-digit *DIGIT
|
|
582
|
-
|
|
693
|
+
}
|
|
694
|
+
if (this.path != null && !PATH_VALUE.test(this.path)) {
|
|
583
695
|
return false;
|
|
696
|
+
}
|
|
584
697
|
|
|
585
698
|
var cdomain = this.cdomain();
|
|
586
699
|
if (cdomain) {
|
|
587
|
-
if (cdomain.match(/\.$/))
|
|
700
|
+
if (cdomain.match(/\.$/)) {
|
|
588
701
|
return false; // S4.1.2.3 suggests that this is bad. domainMatch() tests confirm this
|
|
702
|
+
}
|
|
589
703
|
var suffix = pubsuffix.getPublicSuffix(cdomain);
|
|
590
|
-
if (suffix == null) // it's a public suffix
|
|
704
|
+
if (suffix == null) { // it's a public suffix
|
|
591
705
|
return false;
|
|
706
|
+
}
|
|
592
707
|
}
|
|
593
708
|
return true;
|
|
594
709
|
};
|
|
595
710
|
|
|
596
711
|
Cookie.prototype.setExpires = function setExpires(exp) {
|
|
597
|
-
if (exp instanceof Date)
|
|
598
|
-
|
|
712
|
+
if (exp instanceof Date) {
|
|
713
|
+
this.expires = exp;
|
|
714
|
+
} else {
|
|
715
|
+
this.expires = parseDate(exp) || "Infinity";
|
|
716
|
+
}
|
|
599
717
|
};
|
|
600
718
|
|
|
601
719
|
Cookie.prototype.setMaxAge = function setMaxAge(age) {
|
|
602
|
-
if (age === Infinity || age === -Infinity)
|
|
720
|
+
if (age === Infinity || age === -Infinity) {
|
|
603
721
|
this.maxAge = age.toString(); // so JSON.stringify() works
|
|
604
|
-
else
|
|
722
|
+
} else {
|
|
605
723
|
this.maxAge = age;
|
|
724
|
+
}
|
|
606
725
|
};
|
|
607
726
|
|
|
608
727
|
// gives Cookie header format
|
|
609
728
|
Cookie.prototype.cookieString = function cookieString() {
|
|
610
729
|
var val = this.value;
|
|
611
|
-
if (val == null)
|
|
730
|
+
if (val == null) {
|
|
731
|
+
val = '';
|
|
732
|
+
}
|
|
612
733
|
return this.key+'='+val;
|
|
613
734
|
};
|
|
614
735
|
|
|
@@ -617,22 +738,30 @@ Cookie.prototype.toString = function toString() {
|
|
|
617
738
|
var str = this.cookieString();
|
|
618
739
|
|
|
619
740
|
if (this.expires != Infinity) {
|
|
620
|
-
if (this.expires instanceof Date)
|
|
741
|
+
if (this.expires instanceof Date) {
|
|
621
742
|
str += '; Expires='+formatDate(this.expires);
|
|
622
|
-
else
|
|
743
|
+
} else {
|
|
623
744
|
str += '; Expires='+this.expires;
|
|
745
|
+
}
|
|
624
746
|
}
|
|
625
747
|
|
|
626
|
-
if (this.maxAge != null && this.maxAge != Infinity)
|
|
748
|
+
if (this.maxAge != null && this.maxAge != Infinity) {
|
|
627
749
|
str += '; Max-Age='+this.maxAge;
|
|
750
|
+
}
|
|
628
751
|
|
|
629
|
-
if (this.domain && !this.hostOnly)
|
|
752
|
+
if (this.domain && !this.hostOnly) {
|
|
630
753
|
str += '; Domain='+this.domain;
|
|
631
|
-
|
|
754
|
+
}
|
|
755
|
+
if (this.path) {
|
|
632
756
|
str += '; Path='+this.path;
|
|
757
|
+
}
|
|
633
758
|
|
|
634
|
-
if (this.secure)
|
|
635
|
-
|
|
759
|
+
if (this.secure) {
|
|
760
|
+
str += '; Secure';
|
|
761
|
+
}
|
|
762
|
+
if (this.httpOnly) {
|
|
763
|
+
str += '; HttpOnly';
|
|
764
|
+
}
|
|
636
765
|
if (this.extensions) {
|
|
637
766
|
this.extensions.forEach(function(ext) {
|
|
638
767
|
str += '; '+ext;
|
|
@@ -662,8 +791,9 @@ Cookie.prototype.TTL = function TTL(now) {
|
|
|
662
791
|
expires = parseDate(expires) || Infinity;
|
|
663
792
|
}
|
|
664
793
|
|
|
665
|
-
if (expires == Infinity)
|
|
794
|
+
if (expires == Infinity) {
|
|
666
795
|
return Infinity;
|
|
796
|
+
}
|
|
667
797
|
|
|
668
798
|
return expires.getTime() - (now || Date.now());
|
|
669
799
|
}
|
|
@@ -680,7 +810,9 @@ Cookie.prototype.expiryTime = function expiryTime(now) {
|
|
|
680
810
|
return relativeTo.getTime() + age;
|
|
681
811
|
}
|
|
682
812
|
|
|
683
|
-
if (this.expires == Infinity)
|
|
813
|
+
if (this.expires == Infinity) {
|
|
814
|
+
return Infinity;
|
|
815
|
+
}
|
|
684
816
|
return this.expires.getTime();
|
|
685
817
|
};
|
|
686
818
|
|
|
@@ -688,9 +820,13 @@ Cookie.prototype.expiryTime = function expiryTime(now) {
|
|
|
688
820
|
// elsewhere), except it returns a Date
|
|
689
821
|
Cookie.prototype.expiryDate = function expiryDate(now) {
|
|
690
822
|
var millisec = this.expiryTime(now);
|
|
691
|
-
if (millisec == Infinity)
|
|
692
|
-
|
|
693
|
-
else
|
|
823
|
+
if (millisec == Infinity) {
|
|
824
|
+
return new Date(MAX_TIME);
|
|
825
|
+
} else if (millisec == -Infinity) {
|
|
826
|
+
return new Date(MIN_TIME);
|
|
827
|
+
} else {
|
|
828
|
+
return new Date(millisec);
|
|
829
|
+
}
|
|
694
830
|
};
|
|
695
831
|
|
|
696
832
|
// This replaces the "persistent-flag" parts of S5.3 step 3
|
|
@@ -701,14 +837,19 @@ Cookie.prototype.isPersistent = function isPersistent() {
|
|
|
701
837
|
// Mostly S5.1.2 and S5.2.3:
|
|
702
838
|
Cookie.prototype.cdomain =
|
|
703
839
|
Cookie.prototype.canonicalizedDomain = function canonicalizedDomain() {
|
|
704
|
-
if (this.domain == null)
|
|
840
|
+
if (this.domain == null) {
|
|
841
|
+
return null;
|
|
842
|
+
}
|
|
705
843
|
return canonicalDomain(this.domain);
|
|
706
844
|
};
|
|
707
845
|
|
|
708
846
|
|
|
709
847
|
var memstore;
|
|
710
848
|
function CookieJar(store, rejectPublicSuffixes) {
|
|
711
|
-
if (rejectPublicSuffixes != null)
|
|
849
|
+
if (rejectPublicSuffixes != null) {
|
|
850
|
+
this.rejectPublicSuffixes = rejectPublicSuffixes;
|
|
851
|
+
}
|
|
852
|
+
|
|
712
853
|
if (!store) {
|
|
713
854
|
memstore = memstore || require('./memstore');
|
|
714
855
|
store = new memstore.MemoryCookieStore();
|
|
@@ -717,10 +858,12 @@ function CookieJar(store, rejectPublicSuffixes) {
|
|
|
717
858
|
}
|
|
718
859
|
CookieJar.prototype.store = null;
|
|
719
860
|
CookieJar.prototype.rejectPublicSuffixes = true;
|
|
861
|
+
var CAN_BE_SYNC = [];
|
|
720
862
|
|
|
721
|
-
|
|
863
|
+
CAN_BE_SYNC.push('setCookie');
|
|
864
|
+
CookieJar.prototype.setCookie = function(cookie, url, options, cb) {
|
|
722
865
|
var err;
|
|
723
|
-
var context = (url
|
|
866
|
+
var context = getCookieContext(url);
|
|
724
867
|
if (options instanceof Function) {
|
|
725
868
|
cb = options;
|
|
726
869
|
options = {};
|
|
@@ -729,8 +872,9 @@ CookieJar.prototype.setCookie = function setCookie(cookie, url, options, cb) {
|
|
|
729
872
|
var host = canonicalDomain(context.hostname);
|
|
730
873
|
|
|
731
874
|
// S5.3 step 1
|
|
732
|
-
if (!(cookie instanceof Cookie))
|
|
875
|
+
if (!(cookie instanceof Cookie)) {
|
|
733
876
|
cookie = Cookie.parse(cookie, options.strict === true);
|
|
877
|
+
}
|
|
734
878
|
if (!cookie) {
|
|
735
879
|
err = new Error("Cookie failed to parse");
|
|
736
880
|
return cb(options.ignoreError ? null : err);
|
|
@@ -759,22 +903,21 @@ CookieJar.prototype.setCookie = function setCookie(cookie, url, options, cb) {
|
|
|
759
903
|
return cb(options.ignoreError ? null : err);
|
|
760
904
|
}
|
|
761
905
|
|
|
762
|
-
if (cookie.hostOnly == null) // don't reset if already set
|
|
906
|
+
if (cookie.hostOnly == null) { // don't reset if already set
|
|
763
907
|
cookie.hostOnly = false;
|
|
908
|
+
}
|
|
764
909
|
|
|
765
910
|
} else {
|
|
766
911
|
cookie.hostOnly = true;
|
|
767
912
|
cookie.domain = host;
|
|
768
913
|
}
|
|
769
914
|
|
|
770
|
-
//
|
|
771
|
-
//
|
|
772
|
-
|
|
915
|
+
//S5.2.4 If the attribute-value is empty or if the first character of the
|
|
916
|
+
//attribute-value is not %x2F ("/"):
|
|
917
|
+
//Let cookie-path be the default-path.
|
|
918
|
+
if (!cookie.path || cookie.path[0] !== '/') {
|
|
773
919
|
cookie.path = defaultPath(context.pathname);
|
|
774
920
|
cookie.pathIsDefault = true;
|
|
775
|
-
} else {
|
|
776
|
-
if (cookie.path.length > 1 && cookie.path.substr(-1) == '/')
|
|
777
|
-
cookie.path = cookie.path.slice(0,-1);
|
|
778
921
|
}
|
|
779
922
|
|
|
780
923
|
// S5.3 step 8: NOOP; secure attribute
|
|
@@ -789,17 +932,22 @@ CookieJar.prototype.setCookie = function setCookie(cookie, url, options, cb) {
|
|
|
789
932
|
var store = this.store;
|
|
790
933
|
|
|
791
934
|
if (!store.updateCookie) {
|
|
792
|
-
store.updateCookie = function
|
|
935
|
+
store.updateCookie = function(oldCookie, newCookie, cb) {
|
|
793
936
|
this.putCookie(newCookie, cb);
|
|
794
937
|
};
|
|
795
938
|
}
|
|
796
939
|
|
|
797
|
-
|
|
798
|
-
if (err)
|
|
940
|
+
function withCookie(err, oldCookie) {
|
|
941
|
+
if (err) {
|
|
942
|
+
return cb(err);
|
|
943
|
+
}
|
|
799
944
|
|
|
800
945
|
var next = function(err) {
|
|
801
|
-
if (err)
|
|
802
|
-
|
|
946
|
+
if (err) {
|
|
947
|
+
return cb(err);
|
|
948
|
+
} else {
|
|
949
|
+
cb(null, cookie);
|
|
950
|
+
}
|
|
803
951
|
};
|
|
804
952
|
|
|
805
953
|
if (oldCookie) {
|
|
@@ -818,12 +966,15 @@ CookieJar.prototype.setCookie = function setCookie(cookie, url, options, cb) {
|
|
|
818
966
|
cookie.creation = cookie.lastAccessed = now;
|
|
819
967
|
store.putCookie(cookie, next); // step 12
|
|
820
968
|
}
|
|
821
|
-
}
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
store.findCookie(cookie.domain, cookie.path, cookie.key, withCookie);
|
|
822
972
|
};
|
|
823
973
|
|
|
824
974
|
// RFC6365 S5.4
|
|
825
|
-
|
|
826
|
-
|
|
975
|
+
CAN_BE_SYNC.push('getCookies');
|
|
976
|
+
CookieJar.prototype.getCookies = function(url, options, cb) {
|
|
977
|
+
var context = getCookieContext(url);
|
|
827
978
|
if (options instanceof Function) {
|
|
828
979
|
cb = options;
|
|
829
980
|
options = {};
|
|
@@ -840,7 +991,9 @@ CookieJar.prototype.getCookies = function getCookies(url, options, cb) {
|
|
|
840
991
|
}
|
|
841
992
|
|
|
842
993
|
var http = options.http;
|
|
843
|
-
if (http == null)
|
|
994
|
+
if (http == null) {
|
|
995
|
+
http = true;
|
|
996
|
+
}
|
|
844
997
|
|
|
845
998
|
var now = options.now || Date.now();
|
|
846
999
|
var expireCheck = options.expire !== false;
|
|
@@ -855,24 +1008,31 @@ CookieJar.prototype.getCookies = function getCookies(url, options, cb) {
|
|
|
855
1008
|
// The cookie's host-only-flag is false and the canonicalized
|
|
856
1009
|
// request-host domain-matches the cookie's domain."
|
|
857
1010
|
if (c.hostOnly) {
|
|
858
|
-
if (c.domain != host)
|
|
1011
|
+
if (c.domain != host) {
|
|
1012
|
+
return false;
|
|
1013
|
+
}
|
|
859
1014
|
} else {
|
|
860
|
-
if (!domainMatch(host, c.domain, false))
|
|
1015
|
+
if (!domainMatch(host, c.domain, false)) {
|
|
1016
|
+
return false;
|
|
1017
|
+
}
|
|
861
1018
|
}
|
|
862
1019
|
|
|
863
1020
|
// "The request-uri's path path-matches the cookie's path."
|
|
864
|
-
if (!allPaths && !pathMatch(path, c.path))
|
|
1021
|
+
if (!allPaths && !pathMatch(path, c.path)) {
|
|
865
1022
|
return false;
|
|
1023
|
+
}
|
|
866
1024
|
|
|
867
1025
|
// "If the cookie's secure-only-flag is true, then the request-uri's
|
|
868
1026
|
// scheme must denote a "secure" protocol"
|
|
869
|
-
if (c.secure && !secure)
|
|
1027
|
+
if (c.secure && !secure) {
|
|
870
1028
|
return false;
|
|
1029
|
+
}
|
|
871
1030
|
|
|
872
1031
|
// "If the cookie's http-only-flag is true, then exclude the cookie if the
|
|
873
1032
|
// cookie-string is being generated for a "non-HTTP" API"
|
|
874
|
-
if (c.httpOnly && !http)
|
|
1033
|
+
if (c.httpOnly && !http) {
|
|
875
1034
|
return false;
|
|
1035
|
+
}
|
|
876
1036
|
|
|
877
1037
|
// deferred from S5.3
|
|
878
1038
|
// non-RFC: allow retention of expired cookies by choice
|
|
@@ -885,50 +1045,96 @@ CookieJar.prototype.getCookies = function getCookies(url, options, cb) {
|
|
|
885
1045
|
}
|
|
886
1046
|
|
|
887
1047
|
store.findCookies(host, allPaths ? null : path, function(err,cookies) {
|
|
888
|
-
if (err)
|
|
1048
|
+
if (err) {
|
|
1049
|
+
return cb(err);
|
|
1050
|
+
}
|
|
889
1051
|
|
|
890
1052
|
cookies = cookies.filter(matchingCookie);
|
|
891
1053
|
|
|
892
1054
|
// sorting of S5.4 part 2
|
|
893
|
-
if (options.sort !== false)
|
|
1055
|
+
if (options.sort !== false) {
|
|
894
1056
|
cookies = cookies.sort(cookieCompare);
|
|
1057
|
+
}
|
|
895
1058
|
|
|
896
1059
|
// S5.4 part 3
|
|
897
1060
|
var now = new Date();
|
|
898
|
-
cookies.forEach(function(c) {
|
|
1061
|
+
cookies.forEach(function(c) {
|
|
1062
|
+
c.lastAccessed = now;
|
|
1063
|
+
});
|
|
899
1064
|
// TODO persist lastAccessed
|
|
900
1065
|
|
|
901
1066
|
cb(null,cookies);
|
|
902
1067
|
});
|
|
903
1068
|
};
|
|
904
1069
|
|
|
1070
|
+
CAN_BE_SYNC.push('getCookieString');
|
|
905
1071
|
CookieJar.prototype.getCookieString = function(/*..., cb*/) {
|
|
906
1072
|
var args = Array.prototype.slice.call(arguments,0);
|
|
907
1073
|
var cb = args.pop();
|
|
908
1074
|
var next = function(err,cookies) {
|
|
909
|
-
if (err)
|
|
910
|
-
|
|
1075
|
+
if (err) {
|
|
1076
|
+
cb(err);
|
|
1077
|
+
} else {
|
|
1078
|
+
cb(null, cookies
|
|
1079
|
+
.sort(cookieCompare)
|
|
1080
|
+
.map(function(c){
|
|
1081
|
+
return c.cookieString();
|
|
1082
|
+
})
|
|
1083
|
+
.join('; '));
|
|
1084
|
+
}
|
|
911
1085
|
};
|
|
912
1086
|
args.push(next);
|
|
913
1087
|
this.getCookies.apply(this,args);
|
|
914
1088
|
};
|
|
915
1089
|
|
|
1090
|
+
CAN_BE_SYNC.push('getSetCookieStrings');
|
|
916
1091
|
CookieJar.prototype.getSetCookieStrings = function(/*..., cb*/) {
|
|
917
1092
|
var args = Array.prototype.slice.call(arguments,0);
|
|
918
1093
|
var cb = args.pop();
|
|
919
1094
|
var next = function(err,cookies) {
|
|
920
|
-
if (err)
|
|
921
|
-
|
|
1095
|
+
if (err) {
|
|
1096
|
+
cb(err);
|
|
1097
|
+
} else {
|
|
1098
|
+
cb(null, cookies.map(function(c){
|
|
1099
|
+
return c.toString();
|
|
1100
|
+
}));
|
|
1101
|
+
}
|
|
922
1102
|
};
|
|
923
1103
|
args.push(next);
|
|
924
1104
|
this.getCookies.apply(this,args);
|
|
925
1105
|
};
|
|
926
1106
|
|
|
1107
|
+
// Use a closure to provide a true imperative API for synchronous stores.
|
|
1108
|
+
function syncWrap(method) {
|
|
1109
|
+
return function() {
|
|
1110
|
+
if (!this.store.synchronous) {
|
|
1111
|
+
throw new Error('CookieJar store is not synchronous; use async API instead.');
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
var args = Array.prototype.slice.call(arguments);
|
|
1115
|
+
var syncErr, syncResult;
|
|
1116
|
+
args.push(function syncCb(err, result) {
|
|
1117
|
+
syncErr = err;
|
|
1118
|
+
syncResult = result;
|
|
1119
|
+
});
|
|
1120
|
+
this[method].apply(this, args);
|
|
1121
|
+
|
|
1122
|
+
if (syncErr) {
|
|
1123
|
+
throw syncErr;
|
|
1124
|
+
}
|
|
1125
|
+
return syncResult;
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
927
1128
|
|
|
1129
|
+
// wrap all declared CAN_BE_SYNC methods in the sync wrapper
|
|
1130
|
+
CAN_BE_SYNC.forEach(function(method) {
|
|
1131
|
+
CookieJar.prototype[method+'Sync'] = syncWrap(method);
|
|
1132
|
+
});
|
|
928
1133
|
|
|
929
1134
|
module.exports = {
|
|
930
1135
|
CookieJar: CookieJar,
|
|
931
1136
|
Cookie: Cookie,
|
|
1137
|
+
Store: Store,
|
|
932
1138
|
parseDate: parseDate,
|
|
933
1139
|
formatDate: formatDate,
|
|
934
1140
|
parse: parse,
|