diff --git a/src/llhttp/constants.ts b/src/llhttp/constants.ts index bdf8afbe..00fc5239 100644 --- a/src/llhttp/constants.ts +++ b/src/llhttp/constants.ts @@ -504,6 +504,17 @@ for (let i = 0x21; i <= 0xff; i++) { } } +export const HTAB_SP_VCHAR_OBS_TEXT: CharList = [ '\t', ' ' ]; + +// VCHAR: https://tools.ietf.org/html/rfc5234#appendix-B.1 +for (let i = 0x21; i <= 0x7E; i++) { + HTAB_SP_VCHAR_OBS_TEXT.push(i); +} +// OBS_TEXT: https://datatracker.ietf.org/doc/html/rfc9110#name-collected-abnf +for (let i = 0x80; i <= 0xff; i++) { + HTAB_SP_VCHAR_OBS_TEXT.push(i); +} + export const MAJOR = NUM_MAP; export const MINOR = MAJOR; diff --git a/src/llhttp/http.ts b/src/llhttp/http.ts index 71a7c422..d0bfd480 100644 --- a/src/llhttp/http.ts +++ b/src/llhttp/http.ts @@ -7,7 +7,7 @@ import Node = source.node.Node; import { CharList, CONNECTION_TOKEN_CHARS, ERROR, FINISH, FLAGS, H_METHOD_MAP, HEADER_CHARS, - HEADER_STATE, HEX_MAP, + HEADER_STATE, HEX_MAP, HTAB_SP_VCHAR_OBS_TEXT, LENIENT_FLAGS, MAJOR, METHOD_MAP, METHODS, METHODS_HTTP, METHODS_ICE, METHODS_RTSP, MINOR, NUM_MAP, QUOTED_STRING, SPECIAL_HEADERS, @@ -94,6 +94,7 @@ const NODES: ReadonlyArray = [ 'chunk_extension_name', 'chunk_extension_value', 'chunk_extension_quoted_value', + 'chunk_extension_quoted_value_quoted_pair', 'chunk_extension_quoted_value_done', 'chunk_data', 'chunk_data_almost_done', @@ -989,10 +990,17 @@ export class HTTP { .match('"', this.span.chunkExtensionValue.end( onChunkExtensionValueCompleted(n('chunk_extension_quoted_value_done')), )) + .match('\\', n('chunk_extension_quoted_value_quoted_pair')) .otherwise(this.span.chunkExtensionValue.end().skipTo( p.error(ERROR.STRICT, 'Invalid character in chunk extensions quoted value'), )); + n('chunk_extension_quoted_value_quoted_pair') + .match(HTAB_SP_VCHAR_OBS_TEXT, n('chunk_extension_quoted_value')) + .otherwise(this.span.chunkExtensionValue.end().skipTo( + p.error(ERROR.STRICT, 'Invalid quoted-pair in chunk extensions quoted value'), + )); + n('chunk_extension_quoted_value_done') .match(';', n('chunk_extensions')) .match('\r', n('chunk_size_almost_done')) diff --git a/test/request/transfer-encoding.md b/test/request/transfer-encoding.md index 69e8cf9a..0f839bcc 100644 --- a/test/request/transfer-encoding.md +++ b/test/request/transfer-encoding.md @@ -352,7 +352,7 @@ off=98 error code=2 reason="Invalid character in chunk extensions" POST /chunked_w_unicorns_after_length HTTP/1.1 Transfer-Encoding: chunked -5;ilovew3="I love; extensions";somuchlove="aretheseparametersfor";blah;foo=bar +5;ilovew3="I \"love\"; \\extensions\\";somuchlove="aretheseparametersfor";blah;foo=bar hello 6;blahblah;blah world @@ -375,29 +375,29 @@ off=76 header_value complete off=78 headers complete method=3 v=1/1 flags=208 content_length=0 off=80 len=7 span[chunk_extension_name]="ilovew3" off=88 chunk_extension_name complete -off=88 len=20 span[chunk_extension_value]=""I love; extensions"" -off=108 chunk_extension_value complete -off=109 len=10 span[chunk_extension_name]="somuchlove" -off=120 chunk_extension_name complete -off=120 len=23 span[chunk_extension_value]=""aretheseparametersfor"" -off=143 chunk_extension_value complete -off=144 len=4 span[chunk_extension_name]="blah" -off=149 chunk_extension_name complete -off=149 len=3 span[chunk_extension_name]="foo" -off=153 chunk_extension_name complete -off=153 len=3 span[chunk_extension_value]="bar" -off=157 chunk_extension_value complete -off=158 chunk header len=5 -off=158 len=5 span[body]="hello" -off=165 chunk complete -off=167 len=8 span[chunk_extension_name]="blahblah" -off=176 chunk_extension_name complete -off=176 len=4 span[chunk_extension_name]="blah" -off=181 chunk_extension_name complete -off=182 chunk header len=6 -off=182 len=6 span[body]=" world" -off=190 chunk complete -off=193 chunk header len=0 +off=88 len=28 span[chunk_extension_value]=""I \"love\"; \\extensions\\"" +off=116 chunk_extension_value complete +off=117 len=10 span[chunk_extension_name]="somuchlove" +off=128 chunk_extension_name complete +off=128 len=23 span[chunk_extension_value]=""aretheseparametersfor"" +off=151 chunk_extension_value complete +off=152 len=4 span[chunk_extension_name]="blah" +off=157 chunk_extension_name complete +off=157 len=3 span[chunk_extension_name]="foo" +off=161 chunk_extension_name complete +off=161 len=3 span[chunk_extension_value]="bar" +off=165 chunk_extension_value complete +off=166 chunk header len=5 +off=166 len=5 span[body]="hello" +off=173 chunk complete +off=175 len=8 span[chunk_extension_name]="blahblah" +off=184 chunk_extension_name complete +off=184 len=4 span[chunk_extension_name]="blah" +off=189 chunk_extension_name complete +off=190 chunk header len=6 +off=190 len=6 span[body]=" world" +off=198 chunk complete +off=201 chunk header len=0 ```