Skip to content
Open
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
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,21 @@ With this flag the extra value will be parsed normally.

**Enabling this flag can pose a security issue since you will be exposed to request smuggling attacks. USE WITH CAUTION!**

### `void llhttp_set_lenient_duplicate_content_length(llhttp_t* parser, int enabled)`

Enables/disables lenient handling of a repeated `Content-Length` header.

Normally `llhttp` would error when `Content-Length` appears more than once.

This is important to prevent request smuggling, but RFC 9112 Section 6.3 also
permits collapsing duplicates whose values are identical.

With this flag the repeat is parsed as an ordinary header and the body is framed
using the first value. llhttp does **not** compare the values; the caller must
reject the message itself when they differ.

**Enabling this flag can pose a security issue since you will be exposed to request smuggling attacks unless you compare the values. USE WITH CAUTION!**

### `void llhttp_set_lenient_version(llhttp_t* parser, int enabled)`

Enables/disables lenient handling of HTTP version.
Expand Down
1 change: 1 addition & 0 deletions src/llhttp/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export const LENIENT_FLAGS = {
OPTIONAL_CR_BEFORE_LF: 1 << 8,
SPACES_AFTER_CHUNK_SIZE: 1 << 9,
HEADER_VALUE_RELAXED: 1 << 10,
DUPLICATE_CONTENT_LENGTH: 1 << 11,
} as const;

export const STATUSES = {
Expand Down
4 changes: 3 additions & 1 deletion src/llhttp/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -775,7 +775,9 @@ export class HTTP {
n('header_value_content_length_once')
.otherwise(this.testFlags(FLAGS.CONTENT_LENGTH, {
0: n('header_value_content_length'),
}, p.error(ERROR.UNEXPECTED_CONTENT_LENGTH, 'Duplicate Content-Length')));
}, this.testLenientFlags(LENIENT_FLAGS.DUPLICATE_CONTENT_LENGTH, {
1: fallback,
}, p.error(ERROR.UNEXPECTED_CONTENT_LENGTH, 'Duplicate Content-Length'))));

n('header_value_content_length')
.select(NUM_MAP, this.mulAdd('content_length', {
Expand Down
8 changes: 8 additions & 0 deletions src/native/api.c
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,14 @@ void llhttp_set_lenient_transfer_encoding(llhttp_t* parser, int enabled) {
}
}

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

void llhttp_set_lenient_version(llhttp_t* parser, int enabled) {
if (enabled) {
parser->lenient_flags |= LENIENT_VERSION;
Expand Down
15 changes: 15 additions & 0 deletions src/native/api.h
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,21 @@ void llhttp_set_lenient_keep_alive(llhttp_t* parser, int enabled);
LLHTTP_EXPORT
void llhttp_set_lenient_transfer_encoding(llhttp_t* parser, int enabled);

/* Enables/disables lenient handling of a repeated `Content-Length` header.
*
* Normally `llhttp` would error when `Content-Length` appears more than once.
* This is important to prevent request smuggling, but RFC 9112 Section 6.3 also
* permits collapsing duplicates whose values are identical.
* With this flag the repeat is parsed as an ordinary header and the body is
* framed using the first value. llhttp does not compare the values; the caller
* must reject the message itself when they differ.
*
* **Enabling this flag can pose a security issue since you will be exposed to
* request smuggling attacks unless you compare the values. USE WITH CAUTION!**
*/
LLHTTP_EXPORT
void llhttp_set_lenient_duplicate_content_length(llhttp_t* parser, int enabled);

/* Enables/disables lenient handling of HTTP version.
*
* Normally `llhttp` would error when the HTTP version in the request or status line
Expand Down
5 changes: 5 additions & 0 deletions test/fixtures/extra.c
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,11 @@ void llhttp__test_init_request_lenient_transfer_encoding(llparse_t* s) {
s->lenient_flags |= LENIENT_TRANSFER_ENCODING;
}

void llhttp__test_init_request_lenient_duplicate_content_length(llparse_t* s) {
llhttp__test_init_request(s);
s->lenient_flags |= LENIENT_DUPLICATE_CONTENT_LENGTH;
}


void llhttp__test_init_request_lenient_version(llparse_t* s) {
llhttp__test_init_request(s);
Expand Down
2 changes: 2 additions & 0 deletions test/fixtures/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export type TestType = 'request' | 'response' | 'request-finish' | 'response-fin
'request-lenient-all' | 'response-lenient-all' |
'request-lenient-headers' | 'response-lenient-headers' |
'request-lenient-chunked-length' | 'request-lenient-transfer-encoding' |
'request-lenient-duplicate-content-length' |
'request-lenient-keep-alive' | 'response-lenient-keep-alive' |
'request-lenient-version' | 'response-lenient-version' |
'request-lenient-data-after-close' | 'response-lenient-data-after-close' |
Expand All @@ -39,6 +40,7 @@ export const allowedTypes: TestType[] = [
'response-lenient-keep-alive',
'request-lenient-chunked-length',
'request-lenient-transfer-encoding',
'request-lenient-duplicate-content-length',
'request-lenient-version',
'response-lenient-version',
'request-lenient-data-after-close',
Expand Down
34 changes: 34 additions & 0 deletions test/request/content-length.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,40 @@ off=53 header_field complete
off=54 error code=4 reason="Duplicate Content-Length"
```

## Lenient duplicate `Content-Length` with identical values

<!-- meta={"type": "request-lenient-duplicate-content-length"} -->
```http
PUT /url HTTP/1.1
Content-Length: 3
Content-Length: 3

abc
```

```log
off=0 message begin
off=0 len=3 span[method]="PUT"
off=3 method complete
off=4 len=4 span[url]="/url"
off=9 url complete
off=9 len=4 span[protocol]="HTTP"
off=13 protocol complete
off=14 len=3 span[version]="1.1"
off=17 version complete
off=19 len=14 span[header_field]="Content-Length"
off=34 header_field complete
off=35 len=1 span[header_value]="3"
off=38 header_value complete
off=38 len=14 span[header_field]="Content-Length"
off=53 header_field complete
off=54 len=1 span[header_value]="3"
off=57 header_value complete
off=59 headers complete method=4 v=1/1 flags=20 content_length=3
off=59 len=3 span[body]="abc"
off=62 message complete
```

## Error on simultaneous `Content-Length` and `Transfer-Encoding: identity`

<!-- meta={"type": "request"} -->
Expand Down