blob: 25d261d2a73a1172e87be056c9157130e41e7768 [file] [log] [blame]
#include "CvHttpRequest.h"
#include "CvLogger.h"
#include "CvXcode.h"
#include <stdexcept>
#include <string.h>
#include <stdlib.h>
#include <openssl/crypto.h>
using namespace std;
using namespace CvShared;
#if defined(__linux__)
CvShared::CvMutex* CvHttpRequest::COpenSslMt::m_lockArray = NULL;
#endif
struct CurlWriteBuffer
{
const char *data;
int64_t sizeleft;
CvHttpRequest *request;
};
CvHttpRequest::CvHttpRequest( enHttpMethod_t method ) :
m_bCancel(false), m_responseCode(0), m_progressUp(0), m_progressDown(0),
m_bResponseDataStarted(false), m_mutex("http-request")
{
m_mutex.Create();
m_req.method = method;
}
CvHttpRequest::~CvHttpRequest()
{
}
void CvHttpRequest::SetHeaders( const CMapHttpHeaders& aHeaders )
{
m_req.header_list = NULL;
for ( CMapHttpHeaders::const_iterator itr = aHeaders.begin(); itr != aHeaders.end(); ++itr )
{
m_req.header_list = curl_slist_append( m_req.header_list, (itr->first + ":" + itr->second).c_str() );
}
}
const String& CvHttpRequest::GetResponseHeader( const String& aKey ) const
{
static const String empty;
if ( m_responseHeaders.count(aKey) < 1 )
return empty;
return m_responseHeaders.find(aKey)->second;
}
size_t CvHttpRequest::ReadResponse(void* ptr, size_t size, size_t nmemb, void* stream)
{
size_t bufferSize = size * nmemb;
CvHttpRequest* pRequest = (CvHttpRequest*)stream;
if ( pRequest->m_bResponseDataStarted )
{
size_t currSize = pRequest->m_response.size();
pRequest->m_response.resize( currSize + bufferSize );
memcpy( (void*)(pRequest->m_response.data() + currSize), ptr, bufferSize );
}
if ( bufferSize == strlen("\r\n") && strncmp( (const char*)ptr, "\r\n", bufferSize ) == 0 )
pRequest->m_bResponseDataStarted = true;
return bufferSize;
}
size_t CvHttpRequest::WriteData(void *ptr, size_t size, size_t nmemb, void *stream)
{
struct CurlWriteBuffer *readBuf = (struct CurlWriteBuffer*)stream;
size_t curlBufSize = size * nmemb;
size_t retval = 0;
if (curlBufSize < 1)
return retval;
if (readBuf->sizeleft > curlBufSize)
{
memcpy(ptr, (const void *) readBuf->data, curlBufSize);
String s = String((char *) ptr);
retval = curlBufSize;
readBuf->sizeleft -= curlBufSize;
readBuf->data += curlBufSize;
}
else
{
memcpy(ptr, (const void *) readBuf->data, readBuf->sizeleft);
retval = readBuf->sizeleft;
readBuf->sizeleft = 0;
}
readBuf->request->m_bResponseDataStarted = false;
return retval;
}
size_t CvHttpRequest::WriteToFile(void *ptr, size_t size, size_t nmemb, FILE *stream)
{
size_t written = 0;
if (stream)
{
written = fwrite(ptr, size, nmemb, stream);
return written;
}
return written;
}
size_t CvHttpRequest::HeaderCallback(void *ptr, size_t size, size_t nmemb, void *userdata)
{
CvHttpRequest* pRequest = (CvHttpRequest*)userdata;
const char* pLine = (const char*)ptr;
const char* pDelim = strchr( pLine, ':' );
if ( pDelim != NULL )
{
CvString key( pLine, pDelim - pLine );
CvString value( pDelim + 1 );
// trim leading spaces
value.TrimLeft( " " );
// trim trailing \r\n
value.TrimRight( "\r\n" );
pRequest->m_responseHeaders[key] = value;
if ( key == HTTP_HEADER_CONTENT_LENGTH )
pRequest->m_contentSize = atol( value.c_str() );
}
return size * nmemb;
}
int CvHttpRequest::SetProgress(void *data, double dltotal, double dlnow, double ultotal, double ulnow)
{
CvHttpRequest *c = (CvHttpRequest *)data;
bool cancel = false;
{
CvMutexLock lock(c->m_mutex);
cancel = c->m_bCancel;
}
if (!cancel)
{
c->m_progressDown = dlnow;
c->m_progressUp = ulnow;
}
else
{
return 42; // return non-zero value to cancel transfer
}
return 0;
}
void CvHttpRequest::Start()
{
if ( m_req.data_size > 0 )
{
LogMessage( enLogLevel_Debug2, "==> [%s] HTTP request [%s] data-size [%lld] data [%s]",
m_req.url.c_str(), HttpMethodEnumToString(m_req.method).c_str(), m_req.data_size, m_req.data );
}
else
{
LogMessage( enLogLevel_Debug2, "==> [%s] HTTP request [%s] data-size [0]",
m_req.url.c_str(), HttpMethodEnumToString(m_req.method).c_str() );
}
CURLcode result;
FILE* fp = NULL;
CURL* curlSession = curl_easy_init();
if (curlSession)
{
curl_easy_setopt(curlSession, CURLOPT_NOSIGNAL, 1);
// set up stuff for regular GET request
curl_easy_setopt(curlSession, CURLOPT_URL, m_req.url.c_str());
if ( !m_req.fname.empty() )
{
fp = fopen(m_req.fname.c_str(), "wb");
m_response = "ok";
curl_easy_setopt(curlSession, CURLOPT_WRITEFUNCTION, CvHttpRequest::WriteToFile);
curl_easy_setopt(curlSession, CURLOPT_WRITEDATA, fp);
curl_easy_setopt(curlSession, CURLOPT_RESUME_FROM_LARGE, m_req.resume_from);
}
else
{
curl_easy_setopt(curlSession, CURLOPT_WRITEFUNCTION, CvHttpRequest::ReadResponse);
curl_easy_setopt(curlSession, CURLOPT_WRITEDATA, this);
}
curl_easy_setopt(curlSession, CURLOPT_PROXY, m_req.proxy.c_str()); // if empty - proxy disabled
curl_easy_setopt(curlSession, CURLOPT_NOPROGRESS, m_req.no_progress);
curl_easy_setopt(curlSession, CURLOPT_PROGRESSFUNCTION, &CvHttpRequest::SetProgress);
curl_easy_setopt(curlSession, CURLOPT_PROGRESSDATA, this);
struct CurlWriteBuffer curlBuf;
curlBuf.data = m_req.data;
curlBuf.sizeleft = m_req.data_size;
curlBuf.request = this;
// following if's set up additional stuff only used in POST PUT DEL and HEAD requests
if (m_req.method == enHttpMethod_POST)
{
curl_easy_setopt(curlSession, CURLOPT_POSTFIELDSIZE, m_req.data_size);
curl_easy_setopt(curlSession, CURLOPT_POSTFIELDS, m_req.data);
}
else if (m_req.method == enHttpMethod_PUT)
{
curl_easy_setopt(curlSession, CURLOPT_UPLOAD, 1L);
curl_easy_setopt(curlSession, CURLOPT_READDATA, &curlBuf);
curl_easy_setopt(curlSession, CURLOPT_READFUNCTION, CvHttpRequest::WriteData);
curl_easy_setopt(curlSession, CURLOPT_INFILESIZE, m_req.data_size);
}
else if (m_req.method == enHttpMethod_DEL)
{
curl_easy_setopt(curlSession, CURLOPT_CUSTOMREQUEST, HttpMethodEnumToString(enHttpMethod_DEL).c_str() );
}
else if (m_req.method == enHttpMethod_HEAD)
{
curl_easy_setopt(curlSession, CURLOPT_NOBODY, 1);
}
curl_easy_setopt(curlSession, CURLOPT_HEADER, 1);
curl_easy_setopt(curlSession, CURLOPT_HEADERFUNCTION, CvHttpRequest::HeaderCallback);
curl_easy_setopt(curlSession, CURLOPT_HEADERDATA, this);
char Error[1024] = {'\0'};
curl_easy_setopt(curlSession, CURLOPT_HTTPHEADER, m_req.header_list);
curl_easy_setopt(curlSession, CURLOPT_ERRORBUFFER, Error);
curl_easy_setopt(curlSession, CURLOPT_SSL_VERIFYPEER, false);
curl_easy_setopt(curlSession, CURLOPT_SSL_VERIFYHOST, 0);
if ( m_req.timeout != TIMEOUT_INFINITE )
curl_easy_setopt(curlSession, CURLOPT_TIMEOUT, m_req.timeout);
result = curl_easy_perform(curlSession);
if (result != 0)
{
LogMessage( enLogLevel_Error, "CURL error: %s", Error );
m_response = Error;
}
if (fp != NULL)
fclose(fp);
curl_easy_getinfo(curlSession, CURLINFO_RESPONSE_CODE, &m_responseCode);
curl_slist_free_all(m_req.header_list);
curl_easy_cleanup(curlSession);
LogMessage( enLogLevel_Debug2, "<-- [%s] HTTP response code [%d] data [%s]", m_req.url.c_str(), m_responseCode, m_response.c_str() );
if (result == 0)
{
if (m_responseCode > 202)
{
LogMessage( enLogLevel_Debug2, "<-- [%s] HTTP response: %s", m_req.url.c_str(), m_response.c_str() );
throw runtime_error( m_response.c_str() );
}
}
else
{
throw runtime_error( curl_easy_strerror(result) );
}
}
}
CvHttpRequest::enStatus_t CvHttpRequest::Execute( const Seconds& aTimeout, bool abProgress )
{
m_req.no_progress = abProgress ? 0 : 1; // this tells curl to enable or disable progress
m_req.timeout = aTimeout.Value();
try
{
Start();
}
catch (exception &e)
{
LogMessage( enLogLevel_Debug2, "<-- HTTP Error: %s", e.what() );
if (m_responseCode == 0)
return enStatus_NetworkError;
if (m_responseCode >= 400 && m_responseCode < 500)
return enStatus_ClientError;
if (m_responseCode >= 500)
return enStatus_ServerError;
}
return enStatus_Ok;
}
bool CvHttpRequest::EncodeURL( const String& aUrl, OUT String& aEncodedUrl )
{
bool bOk = false;
CURL* pCurl = curl_easy_init();
char* pEncodedUrl = curl_easy_escape( pCurl, aUrl.c_str(), (int)aUrl.length() );
if ( pEncodedUrl != NULL )
{
aEncodedUrl = pEncodedUrl;
curl_free( pEncodedUrl );
bOk = true;
}
curl_easy_cleanup( pCurl );
return bOk;
}
void CvHttpRequest::Clear()
{
m_responseCode = 0;
m_progressUp = 0;
m_progressDown = 0;
m_contentSize = 0;
m_bResponseDataStarted = false;
{
CvMutexLock lock(m_mutex);
m_bCancel = false;
}
m_response.clear();
m_responseHeaders.clear();
m_req.Clear();
}
void CvHttpRequest::sRequestData_t::Clear()
{
header_list = NULL;
data = NULL;
data_size = 0;
method = enHttpMethod_Unknown;
no_progress = true;
resume_from = 0;
timeout = CvHttpRequest::TIMEOUT_INFINITE;
url.clear();
fname.clear();
proxy.clear();
}
CvHttpRequest::COpenSslMt::COpenSslMt()
{
int i;
m_lockArray = new CvMutex[ CRYPTO_num_locks() ];
for ( int i = 0; i < CRYPTO_num_locks(); ++i )
{
m_lockArray[i].Create();
}
CRYPTO_set_id_callback( ThreadId );
CRYPTO_set_locking_callback( LockCallback );
}
CvHttpRequest::COpenSslMt::~COpenSslMt()
{
CRYPTO_set_locking_callback(NULL);
delete[] m_lockArray;
}
void CvHttpRequest::COpenSslMt::LockCallback( int mode, int type, const char*, int )
{
if (mode & CRYPTO_LOCK)
{
m_lockArray[type].Lock();
}
else
{
m_lockArray[type].Unlock();
}
}
unsigned long CvHttpRequest::COpenSslMt::ThreadId()
{
return (unsigned long)pthread_self();
}