summaryrefslogtreecommitdiff
path: root/src/node_http_parser.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/node_http_parser.cc')
-rw-r--r--src/node_http_parser.cc229
1 files changed, 183 insertions, 46 deletions
diff --git a/src/node_http_parser.cc b/src/node_http_parser.cc
index b82710480d..482c7263ca 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 <stdlib.h> // free()
#include <string.h> // 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<Object> wrap, enum http_parser_type type)
+ Parser(Environment* env, Local<Object> 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<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK(args[0]->IsInt32());
- http_parser_type type =
- static_cast<http_parser_type>(args[0].As<Int32>()->Value());
+ parser_type_t type =
+ static_cast<parser_type_t>(args[0].As<Int32>()->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<Value>& 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<Value> e = Exception::Error(env->parse_error_string());
- Local<Object> obj = e.As<Object>();
- 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<http_parser_type>(args[0].As<Int32>()->Value());
+ parser_type_t type =
+ static_cast<parser_type_t>(args[0].As<Int32>()->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<Value>());
- Local<Integer> nparsed_obj = Integer::New(env()->isolate(), nparsed);
+ Local<Integer> 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<Value> e = Exception::Error(env()->parse_error_string());
Local<Object> 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<Array> 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<Object> 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 <typename Parser, Parser> struct Proxy;
template <typename Parser, typename ...Args, int (Parser::*Member)(Args...)>
struct Proxy<int (Parser::*)(Args...), Member> {
- 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>(args)...);
+ int rv = (parser->*Member)(std::forward<Args>(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<Call, &Parser::on_message_begin>::Raw,
Proxy<DataCall, &Parser::on_url>::Raw,
Proxy<DataCall, &Parser::on_status>::Raw,
@@ -734,8 +866,13 @@ const struct http_parser_settings Parser::settings = {
Proxy<Call, &Parser::on_headers_complete>::Raw,
Proxy<DataCall, &Parser::on_body>::Raw,
Proxy<Call, &Parser::on_message_complete>::Raw,
- nullptr, // on_chunk_header
- nullptr // on_chunk_complete
+#ifdef NODE_EXPERIMENTAL_HTTP
+ Proxy<Call, &Parser::on_chunk_header>::Raw,
+ Proxy<Call, &Parser::on_chunk_complete>::Raw,
+#else /* !NODE_EXPERIMENTAL_HTTP */
+ nullptr,
+ nullptr,
+#endif /* NODE_EXPERIMENTAL_HTTP */
};