Skip to content

Commit

Permalink
Fix HTTP 1xx handling (#247)
Browse files Browse the repository at this point in the history
* fix: Correctly handle 1xx cases.

* fix: Invoke missing callback.
  • Loading branch information
ShogunPanda authored Sep 13, 2023
1 parent 965628d commit 69dafa1
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 26 deletions.
58 changes: 33 additions & 25 deletions src/llhttp/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,7 @@ export class HTTP {
1: this.testFlags(FLAGS.TRAILING, {
1: this.invokePausable('on_chunk_complete',
ERROR.CB_CHUNK_COMPLETE, 'message_done'),
}).otherwise(n('headers_done')),
}).otherwise(this.headersCompleted()),
}, onInvalidHeaderFieldChar),
)
.otherwise(span.headerField.start(n('header_field')));
Expand Down Expand Up @@ -833,13 +833,10 @@ export class HTTP {
}, span.headerValue.start(n('header_value_start'))))
.otherwise(this.setHeaderFlags(onHeaderValueComplete));

// Set `upgrade` if needed
const beforeHeadersComplete = p.invoke(callback.beforeHeadersComplete);

const checkTrailing = this.testFlags(FLAGS.TRAILING, {
1: this.invokePausable('on_chunk_complete',
ERROR.CB_CHUNK_COMPLETE, 'message_done'),
}).otherwise(beforeHeadersComplete);
}).otherwise(this.headersCompleted());

n('headers_almost_done')
.match('\n', checkTrailing)
Expand All @@ -848,26 +845,6 @@ export class HTTP {
1: checkTrailing,
}, p.error(ERROR.STRICT, 'Expected LF after headers')));

/* Here we call the headers_complete callback. This is somewhat
* different than other callbacks because if the user returns 1, we
* will interpret that as saying that this message has no body. This
* is needed for the annoying case of receiving a response to a HEAD
* request.
*
* We'd like to use CALLBACK_NOTIFY_NOADVANCE() here but we cannot, so
* we have to simulate it by handling a change in errno below.
*/
const onHeadersComplete = p.invoke(callback.onHeadersComplete, {
0: n('headers_done'),
1: this.setFlag(FLAGS.SKIPBODY, 'headers_done'),
2: this.update('upgrade', 1,
this.setFlag(FLAGS.SKIPBODY, 'headers_done')),
[ERROR.PAUSED]: this.pause('Paused by on_headers_complete',
'headers_done'),
}, p.error(ERROR.CB_HEADERS_COMPLETE, 'User callback error'));

beforeHeadersComplete.otherwise(onHeadersComplete);

const upgradePause = p.pause(ERROR.PAUSED_UPGRADE,
'Pause on CONNECT/Upgrade');

Expand Down Expand Up @@ -1106,6 +1083,37 @@ export class HTTP {
));
}

private headersCompleted(): Node {
const p = this.llparse;
const callback = this.callback;
const n = (name: string): Match => this.node<Match>(name);

// Set `upgrade` if needed
const beforeHeadersComplete = p.invoke(callback.beforeHeadersComplete);

/* Here we call the headers_complete callback. This is somewhat
* different than other callbacks because if the user returns 1, we
* will interpret that as saying that this message has no body. This
* is needed for the annoying case of receiving a response to a HEAD
* request.
*
* We'd like to use CALLBACK_NOTIFY_NOADVANCE() here but we cannot, so
* we have to simulate it by handling a change in errno below.
*/
const onHeadersComplete = p.invoke(callback.onHeadersComplete, {
0: n('headers_done'),
1: this.setFlag(FLAGS.SKIPBODY, 'headers_done'),
2: this.update('upgrade', 1,
this.setFlag(FLAGS.SKIPBODY, 'headers_done')),
[ERROR.PAUSED]: this.pause('Paused by on_headers_complete',
'headers_done'),
}, p.error(ERROR.CB_HEADERS_COMPLETE, 'User callback error'));

beforeHeadersComplete.otherwise(onHeadersComplete);

return beforeHeadersComplete;
}

