blob: c7a3df8d067af49323306d148bb9a17c0c12211d [file] [log] [blame]
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "node.h"
#include "node_buffer.h"
#include "util.h"
#include "async_wrap-inl.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "stream_base-inl.h"
#include "v8.h"
#include "llhttp.h"
#include <cstdlib> // free()
#include <cstring> // strdup(), strchr()
// This is a binding to llhttp (https://github.com/nodejs/llhttp)
// 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().
// The parser then issues callbacks with slices of the data
// parser.onMessageBegin
// parser.onPath
// parser.onBody
// ...
// No copying is performed when slicing the buffer, only small reference
// allocations.
namespace node {
namespace { // NOLINT(build/namespaces)
using v8::Array;
using v8::Boolean;
using v8::Context;
using v8::EscapableHandleScope;
using v8::Exception;
using v8::Function;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::HandleScope;
using v8::Int32;
using v8::Integer;
using v8::Local;
using v8::MaybeLocal;
using v8::Number;
using v8::Object;
using v8::String;
using v8::Uint32;
using v8::Undefined;
using v8::Value;
const uint32_t kOnHeaders = 0;
const uint32_t kOnHeadersComplete = 1;
const uint32_t kOnBody = 2;
const uint32_t kOnMessageComplete = 3;
const uint32_t kOnExecute = 4;
const uint32_t kOnTimeout = 5;
// Any more fields than this will be flushed into JS
const size_t kMaxHeaderFieldsCount = 32;
inline bool IsOWS(char c) {
return c == ' ' || c == '\t';
}
class BindingData : public BaseObject {
public:
BindingData(Environment* env, Local<Object> obj)
: BaseObject(env, obj) {}
static constexpr FastStringKey binding_data_name { "http_parser" };
std::vector<char> parser_buffer;
bool parser_buffer_in_use = false;
void MemoryInfo(MemoryTracker* tracker) const override {
tracker->TrackField("parser_buffer", parser_buffer);
}
SET_SELF_SIZE(BindingData)
SET_MEMORY_INFO_NAME(BindingData)
};
// TODO(addaleax): Remove once we're on C++17.
constexpr FastStringKey BindingData::binding_data_name;
// helper class for the Parser
struct StringPtr {
StringPtr() {
on_heap_ = false;
Reset();
}
~StringPtr() {
Reset();
}
// If str_ does not point to a heap string yet, this function makes it do
// so. This is called at the end of each http_parser_execute() so as not
// to leak references. See issue #2438 and test-http-parser-bad-ref.js.
void Save() {
if (!on_heap_ && size_ > 0) {
char* s = new char[size_];
memcpy(s, str_, size_);
str_ = s;
on_heap_ = true;
}
}
void Reset() {
if (on_heap_) {
delete[] str_;
on_heap_ = false;
}
str_ = nullptr;
size_ = 0;
}
void Update(const char* str, size_t size) {
if (str_ == nullptr) {
str_ = str;
} else if (on_heap_ || str_ + size_ != str) {
// Non-consecutive input, make a copy on the heap.
// TODO(bnoordhuis) Use slab allocation, O(n) allocs is bad.
char* s = new char[size_ + size];
memcpy(s, str_, size_);
memcpy(s + size_, str, size);
if (on_heap_)
delete[] str_;
else
on_heap_ = true;
str_ = s;
}
size_ += size;
}
Local<String> ToString(Environment* env) const {
if (size_ != 0)
return OneByteString(env->isolate(), str_, size_);
else
return String::Empty(env->isolate());
}
// Strip trailing OWS (SPC or HTAB) from string.
Local<String> ToTrimmedString(Environment* env) {
while (size_ > 0 && IsOWS(str_[size_ - 1])) {
size_--;
}
return ToString(env);
}
const char* str_;
bool on_heap_;
size_t size_;
};
class Parser : public AsyncWrap, public StreamListener {
public:
Parser(BindingData* binding_data, Local<Object> wrap)
: AsyncWrap(binding_data->env(), wrap),
current_buffer_len_(0),
current_buffer_data_(nullptr),
binding_data_(binding_data) {
}
void MemoryInfo(MemoryTracker* tracker) const override {
tracker->TrackField("current_buffer", current_buffer_);
}
SET_MEMORY_INFO_NAME(Parser)
SET_SELF_SIZE(Parser)
int on_message_begin() {
num_fields_ = num_values_ = 0;
url_.Reset();
status_message_.Reset();
header_parsing_start_time_ = uv_hrtime();
return 0;
}
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_++;
if (num_fields_ == kMaxHeaderFieldsCount) {
// ran out of space - flush to javascript land
Flush();
num_fields_ = 1;
num_values_ = 0;
}
fields_[num_fields_ - 1].Reset();
}
CHECK_LT(num_fields_, kMaxHeaderFieldsCount);
CHECK_EQ(num_fields_, num_values_ + 1);
fields_[num_fields_ - 1].Update(at, length);
return 0;
}
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_++;
values_[num_values_ - 1].Reset();
}
CHECK_LT(num_values_, arraysize(values_));
CHECK_EQ(num_values_, num_fields_);
values_[num_values_ - 1].Update(at, length);
return 0;
}
int on_headers_complete() {
header_nread_ = 0;
// 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.
enum on_headers_complete_arg_index {
A_VERSION_MAJOR = 0,
A_VERSION_MINOR,
A_HEADERS,
A_METHOD,
A_URL,
A_STATUS_CODE,
A_STATUS_MESSAGE,
A_UPGRADE,
A_SHOULD_KEEP_ALIVE,
A_MAX
};
Local<Value> argv[A_MAX];
Local<Object> obj = object();
Local<Value> cb = obj->Get(env()->context(),
kOnHeadersComplete).ToLocalChecked();
if (!cb->IsFunction())
return 0;
Local<Value> undefined = Undefined(env()->isolate());
for (size_t i = 0; i < arraysize(argv); i++)
argv[i] = undefined;
if (have_flushed_) {
// Slow case, flush remaining headers.
Flush();
} else {
// Fast case, pass headers and URL to JS land.
argv[A_HEADERS] = CreateHeaders();
if (parser_.type == HTTP_REQUEST)
argv[A_URL] = url_.ToString(env());
}
num_fields_ = 0;
num_values_ = 0;
// METHOD
if (parser_.type == HTTP_REQUEST) {
argv[A_METHOD] =
Uint32::NewFromUnsigned(env()->isolate(), parser_.method);
}
// STATUS
if (parser_.type == HTTP_RESPONSE) {
argv[A_STATUS_CODE] =
Integer::New(env()->isolate(), parser_.status_code);
argv[A_STATUS_MESSAGE] = status_message_.ToString(env());
}
// VERSION
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;
should_keep_alive = llhttp_should_keep_alive(&parser_);
argv[A_SHOULD_KEEP_ALIVE] =
Boolean::New(env()->isolate(), should_keep_alive);
argv[A_UPGRADE] = Boolean::New(env()->isolate(), parser_.upgrade);
MaybeLocal<Value> head_response;
{
InternalCallbackScope callback_scope(
this, InternalCallbackScope::kSkipTaskQueues);
head_response = cb.As<Function>()->Call(
env()->context(), object(), arraysize(argv), argv);
if (head_response.IsEmpty()) callback_scope.MarkAsFailed();
}
int64_t val;
if (head_response.IsEmpty() || !head_response.ToLocalChecked()
->IntegerValue(env()->context())
.To(&val)) {
got_exception_ = true;
return -1;
}
return val;
}
int on_body(const char* at, size_t length) {
EscapableHandleScope scope(env()->isolate());
Local<Object> obj = object();
Local<Value> cb = obj->Get(env()->context(), kOnBody).ToLocalChecked();
if (!cb->IsFunction())
return 0;
// We came from consumed stream
if (current_buffer_.IsEmpty()) {
// Make sure Buffer will be in parent HandleScope
current_buffer_ = scope.Escape(Buffer::Copy(
env()->isolate(),
current_buffer_data_,
current_buffer_len_).ToLocalChecked());
}
Local<Value> argv[3] = {
current_buffer_,
Integer::NewFromUnsigned(env()->isolate(), at - current_buffer_data_),
Integer::NewFromUnsigned(env()->isolate(), length)
};
MaybeLocal<Value> r = MakeCallback(cb.As<Function>(),
arraysize(argv),
argv);
if (r.IsEmpty()) {
got_exception_ = true;
llhttp_set_error_reason(&parser_, "HPE_JS_EXCEPTION:JS Exception");
return HPE_USER;
}
return 0;
}
int on_message_complete() {
HandleScope scope(env()->isolate());
if (num_fields_)
Flush(); // Flush trailing HTTP headers.
Local<Object> obj = object();
Local<Value> cb = obj->Get(env()->context(),
kOnMessageComplete).ToLocalChecked();
if (!cb->IsFunction())
return 0;
MaybeLocal<Value> r;
{
InternalCallbackScope callback_scope(
this, InternalCallbackScope::kSkipTaskQueues);
r = cb.As<Function>()->Call(env()->context(), object(), 0, nullptr);
if (r.IsEmpty()) callback_scope.MarkAsFailed();
}
if (r.IsEmpty()) {
got_exception_ = true;
return -1;
}
return 0;
}
// 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;
}
static void New(const FunctionCallbackInfo<Value>& args) {
BindingData* binding_data = Environment::GetBindingData<BindingData>(args);
new Parser(binding_data, args.This());
}
static void Close(const FunctionCallbackInfo<Value>& args) {
Parser* parser;
ASSIGN_OR_RETURN_UNWRAP(&parser, args.Holder());
delete parser;
}
static void Free(const FunctionCallbackInfo<Value>& args) {
Parser* parser;
ASSIGN_OR_RETURN_UNWRAP(&parser, args.Holder());
// Since the Parser destructor isn't going to run the destroy() callbacks
// it needs to be triggered manually.
parser->EmitTraceEventDestroy();
parser->EmitDestroy();
}
void Save() {
url_.Save();
status_message_.Save();
for (size_t i = 0; i < num_fields_; i++) {
fields_[i].Save();
}
for (size_t i = 0; i < num_values_; i++) {
values_[i].Save();
}
}
// var bytesParsed = parser->execute(buffer);
static void Execute(const FunctionCallbackInfo<Value>& args) {
Parser* parser;
ASSIGN_OR_RETURN_UNWRAP(&parser, args.Holder());
CHECK(parser->current_buffer_.IsEmpty());
CHECK_EQ(parser->current_buffer_len_, 0);
CHECK_NULL(parser->current_buffer_data_);
ArrayBufferViewContents<char> buffer(args[0]);
// This is a hack to get the current_buffer to the callbacks with the least
// amount of overhead. Nothing else will run while http_parser_execute()
// runs, therefore this pointer can be set and used for the execution.
parser->current_buffer_ = args[0].As<Object>();
Local<Value> ret = parser->Execute(buffer.data(), buffer.length());
if (!ret.IsEmpty())
args.GetReturnValue().Set(ret);
}
static void Finish(const FunctionCallbackInfo<Value>& args) {
Parser* parser;
ASSIGN_OR_RETURN_UNWRAP(&parser, args.Holder());
CHECK(parser->current_buffer_.IsEmpty());
Local<Value> ret = parser->Execute(nullptr, 0);
if (!ret.IsEmpty())
args.GetReturnValue().Set(ret);
}
static void Initialize(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
bool lenient = args[3]->IsTrue();
uint64_t max_http_header_size = 0;
uint64_t headers_timeout = 0;
CHECK(args[0]->IsInt32());
CHECK(args[1]->IsObject());
if (args.Length() > 2) {
CHECK(args[2]->IsNumber());
max_http_header_size = args[2].As<Number>()->Value();
}
if (max_http_header_size == 0) {
max_http_header_size = env->options()->max_http_header_size;
}
if (args.Length() > 4) {
CHECK(args[4]->IsInt32());
headers_timeout = args[4].As<Number>()->Value();
}
llhttp_type_t type =
static_cast<llhttp_type_t>(args[0].As<Int32>()->Value());
CHECK(type == HTTP_REQUEST || type == HTTP_RESPONSE);
Parser* parser;
ASSIGN_OR_RETURN_UNWRAP(&parser, args.Holder());
// Should always be called from the same context.
CHECK_EQ(env, parser->env());
AsyncWrap::ProviderType provider =
(type == HTTP_REQUEST ?
AsyncWrap::PROVIDER_HTTPINCOMINGMESSAGE
: AsyncWrap::PROVIDER_HTTPCLIENTREQUEST);
parser->set_provider_type(provider);
parser->AsyncReset(args[1].As<Object>());
parser->Init(type, max_http_header_size, lenient, headers_timeout);
}
template <bool should_pause>
static void Pause(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Parser* parser;
ASSIGN_OR_RETURN_UNWRAP(&parser, args.Holder());
// Should always be called from the same context.
CHECK_EQ(env, parser->env());
if (parser->execute_depth_) {
parser->pending_pause_ = should_pause;
return;
}
if (should_pause) {
llhttp_pause(&parser->parser_);
} else {
llhttp_resume(&parser->parser_);
}
}
static void Consume(const FunctionCallbackInfo<Value>& args) {
Parser* parser;
ASSIGN_OR_RETURN_UNWRAP(&parser, args.Holder());
CHECK(args[0]->IsObject());
StreamBase* stream = StreamBase::FromObject(args[0].As<Object>());
CHECK_NOT_NULL(stream);
stream->PushStreamListener(parser);
}
static void Unconsume(const FunctionCallbackInfo<Value>& args) {
Parser* parser;
ASSIGN_OR_RETURN_UNWRAP(&parser, args.Holder());
// Already unconsumed
if (parser->stream_ == nullptr)
return;
parser->stream_->RemoveStreamListener(parser);
}
static void GetCurrentBuffer(const FunctionCallbackInfo<Value>& args) {
Parser* parser;
ASSIGN_OR_RETURN_UNWRAP(&parser, args.Holder());
Local<Object> ret = Buffer::Copy(
parser->env(),
parser->current_buffer_data_,
parser->current_buffer_len_).ToLocalChecked();
args.GetReturnValue().Set(ret);
}
protected:
static const size_t kAllocBufferSize = 64 * 1024;
uv_buf_t OnStreamAlloc(size_t suggested_size) override {
// For most types of streams, OnStreamRead will be immediately after
// OnStreamAlloc, and will consume all data, so using a static buffer for
// reading is more efficient. For other streams, just use Malloc() directly.
if (binding_data_->parser_buffer_in_use)
return uv_buf_init(Malloc(suggested_size), suggested_size);
binding_data_->parser_buffer_in_use = true;
if (binding_data_->parser_buffer.empty())
binding_data_->parser_buffer.resize(kAllocBufferSize);
return uv_buf_init(binding_data_->parser_buffer.data(), kAllocBufferSize);
}
void OnStreamRead(ssize_t nread, const uv_buf_t& buf) override {
HandleScope scope(env()->isolate());
// Once we’re done here, either indicate that the HTTP parser buffer
// is free for re-use, or free() the data if it didn’t come from there
// in the first place.
auto on_scope_leave = OnScopeLeave([&]() {
if (buf.base == binding_data_->parser_buffer.data())
binding_data_->parser_buffer_in_use = false;
else
free(buf.base);
});
if (nread < 0) {
PassReadErrorToPreviousListener(nread);
return;
}
// Ignore, empty reads have special meaning in http parser
if (nread == 0)
return;
current_buffer_.Clear();
Local<Value> ret = Execute(buf.base, nread);
// Exception
if (ret.IsEmpty())
return;
// check header parsing time
if (header_parsing_start_time_ != 0 && headers_timeout_ != 0) {
uint64_t now = uv_hrtime();
uint64_t parsing_time = (now - header_parsing_start_time_) / 1e6;
if (parsing_time > headers_timeout_) {
Local<Value> cb =
object()->Get(env()->context(), kOnTimeout).ToLocalChecked();
if (!cb->IsFunction())
return;
MakeCallback(cb.As<Function>(), 0, nullptr);
return;
}
}
Local<Value> cb =
object()->Get(env()->context(), kOnExecute).ToLocalChecked();
if (!cb->IsFunction())
return;
// Hooks for GetCurrentBuffer
current_buffer_len_ = nread;
current_buffer_data_ = buf.base;
MakeCallback(cb.As<Function>(), 1, &ret);
current_buffer_len_ = 0;
current_buffer_data_ = nullptr;
}
Local<Value> Execute(const char* data, size_t len) {
EscapableHandleScope scope(env()->isolate());
current_buffer_len_ = len;
current_buffer_data_ = data;
got_exception_ = false;
llhttp_errno_t err;
// 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_);
}
// Unassign the 'buffer_' variable
current_buffer_.Clear();
current_buffer_len_ = 0;
current_buffer_data_ = nullptr;
// If there was an exception in one of the callbacks
if (got_exception_)
return scope.Escape(Local<Value>());
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 && err != HPE_OK) {
Local<Value> e = Exception::Error(env()->parse_error_string());
Local<Object> obj = e->ToObject(env()->isolate()->GetCurrentContext())
.ToLocalChecked();
obj->Set(env()->context(),
env()->bytes_parsed_string(),
nread_obj).Check();
const char* errno_reason = llhttp_get_error_reason(&parser_);
Local<String> code;
Local<String> reason;
if (err == HPE_USER) {
const char* colon = strchr(errno_reason, ':');
CHECK_NOT_NULL(colon);
code = OneByteString(env()->isolate(), errno_reason,
colon - errno_reason);
reason = OneByteString(env()->isolate(), colon + 1);
} else {
code = OneByteString(env()->isolate(), llhttp_errno_name(err));
reason = OneByteString(env()->isolate(), errno_reason);
}
obj->Set(env()->context(), env()->code_string(), code).Check();
obj->Set(env()->context(), env()->reason_string(), reason).Check();
return scope.Escape(e);
}
// No return value is needed for `Finish()`
if (data == nullptr) {
return scope.Escape(Local<Value>());
}
return scope.Escape(nread_obj);
}
Local<Array> CreateHeaders() {
// There could be extra entries but the max size should be fixed
Local<Value> headers_v[kMaxHeaderFieldsCount * 2];
for (size_t i = 0; i < num_values_; ++i) {
headers_v[i * 2] = fields_[i].ToString(env());
headers_v[i * 2 + 1] = values_[i].ToTrimmedString(env());
}
return Array::New(env()->isolate(), headers_v, num_values_ * 2);
}
// spill headers and request path to JS land
void Flush() {
HandleScope scope(env()->isolate());
Local<Object> obj = object();
Local<Value> cb = obj->Get(env()->context(), kOnHeaders).ToLocalChecked();
if (!cb->IsFunction())
return;
Local<Value> argv[2] = {
CreateHeaders(),
url_.ToString(env())
};
MaybeLocal<Value> r = MakeCallback(cb.As<Function>(),
arraysize(argv),
argv);
if (r.IsEmpty())
got_exception_ = true;
url_.Reset();
have_flushed_ = true;
}
void Init(llhttp_type_t type, uint64_t max_http_header_size,
bool lenient, uint64_t headers_timeout) {
llhttp_init(&parser_, type, &settings);
llhttp_set_lenient(&parser_, lenient);
header_nread_ = 0;
url_.Reset();
status_message_.Reset();
num_fields_ = 0;
num_values_ = 0;
have_flushed_ = false;
got_exception_ = false;
max_http_header_size_ = max_http_header_size;
header_parsing_start_time_ = 0;
headers_timeout_ = headers_timeout;
}
int TrackHeader(size_t len) {
header_nread_ += len;
if (header_nread_ >= max_http_header_size_) {
llhttp_set_error_reason(&parser_, "HPE_HEADER_OVERFLOW:Header overflow");
return HPE_USER;
}
return 0;
}
int MaybePause() {
CHECK_NE(execute_depth_, 0);
if (!pending_pause_) {
return 0;
}
pending_pause_ = false;
llhttp_set_error_reason(&parser_, "Paused in callback");
return HPE_PAUSED;
}
llhttp_t parser_;
StringPtr fields_[kMaxHeaderFieldsCount]; // header fields
StringPtr values_[kMaxHeaderFieldsCount]; // header values
StringPtr url_;
StringPtr status_message_;
size_t num_fields_;
size_t num_values_;
bool have_flushed_;
bool got_exception_;
Local<Object> current_buffer_;
size_t current_buffer_len_;
const char* current_buffer_data_;
unsigned int execute_depth_ = 0;
bool pending_pause_ = false;
uint64_t header_nread_ = 0;
uint64_t max_http_header_size_;
uint64_t headers_timeout_;
uint64_t header_parsing_start_time_ = 0;
BaseObjectPtr<BindingData> binding_data_;
// 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(llhttp_t* p, Args ... args) {
Parser* parser = ContainerOf(&Parser::parser_, p);
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 llhttp_settings_t settings;
};
const llhttp_settings_t Parser::settings = {
Proxy<Call, &Parser::on_message_begin>::Raw,
Proxy<DataCall, &Parser::on_url>::Raw,
Proxy<DataCall, &Parser::on_status>::Raw,
Proxy<DataCall, &Parser::on_header_field>::Raw,
Proxy<DataCall, &Parser::on_header_value>::Raw,
Proxy<Call, &Parser::on_headers_complete>::Raw,
Proxy<DataCall, &Parser::on_body>::Raw,
Proxy<Call, &Parser::on_message_complete>::Raw,
Proxy<Call, &Parser::on_chunk_header>::Raw,
Proxy<Call, &Parser::on_chunk_complete>::Raw,
};
void InitializeHttpParser(Local<Object> target,
Local<Value> unused,
Local<Context> context,
void* priv) {
Environment* env = Environment::GetCurrent(context);
BindingData* const binding_data =
env->AddBindingData<BindingData>(context, target);
if (binding_data == nullptr) return;
Local<FunctionTemplate> t = env->NewFunctionTemplate(Parser::New);
t->InstanceTemplate()->SetInternalFieldCount(Parser::kInternalFieldCount);
t->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "HTTPParser"));
t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "REQUEST"),
Integer::New(env->isolate(), HTTP_REQUEST));
t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "RESPONSE"),
Integer::New(env->isolate(), HTTP_RESPONSE));
t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kOnHeaders"),
Integer::NewFromUnsigned(env->isolate(), kOnHeaders));
t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kOnHeadersComplete"),
Integer::NewFromUnsigned(env->isolate(), kOnHeadersComplete));
t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kOnBody"),
Integer::NewFromUnsigned(env->isolate(), kOnBody));
t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kOnMessageComplete"),
Integer::NewFromUnsigned(env->isolate(), kOnMessageComplete));
t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kOnExecute"),
Integer::NewFromUnsigned(env->isolate(), kOnExecute));
t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kOnTimeout"),
Integer::NewFromUnsigned(env->isolate(), kOnTimeout));
Local<Array> methods = Array::New(env->isolate());
#define V(num, name, string) \
methods->Set(env->context(), \
num, FIXED_ONE_BYTE_STRING(env->isolate(), #string)).Check();
HTTP_METHOD_MAP(V)
#undef V
target->Set(env->context(),
FIXED_ONE_BYTE_STRING(env->isolate(), "methods"),
methods).Check();
t->Inherit(AsyncWrap::GetConstructorTemplate(env));
env->SetProtoMethod(t, "close", Parser::Close);
env->SetProtoMethod(t, "free", Parser::Free);
env->SetProtoMethod(t, "execute", Parser::Execute);
env->SetProtoMethod(t, "finish", Parser::Finish);
env->SetProtoMethod(t, "initialize", Parser::Initialize);
env->SetProtoMethod(t, "pause", Parser::Pause<true>);
env->SetProtoMethod(t, "resume", Parser::Pause<false>);
env->SetProtoMethod(t, "consume", Parser::Consume);
env->SetProtoMethod(t, "unconsume", Parser::Unconsume);
env->SetProtoMethod(t, "getCurrentBuffer", Parser::GetCurrentBuffer);
target->Set(env->context(),
FIXED_ONE_BYTE_STRING(env->isolate(), "HTTPParser"),
t->GetFunction(env->context()).ToLocalChecked()).Check();
}
} // anonymous namespace
} // namespace node
NODE_MODULE_CONTEXT_AWARE_INTERNAL(http_parser, node::InitializeHttpParser)