Files
wxWidgets/src/common/webrequest_curl.cpp
Lauri Nurmi 3514dda407 Fix compilation with curl <7.21.6 used under CentOS 6
According to cURL documentation, the CURLOPT_ACCEPT_ENCODING symbol
was called CURLOPT_ENCODING earlier.

Closes https://github.com/wxWidgets/wxWidgets/pull/2210
2021-02-02 13:03:06 +01:00

579 lines
16 KiB
C++

///////////////////////////////////////////////////////////////////////////////
// Name: src/common/webrequest_curl.h
// Purpose: wxWebRequest implementation using libcurl
// Author: Tobias Taschner
// Created: 2018-10-25
// Copyright: (c) 2018 wxWidgets development team
// Licence: wxWindows licence
///////////////////////////////////////////////////////////////////////////////
// For compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"
#include "wx/webrequest.h"
#if wxUSE_WEBREQUEST_CURL
#include "wx/private/webrequest_curl.h"
#ifndef WX_PRECOMP
#include "wx/log.h"
#include "wx/translation.h"
#include "wx/utils.h"
#endif
#include "wx/uri.h"
// Define symbols that might be missing from older libcurl headers
#ifndef CURL_AT_LEAST_VERSION
#define CURL_VERSION_BITS(x,y,z) ((x)<<16|(y)<<8|z)
#define CURL_AT_LEAST_VERSION(x,y,z) \
(LIBCURL_VERSION_NUM >= CURL_VERSION_BITS(x, y, z))
#endif
#if CURL_AT_LEAST_VERSION(7, 28, 0)
#define wxCURL_HAVE_MULTI_WAIT 1
#else
#define wxCURL_HAVE_MULTI_WAIT 0
#endif
// The new name was introduced in curl 7.21.6.
#ifndef CURLOPT_ACCEPT_ENCODING
#define CURLOPT_ACCEPT_ENCODING CURLOPT_ENCODING
#endif
//
// wxWebResponseCURL
//
static size_t wxCURLWriteData(void* buffer, size_t size, size_t nmemb, void* userdata)
{
wxCHECK_MSG( userdata, 0, "invalid curl write callback data" );
return static_cast<wxWebResponseCURL*>(userdata)->CURLOnWrite(buffer, size * nmemb);
}
static size_t wxCURLHeader(char *buffer, size_t size, size_t nitems, void *userdata)
{
wxCHECK_MSG( userdata, 0, "invalid curl header callback data" );
return static_cast<wxWebResponseCURL*>(userdata)->CURLOnHeader(buffer, size * nitems);
}
wxWebResponseCURL::wxWebResponseCURL(wxWebRequestCURL& request) :
wxWebResponseImpl(request)
{
curl_easy_setopt(GetHandle(), CURLOPT_WRITEDATA, static_cast<void*>(this));
curl_easy_setopt(GetHandle(), CURLOPT_HEADERDATA, static_cast<void*>(this));
Init();
}
size_t wxWebResponseCURL::CURLOnWrite(void* buffer, size_t size)
{
void* buf = GetDataBuffer(size);
memcpy(buf, buffer, size);
ReportDataReceived(size);
return size;
}
size_t wxWebResponseCURL::CURLOnHeader(const char * buffer, size_t size)
{
// HTTP headers are supposed to only contain ASCII data, so any encoding
// should work here, but use Latin-1 for compatibility with some servers
// that send it directly and to at least avoid losing data entirely when
// the current encoding is UTF-8 but the input doesn't decode correctly.
wxString hdr = wxString::From8BitData(buffer, size);
hdr.Trim();
if ( hdr.StartsWith("HTTP/") )
{
// First line of the headers contains status text after
// version and status
m_statusText = hdr.AfterFirst(' ').AfterFirst(' ');
m_headers.clear();
}
else if ( !hdr.empty() )
{
wxString hdrValue;
wxString hdrName = hdr.BeforeFirst(':', &hdrValue).Strip(wxString::trailing);
hdrName.MakeUpper();
m_headers[hdrName] = hdrValue.Strip(wxString::leading);
}
return size;
}
wxFileOffset wxWebResponseCURL::GetContentLength() const
{
#if CURL_AT_LEAST_VERSION(7, 55, 0)
curl_off_t len = 0;
curl_easy_getinfo(GetHandle(), CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &len);
return len;
#else
double len = 0;
curl_easy_getinfo(GetHandle(), CURLINFO_CONTENT_LENGTH_DOWNLOAD, &len);
return (wxFileOffset)len;
#endif
}
wxString wxWebResponseCURL::GetURL() const
{
char* urlp = NULL;
curl_easy_getinfo(GetHandle(), CURLINFO_EFFECTIVE_URL, &urlp);
// While URLs should contain ASCII characters only as per
// https://tools.ietf.org/html/rfc3986#section-2 we still want to avoid
// losing data if they somehow contain something else but are not in UTF-8
// by interpreting it as Latin-1.
return wxString::From8BitData(urlp);
}
wxString wxWebResponseCURL::GetHeader(const wxString& name) const
{
wxWebRequestHeaderMap::const_iterator it = m_headers.find(name.Upper());
if ( it != m_headers.end() )
return it->second;
else
return wxString();
}
int wxWebResponseCURL::GetStatus() const
{
long status = 0;
curl_easy_getinfo(GetHandle(), CURLINFO_RESPONSE_CODE, &status);
return status;
}
//
// wxWebRequestCURL
//
static size_t wxCURLRead(char *buffer, size_t size, size_t nitems, void *userdata)
{
wxCHECK_MSG( userdata, 0, "invalid curl read callback data" );
return static_cast<wxWebRequestCURL*>(userdata)->CURLOnRead(buffer, size * nitems);
}
wxWebRequestCURL::wxWebRequestCURL(wxWebSession & session,
wxWebSessionCURL& sessionImpl,
wxEvtHandler* handler,
const wxString & url,
int id):
wxWebRequestImpl(session, sessionImpl, handler, id),
m_sessionImpl(sessionImpl)
{
m_headerList = NULL;
m_handle = curl_easy_init();
if ( !m_handle )
{
wxStrlcpy(m_errorBuffer, "libcurl initialization failed", CURL_ERROR_SIZE);
return;
}
// Set error buffer to get more detailed CURL status
m_errorBuffer[0] = '\0';
curl_easy_setopt(m_handle, CURLOPT_ERRORBUFFER, m_errorBuffer);
// Set this request in the private pointer
curl_easy_setopt(m_handle, CURLOPT_PRIVATE, static_cast<void*>(this));
// Set URL to handle: note that we must use wxURI to escape characters not
// allowed in the URLs correctly (URL API is only available in libcurl
// since the relatively recent v7.62.0, so we don't want to rely on it).
curl_easy_setopt(m_handle, CURLOPT_URL, wxURI(url).BuildURI().utf8_str().data());
// Set callback functions
curl_easy_setopt(m_handle, CURLOPT_WRITEFUNCTION, wxCURLWriteData);
curl_easy_setopt(m_handle, CURLOPT_HEADERFUNCTION, wxCURLHeader);
curl_easy_setopt(m_handle, CURLOPT_READFUNCTION, wxCURLRead);
curl_easy_setopt(m_handle, CURLOPT_READDATA, static_cast<void*>(this));
// Enable gzip, etc decompression
curl_easy_setopt(m_handle, CURLOPT_ACCEPT_ENCODING, "");
// Enable redirection handling
curl_easy_setopt(m_handle, CURLOPT_FOLLOWLOCATION, 1L);
// Limit redirect to HTTP
curl_easy_setopt(m_handle, CURLOPT_REDIR_PROTOCOLS,
CURLPROTO_HTTP | CURLPROTO_HTTPS);
// Enable all supported authentication methods
curl_easy_setopt(m_handle, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
curl_easy_setopt(m_handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
}
wxWebRequestCURL::~wxWebRequestCURL()
{
DestroyHeaderList();
curl_easy_cleanup(m_handle);
}
void wxWebRequestCURL::Start()
{
m_response.reset(new wxWebResponseCURL(*this));
if ( m_dataSize )
{
if ( m_method.empty() || m_method.CmpNoCase("POST") == 0 )
{
curl_easy_setopt(m_handle, CURLOPT_POSTFIELDSIZE_LARGE,
static_cast<curl_off_t>(m_dataSize));
curl_easy_setopt(m_handle, CURLOPT_POST, 1L);
}
else if ( m_method.CmpNoCase("PUT") == 0 )
{
curl_easy_setopt(m_handle, CURLOPT_UPLOAD, 1L);
curl_easy_setopt(m_handle, CURLOPT_INFILESIZE_LARGE,
static_cast<curl_off_t>(m_dataSize));
}
else
{
wxFAIL_MSG(wxString::Format(
"Supplied data is ignored when using method %s", m_method
));
}
}
if ( m_method.CmpNoCase("HEAD") == 0 )
{
curl_easy_setopt(m_handle, CURLOPT_NOBODY, 1L);
}
else if ( !m_method.empty() )
{
curl_easy_setopt(m_handle, CURLOPT_CUSTOMREQUEST,
static_cast<const char*>(m_method.mb_str()));
}
for ( wxWebRequestHeaderMap::const_iterator it = m_headers.begin();
it != m_headers.end(); ++it )
{
// TODO: We need to implement RFC 2047 encoding here instead of blindly
// sending UTF-8 which is against the standard.
wxString hdrStr = wxString::Format("%s: %s", it->first, it->second);
m_headerList = curl_slist_append(m_headerList, hdrStr.utf8_str());
}
curl_easy_setopt(m_handle, CURLOPT_HTTPHEADER, m_headerList);
if ( IsPeerVerifyDisabled() )
curl_easy_setopt(m_handle, CURLOPT_SSL_VERIFYPEER, 0);
StartRequest();
}
bool wxWebRequestCURL::StartRequest()
{
m_bytesSent = 0;
if ( !m_sessionImpl.StartRequest(*this) )
{
SetState(wxWebRequest::State_Failed);
return false;
}
return true;
}
void wxWebRequestCURL::DoCancel()
{
m_sessionImpl.CancelRequest(this);
}
void wxWebRequestCURL::HandleCompletion()
{
int status = m_response ? m_response->GetStatus() : 0;
if ( status == 0 )
{
SetState(wxWebRequest::State_Failed, GetError());
}
else if ( status == 401 || status == 407 )
{
m_authChallenge.reset(new wxWebAuthChallengeCURL(
(status == 407) ? wxWebAuthChallenge::Source_Proxy : wxWebAuthChallenge::Source_Server, *this));
SetState(wxWebRequest::State_Unauthorized, m_response->GetStatusText());
}
else
{
SetFinalStateFromStatus();
}
}
wxString wxWebRequestCURL::GetError() const
{
// We don't know what encoding is used for libcurl errors, so do whatever
// is needed in order to interpret this data at least somehow.
return wxString(m_errorBuffer, wxConvWhateverWorks);
}
size_t wxWebRequestCURL::CURLOnRead(char* buffer, size_t size)
{
if ( m_dataStream )
{
m_dataStream->Read(buffer, size);
size_t readSize = m_dataStream->LastRead();
m_bytesSent += readSize;
return readSize;
}
else
return 0;
}
void wxWebRequestCURL::DestroyHeaderList()
{
if ( m_headerList )
{
curl_slist_free_all(m_headerList);
m_headerList = NULL;
}
}
wxFileOffset wxWebRequestCURL::GetBytesSent() const
{
return m_bytesSent;
}
wxFileOffset wxWebRequestCURL::GetBytesExpectedToSend() const
{
return m_dataSize;
}
//
// wxWebAuthChallengeCURL
//
wxWebAuthChallengeCURL::wxWebAuthChallengeCURL(wxWebAuthChallenge::Source source,
wxWebRequestCURL& request) :
wxWebAuthChallengeImpl(source),
m_request(request)
{
}
void wxWebAuthChallengeCURL::SetCredentials(const wxWebCredentials& cred)
{
const wxSecretString authStr =
wxString::Format
(
"%s:%s",
cred.GetUser(),
static_cast<const wxString&>(wxSecretString(cred.GetPassword()))
);
curl_easy_setopt(m_request.GetHandle(),
(GetSource() == wxWebAuthChallenge::Source_Proxy) ? CURLOPT_PROXYUSERPWD : CURLOPT_USERPWD,
authStr.utf8_str().data());
m_request.StartRequest();
}
//
// wxWebSessionCURL
//
int wxWebSessionCURL::ms_activeSessions = 0;
wxWebSessionCURL::wxWebSessionCURL() :
m_handle(NULL),
m_condition(m_mutex),
m_shuttingDown(false)
{
// Initialize CURL globally if no sessions are active
if ( ms_activeSessions == 0 )
{
if ( curl_global_init(CURL_GLOBAL_ALL) )
wxLogError(_("libcurl could not be initialized"));
}
ms_activeSessions++;
}
wxWebSessionCURL::~wxWebSessionCURL()
{
{
// Notify the work thread
wxMutexLocker lock(m_mutex);
m_shuttingDown = true;
m_condition.Signal();
}
// Wait for work thread to finish
if ( GetThread() && GetThread()->IsRunning() )
GetThread()->Wait(wxTHREAD_WAIT_BLOCK);
if ( m_handle )
curl_multi_cleanup(m_handle);
// Global CURL cleanup if this is the last session
--ms_activeSessions;
if ( ms_activeSessions == 0 )
curl_global_cleanup();
}
wxWebRequestImplPtr
wxWebSessionCURL::CreateRequest(wxWebSession& session,
wxEvtHandler* handler,
const wxString& url,
int id)
{
// Allocate our handle on demand.
if ( !m_handle )
{
m_handle = curl_multi_init();
if ( !m_handle )
{
wxLogDebug("curl_multi_init() failed");
return wxWebRequestImplPtr();
}
}
return wxWebRequestImplPtr(new wxWebRequestCURL(session, *this, handler, url, id));
}
static CURLMcode wx_curl_multi_wait(CURLM *multi_handle, int timeout_ms,
int *ret)
{
// since libcurl 7.28.0 the curl_multi_wait method is more convient than
// calling multiple curl_multi_... methods.
// When support for older libcurl versions is dropped this wrapper can be
// removed.
#if wxCURL_HAVE_MULTI_WAIT
return curl_multi_wait(multi_handle, NULL, 0, timeout_ms, ret);
#else
wxASSERT(ret != NULL);
fd_set fdread;
fd_set fdwrite;
fd_set fdexcep;
timeval timeout;
long curl_timeo;
curl_multi_timeout(multi_handle, &curl_timeo);
if ( curl_timeo < 0 )
curl_timeo = timeout_ms;
timeout.tv_sec = curl_timeo / 1000;
timeout.tv_usec = (curl_timeo % 1000) * 1000;
FD_ZERO(&fdread);
FD_ZERO(&fdwrite);
FD_ZERO(&fdexcep);
curl_multi_fdset(multi_handle, &fdread, &fdwrite, &fdexcep, ret);
if ( *ret == -1 )
return CURLM_OK;
else if ( select(*ret + 1, &fdread, &fdwrite, &fdexcep, &timeout) == -1 )
return CURLM_BAD_SOCKET;
else
return CURLM_OK;
#endif
}
wxThread::ExitCode wxWebSessionCURL::Entry()
{
// This mutex will be unlocked only while we're waiting on the condition.
wxMutexLocker lock(m_mutex);
int activeRequests = -1;
int repeats = 0;
while ( activeRequests )
{
// Handle cancelled requests
{
wxCriticalSectionLocker cancelledLock(m_cancelled.cs);
while ( !m_cancelled.requests.empty() )
{
wxObjectDataPtr<wxWebRequestCURL> request(m_cancelled.requests.back());
m_cancelled.requests.pop_back();
curl_multi_remove_handle(m_handle, request->GetHandle());
request->SetState(wxWebRequest::State_Cancelled);
}
}
// Instruct CURL to work on requests
curl_multi_perform(m_handle, &activeRequests);
// Process CURL message queue
int msgQueueCount;
while ( CURLMsg* msg = curl_multi_info_read(m_handle, &msgQueueCount) )
{
if ( msg->msg == CURLMSG_DONE )
{
wxWebRequestCURL* request;
curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &request);
curl_multi_remove_handle(m_handle, msg->easy_handle);
request->HandleCompletion();
}
}
if ( activeRequests )
{
// Wait for CURL work to finish
int numfds;
wx_curl_multi_wait(m_handle, 500, &numfds);
if ( !numfds )
{
repeats++; // count number of repeated zero numfds
if ( repeats > 1 )
wxMilliSleep(100);
}
else
repeats = 0;
}
else
{
// Wait for new requests or shutdown of the session
m_condition.Wait();
if ( !m_shuttingDown )
activeRequests = -1;
}
}
return (wxThread::ExitCode)0;
}
bool wxWebSessionCURL::StartRequest(wxWebRequestCURL & request)
{
// Add request easy handle to multi handle
curl_multi_add_handle(m_handle, request.GetHandle());
// Create and start session thread if not yet running
if ( !GetThread() )
{
if ( CreateThread() )
return false;
if ( GetThread()->Run() != wxTHREAD_NO_ERROR )
return false;
}
request.SetState(wxWebRequest::State_Active);
// Signal the worker thread to resume work
wxMutexLocker lock(m_mutex);
m_condition.Signal();
return true;
}
void wxWebSessionCURL::CancelRequest(wxWebRequestCURL* request)
{
// Add the request to a list of threads that will be removed from the curl
// multi handle in the worker thread
wxCriticalSectionLocker lock(m_cancelled.cs);
request->IncRef();
m_cancelled.requests.push_back(wxObjectDataPtr<wxWebRequestCURL>(request));
}
wxVersionInfo wxWebSessionCURL::GetLibraryVersionInfo()
{
const curl_version_info_data* vi = curl_version_info(CURLVERSION_NOW);
wxString desc = wxString::Format("libcurl/%s", vi->version);
if (vi->ssl_version[0])
desc += " " + wxString(vi->ssl_version);
return wxVersionInfo("libcurl",
vi->version_num >> 16 & 0xff,
vi->version_num >> 8 & 0xff,
vi->version_num & 0xff,
desc);
}
#endif // wxUSE_WEBREQUEST_CURL