From a835370c29ce7c793bd2cb40cacf626d18669371 Mon Sep 17 00:00:00 2001 From: Robert Nagy Date: Tue, 13 Jul 2021 13:43:48 +0200 Subject: [PATCH 1/7] http: verify chunk parameters --- src/llhttp/http.ts | 5 +++-- test/request/transfer-encoding.md | 22 ++++++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/llhttp/http.ts b/src/llhttp/http.ts index f86d4fbf..092a8e04 100644 --- a/src/llhttp/http.ts +++ b/src/llhttp/http.ts @@ -683,10 +683,11 @@ export class HTTP { .otherwise(p.error(ERROR.INVALID_CHUNK_SIZE, 'Invalid character in chunk size')); - // just ignore this. n('chunk_parameters') .match('\r', n('chunk_size_almost_done')) - .skipTo(n('chunk_parameters')); + .match(HEADER_CHARS, n('chunk_parameters')) + .otherwise(p.error(ERROR.STRICT, + 'Invalid character in chunk parameters')); if (this.mode === 'strict') { n('chunk_size_almost_done') diff --git a/test/request/transfer-encoding.md b/test/request/transfer-encoding.md index fdf89a12..31f970ea 100644 --- a/test/request/transfer-encoding.md +++ b/test/request/transfer-encoding.md @@ -386,3 +386,25 @@ off=52 len=3 span[body]="foo" off=57 chunk complete off=57 error code=12 reason="Invalid character in chunk size" ``` + +## Validate chunk parameters + + +```http +PUT /url HTTP/1.1 +Transfer-Encoding: chunked + +3 \n \r\n\ +foo + + +``` + +```log +off=0 message begin +off=4 len=4 span[url]="/url" +off=19 len=17 span[header_field]="Transfer-Encoding" +off=38 len=7 span[header_value]="chunked" +off=49 headers complete method=4 v=1/1 flags=208 content_length=0 +off=51 error code=2 reason="Invalid character in chunk parameters" +``` From 15ecd206262481bb87f2f9cfbf377278f5513787 Mon Sep 17 00:00:00 2001 From: Akshay K Date: Thu, 1 Jul 2021 01:33:15 -0400 Subject: [PATCH 2/7] http: disable whitespace for special headers --- src/llhttp/http.ts | 22 +++++++++++++++- test/request/connection.md | 46 ++++++++++++++++++++++++++++++++ test/request/content-length.md | 48 ++++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 1 deletion(-) diff --git a/src/llhttp/http.ts b/src/llhttp/http.ts index 092a8e04..db976514 100644 --- a/src/llhttp/http.ts +++ b/src/llhttp/http.ts @@ -45,6 +45,7 @@ const NODES: ReadonlyArray = [ 'header_field_start', 'header_field', 'header_field_colon', + 'header_field_colon_discard_ws', 'header_field_general', 'header_field_general_otherwise', 'header_value_discard_ws', @@ -361,13 +362,32 @@ export class HTTP { .select(SPECIAL_HEADERS, this.store('header_state', 'header_field_colon')) .otherwise(this.resetHeaderState('header_field_general')); + const onInvalidHeaderFieldChar = + p.error(ERROR.INVALID_HEADER_TOKEN, 'Invalid header field char'); + + const checkLenientFlagsOnColon = + this.testFlags(FLAGS.LENIENT, { + 1: n('header_field_colon_discard_ws'), + }, span.headerField.end().skipTo(onInvalidHeaderFieldChar)); + n('header_field_colon') - .match(' ', n('header_field_colon')) + // https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.4 + // Whitespace character is not allowed between the header field-name + // and colon. If the next token matches whitespace then throw an error. + // + // Add a check for the lenient flag. If the lenient flag is set, the + // whitespace token is allowed to support legacy code not following + // http specs. + .peek(' ', checkLenientFlagsOnColon) .peek(':', span.headerField.end().skipTo(n('header_value_discard_ws'))) // Fallback to general header, there're additional characters: // `Connection-Duration` instead of `Connection` and so on. .otherwise(this.resetHeaderState('header_field_general')); + n('header_field_colon_discard_ws') + .match(' ', n('header_field_colon_discard_ws')) + .otherwise(n('header_field_colon')); + n('header_field_general') .match(this.TOKEN, n('header_field_general')) .otherwise(n('header_field_general_otherwise')); diff --git a/test/request/connection.md b/test/request/connection.md index f6e4007e..f2b69c7a 100644 --- a/test/request/connection.md +++ b/test/request/connection.md @@ -348,6 +348,52 @@ off=78 message complete off=78 error code=22 reason="Pause on CONNECT/Upgrade" ``` +### Invalid whitespace token with `Connection` header field + + +```http +PUT /url HTTP/1.1 +Connection : upgrade +Content-Length: 4 +Upgrade: ws + +abcdefgh +``` + +```log +off=0 message begin +off=4 len=4 span[url]="/url" +off=19 len=10 span[header_field]="Connection" +off=30 error code=10 reason="Invalid header field char" +``` + +### Invalid whitespace token with `Connection` header field (lenient) + + +```http +PUT /url HTTP/1.1 +Connection : upgrade +Content-Length: 4 +Upgrade: ws + +abcdefgh +``` + +```log +off=0 message begin +off=4 len=4 span[url]="/url" +off=19 len=11 span[header_field]="Connection " +off=32 len=7 span[header_value]="upgrade" +off=41 len=14 span[header_field]="Content-Length" +off=57 len=1 span[header_value]="4" +off=60 len=7 span[header_field]="Upgrade" +off=69 len=2 span[header_value]="ws" +off=75 headers complete method=4 v=1/1 flags=134 content_length=4 +off=75 len=4 span[body]="abcd" +off=79 message complete +off=79 error code=22 reason="Pause on CONNECT/Upgrade" +``` + ## `upgrade` ### Setting a flag and pausing diff --git a/test/request/content-length.md b/test/request/content-length.md index 80c2f646..a4cad060 100644 --- a/test/request/content-length.md +++ b/test/request/content-length.md @@ -151,6 +151,54 @@ off=57 len=8 span[header_value]="identity" off=69 headers complete method=4 v=1/1 flags=320 content_length=1 ``` +## Invalid whitespace token with `Content-Length` header field + + +```http +PUT /url HTTP/1.1 +Connection: upgrade +Content-Length : 4 +Upgrade: ws + +abcdefgh +``` + +```log +off=0 message begin +off=4 len=4 span[url]="/url" +off=19 len=10 span[header_field]="Connection" +off=31 len=7 span[header_value]="upgrade" +off=40 len=14 span[header_field]="Content-Length" +off=55 error code=10 reason="Invalid header field char" +``` + +## Invalid whitespace token with `Content-Length` header field (lenient) + + +```http +PUT /url HTTP/1.1 +Connection: upgrade +Content-Length : 4 +Upgrade: ws + +abcdefgh +``` + +```log +off=0 message begin +off=4 len=4 span[url]="/url" +off=19 len=10 span[header_field]="Connection" +off=31 len=7 span[header_value]="upgrade" +off=40 len=15 span[header_field]="Content-Length " +off=57 len=1 span[header_value]="4" +off=60 len=7 span[header_field]="Upgrade" +off=69 len=2 span[header_value]="ws" +off=75 headers complete method=4 v=1/1 flags=134 content_length=4 +off=75 len=4 span[body]="abcd" +off=79 message complete +off=79 error code=22 reason="Pause on CONNECT/Upgrade" +``` + ## Funky `Content-Length` with body From d6ea943d8d1c5092f4bf6e10a6a32cfb2dbeaae3 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Tue, 12 Oct 2021 19:46:02 +0200 Subject: [PATCH 3/7] Bumped v2.1.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 787eff60..d041f128 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "llhttp", - "version": "2.1.3", + "version": "2.1.4", "description": "HTTP parser in LLVM IR", "main": "lib/llhttp.js", "types": "lib/llhttp.d.ts", From cc6b967e7fe849d3916b905fd0d41225b3e0c929 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Wed, 6 Jul 2022 14:59:07 +0200 Subject: [PATCH 4/7] Strict transfer encoding 2.1 (#162) * http: stricter Transfer-Encoding and header separator parsing * update deps Signed-off-by: Matteo Collina * chore: fix Windows CI (#149) Update scoop installation. * ci: fix windows builds Co-authored-by: Paolo Insogna Co-authored-by: Rich Trott Co-authored-by: Fedor Indutny --- .github/workflows/ci.yaml | 13 ++- package-lock.json | 176 +++++++++++++++-------------- package.json | 8 +- src/llhttp/constants.ts | 80 ++++++------- src/llhttp/http.ts | 46 +++++++- test/request/invalid.md | 23 ++++ test/request/sample.md | 16 +-- test/request/transfer-encoding.md | 121 ++++++++++++++++++-- test/response/sample.md | 5 +- test/response/transfer-encoding.md | 65 ++++++++++- 10 files changed, 385 insertions(+), 168 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d4fce49e..1e4aa26b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -19,11 +19,12 @@ jobs: - name: Install clang for Windows if: runner.os == 'Windows' run: | - Invoke-Expression (New-Object System.Net.WebClient).DownloadString('https://get.scoop.sh') + iwr -useb get.scoop.sh -outfile 'install.ps1' + .\install.ps1 -RunAsAdmin scoop install llvm --global # Scoop modifies the PATH so we make the modified PATH global. - echo "::set-env name=PATH::$env:PATH" + echo $env:PATH >> $env:GITHUB_PATH - name: Fetch code uses: actions/checkout@v2 @@ -45,7 +46,8 @@ jobs: - name: Build libllhttp.a shell: bash - run: make build/libllhttp.a + run: | + make build/libllhttp.a test: name: Run tests @@ -60,11 +62,12 @@ jobs: - name: Install clang for Windows if: runner.os == 'Windows' run: | - Invoke-Expression (New-Object System.Net.WebClient).DownloadString('https://get.scoop.sh') + iwr -useb get.scoop.sh -outfile 'install.ps1' + .\install.ps1 -RunAsAdmin scoop install llvm --global # Scoop modifies the PATH so we make the modified PATH global. - echo "::set-env name=PATH::$env:PATH" + echo $env:PATH >> $env:GITHUB_PATH - name: Fetch code uses: actions/checkout@v2 diff --git a/package-lock.json b/package-lock.json index a03bd27f..60143456 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "llhttp", - "version": "2.1.3", + "version": "2.1.4", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -30,16 +30,10 @@ "js-tokens": "^4.0.0" } }, - "@types/color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", - "dev": true - }, "@types/debug": { - "version": "0.0.30", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-0.0.30.tgz", - "integrity": "sha512-orGL5LXERPYsLov6CWs3Fh6203+dXzJkR7OnddIr2514Hsecwc8xRpzCapshBbKFImCsvS/mk6+FWiN5LyZJAQ==" + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.5.tgz", + "integrity": "sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==" }, "@types/markdown-it": { "version": "0.0.4", @@ -54,9 +48,9 @@ "dev": true }, "@types/node": { - "version": "10.17.27", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.27.tgz", - "integrity": "sha512-J0oqm9ZfAXaPdwNXMMgAhylw5fhmXkToJd06vuDUSAgEDZ/n/69/69UmyBZbc+zT34UnShuDSBqvim3SPnozJg==", + "version": "10.17.60", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", + "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", "dev": true }, "@types/semver": { @@ -71,18 +65,17 @@ "dev": true }, "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true }, "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" } }, @@ -128,21 +121,6 @@ "resolved": "https://registry.npmjs.org/binary-search/-/binary-search-1.3.6.tgz", "integrity": "sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA==" }, - "bitcode": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/bitcode/-/bitcode-1.2.0.tgz", - "integrity": "sha512-cWgZK/ri/1ZUJ+UKEwP9Cqw10WY5wHz+boMxVO4vvc0btmxa2tMc2m2Zk9HYdCyx4b5+sgQM1/NCJPTIPO1XOw==", - "dev": true, - "requires": { - "bitcode-builder": "^1.2.0" - } - }, - "bitcode-builder": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/bitcode-builder/-/bitcode-builder-1.2.0.tgz", - "integrity": "sha512-biuJIhrog5d1IFMaKtHMJ8PJ1L3zxiWdclwYErjOBWf8Gwyqa4XwflvMufzcQw/OUeAArO1AqOrqsOFsWJ94OA==", - "dev": true - }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -604,22 +582,42 @@ } }, "llparse": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/llparse/-/llparse-7.0.1.tgz", - "integrity": "sha512-PGv8et6mi1Lea1XTnDDJbB6M2baJCIIIcE0e8Vu8CIRkEDneIjNcqjLONR+ju5VdCYa8bc6O7oIKcDjtPWjf9w==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/llparse/-/llparse-7.1.1.tgz", + "integrity": "sha512-lBxN5O6sKq6KSOaRFIGczoVpO/U/37mHhjJioQbPuiXdfZmwzP1zC3txV9xx778TRNFENzeCM0Uoo+mE1rfJOA==", "requires": { - "debug": "^3.2.6", - "llparse-frontend": "^2.0.0" + "debug": "^4.2.0", + "llparse-frontend": "^3.0.0" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + } } }, "llparse-builder": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/llparse-builder/-/llparse-builder-1.4.0.tgz", - "integrity": "sha512-mu0/zgAc1KdD6r+tjmRvF+YgoToQvBun4iXISRfSmx66b5qurckRpYjzBUYpHn0XVqKPRrGg86gMQKv8ogY3Rw==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/llparse-builder/-/llparse-builder-1.5.2.tgz", + "integrity": "sha512-i862UNC3YUEdlfK/NUCJxlKjtWjgAI9AJXDRgjcfRHfwFt4Sf8eFPTRsc91/2R9MBZ0kyFdfhi8SVhMsZf1gNQ==", "requires": { - "@types/debug": "0.0.30", + "@types/debug": "4.1.5 ", "binary-search": "^1.3.6", - "debug": "^3.2.6" + "debug": "^4.2.0" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + } } }, "llparse-dot": { @@ -632,46 +630,50 @@ } }, "llparse-frontend": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/llparse-frontend/-/llparse-frontend-2.0.0.tgz", - "integrity": "sha512-DOJop+Qvzv5BBUoM4Gi5W7JMz57L99GfNgTNrnv3H9IEWMdXBk/fBjiEQuvqmPMAsezAOdSkCr4Wxorv4qTuxQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/llparse-frontend/-/llparse-frontend-3.0.0.tgz", + "integrity": "sha512-G/o0Po2C+G5OtP8MJeQDjDf5qwDxcO7K6x4r6jqGsJwxk7yblbJnRqpmye7G/lZ8dD0Hv5neY4/KB5BhDmEc9Q==", "requires": { "debug": "^3.2.6", - "llparse-builder": "^1.4.0" + "llparse-builder": "^1.5.2" + }, + "dependencies": { + "@types/debug": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.5.tgz", + "integrity": "sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==" + }, + "llparse-builder": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/llparse-builder/-/llparse-builder-1.5.2.tgz", + "integrity": "sha512-i862UNC3YUEdlfK/NUCJxlKjtWjgAI9AJXDRgjcfRHfwFt4Sf8eFPTRsc91/2R9MBZ0kyFdfhi8SVhMsZf1gNQ==", + "requires": { + "@types/debug": "4.1.5 ", + "binary-search": "^1.3.6", + "debug": "^4.2.0" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + } + } + } } }, "llparse-test-fixture": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/llparse-test-fixture/-/llparse-test-fixture-5.0.1.tgz", - "integrity": "sha512-BrnS70lxODcTXttLkfoSqn8DPbNuuSLFR48JnwxLimFkr8QRNBVbUku+bumIIo5Z7gAbIGNQXDOiSi2crMzS8Q==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/llparse-test-fixture/-/llparse-test-fixture-5.0.2.tgz", + "integrity": "sha512-61KI5J/b5uyRktD0y1EezleEW6UfaxhHkn1adLKNVemRZzklE+SpLakr251qo04kb9jN/ytk8lllgK+yFOj4cQ==", "dev": true, "requires": { "esm": "^3.2.25", - "llparse": "^6.4.0", + "llparse": "^7.0.0", "yargs": "^15.4.1" - }, - "dependencies": { - "llparse": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/llparse/-/llparse-6.4.0.tgz", - "integrity": "sha512-ySA+bj2wOLXrKmohAVMw0Nq84oHDPLdg+sUx4+VeSk1U72MEKfKAXS7zh82n15BRjWc/cVgWBN9RQAFdgk0g5Q==", - "dev": true, - "requires": { - "bitcode": "^1.2.0", - "debug": "^3.2.6", - "llparse-frontend": "^1.4.0" - } - }, - "llparse-frontend": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/llparse-frontend/-/llparse-frontend-1.4.0.tgz", - "integrity": "sha512-lUpGvGU9MDPb3k4Wbb0S7FgpceCirXVeFQQZjsYWB3fIEGU0Q6IEiTO91J6MLLN75gsxvGiWZaKVnmcHb7jh6g==", - "dev": true, - "requires": { - "debug": "^3.2.6", - "llparse-builder": "^1.3.2" - } - } } }, "locate-path": { @@ -1119,14 +1121,14 @@ "dev": true }, "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" + "strip-ansi": "^6.0.1" } }, "string.prototype.trimend": { @@ -1150,12 +1152,12 @@ } }, "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "requires": { - "ansi-regex": "^5.0.0" + "ansi-regex": "^5.0.1" } }, "strip-json-comments": { @@ -1243,9 +1245,9 @@ } }, "typescript": { - "version": "3.9.7", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz", - "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==", + "version": "3.9.10", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz", + "integrity": "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==", "dev": true }, "uc.micro": { diff --git a/package.json b/package.json index d041f128..8f0f1c6a 100644 --- a/package.json +++ b/package.json @@ -37,18 +37,18 @@ "homepage": "https://github.com/nodejs/llhttp#readme", "devDependencies": { "@types/mocha": "^5.2.7", - "@types/node": "^10.17.27", + "@types/node": "^10.17.60", "llparse-dot": "^1.0.1", - "llparse-test-fixture": "^5.0.1", + "llparse-test-fixture": "^5.0.2", "mdgator": "^1.1.2", "mocha": "^7.2.0", "ts-node": "^7.0.1", "tslint": "^5.20.1", - "typescript": "^3.9.6" + "typescript": "^3.9.10" }, "dependencies": { "@types/semver": "^5.5.0", - "llparse": "^7.0.0", + "llparse": "^7.1.1", "semver": "^5.7.1" } } diff --git a/src/llhttp/constants.ts b/src/llhttp/constants.ts index 48cfb9b8..a65d812c 100644 --- a/src/llhttp/constants.ts +++ b/src/llhttp/constants.ts @@ -6,38 +6,40 @@ export type HTTPMode = 'loose' | 'strict'; export enum ERROR { OK = 0, - INTERNAL, - STRICT, - LF_EXPECTED, - UNEXPECTED_CONTENT_LENGTH, - CLOSED_CONNECTION, - INVALID_METHOD, - INVALID_URL, - INVALID_CONSTANT, - INVALID_VERSION, - INVALID_HEADER_TOKEN, - INVALID_CONTENT_LENGTH, - INVALID_CHUNK_SIZE, - INVALID_STATUS, - INVALID_EOF_STATE, - INVALID_TRANSFER_ENCODING, - - CB_MESSAGE_BEGIN, - CB_HEADERS_COMPLETE, - CB_MESSAGE_COMPLETE, - CB_CHUNK_HEADER, - CB_CHUNK_COMPLETE, - - PAUSED, - PAUSED_UPGRADE, - - USER, + INTERNAL = 1, + STRICT = 2, + CR_EXPECTED = 25, + LF_EXPECTED = 3, + UNEXPECTED_CONTENT_LENGTH = 4, + CLOSED_CONNECTION = 5, + INVALID_METHOD = 6, + INVALID_URL = 7, + INVALID_CONSTANT = 8, + INVALID_VERSION = 9, + INVALID_HEADER_TOKEN = 10, + INVALID_CONTENT_LENGTH = 11, + INVALID_CHUNK_SIZE = 12, + INVALID_STATUS = 13, + INVALID_EOF_STATE = 14, + INVALID_TRANSFER_ENCODING = 15, + + CB_MESSAGE_BEGIN = 16, + CB_HEADERS_COMPLETE = 17, + CB_MESSAGE_COMPLETE = 18, + CB_CHUNK_HEADER = 19, + CB_CHUNK_COMPLETE = 20, + + PAUSED = 21, + PAUSED_UPGRADE = 22, + // PAUSED_H2_UPGRADE = 23 in v6.x + + USER = 24, } export enum TYPE { BOTH = 0, // default - REQUEST, - RESPONSE, + REQUEST = 1, + RESPONSE = 2, } export enum FLAGS { @@ -111,8 +113,8 @@ Object.keys(METHOD_MAP).forEach((key) => { export enum FINISH { SAFE = 0, - SAFE_WITH_CB, - UNSAFE, + SAFE_WITH_CB = 1, + UNSAFE = 2, } // Internal @@ -208,15 +210,15 @@ export const MINOR = MAJOR; export enum HEADER_STATE { GENERAL = 0, - CONNECTION, - CONTENT_LENGTH, - TRANSFER_ENCODING, - UPGRADE, - - CONNECTION_KEEP_ALIVE, - CONNECTION_CLOSE, - CONNECTION_UPGRADE, - TRANSFER_ENCODING_CHUNKED, + CONNECTION = 1, + CONTENT_LENGTH = 2, + TRANSFER_ENCODING = 3, + UPGRADE = 4, + + CONNECTION_KEEP_ALIVE = 5, + CONNECTION_CLOSE = 6, + CONNECTION_UPGRADE = 7, + TRANSFER_ENCODING_CHUNKED = 8, } export const SPECIAL_HEADERS = { diff --git a/src/llhttp/http.ts b/src/llhttp/http.ts index db976514..42ae94e8 100644 --- a/src/llhttp/http.ts +++ b/src/llhttp/http.ts @@ -55,6 +55,7 @@ const NODES: ReadonlyArray = [ 'header_value', 'header_value_otherwise', 'header_value_lenient', + 'header_value_lenient_failed', 'header_value_lws', 'header_value_te_chunked', 'header_value_te_chunked_last', @@ -433,11 +434,35 @@ export class HTTP { .match([ ' ', '\t' ], n('header_value_discard_ws')) .otherwise(checkContentLengthEmptiness); + // Multiple `Transfer-Encoding` headers should be treated as one, but with + // values separate by a comma. + // + // See: https://tools.ietf.org/html/rfc7230#section-3.2.2 + const toTransferEncoding = this.unsetFlag( + FLAGS.CHUNKED, + 'header_value_te_chunked'); + + // Once chunked has been selected, no other encoding is possible in requests + // https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.1 + const forbidAfterChunkedInRequest = (otherwise: Node) => { + return this.load('type', { + [TYPE.REQUEST]: this.testFlags(FLAGS.LENIENT, { + 0: span.headerValue.end().skipTo( + p.error(ERROR.INVALID_TRANSFER_ENCODING, 'Invalid `Transfer-Encoding` header value'), + ), + }).otherwise(otherwise), + }, otherwise); + }; + n('header_value_start') .otherwise(this.load('header_state', { [HEADER_STATE.UPGRADE]: this.setFlag(FLAGS.UPGRADE, fallback), - [HEADER_STATE.TRANSFER_ENCODING]: this.setFlag( - FLAGS.TRANSFER_ENCODING, 'header_value_te_chunked'), + [HEADER_STATE.TRANSFER_ENCODING]: this.testFlags( + FLAGS.CHUNKED, + { + 1: forbidAfterChunkedInRequest(this.setFlag(FLAGS.TRANSFER_ENCODING, toTransferEncoding)), + }, + this.setFlag(FLAGS.TRANSFER_ENCODING, toTransferEncoding)), [HEADER_STATE.CONTENT_LENGTH]: n('header_value_content_length_once'), [HEADER_STATE.CONNECTION]: n('header_value_connection'), }, 'header_value')); @@ -459,7 +484,8 @@ export class HTTP { .peek([ '\r', '\n' ], this.update('header_state', HEADER_STATE.TRANSFER_ENCODING_CHUNKED, 'header_value_otherwise')) - .otherwise(n('header_value_te_chunked')); + .peek(',', forbidAfterChunkedInRequest(n('header_value_te_chunked'))) + .otherwise(n('header_value_te_token')); n('header_value_te_token') .match(',', n('header_value_te_token_ows')) @@ -544,11 +570,10 @@ export class HTTP { const checkLenient = this.testFlags(FLAGS.LENIENT, { 1: n('header_value_lenient'), - }, p.error(ERROR.INVALID_HEADER_TOKEN, 'Invalid header value char')); + }, n('header_value_lenient_failed')); n('header_value_otherwise') .peek('\r', span.headerValue.end().skipTo(n('header_value_almost_done'))) - .peek('\n', span.headerValue.end(n('header_value_almost_done'))) .otherwise(checkLenient); n('header_value_lenient') @@ -556,6 +581,12 @@ export class HTTP { .peek('\n', span.headerValue.end(n('header_value_almost_done'))) .skipTo(n('header_value_lenient')); + n('header_value_lenient_failed') + .peek('\n', span.headerValue.end().skipTo( + p.error(ERROR.CR_EXPECTED, 'Missing expected CR after header value')), + ) + .otherwise(p.error(ERROR.INVALID_HEADER_TOKEN, 'Invalid header value char')); + n('header_value_almost_done') .match('\n', n('header_value_lws')) .otherwise(p.error(ERROR.LF_EXPECTED, @@ -833,6 +864,11 @@ export class HTTP { return p.invoke(p.code.or('flags', flag), this.node(next)); } + private unsetFlag(flag: FLAGS, next: string | Node): Node { + const p = this.llparse; + return p.invoke(p.code.and('flags', ~flag), this.node(next)); + } + private testFlags(flag: FLAGS, map: { [key: number]: Node }, next?: string | Node): Node { const p = this.llparse; diff --git a/test/request/invalid.md b/test/request/invalid.md index 3af4decf..94d83d9c 100644 --- a/test/request/invalid.md +++ b/test/request/invalid.md @@ -153,3 +153,26 @@ off=16 len=4 span[header_field]="Host" off=22 len=15 span[header_value]="www.example.com" off=52 error code=10 reason="Invalid header token" ``` + +### Missing CR between headers + + + +```http +GET / HTTP/1.1 +Host: localhost +Dummy: x\nContent-Length: 23 +GET / HTTP/1.1 +Dummy: GET /admin HTTP/1.1 +Host: localhost +``` + +```log +off=0 message begin +off=4 len=1 span[url]="/" +off=16 len=4 span[header_field]="Host" +off=22 len=9 span[header_value]="localhost" +off=33 len=5 span[header_field]="Dummy" +off=40 len=1 span[header_value]="x" +off=42 error code=25 reason="Missing expected CR after header value" +``` \ No newline at end of file diff --git a/test/request/sample.md b/test/request/sample.md index 184255a7..2fd12246 100644 --- a/test/request/sample.md +++ b/test/request/sample.md @@ -298,21 +298,7 @@ off=0 message begin off=4 len=1 span[url]="/" off=15 len=5 span[header_field]="Line1" off=24 len=3 span[header_value]="abc" -off=28 len=4 span[header_value]="\tdef" -off=33 len=4 span[header_value]=" ghi" -off=38 len=5 span[header_value]="\t\tjkl" -off=44 len=6 span[header_value]=" mno " -off=51 len=6 span[header_value]="\t \tqrs" -off=58 len=5 span[header_field]="Line2" -off=67 len=6 span[header_value]="line2\t" -off=74 len=5 span[header_field]="Line3" -off=82 len=5 span[header_value]="line3" -off=88 len=5 span[header_field]="Line4" -off=98 len=0 span[header_value]="" -off=98 len=10 span[header_field]="Connection" -off=111 len=5 span[header_value]="close" -off=118 headers complete method=1 v=1/1 flags=2 content_length=0 -off=118 message complete +off=28 error code=25 reason="Missing expected CR after header value" ``` ## Request starting with CRLF diff --git a/test/request/transfer-encoding.md b/test/request/transfer-encoding.md index 31f970ea..1900ab33 100644 --- a/test/request/transfer-encoding.md +++ b/test/request/transfer-encoding.md @@ -253,7 +253,7 @@ off=112 len=1 span[header_value]="5" off=117 error code=4 reason="Content-Length can't be present with Transfer-Encoding" ``` -## POST with `Transfer-Encoding` (that is not chunked) and `Content-Length` (lenient) +## POST with `Transfer-Encoding` and `Content-Length` (lenient) TODO(indutny): should we allow it even in lenient mode? (Consider disabling this). @@ -301,15 +301,35 @@ off=5 len=38 span[url]="/post_identity_body_world?q=search#hey" off=54 len=6 span[header_field]="Accept" off=62 len=3 span[header_value]="*/*" off=67 len=17 span[header_field]="Transfer-Encoding" -off=86 len=16 span[header_value]="chunked, deflate" -off=106 headers complete method=3 v=1/1 flags=200 content_length=0 -off=106 error code=15 reason="Request has invalid `Transfer-Encoding`" +off=86 len=7 span[header_value]="chunked" +off=94 error code=15 reason="Invalid `Transfer-Encoding` header value" ``` -## POST with `chunked` before other transfer-coding (lenient) +## POST with `chunked` and duplicate transfer-encoding -TODO(indutny): should we allow it even in lenient mode? (Consider disabling -this). + +```http +POST /post_identity_body_world?q=search#hey HTTP/1.1 +Accept: */* +Transfer-Encoding: chunked +Transfer-Encoding: deflate + +World +``` + +```log +off=0 message begin +off=5 len=38 span[url]="/post_identity_body_world?q=search#hey" +off=54 len=6 span[header_field]="Accept" +off=62 len=3 span[header_value]="*/*" +off=67 len=17 span[header_field]="Transfer-Encoding" +off=86 len=7 span[header_value]="chunked" +off=95 len=17 span[header_field]="Transfer-Encoding" +off=114 len=0 span[header_value]="" +off=115 error code=15 reason="Invalid `Transfer-Encoding` header value" +``` + +## POST with `chunked` before other transfer-coding (lenient) ```http @@ -331,7 +351,32 @@ off=106 headers complete method=3 v=1/1 flags=300 content_length=0 off=106 len=5 span[body]="World" ``` -## POST with `chunked` as last transfer-coding +## POST with `chunked` and duplicate transfer-encoding (lenient) + + +```http +POST /post_identity_body_world?q=search#hey HTTP/1.1 +Accept: */* +Transfer-Encoding: chunked +Transfer-Encoding: deflate + +World +``` + +```log +off=0 message begin +off=5 len=38 span[url]="/post_identity_body_world?q=search#hey" +off=54 len=6 span[header_field]="Accept" +off=62 len=3 span[header_value]="*/*" +off=67 len=17 span[header_field]="Transfer-Encoding" +off=86 len=7 span[header_value]="chunked" +off=95 len=17 span[header_field]="Transfer-Encoding" +off=114 len=7 span[header_value]="deflate" +off=125 headers complete method=3 v=1/1 flags=300 content_length=0 +off=125 len=5 span[body]="World" +``` + +## POST with `chunked` as last transfer-encoding ```http @@ -362,6 +407,66 @@ off=121 chunk complete off=121 message complete ``` +## POST with `chunked` as last transfer-encoding (multiple headers) + + +```http +POST /post_identity_body_world?q=search#hey HTTP/1.1 +Accept: */* +Transfer-Encoding: deflate +Transfer-Encoding: chunked + +5 +World +0 + + +``` + +```log +off=0 message begin +off=5 len=38 span[url]="/post_identity_body_world?q=search#hey" +off=54 len=6 span[header_field]="Accept" +off=62 len=3 span[header_value]="*/*" +off=67 len=17 span[header_field]="Transfer-Encoding" +off=86 len=7 span[header_value]="deflate" +off=95 len=17 span[header_field]="Transfer-Encoding" +off=114 len=7 span[header_value]="chunked" +off=125 headers complete method=3 v=1/1 flags=208 content_length=0 +off=128 chunk header len=5 +off=128 len=5 span[body]="World" +off=135 chunk complete +off=138 chunk header len=0 +off=140 chunk complete +off=140 message complete +``` + +## POST with `chunkedchunked` as transfer-encoding + + +```http +POST /post_identity_body_world?q=search#hey HTTP/1.1 +Accept: */* +Transfer-Encoding: chunkedchunked + +5 +World +0 + + +``` + +```log +off=0 message begin +off=5 len=38 span[url]="/post_identity_body_world?q=search#hey" +off=54 len=6 span[header_field]="Accept" +off=62 len=3 span[header_value]="*/*" +off=67 len=17 span[header_field]="Transfer-Encoding" +off=86 len=14 span[header_value]="chunkedchunked" +off=104 headers complete method=3 v=1/1 flags=200 content_length=0 +off=104 error code=15 reason="Request has invalid `Transfer-Encoding`" +``` + ## Missing last-chunk diff --git a/test/response/sample.md b/test/response/sample.md index 1804fdb5..2143fb00 100644 --- a/test/response/sample.md +++ b/test/response/sample.md @@ -225,10 +225,7 @@ off=0 message begin off=13 len=2 span[status]="OK" off=16 len=12 span[header_field]="Content-Type" off=30 len=24 span[header_value]="text/html; charset=utf-8" -off=55 len=10 span[header_field]="Connection" -off=67 len=5 span[header_value]="close" -off=74 headers complete status=200 v=1/1 flags=2 content_length=0 -off=74 len=51 span[body]="these headers are from http://news.ycombinator.com/" +off=55 error code=25 reason="Missing expected CR after header value" ``` ## Underscore in header key diff --git a/test/response/transfer-encoding.md b/test/response/transfer-encoding.md index 1a52253b..9797be21 100644 --- a/test/response/transfer-encoding.md +++ b/test/response/transfer-encoding.md @@ -43,7 +43,7 @@ off=159 chunk complete off=159 message complete ``` -## `chunked` before other transfer-coding +### `chunked` before other transfer-encoding ```http @@ -64,3 +64,66 @@ off=49 len=16 span[header_value]="chunked, deflate" off=69 headers complete status=200 v=1/1 flags=200 content_length=0 off=69 len=5 span[body]="World" ``` + +### multiple transfer-encoding where chunked is not the last one + + +```http +HTTP/1.1 200 OK +Accept: */* +Transfer-Encoding: chunked +Transfer-Encoding: identity + +World +``` + +```log +off=0 message begin +off=13 len=2 span[status]="OK" +off=17 len=6 span[header_field]="Accept" +off=25 len=3 span[header_value]="*/*" +off=30 len=17 span[header_field]="Transfer-Encoding" +off=49 len=7 span[header_value]="chunked" +off=58 len=17 span[header_field]="Transfer-Encoding" +off=77 len=8 span[header_value]="identity" +off=89 headers complete status=200 v=1/1 flags=200 content_length=0 +off=89 len=5 span[body]="World" +``` + +### `chunkedchunked` transfer-encoding does not enable chunked enconding + +This check that the word `chunked` repeat more than once (with or without spaces) does not mistakenly enables chunked encoding. + + +```http +HTTP/1.1 200 OK +Accept: */* +Transfer-Encoding: chunkedchunked + +2 +OK +0 + + +``` + +```log +off=0 message begin +off=13 len=2 span[status]="OK" +off=17 len=6 span[header_field]="Accept" +off=25 len=3 span[header_value]="*/*" +off=30 len=17 span[header_field]="Transfer-Encoding" +off=49 len=14 span[header_value]="chunkedchunked" +off=67 headers complete status=200 v=1/1 flags=200 content_length=0 +off=67 len=1 span[body]="2" +off=68 len=1 span[body]=cr +off=69 len=1 span[body]=lf +off=70 len=2 span[body]="OK" +off=72 len=1 span[body]=cr +off=73 len=1 span[body]=lf +off=74 len=1 span[body]="0" +off=75 len=1 span[body]=cr +off=76 len=1 span[body]=lf +off=77 len=1 span[body]=cr +off=78 len=1 span[body]=lf +``` \ No newline at end of file From f014c23bffb327f3749c6222e96247aec73a1caf Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Wed, 6 Jul 2022 15:00:12 +0200 Subject: [PATCH 5/7] Bumepd v2.1.5 Signed-off-by: Matteo Collina --- package-lock.json | 7 +++++-- package.json | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 60143456..0136102a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "llhttp", - "version": "2.1.4", + "version": "2.1.5", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -33,7 +33,8 @@ "@types/debug": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.5.tgz", - "integrity": "sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==" + "integrity": "sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==", + "dev": true }, "@types/markdown-it": { "version": "0.0.4", @@ -604,6 +605,7 @@ "version": "1.5.2", "resolved": "https://registry.npmjs.org/llparse-builder/-/llparse-builder-1.5.2.tgz", "integrity": "sha512-i862UNC3YUEdlfK/NUCJxlKjtWjgAI9AJXDRgjcfRHfwFt4Sf8eFPTRsc91/2R9MBZ0kyFdfhi8SVhMsZf1gNQ==", + "dev": true, "requires": { "@types/debug": "4.1.5 ", "binary-search": "^1.3.6", @@ -614,6 +616,7 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, "requires": { "ms": "2.1.2" } diff --git a/package.json b/package.json index 8f0f1c6a..82379e85 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "llhttp", - "version": "2.1.4", + "version": "2.1.5", "description": "HTTP parser in LLVM IR", "main": "lib/llhttp.js", "types": "lib/llhttp.d.ts", From 181e70093b2dd4eb4fbfa1c787cf88f48a4a8599 Mon Sep 17 00:00:00 2001 From: Paolo Insogna Date: Sat, 24 Sep 2022 00:16:15 +0200 Subject: [PATCH 6/7] http: disable chunked encoding when OBS fold is used (#197) --- package-lock.json | 2 +- package.json | 2 +- src/llhttp/http.ts | 12 ++++++-- test/request/invalid.md | 49 +++++++++++++++++++++++++++++- test/request/lenient.md | 1 + test/request/sample.md | 2 +- test/request/transfer-encoding.md | 25 +++++++++++++++ test/response/sample.md | 2 +- test/response/transfer-encoding.md | 38 ++++++++++++++++++++++- 9 files changed, 124 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0136102a..c36b7f0c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "llhttp", - "version": "2.1.5", + "version": "2.1.6", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 82379e85..9d998cb5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "llhttp", - "version": "2.1.5", + "version": "2.1.6", "description": "HTTP parser in LLVM IR", "main": "lib/llhttp.js", "types": "lib/llhttp.d.ts", diff --git a/src/llhttp/http.ts b/src/llhttp/http.ts index 42ae94e8..c2d0593c 100644 --- a/src/llhttp/http.ts +++ b/src/llhttp/http.ts @@ -411,7 +411,9 @@ export class HTTP { n('header_value_discard_ws') .match([ ' ', '\t' ], n('header_value_discard_ws')) .match('\r', n('header_value_discard_ws_almost_done')) - .match('\n', n('header_value_discard_lws')) + .match('\n', this.testFlags(FLAGS.LENIENT, { + 1: n('header_value_discard_lws'), + }, p.error(ERROR.INVALID_HEADER_TOKEN, 'Invalid header value char'))) .otherwise(span.headerValue.start(n('header_value_start'))); if (this.mode === 'strict') { @@ -570,7 +572,7 @@ export class HTTP { const checkLenient = this.testFlags(FLAGS.LENIENT, { 1: n('header_value_lenient'), - }, n('header_value_lenient_failed')); + }, span.headerValue.end(p.error(ERROR.INVALID_HEADER_TOKEN, 'Invalid header value char'))); n('header_value_otherwise') .peek('\r', span.headerValue.end().skipTo(n('header_value_almost_done'))) @@ -593,7 +595,11 @@ export class HTTP { 'Missing expected LF after header value')); n('header_value_lws') - .peek([ ' ', '\t' ], span.headerValue.start(n('header_value_start'))) + .peek([ ' ', '\t' ], + this.load('header_state', { + [HEADER_STATE.TRANSFER_ENCODING_CHUNKED]: + this.resetHeaderState(span.headerValue.start(n('header_value_start'))), + }, span.headerValue.start(n('header_value_start')))) .otherwise(this.setHeaderFlags('header_field_start')); const checkTrailing = this.testFlags(FLAGS.TRAILING, { diff --git a/test/request/invalid.md b/test/request/invalid.md index 94d83d9c..bdc326bf 100644 --- a/test/request/invalid.md +++ b/test/request/invalid.md @@ -51,6 +51,53 @@ off=21 len=1 span[header_value]="1" off=23 error code=3 reason="Missing expected LF after header value" ``` +### Headers separated by LF + + +```http +POST / HTTP/1.1 +Host: localhost:5000 +x:x\nTransfer-Encoding: chunked + +1 +A +0 + +``` + +```log +off=0 message begin +off=5 len=1 span[url]="/" +off=17 len=4 span[header_field]="Host" +off=23 len=14 span[header_value]="localhost:5000" +off=39 len=1 span[header_field]="x" +off=41 len=1 span[header_value]="x" +off=42 error code=10 reason="Invalid header value char" +``` + +### Empty headers separated by LF + + +```http +POST / HTTP/1.1 +Host: localhost:5000 +x:\nTransfer-Encoding: chunked + +1 +A +0 + +``` + +```log +off=0 message begin +off=5 len=1 span[url]="/" +off=17 len=4 span[header_field]="Host" +off=23 len=14 span[header_value]="localhost:5000" +off=39 len=1 span[header_field]="x" +off=42 error code=10 reason="Invalid header value char" +``` + ### Invalid header token #1 @@ -174,5 +221,5 @@ off=16 len=4 span[header_field]="Host" off=22 len=9 span[header_value]="localhost" off=33 len=5 span[header_field]="Dummy" off=40 len=1 span[header_value]="x" -off=42 error code=25 reason="Missing expected CR after header value" +off=41 error code=10 reason="Invalid header value char" ``` \ No newline at end of file diff --git a/test/request/lenient.md b/test/request/lenient.md index 8fb50e41..0926f8a2 100644 --- a/test/request/lenient.md +++ b/test/request/lenient.md @@ -66,5 +66,6 @@ Header1: \f off=0 message begin off=4 len=4 span[url]="/url" off=19 len=7 span[header_field]="Header1" +off=28 len=0 span[header_value]="" off=28 error code=10 reason="Invalid header value char" ``` diff --git a/test/request/sample.md b/test/request/sample.md index 2fd12246..ed335935 100644 --- a/test/request/sample.md +++ b/test/request/sample.md @@ -298,7 +298,7 @@ off=0 message begin off=4 len=1 span[url]="/" off=15 len=5 span[header_field]="Line1" off=24 len=3 span[header_value]="abc" -off=28 error code=25 reason="Missing expected CR after header value" +off=27 error code=10 reason="Invalid header value char" ``` ## Request starting with CRLF diff --git a/test/request/transfer-encoding.md b/test/request/transfer-encoding.md index 1900ab33..ec04fa35 100644 --- a/test/request/transfer-encoding.md +++ b/test/request/transfer-encoding.md @@ -513,3 +513,28 @@ off=38 len=7 span[header_value]="chunked" off=49 headers complete method=4 v=1/1 flags=208 content_length=0 off=51 error code=2 reason="Invalid character in chunk parameters" ``` + +## Invalid OBS fold after chunked value + + +```http +PUT /url HTTP/1.1 +Transfer-Encoding: chunked + abc + +5 +World +0 + + +``` + +```log +off=0 message begin +off=4 len=4 span[url]="/url" +off=19 len=17 span[header_field]="Transfer-Encoding" +off=38 len=7 span[header_value]="chunked" +off=47 len=5 span[header_value]=" abc" +off=56 headers complete method=4 v=1/1 flags=200 content_length=0 +off=56 error code=15 reason="Request has invalid `Transfer-Encoding`" +``` diff --git a/test/response/sample.md b/test/response/sample.md index 2143fb00..81a2181a 100644 --- a/test/response/sample.md +++ b/test/response/sample.md @@ -225,7 +225,7 @@ off=0 message begin off=13 len=2 span[status]="OK" off=16 len=12 span[header_field]="Content-Type" off=30 len=24 span[header_value]="text/html; charset=utf-8" -off=55 error code=25 reason="Missing expected CR after header value" +off=54 error code=10 reason="Invalid header value char" ``` ## Underscore in header key diff --git a/test/response/transfer-encoding.md b/test/response/transfer-encoding.md index 9797be21..8916884b 100644 --- a/test/response/transfer-encoding.md +++ b/test/response/transfer-encoding.md @@ -126,4 +126,40 @@ off=75 len=1 span[body]=cr off=76 len=1 span[body]=lf off=77 len=1 span[body]=cr off=78 len=1 span[body]=lf -``` \ No newline at end of file +``` + +## Invalid OBS fold after chunked value + + +```http +HTTP/1.1 200 OK +Transfer-Encoding: chunked + abc + +5 +World +0 + + +``` + +```log +off=0 message begin +off=13 len=2 span[status]="OK" +off=17 len=17 span[header_field]="Transfer-Encoding" +off=36 len=7 span[header_value]="chunked" +off=45 len=5 span[header_value]=" abc" +off=54 headers complete status=200 v=1/1 flags=200 content_length=0 +off=54 len=1 span[body]="5" +off=55 len=1 span[body]=cr +off=56 len=1 span[body]=lf +off=57 len=5 span[body]="World" +off=62 len=1 span[body]=cr +off=63 len=1 span[body]=lf +off=64 len=1 span[body]="0" +off=65 len=1 span[body]=cr +off=66 len=1 span[body]=lf +off=67 len=1 span[body]=cr +off=68 len=1 span[body]=lf +``` + From 7f5150efc888820a420d2652446967d297eb0d2a Mon Sep 17 00:00:00 2001 From: Paolo Insogna Date: Wed, 13 Sep 2023 13:39:40 +0200 Subject: [PATCH 7/7] chore: Mark the series as unsupported. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index f1998e92..93144f93 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ Port of [http_parser][0] to [llparse][1]. +**IMPORTANT: The 2.x series is discontinued and not maintained anymore. Update to the latest version of llhttp as soon as possible.** + ## Why? Let's face it, [http_parser][0] is practically unmaintainable. Even