Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
devel:languages:nodejs
nodejs8
CVE-2019-15605.patch
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File CVE-2019-15605.patch of Package nodejs8
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
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor