Skip to content

Commit fcf2212

Browse files
committed
http: add lenient flag for HTTP version parsing
1 parent 2101d70 commit fcf2212

File tree

7 files changed

+96
-45
lines changed

7 files changed

+96
-45
lines changed

src/llhttp/constants.ts

+1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ export enum LENIENT_FLAGS {
5858
HEADERS = 1 << 0,
5959
CHUNKED_LENGTH = 1 << 1,
6060
KEEP_ALIVE = 1 << 2,
61+
VERSION = 1 << 3,
6162
}
6263

6364
export enum METHODS {

src/llhttp/http.ts

+22-29
Original file line numberDiff line numberDiff line change
@@ -243,8 +243,27 @@ export class HTTP {
243243
.match('HTTP/', this.update('type', TYPE.RESPONSE, 'res_http_major'))
244244
.otherwise(p.error(ERROR.INVALID_CONSTANT, 'Invalid word encountered'));
245245

246-
// Response
246+
const checkVersion = (destination: string): Node => {
247+
return this.testLenientFlags(LENIENT_FLAGS.VERSION,
248+
{
249+
1: n(destination),
250+
},
251+
this.load('http_major', {
252+
0: this.load('http_minor', {
253+
9: n(destination),
254+
}, p.error(ERROR.INVALID_VERSION, 'Invalid HTTP version')),
255+
1: this.load('http_minor', {
256+
0: n(destination),
257+
1: n(destination),
258+
}, p.error(ERROR.INVALID_VERSION, 'Invalid HTTP version')),
259+
2: this.load('http_minor', {
260+
0: n(destination),
261+
}, p.error(ERROR.INVALID_VERSION, 'Invalid HTTP version')),
262+
}, p.error(ERROR.INVALID_VERSION, 'Invalid HTTP version')),
263+
);
264+
};
247265

266+
// Response
248267
n('start_res')
249268
.match('HTTP/', n('res_http_major'))
250269
.otherwise(p.error(ERROR.INVALID_CONSTANT, 'Expected HTTP/'));
@@ -258,20 +277,7 @@ export class HTTP {
258277
.otherwise(p.error(ERROR.INVALID_VERSION, 'Expected dot'));
259278

260279
n('res_http_minor')
261-
.select(MINOR, this.store('http_minor',
262-
this.load('http_major', {
263-
0: this.load('http_minor', {
264-
9: n('res_http_end'),
265-
}, p.error(ERROR.INVALID_VERSION, 'Invalid HTTP version')),
266-
1: this.load('http_minor', {
267-
0: n('res_http_end'),
268-
1: n('res_http_end'),
269-
}, p.error(ERROR.INVALID_VERSION, 'Invalid HTTP version')),
270-
2: this.load('http_minor', {
271-
0: n('res_http_end'),
272-
}, p.error(ERROR.INVALID_VERSION, 'Invalid HTTP version')),
273-
}, p.error(ERROR.INVALID_VERSION, 'Invalid HTTP version')),
274-
))
280+
.select(MINOR, this.store('http_minor', checkVersion('res_http_end')))
275281
.otherwise(p.error(ERROR.INVALID_VERSION, 'Invalid minor version'));
276282

277283
n('res_http_end')
@@ -377,20 +383,7 @@ export class HTTP {
377383
.otherwise(p.error(ERROR.INVALID_VERSION, 'Expected dot'));
378384

379385
n('req_http_minor')
380-
.select(MINOR, this.store('http_minor',
381-
this.load('http_major', {
382-
0: this.load('http_minor', {
383-
9: n('req_http_end'),
384-
}, p.error(ERROR.INVALID_VERSION, 'Invalid HTTP version')),
385-
1: this.load('http_minor', {
386-
0: n('req_http_end'),
387-
1: n('req_http_end'),
388-
}, p.error(ERROR.INVALID_VERSION, 'Invalid HTTP version')),
389-
2: this.load('http_minor', {
390-
0: n('req_http_end'),
391-
}, p.error(ERROR.INVALID_VERSION, 'Invalid HTTP version')),
392-
}, p.error(ERROR.INVALID_VERSION, 'Invalid HTTP version')),
393-
))
386+
.select(MINOR, this.store('http_minor', checkVersion('req_http_end')))
394387
.otherwise(p.error(ERROR.INVALID_VERSION, 'Invalid minor version'));
395388

396389
n('req_http_end').otherwise(this.load('method', {

test/fixtures/extra.c

+11
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,22 @@ void llhttp__test_init_request_lenient_keep_alive(llparse_t* s) {
8383
}
8484

8585

86+
void llhttp__test_init_request_lenient_version(llparse_t* s) {
87+
llhttp__test_init_request(s);
88+
s->lenient_flags |= LENIENT_VERSION;
89+
}
90+
91+
8692
void llhttp__test_init_response_lenient_keep_alive(llparse_t* s) {
8793
llhttp__test_init_response(s);
8894
s->lenient_flags |= LENIENT_KEEP_ALIVE;
8995
}
9096

97+
void llhttp__test_init_response_lenient_version(llparse_t* s) {
98+
llhttp__test_init_response(s);
99+
s->lenient_flags |= LENIENT_VERSION;
100+
}
101+
91102

92103
void llhttp__test_finish(llparse_t* s) {
93104
llparse__print(NULL, NULL, "finish=%d", s->finish);

test/fixtures/index.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import * as path from 'path';
99
import * as llhttp from '../../src/llhttp';
1010

1111
export type TestType = 'request' | 'response' | 'request-lenient-headers' |
12-
'request-lenient-chunked-length' | 'request-lenient-keep-alive' |
13-
'response-lenient-keep-alive' | 'request-finish' | 'response-finish' |
12+
'request-lenient-chunked-length' | 'request-lenient-keep-alive' | 'request-lenient-version' |
13+
'response-lenient-keep-alive' | 'response-lenient-version' | 'request-finish' | 'response-finish' |
1414
'none' | 'url';
1515

1616
export { FixtureResult };
@@ -64,7 +64,9 @@ export async function build(
6464
ty === 'request-lenient-headers' ||
6565
ty === 'request-lenient-chunked-length' ||
6666
ty === 'request-lenient-keep-alive' ||
67-
ty === 'response-lenient-keep-alive') {
67+
ty === 'request-lenient-version' ||
68+
ty === 'response-lenient-keep-alive' ||
69+
ty === 'response-lenient-version') {
6870
extra.push(
6971
`-DLLPARSE__TEST_INIT=llhttp__test_init_${ty.replace(/-/g, '_')}`);
7072
} else if (ty === 'request-finish' || ty === 'response-finish') {

test/md-test.ts

+16-13
Original file line numberDiff line numberDiff line change
@@ -79,30 +79,28 @@ const http: IFixtureMap = {
7979
'none': buildMode('loose', 'none'),
8080
'request': buildMode('loose', 'request'),
8181
'request-finish': buildMode('loose', 'request-finish'),
82-
'request-lenient-chunked-length':
83-
buildMode('loose', 'request-lenient-chunked-length'),
82+
'request-lenient-chunked-length': buildMode('loose', 'request-lenient-chunked-length'),
8483
'request-lenient-headers': buildMode('loose', 'request-lenient-headers'),
85-
'request-lenient-keep-alive': buildMode(
86-
'loose', 'request-lenient-keep-alive'),
84+
'request-lenient-keep-alive': buildMode( 'loose', 'request-lenient-keep-alive'),
85+
'request-lenient-version': buildMode( 'loose', 'request-lenient-version'),
8786
'response': buildMode('loose', 'response'),
8887
'response-finish': buildMode('loose', 'response-finish'),
89-
'response-lenient-keep-alive': buildMode(
90-
'loose', 'response-lenient-keep-alive'),
88+
'response-lenient-keep-alive': buildMode( 'loose', 'response-lenient-keep-alive'),
89+
'response-lenient-version': buildMode( 'loose', 'response-lenient-version'),
9190
'url': buildMode('loose', 'url'),
9291
},
9392
strict: {
9493
'none': buildMode('strict', 'none'),
9594
'request': buildMode('strict', 'request'),
9695
'request-finish': buildMode('strict', 'request-finish'),
97-
'request-lenient-chunked-length':
98-
buildMode('strict', 'request-lenient-chunked-length'),
96+
'request-lenient-chunked-length': buildMode('strict', 'request-lenient-chunked-length'),
9997
'request-lenient-headers': buildMode('strict', 'request-lenient-headers'),
100-
'request-lenient-keep-alive': buildMode(
101-
'strict', 'request-lenient-keep-alive'),
98+
'request-lenient-keep-alive': buildMode( 'strict', 'request-lenient-keep-alive'),
99+
'request-lenient-version': buildMode( 'strict', 'request-lenient-version'),
102100
'response': buildMode('strict', 'response'),
103101
'response-finish': buildMode('strict', 'response-finish'),
104-
'response-lenient-keep-alive': buildMode(
105-
'strict', 'response-lenient-keep-alive'),
102+
'response-lenient-keep-alive': buildMode('strict', 'response-lenient-keep-alive'),
103+
'response-lenient-version': buildMode( 'strict', 'response-lenient-version'),
106104
'url': buildMode('strict', 'url'),
107105
},
108106
};
@@ -165,8 +163,12 @@ function run(name: string): void {
165163
types = [ 'request-lenient-chunked-length' ];
166164
} else if (meta.type === 'request-lenient-keep-alive') {
167165
types = [ 'request-lenient-keep-alive' ];
166+
} else if (meta.type === 'request-lenient-version') {
167+
types = [ 'request-lenient-version' ];
168168
} else if (meta.type === 'response-lenient-keep-alive') {
169169
types = [ 'response-lenient-keep-alive' ];
170+
} else if (meta.type === 'response-lenient-version') {
171+
types = [ 'response-lenient-version' ];
170172
} else if (meta.type === 'response-only') {
171173
types = [ 'response' ];
172174
} else if (meta.type === 'request-finish') {
@@ -272,6 +274,7 @@ function run(name: string): void {
272274

273275
run('request/sample');
274276
run('request/lenient-headers');
277+
run('request/lenient-version');
275278
run('request/method');
276279
run('request/uri');
277280
run('request/connection');
@@ -286,5 +289,5 @@ run('response/content-length');
286289
run('response/transfer-encoding');
287290
run('response/invalid');
288291
run('response/finish');
289-
292+
run('request/lenient-version');
290293
run('url');

test/request/lenient-version.md

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
Lenient HTTP version parsing
2+
============================
3+
4+
### Invalid HTTP version with lenient
5+
6+
<!-- meta={"type": "request-lenient-version"} -->
7+
```http
8+
GET / HTTP/5.6
9+
10+
11+
```
12+
13+
```log
14+
off=0 message begin
15+
off=4 len=1 span[url]="/"
16+
off=6 url complete
17+
off=18 headers complete method=1 v=5/6 flags=0 content_length=0
18+
off=18 message complete
19+
```
20+
21+

test/response/lenient-version.md

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
Lenient HTTP version parsing
2+
============================
3+
4+
### Invalid HTTP version with lenient
5+
6+
<!-- meta={"type": "response-lenient-version"} -->
7+
```http
8+
HTTP/5.6 200 OK
9+
10+
11+
```
12+
13+
```log
14+
off=0 message begin
15+
off=13 len=2 span[status]="OK"
16+
off=17 status complete
17+
off=19 headers complete status=200 v=5/6 flags=0 content_length=0
18+
```
19+
20+

0 commit comments

Comments
 (0)