src/Easy.cc
/**
* Copyright (c) Jonathan Cardoso Machado. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "Easy.h"
#include "Curl.h"
#include "CurlHttpPost.h"
#include "Share.h"
#include "make_unique.h"
#include <cctype>
#include <iostream>
#include <string>
// 36055 was allocated on Win64
#define MEMORY_PER_HANDLE 30000
#define TIME_IN_THE_FUTURE "30001231 23:59:59"
namespace NodeLibcurl {
class Easy::ToFree {
public:
std::vector<std::vector<char>> str;
std::vector<curl_slist*> slist;
std::vector<std::unique_ptr<CurlHttpPost>> post;
~ToFree() {
for (unsigned int i = 0; i < slist.size(); i++) {
curl_slist_free_all(slist[i]);
}
}
};
Nan::Persistent<v8::FunctionTemplate> Easy::constructor;
uint32_t Easy::counter = 0;
uint32_t Easy::currentOpenedHandles = 0;
Easy::Easy() {
this->ch = curl_easy_init();
assert(this->ch && "Could not initialize libcurl easy handle.");
NODE_LIBCURL_ADJUST_MEM(MEMORY_PER_HANDLE);
this->toFree = std::make_shared<Easy::ToFree>();
this->ResetRequiredHandleOptions();
++Easy::currentOpenedHandles;
}
Easy::Easy(Easy* orig) {
assert(orig);
assert(orig != this); // should not duplicate itself
this->ch = curl_easy_duphandle(orig->ch);
assert(this->ch && "Could not duplicate libcurl easy handle.");
NODE_LIBCURL_ADJUST_MEM(MEMORY_PER_HANDLE);
// copy the orig callbacks and async resources to the current handle
this->callbacks.insert(orig->callbacks.begin(), orig->callbacks.end());
if (orig->cbOnSocketEvent) {
this->cbOnSocketEvent = orig->cbOnSocketEvent;
}
// make sure to reset the *DATA options when duplicating a handle. We are
// setting all of them, even if they are not set.
curl_easy_setopt(this->ch, CURLOPT_CHUNK_DATA, this);
curl_easy_setopt(this->ch, CURLOPT_DEBUGDATA, this);
curl_easy_setopt(this->ch, CURLOPT_FNMATCH_DATA, this);
curl_easy_setopt(this->ch, CURLOPT_PROGRESSDATA, this);
#if NODE_LIBCURL_VER_GE(7, 32, 0)
curl_easy_setopt(this->ch, CURLOPT_XFERINFODATA, this);
#endif
#if NODE_LIBCURL_VER_GE(7, 64, 0)
curl_easy_setopt(this->ch, CURLOPT_TRAILERDATA, this);
#endif
#if NODE_LIBCURL_VER_GE(7, 74, 0)
curl_easy_setopt(this->ch, CURLOPT_HSTSREADDATA, this);
curl_easy_setopt(this->ch, CURLOPT_HSTSWRITEDATA, this);
#endif
// no need to reset the _DATA option for the READ, SEEK and WRITE callbacks,
// since they are reset on ResetRequiredHandleOptions()
this->toFree = orig->toFree;
this->ResetRequiredHandleOptions();
++Easy::currentOpenedHandles;
}
// Create a new Easy instance using an existing curl handle
// This is the only constructor that is not private
// because it's used inside Multi
Easy::Easy(CURL* easy) {
this->ch = easy;
char* origEasyPtr = nullptr;
CURLcode code = curl_easy_getinfo(easy, CURLINFO_PRIVATE, &origEasyPtr);
// This cannot fail
assert(code == CURLE_OK);
NODE_LIBCURL_ADJUST_MEM(MEMORY_PER_HANDLE);
// We are creating a new Easy instance based in a easy curl handle
// that must be being used by another Easy instance.
// This is basically a copy - just like we have above
// If origEasyPtr is still null here, it means this is a new easy curl handle
// and this scenario should currently never happen
assert(origEasyPtr != nullptr && "CURLINFO_PRIVATE returned a nullptr which is invalid");
Easy* orig = reinterpret_cast<Easy*>(origEasyPtr);
// copy the orig callbacks and async resources to the current handle
this->callbacks.insert(orig->callbacks.begin(), orig->callbacks.end());
if (orig->cbOnSocketEvent) {
this->cbOnSocketEvent = orig->cbOnSocketEvent;
}
// make sure to reset the *DATA options when duplicating a handle. We are
// setting all of them, even if they are not set.
curl_easy_setopt(this->ch, CURLOPT_CHUNK_DATA, this);
curl_easy_setopt(this->ch, CURLOPT_DEBUGDATA, this);
curl_easy_setopt(this->ch, CURLOPT_FNMATCH_DATA, this);
curl_easy_setopt(this->ch, CURLOPT_PROGRESSDATA, this);
#if NODE_LIBCURL_VER_GE(7, 32, 0)
curl_easy_setopt(this->ch, CURLOPT_XFERINFODATA, this);
#endif
#if NODE_LIBCURL_VER_GE(7, 64, 0)
curl_easy_setopt(this->ch, CURLOPT_TRAILERDATA, this);
#endif
#if NODE_LIBCURL_VER_GE(7, 74, 0)
curl_easy_setopt(this->ch, CURLOPT_HSTSREADDATA, this);
curl_easy_setopt(this->ch, CURLOPT_HSTSWRITEDATA, this);
#endif
// no need to reset the _DATA option for the READ, SEEK and WRITE callbacks,
// since they are reset on ResetRequiredHandleOptions()
this->toFree = orig->toFree;
this->ResetRequiredHandleOptions();
++Easy::currentOpenedHandles;
}
v8::Local<v8::Object> Easy::FromCURLHandle(CURL* handle) {
Nan::EscapableHandleScope scope;
// create a new js object using this one as the argument for the constructor.
const int argc = 1;
v8::Local<v8::External> curlEasyHandle = Nan::New<v8::External>(reinterpret_cast<void*>(handle));
v8::Local<v8::Value> argv[argc] = {curlEasyHandle};
v8::Local<v8::Function> cons = Nan::GetFunction(Nan::New(Easy::constructor)).ToLocalChecked();
v8::Local<v8::Object> newInstance = Nan::NewInstance(cons, argc, argv).ToLocalChecked();
return scope.Escape(newInstance);
}
// Implementation of equality operator overload.
bool Easy::operator==(const Easy& other) const { return this->ch == other.ch; }
bool Easy::operator!=(const Easy& other) const { return !(*this == other); }
Easy::~Easy(void) {
if (this->isOpen) {
this->Dispose();
}
}
void Easy::ResetRequiredHandleOptions() {
curl_easy_setopt(this->ch, CURLOPT_PRIVATE,
this); // to be used with Multi handle
curl_easy_setopt(this->ch, CURLOPT_HEADERFUNCTION, Easy::HeaderFunction);
curl_easy_setopt(this->ch, CURLOPT_HEADERDATA, this);
curl_easy_setopt(this->ch, CURLOPT_READFUNCTION, Easy::ReadFunction);
curl_easy_setopt(this->ch, CURLOPT_READDATA, this);
curl_easy_setopt(this->ch, CURLOPT_SEEKFUNCTION, Easy::SeekFunction);
curl_easy_setopt(this->ch, CURLOPT_SEEKDATA, this);
curl_easy_setopt(this->ch, CURLOPT_WRITEFUNCTION, Easy::WriteFunction);
curl_easy_setopt(this->ch, CURLOPT_WRITEDATA, this);
}
// Dispose persistent objects and references stored during the life of this obj.
void Easy::Dispose() {
// this call should only be done when the handle is still open
assert(this->isOpen && "This handle was already closed.");
assert(this->ch && "The curl handle ran away.");
curl_easy_cleanup(this->ch);
NODE_LIBCURL_ADJUST_MEM(-MEMORY_PER_HANDLE);
if (this->isMonitoringSockets) {
this->UnmonitorSockets();
}
this->isOpen = false;
this->callbackError.Reset();
--Easy::currentOpenedHandles;
}
void Easy::MonitorSockets() {
int retUv;
CURLcode retCurl;
int events = 0 | UV_READABLE | UV_WRITABLE;
if (this->socketPollHandle) {
Nan::ThrowError("Already monitoring sockets!");
return;
}
#if NODE_LIBCURL_VER_GE(7, 45, 0)
curl_socket_t socket;
retCurl = curl_easy_getinfo(this->ch, CURLINFO_ACTIVESOCKET, &socket);
if (socket == CURL_SOCKET_BAD) {
Nan::ThrowError("Received invalid socket from the current connection!");
return;
}
#else
long socket; // NOLINT(runtime/int)
retCurl = curl_easy_getinfo(this->ch, CURLINFO_LASTSOCKET, &socket);
#endif
if (retCurl != CURLE_OK) {
std::string errorMsg;
errorMsg += std::string("Failed to receive socket. Reason: ") + curl_easy_strerror(retCurl);
Nan::ThrowError(errorMsg.c_str());
return;
}
this->socketPollHandle = new uv_poll_t;
retUv = uv_poll_init_socket(uv_default_loop(), this->socketPollHandle, socket);
if (retUv < 0) {
std::string errorMsg;
errorMsg +=
std::string("Failed to poll on connection socket. Reason:") + UV_ERROR_STRING(retUv);
Nan::ThrowError(errorMsg.c_str());
return;
}
this->socketPollHandle->data = this;
retUv = uv_poll_start(this->socketPollHandle, events, Easy::OnSocket);
this->isMonitoringSockets = true;
}
void Easy::UnmonitorSockets() {
int retUv;
retUv = uv_poll_stop(this->socketPollHandle);
if (retUv < 0) {
std::string errorMsg;
errorMsg += std::string("Failed to stop polling on socket. Reason: ") + UV_ERROR_STRING(retUv);
Nan::ThrowError(errorMsg.c_str());
return;
}
uv_close(reinterpret_cast<uv_handle_t*>(this->socketPollHandle), Easy::OnSocketClose);
this->isMonitoringSockets = false;
}
void Easy::OnSocket(uv_poll_t* handle, int status, int events) {
Easy* obj = static_cast<Easy*>(handle->data);
assert(obj);
obj->CallSocketEvent(status, events);
}
void Easy::OnSocketClose(uv_handle_t* handle) { delete handle; }
void Easy::CallSocketEvent(int status, int events) {
if (this->cbOnSocketEvent == nullptr) {
return;
}
Nan::HandleScope scope;
v8::Local<v8::Value> err = Nan::Null();
if (status < 0) {
err = Nan::Error(UV_ERROR_STRING(status));
}
const int argc = 2;
v8::Local<v8::Value> argv[argc] = {err, Nan::New<v8::Integer>(events)};
// **(this->cbOnSocketEvent.get()) is the same than this->cbOnSocketEvent->GetFunction()
Nan::AsyncResource asyncResource("Easy::CallSocketEvent");
asyncResource.runInAsyncScope(this->handle(), this->cbOnSocketEvent->GetFunction(), argc, argv);
}
// Called by libcurl when some chunk of data (from body) is available
size_t Easy::WriteFunction(char* ptr, size_t size, size_t nmemb, void* userdata) {
Easy* obj = static_cast<Easy*>(userdata);
return obj->OnData(ptr, size, nmemb);
}
// Called by libcurl when some chunk of data (from headers) is available
size_t Easy::HeaderFunction(char* ptr, size_t size, size_t nmemb, void* userdata) {
Easy* obj = static_cast<Easy*>(userdata);
return obj->OnHeader(ptr, size, nmemb);
}
// Called by libcurl as soon as it needs to read data in order to send it to the
// peer
size_t Easy::ReadFunction(char* ptr, size_t size, size_t nmemb, void* userdata) {
uv_fs_t readReq;
int32_t returnValue = CURL_READFUNC_ABORT;
Easy* obj = static_cast<Easy*>(userdata);
int32_t fd = obj->readDataFileDescriptor;
size_t n = size * nmemb;
CallbacksMap::iterator it = obj->callbacks.find(CURLOPT_READFUNCTION);
// Read callback was set, use it instead
if (it != obj->callbacks.end()) {
Nan::HandleScope scope;
v8::Local<v8::Object> buf = Nan::NewBuffer(static_cast<uint32_t>(n)).ToLocalChecked();
v8::Local<v8::Uint32> sizeArg = Nan::New<v8::Uint32>(static_cast<uint32_t>(size));
v8::Local<v8::Uint32> nmembArg = Nan::New<v8::Uint32>(static_cast<uint32_t>(nmemb));
const int argc = 3;
v8::Local<v8::Value> argv[argc] = {
buf,
sizeArg,
nmembArg,
};
Nan::TryCatch tryCatch;
Nan::AsyncResource asyncResource("Easy::ReadFunction");
Nan::MaybeLocal<v8::Value> returnValueCallback =
asyncResource.runInAsyncScope(obj->handle(), it->second->GetFunction(), argc, argv);
if (tryCatch.HasCaught()) {
if (obj->isInsideMultiHandle) {
obj->callbackError.Reset(tryCatch.Exception());
} else {
tryCatch.ReThrow();
}
return returnValue;
}
if (returnValueCallback.IsEmpty() || !returnValueCallback.ToLocalChecked()->IsInt32()) {
v8::Local<v8::Value> typeError =
Nan::TypeError("Return value from the READ callback must be an integer.");
if (obj->isInsideMultiHandle) {
obj->callbackError.Reset(typeError);
} else {
Nan::ThrowError(typeError);
tryCatch.ReThrow();
}
return returnValue;
} else {
returnValue = Nan::To<int32_t>(returnValueCallback.ToLocalChecked()).FromJust();
}
char* data = node::Buffer::Data(buf);
bool hasData = !!data && returnValue > 0 && returnValue < CURL_READFUNC_ABORT;
if (hasData) {
std::memcpy(ptr, data, returnValue);
}
// otherwise use the default read callback
} else {
// abort early if we don't have a file descriptor
if (fd == -1) {
return CURL_READFUNC_ABORT;
}
// get the offset
curl_off_t offset = obj->readDataOffset;
if (offset >= 0) {
// increment it for the next read
obj->readDataOffset += n;
}
#if UV_VERSION_MAJOR < 1
returnValue = uv_fs_read(uv_default_loop(), &readReq, fd, ptr, n, offset, NULL);
#else
uv_buf_t uvbuf = uv_buf_init(ptr, (unsigned int)(n));
returnValue = uv_fs_read(uv_default_loop(), &readReq, fd, &uvbuf, 1, offset, NULL);
#endif
}
if (returnValue < 0) {
return CURL_READFUNC_ABORT;
}
return static_cast<size_t>(returnValue);
}
size_t Easy::SeekFunction(void* userdata, curl_off_t offset, int origin) {
Easy* obj = static_cast<Easy*>(userdata);
int32_t returnValue = CURL_SEEKFUNC_FAIL;
CallbacksMap::iterator it = obj->callbacks.find(CURLOPT_READFUNCTION);
// Read callback was set, look for a seek callback
if (it != obj->callbacks.end()) {
it = obj->callbacks.find(CURLOPT_SEEKFUNCTION);
// Seek callback was set, use it instead
if (it != obj->callbacks.end()) {
Nan::HandleScope scope;
v8::Local<v8::Uint32> offsetArg = Nan::New<v8::Uint32>(static_cast<uint32_t>(offset));
v8::Local<v8::Uint32> originArg = Nan::New<v8::Uint32>(static_cast<uint32_t>(origin));
const int argc = 2;
v8::Local<v8::Value> argv[argc] = {
offsetArg,
originArg,
};
Nan::TryCatch tryCatch;
Nan::AsyncResource asyncResource("Easy::SeekFunction");
Nan::MaybeLocal<v8::Value> returnValueCallback =
asyncResource.runInAsyncScope(obj->handle(), it->second->GetFunction(), argc, argv);
if (tryCatch.HasCaught()) {
if (obj->isInsideMultiHandle) {
obj->callbackError.Reset(tryCatch.Exception());
} else {
tryCatch.ReThrow();
}
return returnValue;
}
if (returnValueCallback.IsEmpty() || !returnValueCallback.ToLocalChecked()->IsInt32()) {
v8::Local<v8::Value> typeError =
Nan::TypeError("Return value from the SEEK callback must be an integer.");
if (obj->isInsideMultiHandle) {
obj->callbackError.Reset(typeError);
} else {
Nan::ThrowError(typeError);
tryCatch.ReThrow();
}
} else {
returnValue = Nan::To<int32_t>(returnValueCallback.ToLocalChecked()).FromJust();
}
// otherwise we can't seek directly
} else {
returnValue = CURL_SEEKFUNC_CANTSEEK;
}
// otherwise use the default seek callback
} else {
obj->readDataOffset = offset;
returnValue = CURL_SEEKFUNC_OK;
}
return returnValue;
}
size_t Easy::OnData(char* data, size_t size, size_t nmemb) {
Nan::HandleScope scope;
size_t dataLength = size * nmemb;
CallbacksMap::iterator it = this->callbacks.find(CURLOPT_WRITEFUNCTION);
bool hasWriteCallback = (it != this->callbacks.end());
// No callback is set
if (!hasWriteCallback) {
return dataLength;
}
// if this gets returned it will cause a CURLE_WRITE_ERROR
int32_t returnValue = -1;
const int argc = 3;
v8::Local<v8::Object> buf =
Nan::CopyBuffer(data, static_cast<uint32_t>(dataLength)).ToLocalChecked();
v8::Local<v8::Uint32> sizeArg = Nan::New<v8::Uint32>(static_cast<uint32_t>(size));
v8::Local<v8::Uint32> nmembArg = Nan::New<v8::Uint32>(static_cast<uint32_t>(nmemb));
v8::Local<v8::Value> argv[argc] = {buf, sizeArg, nmembArg};
Nan::TryCatch tryCatch;
Nan::AsyncResource asyncResource("Easy::OnData");
Nan::MaybeLocal<v8::Value> returnValueCallback =
asyncResource.runInAsyncScope(this->handle(), it->second->GetFunction(), argc, argv);
if (tryCatch.HasCaught()) {
if (this->isInsideMultiHandle) {
this->callbackError.Reset(tryCatch.Exception());
} else {
tryCatch.ReThrow();
}
return returnValue;
}
if (returnValueCallback.IsEmpty() || !returnValueCallback.ToLocalChecked()->IsInt32()) {
v8::Local<v8::Value> typeError =
Nan::TypeError("Return value from the WRITE callback must be an integer.");
if (this->isInsideMultiHandle) {
this->callbackError.Reset(typeError);
} else {
Nan::ThrowError(typeError);
tryCatch.ReThrow();
}
return returnValue;
} else {
returnValue = Nan::To<int32_t>(returnValueCallback.ToLocalChecked()).FromJust();
}
return returnValue;
}
size_t Easy::OnHeader(char* data, size_t size, size_t nmemb) {
Nan::HandleScope scope;
size_t dataLength = size * nmemb;
CallbacksMap::iterator it = this->callbacks.find(CURLOPT_HEADERFUNCTION);
bool hasHeaderCallback = (it != this->callbacks.end());
// No callback is set
if (!hasHeaderCallback) {
return dataLength;
}
// if this gets returned it will cause a CURLE_WRITE_ERROR
int32_t returnValue = -1;
const int argc = 3;
v8::Local<v8::Object> buf =
Nan::CopyBuffer(data, static_cast<uint32_t>(dataLength)).ToLocalChecked();
v8::Local<v8::Uint32> sizeArg = Nan::New<v8::Uint32>(static_cast<uint32_t>(size));
v8::Local<v8::Uint32> nmembArg = Nan::New<v8::Uint32>(static_cast<uint32_t>(nmemb));
v8::Local<v8::Value> argv[argc] = {buf, sizeArg, nmembArg};
Nan::TryCatch tryCatch;
Nan::AsyncResource asyncResource("Easy::OnHeader");
Nan::MaybeLocal<v8::Value> returnValueCallback =
asyncResource.runInAsyncScope(this->handle(), it->second->GetFunction(), argc, argv);
if (tryCatch.HasCaught()) {
if (this->isInsideMultiHandle) {
this->callbackError.Reset(tryCatch.Exception());
} else {
tryCatch.ReThrow();
}
return returnValue;
}
if (returnValueCallback.IsEmpty() || !returnValueCallback.ToLocalChecked()->IsInt32()) {
v8::Local<v8::Value> typeError =
Nan::TypeError("Return value from the HEADER callback must be an integer.");
if (this->isInsideMultiHandle) {
this->callbackError.Reset(typeError);
} else {
Nan::ThrowError(typeError);
tryCatch.ReThrow();
}
return returnValue;
} else {
returnValue = Nan::To<int32_t>(returnValueCallback.ToLocalChecked()).FromJust();
}
return returnValue;
}
v8::Local<v8::Value> NullValueIfInvalidString(char* str) {
Nan::EscapableHandleScope scope;
v8::Local<v8::Value> ret = Nan::Null();
if (str != NULL && str[0] != '\0') {
ret = Nan::New(str).ToLocalChecked();
}
return scope.Escape(ret);
}
v8::Local<v8::Object> Easy::CreateV8ObjectFromCurlFileInfo(curl_fileinfo* fileInfo) {
Nan::EscapableHandleScope scope;
v8::Local<v8::String> fileName = Nan::New(fileInfo->filename).ToLocalChecked();
v8::Local<v8::Integer> fileType = Nan::New(fileInfo->filetype);
v8::Local<v8::Value> time = Nan::Null().As<v8::Value>();
if (fileInfo->time != 0)
time = Nan::New<v8::Date>(static_cast<double>(fileInfo->time) * 1000)
.ToLocalChecked()
.As<v8::Value>();
v8::Local<v8::Uint32> perm = Nan::New(fileInfo->perm);
v8::Local<v8::Integer> uid = Nan::New(fileInfo->uid);
v8::Local<v8::Integer> gid = Nan::New(fileInfo->gid);
v8::Local<v8::Number> size = Nan::New<v8::Number>(static_cast<double>(fileInfo->size));
v8::Local<v8::Integer> hardLinks = Nan::New(static_cast<int32_t>(fileInfo->hardlinks));
v8::Local<v8::Object> strings = Nan::New<v8::Object>();
Nan::Set(strings, Nan::New("time").ToLocalChecked(),
NullValueIfInvalidString(fileInfo->strings.time));
Nan::Set(strings, Nan::New("perm").ToLocalChecked(),
NullValueIfInvalidString(fileInfo->strings.perm));
Nan::Set(strings, Nan::New("user").ToLocalChecked(),
NullValueIfInvalidString(fileInfo->strings.user));
Nan::Set(strings, Nan::New("group").ToLocalChecked(),
NullValueIfInvalidString(fileInfo->strings.group));
Nan::Set(strings, Nan::New("target").ToLocalChecked(),
NullValueIfInvalidString(fileInfo->strings.target));
v8::Local<v8::Object> obj = Nan::New<v8::Object>();
Nan::Set(obj, Nan::New("fileName").ToLocalChecked(), fileName);
Nan::Set(obj, Nan::New("fileType").ToLocalChecked(), fileType);
Nan::Set(obj, Nan::New("time").ToLocalChecked(), time);
Nan::Set(obj, Nan::New("perm").ToLocalChecked(), perm);
Nan::Set(obj, Nan::New("uid").ToLocalChecked(), uid);
Nan::Set(obj, Nan::New("gid").ToLocalChecked(), gid);
Nan::Set(obj, Nan::New("size").ToLocalChecked(), size);
Nan::Set(obj, Nan::New("hardLinks").ToLocalChecked(), hardLinks);
Nan::Set(obj, Nan::New("strings").ToLocalChecked(), strings);
return scope.Escape(obj);
}
v8::Local<v8::Object> Easy::CreateV8ObjectFromCurlHstsEntry(struct curl_hstsentry* sts) {
Nan::EscapableHandleScope scope;
auto hasExpire = !!sts->expire[0] && !!strcmp(sts->expire, TIME_IN_THE_FUTURE);
v8::Local<v8::String> host = Nan::New(sts->name).ToLocalChecked();
v8::Local<v8::Boolean> includeSubDomains = Nan::New(!!sts->includeSubDomains);
v8::Local<v8::Value> expire = hasExpire ? Nan::New(sts->expire).ToLocalChecked().As<v8::Value>()
: Nan::Null().As<v8::Value>();
v8::Local<v8::Object> obj = Nan::New<v8::Object>();
Nan::Set(obj, Nan::New("host").ToLocalChecked(), host);
Nan::Set(obj, Nan::New("includeSubDomains").ToLocalChecked(), includeSubDomains);
Nan::Set(obj, Nan::New("expire").ToLocalChecked(), expire);
return scope.Escape(obj);
}
long Easy::CbChunkBgn(curl_fileinfo* transferInfo, void* ptr, int remains) { // NOLINT(runtime/int)
Easy* obj = static_cast<Easy*>(ptr);
assert(obj);
CallbacksMap::iterator it = obj->callbacks.find(CURLOPT_CHUNK_BGN_FUNCTION);
assert(it != obj->callbacks.end() && "CHUNK_BGN callback not set.");
const int argc = 2;
v8::Local<v8::Value> argv[argc] = {Easy::CreateV8ObjectFromCurlFileInfo(transferInfo),
Nan::New<v8::Number>(remains)};
int32_t returnValue = CURL_CHUNK_BGN_FUNC_FAIL;
Nan::TryCatch tryCatch;
Nan::AsyncResource asyncResource("Easy::CbChunkBgn");
Nan::MaybeLocal<v8::Value> returnValueCallback =
asyncResource.runInAsyncScope(obj->handle(), it->second->GetFunction(), argc, argv);
if (tryCatch.HasCaught()) {
if (obj->isInsideMultiHandle) {
obj->callbackError.Reset(tryCatch.Exception());
} else {
tryCatch.ReThrow();
}
return returnValue;
}
if (returnValueCallback.IsEmpty() || !returnValueCallback.ToLocalChecked()->IsInt32()) {
v8::Local<v8::Value> typeError =
Nan::TypeError("Return value from the CHUNK_BGN callback must be an integer.");
if (obj->isInsideMultiHandle) {
obj->callbackError.Reset(typeError);
} else {
Nan::ThrowError(typeError);
tryCatch.ReThrow();
}
} else {
returnValue = Nan::To<int32_t>(returnValueCallback.ToLocalChecked()).FromJust();
}
return returnValue;
}
long Easy::CbChunkEnd(void* ptr) { // NOLINT(runtime/int)
Easy* obj = static_cast<Easy*>(ptr);
assert(obj);
CallbacksMap::iterator it = obj->callbacks.find(CURLOPT_CHUNK_END_FUNCTION);
assert(it != obj->callbacks.end() && "CHUNK_END callback not set.");
int32_t returnValue = CURL_CHUNK_END_FUNC_FAIL;
Nan::TryCatch tryCatch;
Nan::AsyncResource asyncResource("Easy::CbChunkEnd");
Nan::MaybeLocal<v8::Value> returnValueCallback =
asyncResource.runInAsyncScope(obj->handle(), it->second->GetFunction(), 0, NULL);
if (tryCatch.HasCaught()) {
if (obj->isInsideMultiHandle) {
obj->callbackError.Reset(tryCatch.Exception());
} else {
tryCatch.ReThrow();
}
return returnValue;
}
if (returnValueCallback.IsEmpty() || !returnValueCallback.ToLocalChecked()->IsInt32()) {
v8::Local<v8::Value> typeError =
Nan::TypeError("Return value from the CHUNK_END callback must be an integer.");
if (obj->isInsideMultiHandle) {
obj->callbackError.Reset(typeError);
} else {
Nan::ThrowError(typeError);
tryCatch.ReThrow();
}
} else {
returnValue = Nan::To<int32_t>(returnValueCallback.ToLocalChecked()).FromJust();
}
return returnValue;
}
int Easy::CbDebug(CURL* handle, curl_infotype type, char* data, size_t size, void* userptr) {
Nan::HandleScope scope;
Easy* obj = static_cast<Easy*>(userptr);
assert(obj);
CallbacksMap::iterator it = obj->callbacks.find(CURLOPT_DEBUGFUNCTION);
assert(it != obj->callbacks.end() && "DEBUG callback not set.");
const int argc = 2;
v8::Local<v8::Object> buf = Nan::CopyBuffer(data, static_cast<uint32_t>(size)).ToLocalChecked();
v8::Local<v8::Value> argv[argc] = {
Nan::New<v8::Integer>(type),
buf,
};
int32_t returnValue = 1;
Nan::TryCatch tryCatch;
Nan::AsyncResource asyncResource("Easy::CbDebug");
Nan::MaybeLocal<v8::Value> returnValueCallback =
asyncResource.runInAsyncScope(obj->handle(), it->second->GetFunction(), argc, argv);
if (tryCatch.HasCaught()) {
if (obj->isInsideMultiHandle) {
obj->callbackError.Reset(tryCatch.Exception());
} else {
tryCatch.ReThrow();
}
return returnValue;
}
if (returnValueCallback.IsEmpty() || !returnValueCallback.ToLocalChecked()->IsInt32()) {
v8::Local<v8::Value> typeError =
Nan::TypeError("Return value from the DEBUG callback must be an integer.");
if (obj->isInsideMultiHandle) {
obj->callbackError.Reset(typeError);
} else {
Nan::ThrowError(typeError);
tryCatch.ReThrow();
}
} else {
returnValue = Nan::To<int32_t>(returnValueCallback.ToLocalChecked()).FromJust();
}
return returnValue;
}
int Easy::CbFnMatch(void* ptr, const char* pattern, const char* string) {
Nan::HandleScope scope;
Easy* obj = static_cast<Easy*>(ptr);
assert(obj);
CallbacksMap::iterator it = obj->callbacks.find(CURLOPT_FNMATCH_FUNCTION);
assert(it != obj->callbacks.end() && "FNMATCH callback not set.");
const int argc = 2;
v8::Local<v8::Value> argv[argc] = {Nan::New(pattern).ToLocalChecked(),
Nan::New(string).ToLocalChecked()};
int32_t returnValue = CURL_FNMATCHFUNC_FAIL;
Nan::TryCatch tryCatch;
Nan::AsyncResource asyncResource("Easy::CbFnMatch");
Nan::MaybeLocal<v8::Value> returnValueCallback =
asyncResource.runInAsyncScope(obj->handle(), it->second->GetFunction(), argc, argv);
if (tryCatch.HasCaught()) {
if (obj->isInsideMultiHandle) {
obj->callbackError.Reset(tryCatch.Exception());
} else {
tryCatch.ReThrow();
}
return returnValue;
}
if (returnValueCallback.IsEmpty() || !returnValueCallback.ToLocalChecked()->IsInt32()) {
v8::Local<v8::Value> typeError =
Nan::TypeError("Return value from the FNMATCH callback must be an integer.");
if (obj->isInsideMultiHandle) {
obj->callbackError.Reset(typeError);
} else {
Nan::ThrowError(typeError);
tryCatch.ReThrow();
}
} else {
returnValue = Nan::To<int32_t>(returnValueCallback.ToLocalChecked()).FromJust();
}
return returnValue;
}
int Easy::CbHstsRead(CURL* handle, struct curl_hstsentry* sts, void* userdata) {
#if NODE_LIBCURL_VER_GE(7, 74, 0)
Nan::HandleScope scope;
Easy* obj = static_cast<Easy*>(userdata);
assert(obj);
CallbacksMap::iterator it = obj->callbacks.find(CURLOPT_HSTSREADFUNCTION);
assert(it != obj->callbacks.end() && "HSTSREADFUNCTION callback not set.");
int32_t returnValue = CURLSTS_FAIL;
Nan::TryCatch tryCatch;
v8::Local<v8::Value> cacheEntryObject;
v8::Local<v8::Value> typeError = Nan::TypeError(
"Return value from the HSTSREADFUNCTION callback must be one of the following:\n"
" - Object matching the type CurlHstsEntry\n"
" - An array matching the type CurlHstsEntry[]\n"
" - null\n"
"Libcurl <= 7.79.0 does not stop requests from firing if there are errors in the HSTS "
"callback, thus you may be receiving an error while the request did in fact work. Please "
"fix "
"the HSTS callback to return the correct data to avoid this.");
if (obj->hstsReadCache.size() > 0) {
auto persistentValue = obj->hstsReadCache.back();
cacheEntryObject = Nan::New(obj->hstsReadCache.back());
// reset the persistent handler so we do not leak memory
persistentValue.Reset();
// remove it from the stack
obj->hstsReadCache.pop_back();
} else {
// if this is true, this means we got all the entries in the cache provided by the user
if (obj->wasHstsReadCacheSet) {
obj->wasHstsReadCacheSet = false;
return CURLSTS_DONE;
}
Nan::AsyncResource asyncResource("Easy::CbHstsRead");
Nan::MaybeLocal<v8::Value> returnValueFromHstsReadCallback =
asyncResource.runInAsyncScope(obj->handle(), it->second->GetFunction(), 0, NULL);
if (tryCatch.HasCaught()) {
if (obj->isInsideMultiHandle) {
obj->callbackError.Reset(tryCatch.Exception());
} else {
tryCatch.ReThrow();
}
return returnValue;
}
if (returnValueFromHstsReadCallback.IsEmpty()) {
THROW_ERROR_OR_SET_MULTI_CALLBACK_ERROR_IF_INSIDE_MULTI(typeError)
return returnValue;
}
cacheEntryObject = returnValueFromHstsReadCallback.ToLocalChecked();
}
if (cacheEntryObject->IsNull()) {
return CURLSTS_DONE;
} else {
// returning an array from the callback can be used to avoid multiple
// context switches between v8 and js
if (cacheEntryObject->IsArray()) {
auto cacheArray = cacheEntryObject.As<v8::Array>();
auto cacheArrayLength = cacheArray->Length();
if (cacheArrayLength == 0) {
return CURLSTS_DONE;
}
// inserting in reverse order as we are processing the hstsReadCache stack from back to front
for (int i = cacheArrayLength - 1; i >= 0; i--) {
auto idxValue = Nan::Get(cacheArray, i);
assert(!idxValue.IsEmpty() &&
"Value inside array could not be found - Process may be running out of memory");
auto idxValueChecked = idxValue.ToLocalChecked();
// we check for an array here too to avoid passing a child array here.
// If that happens, the code would get to this condition again when we
// process this cache entry in a future iteration
if (!idxValueChecked->IsObject() || idxValueChecked->IsArray()) {
THROW_ERROR_OR_SET_MULTI_CALLBACK_ERROR_IF_INSIDE_MULTI(typeError)
return returnValue;
}
auto idxValueAsObject = idxValueChecked.As<v8::Object>();
Nan::CopyablePersistentTraits<v8::Object>::CopyablePersistent persistentValue;
persistentValue.Reset(Nan::GetCurrentContext()->GetIsolate(), idxValueAsObject);
obj->hstsReadCache.push_back(persistentValue);
}
auto persistentValue = obj->hstsReadCache.back();
cacheEntryObject = Nan::New(obj->hstsReadCache.back());
persistentValue.Reset();
obj->hstsReadCache.pop_back();
obj->wasHstsReadCacheSet = true;
}
if (cacheEntryObject->IsObject()) {
// napi would make this so much cleaner...
auto cacheEntry = cacheEntryObject.As<v8::Object>();
auto hostPropertyStr = Nan::New("host").ToLocalChecked();
auto includeSubDomainsPropertyStr = Nan::New("includeSubDomains").ToLocalChecked();
auto expirePropertyStr = Nan::New("expire").ToLocalChecked();
auto hostPropertyValue = Nan::Get(cacheEntry, hostPropertyStr);
auto includeSubDomainsPropertyValue = Nan::Get(cacheEntry, includeSubDomainsPropertyStr);
auto expirePropertyValue = Nan::Get(cacheEntry, expirePropertyStr);
if (hostPropertyValue.IsEmpty() || includeSubDomainsPropertyValue.IsEmpty() ||
expirePropertyValue.IsEmpty()) {
assert("Process ran out of memory - fields returned from HSTSREADFUNCTION were empty");
}
auto hostPropertyValueChecked = hostPropertyValue.ToLocalChecked();
auto includeSubDomainsPropertyValueChecked = includeSubDomainsPropertyValue.ToLocalChecked();
auto expirePropertyValueChecked = expirePropertyValue.ToLocalChecked();
// the validation here is pretty basic, and we are not really validating
// the format of the expire string - libcurl should do that
// make sure the provided data is valid
if (!hostPropertyValueChecked->IsString() ||
(!includeSubDomainsPropertyValueChecked->IsNullOrUndefined() &&
!includeSubDomainsPropertyValueChecked->IsBoolean()) ||
(!expirePropertyValueChecked->IsNullOrUndefined() &&
!expirePropertyValueChecked->IsString())) {
THROW_ERROR_OR_SET_MULTI_CALLBACK_ERROR_IF_INSIDE_MULTI(typeError)
return returnValue;
}
Nan::Utf8String hostStrValue(hostPropertyValueChecked);
// make sure str len is inside the given max length
if (static_cast<size_t>(hostStrValue.length()) > sts->namelen) {
v8::Local<v8::Value> typeError = Nan::TypeError(
"The host property value returned from the HSTSREADFUNCTION callback function was "
"invalid. The host string is too long.\n"
"Libcurl <= 7.79.0 does not stop requests from firing if there are errors in the HSTS "
"callback, thus you may be receiving an error while the request did in fact work. "
"Please fix the HSTS callback to return the correct data to avoid this.");
THROW_ERROR_OR_SET_MULTI_CALLBACK_ERROR_IF_INSIDE_MULTI(typeError)
return returnValue;
}
sts->name = *hostStrValue;
sts->includeSubDomains = Nan::To<bool>(includeSubDomainsPropertyValueChecked).FromJust();
if (expirePropertyValueChecked->IsString()) {
// make sure expire length is one expected by libcurl
// YYYYMMDD HH:MM:SS [null-terminated]
size_t currentSize =
static_cast<size_t>(expirePropertyValueChecked.As<v8::String>()->Length());
size_t expectedSize = sizeof(sts->expire) / sizeof(sts->expire[0]) - 1;
if (currentSize != expectedSize) {
v8::Local<v8::Value> typeError = Nan::TypeError(
"The expire property value returned from the HSTSREADFUNCTION callback function was "
"invalid. String is either too long, or too short.\n"
"Libcurl <= 7.79.0 does not stop requests from firing if there are errors in the "
"HSTS "
"callback, thus you may be receiving an error while the request did in fact work. "
"Please fix the HSTS callback to return the correct data to avoid this.");
THROW_ERROR_OR_SET_MULTI_CALLBACK_ERROR_IF_INSIDE_MULTI(typeError)
return returnValue;
}
Nan::Utf8String expireStrValue(expirePropertyValueChecked);
auto expireCharValue = *expireStrValue;
strcpy(sts->expire, expireCharValue);
} else {
// TODO(jonathan): libcurl <= 7.79 has a bug when expire is not set, see:
// https://github.com/curl/curl/issues/7720 - to avoid this bug we are setting it manually
// to a future date here
strcpy(sts->expire, TIME_IN_THE_FUTURE);
}
returnValue = CURLSTS_OK;
} else {
THROW_ERROR_OR_SET_MULTI_CALLBACK_ERROR_IF_INSIDE_MULTI(typeError)
}
}
return returnValue;
#else
return 0;
#endif
}
int Easy::CbHstsWrite(CURL* handle, struct curl_hstsentry* sts, struct curl_index* count,
void* userdata) {
#if NODE_LIBCURL_VER_GE(7, 74, 0)
Nan::HandleScope scope;
Easy* obj = static_cast<Easy*>(userdata);
assert(obj);
CallbacksMap::iterator it = obj->callbacks.find(CURLOPT_HSTSWRITEFUNCTION);
assert(it != obj->callbacks.end() && "HSTSWRITEFUNCTION callback not set.");
int32_t returnValue = CURLSTS_FAIL;
Nan::TryCatch tryCatch;
v8::Local<v8::Value> value;
v8::Local<v8::Value> typeError =
Nan::TypeError("Return value from the HSTSWRITEFUNCTION callback must be an integer.");
// TODO(jonathan): give the option to receive an array directly?
v8::Local<v8::Object> countObj = Nan::New<v8::Object>();
v8::Local<v8::Number> index = Nan::New(static_cast<uint32_t>(count->index));
v8::Local<v8::Number> total = Nan::New(static_cast<uint32_t>(count->total));
Nan::Set(countObj, Nan::New("index").ToLocalChecked(), index);
Nan::Set(countObj, Nan::New("total").ToLocalChecked(), total);
const int argc = 2;
v8::Local<v8::Value> argv[argc] = {Easy::CreateV8ObjectFromCurlHstsEntry(sts), countObj};
Nan::AsyncResource asyncResource("Easy::CbHstsWrite");
Nan::MaybeLocal<v8::Value> returnValueCallback =
asyncResource.runInAsyncScope(obj->handle(), it->second->GetFunction(), argc, argv);
if (tryCatch.HasCaught()) {
if (obj->isInsideMultiHandle) {
obj->callbackError.Reset(tryCatch.Exception());
} else {
tryCatch.ReThrow();
}
return returnValue;
}
if (returnValueCallback.IsEmpty()) {
THROW_ERROR_OR_SET_MULTI_CALLBACK_ERROR_IF_INSIDE_MULTI(typeError)
return returnValue;
}
value = returnValueCallback.ToLocalChecked();
if (!value->IsNumber()) {
THROW_ERROR_OR_SET_MULTI_CALLBACK_ERROR_IF_INSIDE_MULTI(typeError)
return returnValue;
}
returnValue = Nan::To<int32_t>(value).FromJust();
return returnValue;
#else
return 0;
#endif
}
int Easy::CbProgress(void* clientp, double dltotal, double dlnow, double ultotal, double ulnow) {
Nan::HandleScope scope;
Easy* obj = static_cast<Easy*>(clientp);
assert(obj);
int32_t returnValue = 1;
// See the thread here for explanation on why this flag is needed
// https://curl.haxx.se/mail/lib-2014-06/0062.html
// This was fixed here
// https://github.com/curl/curl/commit/907520c4b93616bddea15757bbf0bfb45cde8101
if (obj->isCbProgressAlreadyAborted) {
return returnValue;
}
CallbacksMap::iterator it = obj->callbacks.find(CURLOPT_PROGRESSFUNCTION);
assert(it != obj->callbacks.end() && "PROGRESS callback not set.");
const int argc = 4;
v8::Local<v8::Value> argv[argc] = {Nan::New<v8::Number>(static_cast<double>(dltotal)),
Nan::New<v8::Number>(static_cast<double>(dlnow)),
Nan::New<v8::Number>(static_cast<double>(ultotal)),
Nan::New<v8::Number>(static_cast<double>(ulnow))};
Nan::TryCatch tryCatch;
Nan::AsyncResource asyncResource("Easy::CbProgress");
Nan::MaybeLocal<v8::Value> returnValueCallback =
asyncResource.runInAsyncScope(obj->handle(), it->second->GetFunction(), argc, argv);
if (tryCatch.HasCaught()) {
if (obj->isInsideMultiHandle) {
obj->callbackError.Reset(tryCatch.Exception());
} else {
tryCatch.ReThrow();
}
return returnValue;
}
if (returnValueCallback.IsEmpty() || !returnValueCallback.ToLocalChecked()->IsInt32()) {
v8::Local<v8::Value> typeError =
Nan::TypeError("Return value from the PROGRESS callback must be an integer.");
if (obj->isInsideMultiHandle) {
obj->callbackError.Reset(typeError);
} else {
Nan::ThrowError(typeError);
tryCatch.ReThrow();
}
} else {
returnValue = Nan::To<int32_t>(returnValueCallback.ToLocalChecked()).FromJust();
}
#if NODE_LIBCURL_VER_GE(7, 68, 0)
if (returnValue && returnValue != CURL_PROGRESSFUNC_CONTINUE) {
#else
if (returnValue) {
#endif
obj->isCbProgressAlreadyAborted = true;
}
return returnValue;
}
int Easy::CbTrailer(struct curl_slist** headerList, void* userdata) {
#if NODE_LIBCURL_VER_GE(7, 64, 0)
Nan::HandleScope scope;
Easy* obj = static_cast<Easy*>(userdata);
assert(obj);
CallbacksMap::iterator it;
// make sure the callback was set
it = obj->callbacks.find(CURLOPT_TRAILERFUNCTION);
assert(it != obj->callbacks.end() && "Trailer callback not set.");
Nan::TryCatch tryCatch;
Nan::AsyncResource asyncResource("Easy::CbTrailer");
Nan::MaybeLocal<v8::Value> returnValueCallback =
asyncResource.runInAsyncScope(obj->handle(), it->second->GetFunction(), 0, NULL);
if (tryCatch.HasCaught()) {
if (obj->isInsideMultiHandle) {
obj->callbackError.Reset(tryCatch.Exception());
} else {
tryCatch.ReThrow();
}
return CURL_TRAILERFUNC_ABORT;
}
v8::Local<v8::Value> returnValueCbTypeError = Nan::TypeError(
"Return value from the Trailer callback must be an array of strings or false.");
bool isInvalid =
returnValueCallback.IsEmpty() || (!returnValueCallback.ToLocalChecked()->IsArray() &&
!returnValueCallback.ToLocalChecked()->IsFalse());
if (isInvalid) {
if (obj->isInsideMultiHandle) {
obj->callbackError.Reset(returnValueCbTypeError);
} else {
Nan::ThrowError(returnValueCbTypeError);
tryCatch.ReThrow();
}
return CURL_TRAILERFUNC_ABORT;
}
v8::Local<v8::Value> returnValueCallbackChecked = returnValueCallback.ToLocalChecked();
if (returnValueCallbackChecked->IsFalse()) {
return CURL_TRAILERFUNC_ABORT;
}
v8::Local<v8::Array> rows = v8::Local<v8::Array>::Cast(returnValueCallbackChecked);
// [headerStr1, headerStr2]
for (uint32_t i = 0, len = rows->Length(); i < len; ++i) {
// not an array of objects
v8::Local<v8::Value> headerStrValue = Nan::Get(rows, i).ToLocalChecked();
if (!headerStrValue->IsString()) {
if (obj->isInsideMultiHandle) {
obj->callbackError.Reset(returnValueCbTypeError);
} else {
Nan::ThrowError(returnValueCbTypeError);
tryCatch.ReThrow();
}
return CURL_TRAILERFUNC_ABORT;
}
*headerList = curl_slist_append(*headerList, *Nan::Utf8String(headerStrValue));
}
return CURL_TRAILERFUNC_OK;
#else
return 0;
#endif
}
int Easy::CbXferinfo(void* clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal,
curl_off_t ulnow) {
Nan::HandleScope scope;
Easy* obj = static_cast<Easy*>(clientp);
assert(obj);
int32_t returnValue = 1;
// same check than above, see it for comments.
if (obj->isCbProgressAlreadyAborted) {
return returnValue;
}
CallbacksMap::iterator it;
// make sure the callback was set
#if NODE_LIBCURL_VER_GE(7, 32, 0)
it = obj->callbacks.find(CURLOPT_XFERINFOFUNCTION);
#else
// just to make it compile ¯\_(ツ)_/¯
it = obj->callbacks.end();
#endif
assert(it != obj->callbacks.end() && "XFERINFO callback not set.");
const int argc = 4;
v8::Local<v8::Value> argv[argc] = {Nan::New<v8::Number>(static_cast<double>(dltotal)),
Nan::New<v8::Number>(static_cast<double>(dlnow)),
Nan::New<v8::Number>(static_cast<double>(ultotal)),
Nan::New<v8::Number>(static_cast<double>(ulnow))};
Nan::TryCatch tryCatch;
Nan::AsyncResource asyncResource("Easy::CbXferinfo");
Nan::MaybeLocal<v8::Value> returnValueCallback =
asyncResource.runInAsyncScope(obj->handle(), it->second->GetFunction(), argc, argv);
if (tryCatch.HasCaught()) {
if (obj->isInsideMultiHandle) {
obj->callbackError.Reset(tryCatch.Exception());
} else {
tryCatch.ReThrow();
}
return returnValue;
}
if (returnValueCallback.IsEmpty() || !returnValueCallback.ToLocalChecked()->IsInt32()) {
v8::Local<v8::Value> typeError =
Nan::TypeError("Return value from the XFERINFO callback must be an integer.");
if (obj->isInsideMultiHandle) {
obj->callbackError.Reset(typeError);
} else {
Nan::ThrowError(typeError);
tryCatch.ReThrow();
}
} else {
returnValue = Nan::To<int32_t>(returnValueCallback.ToLocalChecked()).FromJust();
}
#if NODE_LIBCURL_VER_GE(7, 68, 0)
if (returnValue && returnValue != CURL_PROGRESSFUNC_CONTINUE) {
#else
if (returnValue) {
#endif
obj->isCbProgressAlreadyAborted = true;
}
return returnValue;
}
NAN_MODULE_INIT(Easy::Initialize) {
Nan::HandleScope scope;
// Easy js "class" function template initialization
v8::Local<v8::FunctionTemplate> tmpl = Nan::New<v8::FunctionTemplate>(Easy::New);
tmpl->SetClassName(Nan::New("Easy").ToLocalChecked());
tmpl->InstanceTemplate()->SetInternalFieldCount(1);
v8::Local<v8::ObjectTemplate> proto = tmpl->PrototypeTemplate();
// prototype methods
Nan::SetPrototypeMethod(tmpl, "setOpt", Easy::SetOpt);
Nan::SetPrototypeMethod(tmpl, "getInfo", Easy::GetInfo);
Nan::SetPrototypeMethod(tmpl, "send", Easy::Send);
Nan::SetPrototypeMethod(tmpl, "recv", Easy::Recv);
Nan::SetPrototypeMethod(tmpl, "perform", Easy::Perform);
Nan::SetPrototypeMethod(tmpl, "upkeep", Easy::Upkeep);
Nan::SetPrototypeMethod(tmpl, "pause", Easy::Pause);
Nan::SetPrototypeMethod(tmpl, "reset", Easy::Reset);
Nan::SetPrototypeMethod(tmpl, "dupHandle", Easy::DupHandle);
Nan::SetPrototypeMethod(tmpl, "onSocketEvent", Easy::OnSocketEvent);
Nan::SetPrototypeMethod(tmpl, "monitorSocketEvents", Easy::MonitorSocketEvents);
Nan::SetPrototypeMethod(tmpl, "unmonitorSocketEvents", Easy::UnmonitorSocketEvents);
Nan::SetPrototypeMethod(tmpl, "close", Easy::Close);
// static methods
Nan::SetMethod(tmpl, "strError", Easy::StrError);
// Instance accessors
Nan::SetAccessor(proto, Nan::New("id").ToLocalChecked(), Easy::IdGetter, 0,
v8::Local<v8::Value>(), v8::DEFAULT, v8::ReadOnly);
Nan::SetAccessor(proto, Nan::New("isInsideMultiHandle").ToLocalChecked(),
Easy::IsInsideMultiHandleGetter, 0, v8::Local<v8::Value>(), v8::DEFAULT,
v8::ReadOnly);
Nan::SetAccessor(proto, Nan::New("isMonitoringSockets").ToLocalChecked(),
Easy::IsMonitoringSocketsGetter, 0, v8::Local<v8::Value>(), v8::DEFAULT,
v8::ReadOnly);
Nan::SetAccessor(proto, Nan::New("isOpen").ToLocalChecked(), Easy::IsOpenGetter, 0,
v8::Local<v8::Value>(), v8::DEFAULT, v8::ReadOnly);
Easy::constructor.Reset(tmpl);
Nan::Set(target, Nan::New("Easy").ToLocalChecked(), Nan::GetFunction(tmpl).ToLocalChecked());
}
NAN_METHOD(Easy::New) {
if (!info.IsConstructCall()) {
Nan::ThrowError("You must use \"new\" to instantiate this object.");
}
v8::Local<v8::Value> jsHandle = info[0];
Easy* obj = nullptr;
// Copy constructor, used when duplicating handles.
if (!jsHandle->IsUndefined()) {
if (!jsHandle->IsExternal() &&
(!jsHandle->IsObject() || !Nan::New(Easy::constructor)->HasInstance(jsHandle))) {
Nan::ThrowError(Nan::TypeError("Argument must be an instance of an Easy handle."));
return;
}
// This is the case when calling with a curl easy handle directly
if (jsHandle->IsExternal()) {
CURL* curlEasyHandle = reinterpret_cast<CURL*>(info[0].As<v8::External>()->Value());
obj = new Easy(curlEasyHandle);
} else {
Easy* orig = Nan::ObjectWrap::Unwrap<Easy>(Nan::To<v8::Object>(info[0]).ToLocalChecked());
obj = new Easy(orig);
}
} else {
obj = new Easy();
}
if (obj) {
obj->Wrap(info.This());
info.GetReturnValue().Set(info.This());
}
}
NAN_GETTER(Easy::IdGetter) {
Easy* obj = Nan::ObjectWrap::Unwrap<Easy>(info.This());
info.GetReturnValue().Set(Nan::New(obj->id));
}
NAN_GETTER(Easy::IsInsideMultiHandleGetter) {
Easy* obj = Nan::ObjectWrap::Unwrap<Easy>(info.This());
info.GetReturnValue().Set(Nan::New(obj->isInsideMultiHandle));
}
NAN_GETTER(Easy::IsMonitoringSocketsGetter) {
Easy* obj = Nan::ObjectWrap::Unwrap<Easy>(info.This());
info.GetReturnValue().Set(Nan::New(obj->isMonitoringSockets));
}
NAN_GETTER(Easy::IsOpenGetter) {
Easy* obj = Nan::ObjectWrap::Unwrap<Easy>(info.This());
info.GetReturnValue().Set(Nan::New(obj->isOpen));
}
NAN_METHOD(Easy::SetOpt) {
Nan::HandleScope scope;
Easy* obj = Nan::ObjectWrap::Unwrap<Easy>(info.This());
if (!obj->isOpen) {
Nan::ThrowError("Curl handle is closed.");
return;
}
v8::Local<v8::Value> opt = info[0];
v8::Local<v8::Value> value = info[1];
CURLcode setOptRetCode = CURLE_UNKNOWN_OPTION;
int optionId;
// See this: https://daniel.haxx.se/blog/2020/08/28/enabling-better-curl-bindings/
// we probably could use these here for newer libcurl versions...
if ((optionId = IsInsideCurlConstantStruct(curlOptionNotImplemented, opt))) {
Nan::ThrowError(
"Unsupported option, probably because it's too complex to implement "
"using javascript or unecessary when using javascript (like the _DATA "
"options).");
return;
} else if ((optionId = IsInsideCurlConstantStruct(curlOptionSpecific, opt))) {
switch (optionId) {
case CURLOPT_SHARE:
if (value->IsNull()) {
setOptRetCode = curl_easy_setopt(obj->ch, CURLOPT_SHARE, NULL);
} else {
if (!value->IsObject() || !Nan::New(Share::constructor)->HasInstance(value)) {
Nan::ThrowTypeError(
"Invalid value for the SHARE option. It must be a Share "
"instance.");
return;
}
Share* share = Nan::ObjectWrap::Unwrap<Share>(value.As<v8::Object>());
if (!share->isOpen) {
Nan::ThrowError("Share handle is already closed.");
return;
}
setOptRetCode = curl_easy_setopt(obj->ch, CURLOPT_SHARE, share->sh);
}
break;
}
// linked list options
} else if ((optionId = IsInsideCurlConstantStruct(curlOptionLinkedList, opt))) {
if (value->IsNull()) {
setOptRetCode = curl_easy_setopt(obj->ch, static_cast<CURLoption>(optionId), NULL);
// HTTPPOST is a special case, since it's an array of objects.
} else if (optionId == CURLOPT_HTTPPOST) {
std::string invalidArrayMsg = "HTTPPOST option value should be an Array of Objects.";
if (!value->IsArray()) {
Nan::ThrowTypeError(invalidArrayMsg.c_str());
return;
}
v8::Local<v8::Array> rows = v8::Local<v8::Array>::Cast(value);
std::unique_ptr<CurlHttpPost> httpPost = std::make_unique<CurlHttpPost>();
// [{ key : val }]
for (uint32_t i = 0, len = rows->Length(); i < len; ++i) {
// not an array of objects
v8::Local<v8::Value> obj = Nan::Get(rows, i).ToLocalChecked();
if (!obj->IsObject()) {
Nan::ThrowTypeError(invalidArrayMsg.c_str());
return;
}
v8::Local<v8::Object> postData = v8::Local<v8::Object>::Cast(obj);
const v8::Local<v8::Array> props = Nan::GetPropertyNames(postData).ToLocalChecked();
const uint32_t postDataLength = props->Length();
bool hasFile = false;
bool hasContentType = false;
bool hasContent = false;
bool hasName = false;
bool hasNewFileName = false;
// loop through the properties names, making sure they are valid.
for (uint32_t j = 0; j < postDataLength; ++j) {
int32_t httpPostId = -1;
const v8::Local<v8::Value> postDataKey = Nan::Get(props, j).ToLocalChecked();
const v8::Local<v8::Value> postDataValue =
Nan::Get(postData, postDataKey).ToLocalChecked();
// convert postDataKey to httppost id
Nan::Utf8String fieldName(postDataKey);
std::string optionName = std::string(*fieldName);
std::transform(optionName.begin(), optionName.end(), optionName.begin(), ::toupper);
for (std::vector<CurlConstant>::const_iterator it = curlOptionHttpPost.begin(),
end = curlOptionHttpPost.end();
it != end; ++it) {
if (it->name == optionName) {
httpPostId = static_cast<int32_t>(it->value);
}
}
switch (httpPostId) {
case CurlHttpPost::FILE:
hasFile = true;
break;
case CurlHttpPost::TYPE:
hasContentType = true;
break;
case CurlHttpPost::CONTENTS:
hasContent = true;
break;
case CurlHttpPost::NAME:
hasName = true;
break;
case CurlHttpPost::FILENAME:
hasNewFileName = true;
break;
case -1: // property not found
std::string errorMsg;
errorMsg += std::string("Invalid property given: \"") + optionName +
"\". Valid properties are file, type, contents, name "
"and filename.";
Nan::ThrowError(errorMsg.c_str());
return;
}
// check if value is a string.
if (!postDataValue->IsString()) {
std::string errorMsg;
errorMsg += std::string("Value for property \"") + optionName + "\" must be a string.";
Nan::ThrowTypeError(errorMsg.c_str());
return;
}
}
if (!hasName) {
Nan::ThrowError("Missing field \"name\".");
return;
}
Nan::Utf8String fieldName(
Nan::Get(postData, Nan::New<v8::String>("name").ToLocalChecked()).ToLocalChecked());
CURLFORMcode curlFormCode;
if (hasFile) {
Nan::Utf8String file(
Nan::Get(postData, Nan::New<v8::String>("file").ToLocalChecked()).ToLocalChecked());
if (hasContentType) {
Nan::Utf8String contentType(
Nan::Get(postData, Nan::New<v8::String>("type").ToLocalChecked()).ToLocalChecked());
if (hasNewFileName) {
Nan::Utf8String fileName(
Nan::Get(postData, Nan::New<v8::String>("filename").ToLocalChecked())
.ToLocalChecked());
curlFormCode =
httpPost->AddFile(*fieldName, fieldName.length(), *file, *contentType, *fileName);
} else {
curlFormCode = httpPost->AddFile(*fieldName, fieldName.length(), *file, *contentType);
}
} else {
curlFormCode = httpPost->AddFile(*fieldName, fieldName.length(), *file);
}
} else if (hasContent) { // if file is not set, the contents field MUST
// be set.
Nan::Utf8String fieldValue(
Nan::Get(postData, Nan::New<v8::String>("contents").ToLocalChecked())
.ToLocalChecked());
curlFormCode =
httpPost->AddField(*fieldName, fieldName.length(), *fieldValue, fieldValue.length());
} else {
Nan::ThrowError("Missing field \"contents\".");
return;
}
if (curlFormCode != CURL_FORMADD_OK) {
std::string errorMsg;
errorMsg += std::string("Error while adding field \"") + *fieldName + "\" to post data.";
Nan::ThrowError(errorMsg.c_str());
return;
}
}
setOptRetCode = curl_easy_setopt(obj->ch, CURLOPT_HTTPPOST, httpPost->first);
if (setOptRetCode == CURLE_OK) {
obj->toFree->post.push_back(std::move(httpPost));
}
} else {
if (!value->IsArray()) {
Nan::ThrowTypeError("Option value must be an Array.");
return;
}
// convert value to curl linked list (curl_slist)
curl_slist* slist = NULL;
v8::Local<v8::Array> array = v8::Local<v8::Array>::Cast(value);
for (uint32_t i = 0, len = array->Length(); i < len; ++i) {
slist = curl_slist_append(slist, *Nan::Utf8String(Nan::Get(array, i).ToLocalChecked()));
}
setOptRetCode = curl_easy_setopt(obj->ch, static_cast<CURLoption>(optionId), slist);
if (setOptRetCode == CURLE_OK) {
obj->toFree->slist.push_back(slist);
}
}
// check if option is string, and the value is correct
} else if ((optionId = IsInsideCurlConstantStruct(curlOptionString, opt))) {
if (value->IsNull()) {
setOptRetCode = curl_easy_setopt(obj->ch, static_cast<CURLoption>(optionId), NULL);
} else {
if (!value->IsString()) {
Nan::ThrowTypeError("Option value must be a string.");
return;
}
Nan::Utf8String value(info[1]);
size_t length = static_cast<size_t>(value.length());
std::string valueStr = std::string(*value, length);
// libcurl makes a copy of the strings after version 7.17, CURLOPT_POSTFIELD
// is the only exception
if (static_cast<CURLoption>(optionId) == CURLOPT_POSTFIELDS) {
std::vector<char> valueChar = std::vector<char>(valueStr.begin(), valueStr.end());
valueChar.push_back(0);
setOptRetCode = curl_easy_setopt(obj->ch, static_cast<CURLoption>(optionId), &valueChar[0]);
if (setOptRetCode == CURLE_OK) {
obj->toFree->str.push_back(std::move(valueChar));
}
} else {
setOptRetCode =
curl_easy_setopt(obj->ch, static_cast<CURLoption>(optionId), valueStr.c_str());
}
}
// check if option is an integer, and the value is correct
} else if ((optionId = IsInsideCurlConstantStruct(curlOptionInteger, opt))) {
switch (optionId) {
case CURLOPT_INFILESIZE_LARGE:
case CURLOPT_MAXFILESIZE_LARGE:
case CURLOPT_MAX_RECV_SPEED_LARGE:
case CURLOPT_MAX_SEND_SPEED_LARGE:
case CURLOPT_POSTFIELDSIZE_LARGE:
case CURLOPT_RESUME_FROM_LARGE:
setOptRetCode =
curl_easy_setopt(obj->ch, static_cast<CURLoption>(optionId),
static_cast<curl_off_t>(Nan::To<double>(value).FromJust()));
break;
// special case with READDATA, since we need to store the file descriptor
// and not overwrite the READDATA already set in the handle.
case CURLOPT_READDATA:
obj->readDataFileDescriptor = Nan::To<int32_t>(value).FromJust();
setOptRetCode = CURLE_OK;
break;
default:
setOptRetCode = curl_easy_setopt(
obj->ch, static_cast<CURLoption>(optionId),
static_cast<long>(Nan::To<int32_t>(value).FromJust())); // NOLINT(runtime/int)
break;
}
// check if option is a function, and the value is correct
} else if ((optionId = IsInsideCurlConstantStruct(curlOptionFunction, opt))) {
bool isNull = value->IsNull();
if (!value->IsFunction() && !isNull) {
Nan::ThrowTypeError("Option value must be a null or a function.");
return;
}
switch (optionId) {
case CURLOPT_CHUNK_BGN_FUNCTION:
if (isNull) {
// only unset the CHUNK_DATA if CURLOPT_CHUNK_END_FUNCTION is not set.
if (!obj->callbacks.count(CURLOPT_CHUNK_END_FUNCTION)) {
curl_easy_setopt(obj->ch, CURLOPT_CHUNK_DATA, NULL);
}
obj->callbacks.erase(CURLOPT_CHUNK_BGN_FUNCTION);
setOptRetCode = curl_easy_setopt(obj->ch, CURLOPT_CHUNK_BGN_FUNCTION, NULL);
} else {
obj->callbacks[CURLOPT_CHUNK_BGN_FUNCTION].reset(
new Nan::Callback(value.As<v8::Function>()));
curl_easy_setopt(obj->ch, CURLOPT_CHUNK_DATA, obj);
setOptRetCode = curl_easy_setopt(obj->ch, CURLOPT_CHUNK_BGN_FUNCTION, Easy::CbChunkBgn);
}
break;
case CURLOPT_CHUNK_END_FUNCTION:
if (isNull) {
// only unset the CHUNK_DATA if CURLOPT_CHUNK_BGN_FUNCTION is not set.
if (!obj->callbacks.count(CURLOPT_CHUNK_BGN_FUNCTION)) {
curl_easy_setopt(obj->ch, CURLOPT_CHUNK_DATA, NULL);
}
obj->callbacks.erase(CURLOPT_CHUNK_END_FUNCTION);
setOptRetCode = curl_easy_setopt(obj->ch, CURLOPT_CHUNK_END_FUNCTION, NULL);
} else {
obj->callbacks[CURLOPT_CHUNK_END_FUNCTION].reset(
new Nan::Callback(value.As<v8::Function>()));
curl_easy_setopt(obj->ch, CURLOPT_CHUNK_DATA, obj);
setOptRetCode = curl_easy_setopt(obj->ch, CURLOPT_CHUNK_END_FUNCTION, Easy::CbChunkEnd);
}
break;
case CURLOPT_DEBUGFUNCTION:
if (isNull) {
obj->callbacks.erase(CURLOPT_DEBUGFUNCTION);
curl_easy_setopt(obj->ch, CURLOPT_DEBUGDATA, NULL);
setOptRetCode = curl_easy_setopt(obj->ch, CURLOPT_DEBUGFUNCTION, NULL);
} else {
obj->callbacks[CURLOPT_DEBUGFUNCTION].reset(new Nan::Callback(value.As<v8::Function>()));
curl_easy_setopt(obj->ch, CURLOPT_DEBUGDATA, obj);
setOptRetCode = curl_easy_setopt(obj->ch, CURLOPT_DEBUGFUNCTION, Easy::CbDebug);
}
break;
case CURLOPT_FNMATCH_FUNCTION:
if (isNull) {
obj->callbacks.erase(CURLOPT_FNMATCH_FUNCTION);
curl_easy_setopt(obj->ch, CURLOPT_FNMATCH_DATA, NULL);
setOptRetCode = curl_easy_setopt(obj->ch, CURLOPT_FNMATCH_FUNCTION, NULL);
} else {
obj->callbacks[CURLOPT_FNMATCH_FUNCTION].reset(
new Nan::Callback(value.As<v8::Function>()));
curl_easy_setopt(obj->ch, CURLOPT_FNMATCH_DATA, obj);
setOptRetCode = curl_easy_setopt(obj->ch, CURLOPT_FNMATCH_FUNCTION, Easy::CbFnMatch);
}
break;
case CURLOPT_HEADERFUNCTION:
setOptRetCode = CURLE_OK;
if (isNull) {
obj->callbacks.erase(CURLOPT_HEADERFUNCTION);
} else {
obj->callbacks[CURLOPT_HEADERFUNCTION].reset(new Nan::Callback(value.As<v8::Function>()));
}
break;
#if NODE_LIBCURL_VER_GE(7, 74, 0)
case CURLOPT_HSTSREADFUNCTION:
if (isNull) {
obj->callbacks.erase(CURLOPT_HSTSREADFUNCTION);
curl_easy_setopt(obj->ch, CURLOPT_HSTSREADDATA, NULL);
setOptRetCode = curl_easy_setopt(obj->ch, CURLOPT_HSTSREADFUNCTION, NULL);
} else {
obj->callbacks[CURLOPT_HSTSREADFUNCTION].reset(
new Nan::Callback(value.As<v8::Function>()));
curl_easy_setopt(obj->ch, CURLOPT_HSTSREADDATA, obj);
setOptRetCode = curl_easy_setopt(obj->ch, CURLOPT_HSTSREADFUNCTION, Easy::CbHstsRead);
}
break;
case CURLOPT_HSTSWRITEFUNCTION:
if (isNull) {
obj->callbacks.erase(CURLOPT_HSTSWRITEFUNCTION);
curl_easy_setopt(obj->ch, CURLOPT_HSTSWRITEDATA, NULL);
setOptRetCode = curl_easy_setopt(obj->ch, CURLOPT_HSTSWRITEFUNCTION, NULL);
} else {
obj->callbacks[CURLOPT_HSTSWRITEFUNCTION].reset(
new Nan::Callback(value.As<v8::Function>()));
curl_easy_setopt(obj->ch, CURLOPT_HSTSWRITEDATA, obj);
setOptRetCode = curl_easy_setopt(obj->ch, CURLOPT_HSTSWRITEFUNCTION, Easy::CbHstsWrite);
}
break;
#endif
case CURLOPT_PROGRESSFUNCTION:
if (isNull) {
obj->callbacks.erase(CURLOPT_PROGRESSFUNCTION);
curl_easy_setopt(obj->ch, CURLOPT_PROGRESSDATA, NULL);
setOptRetCode = curl_easy_setopt(obj->ch, CURLOPT_PROGRESSFUNCTION, NULL);
} else {
obj->callbacks[CURLOPT_PROGRESSFUNCTION].reset(
new Nan::Callback(value.As<v8::Function>()));
curl_easy_setopt(obj->ch, CURLOPT_PROGRESSDATA, obj);
setOptRetCode = curl_easy_setopt(obj->ch, CURLOPT_PROGRESSFUNCTION, Easy::CbProgress);
}
break;
case CURLOPT_READFUNCTION:
setOptRetCode = CURLE_OK;
if (isNull) {
obj->callbacks.erase(CURLOPT_READFUNCTION);
} else {
obj->callbacks[CURLOPT_READFUNCTION].reset(new Nan::Callback(value.As<v8::Function>()));
}
break;
case CURLOPT_SEEKFUNCTION:
setOptRetCode = CURLE_OK;
if (isNull) {
obj->callbacks.erase(CURLOPT_SEEKFUNCTION);
} else {
obj->callbacks[CURLOPT_SEEKFUNCTION].reset(new Nan::Callback(value.As<v8::Function>()));
}
break;
#if NODE_LIBCURL_VER_GE(7, 64, 0)
case CURLOPT_TRAILERFUNCTION:
if (isNull) {
obj->callbacks.erase(CURLOPT_TRAILERFUNCTION);
curl_easy_setopt(obj->ch, CURLOPT_TRAILERDATA, NULL);
setOptRetCode = curl_easy_setopt(obj->ch, CURLOPT_TRAILERFUNCTION, NULL);
} else {
obj->callbacks[CURLOPT_TRAILERFUNCTION].reset(
new Nan::Callback(value.As<v8::Function>()));
curl_easy_setopt(obj->ch, CURLOPT_TRAILERDATA, obj);
setOptRetCode = curl_easy_setopt(obj->ch, CURLOPT_TRAILERFUNCTION, Easy::CbTrailer);
}
break;
#endif
#if NODE_LIBCURL_VER_GE(7, 32, 0)
/* xferinfo was introduced in 7.32.0.
New libcurls will prefer the new callback and instead use that one even
if both callbacks are set. */
case CURLOPT_XFERINFOFUNCTION:
if (isNull) {
obj->callbacks.erase(CURLOPT_XFERINFOFUNCTION);
curl_easy_setopt(obj->ch, CURLOPT_XFERINFODATA, NULL);
setOptRetCode = curl_easy_setopt(obj->ch, CURLOPT_XFERINFOFUNCTION, NULL);
} else {
obj->callbacks[CURLOPT_XFERINFOFUNCTION].reset(
new Nan::Callback(value.As<v8::Function>()));
curl_easy_setopt(obj->ch, CURLOPT_XFERINFODATA, obj);
setOptRetCode = curl_easy_setopt(obj->ch, CURLOPT_XFERINFOFUNCTION, Easy::CbXferinfo);
}
break;
#endif
case CURLOPT_WRITEFUNCTION:
setOptRetCode = CURLE_OK;
if (isNull) {
obj->callbacks.erase(CURLOPT_WRITEFUNCTION);
} else {
obj->callbacks[CURLOPT_WRITEFUNCTION].reset(new Nan::Callback(value.As<v8::Function>()));
}
break;
}
// check if option is a blob, and the value is correct
} else if ((optionId = IsInsideCurlConstantStruct(curlOptionBlob, opt))) {
#if NODE_LIBCURL_VER_GE(7, 71, 0)
if (value->IsNull()) {
setOptRetCode = curl_easy_setopt(obj->ch, static_cast<CURLoption>(optionId), NULL);
} else if (value->IsString()) {
Nan::Utf8String utf8StringValue(value);
size_t length = static_cast<size_t>(utf8StringValue.length());
struct curl_blob blob;
blob.data = *utf8StringValue;
blob.len = length;
blob.flags = CURL_BLOB_COPY;
setOptRetCode = curl_easy_setopt(obj->ch, static_cast<CURLoption>(optionId), &blob);
} else if (node::Buffer::HasInstance(value)) {
struct curl_blob blob;
blob.data = node::Buffer::Data(value);
blob.len = node::Buffer::Length(value);
blob.flags = CURL_BLOB_COPY;
setOptRetCode = curl_easy_setopt(obj->ch, static_cast<CURLoption>(optionId), &blob);
} else {
Nan::ThrowTypeError("Option value must be a string or Buffer.");
return;
}
#else
Nan::ThrowError("Blob options require curl 7.71 or newer.");
return;
#endif
}
info.GetReturnValue().Set(setOptRetCode);
}
// traits class to determine if we need to check for null pointer first
template <typename>
struct ResultTypeIsChar : std::false_type {};
template <>
struct ResultTypeIsChar<char*> : std::true_type {};
template <typename TResultType, typename Tv8MappingType>
v8::Local<v8::Value> Easy::GetInfoTmpl(const Easy* obj, int infoId) {
Nan::EscapableHandleScope scope;
TResultType result;
CURLINFO info = static_cast<CURLINFO>(infoId);
CURLcode code = curl_easy_getinfo(obj->ch, info, &result);
v8::Local<v8::Value> retVal = Nan::Undefined();
if (code != CURLE_OK) {
std::string str = std::to_string(static_cast<int>(code));
Nan::ThrowError(str.c_str());
} else {
// is string
if (ResultTypeIsChar<TResultType>::value && !result) {
retVal = Nan::MakeMaybe(Nan::EmptyString()).ToLocalChecked();
} else {
retVal = Nan::MakeMaybe(Nan::New<Tv8MappingType>(result)).ToLocalChecked();
}
}
return scope.Escape(retVal);
}
NAN_METHOD(Easy::GetInfo) {
Nan::HandleScope scope;
Easy* obj = Nan::ObjectWrap::Unwrap<Easy>(info.This());
if (!obj->isOpen) {
Nan::ThrowError("Curl handle is closed.");
return;
}
v8::Local<v8::Value> infoVal = info[0];
v8::Local<v8::Value> retVal = Nan::Undefined();
int infoId;
CURLINFO curlInfo;
CURLcode code = CURLE_OK;
// Special case for unsupported info
if ((infoId = IsInsideCurlConstantStruct(curlInfoNotImplemented, infoVal))) {
Nan::ThrowError(
"Unsupported info, probably because it's too complex to implement "
"using javascript or unecessary when using javascript.");
return;
}
Nan::TryCatch tryCatch;
// String
if ((infoId = IsInsideCurlConstantStruct(curlInfoString, infoVal))) {
retVal = Easy::GetInfoTmpl<char*, v8::String>(obj, infoId);
// curl_off_t
} else if ((infoId = IsInsideCurlConstantStruct(curlInfoOffT, infoVal))) {
retVal = Easy::GetInfoTmpl<curl_off_t, v8::Number>(obj, infoId);
// Double
} else if ((infoId = IsInsideCurlConstantStruct(curlInfoDouble, infoVal))) {
retVal = Easy::GetInfoTmpl<double, v8::Number>(obj, infoId);
// Integer
} else if ((infoId = IsInsideCurlConstantStruct(curlInfoInteger, infoVal))) {
retVal = Easy::GetInfoTmpl<long, v8::Number>(obj, infoId); // NOLINT(runtime/int)
// ACTIVESOCKET and alike
} else if ((infoId = IsInsideCurlConstantStruct(curlInfoSocket, infoVal))) {
#if NODE_LIBCURL_VER_GE(7, 45, 0)
curl_socket_t socket;
#else
// this should never really used tho, as it's only possible to have
// an curlInfoSocket value with libcurl >= 7.45.0
long socket; // NOLINT(runtime/int)
#endif
code = curl_easy_getinfo(obj->ch, static_cast<CURLINFO>(infoId), &socket);
if (code == CURLE_OK) {
// curl_socket_t is of type SOCKET on Windows,
// casting it to int32_t can be dangerous, only if Microsoft ever decides
// to change the underlying architecture behind it.
// https://stackoverflow.com/a/26496808/710693
retVal = Nan::New<v8::Integer>(static_cast<int32_t>(socket));
}
// Linked list
} else if ((infoId = IsInsideCurlConstantStruct(curlInfoLinkedList, infoVal))) {
curl_slist* linkedList;
curl_slist* curr;
curlInfo = static_cast<CURLINFO>(infoId);
if (curlInfo == CURLINFO_CERTINFO) {
curl_certinfo* ci = NULL;
code = curl_easy_getinfo(obj->ch, curlInfo, &ci);
if (code == CURLE_OK) {
v8::Local<v8::Array> arr = Nan::New<v8::Array>();
bool isValid = true;
for (int i = 0; i < ci->num_of_certs; i++) {
linkedList = ci->certinfo[i];
if (linkedList) {
curr = linkedList;
while (curr) {
auto value = arr->Set(arr->CreationContext(), arr->Length(),
Nan::New<v8::String>(curr->data).ToLocalChecked());
if (value.IsJust()) {
curr = curr->next;
} else {
curr = NULL;
isValid = false;
}
}
// stop the loop if we found an invalid value
if (!isValid) {
break;
}
}
}
if (isValid) {
retVal = arr;
} else {
Nan::ThrowError("Something went wrong while trying to retrieve info from curl slist");
}
}
} else {
code = curl_easy_getinfo(obj->ch, curlInfo, &linkedList);
if (code == CURLE_OK) {
v8::Local<v8::Array> arr = Nan::New<v8::Array>();
bool isValid = true;
if (linkedList) {
curr = linkedList;
while (curr) {
auto value = arr->Set(arr->CreationContext(), arr->Length(),
Nan::New<v8::String>(curr->data).ToLocalChecked());
if (value.IsJust()) {
curr = curr->next;
} else {
curr = NULL;
isValid = false;
}
}
curl_slist_free_all(linkedList);
}
if (isValid) {
retVal = arr;
} else {
Nan::ThrowError("Something went wrong while trying to retrieve info from curl slist");
}
}
}
}
if (tryCatch.HasCaught()) {
Nan::Utf8String msg(tryCatch.Message()->Get());
std::string errCode = std::string(*msg);
// based on this interesting answer
// https://stackoverflow.com/a/27538478/710693
errCode.erase(std::remove_if(errCode.begin(), errCode.end(),
[](unsigned char c) { return !std::isdigit(c); }),
errCode.end());
// 43 is CURLE_BAD_FUNCTION_ARGUMENT
code = static_cast<CURLcode>(std::stoi(errCode.length() > 0 ? errCode : "43"));
}
v8::Local<v8::Object> ret = Nan::New<v8::Object>();
Nan::Set(ret, Nan::New("code").ToLocalChecked(), Nan::New(static_cast<int32_t>(code)));
Nan::Set(ret, Nan::New("data").ToLocalChecked(), retVal);
info.GetReturnValue().Set(ret);
}
NAN_METHOD(Easy::Send) {
Nan::HandleScope scope;
Easy* obj = Nan::ObjectWrap::Unwrap<Easy>(info.This());
if (!obj->isOpen) {
Nan::ThrowError("Curl handle is closed.");
return;
}
if (info.Length() == 0) {
Nan::ThrowError("Missing buffer argument.");
return;
}
v8::Local<v8::Value> buf = info[0];
if (!buf->IsObject() || !node::Buffer::HasInstance(buf)) {
Nan::ThrowError("Invalid Buffer instance given.");
return;
}
const char* bufContent = node::Buffer::Data(buf);
size_t bufLength = node::Buffer::Length(buf);
size_t n = 0;
CURLcode curlRet = curl_easy_send(obj->ch, bufContent, bufLength, &n);
v8::Local<v8::Object> ret = Nan::New<v8::Object>();
Nan::Set(ret, Nan::New("code").ToLocalChecked(), Nan::New(static_cast<int32_t>(curlRet)));
Nan::Set(ret, Nan::New("bytesSent").ToLocalChecked(), Nan::New(static_cast<int32_t>(n)));
info.GetReturnValue().Set(ret);
}
NAN_METHOD(Easy::Recv) {
Nan::HandleScope scope;
Easy* obj = Nan::ObjectWrap::Unwrap<Easy>(info.This());
if (!obj->isOpen) {
Nan::ThrowError("Curl handle is closed.");
return;
}
if (info.Length() == 0) {
Nan::ThrowError("Missing buffer argument.");
return;
}
v8::Local<v8::Value> buf = info[0];
if (!buf->IsObject() || !node::Buffer::HasInstance(buf)) {
Nan::ThrowError("Invalid Buffer instance given.");
return;
}
char* bufContent = node::Buffer::Data(buf);
size_t bufLength = node::Buffer::Length(buf);
size_t n = 0;
CURLcode curlRet = curl_easy_recv(obj->ch, bufContent, bufLength, &n);
v8::Local<v8::Object> ret = Nan::New<v8::Object>();
Nan::Set(ret, Nan::New("code").ToLocalChecked(), Nan::New(static_cast<int32_t>(curlRet)));
Nan::Set(ret, Nan::New("bytesReceived").ToLocalChecked(), Nan::New(static_cast<int32_t>(n)));
info.GetReturnValue().Set(ret);
}
// exec this handle
NAN_METHOD(Easy::Perform) {
Nan::HandleScope scope;
Easy* obj = Nan::ObjectWrap::Unwrap<Easy>(info.This());
if (!obj->isOpen) {
Nan::ThrowError("Curl handle is closed.");
return;
}
SETLOCALE_WRAPPER(CURLcode code = curl_easy_perform(obj->ch););
v8::Local<v8::Integer> ret = Nan::New<v8::Integer>(static_cast<int32_t>(code));
info.GetReturnValue().Set(ret);
}
NAN_METHOD(Easy::Upkeep) {
Nan::HandleScope scope;
Easy* obj = Nan::ObjectWrap::Unwrap<Easy>(info.This());
if (!obj->isOpen) {
Nan::ThrowError("Curl handle is closed.");
return;
}
#if NODE_LIBCURL_VER_GE(7, 62, 0)
CURLcode code = curl_easy_upkeep(obj->ch);
#else
CURLcode code = CURLE_FUNCTION_NOT_FOUND;
Nan::ThrowError(
"The addon was built against a libcurl version that does not support upkeep. It requires "
"libcurl >= 7.62");
return;
#endif
v8::Local<v8::Integer> ret = Nan::New<v8::Integer>(static_cast<int32_t>(code));
info.GetReturnValue().Set(ret);
}
NAN_METHOD(Easy::Pause) {
Nan::HandleScope scope;
Easy* obj = Nan::ObjectWrap::Unwrap<Easy>(info.This());
if (!obj->isOpen) {
Nan::ThrowError("Curl handle is closed.");
return;
}
if (!info[0]->IsUint32()) {
Nan::ThrowTypeError("Bitmask value must be an integer.");
return;
}
uint32_t bitmask = Nan::To<uint32_t>(info[0]).FromJust();
CURLcode code = curl_easy_pause(obj->ch, static_cast<int>(bitmask));
info.GetReturnValue().Set(static_cast<int32_t>(code));
}
NAN_METHOD(Easy::Reset) {
Nan::HandleScope scope;
Easy* obj = Nan::ObjectWrap::Unwrap<Easy>(info.This());
if (!obj->isOpen) {
Nan::ThrowError("Curl handle closed.");
return;
}
curl_easy_reset(obj->ch);
// reset the URL,
// https://github.com/bagder/curl/commit/ac6da721a3740500cc0764947385eb1c22116b83
curl_easy_setopt(obj->ch, CURLOPT_URL, "");
obj->callbacks.clear();
obj->ResetRequiredHandleOptions();
obj->toFree = nullptr;
obj->toFree = std::make_shared<Easy::ToFree>();
obj->readDataFileDescriptor = -1;
obj->readDataOffset = -1;
info.GetReturnValue().Set(info.This());
}
NAN_METHOD(Easy::DupHandle) {
Nan::HandleScope scope;
// create a new js object using this one as the argument for the constructor.
const int argc = 1;
v8::Local<v8::Value> argv[argc] = {info.This()};
v8::Local<v8::Function> cons = Nan::GetFunction(Nan::New(Easy::constructor)).ToLocalChecked();
v8::Local<v8::Object> newInstance = Nan::NewInstance(cons, argc, argv).ToLocalChecked();
info.GetReturnValue().Set(newInstance);
}
NAN_METHOD(Easy::OnSocketEvent) {
Nan::HandleScope scope;
Easy* obj = Nan::ObjectWrap::Unwrap<Easy>(info.This());
if (!info.Length()) {
Nan::ThrowError("You must specify the callback function.");
return;
}
v8::Local<v8::Value> arg = info[0];
if (arg->IsNull()) {
obj->cbOnSocketEvent = nullptr;
info.GetReturnValue().Set(info.This());
return;
}
if (!arg->IsFunction()) {
Nan::ThrowTypeError("Invalid callback given.");
return;
}
v8::Local<v8::Function> callback = arg.As<v8::Function>();
obj->cbOnSocketEvent.reset(new Nan::Callback(callback));
info.GetReturnValue().Set(info.This());
}
NAN_METHOD(Easy::MonitorSocketEvents) {
Nan::HandleScope scope;
Easy* obj = Nan::ObjectWrap::Unwrap<Easy>(info.This());
Nan::TryCatch tryCatch;
obj->MonitorSockets();
if (tryCatch.HasCaught()) {
tryCatch.ReThrow();
return;
}
info.GetReturnValue().Set(info.This());
}
NAN_METHOD(Easy::UnmonitorSocketEvents) {
Nan::HandleScope scope;
Easy* obj = Nan::ObjectWrap::Unwrap<Easy>(info.This());
Nan::TryCatch tryCatch;
obj->UnmonitorSockets();
if (tryCatch.HasCaught()) {
tryCatch.ReThrow();
return;
}
info.GetReturnValue().Set(info.This());
}
NAN_METHOD(Easy::Close) {
// check https://github.com/php/php-src/blob/master/ext/curl/interface.c#L3196
Nan::HandleScope scope;
Easy* obj = Nan::ObjectWrap::Unwrap<Easy>(info.This());
if (!obj->isOpen) {
Nan::ThrowError("Curl handle already closed.");
return;
}
if (obj->isInsideMultiHandle) {
Nan::ThrowError("Curl handle is inside a Multi instance, you must remove it first.");
return;
}
obj->Dispose();
return;
}
NAN_METHOD(Easy::StrError) {
Nan::HandleScope scope;
v8::Local<v8::Value> errCode = info[0];
if (!errCode->IsInt32()) {
Nan::ThrowTypeError("Invalid errCode passed to Easy.strError.");
return;
}
const char* errorMsg =
curl_easy_strerror(static_cast<CURLcode>(Nan::To<int32_t>(errCode).FromJust()));
v8::Local<v8::String> ret = Nan::New(errorMsg).ToLocalChecked();
info.GetReturnValue().Set(ret);
}
} // namespace NodeLibcurl