Files
nodejs8/CVE-2019-15605.patch

2083 lines
65 KiB
Diff
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

ported commits,
commit e2c8f89b7572a7aea62927923e425bbd7725dca2
Author: Sam Roberts <vieuxtech@gmail.com>
Date: Thu Jan 16 11:55:52 2020 -0800
test: using TE to smuggle reqs is not possible
See: https://hackerone.com/reports/735748
PR-URL: https://github.com/nodejs-private/node-private/pull/192
Reviewed-By: Beth Griggs <Bethany.Griggs@uk.ibm.com>
commit 49f4220ce5b92bec68c040f46823e55c27d50517
Author: Sam Roberts <vieuxtech@gmail.com>
Date: Tue Feb 4 10:36:57 2020 -0800
deps: upgrade http-parser to v2.9.3
PR-URL: https://github.com/nodejs-private/http-parser-private/pull/4
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Sam Roberts <vieuxtech@gmail.com>
commit d616722f65fcfbce57e597f41466e864eba22c4f
Author: Sam Roberts <vieuxtech@gmail.com>
Date: Tue Jan 7 14:24:54 2020 -0800
test: check that --insecure-http-parser works
Test that using --insecure-http-parser will disable validation of
invalid characters in HTTP headers.
See:
- https://github.com/nodejs/node/pull/30567
Backport-PR-URL: https://github.com/nodejs/node/pull/30471
PR-URL: https://github.com/nodejs/node/pull/31253
Reviewed-By: Richard Lau <riclau@uk.ibm.com>
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
commit a9849c0ff6b4459880f8f6da10e6fedb3c4df620
Author: Sam Roberts <vieuxtech@gmail.com>
Date: Wed Nov 20 11:48:58 2019 -0800
http: opt-in insecure HTTP header parsing
Allow insecure HTTP header parsing. Make clear it is insecure.
See:
- https://github.com/nodejs/node/pull/30553
- https://github.com/nodejs/node/issues/27711#issuecomment-556265881
- https://github.com/nodejs/node/issues/30515
Backport-PR-URL: https://github.com/nodejs/node/pull/30471
PR-URL: https://github.com/nodejs/node/pull/30567
Reviewed-By: Fedor Indutny <fedor.indutny@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Denys Otrishko <shishugi@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
commit a28e5cc1ed7e298118bd3ea8b5b96712467c3703
Author: Sam Roberts <vieuxtech@gmail.com>
Date: Wed Nov 13 10:05:38 2019 -0800
deps: upgrade http-parser to v2.9.1
PR-URL: https://github.com/nodejs/node/pull/30471
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Jiawen Geng <technicalcute@gmail.com>
Reviewed-By: Richard Lau <riclau@uk.ibm.com>
Reviewed-By: Beth Griggs <Bethany.Griggs@uk.ibm.com>
Index: node-v8.17.0/deps/http_parser/Makefile
===================================================================
--- node-v8.17.0.orig/deps/http_parser/Makefile
+++ node-v8.17.0/deps/http_parser/Makefile
@@ -23,8 +23,8 @@ HELPER ?=
BINEXT ?=
SOLIBNAME = libhttp_parser
SOMAJOR = 2
-SOMINOR = 8
-SOREV = 0
+SOMINOR = 9
+SOREV = 3
ifeq (darwin,$(PLATFORM))
SOEXT ?= dylib
SONAME ?= $(SOLIBNAME).$(SOMAJOR).$(SOMINOR).$(SOEXT)
@@ -133,14 +133,14 @@ tags: http_parser.c http_parser.h test.c
install: library
$(INSTALL) -D http_parser.h $(DESTDIR)$(INCLUDEDIR)/http_parser.h
$(INSTALL) -D $(LIBNAME) $(DESTDIR)$(LIBDIR)/$(LIBNAME)
- ln -s $(LIBNAME) $(DESTDIR)$(LIBDIR)/$(SONAME)
- ln -s $(LIBNAME) $(DESTDIR)$(LIBDIR)/$(SOLIBNAME).$(SOEXT)
+ ln -sf $(LIBNAME) $(DESTDIR)$(LIBDIR)/$(SONAME)
+ ln -sf $(LIBNAME) $(DESTDIR)$(LIBDIR)/$(SOLIBNAME).$(SOEXT)
install-strip: library
$(INSTALL) -D http_parser.h $(DESTDIR)$(INCLUDEDIR)/http_parser.h
$(INSTALL) -D -s $(LIBNAME) $(DESTDIR)$(LIBDIR)/$(LIBNAME)
- ln -s $(LIBNAME) $(DESTDIR)$(LIBDIR)/$(SONAME)
- ln -s $(LIBNAME) $(DESTDIR)$(LIBDIR)/$(SOLIBNAME).$(SOEXT)
+ ln -sf $(LIBNAME) $(DESTDIR)$(LIBDIR)/$(SONAME)
+ ln -sf $(LIBNAME) $(DESTDIR)$(LIBDIR)/$(SOLIBNAME).$(SOEXT)
uninstall:
rm $(DESTDIR)$(INCLUDEDIR)/http_parser.h
Index: node-v8.17.0/deps/http_parser/README.md
===================================================================
--- node-v8.17.0.orig/deps/http_parser/README.md
+++ node-v8.17.0/deps/http_parser/README.md
@@ -148,7 +148,7 @@ callback in a threadsafe manner. This al
multi-threaded contexts.
Example:
-```
+```c
typedef struct {
socket_t sock;
void* buffer;
@@ -184,7 +184,7 @@ void http_parser_thread(socket_t sock) {
parser supplied to callback functions */
parser->data = my_data;
- http_parser_settings settings; / * set up callbacks */
+ http_parser_settings settings; /* set up callbacks */
settings.on_url = my_url_callback;
/* execute parser */
Index: node-v8.17.0/deps/http_parser/bench.c
===================================================================
--- node-v8.17.0.orig/deps/http_parser/bench.c
+++ node-v8.17.0/deps/http_parser/bench.c
@@ -20,10 +20,14 @@
*/
#include "http_parser.h"
#include <assert.h>
+#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
+/* 8 gb */
+static const int64_t kBytes = 8LL << 30;
+
static const char data[] =
"POST /joyent/http-parser HTTP/1.1\r\n"
"Host: github.com\r\n"
@@ -38,7 +42,7 @@ static const char data[] =
"Referer: https://github.com/joyent/http-parser\r\n"
"Connection: keep-alive\r\n"
"Transfer-Encoding: chunked\r\n"
- "Cache-Control: max-age=0\r\n\r\nb\r\nhello world\r\n0\r\n\r\n";
+ "Cache-Control: max-age=0\r\n\r\nb\r\nhello world\r\n0\r\n";
static const size_t data_len = sizeof(data) - 1;
static int on_info(http_parser* p) {
@@ -67,13 +71,13 @@ int bench(int iter_count, int silent) {
int err;
struct timeval start;
struct timeval end;
- float rps;
if (!silent) {
err = gettimeofday(&start, NULL);
assert(err == 0);
}
+ fprintf(stderr, "req_len=%d\n", (int) data_len);
for (i = 0; i < iter_count; i++) {
size_t parsed;
http_parser_init(&parser, HTTP_REQUEST);
@@ -83,17 +87,27 @@ int bench(int iter_count, int silent) {
}
if (!silent) {
+ double elapsed;
+ double bw;
+ double total;
+
err = gettimeofday(&end, NULL);
assert(err == 0);
fprintf(stdout, "Benchmark result:\n");
- rps = (float) (end.tv_sec - start.tv_sec) +
- (end.tv_usec - start.tv_usec) * 1e-6f;
- fprintf(stdout, "Took %f seconds to run\n", rps);
+ elapsed = (double) (end.tv_sec - start.tv_sec) +
+ (end.tv_usec - start.tv_usec) * 1e-6f;
+
+ total = (double) iter_count * data_len;
+ bw = (double) total / elapsed;
+
+ fprintf(stdout, "%.2f mb | %.2f mb/s | %.2f req/sec | %.2f s\n",
+ (double) total / (1024 * 1024),
+ bw / (1024 * 1024),
+ (double) iter_count / elapsed,
+ elapsed);
- rps = (float) iter_count / rps;
- fprintf(stdout, "%f req/sec\n", rps);
fflush(stdout);
}
@@ -101,11 +115,14 @@ int bench(int iter_count, int silent) {
}
int main(int argc, char** argv) {
+ int64_t iterations;
+
+ iterations = kBytes / (int64_t) data_len;
if (argc == 2 && strcmp(argv[1], "infinite") == 0) {
for (;;)
- bench(5000000, 1);
+ bench(iterations, 1);
return 0;
} else {
- return bench(5000000, 0);
+ return bench(iterations, 0);
}
}
Index: node-v8.17.0/deps/http_parser/http_parser.c
===================================================================
--- node-v8.17.0.orig/deps/http_parser/http_parser.c
+++ node-v8.17.0/deps/http_parser/http_parser.c
@@ -51,6 +51,7 @@ static uint32_t max_header_size = HTTP_M
#define SET_ERRNO(e) \
do { \
+ parser->nread = nread; \
parser->http_errno = (e); \
} while(0)
@@ -58,6 +59,7 @@ do {
#define UPDATE_STATE(V) p_state = (enum state) (V);
#define RETURN(V) \
do { \
+ parser->nread = nread; \
parser->state = CURRENT_STATE(); \
return (V); \
} while (0);
@@ -151,8 +153,8 @@ do {
*/
#define COUNT_HEADER_SIZE(V) \
do { \
- parser->nread += (V); \
- if (UNLIKELY(parser->nread > max_header_size)) { \
+ nread += (uint32_t)(V); \
+ if (UNLIKELY(nread > max_header_size)) { \
SET_ERRNO(HPE_HEADER_OVERFLOW); \
goto error; \
} \
@@ -194,7 +196,7 @@ static const char tokens[256] = {
/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */
0, 0, 0, 0, 0, 0, 0, 0,
/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */
- 0, '!', 0, '#', '$', '%', '&', '\'',
+ ' ', '!', 0, '#', '$', '%', '&', '\'',
/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */
0, 0, '*', '+', 0, '-', '.', 0,
/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */
@@ -314,6 +316,8 @@ enum state
, s_req_http_HT
, s_req_http_HTT
, s_req_http_HTTP
+ , s_req_http_I
+ , s_req_http_IC
, s_req_http_major
, s_req_http_dot
, s_req_http_minor
@@ -377,7 +381,10 @@ enum header_states
, h_transfer_encoding
, h_upgrade
+ , h_matching_transfer_encoding_token_start
, h_matching_transfer_encoding_chunked
+ , h_matching_transfer_encoding_token
+
, h_matching_connection_token_start
, h_matching_connection_keep_alive
, h_matching_connection_close
@@ -421,14 +428,14 @@ enum http_host_state
(c) == ';' || (c) == ':' || (c) == '&' || (c) == '=' || (c) == '+' || \
(c) == '$' || (c) == ',')
-#define STRICT_TOKEN(c) (tokens[(unsigned char)c])
+#define STRICT_TOKEN(c) ((c == ' ') ? 0 : tokens[(unsigned char)c])
#if HTTP_PARSER_STRICT
-#define TOKEN(c) (tokens[(unsigned char)c])
+#define TOKEN(c) STRICT_TOKEN(c)
#define IS_URL_CHAR(c) (BIT_AT(normal_url_char, (unsigned char)c))
#define IS_HOST_CHAR(c) (IS_ALPHANUM(c) || (c) == '.' || (c) == '-')
#else
-#define TOKEN(c) ((c == ' ') ? ' ' : tokens[(unsigned char)c])
+#define TOKEN(c) tokens[(unsigned char)c]
#define IS_URL_CHAR(c) \
(BIT_AT(normal_url_char, (unsigned char)c) || ((c) & 0x80))
#define IS_HOST_CHAR(c) \
@@ -542,7 +549,7 @@ parse_url_char(enum state s, const char
return s_dead;
}
- /* FALLTHROUGH */
+ /* fall through */
case s_req_server_start:
case s_req_server:
if (ch == '/') {
@@ -646,6 +653,7 @@ size_t http_parser_execute (http_parser
const char *status_mark = 0;
enum state p_state = (enum state) parser->state;
const unsigned int lenient = parser->lenient_http_headers;
+ uint32_t nread = parser->nread;
/* We're in an error state. Don't bother doing anything. */
if (HTTP_PARSER_ERRNO(parser) != HPE_OK) {
@@ -757,21 +765,16 @@ reexecute:
case s_start_res:
{
+ if (ch == CR || ch == LF)
+ break;
parser->flags = 0;
parser->content_length = ULLONG_MAX;
- switch (ch) {
- case 'H':
- UPDATE_STATE(s_res_H);
- break;
-
- case CR:
- case LF:
- break;
-
- default:
- SET_ERRNO(HPE_INVALID_CONSTANT);
- goto error;
+ if (ch == 'H') {
+ UPDATE_STATE(s_res_H);
+ } else {
+ SET_ERRNO(HPE_INVALID_CONSTANT);
+ goto error;
}
CALLBACK_NOTIFY(message_begin);
@@ -1088,11 +1091,17 @@ reexecute:
case s_req_http_start:
switch (ch) {
+ case ' ':
+ break;
case 'H':
UPDATE_STATE(s_req_http_H);
break;
- case ' ':
- break;
+ case 'I':
+ if (parser->method == HTTP_SOURCE) {
+ UPDATE_STATE(s_req_http_I);
+ break;
+ }
+ /* fall through */
default:
SET_ERRNO(HPE_INVALID_CONSTANT);
goto error;
@@ -1114,6 +1123,16 @@ reexecute:
UPDATE_STATE(s_req_http_HTTP);
break;
+ case s_req_http_I:
+ STRICT_CHECK(ch != 'C');
+ UPDATE_STATE(s_req_http_IC);
+ break;
+
+ case s_req_http_IC:
+ STRICT_CHECK(ch != 'E');
+ UPDATE_STATE(s_req_http_HTTP); /* Treat "ICE" as "HTTP". */
+ break;
+
case s_req_http_HTTP:
STRICT_CHECK(ch != '/');
UPDATE_STATE(s_req_http_major);
@@ -1240,8 +1259,14 @@ reexecute:
break;
switch (parser->header_state) {
- case h_general:
+ case h_general: {
+ size_t left = data + len - p;
+ const char* pe = p + MIN(left, max_header_size);
+ while (p+1 < pe && TOKEN(p[1])) {
+ p++;
+ }
break;
+ }
case h_C:
parser->index++;
@@ -1313,6 +1338,7 @@ reexecute:
parser->header_state = h_general;
} else if (parser->index == sizeof(TRANSFER_ENCODING)-2) {
parser->header_state = h_transfer_encoding;
+ parser->flags |= F_TRANSFER_ENCODING;
}
break;
@@ -1341,13 +1367,14 @@ reexecute:
}
}
- COUNT_HEADER_SIZE(p - start);
-
if (p == data + len) {
--p;
+ COUNT_HEADER_SIZE(p - start);
break;
}
+ COUNT_HEADER_SIZE(p - start);
+
if (ch == ':') {
UPDATE_STATE(s_header_value_discard_ws);
CALLBACK_DATA(header_field);
@@ -1371,7 +1398,7 @@ reexecute:
break;
}
- /* FALLTHROUGH */
+ /* fall through */
case s_header_value_start:
{
@@ -1393,10 +1420,14 @@ reexecute:
if ('c' == c) {
parser->header_state = h_matching_transfer_encoding_chunked;
} else {
- parser->header_state = h_general;
+ parser->header_state = h_matching_transfer_encoding_token;
}
break;
+ /* Multi-value `Transfer-Encoding` header */
+ case h_matching_transfer_encoding_token_start:
+ break;
+
case h_content_length:
if (UNLIKELY(!IS_NUM(ch))) {
SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
@@ -1413,6 +1444,11 @@ reexecute:
parser->header_state = h_content_length_num;
break;
+ /* when obsolete line folding is encountered for content length
+ * continue to the s_header_value state */
+ case h_content_length_ws:
+ break;
+
case h_connection:
/* looking for 'Connection: keep-alive' */
if (c == 'k') {
@@ -1468,29 +1504,25 @@ reexecute:
switch (h_state) {
case h_general:
- {
- const char* p_cr;
- const char* p_lf;
- size_t limit = data + len - p;
-
- limit = MIN(limit, max_header_size);
-
- p_cr = (const char*) memchr(p, CR, limit);
- p_lf = (const char*) memchr(p, LF, limit);
- if (p_cr != NULL) {
- if (p_lf != NULL && p_cr >= p_lf)
- p = p_lf;
- else
- p = p_cr;
- } else if (UNLIKELY(p_lf != NULL)) {
- p = p_lf;
- } else {
- p = data + len;
+ {
+ size_t left = data + len - p;
+ const char* pe = p + MIN(left, max_header_size);
+
+ for (; p != pe; p++) {
+ ch = *p;
+ if (ch == CR || ch == LF) {
+ --p;
+ break;
+ }
+ if (!lenient && !IS_HEADER_CHAR(ch)) {
+ SET_ERRNO(HPE_INVALID_HEADER_TOKEN);
+ goto error;
+ }
+ }
+ if (p == data + len)
+ --p;
+ break;
}
- --p;
-
- break;
- }
case h_connection:
case h_transfer_encoding:
@@ -1500,7 +1532,7 @@ reexecute:
case h_content_length:
if (ch == ' ') break;
h_state = h_content_length_num;
- /* FALLTHROUGH */
+ /* fall through */
case h_content_length_num:
{
@@ -1539,16 +1571,41 @@ reexecute:
goto error;
/* Transfer-Encoding: chunked */
+ case h_matching_transfer_encoding_token_start:
+ /* looking for 'Transfer-Encoding: chunked' */
+ if ('c' == c) {
+ h_state = h_matching_transfer_encoding_chunked;
+ } else if (STRICT_TOKEN(c)) {
+ /* TODO(indutny): similar code below does this, but why?
+ * At the very least it seems to be inconsistent given that
+ * h_matching_transfer_encoding_token does not check for
+ * `STRICT_TOKEN`
+ */
+ h_state = h_matching_transfer_encoding_token;
+ } else if (c == ' ' || c == '\t') {
+ /* Skip lws */
+ } else {
+ h_state = h_general;
+ }
+ break;
+
case h_matching_transfer_encoding_chunked:
parser->index++;
if (parser->index > sizeof(CHUNKED)-1
|| c != CHUNKED[parser->index]) {
- h_state = h_general;
+ h_state = h_matching_transfer_encoding_token;
} else if (parser->index == sizeof(CHUNKED)-2) {
h_state = h_transfer_encoding_chunked;
}
break;
+ case h_matching_transfer_encoding_token:
+ if (ch == ',') {
+ h_state = h_matching_transfer_encoding_token_start;
+ parser->index = 0;
+ }
+ break;
+
case h_matching_connection_token_start:
/* looking for 'Connection: keep-alive' */
if (c == 'k') {
@@ -1607,7 +1664,7 @@ reexecute:
break;
case h_transfer_encoding_chunked:
- if (ch != ' ') h_state = h_general;
+ if (ch != ' ') h_state = h_matching_transfer_encoding_token;
break;
case h_connection_keep_alive:
@@ -1636,10 +1693,10 @@ reexecute:
}
parser->header_state = h_state;
- COUNT_HEADER_SIZE(p - start);
-
if (p == data + len)
--p;
+
+ COUNT_HEADER_SIZE(p - start);
break;
}
@@ -1657,6 +1714,10 @@ reexecute:
case s_header_value_lws:
{
if (ch == ' ' || ch == '\t') {
+ if (parser->header_state == h_content_length_num) {
+ /* treat obsolete line folding as space */
+ parser->header_state = h_content_length_ws;
+ }
UPDATE_STATE(s_header_value_start);
REEXECUTE();
}
@@ -1709,6 +1770,11 @@ reexecute:
case h_transfer_encoding_chunked:
parser->flags |= F_CHUNKED;
break;
+ case h_content_length:
+ /* do not allow empty content length */
+ SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
+ goto error;
+ break;
default:
break;
}
@@ -1732,12 +1798,17 @@ reexecute:
REEXECUTE();
}
- /* Cannot use chunked encoding and a content-length header together
- per the HTTP specification. */
- if ((parser->flags & F_CHUNKED) &&
+ /* Cannot us transfer-encoding and a content-length header together
+ per the HTTP specification. (RFC 7230 Section 3.3.3) */
+ if ((parser->flags & F_TRANSFER_ENCODING) &&
(parser->flags & F_CONTENTLENGTH)) {
- SET_ERRNO(HPE_UNEXPECTED_CONTENT_LENGTH);
- goto error;
+ /* Allow it for lenient parsing as long as `Transfer-Encoding` is
+ * not `chunked`
+ */
+ if (!lenient || (parser->flags & F_CHUNKED)) {
+ SET_ERRNO(HPE_UNEXPECTED_CONTENT_LENGTH);
+ goto error;
+ }
}
UPDATE_STATE(s_headers_done);
@@ -1772,7 +1843,7 @@ reexecute:
case 2:
parser->upgrade = 1;
- /* FALLTHROUGH */
+ /* fall through */
case 1:
parser->flags |= F_SKIPBODY;
break;
@@ -1796,6 +1867,7 @@ reexecute:
STRICT_CHECK(ch != LF);
parser->nread = 0;
+ nread = 0;
hasBody = parser->flags & F_CHUNKED ||
(parser->content_length > 0 && parser->content_length != ULLONG_MAX);
@@ -1811,8 +1883,31 @@ reexecute:
UPDATE_STATE(NEW_MESSAGE());
CALLBACK_NOTIFY(message_complete);
} else if (parser->flags & F_CHUNKED) {
- /* chunked encoding - ignore Content-Length header */
+ /* chunked encoding - ignore Content-Length header,
+ * prepare for a chunk */
UPDATE_STATE(s_chunk_size_start);
+ } else if (parser->flags & F_TRANSFER_ENCODING) {
+ if (parser->type == HTTP_REQUEST && !lenient) {
+ /* RFC 7230 3.3.3 */
+
+ /* If a Transfer-Encoding header field
+ * is present in a request and the chunked transfer coding is not
+ * the final encoding, the message body length cannot be determined
+ * reliably; the server MUST respond with the 400 (Bad Request)
+ * status code and then close the connection.
+ */
+ SET_ERRNO(HPE_INVALID_TRANSFER_ENCODING);
+ RETURN(p - data); /* Error */
+ } else {
+ /* RFC 7230 3.3.3 */
+
+ /* If a Transfer-Encoding header field is present in a response and
+ * the chunked transfer coding is not the final encoding, the
+ * message body length is determined by reading the connection until
+ * it is closed by the server.
+ */
+ UPDATE_STATE(s_body_identity_eof);
+ }
} else {
if (parser->content_length == 0) {
/* Content-Length header given but zero: Content-Length: 0\r\n */
@@ -1890,7 +1985,7 @@ reexecute:
case s_chunk_size_start:
{
- assert(parser->nread == 1);
+ assert(nread == 1);
assert(parser->flags & F_CHUNKED);
unhex_val = unhex[(unsigned char)ch];
@@ -1958,6 +2053,7 @@ reexecute:
STRICT_CHECK(ch != LF);
parser->nread = 0;
+ nread = 0;
if (parser->content_length == 0) {
parser->flags |= F_TRAILING;
@@ -2004,6 +2100,7 @@ reexecute:
assert(parser->flags & F_CHUNKED);
STRICT_CHECK(ch != LF);
parser->nread = 0;
+ nread = 0;
UPDATE_STATE(s_chunk_size_start);
CALLBACK_NOTIFY(chunk_complete);
break;
@@ -2015,7 +2112,7 @@ reexecute:
}
}
- /* Run callbacks for any marks that we have leftover after we ran our of
+ /* Run callbacks for any marks that we have leftover after we ran out of
* bytes. There should be at most one of these set, so it's OK to invoke
* them in series (unset marks will not result in callbacks).
*
@@ -2064,6 +2161,12 @@ http_message_needs_eof (const http_parse
return 0;
}
+ /* RFC 7230 3.3.3, see `s_headers_almost_done` */
+ if ((parser->flags & F_TRANSFER_ENCODING) &&
+ (parser->flags & F_CHUNKED) == 0) {
+ return 1;
+ }
+
if ((parser->flags & F_CHUNKED) || parser->content_length != ULLONG_MAX) {
return 0;
}
@@ -2097,6 +2200,16 @@ http_method_str (enum http_method m)
return ELEM_AT(method_strings, m, "<unknown>");
}
+const char *
+http_status_str (enum http_status s)
+{
+ switch (s) {
+#define XX(num, name, string) case HTTP_STATUS_##name: return #string;
+ HTTP_STATUS_MAP(XX)
+#undef XX
+ default: return "<unknown>";
+ }
+}
void
http_parser_init (http_parser *parser, enum http_parser_type t)
@@ -2157,7 +2270,7 @@ http_parse_host_char(enum http_host_stat
return s_http_host;
}
- /* FALLTHROUGH */
+ /* fall through */
case s_http_host_v6_end:
if (ch == ':') {
return s_http_host_port_start;
@@ -2170,7 +2283,7 @@ http_parse_host_char(enum http_host_stat
return s_http_host_v6_end;
}
- /* FALLTHROUGH */
+ /* fall through */
case s_http_host_v6_start:
if (IS_HEX(ch) || ch == ':' || ch == '.') {
return s_http_host_v6;
@@ -2186,7 +2299,7 @@ http_parse_host_char(enum http_host_stat
return s_http_host_v6_end;
}
- /* FALLTHROUGH */
+ /* fall through */
case s_http_host_v6_zone_start:
/* RFC 6874 Zone ID consists of 1*( unreserved / pct-encoded) */
if (IS_ALPHANUM(ch) || ch == '%' || ch == '.' || ch == '-' || ch == '_' ||
@@ -2211,12 +2324,13 @@ http_parse_host_char(enum http_host_stat
static int
http_parse_host(const char * buf, struct http_parser_url *u, int found_at) {
- assert(u->field_set & (1 << UF_HOST));
enum http_host_state s;
const char *p;
size_t buflen = u->field_data[UF_HOST].off + u->field_data[UF_HOST].len;
+ assert(u->field_set & (1 << UF_HOST));
+
u->field_data[UF_HOST].len = 0;
s = found_at ? s_http_userinfo_start : s_http_host_start;
@@ -2231,14 +2345,14 @@ http_parse_host(const char * buf, struct
switch(new_s) {
case s_http_host:
if (s != s_http_host) {
- u->field_data[UF_HOST].off = p - buf;
+ u->field_data[UF_HOST].off = (uint16_t)(p - buf);
}
u->field_data[UF_HOST].len++;
break;
case s_http_host_v6:
if (s != s_http_host_v6) {
- u->field_data[UF_HOST].off = p - buf;
+ u->field_data[UF_HOST].off = (uint16_t)(p - buf);
}
u->field_data[UF_HOST].len++;
break;
@@ -2250,7 +2364,7 @@ http_parse_host(const char * buf, struct
case s_http_host_port:
if (s != s_http_host_port) {
- u->field_data[UF_PORT].off = p - buf;
+ u->field_data[UF_PORT].off = (uint16_t)(p - buf);
u->field_data[UF_PORT].len = 0;
u->field_set |= (1 << UF_PORT);
}
@@ -2259,7 +2373,7 @@ http_parse_host(const char * buf, struct
case s_http_userinfo:
if (s != s_http_userinfo) {
- u->field_data[UF_USERINFO].off = p - buf ;
+ u->field_data[UF_USERINFO].off = (uint16_t)(p - buf);
u->field_data[UF_USERINFO].len = 0;
u->field_set |= (1 << UF_USERINFO);
}
@@ -2304,6 +2418,10 @@ http_parser_parse_url(const char *buf, s
enum http_parser_url_fields uf, old_uf;
int found_at = 0;
+ if (buflen == 0) {
+ return 1;
+ }
+
u->port = u->field_set = 0;
s = is_connect ? s_req_server_start : s_req_spaces_before_url;
old_uf = UF_MAX;
@@ -2331,7 +2449,7 @@ http_parser_parse_url(const char *buf, s
case s_req_server_with_at:
found_at = 1;
- /* FALLTHROUGH */
+ /* fall through */
case s_req_server:
uf = UF_HOST;
break;
@@ -2359,7 +2477,7 @@ http_parser_parse_url(const char *buf, s
continue;
}
- u->field_data[uf].off = p - buf;
+ u->field_data[uf].off = (uint16_t)(p - buf);
u->field_data[uf].len = 1;
u->field_set |= (1 << uf);
@@ -2422,6 +2540,7 @@ http_parser_pause(http_parser *parser, i
*/
if (HTTP_PARSER_ERRNO(parser) == HPE_OK ||
HTTP_PARSER_ERRNO(parser) == HPE_PAUSED) {
+ uint32_t nread = parser->nread; /* used by the SET_ERRNO macro */
SET_ERRNO((paused) ? HPE_PAUSED : HPE_OK);
} else {
assert(0 && "Attempting to pause parser in error state");
Index: node-v8.17.0/deps/http_parser/http_parser.gyp
===================================================================
--- node-v8.17.0.orig/deps/http_parser/http_parser.gyp
+++ node-v8.17.0/deps/http_parser/http_parser.gyp
@@ -56,7 +56,7 @@
'defines': [ 'HTTP_PARSER_STRICT=0' ],
'include_dirs': [ '.' ],
},
- 'defines': [ 'HTTP_MAX_HEADER_SIZE=8192', 'HTTP_PARSER_STRICT=0' ],
+ 'defines': [ 'HTTP_PARSER_STRICT=0' ],
'sources': [ './http_parser.c', ],
'conditions': [
['OS=="win"', {
@@ -79,7 +79,7 @@
'defines': [ 'HTTP_PARSER_STRICT=1' ],
'include_dirs': [ '.' ],
},
- 'defines': [ 'HTTP_MAX_HEADER_SIZE=8192', 'HTTP_PARSER_STRICT=1' ],
+ 'defines': [ 'HTTP_PARSER_STRICT=1' ],
'sources': [ './http_parser.c', ],
'conditions': [
['OS=="win"', {
Index: node-v8.17.0/deps/http_parser/http_parser.h
===================================================================
--- node-v8.17.0.orig/deps/http_parser/http_parser.h
+++ node-v8.17.0/deps/http_parser/http_parser.h
@@ -26,8 +26,8 @@ extern "C" {
/* Also update SONAME in the Makefile whenever you change these. */
#define HTTP_PARSER_VERSION_MAJOR 2
-#define HTTP_PARSER_VERSION_MINOR 8
-#define HTTP_PARSER_VERSION_PATCH 0
+#define HTTP_PARSER_VERSION_MINOR 9
+#define HTTP_PARSER_VERSION_PATCH 3
#include <stddef.h>
#if defined(_WIN32) && !defined(__MINGW32__) && \
@@ -225,6 +225,7 @@ enum flags
, F_UPGRADE = 1 << 5
, F_SKIPBODY = 1 << 6
, F_CONTENTLENGTH = 1 << 7
+ , F_TRANSFER_ENCODING = 1 << 8
};
@@ -271,6 +272,8 @@ enum flags
"unexpected content-length header") \
XX(INVALID_CHUNK_SIZE, \
"invalid character in chunk size header") \
+ XX(INVALID_TRANSFER_ENCODING, \
+ "request has invalid transfer-encoding") \
XX(INVALID_CONSTANT, "invalid constant string") \
XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\
XX(STRICT, "strict mode assertion failed") \
@@ -293,11 +296,11 @@ enum http_errno {
struct http_parser {
/** PRIVATE **/
unsigned int type : 2; /* enum http_parser_type */
- unsigned int flags : 8; /* F_* values from 'flags' enum; semi-public */
unsigned int state : 7; /* enum state from http_parser.c */
unsigned int header_state : 7; /* enum header_state from http_parser.c */
unsigned int index : 7; /* index into current matcher */
unsigned int lenient_http_headers : 1;
+ unsigned int flags : 16; /* F_* values from 'flags' enum; semi-public */
uint32_t nread; /* # bytes read in various scenarios */
uint64_t content_length; /* # bytes in body (0 if no Content-Length header) */
@@ -407,6 +410,9 @@ int http_should_keep_alive(const http_pa
/* Returns a string version of the HTTP method. */
const char *http_method_str(enum http_method m);
+/* Returns a string version of the HTTP status code. */
+const char *http_status_str(enum http_status s);
+
/* Return a string name of the given error */
const char *http_errno_name(enum http_errno err);
Index: node-v8.17.0/deps/http_parser/test.c
===================================================================
--- node-v8.17.0.orig/deps/http_parser/test.c
+++ node-v8.17.0/deps/http_parser/test.c
@@ -27,9 +27,7 @@
#include <stdarg.h>
#if defined(__APPLE__)
-# undef strlcat
# undef strlncpy
-# undef strlcpy
#endif /* defined(__APPLE__) */
#undef TRUE
@@ -43,7 +41,9 @@
#define MIN(a,b) ((a) < (b) ? (a) : (b))
-static http_parser *parser;
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof(*x))
+
+static http_parser parser;
struct message {
const char *name; // for debugging purposes
@@ -153,10 +153,10 @@ const struct message requests[] =
,.body= ""
}
-#define DUMBFUCK 2
-, {.name= "dumbfuck"
+#define DUMBLUCK 2
+, {.name= "dumbluck"
,.type= HTTP_REQUEST
- ,.raw= "GET /dumbfuck HTTP/1.1\r\n"
+ ,.raw= "GET /dumbluck HTTP/1.1\r\n"
"aaaaaaaaaaaaa:++++++++++\r\n"
"\r\n"
,.should_keep_alive= TRUE
@@ -166,8 +166,8 @@ const struct message requests[] =
,.method= HTTP_GET
,.query_string= ""
,.fragment= ""
- ,.request_path= "/dumbfuck"
- ,.request_url= "/dumbfuck"
+ ,.request_path= "/dumbluck"
+ ,.request_url= "/dumbluck"
,.num_headers= 1
,.headers=
{ { "aaaaaaaaaaaaa", "++++++++++" }
@@ -262,7 +262,6 @@ const struct message requests[] =
,.type= HTTP_REQUEST
,.raw= "POST /post_identity_body_world?q=search#hey HTTP/1.1\r\n"
"Accept: */*\r\n"
- "Transfer-Encoding: identity\r\n"
"Content-Length: 5\r\n"
"\r\n"
"World"
@@ -275,10 +274,9 @@ const struct message requests[] =
,.fragment= "hey"
,.request_path= "/post_identity_body_world"
,.request_url= "/post_identity_body_world?q=search#hey"
- ,.num_headers= 3
+ ,.num_headers= 2
,.headers=
{ { "Accept", "*/*" }
- , { "Transfer-Encoding", "identity" }
, { "Content-Length", "5" }
}
,.body= "World"
@@ -371,13 +369,13 @@ const struct message requests[] =
,.chunk_lengths= { 5, 6 }
}
-#define CHUNKED_W_BULLSHIT_AFTER_LENGTH 11
-, {.name= "with bullshit after the length"
+#define CHUNKED_W_NONSENSE_AFTER_LENGTH 11
+, {.name= "with nonsense after the length"
,.type= HTTP_REQUEST
- ,.raw= "POST /chunked_w_bullshit_after_length HTTP/1.1\r\n"
+ ,.raw= "POST /chunked_w_nonsense_after_length HTTP/1.1\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
- "5; ihatew3;whatthefuck=aretheseparametersfor\r\nhello\r\n"
+ "5; ilovew3;whattheluck=aretheseparametersfor\r\nhello\r\n"
"6; blahblah; blah\r\n world\r\n"
"0\r\n"
"\r\n"
@@ -388,8 +386,8 @@ const struct message requests[] =
,.method= HTTP_POST
,.query_string= ""
,.fragment= ""
- ,.request_path= "/chunked_w_bullshit_after_length"
- ,.request_url= "/chunked_w_bullshit_after_length"
+ ,.request_path= "/chunked_w_nonsense_after_length"
+ ,.request_url= "/chunked_w_nonsense_after_length"
,.num_headers= 1
,.headers=
{ { "Transfer-Encoding", "chunked" }
@@ -1174,7 +1172,80 @@ const struct message requests[] =
,.body= ""
}
-, {.name= NULL } /* sentinel */
+#define SOURCE_ICE_REQUEST 42
+, {.name = "source request"
+ ,.type= HTTP_REQUEST
+ ,.raw= "SOURCE /music/sweet/music ICE/1.0\r\n"
+ "Host: example.com\r\n"
+ "\r\n"
+ ,.should_keep_alive= FALSE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 0
+ ,.method= HTTP_SOURCE
+ ,.request_path= "/music/sweet/music"
+ ,.request_url= "/music/sweet/music"
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.num_headers= 1
+ ,.headers= { { "Host", "example.com" } }
+ ,.body= ""
+ }
+
+#define POST_MULTI_TE_LAST_CHUNKED 43
+, {.name= "post - multi coding transfer-encoding chunked body"
+ ,.type= HTTP_REQUEST
+ ,.raw= "POST / HTTP/1.1\r\n"
+ "Transfer-Encoding: deflate, chunked\r\n"
+ "\r\n"
+ "1e\r\nall your base are belong to us\r\n"
+ "0\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_POST
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "/"
+ ,.request_url= "/"
+ ,.num_headers= 1
+ ,.headers=
+ { { "Transfer-Encoding" , "deflate, chunked" }
+ }
+ ,.body= "all your base are belong to us"
+ ,.num_chunks_complete= 2
+ ,.chunk_lengths= { 0x1e }
+ }
+
+#define POST_MULTI_LINE_TE_LAST_CHUNKED 43
+, {.name= "post - multi coding transfer-encoding chunked body"
+ ,.type= HTTP_REQUEST
+ ,.raw= "POST / HTTP/1.1\r\n"
+ "Transfer-Encoding: deflate,\r\n"
+ " chunked\r\n"
+ "\r\n"
+ "1e\r\nall your base are belong to us\r\n"
+ "0\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_POST
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "/"
+ ,.request_url= "/"
+ ,.num_headers= 1
+ ,.headers=
+ { { "Transfer-Encoding" , "deflate, chunked" }
+ }
+ ,.body= "all your base are belong to us"
+ ,.num_chunks_complete= 2
+ ,.chunk_lengths= { 0x1e }
+ }
};
/* * R E S P O N S E S * */
@@ -1952,8 +2023,28 @@ const struct message responses[] =
,.num_chunks_complete= 3
,.chunk_lengths= { 2, 2 }
}
-
-, {.name= NULL } /* sentinel */
+#define HTTP_200_MULTI_TE_NOT_LAST_CHUNKED 28
+, {.name= "HTTP 200 response with `chunked` being *not last* Transfer-Encoding"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/1.1 200 OK\r\n"
+ "Transfer-Encoding: chunked, identity\r\n"
+ "\r\n"
+ "2\r\n"
+ "OK\r\n"
+ "0\r\n"
+ "\r\n"
+ ,.should_keep_alive= FALSE
+ ,.message_complete_on_eof= TRUE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.status_code= 200
+ ,.response_status= "OK"
+ ,.num_headers= 1
+ ,.headers= { { "Transfer-Encoding", "chunked, identity" }
+ }
+ ,.body= "2\r\nOK\r\n0\r\n\r\n"
+ ,.num_chunks_complete= 0
+ }
};
/* strnlen() is a POSIX.2008 addition. Can't rely on it being available so
@@ -1994,12 +2085,6 @@ strlncat(char *dst, size_t len, const ch
}
size_t
-strlcat(char *dst, const char *src, size_t len)
-{
- return strlncat(dst, len, src, (size_t) -1);
-}
-
-size_t
strlncpy(char *dst, size_t len, const char *src, size_t n)
{
size_t slen;
@@ -2017,16 +2102,10 @@ strlncpy(char *dst, size_t len, const ch
return slen;
}
-size_t
-strlcpy(char *dst, const char *src, size_t len)
-{
- return strlncpy(dst, len, src, (size_t) -1);
-}
-
int
request_url_cb (http_parser *p, const char *buf, size_t len)
{
- assert(p == parser);
+ assert(p == &parser);
strlncat(messages[num_messages].request_url,
sizeof(messages[num_messages].request_url),
buf,
@@ -2037,7 +2116,7 @@ request_url_cb (http_parser *p, const ch
int
header_field_cb (http_parser *p, const char *buf, size_t len)
{
- assert(p == parser);
+ assert(p == &parser);
struct message *m = &messages[num_messages];
if (m->last_header_element != FIELD)
@@ -2056,7 +2135,7 @@ header_field_cb (http_parser *p, const c
int
header_value_cb (http_parser *p, const char *buf, size_t len)
{
- assert(p == parser);
+ assert(p == &parser);
struct message *m = &messages[num_messages];
strlncat(m->headers[m->num_headers-1][1],
@@ -2085,7 +2164,7 @@ check_body_is_final (const http_parser *
int
body_cb (http_parser *p, const char *buf, size_t len)
{
- assert(p == parser);
+ assert(p == &parser);
strlncat(messages[num_messages].body,
sizeof(messages[num_messages].body),
buf,
@@ -2099,7 +2178,7 @@ body_cb (http_parser *p, const char *buf
int
count_body_cb (http_parser *p, const char *buf, size_t len)
{
- assert(p == parser);
+ assert(p == &parser);
assert(buf);
messages[num_messages].body_size += len;
check_body_is_final(p);
@@ -2109,7 +2188,8 @@ count_body_cb (http_parser *p, const cha
int
message_begin_cb (http_parser *p)
{
- assert(p == parser);
+ assert(p == &parser);
+ assert(!messages[num_messages].message_begin_cb_called);
messages[num_messages].message_begin_cb_called = TRUE;
return 0;
}
@@ -2117,21 +2197,22 @@ message_begin_cb (http_parser *p)
int
headers_complete_cb (http_parser *p)
{
- assert(p == parser);
- messages[num_messages].method = parser->method;
- messages[num_messages].status_code = parser->status_code;
- messages[num_messages].http_major = parser->http_major;
- messages[num_messages].http_minor = parser->http_minor;
+ assert(p == &parser);
+ messages[num_messages].method = parser.method;
+ messages[num_messages].status_code = parser.status_code;
+ messages[num_messages].http_major = parser.http_major;
+ messages[num_messages].http_minor = parser.http_minor;
messages[num_messages].headers_complete_cb_called = TRUE;
- messages[num_messages].should_keep_alive = http_should_keep_alive(parser);
+ messages[num_messages].should_keep_alive = http_should_keep_alive(&parser);
return 0;
}
int
message_complete_cb (http_parser *p)
{
- assert(p == parser);
- if (messages[num_messages].should_keep_alive != http_should_keep_alive(parser))
+ assert(p == &parser);
+ if (messages[num_messages].should_keep_alive !=
+ http_should_keep_alive(&parser))
{
fprintf(stderr, "\n\n *** Error http_should_keep_alive() should have same "
"value in both on_message_complete and on_headers_complete "
@@ -2162,7 +2243,7 @@ message_complete_cb (http_parser *p)
int
response_status_cb (http_parser *p, const char *buf, size_t len)
{
- assert(p == parser);
+ assert(p == &parser);
messages[num_messages].status_cb_called = TRUE;
@@ -2176,7 +2257,7 @@ response_status_cb (http_parser *p, cons
int
chunk_header_cb (http_parser *p)
{
- assert(p == parser);
+ assert(p == &parser);
int chunk_idx = messages[num_messages].num_chunks;
messages[num_messages].num_chunks++;
if (chunk_idx < MAX_CHUNKS) {
@@ -2189,7 +2270,7 @@ chunk_header_cb (http_parser *p)
int
chunk_complete_cb (http_parser *p)
{
- assert(p == parser);
+ assert(p == &parser);
/* Here we want to verify that each chunk_header_cb is matched by a
* chunk_complete_cb, so not only should the total number of calls to
@@ -2394,7 +2475,7 @@ connect_headers_complete_cb (http_parser
int
connect_message_complete_cb (http_parser *p)
{
- messages[num_messages].should_keep_alive = http_should_keep_alive(parser);
+ messages[num_messages].should_keep_alive = http_should_keep_alive(&parser);
return message_complete_cb(p);
}
@@ -2467,30 +2548,15 @@ void
parser_init (enum http_parser_type type)
{
num_messages = 0;
-
- assert(parser == NULL);
-
- parser = malloc(sizeof(http_parser));
-
- http_parser_init(parser, type);
-
+ http_parser_init(&parser, type);
memset(&messages, 0, sizeof messages);
-
-}
-
-void
-parser_free ()
-{
- assert(parser);
- free(parser);
- parser = NULL;
}
size_t parse (const char *buf, size_t len)
{
size_t nparsed;
currently_parsing_eof = (len == 0);
- nparsed = http_parser_execute(parser, &settings, buf, len);
+ nparsed = http_parser_execute(&parser, &settings, buf, len);
return nparsed;
}
@@ -2498,7 +2564,7 @@ size_t parse_count_body (const char *buf
{
size_t nparsed;
currently_parsing_eof = (len == 0);
- nparsed = http_parser_execute(parser, &settings_count_body, buf, len);
+ nparsed = http_parser_execute(&parser, &settings_count_body, buf, len);
return nparsed;
}
@@ -2509,7 +2575,7 @@ size_t parse_pause (const char *buf, siz
currently_parsing_eof = (len == 0);
current_pause_parser = &s;
- nparsed = http_parser_execute(parser, current_pause_parser, buf, len);
+ nparsed = http_parser_execute(&parser, current_pause_parser, buf, len);
return nparsed;
}
@@ -2517,7 +2583,7 @@ size_t parse_connect (const char *buf, s
{
size_t nparsed;
currently_parsing_eof = (len == 0);
- nparsed = http_parser_execute(parser, &settings_connect, buf, len);
+ nparsed = http_parser_execute(&parser, &settings_connect, buf, len);
return nparsed;
}
@@ -2737,7 +2803,7 @@ static void
print_error (const char *raw, size_t error_location)
{
fprintf(stderr, "\n*** %s ***\n\n",
- http_errno_description(HTTP_PARSER_ERRNO(parser)));
+ http_errno_description(HTTP_PARSER_ERRNO(&parser)));
int this_line = 0, char_len = 0;
size_t i, j, len = strlen(raw), error_location_line = 0;
@@ -3280,6 +3346,24 @@ const struct url_test url_tests[] =
,.rv=1 /* s_dead */
}
+, {.name="empty url"
+ ,.url=""
+ ,.is_connect=0
+ ,.rv=1
+ }
+
+, {.name="NULL url"
+ ,.url=NULL
+ ,.is_connect=0
+ ,.rv=1
+ }
+
+, {.name="full of spaces url"
+ ,.url=" "
+ ,.is_connect=0
+ ,.rv=1
+ }
+
#if HTTP_PARSER_STRICT
, {.name="tab in URL"
@@ -3364,7 +3448,7 @@ test_parse_url (void)
memset(&u, 0, sizeof(u));
rv = http_parser_parse_url(test->url,
- strlen(test->url),
+ test->url ? strlen(test->url) : 0,
test->is_connect,
&u);
@@ -3405,6 +3489,14 @@ test_method_str (void)
}
void
+test_status_str (void)
+{
+ assert(0 == strcmp("OK", http_status_str(HTTP_STATUS_OK)));
+ assert(0 == strcmp("Not Found", http_status_str(HTTP_STATUS_NOT_FOUND)));
+ assert(0 == strcmp("<unknown>", http_status_str(1337)));
+}
+
+void
test_message (const struct message *message)
{
size_t raw_len = strlen(message->raw);
@@ -3418,9 +3510,18 @@ test_message (const struct message *mess
size_t msg2len = raw_len - msg1len;
if (msg1len) {
+ assert(num_messages == 0);
+ messages[0].headers_complete_cb_called = FALSE;
+
read = parse(msg1, msg1len);
- if (message->upgrade && parser->upgrade && num_messages > 0) {
+ if (!messages[0].headers_complete_cb_called && parser.nread != read) {
+ assert(parser.nread == read);
+ print_error(msg1, read);
+ abort();
+ }
+
+ if (message->upgrade && parser.upgrade && num_messages > 0) {
messages[num_messages - 1].upgrade = msg1 + read;
goto test;
}
@@ -3434,7 +3535,7 @@ test_message (const struct message *mess
read = parse(msg2, msg2len);
- if (message->upgrade && parser->upgrade) {
+ if (message->upgrade && parser.upgrade) {
messages[num_messages - 1].upgrade = msg2 + read;
goto test;
}
@@ -3459,8 +3560,6 @@ test_message (const struct message *mess
}
if(!message_eq(0, 0, message)) abort();
-
- parser_free();
}
}
@@ -3496,8 +3595,6 @@ test_message_count_body (const struct me
}
if(!message_eq(0, 0, message)) abort();
-
- parser_free();
}
void
@@ -3510,11 +3607,9 @@ test_simple_type (const char *buf,
enum http_errno err;
parse(buf, strlen(buf));
- err = HTTP_PARSER_ERRNO(parser);
+ err = HTTP_PARSER_ERRNO(&parser);
parse(NULL, 0);
- parser_free();
-
/* In strict mode, allow us to pass with an unexpected HPE_STRICT as
* long as the caller isn't expecting success.
*/
@@ -3643,7 +3738,7 @@ test_chunked_content_length_error (int r
parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf));
assert(parsed == strlen(buf));
- buf = "Transfer-Encoding: chunked\r\nContent-Length: 1\r\n\r\n";
+ buf = "Transfer-Encoding: anything\r\nContent-Length: 1\r\n\r\n";
size_t buflen = strlen(buf);
parsed = http_parser_execute(&parser, &settings_null, buf, buflen);
@@ -3854,7 +3949,7 @@ test_multiple3 (const struct message *r1
read = parse(total, strlen(total));
- if (parser->upgrade) {
+ if (parser.upgrade) {
upgrade_message_fix(total, read, 3, r1, r2, r3);
goto test;
}
@@ -3881,8 +3976,6 @@ test:
if (!message_eq(0, 0, r1)) abort();
if (message_count > 1 && !message_eq(1, 0, r2)) abort();
if (message_count > 2 && !message_eq(2, 0, r3)) abort();
-
- parser_free();
}
/* SCAN through every possible breaking to make sure the
@@ -3936,9 +4029,17 @@ test_scan (const struct message *r1, con
strlncpy(buf3, sizeof(buf1), total+j, buf3_len);
buf3[buf3_len] = 0;
+ assert(num_messages == 0);
+ messages[0].headers_complete_cb_called = FALSE;
+
read = parse(buf1, buf1_len);
- if (parser->upgrade) goto test;
+ if (!messages[0].headers_complete_cb_called && parser.nread != read) {
+ print_error(buf1, read);
+ goto error;
+ }
+
+ if (parser.upgrade) goto test;
if (read != buf1_len) {
print_error(buf1, read);
@@ -3947,7 +4048,7 @@ test_scan (const struct message *r1, con
read += parse(buf2, buf2_len);
- if (parser->upgrade) goto test;
+ if (parser.upgrade) goto test;
if (read != buf1_len + buf2_len) {
print_error(buf2, read);
@@ -3956,7 +4057,7 @@ test_scan (const struct message *r1, con
read += parse(buf3, buf3_len);
- if (parser->upgrade) goto test;
+ if (parser.upgrade) goto test;
if (read != buf1_len + buf2_len + buf3_len) {
print_error(buf3, read);
@@ -3966,7 +4067,7 @@ test_scan (const struct message *r1, con
parse(NULL, 0);
test:
- if (parser->upgrade) {
+ if (parser.upgrade) {
upgrade_message_fix(total, read, 3, r1, r2, r3);
}
@@ -3990,8 +4091,6 @@ test:
fprintf(stderr, "\n\nError matching messages[2] in test_scan.\n");
goto error;
}
-
- parser_free();
}
}
}
@@ -4055,7 +4154,7 @@ test_message_pause (const struct message
// completion callback.
if (messages[0].message_complete_cb_called &&
msg->upgrade &&
- parser->upgrade) {
+ parser.upgrade) {
messages[0].upgrade = buf + nread;
goto test;
}
@@ -4063,17 +4162,16 @@ test_message_pause (const struct message
if (nread < buflen) {
// Not much do to if we failed a strict-mode check
- if (HTTP_PARSER_ERRNO(parser) == HPE_STRICT) {
- parser_free();
+ if (HTTP_PARSER_ERRNO(&parser) == HPE_STRICT) {
return;
}
- assert (HTTP_PARSER_ERRNO(parser) == HPE_PAUSED);
+ assert (HTTP_PARSER_ERRNO(&parser) == HPE_PAUSED);
}
buf += nread;
buflen -= nread;
- http_parser_pause(parser, 0);
+ http_parser_pause(&parser, 0);
} while (buflen > 0);
nread = parse_pause(NULL, 0);
@@ -4086,8 +4184,6 @@ test:
}
if(!message_eq(0, 0, msg)) abort();
-
- parser_free();
}
/* Verify that body and next message won't be parsed in responses to CONNECT */
@@ -4107,17 +4203,12 @@ test_message_connect (const struct messa
}
if(!message_eq(0, 1, msg)) abort();
-
- parser_free();
}
int
main (void)
{
- parser = NULL;
- int i, j, k;
- int request_count;
- int response_count;
+ unsigned i, j, k;
unsigned long version;
unsigned major;
unsigned minor;
@@ -4131,13 +4222,11 @@ main (void)
printf("sizeof(http_parser) = %u\n", (unsigned int)sizeof(http_parser));
- for (request_count = 0; requests[request_count].name; request_count++);
- for (response_count = 0; responses[response_count].name; response_count++);
-
//// API
test_preserve_data();
test_parse_url();
test_method_str();
+ test_status_str();
//// NREAD
test_header_nread_value();
@@ -4170,6 +4259,13 @@ main (void)
test_simple_type(
"POST / HTTP/1.1\r\n"
+ "Content-Length:\r\n" // empty
+ "\r\n",
+ HPE_INVALID_CONTENT_LENGTH,
+ HTTP_REQUEST);
+
+ test_simple_type(
+ "POST / HTTP/1.1\r\n"
"Content-Length: 42 \r\n" // Note the surrounding whitespace.
"\r\n",
HPE_OK,
@@ -4189,6 +4285,20 @@ main (void)
HPE_INVALID_CONTENT_LENGTH,
HTTP_REQUEST);
+ test_simple_type(
+ "POST / HTTP/1.1\r\n"
+ "Content-Length: 42\r\n"
+ " Hello world!\r\n",
+ HPE_INVALID_CONTENT_LENGTH,
+ HTTP_REQUEST);
+
+ test_simple_type(
+ "POST / HTTP/1.1\r\n"
+ "Content-Length: 42\r\n"
+ " \r\n",
+ HPE_OK,
+ HTTP_REQUEST);
+
//// RESPONSES
test_simple_type("HTP/1.1 200 OK\r\n\r\n", HPE_INVALID_VERSION, HTTP_RESPONSE);
@@ -4196,24 +4306,25 @@ main (void)
test_simple_type("HTTP/11.1 200 OK\r\n\r\n", HPE_INVALID_VERSION, HTTP_RESPONSE);
test_simple_type("HTTP/1.01 200 OK\r\n\r\n", HPE_INVALID_VERSION, HTTP_RESPONSE);
test_simple_type("HTTP/1.1\t200 OK\r\n\r\n", HPE_INVALID_VERSION, HTTP_RESPONSE);
+ test_simple_type("\rHTTP/1.1\t200 OK\r\n\r\n", HPE_INVALID_VERSION, HTTP_RESPONSE);
- for (i = 0; i < response_count; i++) {
+ for (i = 0; i < ARRAY_SIZE(responses); i++) {
test_message(&responses[i]);
}
- for (i = 0; i < response_count; i++) {
+ for (i = 0; i < ARRAY_SIZE(responses); i++) {
test_message_pause(&responses[i]);
}
- for (i = 0; i < response_count; i++) {
+ for (i = 0; i < ARRAY_SIZE(responses); i++) {
test_message_connect(&responses[i]);
}
- for (i = 0; i < response_count; i++) {
+ for (i = 0; i < ARRAY_SIZE(responses); i++) {
if (!responses[i].should_keep_alive) continue;
- for (j = 0; j < response_count; j++) {
+ for (j = 0; j < ARRAY_SIZE(responses); j++) {
if (!responses[j].should_keep_alive) continue;
- for (k = 0; k < response_count; k++) {
+ for (k = 0; k < ARRAY_SIZE(responses); k++) {
test_multiple3(&responses[i], &responses[j], &responses[k]);
}
}
@@ -4273,11 +4384,16 @@ main (void)
/// REQUESTS
+ test_simple("GET / IHTTP/1.0\r\n\r\n", HPE_INVALID_CONSTANT);
+ test_simple("GET / ICE/1.0\r\n\r\n", HPE_INVALID_CONSTANT);
test_simple("GET / HTP/1.1\r\n\r\n", HPE_INVALID_VERSION);
test_simple("GET / HTTP/01.1\r\n\r\n", HPE_INVALID_VERSION);
test_simple("GET / HTTP/11.1\r\n\r\n", HPE_INVALID_VERSION);
test_simple("GET / HTTP/1.01\r\n\r\n", HPE_INVALID_VERSION);
+ test_simple("GET / HTTP/1.0\r\nHello: w\1rld\r\n\r\n", HPE_INVALID_HEADER_TOKEN);
+ test_simple("GET / HTTP/1.0\r\nHello: woooo\2rld\r\n\r\n", HPE_INVALID_HEADER_TOKEN);
+
// Extended characters - see nodejs/test/parallel/test-http-headers-obstext.js
test_simple("GET / HTTP/1.1\r\n"
"Test: Düsseldorf\r\n",
@@ -4291,6 +4407,12 @@ main (void)
"fooba",
HPE_OK);
+ // Unknown Transfer-Encoding in request
+ test_simple("GET / HTTP/1.1\r\n"
+ "Transfer-Encoding: unknown\r\n"
+ "\r\n",
+ HPE_INVALID_TRANSFER_ENCODING);
+
static const char *all_methods[] = {
"DELETE",
"GET",
@@ -4360,9 +4482,9 @@ main (void)
"\r\n",
HPE_INVALID_HEADER_TOKEN);
- const char *dumbfuck2 =
+ const char *dumbluck2 =
"GET / HTTP/1.1\r\n"
- "X-SSL-Bullshit: -----BEGIN CERTIFICATE-----\r\n"
+ "X-SSL-Nonsense: -----BEGIN CERTIFICATE-----\r\n"
"\tMIIFbTCCBFWgAwIBAgICH4cwDQYJKoZIhvcNAQEFBQAwcDELMAkGA1UEBhMCVUsx\r\n"
"\tETAPBgNVBAoTCGVTY2llbmNlMRIwEAYDVQQLEwlBdXRob3JpdHkxCzAJBgNVBAMT\r\n"
"\tAkNBMS0wKwYJKoZIhvcNAQkBFh5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMu\r\n"
@@ -4395,7 +4517,7 @@ main (void)
"\tRA==\r\n"
"\t-----END CERTIFICATE-----\r\n"
"\r\n";
- test_simple(dumbfuck2, HPE_OK);
+ test_simple(dumbluck2, HPE_OK);
const char *corrupted_connection =
"GET / HTTP/1.1\r\n"
@@ -4429,19 +4551,19 @@ main (void)
/* check to make sure our predefined requests are okay */
- for (i = 0; requests[i].name; i++) {
+ for (i = 0; i < ARRAY_SIZE(requests); i++) {
test_message(&requests[i]);
}
- for (i = 0; i < request_count; i++) {
+ for (i = 0; i < ARRAY_SIZE(requests); i++) {
test_message_pause(&requests[i]);
}
- for (i = 0; i < request_count; i++) {
+ for (i = 0; i < ARRAY_SIZE(requests); i++) {
if (!requests[i].should_keep_alive) continue;
- for (j = 0; j < request_count; j++) {
+ for (j = 0; j < ARRAY_SIZE(requests); j++) {
if (!requests[j].should_keep_alive) continue;
- for (k = 0; k < request_count; k++) {
+ for (k = 0; k < ARRAY_SIZE(requests); k++) {
test_multiple3(&requests[i], &requests[j], &requests[k]);
}
}
@@ -4462,7 +4584,7 @@ main (void)
printf("request scan 3/4 ");
test_scan( &requests[TWO_CHUNKS_MULT_ZERO_END]
, &requests[CHUNKED_W_TRAILING_HEADERS]
- , &requests[CHUNKED_W_BULLSHIT_AFTER_LENGTH]
+ , &requests[CHUNKED_W_NONSENSE_AFTER_LENGTH]
);
printf("request scan 4/4 ");
Index: node-v8.17.0/test/parallel/test-http-invalid-te.js
===================================================================
--- /dev/null
+++ node-v8.17.0/test/parallel/test-http-invalid-te.js
@@ -0,0 +1,40 @@
+'use strict';
+
+const common = require('../common');
+
+// Test https://hackerone.com/reports/735748 is fixed.
+
+const assert = require('assert');
+const http = require('http');
+const net = require('net');
+
+const REQUEST_BB = `POST / HTTP/1.1
+Content-Type: text/plain; charset=utf-8
+Host: hacker.exploit.com
+Connection: keep-alive
+Content-Length: 10
+Transfer-Encoding: chunked, eee
+
+HELLOWORLDPOST / HTTP/1.1
+Content-Type: text/plain; charset=utf-8
+Host: hacker.exploit.com
+Connection: keep-alive
+Content-Length: 28
+
+I AM A SMUGGLED REQUEST!!!
+`;
+
+const server = http.createServer(common.mustNotCall());
+
+server.on('clientError', common.mustCall((err) => {
+ assert.strictEqual(err.code, 'HPE_UNEXPECTED_CONTENT_LENGTH');
+ server.close();
+}));
+
+server.listen(0, common.mustCall(() => {
+ const client = net.connect(
+ server.address().port,
+ common.mustCall(() => {
+ client.end(REQUEST_BB.replace(/\n/g, '\r\n'));
+ }));
+}));
Index: node-v8.17.0/test/parallel/test-http-insecure-parser.js
===================================================================
--- /dev/null
+++ node-v8.17.0/test/parallel/test-http-insecure-parser.js
@@ -0,0 +1,35 @@
+// Flags: --insecure-http-parser
+
+'use strict';
+const common = require('../common');
+const assert = require('assert');
+const http = require('http');
+const net = require('net');
+
+const server = http.createServer(function(req, res) {
+ assert.strictEqual(req.headers['content-type'], 'text/te\bt');
+ req.pipe(res);
+});
+
+server.listen(0, common.mustCall(function() {
+ const bufs = [];
+ const client = net.connect(
+ this.address().port,
+ function() {
+ client.write(
+ 'GET / HTTP/1.1\r\n' +
+ 'Content-Type: text/te\x08t\r\n' +
+ 'Connection: close\r\n\r\n');
+ }
+ );
+ client.on('data', function(chunk) {
+ bufs.push(chunk);
+ });
+ client.on('end', common.mustCall(function() {
+ const head = Buffer.concat(bufs)
+ .toString('latin1')
+ .split('\r\n')[0];
+ assert.strictEqual(head, 'HTTP/1.1 200 OK');
+ server.close();
+ }));
+}));
Index: node-v8.17.0/doc/api/cli.md
===================================================================
--- node-v8.17.0.orig/doc/api/cli.md
+++ node-v8.17.0/doc/api/cli.md
@@ -412,6 +412,17 @@ added: v8.15.0
Specify the maximum size, in bytes, of HTTP headers. Defaults to 8KB.
+### `--insecure-http-parser`
+<!-- YAML
+added: REPLACEME
+-->
+
+Use an insecure HTTP parser that accepts invalid HTTP headers. This may allow
+interoperability with non-conformant HTTP implementations. It may also allow
+request smuggling and other HTTP attacks that rely on invalid headers being
+accepted. Avoid using this option.
+
+
## Environment Variables
### `NODE_DEBUG=module[,…]`
Index: node-v8.17.0/doc/node.1
===================================================================
--- node-v8.17.0.orig/doc/node.1
+++ node-v8.17.0/doc/node.1
@@ -115,6 +115,13 @@ Set the host:port to be used when the in
Specify the maximum size of HTTP headers in bytes. Defaults to 8KB.
.TP
+.BR \-\-insecure\-http\-parser
+Use an insecure HTTP parser that accepts invalid HTTP headers. This may allow
+interoperability with non-conformant HTTP implementations. It may also allow
+request smuggling and other HTTP attacks that rely on invalid headers being
+accepted. Avoid using this option.
+
+.TP
.BR \-\-no\-deprecation
Silence deprecation warnings.
Index: node-v8.17.0/lib/_http_client.js
===================================================================
--- node-v8.17.0.orig/lib/_http_client.js
+++ node-v8.17.0/lib/_http_client.js
@@ -31,6 +31,7 @@ const {
debug,
freeParser,
httpSocketSetup,
+ isLenient,
parsers
} = require('_http_common');
const { OutgoingMessage } = require('_http_outgoing');
@@ -622,7 +623,7 @@ function tickOnSocket(req, socket) {
var parser = parsers.alloc();
req.socket = socket;
req.connection = socket;
- parser.reinitialize(HTTPParser.RESPONSE, parser[is_reused_symbol]);
+ parser.reinitialize(HTTPParser.RESPONSE, parser[is_reused_symbol], isLenient());
if (process.domain) {
process.domain.add(parser);
}
Index: node-v8.17.0/lib/_http_common.js
===================================================================
--- node-v8.17.0.orig/lib/_http_common.js
+++ node-v8.17.0/lib/_http_common.js
@@ -164,7 +164,7 @@ function parserOnMessageComplete() {
var parsers = new FreeList('parsers', 1000, function() {
- var parser = new HTTPParser(HTTPParser.REQUEST);
+ var parser = new HTTPParser(HTTPParser.REQUEST, isLenient());
parser._headers = [];
parser._url = '';
@@ -353,6 +353,16 @@ function checkInvalidHeaderChar(val) {
return false;
}
+let warnedLenient = false;
+
+function isLenient() {
+ if (process.insecureHttpParser && !warnedLenient) {
+ warnedLenient = true;
+ process.emitWarning('Using insecure HTTP parsing');
+ }
+ return !!process.insecureHttpParser;
+}
+
module.exports = {
_checkInvalidHeaderChar: checkInvalidHeaderChar,
_checkIsHttpToken: checkIsHttpToken,
@@ -364,5 +374,6 @@ module.exports = {
httpSocketSetup,
methods,
parsers,
- kIncomingMessage
+ kIncomingMessage,
+ isLenient
};
Index: node-v8.17.0/lib/_http_server.js
===================================================================
--- node-v8.17.0.orig/lib/_http_server.js
+++ node-v8.17.0/lib/_http_server.js
@@ -34,6 +34,7 @@ const {
chunkExpression,
httpSocketSetup,
kIncomingMessage,
+ isLenient,
_checkInvalidHeaderChar: checkInvalidHeaderChar
} = require('_http_common');
const { OutgoingMessage } = require('_http_outgoing');
@@ -333,7 +334,7 @@ function connectionListenerInternal(serv
socket.on('timeout', socketOnTimeout);
var parser = parsers.alloc();
- parser.reinitialize(HTTPParser.REQUEST, parser[is_reused_symbol]);
+ parser.reinitialize(HTTPParser.REQUEST, parser[is_reused_symbol], isLenient());
parser.socket = socket;
// We are starting to wait for our headers.
Index: node-v8.17.0/src/node.cc
===================================================================
--- node-v8.17.0.orig/src/node.cc
+++ node-v8.17.0/src/node.cc
@@ -206,6 +206,8 @@ uint64_t max_http_header_size = 8 * 1024
// used by C++ modules as well
bool no_deprecation = false;
+bool insecure_http_parser = false;
+
#if HAVE_OPENSSL
// use OpenSSL's cert store instead of bundled certs
bool ssl_openssl_cert_store =
@@ -2865,6 +2867,11 @@ void SetupProcessObject(Environment* env
READONLY_PROPERTY(process, "noDeprecation", True(env->isolate()));
}
+ // --insecure-http-parser
+ if (insecure_http_parser) {
+ READONLY_PROPERTY(process, "insecureHttpParser", True(env->isolate()));
+ }
+
// --no-warnings
if (no_process_warnings) {
READONLY_PROPERTY(process, "noProcessWarnings", True(env->isolate()));
@@ -3403,6 +3410,8 @@ static void ParseArgs(int* argc,
force_repl = true;
} else if (strcmp(arg, "--no-deprecation") == 0) {
no_deprecation = true;
+ } else if (strcmp(arg, "--insecure-http-parser") == 0) {
+ insecure_http_parser = true;
} else if (strcmp(arg, "--napi-modules") == 0) {
// no-op
} else if (strcmp(arg, "--no-warnings") == 0) {
Index: node-v8.17.0/src/node_http_parser.cc
===================================================================
--- node-v8.17.0.orig/src/node_http_parser.cc
+++ node-v8.17.0/src/node_http_parser.cc
@@ -161,12 +161,12 @@ struct StringPtr {
class Parser : public AsyncWrap {
public:
- Parser(Environment* env, Local<Object> wrap, enum http_parser_type type)
+ Parser(Environment* env, Local<Object> wrap, enum http_parser_type type, bool lenient)
: AsyncWrap(env, wrap, AsyncWrap::PROVIDER_HTTPPARSER),
current_buffer_len_(0),
current_buffer_data_(nullptr) {
Wrap(object(), this);
- Init(type);
+ Init(type, lenient);
}
@@ -383,7 +383,7 @@ class Parser : public AsyncWrap {
http_parser_type type =
static_cast<http_parser_type>(args[0]->Int32Value());
CHECK(type == HTTP_REQUEST || type == HTTP_RESPONSE);
- new Parser(env, args.This(), type);
+ new Parser(env, args.This(), type, args[1]->IsTrue());
}
@@ -476,6 +476,7 @@ class Parser : public AsyncWrap {
static void Reinitialize(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
+ bool lenient = args[2]->IsTrue();
CHECK(args[0]->IsInt32());
CHECK(args[1]->IsBoolean());
@@ -494,7 +495,7 @@ class Parser : public AsyncWrap {
if (isReused) {
parser->AsyncReset();
}
- parser->Init(type);
+ parser->Init(type, lenient);
}
@@ -738,8 +739,9 @@ class Parser : public AsyncWrap {
}
- void Init(enum http_parser_type type) {
+ void Init(enum http_parser_type type, bool lenient) {
http_parser_init(&parser_, type);
+ parser_.lenient_http_headers = lenient;
url_.Reset();
status_message_.Reset();
num_fields_ = 0;
Index: node-v8.17.0/src/node.h
===================================================================
--- node-v8.17.0.orig/src/node.h
+++ node-v8.17.0/src/node.h
@@ -197,6 +197,7 @@ typedef intptr_t ssize_t;
namespace node {
NODE_EXTERN extern bool no_deprecation;
+NODE_EXTERN extern bool insecure_http_parser;
#if HAVE_OPENSSL
NODE_EXTERN extern bool ssl_openssl_cert_store;
# if NODE_FIPS_MODE