private node<T extends Node>(name: string | T): T {
if (name instanceof Node) {
return name;
Expand Down
7 changes: 6 additions & 1 deletion src/native/http.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@ int llhttp__after_headers_complete(llhttp_t* parser, const char* p,
(parser->upgrade && (parser->method == HTTP_CONNECT ||
(parser->flags & F_SKIPBODY) || !hasBody)) ||
/* See RFC 2616 section 4.4 - 1xx e.g. Continue */
(parser->type == HTTP_RESPONSE && parser->status_code / 100 == 1)
(
parser->type == HTTP_RESPONSE &&
(parser->status_code == 100 || parser->status_code == 101)
)
) {
/* Exit, the rest of the message is in a different protocol. */
return 1;
Expand All @@ -54,6 +57,8 @@ int llhttp__after_headers_complete(llhttp_t* parser, const char* p,
parser->flags & F_SKIPBODY || /* response to a HEAD request */
(
parser->type == HTTP_RESPONSE && (
parser->status_code == 102 || /* Processing */
parser->status_code == 103 || /* Early Hints */
parser->status_code == 204 || /* No Content */
parser->status_code == 304 /* Not Modified */
)
Expand Down
1 change: 1 addition & 0 deletions test/request/invalid.md
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,7 @@ off=66 len=3 span[header_field]="Bar"
off=70 header_field complete
off=71 len=3 span[header_value]="def"
off=75 header_value complete
off=76 headers complete method=3 v=1/1 flags=208 content_length=0
off=78 chunk header len=1
off=78 len=1 span[body]="A"
off=80 chunk complete
Expand Down
55 changes: 55 additions & 0 deletions test/response/connection.md
Original file line number Diff line number Diff line change
Expand Up @@ -540,3 +540,58 @@ off=107 len=5 span[body]="hello"
off=114 chunk complete
off=117 chunk header len=0
```


## HTTP 103 first, then 200

<!-- meta={"type": "response"} -->
```http
HTTP/1.1 103 Early Hints
Link: </styles.css>; rel=preload; as=style
HTTP/1.1 200 OK
Date: Wed, 13 Sep 2023 11:09:41 GMT
Connection: keep-alive
Keep-Alive: timeout=5
Content-Length: 17
response content
```

```log
off=0 message begin
off=5 len=3 span[version]="1.1"
off=8 version complete
off=13 len=11 span[status]="Early Hints"
off=26 status complete
off=26 len=4 span[header_field]="Link"
off=31 header_field complete
off=32 len=36 span[header_value]="</styles.css>; rel=preload; as=style"
off=70 header_value complete
off=72 headers complete status=103 v=1/1 flags=0 content_length=0
off=72 message complete
off=72 reset
off=72 message begin
off=77 len=3 span[version]="1.1"
off=80 version complete
off=85 len=2 span[status]="OK"
off=89 status complete
off=89 len=4 span[header_field]="Date"
off=94 header_field complete
off=95 len=29 span[header_value]="Wed, 13 Sep 2023 11:09:41 GMT"
off=126 header_value complete
off=126 len=10 span[header_field]="Connection"
off=137 header_field complete
off=138 len=10 span[header_value]="keep-alive"
off=150 header_value complete
off=150 len=10 span[header_field]="Keep-Alive"
off=161 header_field complete
off=162 len=9 span[header_value]="timeout=5"
off=173 header_value complete
off=173 len=14 span[header_field]="Content-Length"
off=188 header_field complete
off=189 len=2 span[header_value]="17"
off=193 header_value complete
off=195 headers complete status=200 v=1/1 flags=21 content_length=17
off=195 len=16 span[body]="response content"
```
2 changes: 2 additions & 0 deletions test/response/invalid.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ off=16 len=14 span[header_field]="Content-Length"
off=31 header_field complete
off=32 len=1 span[header_value]="0"
off=34 header_value complete
off=35 headers complete status=200 v=1/1 flags=20 content_length=0
off=35 message complete
```

Expand Down Expand Up @@ -277,6 +278,7 @@ off=25 len=3 span[header_field]="Bar"
off=29 header_field complete
off=30 len=3 span[header_value]="def"
off=34 header_value complete
off=35 headers complete status=200 v=1/1 flags=0 content_length=0
off=35 len=4 span[body]="BODY"
off=39 len=1 span[body]=lf
off=40 len=1 span[body]="\"
Expand Down
1 change: 1 addition & 0 deletions test/response/sample.md
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@ off=55 len=10 span[header_field]="Connection"
off=66 header_field complete
off=67 len=5 span[header_value]="close"
off=73 header_value complete
off=74 headers complete status=200 v=1/1 flags=2 content_length=0
off=74 len=51 span[body]="these headers are from http://news.ycombinator.com/"
```

Expand Down

0 comments on commit 69dafa1

Please sign in to comment.