diff --git a/lib/web/fetch/formdata-parser.js b/lib/web/fetch/formdata-parser.js index a4622ae10a3..b14e358d4ae 100644 --- a/lib/web/fetch/formdata-parser.js +++ b/lib/web/fetch/formdata-parser.js @@ -538,15 +538,16 @@ function isCTL (char) { } /** - * tspecials := "(" / ")" / "<" / ">" / "@" / - * "," / ";" / ":" / "\" / <"> - * "/" / "[" / "]" / "?" / "=" - * ; Must be in quoted-string, - * ; to use within parameter values + * tspecials := "(" | ")" | "<" | ">" | "@" | + * "," | ";" | ":" | "\" | <"> | + * "/" | "[" | "]" | "?" | "=" | + * SP | HT * @param {number} char */ function isTSpecial (char) { return ( + char === 0x20 || // Space + char === 0x09 || // Horizontal Tab char === 0x28 || // ( char === 0x29 || // ) char === 0x3c || // < @@ -561,20 +562,18 @@ function isTSpecial (char) { char === 0x5b || // [ char === 0x5d || // ] char === 0x3f || // ? - char === 0x3d // + + char === 0x3d // = ) } /** - * token := 1* + * token := 1* + * Note: tspecials explicitly includes SPACE (0x20) and HTAB (0x09). * @param {number} char */ function isToken (char) { return ( char <= 0x7f && // ascii - char !== 0x20 && // space - char !== 0x09 && !isCTL(char) && !isTSpecial(char) ) @@ -582,5 +581,8 @@ function isToken (char) { module.exports = { multipartFormDataParser, - validateBoundary + validateBoundary, + isCTL, + isTSpecial, + isToken } diff --git a/test/fetch/formdata-parse.js b/test/fetch/formdata-parse.js new file mode 100644 index 00000000000..577e5266b96 --- /dev/null +++ b/test/fetch/formdata-parse.js @@ -0,0 +1,29 @@ +'use strict' + +const { test } = require('node:test') +const { isToken, isTSpecial, isCTL } = require('../../lib/web/fetch/formdata-parser') + +test('HTTP/1.1 token character validation rules', async (t) => { + await t.test('isCTL should catch control sequences', (t) => { + t.assert.strictEqual(isCTL(0x00), true) // Null + t.assert.strictEqual(isCTL(0x09), true) // Tab + t.assert.strictEqual(isCTL(0x1F), true) // US + t.assert.strictEqual(isCTL(0x7F), true) // DEL + t.assert.strictEqual(isCTL(0x41), false) // 'A' + }) + + await t.test('isTSpecial should flag accurate network separators', (t) => { + t.assert.strictEqual(isTSpecial(0x20), true) // Space + t.assert.strictEqual(isTSpecial(0x09), true) // Tab + t.assert.strictEqual(isTSpecial(0x3D), true) // '=' + t.assert.strictEqual(isTSpecial(0x2B), false) // '+' (Must be false!) + t.assert.strictEqual(isTSpecial(0x41), false) // 'A' + }) + + await t.test('isToken should confirm safe token symbols', (t) => { + t.assert.strictEqual(isToken(0x2B), true) // '+' should be a valid token + t.assert.strictEqual(isToken(0x2D), true) // '-' should be a valid token + t.assert.strictEqual(isToken(0x20), false) // Space is blocked + t.assert.strictEqual(isToken(0x3D), false) // '=' is blocked + }) +})