From d4654d89be0c20f8ca1e153d074a236348618b00 Mon Sep 17 00:00:00 2001 From: Fedor Indutny Date: Mon, 29 Oct 2018 22:06:09 -0400 Subject: deps: introduce `llhttp` llhttp is modern, written in human-readable TypeScript, verifiable, and is very easy to maintain. See: https://github.com/indutny/llhttp PR-URL: https://github.com/nodejs/node/pull/24059 Reviewed-By: Matteo Collina Reviewed-By: Refael Ackermann Reviewed-By: James M Snell Reviewed-By: Rod Vagg Reviewed-By: Colin Ihrig Reviewed-By: Gus Caplan Reviewed-By: Ujjwal Sharma Reviewed-By: Ben Noordhuis --- src/node_http_parser.cc | 229 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 183 insertions(+), 46 deletions(-) (limited to 'src/node_http_parser.cc') diff --git a/src/node_http_parser.cc b/src/node_http_parser.cc index b82710480de..482c7263ca7 100644 --- a/src/node_http_parser.cc +++ b/src/node_http_parser.cc @@ -25,7 +25,6 @@ #include "async_wrap-inl.h" #include "env-inl.h" -#include "http_parser.h" #include "stream_base-inl.h" #include "util-inl.h" #include "v8.h" @@ -33,6 +32,8 @@ #include // free() #include // strdup() +#include "http_parser_adaptor.h" + // This is a binding to http_parser (https://github.com/nodejs/http-parser) // The goal is to decouple sockets from parsing for more javascript-level // agility. A Buffer is read from a socket and passed to parser.execute(). @@ -148,7 +149,7 @@ struct StringPtr { class Parser : public AsyncWrap, public StreamListener { public: - Parser(Environment* env, Local wrap, enum http_parser_type type) + Parser(Environment* env, Local wrap, parser_type_t type) : AsyncWrap(env, wrap, AsyncWrap::PROVIDER_HTTPPARSER), current_buffer_len_(0), current_buffer_data_(nullptr) { @@ -172,18 +173,33 @@ class Parser : public AsyncWrap, public StreamListener { int on_url(const char* at, size_t length) { + int rv = TrackHeader(length); + if (rv != 0) { + return rv; + } + url_.Update(at, length); return 0; } int on_status(const char* at, size_t length) { + int rv = TrackHeader(length); + if (rv != 0) { + return rv; + } + status_message_.Update(at, length); return 0; } int on_header_field(const char* at, size_t length) { + int rv = TrackHeader(length); + if (rv != 0) { + return rv; + } + if (num_fields_ == num_values_) { // start of new field name num_fields_++; @@ -206,6 +222,11 @@ class Parser : public AsyncWrap, public StreamListener { int on_header_value(const char* at, size_t length) { + int rv = TrackHeader(length); + if (rv != 0) { + return rv; + } + if (num_values_ != num_fields_) { // start of new header value num_values_++; @@ -222,6 +243,10 @@ class Parser : public AsyncWrap, public StreamListener { int on_headers_complete() { +#ifdef NODE_EXPERIMENTAL_HTTP + header_nread_ = 0; +#endif /* NODE_EXPERIMENTAL_HTTP */ + // Arguments for the on-headers-complete javascript callback. This // list needs to be kept in sync with the actual argument list for // `parserOnHeadersComplete` in lib/_http_common.js. @@ -279,8 +304,15 @@ class Parser : public AsyncWrap, public StreamListener { argv[A_VERSION_MAJOR] = Integer::New(env()->isolate(), parser_.http_major); argv[A_VERSION_MINOR] = Integer::New(env()->isolate(), parser_.http_minor); + bool should_keep_alive; +#ifdef NODE_EXPERIMENTAL_HTTP + should_keep_alive = llhttp_should_keep_alive(&parser_); +#else /* !NODE_EXPERIMENTAL_HTTP */ + should_keep_alive = http_should_keep_alive(&parser_); +#endif /* NODE_EXPERIMENTAL_HTTP */ + argv[A_SHOULD_KEEP_ALIVE] = - Boolean::New(env()->isolate(), http_should_keep_alive(&parser_)); + Boolean::New(env()->isolate(), should_keep_alive); argv[A_UPGRADE] = Boolean::New(env()->isolate(), parser_.upgrade); @@ -332,7 +364,10 @@ class Parser : public AsyncWrap, public StreamListener { if (r.IsEmpty()) { got_exception_ = true; - return -1; +#ifdef NODE_EXPERIMENTAL_HTTP + llhttp_set_error_reason(&parser_, "JS Exception"); +#endif /* NODE_EXPERIMENTAL_HTTP */ + return HPE_USER; } return 0; @@ -357,18 +392,33 @@ class Parser : public AsyncWrap, public StreamListener { if (r.IsEmpty()) { got_exception_ = true; - return -1; + return HPE_USER; } return 0; } +#ifdef NODE_EXPERIMENTAL_HTTP + // Reset nread for the next chunk + int on_chunk_header() { + header_nread_ = 0; + return 0; + } + + + // Reset nread for the next chunk + int on_chunk_complete() { + header_nread_ = 0; + return 0; + } +#endif /* NODE_EXPERIMENTAL_HTTP */ + static void New(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); CHECK(args[0]->IsInt32()); - http_parser_type type = - static_cast(args[0].As()->Value()); + parser_type_t type = + static_cast(args[0].As()->Value()); CHECK(type == HTTP_REQUEST || type == HTTP_RESPONSE); new Parser(env, args.This(), type); } @@ -434,30 +484,11 @@ class Parser : public AsyncWrap, public StreamListener { static void Finish(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - Parser* parser; ASSIGN_OR_RETURN_UNWRAP(&parser, args.Holder()); CHECK(parser->current_buffer_.IsEmpty()); - parser->got_exception_ = false; - - int rv = http_parser_execute(&(parser->parser_), &settings, nullptr, 0); - - if (parser->got_exception_) - return; - - if (rv != 0) { - enum http_errno err = HTTP_PARSER_ERRNO(&parser->parser_); - - Local e = Exception::Error(env->parse_error_string()); - Local obj = e.As(); - obj->Set(env->bytes_parsed_string(), Integer::New(env->isolate(), 0)); - obj->Set(env->code_string(), - OneByteString(env->isolate(), http_errno_name(err))); - - args.GetReturnValue().Set(e); - } + parser->Execute(nullptr, 0); } @@ -467,8 +498,8 @@ class Parser : public AsyncWrap, public StreamListener { CHECK(args[0]->IsInt32()); CHECK(args[1]->IsBoolean()); bool isReused = args[1]->IsTrue(); - http_parser_type type = - static_cast(args[0].As()->Value()); + parser_type_t type = + static_cast(args[0].As()->Value()); CHECK(type == HTTP_REQUEST || type == HTTP_RESPONSE); Parser* parser; @@ -492,7 +523,21 @@ class Parser : public AsyncWrap, public StreamListener { ASSIGN_OR_RETURN_UNWRAP(&parser, args.Holder()); // Should always be called from the same context. CHECK_EQ(env, parser->env()); + +#ifdef NODE_EXPERIMENTAL_HTTP + if (parser->execute_depth_) { + parser->pending_pause_ = should_pause; + return; + } + + if (should_pause) { + llhttp_pause(&parser->parser_); + } else { + llhttp_resume(&parser->parser_); + } +#else /* !NODE_EXPERIMENTAL_HTTP */ http_parser_pause(&parser->parser_, should_pause); +#endif /* NODE_EXPERIMENTAL_HTTP */ } @@ -602,10 +647,46 @@ class Parser : public AsyncWrap, public StreamListener { current_buffer_data_ = data; got_exception_ = false; - size_t nparsed = - http_parser_execute(&parser_, &settings, data, len); + parser_errno_t err; + +#ifdef NODE_EXPERIMENTAL_HTTP + // Do not allow re-entering `http_parser_execute()` + CHECK_EQ(execute_depth_, 0); + + execute_depth_++; + if (data == nullptr) { + err = llhttp_finish(&parser_); + } else { + err = llhttp_execute(&parser_, data, len); + Save(); + } + execute_depth_--; + + // Calculate bytes read and resume after Upgrade/CONNECT pause + size_t nread = len; + if (err != HPE_OK) { + nread = llhttp_get_error_pos(&parser_) - data; + + // This isn't a real pause, just a way to stop parsing early. + if (err == HPE_PAUSED_UPGRADE) { + err = HPE_OK; + llhttp_resume_after_upgrade(&parser_); + } + } + + // Apply pending pause + if (pending_pause_) { + pending_pause_ = false; + llhttp_pause(&parser_); + } +#else /* !NODE_EXPERIMENTAL_HTTP */ + size_t nread = http_parser_execute(&parser_, &settings, data, len); + if (data != nullptr) { + Save(); + } - Save(); + err = HTTP_PARSER_ERRNO(&parser_); +#endif /* NODE_EXPERIMENTAL_HTTP */ // Unassign the 'buffer_' variable current_buffer_.Clear(); @@ -616,22 +697,29 @@ class Parser : public AsyncWrap, public StreamListener { if (got_exception_) return scope.Escape(Local()); - Local nparsed_obj = Integer::New(env()->isolate(), nparsed); + Local nread_obj = Integer::New(env()->isolate(), nread); + // If there was a parse error in one of the callbacks // TODO(bnoordhuis) What if there is an error on EOF? - if (!parser_.upgrade && nparsed != len) { - enum http_errno err = HTTP_PARSER_ERRNO(&parser_); - + if (!parser_.upgrade && err != HPE_OK) { Local e = Exception::Error(env()->parse_error_string()); Local obj = e->ToObject(env()->isolate()->GetCurrentContext()) .ToLocalChecked(); - obj->Set(env()->bytes_parsed_string(), nparsed_obj); + obj->Set(env()->bytes_parsed_string(), nread_obj); +#ifdef NODE_EXPERIMENTAL_HTTP + obj->Set(env()->code_string(), + OneByteString(env()->isolate(), llhttp_errno_name(err))); + obj->Set(env()->reason_string(), + OneByteString(env()->isolate(), parser_.reason)); +#else /* !NODE_EXPERIMENTAL_HTTP */ obj->Set(env()->code_string(), OneByteString(env()->isolate(), http_errno_name(err))); +#endif /* NODE_EXPERIMENTAL_HTTP */ return scope.Escape(e); } - return scope.Escape(nparsed_obj); + + return scope.Escape(nread_obj); } Local CreateHeaders() { @@ -684,8 +772,12 @@ class Parser : public AsyncWrap, public StreamListener { } - void Init(enum http_parser_type type) { + void Init(parser_type_t type) { +#ifdef NODE_EXPERIMENTAL_HTTP + llhttp_init(&parser_, type, &settings); +#else /* !NODE_EXPERIMENTAL_HTTP */ http_parser_init(&parser_, type); +#endif /* NODE_EXPERIMENTAL_HTTP */ url_.Reset(); status_message_.Reset(); num_fields_ = 0; @@ -695,7 +787,35 @@ class Parser : public AsyncWrap, public StreamListener { } - http_parser parser_; + int TrackHeader(size_t len) { +#ifdef NODE_EXPERIMENTAL_HTTP + header_nread_ += len; + if (header_nread_ >= kMaxHeaderSize) { + llhttp_set_error_reason(&parser_, "Headers overflow"); + return HPE_USER; + } +#endif /* NODE_EXPERIMENTAL_HTTP */ + return 0; + } + + + int MaybePause() { +#ifdef NODE_EXPERIMENTAL_HTTP + CHECK_NE(execute_depth_, 0); + + if (!pending_pause_) { + return 0; + } + + pending_pause_ = false; + llhttp_set_error_reason(&parser_, "Paused in callback"); + return HPE_PAUSED; +#else /* !NODE_EXPERIMENTAL_HTTP */ + return 0; +#endif /* NODE_EXPERIMENTAL_HTTP */ + } + + parser_t parser_; StringPtr fields_[32]; // header fields StringPtr values_[32]; // header values StringPtr url_; @@ -707,25 +827,37 @@ class Parser : public AsyncWrap, public StreamListener { Local current_buffer_; size_t current_buffer_len_; char* current_buffer_data_; +#ifdef NODE_EXPERIMENTAL_HTTP + unsigned int execute_depth_ = 0; + bool pending_pause_ = false; + uint64_t header_nread_ = 0; +#endif /* NODE_EXPERIMENTAL_HTTP */ // These are helper functions for filling `http_parser_settings`, which turn // a member function of Parser into a C-style HTTP parser callback. template struct Proxy; template struct Proxy { - static int Raw(http_parser* p, Args ... args) { + static int Raw(parser_t* p, Args ... args) { Parser* parser = ContainerOf(&Parser::parser_, p); - return (parser->*Member)(std::forward(args)...); + int rv = (parser->*Member)(std::forward(args)...); + if (rv == 0) { + rv = parser->MaybePause(); + } + return rv; } }; typedef int (Parser::*Call)(); typedef int (Parser::*DataCall)(const char* at, size_t length); - static const struct http_parser_settings settings; + static const parser_settings_t settings; +#ifdef NODE_EXPERIMENTAL_HTTP + static const uint64_t kMaxHeaderSize = 80 * 1024; +#endif /* NODE_EXPERIMENTAL_HTTP */ }; -const struct http_parser_settings Parser::settings = { +const parser_settings_t Parser::settings = { Proxy::Raw, Proxy::Raw, Proxy::Raw, @@ -734,8 +866,13 @@ const struct http_parser_settings Parser::settings = { Proxy::Raw, Proxy::Raw, Proxy::Raw, - nullptr, // on_chunk_header - nullptr // on_chunk_complete +#ifdef NODE_EXPERIMENTAL_HTTP + Proxy::Raw, + Proxy::Raw, +#else /* !NODE_EXPERIMENTAL_HTTP */ + nullptr, + nullptr, +#endif /* NODE_EXPERIMENTAL_HTTP */ }; -- cgit v1.2.3