Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New lenient flag to have spaces after chunk header. #245

Merged
merged 2 commits into from
Sep 13, 2023
Merged
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
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,15 @@ With this flag the new chunk can start immediately after the previous one.

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

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

Enables/disables lenient handling of spaces after chunk size.

Normally `llhttp` would error when after a chunk size is followed by one or more spaces are present instead of a CRLF or `;`.
With this flag this check is disabled.

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

## Build Instructions

Make sure you have [Node.js](https://nodejs.org/), npm and npx installed. Then under project directory run:
Expand Down
1 change: 1 addition & 0 deletions src/llhttp/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export enum LENIENT_FLAGS {
OPTIONAL_LF_AFTER_CR = 1 << 6,
OPTIONAL_CRLF_AFTER_CHUNK = 1 << 7,
OPTIONAL_CR_BEFORE_LF = 1 << 8,
SPACES_AFTER_CHUNK_SIZE = 1 << 9,
}

export enum METHODS {
Expand Down
10 changes: 10 additions & 0 deletions src/llhttp/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -924,6 +924,16 @@ export class HTTP {
.otherwise(n('chunk_size_otherwise'));

n('chunk_size_otherwise')
.match(
[ ' ', '\t' ],
this.testLenientFlags(
LENIENT_FLAGS.SPACES_AFTER_CHUNK_SIZE,
{
1: n('chunk_size_otherwise'),
},
p.error(ERROR.INVALID_CHUNK_SIZE, 'Invalid character in chunk size'),
),
)
.match('\r', n('chunk_size_almost_done'))
.match(
'\n',
Expand Down
8 changes: 8 additions & 0 deletions src/native/api.c
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,14 @@ void llhttp_set_lenient_optional_cr_before_lf(llhttp_t* parser, int enabled) {
}
}

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

/* Callbacks */


Expand Down
19 changes: 16 additions & 3 deletions src/native/http.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,26 @@ int llhttp__after_headers_complete(llhttp_t* parser, const char* p,
int hasBody;

hasBody = parser->flags & F_CHUNKED || parser->content_length > 0;
if (parser->upgrade && (parser->method == HTTP_CONNECT ||
(parser->flags & F_SKIPBODY) || !hasBody)) {
if (
(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)
) {
/* Exit, the rest of the message is in a different protocol. */
return 1;
}

if (parser->flags & F_SKIPBODY) {
/* See RFC 2616 section 4.4 */
if (
parser->flags & F_SKIPBODY || /* response to a HEAD request */
(
parser->type == HTTP_RESPONSE && (
parser->status_code == 204 || /* No Content */
parser->status_code == 304 /* Not Modified */
)
)
) {
return 0;
} else if (parser->flags & F_CHUNKED) {
/* chunked encoding - ignore Content-Length header, prepare for a chunk */
Expand Down
10 changes: 10 additions & 0 deletions test/fixtures/extra.c
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,16 @@ void llhttp__test_init_response_lenient_optional_crlf_after_chunk(llparse_t* s)
s->lenient_flags |= LENIENT_OPTIONAL_CRLF_AFTER_CHUNK;
}

void llhttp__test_init_request_lenient_spaces_after_chunk_size(llparse_t* s) {
llhttp__test_init_request(s);
s->lenient_flags |= LENIENT_SPACES_AFTER_CHUNK_SIZE;
}

void llhttp__test_init_response_lenient_spaces_after_chunk_size(llparse_t* s) {
llhttp__test_init_response(s);
s->lenient_flags |= LENIENT_SPACES_AFTER_CHUNK_SIZE;
}


void llhttp__test_finish(llparse_t* s) {
llparse__print(NULL, NULL, "finish=%d", s->finish);
Expand Down
3 changes: 3 additions & 0 deletions test/fixtures/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export type TestType = 'request' | 'response' | 'request-finish' | 'response-fin
'request-lenient-optional-lf-after-cr' | 'response-lenient-optional-lf-after-cr' |
'request-lenient-optional-cr-before-lf' | 'response-lenient-optional-cr-before-lf' |
'request-lenient-optional-crlf-after-chunk' | 'response-lenient-optional-crlf-after-chunk' |
'request-lenient-spaces-after-chunk-size' | 'response-lenient-spaces-after-chunk-size' |
'none' | 'url';

export const allowedTypes: TestType[] = [
Expand All @@ -45,6 +46,8 @@ export const allowedTypes: TestType[] = [
'response-lenient-optional-cr-before-lf',
'request-lenient-optional-crlf-after-chunk',
'response-lenient-optional-crlf-after-chunk',
'request-lenient-spaces-after-chunk-size',
'response-lenient-spaces-after-chunk-size',
];

const BUILD_DIR = path.join(__dirname, '..', 'tmp');
Expand Down
67 changes: 65 additions & 2 deletions test/request/transfer-encoding.md
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ off=83 header_field complete
off=84 len=7 span[header_value]="chunked"
off=93 header_value complete
off=95 headers complete method=3 v=1/1 flags=208 content_length=0
off=96 error code=12 reason="Invalid character in chunk size"
off=97 error code=12 reason="Invalid character in chunk size"
```

### No extension after semicolon
Expand Down Expand Up @@ -884,7 +884,7 @@ off=37 header_field complete
off=38 len=7 span[header_value]="chunked"
off=47 header_value complete
off=49 headers complete method=4 v=1/1 flags=208 content_length=0
off=50 error code=12 reason="Invalid character in chunk size"
off=51 error code=12 reason="Invalid character in chunk size"
```

## Invalid OBS fold after chunked value
Expand Down Expand Up @@ -1117,4 +1117,67 @@ off=79 chunk header len=5
off=79 len=5 span[body]="ABCDE"
off=84 chunk complete
off=87 chunk header len=0
```

## Space after chunk header

<!-- meta={"type": "request"} -->
```http
PUT /url HTTP/1.1
Transfer-Encoding: chunked

a \r\n0123456789
0


```

```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=14 len=3 span[version]="1.1"
off=17 version complete
off=19 len=17 span[header_field]="Transfer-Encoding"
off=37 header_field complete
off=38 len=7 span[header_value]="chunked"
off=47 header_value complete
off=49 headers complete method=4 v=1/1 flags=208 content_length=0
off=51 error code=12 reason="Invalid character in chunk size"
```

## Space after chunk header (lenient)

<!-- meta={"type": "request-lenient-spaces-after-chunk-size"} -->
```http
PUT /url HTTP/1.1
Transfer-Encoding: chunked

a \r\n0123456789
0


```

```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=14 len=3 span[version]="1.1"
off=17 version complete
off=19 len=17 span[header_field]="Transfer-Encoding"
off=37 header_field complete
off=38 len=7 span[header_value]="chunked"
off=47 header_value complete
off=49 headers complete method=4 v=1/1 flags=208 content_length=0
off=53 chunk header len=10
off=53 len=10 span[body]="0123456789"
off=65 chunk complete
off=68 chunk header len=0
off=70 chunk complete
off=70 message complete
```
103 changes: 90 additions & 13 deletions test/response/connection.md
Original file line number Diff line number Diff line change
Expand Up @@ -298,9 +298,8 @@ off=84 header_field complete
off=85 len=1 span[header_value]="4"
off=88 header_value complete
off=90 headers complete status=101 v=1/1 flags=34 content_length=4
off=90 len=4 span[body]="body"
off=94 message complete
off=94 error code=22 reason="Pause on CONNECT/Upgrade"
off=90 message complete
off=90 error code=22 reason="Pause on CONNECT/Upgrade"
```

## HTTP 101 response with Upgrade and Transfer-Encoding header
Expand Down Expand Up @@ -340,16 +339,8 @@ off=87 header_field complete
off=88 len=7 span[header_value]="chunked"
off=97 header_value complete
off=99 headers complete status=101 v=1/1 flags=21c content_length=0
off=102 chunk header len=2
off=102 len=2 span[body]="bo"
off=106 chunk complete
off=109 chunk header len=2
off=109 len=2 span[body]="dy"
off=113 chunk complete
off=116 chunk header len=0
off=118 chunk complete
off=118 message complete
off=118 error code=22 reason="Pause on CONNECT/Upgrade"
off=99 message complete
off=99 error code=22 reason="Pause on CONNECT/Upgrade"
```

## HTTP 200 response with Upgrade header
Expand Down Expand Up @@ -463,3 +454,89 @@ off=99 chunk header len=0
off=101 chunk complete
off=101 message complete
```

## HTTP 304 with Content-Length

<!-- meta={"type": "response"} -->
```http
HTTP/1.1 304 Not Modified
Content-Length: 10


HTTP/1.1 200 OK
Content-Length: 5

hello
```

```log
off=0 message begin
off=5 len=3 span[version]="1.1"
off=8 version complete
off=13 len=12 span[status]="Not Modified"
off=27 status complete
off=27 len=14 span[header_field]="Content-Length"
off=42 header_field complete
off=43 len=2 span[header_value]="10"
off=47 header_value complete
off=49 headers complete status=304 v=1/1 flags=20 content_length=10
off=49 message complete
off=51 reset
off=51 message begin
off=56 len=3 span[version]="1.1"
off=59 version complete
off=64 len=2 span[status]="OK"
off=68 status complete
off=68 len=14 span[header_field]="Content-Length"
off=83 header_field complete
off=84 len=1 span[header_value]="5"
off=87 header_value complete
off=89 headers complete status=200 v=1/1 flags=20 content_length=5
off=89 len=5 span[body]="hello"
off=94 message complete
```

## HTTP 304 with Transfer-Encoding

<!-- meta={"type": "response"} -->
```http
HTTP/1.1 304 Not Modified
Transfer-Encoding: chunked

HTTP/1.1 200 OK
Transfer-Encoding: chunked

5
hello
0

```

```log
off=0 message begin
off=5 len=3 span[version]="1.1"
off=8 version complete
off=13 len=12 span[status]="Not Modified"
off=27 status complete
off=27 len=17 span[header_field]="Transfer-Encoding"
off=45 header_field complete
off=46 len=7 span[header_value]="chunked"
off=55 header_value complete
off=57 headers complete status=304 v=1/1 flags=208 content_length=0
off=57 message complete
off=57 reset
off=57 message begin
off=62 len=3 span[version]="1.1"
off=65 version complete
off=70 len=2 span[status]="OK"
off=74 status complete
off=74 len=17 span[header_field]="Transfer-Encoding"
off=92 header_field complete
off=93 len=7 span[header_value]="chunked"
off=102 header_value complete
off=104 headers complete status=200 v=1/1 flags=208 content_length=0
off=107 chunk header len=5
off=107 len=5 span[body]="hello"
off=114 chunk complete
off=117 chunk header len=0
```
4 changes: 2 additions & 2 deletions test/response/transfer-encoding.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ off=61 header_field complete
off=62 len=7 span[header_value]="chunked"
off=71 header_value complete
off=73 headers complete status=200 v=1/1 flags=208 content_length=0
off=75 error code=12 reason="Invalid character in chunk size"
off=76 error code=12 reason="Invalid character in chunk size"
```

## `chunked` before other transfer-encoding
Expand Down Expand Up @@ -229,7 +229,7 @@ off=52 header_field complete
off=53 len=7 span[header_value]="chunked"
off=62 header_value complete
off=64 headers complete status=200 v=1/1 flags=208 content_length=0
off=65 error code=12 reason="Invalid character in chunk size"
off=66 error code=12 reason="Invalid character in chunk size"
```


Expand Down