diff --git a/benchmarks/cookies/validate-cookie-name.mjs b/benchmarks/cookies/validate-cookie-name.mjs new file mode 100644 index 00000000000..70e2b8a7406 --- /dev/null +++ b/benchmarks/cookies/validate-cookie-name.mjs @@ -0,0 +1,12 @@ +import { bench, group, run } from 'mitata' +import { validateCookieName } from '../../lib/web/cookies/util.js' + +const valid = 'Cat' + +group('validateCookieName', () => { + bench(`valid: ${valid}`, () => { + return validateCookieName(valid) + }) +}) + +await run() diff --git a/lib/web/cookies/util.js b/lib/web/cookies/util.js index b043054d1ce..15f7b0f1125 100644 --- a/lib/web/cookies/util.js +++ b/lib/web/cookies/util.js @@ -32,28 +32,29 @@ function isCTLExcludingHtab (value) { * @param {string} name */ function validateCookieName (name) { - for (const char of name) { - const code = char.charCodeAt(0) + for (let i = 0; i < name.length; ++i) { + const code = name.charCodeAt(i) if ( - (code <= 0x20 || code > 0x7F) || - char === '(' || - char === ')' || - char === '>' || - char === '<' || - char === '@' || - char === ',' || - char === ';' || - char === ':' || - char === '\\' || - char === '"' || - char === '/' || - char === '[' || - char === ']' || - char === '?' || - char === '=' || - char === '{' || - char === '}' + code < 0x21 || // exclude CTLs (0-31), SP and HT + code > 0x7E || // exclude non-ascii and DEL + code === 0x22 || // " + code === 0x28 || // ( + code === 0x29 || // ) + code === 0x3C || // < + code === 0x3E || // > + code === 0x40 || // @ + code === 0x2C || // , + code === 0x3B || // ; + code === 0x3A || // : + code === 0x5C || // \ + code === 0x2F || // / + code === 0x5B || // [ + code === 0x5D || // ] + code === 0x3F || // ? + code === 0x3D || // = + code === 0x7B || // { + code === 0x7D // } ) { throw new Error('Invalid cookie name') } @@ -285,6 +286,7 @@ function getHeadersList (headers) { module.exports = { isCTLExcludingHtab, + validateCookieName, validateCookiePath, toIMFDate, stringify, diff --git a/test/cookie/validate-cookie-name.js b/test/cookie/validate-cookie-name.js new file mode 100644 index 00000000000..32e4d9d2b66 --- /dev/null +++ b/test/cookie/validate-cookie-name.js @@ -0,0 +1,130 @@ +'use strict' + +const { test, describe } = require('node:test') +const { throws, strictEqual } = require('node:assert') + +const { + validateCookieName +} = require('../../lib/web/cookies/util') + +describe('validateCookieName', () => { + test('should throw for CTLs', () => { + throws(() => validateCookieName('\x00'), new Error('Invalid cookie name')) + throws(() => validateCookieName('\x01'), new Error('Invalid cookie name')) + throws(() => validateCookieName('\x02'), new Error('Invalid cookie name')) + throws(() => validateCookieName('\x03'), new Error('Invalid cookie name')) + throws(() => validateCookieName('\x04'), new Error('Invalid cookie name')) + throws(() => validateCookieName('\x05'), new Error('Invalid cookie name')) + throws(() => validateCookieName('\x06'), new Error('Invalid cookie name')) + throws(() => validateCookieName('\x07'), new Error('Invalid cookie name')) + throws(() => validateCookieName('\x08'), new Error('Invalid cookie name')) + throws(() => validateCookieName('\x09'), new Error('Invalid cookie name')) + throws(() => validateCookieName('\x0A'), new Error('Invalid cookie name')) + throws(() => validateCookieName('\x0B'), new Error('Invalid cookie name')) + throws(() => validateCookieName('\x0C'), new Error('Invalid cookie name')) + throws(() => validateCookieName('\x0D'), new Error('Invalid cookie name')) + throws(() => validateCookieName('\x0E'), new Error('Invalid cookie name')) + throws(() => validateCookieName('\x0F'), new Error('Invalid cookie name')) + throws(() => validateCookieName('\x10'), new Error('Invalid cookie name')) + throws(() => validateCookieName('\x11'), new Error('Invalid cookie name')) + throws(() => validateCookieName('\x12'), new Error('Invalid cookie name')) + throws(() => validateCookieName('\x13'), new Error('Invalid cookie name')) + throws(() => validateCookieName('\x14'), new Error('Invalid cookie name')) + throws(() => validateCookieName('\x15'), new Error('Invalid cookie name')) + throws(() => validateCookieName('\x16'), new Error('Invalid cookie name')) + throws(() => validateCookieName('\x17'), new Error('Invalid cookie name')) + throws(() => validateCookieName('\x18'), new Error('Invalid cookie name')) + throws(() => validateCookieName('\x19'), new Error('Invalid cookie name')) + throws(() => validateCookieName('\x1A'), new Error('Invalid cookie name')) + throws(() => validateCookieName('\x1B'), new Error('Invalid cookie name')) + throws(() => validateCookieName('\x1C'), new Error('Invalid cookie name')) + throws(() => validateCookieName('\x1D'), new Error('Invalid cookie name')) + throws(() => validateCookieName('\x1E'), new Error('Invalid cookie name')) + throws(() => validateCookieName('\x1F'), new Error('Invalid cookie name')) + throws(() => validateCookieName('\x7F'), new Error('Invalid cookie name')) + }) + + test('should throw for " " character', () => { + throws(() => validateCookieName(' '), new Error('Invalid cookie name')) + }) + + test('should throw for Horizontal Tab character', () => { + throws(() => validateCookieName('\t'), new Error('Invalid cookie name')) + }) + + test('should throw for ; character', () => { + throws(() => validateCookieName(';'), new Error('Invalid cookie name')) + }) + + test('should throw for " character', () => { + throws(() => validateCookieName('"'), new Error('Invalid cookie name')) + }) + + test('should throw for , character', () => { + throws(() => validateCookieName(','), new Error('Invalid cookie name')) + }) + + test('should throw for \\ character', () => { + throws(() => validateCookieName('\\'), new Error('Invalid cookie name')) + }) + + test('should throw for ( character', () => { + throws(() => validateCookieName('('), new Error('Invalid cookie name')) + }) + + test('should throw for ) character', () => { + throws(() => validateCookieName(')'), new Error('Invalid cookie name')) + }) + + test('should throw for < character', () => { + throws(() => validateCookieName('<'), new Error('Invalid cookie name')) + }) + + test('should throw for > character', () => { + throws(() => validateCookieName('>'), new Error('Invalid cookie name')) + }) + + test('should throw for @ character', () => { + throws(() => validateCookieName('@'), new Error('Invalid cookie name')) + }) + + test('should throw for : character', () => { + throws(() => validateCookieName(':'), new Error('Invalid cookie name')) + }) + + test('should throw for / character', () => { + throws(() => validateCookieName('/'), new Error('Invalid cookie name')) + }) + + test('should throw for [ character', () => { + throws(() => validateCookieName('['), new Error('Invalid cookie name')) + }) + + test('should throw for ] character', () => { + throws(() => validateCookieName(']'), new Error('Invalid cookie name')) + }) + + test('should throw for ? character', () => { + throws(() => validateCookieName('?'), new Error('Invalid cookie name')) + }) + + test('should throw for = character', () => { + throws(() => validateCookieName('='), new Error('Invalid cookie name')) + }) + + test('should throw for { character', () => { + throws(() => validateCookieName('{'), new Error('Invalid cookie name')) + }) + + test('should throw for } character', () => { + throws(() => validateCookieName('}'), new Error('Invalid cookie name')) + }) + + test('should pass for a printable character', t => { + strictEqual(validateCookieName('A'), undefined) + strictEqual(validateCookieName('Z'), undefined) + strictEqual(validateCookieName('a'), undefined) + strictEqual(validateCookieName('z'), undefined) + strictEqual(validateCookieName('!'), undefined) + }) +})