Skip to content

Commit 02d8038

Browse files
committed
cookies: improve validateCookieValue
1 parent 3b2df8e commit 02d8038

4 files changed

Lines changed: 117 additions & 10 deletions

File tree

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { bench, group, run } from 'mitata'
2+
import { validateCookieValue } from '../../lib/web/cookies/util.js'
3+
4+
const valid = 'Cat'
5+
const wrappedValid = `"${valid}"`
6+
7+
group('validateCookieValue', () => {
8+
bench(`valid: ${valid}`, () => {
9+
return validateCookieValue(valid)
10+
})
11+
bench(`valid: ${wrappedValid}`, () => {
12+
return validateCookieValue(wrappedValid)
13+
})
14+
})
15+
16+
await run()

lib/web/cookies/util.js

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -69,18 +69,30 @@ function validateCookieName (name) {
6969
* @param {string} value
7070
*/
7171
function validateCookieValue (value) {
72-
for (const char of value) {
73-
const code = char.charCodeAt(0)
72+
let len = value.length
73+
let i = 0
74+
75+
// if the value is wrapped in DQUOTE
76+
if (value[0] === '"') {
77+
if (len === 1 || value[len - 1] !== '"') {
78+
throw new Error('Invalid cookie value')
79+
}
80+
--len
81+
++i
82+
}
83+
84+
while (i < len) {
85+
const code = value.charCodeAt(i++)
7486

7587
if (
7688
code < 0x21 || // exclude CTLs (0-31)
77-
code === 0x22 ||
78-
code === 0x2C ||
79-
code === 0x3B ||
80-
code === 0x5C ||
81-
code > 0x7E // non-ascii
89+
code > 0x7E || // non-ascii and DEL (127)
90+
code === 0x22 || // "
91+
code === 0x2C || // ,
92+
code === 0x3B || // ;
93+
code === 0x5C // \
8294
) {
83-
throw new Error('Invalid header value')
95+
throw new Error('Invalid cookie value')
8496
}
8597
}
8698
}
@@ -286,6 +298,7 @@ function getHeadersList (headers) {
286298
module.exports = {
287299
isCTLExcludingHtab,
288300
validateCookiePath,
301+
validateCookieValue,
289302
toIMFDate,
290303
stringify,
291304
getHeadersList

test/cookie/cookies.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ test('Cookie Value Validation', () => {
116116
}
117117
)
118118
},
119-
new Error('Invalid header value'),
119+
new Error('Invalid cookie value'),
120120
"RFC2616 cookie 'Space'"
121121
)
122122
})
@@ -128,7 +128,7 @@ test('Cookie Value Validation', () => {
128128
value: 'United Kingdom'
129129
})
130130
},
131-
new Error('Invalid header value'),
131+
new Error('Invalid cookie value'),
132132
"RFC2616 cookie 'location' cannot contain character ' '"
133133
)
134134
})
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
'use strict'
2+
3+
const { test, describe } = require('node:test')
4+
const { throws, strictEqual } = require('node:assert')
5+
6+
const {
7+
validateCookieValue
8+
} = require('../../lib/web/cookies/util')
9+
10+
describe('validateCookieValue', () => {
11+
test('should throw for CTLs', () => {
12+
throws(() => validateCookieValue('\x00'), new Error('Invalid cookie value'))
13+
throws(() => validateCookieValue('\x01'), new Error('Invalid cookie value'))
14+
throws(() => validateCookieValue('\x02'), new Error('Invalid cookie value'))
15+
throws(() => validateCookieValue('\x03'), new Error('Invalid cookie value'))
16+
throws(() => validateCookieValue('\x04'), new Error('Invalid cookie value'))
17+
throws(() => validateCookieValue('\x05'), new Error('Invalid cookie value'))
18+
throws(() => validateCookieValue('\x06'), new Error('Invalid cookie value'))
19+
throws(() => validateCookieValue('\x07'), new Error('Invalid cookie value'))
20+
throws(() => validateCookieValue('\x08'), new Error('Invalid cookie value'))
21+
throws(() => validateCookieValue('\x09'), new Error('Invalid cookie value'))
22+
throws(() => validateCookieValue('\x0A'), new Error('Invalid cookie value'))
23+
throws(() => validateCookieValue('\x0B'), new Error('Invalid cookie value'))
24+
throws(() => validateCookieValue('\x0C'), new Error('Invalid cookie value'))
25+
throws(() => validateCookieValue('\x0D'), new Error('Invalid cookie value'))
26+
throws(() => validateCookieValue('\x0E'), new Error('Invalid cookie value'))
27+
throws(() => validateCookieValue('\x0F'), new Error('Invalid cookie value'))
28+
throws(() => validateCookieValue('\x10'), new Error('Invalid cookie value'))
29+
throws(() => validateCookieValue('\x11'), new Error('Invalid cookie value'))
30+
throws(() => validateCookieValue('\x12'), new Error('Invalid cookie value'))
31+
throws(() => validateCookieValue('\x13'), new Error('Invalid cookie value'))
32+
throws(() => validateCookieValue('\x14'), new Error('Invalid cookie value'))
33+
throws(() => validateCookieValue('\x15'), new Error('Invalid cookie value'))
34+
throws(() => validateCookieValue('\x16'), new Error('Invalid cookie value'))
35+
throws(() => validateCookieValue('\x17'), new Error('Invalid cookie value'))
36+
throws(() => validateCookieValue('\x18'), new Error('Invalid cookie value'))
37+
throws(() => validateCookieValue('\x19'), new Error('Invalid cookie value'))
38+
throws(() => validateCookieValue('\x1A'), new Error('Invalid cookie value'))
39+
throws(() => validateCookieValue('\x1B'), new Error('Invalid cookie value'))
40+
throws(() => validateCookieValue('\x1C'), new Error('Invalid cookie value'))
41+
throws(() => validateCookieValue('\x1D'), new Error('Invalid cookie value'))
42+
throws(() => validateCookieValue('\x1E'), new Error('Invalid cookie value'))
43+
throws(() => validateCookieValue('\x1F'), new Error('Invalid cookie value'))
44+
throws(() => validateCookieValue('\x7F'), new Error('Invalid cookie value'))
45+
})
46+
47+
test('should throw for ; character', () => {
48+
throws(() => validateCookieValue(';'), new Error('Invalid cookie value'))
49+
})
50+
51+
test('should throw for " character', () => {
52+
throws(() => validateCookieValue('"'), new Error('Invalid cookie value'))
53+
})
54+
55+
test('should throw for " character', () => {
56+
throws(() => validateCookieValue(','), new Error('Invalid cookie value'))
57+
})
58+
59+
test('should throw for \\ character', () => {
60+
throws(() => validateCookieValue('\\'), new Error('Invalid cookie value'))
61+
})
62+
63+
test('should pass for a printable character', t => {
64+
strictEqual(validateCookieValue('A'), undefined)
65+
strictEqual(validateCookieValue('Z'), undefined)
66+
strictEqual(validateCookieValue('a'), undefined)
67+
strictEqual(validateCookieValue('z'), undefined)
68+
strictEqual(validateCookieValue('!'), undefined)
69+
strictEqual(validateCookieValue('='), undefined)
70+
})
71+
72+
test('should handle strings wrapped in DQUOTE', t => {
73+
strictEqual(validateCookieValue('""'), undefined)
74+
strictEqual(validateCookieValue('"helloworld"'), undefined)
75+
throws(() => validateCookieValue('"'), new Error('Invalid cookie value'))
76+
throws(() => validateCookieValue('"""'), new Error('Invalid cookie value'))
77+
})
78+
})

0 commit comments

Comments
 (0)