diff --git a/src/llhttp/http.ts b/src/llhttp/http.ts index 2f49e30..fa3ea14 100644 --- a/src/llhttp/http.ts +++ b/src/llhttp/http.ts @@ -863,9 +863,13 @@ export class HTTP { .peek('\n', span.headerValue.end(n('header_value_almost_done'))) .skipTo(n('header_value_lenient')); + // After consuming a relaxed byte, return to the ordinary fast path. + // Staying in this state would duplicate the entire header-value scanning + // loop (including its vectorized blocks) on the hot state machine. n('header_value_relaxed') - .match(RELAXED_HEADER_CHARS, n('header_value_relaxed')) - .otherwise(n('header_value_otherwise')); + .match(RELAXED_HEADER_CHARS, n('header_value')) + .otherwise(span.headerValue.end(p.error(ERROR.INVALID_HEADER_TOKEN, + 'Invalid header value char'))); n('header_value_almost_done') .match('\n', n('header_value_lws')) diff --git a/test/request/lenient-header-value-relaxed.md b/test/request/lenient-header-value-relaxed.md index bc0cecb..76ef32f 100644 --- a/test/request/lenient-header-value-relaxed.md +++ b/test/request/lenient-header-value-relaxed.md @@ -35,6 +35,60 @@ off=43 headers complete method=1 v=1/1 flags=0 content_length=0 off=43 message complete ``` +## Consecutive control chars return to the ordinary header scanner + + +```http +GET /url HTTP/1.1 +Header1: hello\f\fworld + + +``` + +```log +off=0 message begin +off=0 len=3 span[method]="GET" +off=3 method complete +off=4 len=4 span[url]="/url" +off=9 url complete +off=9 len=4 span[protocol]="HTTP" +off=13 protocol complete +off=14 len=3 span[version]="1.1" +off=17 version complete +off=19 len=7 span[header_field]="Header1" +off=27 header_field complete +off=28 len=12 span[header_value]="hello\f\fworld" +off=42 header_value complete +off=44 headers complete method=1 v=1/1 flags=0 content_length=0 +off=44 message complete +``` + +## Bare CR after a relaxed control char is still rejected + + +```http +GET /url HTTP/1.1 +Header1: hello\f\rworld + + +``` + +```log +off=0 message begin +off=0 len=3 span[method]="GET" +off=3 method complete +off=4 len=4 span[url]="/url" +off=9 url complete +off=9 len=4 span[protocol]="HTTP" +off=13 protocol complete +off=14 len=3 span[version]="1.1" +off=17 version complete +off=19 len=7 span[header_field]="Header1" +off=27 header_field complete +off=28 len=6 span[header_value]="hello\f" +off=35 error code=3 reason="Missing expected LF after header value" +``` + ## Control char in header value (strict) Control characters should be rejected in strict mode.