@@ -44,37 +44,24 @@ try {
4444 console . warn ( "cookie: can't load punycode; won't use punycode for domain normalization" ) ;
4545}
4646
47- var DATE_DELIM = / [ \x09 \x20 - \x2F \x3B - \x40 \x5B - \x60 \x7B - \x7E ] / ;
48-
4947// From RFC6265 S4.1.1
5048// 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 ] + $ / ;
5350
5451var CONTROL_CHARS = / [ \x00 - \x1F ] / ;
5552
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' ] ;
7057
7158// RFC6265 S4.1.1 defines path value as 'any CHAR except CTLs or ";"'
7259// Note ';' is \x3B
7360var PATH_VALUE = / [ \x20 - \x3A \x3C - \x7E ] + / ;
7461
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 = / ^ ( J a n | F e b | M a r | A p r | M a y | J u n | J u l | A u g | S e p | O c t | N o v | D e c ) / i ;
62+ // date-time parsing constants (RFC6265 S5.1.1)
63+
64+ var DATE_DELIM = / [ \x09 \x20 - \x2F \x3B - \x40 \x5B - \x60 \x7B - \x7E ] / ;
7865
7966var MONTH_TO_NUM = {
8067 jan :0 , feb :1 , mar :2 , apr :3 , may :4 , jun :5 ,
@@ -87,13 +74,80 @@ var NUM_TO_DAY = [
8774 'Sun' , 'Mon' , 'Tue' , 'Wed' , 'Thu' , 'Fri' , 'Sat'
8875] ;
8976
90- var YEAR = / ^ ( \d { 2 } | \d { 4 } ) $ / ; // 2 to 4 digits
91-
9277var MAX_TIME = 2147483647000 ; // 31-bit max
9378var MIN_TIME = 0 ; // 31-bit min
9479
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+ }
95109
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+ */
97151function parseDate ( str ) {
98152 if ( ! str ) {
99153 return ;
@@ -109,9 +163,9 @@ function parseDate(str) {
109163 }
110164
111165 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 ;
115169 var month = null ;
116170 var year = null ;
117171
@@ -129,22 +183,12 @@ function parseDate(str) {
129183 * the date-token, respectively. Skip the remaining sub-steps and continue
130184 * to the next date-token.
131185 */
132- if ( seconds === null ) {
133- result = TIME . exec ( token ) ;
186+ if ( second === null ) {
187+ result = parseTime ( token ) ;
134188 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 ] ;
148192 continue ;
149193 }
150194 }
@@ -154,16 +198,11 @@ function parseDate(str) {
154198 * the day-of-month-value to the number denoted by the date-token. Skip
155199 * the remaining sub-steps and continue to the next date-token.
156200 */
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 ;
167206 continue ;
168207 }
169208 }
@@ -174,47 +213,63 @@ function parseDate(str) {
174213 * continue to the next date-token.
175214 */
176215 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 ;
180219 continue ;
181220 }
182221 }
183222
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.
188227 */
189228 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 ;
193233 /* From S5.1.1:
194234 * 3. If the year-value is greater than or equal to 70 and less
195235 * than or equal to 99, increment the year-value by 1900.
196236 * 4. If the year-value is greater than or equal to 0 and less
197237 * than or equal to 69, increment the year-value by 2000.
198238 */
199- if ( 70 <= year && year <= 99 ) {
239+ if ( year >= 70 && year <= 99 ) {
200240 year += 1900 ;
201- } else if ( 0 <= year && year <= 69 ) {
241+ } else if ( year >= 0 && year <= 69 ) {
202242 year += 2000 ;
203243 }
204-
205- if ( year < 1601 ) {
206- return ; // 5. ... the year-value is less than 1601
207- }
208244 }
209245 }
210246 }
211247
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 ;
215270 }
216271
217- return new Date ( Date . UTC ( year , month , day , hour , minutes , seconds ) ) ;
272+ return new Date ( Date . UTC ( year , month , dayOfMonth , hour , minute , second ) ) ;
218273}
219274
220275function formatDate ( date ) {
@@ -321,32 +376,62 @@ function defaultPath(path) {
321376 return path . slice ( 0 , rightSlash ) ;
322377}
323378
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+ }
324386
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+ }
328403 }
329- str = str . trim ( ) ;
330404
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+ }
335413
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 ) ) {
339415 return ;
340416 }
341417
342418 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 = { } ;
347427 }
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 ) {
350435 return ;
351436 }
352437
0 commit comments