Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/llhttp/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ export const ERROR = {
INVALID_STATUS: 13,
INVALID_EOF_STATE: 14,
INVALID_TRANSFER_ENCODING: 15,
HOST_PREVIOUSLY_SEEN: 39,
HOST_NOT_PROVIDED: 40,

CB_MESSAGE_BEGIN: 16,
CB_HEADERS_COMPLETE: 17,
Expand Down Expand Up @@ -66,6 +68,7 @@ export const FLAGS = {
TRAILING: 1 << 7,
// 1 << 8 is unused
TRANSFER_ENCODING: 1 << 9,
HOST_SEEN: 1 << 10,
} as const;

export const LENIENT_FLAGS = {
Expand All @@ -80,6 +83,7 @@ export const LENIENT_FLAGS = {
OPTIONAL_CR_BEFORE_LF: 1 << 8,
SPACES_AFTER_CHUNK_SIZE: 1 << 9,
HEADER_VALUE_RELAXED: 1 << 10,
HOST_RELAXED: 1 << 11,
} as const;

export const STATUSES = {
Expand Down
35 changes: 33 additions & 2 deletions src/llhttp/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,21 @@ export class HTTP {
this.buildHeaderValue();
}

private buildHostCheck(next: Node): Node {
// Check if the lentient flag given is not set to HOST_RELAXED
// This will reject repetative versions of the host header
const p = this.llparse;
return this.testLenientFlags(
~LENIENT_FLAGS.HOST_RELAXED,
{1: next},
this.testFlags(
FLAGS.HOST_SEEN,
{1: p.error(ERROR.HOST_PREVIOUSLY_SEEN, "host provided multiple times.")},
this.setFlag(FLAGS.HOST_SEEN, next),
)
);
}

private buildHeaderField(): void {
const p = this.llparse;
const span = this.span;
Expand Down Expand Up @@ -578,12 +593,18 @@ export class HTTP {
)
.peek(':', p.error(ERROR.INVALID_HEADER_TOKEN, 'Invalid header token'))
.otherwise(span.headerField.start(n('header_field')));


const reset_header_state = this.resetHeaderState('header_field_general');

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

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

beforeHeadersComplete.otherwise(onHeadersComplete);

return beforeHeadersComplete;
// before leaving header state if Host is not set to being
// relaxed see if no host has been provided at all...
return this.testLenientFlags(
~LENIENT_FLAGS.HOST_RELAXED,
{1:this.testFlags(
FLAGS.HOST_SEEN,
{1: beforeHeadersComplete},
p.error(ERROR.HOST_NOT_PROVIDED, "No host header or value was provided.")
)},
beforeHeadersComplete,
);
}

private node<T extends Node>(name: string | T): T {
Expand Down
8 changes: 8 additions & 0 deletions src/native/api.c
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,14 @@ void llhttp_set_lenient_header_value_relaxed(llhttp_t* parser, int enabled) {
}
}

void llhttp_set_lenient_host_relaxed(llhttp_t* parser, int enabled) {
if (enabled) {
parser->lenient_flags |= LENIENT_HOST_RELAXED;
} else {
parser->lenient_flags &= ~LENIENT_HOST_RELAXED;
}
}

/* Callbacks */


Expand Down
9 changes: 9 additions & 0 deletions src/native/api.h
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,15 @@ void llhttp_set_lenient_spaces_after_chunk_size(llhttp_t* parser, int enabled);
LLHTTP_EXPORT
void llhttp_set_lenient_header_value_relaxed(llhttp_t* parser, int enabled);


/* Enables/disables relaxed handling of the host header, which can allow multiple
* or no host headers, when disabled it strictly prohibits these form of requests
* from being accepted.
*/
LLHTTP_EXPORT
void llhttp_set_lenient_host_relaxed(llhttp_t* parser, int enabled);


#ifdef __cplusplus
} /* extern "C" */
#endif
Expand Down
Loading