From 6722d712de216bbcc2b083a4d90c6c60e840ed5c Mon Sep 17 00:00:00 2001 From: Paolo Insogna Date: Tue, 23 Aug 2022 14:07:27 +0200 Subject: [PATCH] feat: Do not allow whitespaces after start line. --- src/llhttp/constants.ts | 1 + src/llhttp/http.ts | 15 ++++++++++++--- test/request/invalid.md | 15 +++++++++++++++ test/response/invalid.md | 15 +++++++++++++++ 4 files changed, 43 insertions(+), 3 deletions(-) diff --git a/src/llhttp/constants.ts b/src/llhttp/constants.ts index 926e5ce2..cdf83d14 100644 --- a/src/llhttp/constants.ts +++ b/src/llhttp/constants.ts @@ -11,6 +11,7 @@ export enum ERROR { CR_EXPECTED = 25, LF_EXPECTED = 3, UNEXPECTED_CONTENT_LENGTH = 4, + UNEXPECTED_SPACE = 30, CLOSED_CONNECTION = 5, INVALID_METHOD = 6, INVALID_URL = 7, diff --git a/src/llhttp/http.ts b/src/llhttp/http.ts index 91d995ef..e2ae0e5f 100644 --- a/src/llhttp/http.ts +++ b/src/llhttp/http.ts @@ -47,6 +47,7 @@ const NODES: ReadonlyArray = [ 'req_pri_upgrade', + 'headers_start', 'header_field_start', 'header_field', 'header_field_colon', @@ -301,7 +302,7 @@ export class HTTP { .otherwise(p.error(ERROR.INVALID_STATUS, 'Invalid response status')); const onStatusComplete = p.invoke(this.callback.onStatusComplete); - onStatusComplete.otherwise(n('header_field_start')); + onStatusComplete.otherwise(n('headers_start')); n('res_status_start') .match('\r', n('res_line_almost_done')) @@ -348,7 +349,7 @@ export class HTTP { .otherwise(onUrlCompleteHTTP); const onUrlCompleteHTTP09 = this.invokePausable( - 'on_url_complete', ERROR.CB_URL_COMPLETE, n('header_field_start'), + 'on_url_complete', ERROR.CB_URL_COMPLETE, n('headers_start'), ); url.exit.toHTTP09 @@ -396,7 +397,7 @@ export class HTTP { }, n('req_http_complete'))); n('req_http_complete') - .match([ '\r\n', '\n' ], n('header_field_start')) + .match([ '\r\n', '\n' ], n('headers_start')) .otherwise(p.error(ERROR.INVALID_VERSION, 'Expected CRLF after version')); n('req_pri_upgrade') @@ -416,6 +417,14 @@ export class HTTP { const span = this.span; const n = (name: string): Match => this.node(name); + n('headers_start') + .match(' ', + this.testLenientFlags(LENIENT_FLAGS.HEADERS, { + 1: n('header_field_start'), + }, p.error(ERROR.UNEXPECTED_SPACE, 'Unexpected space after start line')), + ) + .otherwise(n('header_field_start')); + n('header_field_start') .match('\r', n('headers_almost_done')) /* they might be just sending \n instead of \r\n so this would be diff --git a/test/request/invalid.md b/test/request/invalid.md index 2154ddf3..8b7a31c7 100644 --- a/test/request/invalid.md +++ b/test/request/invalid.md @@ -243,4 +243,19 @@ off=0 message begin off=4 len=1 span[url]="/" off=6 url complete off=14 error code=9 reason="Invalid HTTP version" +``` + +## Invalid space after start line + + +```http +GET / HTTP/1.1 + Host: foo +``` + +```log +off=0 message begin +off=4 len=1 span[url]="/" +off=6 url complete +off=17 error code=30 reason="Unexpected space after start line" ``` \ No newline at end of file diff --git a/test/response/invalid.md b/test/response/invalid.md index bbdc40ba..c708c7f0 100644 --- a/test/response/invalid.md +++ b/test/response/invalid.md @@ -119,4 +119,19 @@ HTTP/5.6 200 OK ```log off=0 message begin off=8 error code=9 reason="Invalid HTTP version" +``` + +## Invalid space after start line + + +```http +HTTP/1.1 200 OK + Host: foo +``` + +```log +off=0 message begin +off=13 len=2 span[status]="OK" +off=17 status complete +off=18 error code=30 reason="Unexpected space after start line" ``` \ No newline at end of file