Skip to content

Commit b83f61b

Browse files
committed
perf: return relaxed header values to the fast scanner
1 parent ee51043 commit b83f61b

2 files changed

Lines changed: 60 additions & 2 deletions

File tree

‎src/llhttp/http.ts‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -863,9 +863,13 @@ export class HTTP {
863863
.peek('\n', span.headerValue.end(n('header_value_almost_done')))
864864
.skipTo(n('header_value_lenient'));
865865

866+
// After consuming a relaxed byte, return to the ordinary fast path.
867+
// Staying in this state would duplicate the entire header-value scanning
868+
// loop (including its vectorized blocks) on the hot state machine.
866869
n('header_value_relaxed')
867-
.match(RELAXED_HEADER_CHARS, n('header_value_relaxed'))
868-
.otherwise(n('header_value_otherwise'));
870+
.match(RELAXED_HEADER_CHARS, n('header_value'))
871+
.otherwise(span.headerValue.end(p.error(ERROR.INVALID_HEADER_TOKEN,
872+
'Invalid header value char')));
869873

870874
n('header_value_almost_done')
871875
.match('\n', n('header_value_lws'))

‎test/request/lenient-header-value-relaxed.md‎

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,60 @@ off=43 headers complete method=1 v=1/1 flags=0 content_length=0
3535
off=43 message complete
3636
```
3737

38+
## Consecutive control chars return to the ordinary header scanner
39+
40+
<!-- meta={"type": "request-lenient-header-value-relaxed"} -->
41+
```http
42+
GET /url HTTP/1.1
43+
Header1: hello\f\fworld
44+
45+
46+
```
47+
48+
```log
49+
off=0 message begin
50+
off=0 len=3 span[method]="GET"
51+
off=3 method complete
52+
off=4 len=4 span[url]="/url"
53+
off=9 url complete
54+
off=9 len=4 span[protocol]="HTTP"
55+
off=13 protocol complete
56+
off=14 len=3 span[version]="1.1"
57+
off=17 version complete
58+
off=19 len=7 span[header_field]="Header1"
59+
off=27 header_field complete
60+
off=28 len=12 span[header_value]="hello\f\fworld"
61+
off=42 header_value complete
62+
off=44 headers complete method=1 v=1/1 flags=0 content_length=0
63+
off=44 message complete
64+
```
65+
66+
## Bare CR after a relaxed control char is still rejected
67+
68+
<!-- meta={"type": "request-lenient-header-value-relaxed"} -->
69+
```http
70+
GET /url HTTP/1.1
71+
Header1: hello\f\rworld
72+
73+
74+
```
75+
76+
```log
77+
off=0 message begin
78+
off=0 len=3 span[method]="GET"
79+
off=3 method complete
80+
off=4 len=4 span[url]="/url"
81+
off=9 url complete
82+
off=9 len=4 span[protocol]="HTTP"
83+
off=13 protocol complete
84+
off=14 len=3 span[version]="1.1"
85+
off=17 version complete
86+
off=19 len=7 span[header_field]="Header1"
87+
off=27 header_field complete
88+
off=28 len=6 span[header_value]="hello\f"
89+
off=35 error code=3 reason="Missing expected LF after header value"
90+
```
91+
3892
## Control char in header value (strict)
3993

4094
Control characters should be rejected in strict mode.

0 commit comments

Comments
 (0)