tough-cookie 2.3.3 → 2.3.4

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/README.md CHANGED
@@ -134,7 +134,7 @@ else
134
134
  cookies = [Cookie.parse(res.headers['set-cookie'])];
135
135
  ```
136
136
 
137
- _Potentially non-standard behavior:_ currently, tough-cookie will limit the number of spaces before the `=` to 256 characters.
137
+ _Note:_ in version 2.3.3, tough-cookie limited the number of spaces before the `=` to 256 characters. This limitation has since been removed.
138
138
  See [Issue 92](https://github.com/salesforce/tough-cookie/issues/92)
139
139
 
140
140
  ### Properties
package/lib/cookie.js CHANGED
@@ -44,37 +44,24 @@ try {
44
44
  console.warn("cookie: can't load punycode; won't use punycode for domain normalization");
45
45
  }
46
46
 
47
- var DATE_DELIM = /[\x09\x20-\x2F\x3B-\x40\x5B-\x60\x7B-\x7E]/;
48
-
49
47
  // From RFC6265 S4.1.1
50
48
  // note that it excludes \x3B ";"
51
- var COOKIE_OCTET = /[\x21\x23-\x2B\x2D-\x3A\x3C-\x5B\x5D-\x7E]/;
52
- var COOKIE_OCTETS = new RegExp('^'+COOKIE_OCTET.source+'+$');
49
+ var COOKIE_OCTETS = /^[\x21\x23-\x2B\x2D-\x3A\x3C-\x5B\x5D-\x7E]+$/;
53
50
 
54
51
  var CONTROL_CHARS = /[\x00-\x1F]/;
55
52
 
56
- // For COOKIE_PAIR and LOOSE_COOKIE_PAIR below, the number of spaces has been
57
- // restricted to 256 to side-step a ReDoS issue reported here:
58
- // https://github.com/salesforce/tough-cookie/issues/92
59
-
60
- // Double quotes are part of the value (see: S4.1.1).
61
- // '\r', '\n' and '\0' should be treated as a terminator in the "relaxed" mode
62
- // (see: https://github.com/ChromiumWebApps/chromium/blob/b3d3b4da8bb94c1b2e061600df106d590fda3620/net/cookies/parsed_cookie.cc#L60)
63
- // '=' and ';' are attribute/values separators
64
- // (see: https://github.com/ChromiumWebApps/chromium/blob/b3d3b4da8bb94c1b2e061600df106d590fda3620/net/cookies/parsed_cookie.cc#L64)
65
- var COOKIE_PAIR = /^(([^=;]+))\s{0,256}=\s*([^\n\r\0]*)/;
66
-
67
- // Used to parse non-RFC-compliant cookies like '=abc' when given the `loose`
68
- // option in Cookie.parse:
69
- var LOOSE_COOKIE_PAIR = /^((?:=)?([^=;]*)\s{0,256}=\s*)?([^\n\r\0]*)/;
53
+ // From Chromium // '\r', '\n' and '\0' should be treated as a terminator in
54
+ // the "relaxed" mode, see:
55
+ // https://github.com/ChromiumWebApps/chromium/blob/b3d3b4da8bb94c1b2e061600df106d590fda3620/net/cookies/parsed_cookie.cc#L60
56
+ var TERMINATORS = ['\n', '\r', '\0'];
70
57
 
71
58
  // RFC6265 S4.1.1 defines path value as 'any CHAR except CTLs or ";"'
72
59
  // Note ';' is \x3B
73
60
  var PATH_VALUE = /[\x20-\x3A\x3C-\x7E]+/;
74
61
 
75
- var DAY_OF_MONTH = /^(\d{1,2})[^\d]*$/;
76
- var TIME = /^(\d{1,2})[^\d]*:(\d{1,2})[^\d]*:(\d{1,2})[^\d]*$/;
77
- var MONTH = /^(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)/i;
62
+ // date-time parsing constants (RFC6265 S5.1.1)
63
+
64
+ var DATE_DELIM = /[\x09\x20-\x2F\x3B-\x40\x5B-\x60\x7B-\x7E]/;
78
65
 
79
66
  var MONTH_TO_NUM = {
80
67
  jan:0, feb:1, mar:2, apr:3, may:4, jun:5,
@@ -87,13 +74,80 @@ var NUM_TO_DAY = [
87
74
  'Sun','Mon','Tue','Wed','Thu','Fri','Sat'
88
75
  ];
89
76
 
90
- var YEAR = /^(\d{2}|\d{4})$/; // 2 to 4 digits
91
-
92
77
  var MAX_TIME = 2147483647000; // 31-bit max
93
78
  var MIN_TIME = 0; // 31-bit min
94
79
 
80
+ /*
81
+ * Parses a Natural number (i.e., non-negative integer) with either the
82
+ * <min>*<max>DIGIT ( non-digit *OCTET )
83
+ * or
84
+ * <min>*<max>DIGIT
85
+ * grammar (RFC6265 S5.1.1).
86
+ *
87
+ * The "trailingOK" boolean controls if the grammar accepts a
88
+ * "( non-digit *OCTET )" trailer.
89
+ */
90
+ function parseDigits(token, minDigits, maxDigits, trailingOK) {
91
+ var count = 0;
92
+ while (count < token.length) {
93
+ var c = token.charCodeAt(count);
94
+ // "non-digit = %x00-2F / %x3A-FF"
95
+ if (c <= 0x2F || c >= 0x3A) {
96
+ break;
97
+ }
98
+ count++;
99
+ }
100
+
101
+ // constrain to a minimum and maximum number of digits.
102
+ if (count < minDigits || count > maxDigits) {
103
+ return null;
104
+ }
105
+
106
+ if (!trailingOK && count != token.length) {
107
+ return null;
108
+ }
95
109
 
96
- // RFC6265 S5.1.1 date parser:
110
+ return parseInt(token.substr(0,count), 10);
111
+ }
112
+
113
+ function parseTime(token) {
114
+ var parts = token.split(':');
115
+ var result = [0,0,0];
116
+
117
+ /* RF6256 S5.1.1:
118
+ * time = hms-time ( non-digit *OCTET )
119
+ * hms-time = time-field ":" time-field ":" time-field
120
+ * time-field = 1*2DIGIT
121
+ */
122
+
123
+ if (parts.length !== 3) {
124
+ return null;
125
+ }
126
+
127
+ for (var i = 0; i < 3; i++) {
128
+ // "time-field" must be strictly "1*2DIGIT", HOWEVER, "hms-time" can be
129
+ // followed by "( non-digit *OCTET )" so therefore the last time-field can
130
+ // have a trailer
131
+ var trailingOK = (i == 2);
132
+ var num = parseDigits(parts[i], 1, 2, trailingOK);
133
+ if (num === null) {
134
+ return null;
135
+ }
136
+ result[i] = num;
137
+ }
138
+
139
+ return result;
140
+ }
141
+
142
+ function parseMonth(token) {
143
+ token = String(token).substr(0,3).toLowerCase();
144
+ var num = MONTH_TO_NUM[token];
145
+ return num >= 0 ? num : null;
146
+ }
147
+
148
+ /*
149
+ * RFC6265 S5.1.1 date parser (see RFC for full grammar)
150
+ */
97
151
  function parseDate(str) {
98
152
  if (!str) {
99
153
  return;
@@ -109,9 +163,9 @@ function parseDate(str) {
109
163
  }
110
164
 
111
165
  var hour = null;
112
- var minutes = null;
113
- var seconds = null;
114
- var day = null;
166
+ var minute = null;
167
+ var second = null;
168
+ var dayOfMonth = null;
115
169
  var month = null;
116
170
  var year = null;
117
171
 
@@ -129,22 +183,12 @@ function parseDate(str) {
129
183
  * the date-token, respectively. Skip the remaining sub-steps and continue
130
184
  * to the next date-token.
131
185
  */
132
- if (seconds === null) {
133
- result = TIME.exec(token);
186
+ if (second === null) {
187
+ result = parseTime(token);
134
188
  if (result) {
135
- hour = parseInt(result[1], 10);
136
- minutes = parseInt(result[2], 10);
137
- seconds = parseInt(result[3], 10);
138
- /* RFC6265 S5.1.1.5:
139
- * [fail if]
140
- * * the hour-value is greater than 23,
141
- * * the minute-value is greater than 59, or
142
- * * the second-value is greater than 59.
143
- */
144
- if(hour > 23 || minutes > 59 || seconds > 59) {
145
- return;
146
- }
147
-
189
+ hour = result[0];
190
+ minute = result[1];
191
+ second = result[2];
148
192
  continue;
149
193
  }
150
194
  }
@@ -154,16 +198,11 @@ function parseDate(str) {
154
198
  * the day-of-month-value to the number denoted by the date-token. Skip
155
199
  * the remaining sub-steps and continue to the next date-token.
156
200
  */
157
- if (day === null) {
158
- result = DAY_OF_MONTH.exec(token);
159
- if (result) {
160
- day = parseInt(result, 10);
161
- /* RFC6265 S5.1.1.5:
162
- * [fail if] the day-of-month-value is less than 1 or greater than 31
163
- */
164
- if(day < 1 || day > 31) {
165
- return;
166
- }
201
+ if (dayOfMonth === null) {
202
+ // "day-of-month = 1*2DIGIT ( non-digit *OCTET )"
203
+ result = parseDigits(token, 1, 2, true);
204
+ if (result !== null) {
205
+ dayOfMonth = result;
167
206
  continue;
168
207
  }
169
208
  }
@@ -174,47 +213,63 @@ function parseDate(str) {
174
213
  * continue to the next date-token.
175
214
  */
176
215
  if (month === null) {
177
- result = MONTH.exec(token);
178
- if (result) {
179
- month = MONTH_TO_NUM[result[1].toLowerCase()];
216
+ result = parseMonth(token);
217
+ if (result !== null) {
218
+ month = result;
180
219
  continue;
181
220
  }
182
221
  }
183
222
 
184
- /* 2.4. If the found-year flag is not set and the date-token matches the year
185
- * production, set the found-year flag and set the year-value to the number
186
- * denoted by the date-token. Skip the remaining sub-steps and continue to
187
- * the next date-token.
223
+ /* 2.4. If the found-year flag is not set and the date-token matches the
224
+ * year production, set the found-year flag and set the year-value to the
225
+ * number denoted by the date-token. Skip the remaining sub-steps and
226
+ * continue to the next date-token.
188
227
  */
189
228
  if (year === null) {
190
- result = YEAR.exec(token);
191
- if (result) {
192
- year = parseInt(result[0], 10);
229
+ // "year = 2*4DIGIT ( non-digit *OCTET )"
230
+ result = parseDigits(token, 2, 4, true);
231
+ if (result !== null) {
232
+ year = result;
193
233
  /* From S5.1.1:
194
234
  * 3. If the year-value is greater than or equal to 70 and less
195
235
  * than or equal to 99, increment the year-value by 1900.
196
236
  * 4. If the year-value is greater than or equal to 0 and less
197
237
  * than or equal to 69, increment the year-value by 2000.
198
238
  */
199
- if (70 <= year && year <= 99) {
239
+ if (year >= 70 && year <= 99) {
200
240
  year += 1900;
201
- } else if (0 <= year && year <= 69) {
241
+ } else if (year >= 0 && year <= 69) {
202
242
  year += 2000;
203
243
  }
204
-
205
- if (year < 1601) {
206
- return; // 5. ... the year-value is less than 1601
207
- }
208
244
  }
209
245
  }
210
246
  }
211
247
 
212
- if (seconds === null || day === null || month === null || year === null) {
213
- return; // 5. ... at least one of the found-day-of-month, found-month, found-
214
- // year, or found-time flags is not set,
248
+ /* RFC 6265 S5.1.1
249
+ * "5. Abort these steps and fail to parse the cookie-date if:
250
+ * * at least one of the found-day-of-month, found-month, found-
251
+ * year, or found-time flags is not set,
252
+ * * the day-of-month-value is less than 1 or greater than 31,
253
+ * * the year-value is less than 1601,
254
+ * * the hour-value is greater than 23,
255
+ * * the minute-value is greater than 59, or
256
+ * * the second-value is greater than 59.
257
+ * (Note that leap seconds cannot be represented in this syntax.)"
258
+ *
259
+ * So, in order as above:
260
+ */
261
+ if (
262
+ dayOfMonth === null || month === null || year === null || second === null ||
263
+ dayOfMonth < 1 || dayOfMonth > 31 ||
264
+ year < 1601 ||
265
+ hour > 23 ||
266
+ minute > 59 ||
267
+ second > 59
268
+ ) {
269
+ return;
215
270
  }
216
271
 
217
- return new Date(Date.UTC(year, month, day, hour, minutes, seconds));
272
+ return new Date(Date.UTC(year, month, dayOfMonth, hour, minute, second));
218
273
  }
219
274
 
220
275
  function formatDate(date) {
@@ -321,32 +376,62 @@ function defaultPath(path) {
321
376
  return path.slice(0, rightSlash);
322
377
  }
323
378
 
379
+ function trimTerminator(str) {
380
+ for (var t = 0; t < TERMINATORS.length; t++) {
381
+ var terminatorIdx = str.indexOf(TERMINATORS[t]);
382
+ if (terminatorIdx !== -1) {
383
+ str = str.substr(0,terminatorIdx);
384
+ }
385
+ }
324
386
 
325
- function parse(str, options) {
326
- if (!options || typeof options !== 'object') {
327
- options = {};
387
+ return str;
388
+ }
389
+
390
+ function parseCookiePair(cookiePair, looseMode) {
391
+ cookiePair = trimTerminator(cookiePair);
392
+
393
+ var firstEq = cookiePair.indexOf('=');
394
+ if (looseMode) {
395
+ if (firstEq === 0) { // '=' is immediately at start
396
+ cookiePair = cookiePair.substr(1);
397
+ firstEq = cookiePair.indexOf('='); // might still need to split on '='
398
+ }
399
+ } else { // non-loose mode
400
+ if (firstEq <= 0) { // no '=' or is at start
401
+ return; // needs to have non-empty "cookie-name"
402
+ }
328
403
  }
329
- str = str.trim();
330
404
 
331
- // We use a regex to parse the "name-value-pair" part of S5.2
332
- var firstSemi = str.indexOf(';'); // S5.2 step 1
333
- var pairRe = options.loose ? LOOSE_COOKIE_PAIR : COOKIE_PAIR;
334
- var result = pairRe.exec(firstSemi === -1 ? str : str.substr(0,firstSemi));
405
+ var cookieName, cookieValue;
406
+ if (firstEq <= 0) {
407
+ cookieName = "";
408
+ cookieValue = cookiePair.trim();
409
+ } else {
410
+ cookieName = cookiePair.substr(0, firstEq).trim();
411
+ cookieValue = cookiePair.substr(firstEq+1).trim();
412
+ }
335
413
 
336
- // Rx satisfies the "the name string is empty" and "lacks a %x3D ("=")"
337
- // constraints as well as trimming any whitespace.
338
- if (!result) {
414
+ if (CONTROL_CHARS.test(cookieName) || CONTROL_CHARS.test(cookieValue)) {
339
415
  return;
340
416
  }
341
417
 
342
418
  var c = new Cookie();
343
- if (result[1]) {
344
- c.key = result[2].trim();
345
- } else {
346
- c.key = '';
419
+ c.key = cookieName;
420
+ c.value = cookieValue;
421
+ return c;
422
+ }
423
+
424
+ function parse(str, options) {
425
+ if (!options || typeof options !== 'object') {
426
+ options = {};
347
427
  }
348
- c.value = result[3].trim();
349
- if (CONTROL_CHARS.test(c.key) || CONTROL_CHARS.test(c.value)) {
428
+ str = str.trim();
429
+
430
+ // We use a regex to parse the "name-value-pair" part of S5.2
431
+ var firstSemi = str.indexOf(';'); // S5.2 step 1
432
+ var cookiePair = (firstSemi === -1) ? str : str.substr(0, firstSemi);
433
+ var c = parseCookiePair(cookiePair, !!options.loose);
434
+ if (!c) {
350
435
  return;
351
436
  }
352
437