Skip to content

Commit 2090cc3

Browse files
committed
add lentient flag that probitings multiple host headers or none at all.
1 parent 15ce0a9 commit 2090cc3

File tree

2 files changed

+37
-2
lines changed

2 files changed

+37
-2
lines changed

src/llhttp/constants.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ export const ERROR = {
2424
INVALID_STATUS: 13,
2525
INVALID_EOF_STATE: 14,
2626
INVALID_TRANSFER_ENCODING: 15,
27+
HOST_PREVIOUSLY_SEEN: 39,
28+
HOST_NOT_PROVIDED: 40,
2729

2830
CB_MESSAGE_BEGIN: 16,
2931
CB_HEADERS_COMPLETE: 17,
@@ -66,6 +68,7 @@ export const FLAGS = {
6668
TRAILING: 1 << 7,
6769
// 1 << 8 is unused
6870
TRANSFER_ENCODING: 1 << 9,
71+
HOST_SEEN: 1 << 10,
6972
} as const;
7073

7174
export const LENIENT_FLAGS = {
@@ -80,6 +83,7 @@ export const LENIENT_FLAGS = {
8083
OPTIONAL_CR_BEFORE_LF: 1 << 8,
8184
SPACES_AFTER_CHUNK_SIZE: 1 << 9,
8285
HEADER_VALUE_RELAXED: 1 << 10,
86+
HOST_RELAXED: 1 << 11,
8387
} as const;
8488

8589
export const STATUSES = {

src/llhttp/http.ts

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,21 @@ export class HTTP {
550550
this.buildHeaderValue();
551551
}
552552

553+
private buildHostCheck(next: Node): Node {
554+
// Check if the lentient flag given is not set to HOST_RELAXED
555+
// This will reject repetative versions of the host header
556+
const p = this.llparse;
557+
return this.testLenientFlags(
558+
~LENIENT_FLAGS.HOST_RELAXED,
559+
{1: next},
560+
this.testFlags(
561+
FLAGS.HOST_SEEN,
562+
{1: p.error(ERROR.HOST_PREVIOUSLY_SEEN, "host provided multiple times.")},
563+
this.setFlag(FLAGS.HOST_SEEN, next),
564+
)
565+
);
566+
}
567+
553568
private buildHeaderField(): void {
554569
const p = this.llparse;
555570
const span = this.span;
@@ -578,12 +593,18 @@ export class HTTP {
578593
)
579594
.peek(':', p.error(ERROR.INVALID_HEADER_TOKEN, 'Invalid header token'))
580595
.otherwise(span.headerField.start(n('header_field')));
596+
597+
598+
const reset_header_state = this.resetHeaderState('header_field_general');
581599

582600
n('header_field')
583601
.transform(p.transform.toLower())
584602
// Match headers that need special treatment
585603
.select(SPECIAL_HEADERS, this.store('header_state', 'header_field_colon'))
586-
.otherwise(this.resetHeaderState('header_field_general'));
604+
// check to see if host was given once or multiple times which if not
605+
// relaxed should be easily rejected.
606+
.match('host', this.buildHostCheck(reset_header_state))
607+
.otherwise(reset_header_state);
587608

588609
/* https://www.rfc-editor.org/rfc/rfc7230.html#section-3.3.3, paragraph 3.
589610
*
@@ -1165,7 +1186,17 @@ export class HTTP {
11651186

11661187
beforeHeadersComplete.otherwise(onHeadersComplete);
11671188

1168-
return beforeHeadersComplete;
1189+
// before leaving header state if Host is not set to being
1190+
// relaxed see if no host has been provided at all...
1191+
return this.testLenientFlags(
1192+
~LENIENT_FLAGS.HOST_RELAXED,
1193+
{1:this.testFlags(
1194+
FLAGS.HOST_SEEN,
1195+
{1: beforeHeadersComplete},
1196+
p.error(ERROR.HOST_NOT_PROVIDED, "No host header or value was provided.")
1197+
)},
1198+
beforeHeadersComplete,
1199+
);
11691200
}
11701201

11711202
private node<T extends Node>(name: string | T): T {

0 commit comments

Comments
 (0)