Skip to content

Instantly share code, notes, and snippets.

@JCMais

JCMais/Easy.cc Secret

Created July 12, 2016 14:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JCMais/f547144a6223f01006d0b6f2e3487c66 to your computer and use it in GitHub Desktop.
Save JCMais/f547144a6223f01006d0b6f2e3487c66 to your computer and use it in GitHub Desktop.
/**
* @author Jonathan Cardoso Machado
* @license MIT
* @copyright 2015-2016, Jonathan Cardoso Machado
*
* 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 <iostream>
#include <stdlib.h>
#include <string.h>
#include "Easy.h"
#include "Share.h"
#include "make_unique.h"
#include "string_format.h"
// 36055 was allocated on Win64
#define MEMORY_PER_HANDLE 30000
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;
Nan::Persistent<v8::String> Easy::onDataCbSymbol;
Nan::Persistent<v8::String> Easy::onHeaderCbSymbol;
uint32_t Easy::counter = 0;
int32_t Easy::currentOpenedHandles = 0;
Easy::Easy() :
callbacks(), cbOnSocketEvent( nullptr ), pollHandle( nullptr ),
isCbProgressAlreadyAborted( false ), isMonitoringSockets( false ),
readDataFileDescriptor( -1 ), id( Easy::counter++ ), isInsideMultiHandle( false ), isOpen( true )
{
this->ch = curl_easy_init();
assert( this->ch );
NODE_LIBCURL_ADJUST_MEM( MEMORY_PER_HANDLE );
this->toFree = std::make_shared<Easy::ToFree>();
this->ResetRequiredHandleOptions();
++Easy::currentOpenedHandles;
}
Easy::Easy( Easy *orig ) :
callbacks(), cbOnSocketEvent( nullptr ), pollHandle( nullptr ),
isCbProgressAlreadyAborted( false ), isMonitoringSockets( false ),
readDataFileDescriptor( -1 ), id( Easy::counter++ ), isInsideMultiHandle( false ), isOpen( true )
{
assert( orig );
assert( orig != this ); //should not duplicate itself
this->ch = curl_easy_duphandle( orig->ch );
assert( this->ch );
NODE_LIBCURL_ADJUST_MEM( MEMORY_PER_HANDLE );
Nan::HandleScope scope;
// copy the orig callbacks 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
// no need to reset the _DATA option for the READ and WRITE callbacks, since they are reset on ResetRequiredHandleOptions()
this->toFree = orig->toFree;
this->ResetRequiredHandleOptions();
++Easy::currentOpenedHandles;
}
// 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 );
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_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 be only 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 );
//dispose persistent callbacks
this->DisposeCallbacks();
if ( this->isMonitoringSockets ) {
this->UnmonitorSockets();
}
this->isOpen = false;
--Easy::currentOpenedHandles;
}
void Easy::DisposeCallbacks()
{
}
void Easy::MonitorSockets()
{
int retUv;
CURLcode retCurl;
int events = 0 | UV_READABLE | UV_WRITABLE;
if ( this->pollHandle ) {
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;
retCurl = curl_easy_getinfo( this->ch, CURLINFO_LASTSOCKET, &socket );
#endif
if ( retCurl != CURLE_OK ) {
ThrowError( "Failed to receive socket!", curl_easy_strerror( retCurl ) );
return;
}
this->pollHandle = new uv_poll_t;
retUv = uv_poll_init_socket( uv_default_loop(), this->pollHandle, socket );
if ( retUv < 0 ) {
ThrowError( "Failed to poll on connection socket.", UV_ERROR_STRING( retUv ) );
return;
}
this->pollHandle->data = this;
retUv = uv_poll_start( this->pollHandle, events, Easy::OnSocket );
this->isMonitoringSockets = true;
}
void Easy::UnmonitorSockets()
{
int uvRet;
uvRet = uv_poll_stop( this->pollHandle );
if ( uvRet < 0 ) {
ThrowError( "Failed to stop polling on socket.", UV_ERROR_STRING( uvRet ) );
return;
}
uv_close( reinterpret_cast<uv_handle_t *>( this->pollHandle ), Easy::OnSocketClose );
this->isMonitoringSockets = false;
}
void Easy::OnSocket( uv_poll_t* handle, int status, int events )
{ //this runs on main thread
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 ) );
}
v8::Local<v8::Value> argv[] = {
err,
Nan::New<v8::Integer>( events )
};
this->cbOnSocketEvent->Call( this->handle(), 2, 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 ret = 0;
Easy *obj = static_cast<Easy*>( userdata );
int32_t fd = obj->readDataFileDescriptor;
CallbacksMap::iterator it = obj->callbacks.find( CURLOPT_READFUNCTION );
//read callback set, use it instead
if ( it != obj->callbacks.end() ) {
size_t n = size * nmemb;
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 ) );
v8::Local<v8::Value> argv[] = { buf, sizeArg, nmembArg };
v8::Local<v8::Value> retval = it->second->Call( obj->handle(), 3, argv );
char *data = node::Buffer::Data( buf );
ret = retval->Int32Value();
bool hasData = !!data && ret > 0 && ret < CURL_READFUNC_ABORT;
if ( hasData ) {
std::memcpy( ptr, data, ret );
}
// otherwise use the default read callback
} else {
//abort early if we don't have a file descriptor
if ( fd == -1 ) {
return CURL_READFUNC_ABORT;
}
#if UV_VERSION_MAJOR < 1
ret = uv_fs_read( uv_default_loop(), &readReq, fd, ptr, 1, -1, NULL );
#else
unsigned int len = (unsigned int) ( size * nmemb );
uv_buf_t uvbuf = uv_buf_init( ptr, len );
ret = uv_fs_read( uv_default_loop(), &readReq, fd, &uvbuf, 1, -1, NULL );
#endif
}
if ( ret < 0 ) {
return CURL_READFUNC_ABORT;
}
return static_cast<size_t>( ret );
}
size_t Easy::OnData( char *data, size_t size, size_t nmemb )
{
//@TODO If the callback close the connection, an error will be throw!
Nan::HandleScope scope;
size_t n = size * nmemb;
CallbacksMap::iterator it = this->callbacks.find( CURLOPT_WRITEFUNCTION );
v8::Local<v8::Value> cbOnData = this->handle()->Get( Nan::New( Easy::onDataCbSymbol ) );
bool hasWriteCallback = ( it != this->callbacks.end() );
// No callback is set
if ( !hasWriteCallback && cbOnData->IsUndefined() ) {
return n;
}
const int argc = 3;
v8::Local<v8::Object> buf = Nan::CopyBuffer( data, 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 ) );
v8::Local<v8::Value> argv[argc] = { buf, sizeArg, nmembArg };
v8::Local<v8::Value> retVal;
// Callback set with WRITEFUNCTION has priority over the onData one.
if ( hasWriteCallback ) {
retVal = it->second->Call( this->handle(), argc, argv );
}
else {
// if the cbWrite is not set, the onData cb must be
retVal = Nan::MakeCallback( this->handle(), cbOnData.As<v8::Function>(), argc, argv );
}
size_t ret = n;
if ( retVal.IsEmpty() ) {
ret = 0;
}
else {
ret = retVal->Uint32Value();
}
return ret;
}
size_t Easy::OnHeader( char *data, size_t size, size_t nmemb )
{
Nan::HandleScope scope;
size_t n = size * nmemb;
CallbacksMap::iterator it = this->callbacks.find( CURLOPT_HEADERFUNCTION );
v8::Local<v8::Value> cbOnHeader = this->handle()->Get( Nan::New( Easy::onHeaderCbSymbol ) );
bool hasHeaderCallback = ( it != this->callbacks.end() );
// No callback is set
if ( !hasHeaderCallback && cbOnHeader->IsUndefined() ) {
return n;
}
const int argc = 3;
v8::Local<v8::Object> buf = Nan::CopyBuffer( data, 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 ) );
v8::Local<v8::Value> argv[argc] = { buf, sizeArg, nmembArg };
v8::Local<v8::Value> retVal;
// Callback set with HEADERFUNCTION has priority over the onHeader one.
if ( hasHeaderCallback ) {
retVal = it->second->Call( this->handle(), argc, argv );
}
else {
// if the cbHeader is not set, the onData cb must be
retVal = Nan::MakeCallback( this->handle(), cbOnHeader.As<v8::Function>(), argc, argv );
}
size_t ret = n;
if ( retVal.IsEmpty() ) {
ret = 0;
}
else {
ret = retVal->Uint32Value();
}
return ret;
}
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::Date> time = Nan::New<v8::Date>( static_cast<double>( fileInfo->time ) * 1000 ).ToLocalChecked();
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 );
}
long Easy::CbChunkBgn( curl_fileinfo *transferInfo, void *ptr, int remains )
{
Easy *obj = static_cast<Easy *>( ptr );
assert( obj );
int32_t retValInt = CURL_CHUNK_BGN_FUNC_FAIL;
CallbacksMap::iterator it = obj->callbacks.find( CURLOPT_CHUNK_BGN_FUNCTION );
assert( it != obj->callbacks.end() && "CHUNK_BGN callback not initialized." );
const int argc = 2;
v8::Local<v8::Value> argv[argc] = {
Easy::CreateV8ObjectFromCurlFileInfo( transferInfo ),
Nan::New<v8::Number>( remains )
};
v8::Local<v8::Value> retVal = it->second->Call( obj->handle(), argc, argv );
if ( !retVal->IsInt32() ) {
Nan::ThrowTypeError( "Return value from the CHUNK_BGN callback must be an integer." );
}
else {
retValInt = retVal->Int32Value();
}
return retValInt;
}
long Easy::CbChunkEnd( void *ptr )
{
Easy *obj = static_cast<Easy *>( ptr );
assert( obj );
int32_t retValInt = CURL_CHUNK_END_FUNC_FAIL;
CallbacksMap::iterator it = obj->callbacks.find( CURLOPT_CHUNK_END_FUNCTION );
assert( it != obj->callbacks.end() && "CHUNK_END callback not initialized." );
v8::Local<v8::Value> retVal = it->second->Call( obj->handle(), 0, NULL );
if ( !retVal->IsInt32() ) {
Nan::ThrowTypeError( "Return value from the CHUNK_END callback must be an integer." );
}
else {
retValInt = retVal->Int32Value();
}
return retValInt;
}
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 );
int32_t retvalInt = 1;
CallbacksMap::iterator it = obj->callbacks.find( CURLOPT_DEBUGFUNCTION );
assert( it != obj->callbacks.end() && "DEBUG callback not initialized." );
v8::Local<v8::Value> argv[] = {
Nan::New<v8::Integer>( type ),
Nan::New<v8::String>( data, static_cast<int>( size ) ).ToLocalChecked()
};
v8::Local<v8::Value> retval = it->second->Call( obj->handle(), 2, argv );
if ( !retval->IsInt32() ) {
Nan::ThrowTypeError( "Return value from the DEBUG callback must be an integer." );
}
else {
retvalInt = retval->Int32Value();
}
return retvalInt;
}
int Easy::CbFnMatch( void *ptr, const char *pattern, const char *string )
{
Nan::HandleScope scope;
Easy *obj = static_cast<Easy *>( ptr );
assert( obj );
int32_t retvalInt32 = CURL_FNMATCHFUNC_FAIL;
CallbacksMap::iterator it = obj->callbacks.find( CURLOPT_FNMATCH_FUNCTION );
assert( it != obj->callbacks.end() && "FNMATCH callback not initialized." );
v8::Local<v8::String> argPattern = Nan::New( pattern ).ToLocalChecked();
v8::Local<v8::String> argString = Nan::New( string ).ToLocalChecked();
const int argc = 2;
v8::Local<v8::Value> argv[argc] = {
argPattern,
argString
};
v8::Local<v8::Value> retval = it->second->Call( obj->handle(), argc, argv );
if ( !retval->IsInt32() ) {
Nan::ThrowTypeError( "Return value from the fnmatch callback must be an integer." );
}
else {
retvalInt32 = retval->Int32Value();
}
return retvalInt32;
}
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 retvalInt32 = 1;
if ( obj->isCbProgressAlreadyAborted ) {
return retvalInt32;
}
CallbacksMap::iterator it = obj->callbacks.find( CURLOPT_PROGRESSFUNCTION );
assert( it != obj->callbacks.end() && "PROGRESS callback not initialized." );
v8::Local<v8::Value> argv[] = {
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 ) )
};
// Should handle possible exceptions here?
v8::Local<v8::Value> retval = it->second->Call( obj->handle(), 4, argv );
if ( !retval->IsInt32() ) {
Nan::ThrowTypeError( "Return value from the progress callback must be an integer." );
}
else {
retvalInt32 = retval->Int32Value();
}
if ( retvalInt32 ) {
obj->isCbProgressAlreadyAborted = true;
}
return retvalInt32;
}
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 retvalInt32 = 1;
if ( obj->isCbProgressAlreadyAborted ) {
return retvalInt32;
}
// make sure the callback was set
#if NODE_LIBCURL_VER_GE( 7, 32, 0 )
CallbacksMap::iterator it = obj->callbacks.find( CURLOPT_XFERINFOFUNCTION );
#elif
CallbacksMap::iterator it = obj->callbacks.end(); // just to make it compile ¯\_(ツ)_/¯
#endif
assert( it != obj->callbacks.end() && "XFERINFO callback not initialized." );
v8::Local<v8::Value> argv[] = {
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 ) )
};
v8::Local<v8::Value> retval = it->second->Call( obj->handle(), 4, argv );
if ( !retval->IsInt32() ) {
Nan::ThrowTypeError( "Return value from the progress callback must be an integer." );
}
else {
retvalInt32 = retval->Int32Value();
}
if ( retvalInt32 ) {
obj->isCbProgressAlreadyAborted = true;
}
return retvalInt32;
}
NODE_LIBCURL_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, "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 );
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 );
Easy::constructor.Reset( tmpl );
Easy::onDataCbSymbol.Reset( Nan::New( "onData" ).ToLocalChecked() );
Easy::onHeaderCbSymbol.Reset( Nan::New( "onHeader" ).ToLocalChecked() );
Nan::Set( exports, Nan::New( "Easy" ).ToLocalChecked(), tmpl->GetFunction() );
}
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;
if ( !jsHandle->IsUndefined() ) {
if ( !jsHandle->IsObject() || !Nan::New( Easy::constructor )->HasInstance( jsHandle ) ) {
Nan::ThrowError( Nan::TypeError( "Argument must be an instance of an Easy handle." ) );
return;
}
Easy *orig = Nan::ObjectWrap::Unwrap<Easy>( info[0]->ToObject() );
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_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;
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 ) ) ) {
// HTTPPOST is a special case, since it's an array of objects.
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
if ( !rows->Get( i )->IsObject() ) {
Nan::ThrowTypeError( invalidArrayMsg.c_str() );
return;
}
v8::Local<v8::Object> postData = v8::Local<v8::Object>::Cast( rows->Get( i ) );
const v8::Local<v8::Array> props = postData->GetPropertyNames();
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 = props->Get( j );
const v8::Local<v8::Value> postDataValue = postData->Get( postDataKey );
// convert postDataKey to httppost id
Nan::Utf8String fieldName( postDataKey );
std::string optionName = std::string( *fieldName );
stringToUpper( optionName );
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 = string_format( "Invalid property \"%s\" given. Valid properties are FILE, TYPE, CONTENTS, NAME and FILENAME.", *fieldName );
Nan::ThrowError( errorMsg.c_str() );
return;
}
// check if value is a string.
if ( !postDataValue->IsString() ) {
std::string errorMsg = string_format( "Value for property \"%s\" must be a string.", *fieldName );
Nan::ThrowTypeError( errorMsg.c_str() );
return;
}
}
if ( !hasName ) {
Nan::ThrowError( "Missing field \"name\"." );
return;
}
Nan::Utf8String fieldName( postData->Get( Nan::New<v8::String>( "name" ).ToLocalChecked() ) );
CURLFORMcode curlFormCode;
if ( hasFile ) {
Nan::Utf8String file( postData->Get( Nan::New<v8::String>( "file" ).ToLocalChecked() ) );
if ( hasContentType ) {
Nan::Utf8String contentType( postData->Get( Nan::New<v8::String>( "type" ).ToLocalChecked() ) );
if ( hasNewFileName ) {
Nan::Utf8String fileName( postData->Get( Nan::New<v8::String>( "filename" ).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( postData->Get( Nan::New<v8::String>( "contents" ).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 = string_format( "Error while adding field \"%s\" to post data. CURL_FORMADD error code: %d", *fieldName, static_cast<int>( curlFormCode ) );
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->IsNull() ) {
setOptRetCode = curl_easy_setopt( obj->ch, static_cast<CURLoption>( optionId ), NULL );
}
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( array->Get( i ) ) );
}
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->IsString() ) {
Nan::ThrowTypeError( "Option value must be a string." );
return;
}
// Create a string copy
bool isNull = value->IsNull();
if ( isNull ) {
setOptRetCode = curl_easy_setopt( obj->ch, static_cast<CURLoption>( optionId ), NULL );
}
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 a 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>( value->NumberValue() ) );
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 = value->Int32Value();
setOptRetCode = CURLE_OK;
break;
default:
setOptRetCode = curl_easy_setopt( obj->ch, static_cast<CURLoption>( optionId ), static_cast<long>( value->Int32Value() ) );
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;
}
v8::Local<v8::Function> callback = value.As<v8::Function>();
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( callback ) );
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( callback ) );
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( callback ) );
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( callback ) );
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;
obj->callbacks.erase( CURLOPT_HEADERFUNCTION );
if ( !isNull ) {
obj->callbacks[CURLOPT_HEADERFUNCTION].reset( new Nan::Callback( callback ) );
}
break;
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( callback ) );
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;
obj->callbacks.erase( CURLOPT_READFUNCTION );
if ( !isNull ) {
obj->callbacks[CURLOPT_READFUNCTION].reset( new Nan::Callback( callback ) );
}
break;
#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( callback ) );
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;
obj->callbacks.erase( CURLOPT_WRITEFUNCTION );
if ( !isNull ) {
obj->callbacks[CURLOPT_WRITEFUNCTION].reset( new Nan::Callback( callback ) );
}
break;
}
}
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 );
//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 );
//ACTIVESOCKET and alike
}
else if ( ( infoId = IsInsideCurlConstantStruct( curlInfoSocket, infoVal ) ) ) {
curl_socket_t sock;
code = curl_easy_getinfo( obj->ch, static_cast<CURLINFO>( infoId ), &sock );
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.
retVal = Nan::New<v8::Integer>( static_cast<int32_t>( sock ) );
}
//Linked list
}
else if ( ( infoId = IsInsideCurlConstantStruct( curlInfoLinkedList, infoVal ) ) ) {
curl_slist *linkedList;
curl_slist *curr;
curlInfo = static_cast<CURLINFO>( infoId );
code = curl_easy_getinfo( obj->ch, curlInfo, &linkedList );
if ( code == CURLE_OK ) {
v8::Local<v8::Array> arr = Nan::New<v8::Array>();
if ( linkedList ) {
curr = linkedList;
while ( curr ) {
arr->Set( arr->Length(), Nan::New<v8::String>( curr->data ).ToLocalChecked() );
curr = curr->next;
}
curl_slist_free_all( linkedList );
}
retVal = arr;
}
}
if ( tryCatch.HasCaught() ) {
Nan::Utf8String msg( tryCatch.Message()->Get() );
std::string errCode = std::string( *msg );
code = static_cast<CURLcode>( std::stoi( errCode ) );
}
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::Array> ret = Nan::New<v8::Array>();
Nan::Set( ret, 0, Nan::New( static_cast<int32_t>( curlRet ) ) );
Nan::Set( ret, 1, 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::Array> ret = Nan::New<v8::Array>();
Nan::Set( ret, 0, Nan::New( static_cast<int32_t>( curlRet ) ) );
Nan::Set( ret, 1, 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;
}
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::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 = info[0]->Uint32Value();
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->DisposeCallbacks();
obj->ResetRequiredHandleOptions();
obj->toFree = nullptr;
obj->toFree = std::make_shared<Easy::ToFree>();
obj->readDataFileDescriptor = -1;
info.GetReturnValue().Set( info.This() );
}
NAN_METHOD( Easy::DupHandle )
{
Nan::HandleScope scope;
// create a new js object.
const int argc = 1;
v8::Local<v8::Value> argv[argc] = { info.This() };
v8::Local<v8::Function> cons = Nan::New( Easy::constructor )->GetFunction();
v8::Local<v8::Object> newInstance = cons->NewInstance( argc, argv );
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>( errCode->Int32Value() ) );
v8::Local<v8::String> ret = Nan::New( errorMsg ).ToLocalChecked();
info.GetReturnValue().Set( ret );
}
}
#ifndef NODELIBCURL_EASY_H
#define NODELIBCURL_EASY_H
/**
* @author Jonathan Cardoso Machado
* @license MIT
* @copyright 2015-2016, Jonathan Cardoso Machado
*
* 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 <map>
#include <vector>
#include <string>
#include <memory>
#include <node.h>
#include <nan.h>
#include <curl/curl.h>
#include "Curl.h"
#include "CurlHttpPost.h"
#include "macros.h"
using Nan::ObjectWrap;
namespace NodeLibcurl {
class Easy: public ObjectWrap {
class ToFree;
Easy();
explicit Easy( Easy* orig );
//@TODO implement copy constructor to the duphandle?
Easy( const Easy &that );
Easy& operator=( const Easy &that );
~Easy();
// instance methods
void Dispose();
void DisposeCallbacks();
void ResetRequiredHandleOptions();
void CallSocketEvent( int status, int events );
void MonitorSockets();
void UnmonitorSockets();
size_t OnData( char *data, size_t size, size_t nmemb );
size_t OnHeader( char *data, size_t size, size_t nmemb );
// static members
static uint32_t counter;
// callbacks
typedef std::map<CURLoption, std::shared_ptr<Nan::Callback>> CallbacksMap;
CallbacksMap callbacks;
std::shared_ptr<Nan::Callback> cbOnSocketEvent;
// members
uv_poll_t *pollHandle;
std::shared_ptr<ToFree> toFree;
bool isCbProgressAlreadyAborted; // we need this flag because of https://github.com/bagder/curl/commit/907520c4b93616bddea15757bbf0bfb45cde8101
bool isMonitoringSockets;
int32_t readDataFileDescriptor; //READDATA sets that
uint32_t id;
// static methods
template<typename TResultType, typename Tv8MappingType>
static v8::Local<v8::Value> GetInfoTmpl( const Easy *obj, int infoId );
static v8::Local<v8::Object> CreateV8ObjectFromCurlFileInfo( curl_fileinfo *fileInfo );
// persistent objects
static Nan::Persistent<v8::String> onDataCbSymbol;
static Nan::Persistent<v8::String> onHeaderCbSymbol;
public:
// operators
bool operator==( const Easy &easy ) const;
bool operator!=( const Easy &other ) const;
// js object constructor template
static Nan::Persistent<v8::FunctionTemplate> constructor;
// members
CURL *ch;
bool isInsideMultiHandle;
bool isOpen;
// static members
static int32_t currentOpenedHandles;
// export Easy to js
static NODE_LIBCURL_MODULE_INIT( Initialize );
// js available methods
static NAN_METHOD( New );
static NAN_GETTER( IdGetter );
static NAN_GETTER( IsInsideMultiHandleGetter );
static NAN_METHOD( SetOpt );
static NAN_METHOD( GetInfo );
static NAN_METHOD( Send );
static NAN_METHOD( Recv );
static NAN_METHOD( Perform );
static NAN_METHOD( Pause );
static NAN_METHOD( Reset );
static NAN_METHOD( DupHandle );
static NAN_METHOD( OnSocketEvent );
static NAN_METHOD( MonitorSocketEvents );
static NAN_METHOD( UnmonitorSocketEvents );
static NAN_METHOD( Close );
static NAN_METHOD( StrError );
// cURL callbacks
static size_t ReadFunction( char *ptr, size_t size, size_t nmemb, void *userdata );
static size_t HeaderFunction( char *ptr, size_t size, size_t nmemb, void *userdata );
static size_t WriteFunction( char *ptr, size_t size, size_t nmemb, void *userdata );
static long CbChunkBgn( curl_fileinfo *transferInfo, void *ptr, int remains );
static long CbChunkEnd( void *ptr );
static int CbDebug( CURL *handle, curl_infotype type, char *data, size_t size, void *userptr );
static int CbFnMatch( void *ptr, const char *pattern, const char *string );
static int CbProgress( void *clientp, double dltotal, double dlnow, double ultotal, double ulnow );
static int CbXferinfo( void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow );
// libuv callbacks
static void OnSocket( uv_poll_t* handle, int status, int events );
static void OnSocketClose( uv_handle_t* handle );
};
}
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment