From f49fbf5c818ccc7e4d9994393b8f8e1d96483d7e Mon Sep 17 00:00:00 2001 From: Paolo Insogna Date: Fri, 23 Sep 2022 23:32:12 +0200 Subject: [PATCH 1/8] Disable chunked on obs (#196) * http: disable chunked encoding when OBS fold is used * http: do not accept LF in headers without lenient flag --- src/llhttp/http.ts | 12 +++++-- test/fixtures/extra.c | 6 ++++ test/fixtures/index.ts | 5 +-- test/md-test.ts | 2 ++ test/request/invalid.md | 57 +++++++++++++++++++++++++++++- test/request/lenient-headers.md | 1 + test/request/sample.md | 2 +- test/request/transfer-encoding.md | 28 +++++++++++++++ test/response/sample.md | 2 +- test/response/transfer-encoding.md | 41 ++++++++++++++++++++- 10 files changed, 147 insertions(+), 9 deletions(-) diff --git a/src/llhttp/http.ts b/src/llhttp/http.ts index e44c8598..72291be2 100644 --- a/src/llhttp/http.ts +++ b/src/llhttp/http.ts @@ -476,7 +476,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.testLenientFlags(LENIENT_FLAGS.HEADERS, { + 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') { @@ -638,7 +640,7 @@ export class HTTP { const checkLenient = this.testLenientFlags(LENIENT_FLAGS.HEADERS, { 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'))) @@ -661,7 +663,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(onHeaderValueComplete)); const checkTrailing = this.testFlags(FLAGS.TRAILING, { diff --git a/test/fixtures/extra.c b/test/fixtures/extra.c index 64e9ae97..7528a036 100644 --- a/test/fixtures/extra.c +++ b/test/fixtures/extra.c @@ -105,6 +105,12 @@ void llhttp__test_init_response_lenient_version(llparse_t* s) { } +void llhttp__test_init_response_lenient_headers(llparse_t* s) { + llhttp__test_init_response(s); + s->lenient_flags |= LENIENT_HEADERS; +} + + void llhttp__test_finish(llparse_t* s) { llparse__print(NULL, NULL, "finish=%d", s->finish); } diff --git a/test/fixtures/index.ts b/test/fixtures/index.ts index 9ac0fd05..a3cb1f6f 100644 --- a/test/fixtures/index.ts +++ b/test/fixtures/index.ts @@ -11,7 +11,7 @@ import * as llhttp from '../../src/llhttp'; export type TestType = 'request' | 'response' | 'request-lenient-headers' | 'request-lenient-chunked-length' | 'request-lenient-transfer-encoding' | 'request-lenient-keep-alive' | 'response-lenient-keep-alive' | - 'request-lenient-version' | 'response-lenient-version' | + 'response-lenient-headers' | 'request-lenient-version' | 'response-lenient-version' | 'request-finish' | 'response-finish' | 'none' | 'url'; @@ -67,8 +67,9 @@ export async function build( ty === 'request-lenient-chunked-length' || ty === 'request-lenient-transfer-encoding' || ty === 'request-lenient-keep-alive' || - ty === 'response-lenient-keep-alive' || ty === 'request-lenient-version' || + ty === 'response-lenient-headers' || + ty === 'response-lenient-keep-alive' || ty === 'response-lenient-version') { extra.push( `-DLLPARSE__TEST_INIT=llhttp__test_init_${ty.replace(/-/g, '_')}`); diff --git a/test/md-test.ts b/test/md-test.ts index 20d9b3d2..0fd4adbc 100644 --- a/test/md-test.ts +++ b/test/md-test.ts @@ -171,6 +171,8 @@ function run(name: string): void { types = [ 'request-lenient-version' ]; } else if (meta.type === 'response-lenient-keep-alive') { types = [ 'response-lenient-keep-alive' ]; + } else if (meta.type === 'response-lenient-headers') { + types = [ 'response-lenient-headers' ]; } else if (meta.type === 'response-lenient-version') { types = [ 'response-lenient-version' ]; } else if (meta.type === 'response-only') { diff --git a/test/request/invalid.md b/test/request/invalid.md index 2154ddf3..224a0af7 100644 --- a/test/request/invalid.md +++ b/test/request/invalid.md @@ -89,6 +89,61 @@ 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=7 url complete +off=17 len=4 span[header_field]="Host" +off=22 header_field complete +off=23 len=14 span[header_value]="localhost:5000" +off=39 header_value complete +off=39 len=1 span[header_field]="x" +off=41 header_field complete +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=7 url complete +off=17 len=4 span[header_field]="Host" +off=22 header_field complete +off=23 len=14 span[header_value]="localhost:5000" +off=39 header_value complete +off=39 len=1 span[header_field]="x" +off=41 header_field complete +off=42 error code=10 reason="Invalid header value char" +``` + ### Invalid header token #1 @@ -228,7 +283,7 @@ off=33 header_value complete off=33 len=5 span[header_field]="Dummy" off=39 header_field complete 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" ``` ### Invalid HTTP version diff --git a/test/request/lenient-headers.md b/test/request/lenient-headers.md index 8918e604..ce2317cc 100644 --- a/test/request/lenient-headers.md +++ b/test/request/lenient-headers.md @@ -77,5 +77,6 @@ off=4 len=4 span[url]="/url" off=9 url complete off=19 len=7 span[header_field]="Header1" off=27 header_field complete +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 61d224a2..145b66de 100644 --- a/test/request/sample.md +++ b/test/request/sample.md @@ -357,7 +357,7 @@ off=6 url complete off=15 len=5 span[header_field]="Line1" off=21 header_field complete 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 194bf16e..3a596b9f 100644 --- a/test/request/transfer-encoding.md +++ b/test/request/transfer-encoding.md @@ -600,3 +600,31 @@ off=47 header_value complete 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=9 url complete +off=19 len=17 span[header_field]="Transfer-Encoding" +off=37 header_field complete +off=38 len=7 span[header_value]="chunked" +off=47 len=5 span[header_value]=" abc" +off=54 header_value complete +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 a1c6e144..d677b24c 100644 --- a/test/response/sample.md +++ b/test/response/sample.md @@ -274,7 +274,7 @@ off=16 status complete off=16 len=12 span[header_field]="Content-Type" off=29 header_field complete 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 42fb588d..6005e049 100644 --- a/test/response/transfer-encoding.md +++ b/test/response/transfer-encoding.md @@ -148,4 +148,43 @@ 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 status complete +off=17 len=17 span[header_field]="Transfer-Encoding" +off=35 header_field complete +off=36 len=7 span[header_value]="chunked" +off=45 len=5 span[header_value]=" abc" +off=52 header_value complete +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 07cf037a904240a3a095d796f6e61ed2f563a8fb Mon Sep 17 00:00:00 2001 From: Paolo Insogna Date: Fri, 23 Sep 2022 23:33:09 +0200 Subject: [PATCH 2/8] 6.0.10 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6dcbec5a..f9f799e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { "name": "llhttp", - "version": "6.0.9", + "version": "6.0.10", "lockfileVersion": 2, "requires": true, "packages": { "": { - "version": "6.0.9", + "version": "6.0.10", "license": "MIT", "dependencies": { "@types/semver": "^5.5.0", diff --git a/package.json b/package.json index 3f73b5f7..574f40b8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "llhttp", - "version": "6.0.9", + "version": "6.0.10", "description": "HTTP parser in LLVM IR", "main": "lib/llhttp.js", "types": "lib/llhttp.d.ts", From 94a8e7e2588a62de9c1337ea1b02e6ea5c06acbd Mon Sep 17 00:00:00 2001 From: Paolo Insogna Date: Wed, 21 Jun 2023 10:23:37 +0200 Subject: [PATCH 3/8] fix: Do not allow empty header separated by CR. (#228) --- Makefile | 2 +- package.json | 2 +- src/llhttp.gyp | 11 ++++++++- src/llhttp/http.ts | 19 ++++++++------- test/request/invalid.md | 32 +++++++++++++++++++++++++ test/request/lenient-headers.md | 42 +++++++++++++++++++++++++++++++++ test/request/sample.md | 2 +- 7 files changed, 97 insertions(+), 13 deletions(-) diff --git a/Makefile b/Makefile index 852385aa..e3ed2ca0 100644 --- a/Makefile +++ b/Makefile @@ -51,7 +51,7 @@ release: generate cp -rf src/llhttp.gyp release/ cp -rf src/common.gypi release/ cp -rf CMakeLists.txt release/ - sed -i s/_TAG_/$(TAG)/ release/CMakeLists.txt + sed -i '' s/_TAG_/$(TAG)/ release/CMakeLists.txt cp -rf libllhttp.pc.in release/ cp -rf README.md release/ cp -rf LICENSE-MIT release/ diff --git a/package.json b/package.json index 574f40b8..907c74cc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "llhttp", - "version": "6.0.10", + "version": "6.0.11", "description": "HTTP parser in LLVM IR", "main": "lib/llhttp.js", "types": "lib/llhttp.d.ts", diff --git a/src/llhttp.gyp b/src/llhttp.gyp index 4acc79bd..c7b8800a 100644 --- a/src/llhttp.gyp +++ b/src/llhttp.gyp @@ -1,4 +1,11 @@ { + 'variables': { + 'llhttp_sources': [ + 'src/llhttp.c', + 'src/api.c', + 'src/http.c', + ] + }, 'targets': [ { 'target_name': 'llhttp', @@ -7,7 +14,9 @@ 'direct_dependent_settings': { 'include_dirs': [ 'include' ], }, - 'sources': [ 'src/llhttp.c', 'src/api.c', 'src/http.c' ], + 'sources': [ + '<@(llhttp_sources)', + ], }, ] } diff --git a/src/llhttp/http.ts b/src/llhttp/http.ts index 72291be2..e1e44d50 100644 --- a/src/llhttp/http.ts +++ b/src/llhttp/http.ts @@ -481,14 +481,13 @@ export class HTTP { }, p.error(ERROR.INVALID_HEADER_TOKEN, 'Invalid header value char'))) .otherwise(span.headerValue.start(n('header_value_start'))); - if (this.mode === 'strict') { - n('header_value_discard_ws_almost_done') - .match('\n', n('header_value_discard_lws')) - .otherwise(p.error(ERROR.STRICT, 'Expected LF after CR')); - } else { - n('header_value_discard_ws_almost_done').skipTo( - n('header_value_discard_lws')); - } + n('header_value_discard_ws_almost_done') + .match('\n', n('header_value_discard_lws')) + .otherwise( + this.testLenientFlags(LENIENT_FLAGS.HEADERS, { + 1: n('header_value_discard_lws'), + }, p.error(ERROR.STRICT, 'Expected LF after CR')), + ); const onHeaderValueComplete = p.invoke(this.callback.onHeaderValueComplete); onHeaderValueComplete.otherwise(n('header_field_start')); @@ -501,7 +500,9 @@ export class HTTP { this.emptySpan(span.headerValue, onHeaderValueComplete))); n('header_value_discard_lws') - .match([ ' ', '\t' ], n('header_value_discard_ws')) + .match([ ' ', '\t' ], this.testLenientFlags(LENIENT_FLAGS.HEADERS, { + 1: n('header_value_discard_ws'), + }, p.error(ERROR.INVALID_HEADER_TOKEN, 'Invalid header value char'))) .otherwise(checkContentLengthEmptiness); // Multiple `Transfer-Encoding` headers should be treated as one, but with diff --git a/test/request/invalid.md b/test/request/invalid.md index 224a0af7..b3531710 100644 --- a/test/request/invalid.md +++ b/test/request/invalid.md @@ -117,6 +117,38 @@ off=41 len=1 span[header_value]="x" off=42 error code=10 reason="Invalid header value char" ``` +### Empty headers separated by CR + + +```http +POST / HTTP/1.1 +Connection: Close +Host: localhost:5000 +x:\rTransfer-Encoding: chunked + +1 +A +0 + +``` + +```log +off=0 message begin +off=5 len=1 span[url]="/" +off=7 url complete +off=17 len=10 span[header_field]="Connection" +off=28 header_field complete +off=29 len=5 span[header_value]="Close" +off=36 header_value complete +off=36 len=4 span[header_field]="Host" +off=41 header_field complete +off=42 len=14 span[header_value]="localhost:5000" +off=58 header_value complete +off=58 len=1 span[header_field]="x" +off=60 header_field complete +off=61 error code=2 reason="Expected LF after CR" +``` + ### Empty headers separated by LF diff --git a/test/request/lenient-headers.md b/test/request/lenient-headers.md index ce2317cc..32802c9c 100644 --- a/test/request/lenient-headers.md +++ b/test/request/lenient-headers.md @@ -80,3 +80,45 @@ off=27 header_field complete off=28 len=0 span[header_value]="" off=28 error code=10 reason="Invalid header value char" ``` + +### Empty headers separated by CR (Lenient) + + +```http +POST / HTTP/1.1 +Connection: Close +Host: localhost:5000 +x:\rTransfer-Encoding: chunked + +1 +A +0 + +``` + +```log +off=0 message begin +off=5 len=1 span[url]="/" +off=7 url complete +off=17 len=10 span[header_field]="Connection" +off=28 header_field complete +off=29 len=5 span[header_value]="Close" +off=36 header_value complete +off=36 len=4 span[header_field]="Host" +off=41 header_field complete +off=42 len=14 span[header_value]="localhost:5000" +off=58 header_value complete +off=58 len=1 span[header_field]="x" +off=60 header_field complete +off=61 len=0 span[header_value]="" +off=61 header_value complete +off=61 len=17 span[header_field]="Transfer-Encoding" +off=79 header_field complete +off=80 len=7 span[header_value]="chunked" +off=89 header_value complete +off=91 headers complete method=3 v=1/1 flags=20a content_length=0 +off=94 chunk header len=1 +off=94 len=1 span[body]="A" +off=97 chunk complete +off=100 chunk header len=0 +``` \ No newline at end of file diff --git a/test/request/sample.md b/test/request/sample.md index 145b66de..ce56c236 100644 --- a/test/request/sample.md +++ b/test/request/sample.md @@ -276,7 +276,7 @@ off=9 message complete ## Line folding in header value with CRLF - + ```http GET / HTTP/1.1 Line1: abc From ef539d7293fc28577d45f4d7eb6ff5aabb59b372 Mon Sep 17 00:00:00 2001 From: Paolo Insogna Date: Tue, 9 Jan 2024 17:59:21 +0100 Subject: [PATCH 4/8] feat: Added on_chunk_parameters callback. (#264) --- src/llhttp/http.ts | 11 +++++++++-- src/native/api.c | 7 +++++++ src/native/api.h | 3 +++ test/fixtures/extra.c | 6 ++++++ test/request/transfer-encoding.md | 4 +++- test/response/transfer-encoding.md | 14 +------------- 6 files changed, 29 insertions(+), 16 deletions(-) diff --git a/src/llhttp/http.ts b/src/llhttp/http.ts index e1e44d50..5dd9f631 100644 --- a/src/llhttp/http.ts +++ b/src/llhttp/http.ts @@ -84,6 +84,7 @@ const NODES: ReadonlyArray = [ 'chunk_size_almost_done', 'chunk_size_almost_done_lf', 'chunk_parameters', + 'chunk_parameters_ows', 'chunk_data', 'chunk_data_almost_done', 'chunk_data_almost_done_skip', @@ -101,6 +102,7 @@ const NODES: ReadonlyArray = [ interface ISpanMap { readonly body: source.Span; + readonly chunkParameters: source.Span; readonly headerField: source.Span; readonly headerValue: source.Span; readonly status: source.Span; @@ -157,6 +159,7 @@ export class HTTP { this.span = { body: p.span(p.code.span('llhttp__on_body')), + chunkParameters: p.span(p.code.span('llhttp__on_chunk_parameters')), headerField: p.span(p.code.span('llhttp__on_header_field')), headerValue: p.span(p.code.span('llhttp__on_header_value')), status: p.span(p.code.span('llhttp__on_status')), @@ -798,12 +801,16 @@ export class HTTP { n('chunk_size_otherwise') .match('\r', n('chunk_size_almost_done')) - .match([ ';', ' ' ], n('chunk_parameters')) + .match([ ';' ], n('chunk_parameters_ows')) .otherwise(p.error(ERROR.INVALID_CHUNK_SIZE, 'Invalid character in chunk size')); + n('chunk_parameters_ows') + .match(' ', n('chunk_parameters_ows')) + .otherwise(span.chunkParameters.start(n('chunk_parameters'))); + n('chunk_parameters') - .match('\r', n('chunk_size_almost_done')) + .peek('\r', span.chunkParameters.end().skipTo(n('chunk_size_almost_done'))) .match(HEADER_CHARS, n('chunk_parameters')) .otherwise(p.error(ERROR.STRICT, 'Invalid character in chunk parameters')); diff --git a/src/native/api.c b/src/native/api.c index c4ce197c..d3065b36 100644 --- a/src/native/api.c +++ b/src/native/api.c @@ -355,6 +355,13 @@ int llhttp__on_chunk_header(llhttp_t* s, const char* p, const char* endp) { } +int llhttp__on_chunk_parameters(llhttp_t* s, const char* p, const char* endp) { + int err; + SPAN_CALLBACK_MAYBE(s, on_chunk_parameters, p, endp - p); + return err; +} + + int llhttp__on_chunk_complete(llhttp_t* s, const char* p, const char* endp) { int err; CALLBACK_MAYBE(s, on_chunk_complete); diff --git a/src/native/api.h b/src/native/api.h index 0e71d1a6..445aa9c8 100644 --- a/src/native/api.h +++ b/src/native/api.h @@ -38,6 +38,9 @@ struct llhttp_settings_s { */ llhttp_cb on_headers_complete; + /* Possible return values 0, -1, HPE_USER */ + llhttp_data_cb on_chunk_parameters; + /* Possible return values 0, -1, HPE_USER */ llhttp_data_cb on_body; diff --git a/test/fixtures/extra.c b/test/fixtures/extra.c index 7528a036..7e85a36b 100644 --- a/test/fixtures/extra.c +++ b/test/fixtures/extra.c @@ -203,6 +203,12 @@ int llhttp__on_body(llparse_t* s, const char* p, const char* endp) { return llparse__print_span("body", p, endp); } +int llhttp__on_chunk_parameters(llparse_t* s, const char* p, const char* endp) { + if (llparse__in_bench) + return 0; + return llparse__print_span("chunk parameters", p, endp); +} + int llhttp__on_chunk_header(llparse_t* s, const char* p, const char* endp) { if (llparse__in_bench) diff --git a/test/request/transfer-encoding.md b/test/request/transfer-encoding.md index 3a596b9f..e0c4ae5e 100644 --- a/test/request/transfer-encoding.md +++ b/test/request/transfer-encoding.md @@ -223,9 +223,11 @@ off=66 header_field complete off=67 len=7 span[header_value]="chunked" off=76 header_value complete off=78 headers complete method=3 v=1/1 flags=208 content_length=0 +off=81 len=40 span[chunk parameters]="ilovew3;somuchlove=aretheseparametersfor" off=123 chunk header len=5 off=123 len=5 span[body]="hello" off=130 chunk complete +off=133 len=14 span[chunk parameters]="blahblah; blah" off=149 chunk header len=6 off=149 len=6 span[body]=" world" off=157 chunk complete @@ -598,7 +600,7 @@ off=37 header_field complete off=38 len=7 span[header_value]="chunked" off=47 header_value complete 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" +off=50 error code=12 reason="Invalid character in chunk size" ``` ## Invalid OBS fold after chunked value diff --git a/test/response/transfer-encoding.md b/test/response/transfer-encoding.md index 6005e049..c43e3590 100644 --- a/test/response/transfer-encoding.md +++ b/test/response/transfer-encoding.md @@ -33,19 +33,7 @@ off=61 header_field complete off=62 len=7 span[header_value]="chunked" off=71 header_value complete off=73 headers complete status=200 v=1/1 flags=208 content_length=0 -off=79 chunk header len=37 -off=79 len=35 span[body]="This is the data in the first chunk" -off=114 len=1 span[body]=cr -off=115 len=1 span[body]=lf -off=118 chunk complete -off=122 chunk header len=28 -off=122 len=26 span[body]="and this is the second one" -off=148 len=1 span[body]=cr -off=149 len=1 span[body]=lf -off=152 chunk complete -off=157 chunk header len=0 -off=159 chunk complete -off=159 message complete +off=75 error code=12 reason="Invalid character in chunk size" ``` ### `chunked` before other transfer-encoding From 81dae757253c653ae47cd555bcb1da3e2f2323ca Mon Sep 17 00:00:00 2001 From: Paolo Insogna Date: Tue, 9 Jan 2024 18:01:05 +0100 Subject: [PATCH 5/8] 6.0.12 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index f9f799e7..9879188e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { "name": "llhttp", - "version": "6.0.10", + "version": "6.0.12", "lockfileVersion": 2, "requires": true, "packages": { "": { - "version": "6.0.10", + "version": "6.0.12", "license": "MIT", "dependencies": { "@types/semver": "^5.5.0", diff --git a/package.json b/package.json index 907c74cc..641e7f30 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "llhttp", - "version": "6.0.11", + "version": "6.0.12", "description": "HTTP parser in LLVM IR", "main": "lib/llhttp.js", "types": "lib/llhttp.d.ts", From 0ed1d84ceef966e638cf5e72bb60be6731e8f32b Mon Sep 17 00:00:00 2001 From: Paolo Insogna Date: Tue, 9 Jan 2024 18:04:24 +0100 Subject: [PATCH 6/8] 6.1.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9879188e..70d25250 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { "name": "llhttp", - "version": "6.0.12", + "version": "6.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { - "version": "6.0.12", + "version": "6.1.0", "license": "MIT", "dependencies": { "@types/semver": "^5.5.0", diff --git a/package.json b/package.json index 641e7f30..addb6491 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "llhttp", - "version": "6.0.12", + "version": "6.1.0", "description": "HTTP parser in LLVM IR", "main": "lib/llhttp.js", "types": "lib/llhttp.d.ts", From 338e41124d431cc30396462daa9d7c815952ff7e Mon Sep 17 00:00:00 2001 From: Paolo Insogna Date: Thu, 4 Apr 2024 13:04:37 +0200 Subject: [PATCH 7/8] fix: Do not allow OBS fold in headers by default. (#350) --- src/llhttp/http.ts | 13 +++--- test/md-test.ts | 2 + test/request/connection.md | 4 +- test/request/invalid.md | 72 ++++++++++++++++++++++++++++++ test/request/sample.md | 2 +- test/request/transfer-encoding.md | 2 +- test/response/transfer-encoding.md | 2 +- tsconfig.json | 3 +- 8 files changed, 89 insertions(+), 11 deletions(-) diff --git a/src/llhttp/http.ts b/src/llhttp/http.ts index 5dd9f631..498dde05 100644 --- a/src/llhttp/http.ts +++ b/src/llhttp/http.ts @@ -667,11 +667,14 @@ export class HTTP { 'Missing expected LF after header value')); n('header_value_lws') - .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')))) + .peek( + [ ' ', '\t' ], + this.testLenientFlags(LENIENT_FLAGS.HEADERS, { + 1: 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'))), + }, p.error(ERROR.INVALID_HEADER_TOKEN, 'Unexpected whitespace after header value'))) .otherwise(this.setHeaderFlags(onHeaderValueComplete)); const checkTrailing = this.testFlags(FLAGS.TRAILING, { diff --git a/test/md-test.ts b/test/md-test.ts index 0fd4adbc..3a6b3874 100644 --- a/test/md-test.ts +++ b/test/md-test.ts @@ -86,6 +86,7 @@ const http: IFixtureMap = { 'request-lenient-version': buildMode( 'loose', 'request-lenient-version'), 'response': buildMode('loose', 'response'), 'response-finish': buildMode('loose', 'response-finish'), + 'response-lenient-headers': buildMode( 'loose', 'response-lenient-headers'), 'response-lenient-keep-alive': buildMode( 'loose', 'response-lenient-keep-alive'), 'response-lenient-version': buildMode( 'loose', 'response-lenient-version'), 'url': buildMode('loose', 'url'), @@ -101,6 +102,7 @@ const http: IFixtureMap = { 'request-lenient-version': buildMode( 'strict', 'request-lenient-version'), 'response': buildMode('strict', 'response'), 'response-finish': buildMode('strict', 'response-finish'), + 'response-lenient-headers': buildMode('strict', 'response-lenient-headers'), 'response-lenient-keep-alive': buildMode('strict', 'response-lenient-keep-alive'), 'response-lenient-version': buildMode( 'strict', 'response-lenient-version'), 'url': buildMode('strict', 'url'), diff --git a/test/request/connection.md b/test/request/connection.md index 95d6f3d0..118ec27b 100644 --- a/test/request/connection.md +++ b/test/request/connection.md @@ -314,7 +314,7 @@ off=75 message complete ### Multiple tokens with folding - + ```http GET /demo HTTP/1.1 Host: example.com @@ -397,7 +397,7 @@ off=75 error code=22 reason="Pause on CONNECT/Upgrade" ### Multiple tokens with folding, LWS, and CRLF - + ```http GET /demo HTTP/1.1 Connection: keep-alive, \r\n upgrade diff --git a/test/request/invalid.md b/test/request/invalid.md index b3531710..b505cd1e 100644 --- a/test/request/invalid.md +++ b/test/request/invalid.md @@ -330,4 +330,76 @@ off=0 message begin off=4 len=1 span[url]="/" off=6 url complete off=14 error code=9 reason="Invalid HTTP version" +``` + +### Spaces before headers + + + +```http +POST /hello HTTP/1.1 +Host: localhost +Foo: bar + Content-Length: 38 + +GET /bye HTTP/1.1 +Host: localhost + + +``` + +```log +off=0 message begin +off=5 len=6 span[url]="/hello" +off=12 url complete +off=22 len=4 span[header_field]="Host" +off=27 header_field complete +off=28 len=9 span[header_value]="localhost" +off=39 header_value complete +off=39 len=3 span[header_field]="Foo" +off=43 header_field complete +off=44 len=3 span[header_value]="bar" +off=49 error code=10 reason="Unexpected whitespace after header value" +``` + +### Spaces before headers (lenient) + + + +```http +POST /hello HTTP/1.1 +Host: localhost +Foo: bar + Content-Length: 38 + +GET /bye HTTP/1.1 +Host: localhost + + +``` + +```log +off=0 message begin +off=5 len=6 span[url]="/hello" +off=12 url complete +off=22 len=4 span[header_field]="Host" +off=27 header_field complete +off=28 len=9 span[header_value]="localhost" +off=39 header_value complete +off=39 len=3 span[header_field]="Foo" +off=43 header_field complete +off=44 len=3 span[header_value]="bar" +off=49 len=19 span[header_value]=" Content-Length: 38" +off=70 header_value complete +off=72 headers complete method=3 v=1/1 flags=0 content_length=0 +off=72 message complete +off=72 message begin +off=76 len=4 span[url]="/bye" +off=81 url complete +off=91 len=4 span[header_field]="Host" +off=96 header_field complete +off=97 len=9 span[header_value]="localhost" +off=108 header_value complete +off=110 headers complete method=1 v=1/1 flags=0 content_length=0 +off=110 message complete ``` \ No newline at end of file diff --git a/test/request/sample.md b/test/request/sample.md index ce56c236..f740e14b 100644 --- a/test/request/sample.md +++ b/test/request/sample.md @@ -439,7 +439,7 @@ off=61 message complete See nodejs/test/parallel/test-http-headers-obstext.js - + ```http GET / HTTP/1.1 X-SSL-Nonsense: -----BEGIN CERTIFICATE----- diff --git a/test/request/transfer-encoding.md b/test/request/transfer-encoding.md index e0c4ae5e..119a0d85 100644 --- a/test/request/transfer-encoding.md +++ b/test/request/transfer-encoding.md @@ -605,7 +605,7 @@ off=50 error code=12 reason="Invalid character in chunk size" ## Invalid OBS fold after chunked value - + ```http PUT /url HTTP/1.1 Transfer-Encoding: chunked diff --git a/test/response/transfer-encoding.md b/test/response/transfer-encoding.md index c43e3590..dedf41d6 100644 --- a/test/response/transfer-encoding.md +++ b/test/response/transfer-encoding.md @@ -140,7 +140,7 @@ off=78 len=1 span[body]=lf ## Invalid OBS fold after chunked value - + ```http HTTP/1.1 200 OK Transfer-Encoding: chunked diff --git a/tsconfig.json b/tsconfig.json index 01ec7c26..b0775b15 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,7 +7,8 @@ "outDir": "./lib", "declaration": true, "pretty": true, - "sourceMap": true + "sourceMap": true, + "skipLibCheck": true }, "include": [ "src/**/*.ts" From 6d6527e47c5d9876e7dfc7251761226d07dd3cde Mon Sep 17 00:00:00 2001 From: Paolo Insogna Date: Thu, 4 Apr 2024 12:16:32 +0100 Subject: [PATCH 8/8] 6.1.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 70d25250..4e44a83b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { "name": "llhttp", - "version": "6.1.0", + "version": "6.1.1", "lockfileVersion": 2, "requires": true, "packages": { "": { - "version": "6.1.0", + "version": "6.1.1", "license": "MIT", "dependencies": { "@types/semver": "^5.5.0", diff --git a/package.json b/package.json index addb6491..26c82a8b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "llhttp", - "version": "6.1.0", + "version": "6.1.1", "description": "HTTP parser in LLVM IR", "main": "lib/llhttp.js", "types": "lib/llhttp.d.ts",