1 ///////////////////////////////////////////////////////////////////////////////
2 // Name:        src/common/webrequest_curl.h
3 // Purpose:     wxWebRequest implementation using libcurl
4 // Author:      Tobias Taschner
5 // Created:     2018-10-25
6 // Copyright:   (c) 2018 wxWidgets development team
7 // Licence:     wxWindows licence
8 ///////////////////////////////////////////////////////////////////////////////
9 
10 // For compilers that support precompilation, includes "wx.h".
11 #include "wx/wxprec.h"
12 
13 #include "wx/webrequest.h"
14 
15 #if wxUSE_WEBREQUEST && wxUSE_WEBREQUEST_CURL
16 
17 #include "wx/private/webrequest_curl.h"
18 
19 #ifndef WX_PRECOMP
20     #include "wx/log.h"
21     #include "wx/translation.h"
22     #include "wx/utils.h"
23 #endif
24 
25 #include "wx/uri.h"
26 #include "wx/private/socket.h"
27 #include "wx/evtloop.h"
28 
29 #ifdef __WINDOWS__
30     #include "wx/hashset.h"
31     #include "wx/msw/wrapwin.h"
32 #else
33     #include "wx/evtloopsrc.h"
34     #include "wx/evtloop.h"
35 #endif
36 
37 
38 // Define symbols that might be missing from older libcurl headers
39 #ifndef CURL_AT_LEAST_VERSION
40 #define CURL_VERSION_BITS(x,y,z) ((x)<<16|(y)<<8|z)
41 #define CURL_AT_LEAST_VERSION(x,y,z) \
42   (LIBCURL_VERSION_NUM >= CURL_VERSION_BITS(x, y, z))
43 #endif
44 
45 // The new name was introduced in curl 7.21.6.
46 #ifndef CURLOPT_ACCEPT_ENCODING
47     #define CURLOPT_ACCEPT_ENCODING CURLOPT_ENCODING
48 #endif
49 
50 //
51 // wxWebResponseCURL
52 //
53 
wxCURLWriteData(void * buffer,size_t size,size_t nmemb,void * userdata)54 static size_t wxCURLWriteData(void* buffer, size_t size, size_t nmemb, void* userdata)
55 {
56     wxCHECK_MSG( userdata, 0, "invalid curl write callback data" );
57 
58     return static_cast<wxWebResponseCURL*>(userdata)->CURLOnWrite(buffer, size * nmemb);
59 }
60 
wxCURLHeader(char * buffer,size_t size,size_t nitems,void * userdata)61 static size_t wxCURLHeader(char *buffer, size_t size, size_t nitems, void *userdata)
62 {
63     wxCHECK_MSG( userdata, 0, "invalid curl header callback data" );
64 
65     return static_cast<wxWebResponseCURL*>(userdata)->CURLOnHeader(buffer, size * nitems);
66 }
67 
wxCURLXferInfo(void * clientp,curl_off_t dltotal,curl_off_t WXUNUSED (dlnow),curl_off_t WXUNUSED (ultotal),curl_off_t WXUNUSED (ulnow))68 static int wxCURLXferInfo(void* clientp, curl_off_t dltotal,
69                           curl_off_t WXUNUSED(dlnow),
70                           curl_off_t WXUNUSED(ultotal),
71                           curl_off_t WXUNUSED(ulnow))
72 {
73     wxCHECK_MSG( clientp, 0, "invalid curl progress callback data" );
74 
75     wxWebResponseCURL* response = reinterpret_cast<wxWebResponseCURL*>(clientp);
76     return response->CURLOnProgress(dltotal);
77 }
78 
wxCURLProgress(void * clientp,double dltotal,double dlnow,double ultotal,double ulnow)79 static int wxCURLProgress(void* clientp, double dltotal, double dlnow,
80                           double ultotal, double ulnow)
81 {
82     return wxCURLXferInfo(clientp, static_cast<curl_off_t>(dltotal),
83                           static_cast<curl_off_t>(dlnow),
84                           static_cast<curl_off_t>(ultotal),
85                           static_cast<curl_off_t>(ulnow));
86 }
87 
wxWebResponseCURL(wxWebRequestCURL & request)88 wxWebResponseCURL::wxWebResponseCURL(wxWebRequestCURL& request) :
89     wxWebResponseImpl(request)
90 {
91     m_knownDownloadSize = 0;
92 
93     curl_easy_setopt(GetHandle(), CURLOPT_WRITEDATA, static_cast<void*>(this));
94     curl_easy_setopt(GetHandle(), CURLOPT_HEADERDATA, static_cast<void*>(this));
95 
96  // Set the progress callback.
97     #if CURL_AT_LEAST_VERSION(7, 32, 0)
98         if ( wxWebSessionCURL::CurlRuntimeAtLeastVersion(7, 32, 0) )
99         {
100             curl_easy_setopt(GetHandle(), CURLOPT_XFERINFOFUNCTION,
101                              wxCURLXferInfo);
102             curl_easy_setopt(GetHandle(), CURLOPT_XFERINFODATA,
103                              static_cast<void*>(this));
104         }
105         else
106     #endif
107         {
108             curl_easy_setopt(GetHandle(), CURLOPT_PROGRESSFUNCTION,
109                              wxCURLProgress);
110             curl_easy_setopt(GetHandle(), CURLOPT_PROGRESSDATA,
111                              static_cast<void*>(this));
112         }
113 
114     // Have curl call the progress callback.
115     curl_easy_setopt(GetHandle(), CURLOPT_NOPROGRESS, 0L);
116 
117     Init();
118 }
119 
CURLOnWrite(void * buffer,size_t size)120 size_t wxWebResponseCURL::CURLOnWrite(void* buffer, size_t size)
121 {
122     void* buf = GetDataBuffer(size);
123     memcpy(buf, buffer, size);
124     ReportDataReceived(size);
125     return size;
126 }
127 
CURLOnHeader(const char * buffer,size_t size)128 size_t wxWebResponseCURL::CURLOnHeader(const char * buffer, size_t size)
129 {
130     // HTTP headers are supposed to only contain ASCII data, so any encoding
131     // should work here, but use Latin-1 for compatibility with some servers
132     // that send it directly and to at least avoid losing data entirely when
133     // the current encoding is UTF-8 but the input doesn't decode correctly.
134     wxString hdr = wxString::From8BitData(buffer, size);
135     hdr.Trim();
136 
137     if ( hdr.StartsWith("HTTP/") )
138     {
139         // First line of the headers contains status text after
140         // version and status
141         m_statusText = hdr.AfterFirst(' ').AfterFirst(' ');
142         m_headers.clear();
143     }
144     else if ( !hdr.empty() )
145     {
146         wxString hdrValue;
147         wxString hdrName = hdr.BeforeFirst(':', &hdrValue).Strip(wxString::trailing);
148         hdrName.MakeUpper();
149         m_headers[hdrName] = hdrValue.Strip(wxString::leading);
150     }
151 
152     return size;
153 }
154 
CURLOnProgress(curl_off_t total)155 int wxWebResponseCURL::CURLOnProgress(curl_off_t total)
156 {
157     if ( m_knownDownloadSize != total )
158     {
159         if ( m_request.GetStorage() == wxWebRequest::Storage_Memory )
160         {
161             PreAllocBuffer(static_cast<size_t>(total));
162         }
163         m_knownDownloadSize = total;
164     }
165 
166     return 0;
167 }
168 
GetContentLength() const169 wxFileOffset wxWebResponseCURL::GetContentLength() const
170 {
171 #if CURL_AT_LEAST_VERSION(7, 55, 0)
172     curl_off_t len = 0;
173     curl_easy_getinfo(GetHandle(), CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &len);
174     return len;
175 #else
176     double len = 0;
177     curl_easy_getinfo(GetHandle(), CURLINFO_CONTENT_LENGTH_DOWNLOAD, &len);
178     return (wxFileOffset)len;
179 #endif
180 }
181 
GetURL() const182 wxString wxWebResponseCURL::GetURL() const
183 {
184     char* urlp = NULL;
185     curl_easy_getinfo(GetHandle(), CURLINFO_EFFECTIVE_URL, &urlp);
186 
187     // While URLs should contain ASCII characters only as per
188     // https://tools.ietf.org/html/rfc3986#section-2 we still want to avoid
189     // losing data if they somehow contain something else but are not in UTF-8
190     // by interpreting it as Latin-1.
191     return wxString::From8BitData(urlp);
192 }
193 
GetHeader(const wxString & name) const194 wxString wxWebResponseCURL::GetHeader(const wxString& name) const
195 {
196     wxWebRequestHeaderMap::const_iterator it = m_headers.find(name.Upper());
197     if ( it != m_headers.end() )
198         return it->second;
199     else
200         return wxString();
201 }
202 
GetStatus() const203 int wxWebResponseCURL::GetStatus() const
204 {
205     long status = 0;
206     curl_easy_getinfo(GetHandle(), CURLINFO_RESPONSE_CODE, &status);
207     return status;
208 }
209 
210 //
211 // wxWebRequestCURL
212 //
213 
wxCURLRead(char * buffer,size_t size,size_t nitems,void * userdata)214 static size_t wxCURLRead(char *buffer, size_t size, size_t nitems, void *userdata)
215 {
216     wxCHECK_MSG( userdata, 0, "invalid curl read callback data" );
217 
218     return static_cast<wxWebRequestCURL*>(userdata)->CURLOnRead(buffer, size * nitems);
219 }
220 
wxWebRequestCURL(wxWebSession & session,wxWebSessionCURL & sessionImpl,wxEvtHandler * handler,const wxString & url,int id)221 wxWebRequestCURL::wxWebRequestCURL(wxWebSession & session,
222                                    wxWebSessionCURL& sessionImpl,
223                                    wxEvtHandler* handler,
224                                    const wxString & url,
225                                    int id):
226     wxWebRequestImpl(session, sessionImpl, handler, id),
227     m_sessionImpl(sessionImpl)
228 {
229     m_headerList = NULL;
230 
231     m_handle = curl_easy_init();
232     if ( !m_handle )
233     {
234         wxStrlcpy(m_errorBuffer, "libcurl initialization failed", CURL_ERROR_SIZE);
235         return;
236     }
237 
238     // Set error buffer to get more detailed CURL status
239     m_errorBuffer[0] = '\0';
240     curl_easy_setopt(m_handle, CURLOPT_ERRORBUFFER, m_errorBuffer);
241     // Set URL to handle: note that we must use wxURI to escape characters not
242     // allowed in the URLs correctly (URL API is only available in libcurl
243     // since the relatively recent v7.62.0, so we don't want to rely on it).
244     curl_easy_setopt(m_handle, CURLOPT_URL, wxURI(url).BuildURI().utf8_str().data());
245     // Set callback functions
246     curl_easy_setopt(m_handle, CURLOPT_WRITEFUNCTION, wxCURLWriteData);
247     curl_easy_setopt(m_handle, CURLOPT_HEADERFUNCTION, wxCURLHeader);
248     curl_easy_setopt(m_handle, CURLOPT_READFUNCTION, wxCURLRead);
249     curl_easy_setopt(m_handle, CURLOPT_READDATA, static_cast<void*>(this));
250     // Enable gzip, etc decompression
251     curl_easy_setopt(m_handle, CURLOPT_ACCEPT_ENCODING, "");
252     // Enable redirection handling
253     curl_easy_setopt(m_handle, CURLOPT_FOLLOWLOCATION, 1L);
254     // Limit redirect to HTTP
255     curl_easy_setopt(m_handle, CURLOPT_REDIR_PROTOCOLS,
256         CURLPROTO_HTTP | CURLPROTO_HTTPS);
257     // Enable all supported authentication methods
258     curl_easy_setopt(m_handle, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
259     curl_easy_setopt(m_handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
260 }
261 
~wxWebRequestCURL()262 wxWebRequestCURL::~wxWebRequestCURL()
263 {
264     DestroyHeaderList();
265     m_sessionImpl.RequestHasTerminated(this);
266 }
267 
Start()268 void wxWebRequestCURL::Start()
269 {
270     m_response.reset(new wxWebResponseCURL(*this));
271 
272     if ( m_dataSize )
273     {
274         if ( m_method.empty() || m_method.CmpNoCase("POST") == 0 )
275         {
276             curl_easy_setopt(m_handle, CURLOPT_POSTFIELDSIZE_LARGE,
277                 static_cast<curl_off_t>(m_dataSize));
278             curl_easy_setopt(m_handle, CURLOPT_POST, 1L);
279         }
280         else if ( m_method.CmpNoCase("PUT") == 0 )
281         {
282             curl_easy_setopt(m_handle, CURLOPT_UPLOAD, 1L);
283             curl_easy_setopt(m_handle, CURLOPT_INFILESIZE_LARGE,
284                 static_cast<curl_off_t>(m_dataSize));
285         }
286         else
287         {
288             wxFAIL_MSG(wxString::Format(
289                 "Supplied data is ignored when using method %s", m_method
290             ));
291         }
292     }
293 
294     if ( m_method.CmpNoCase("HEAD") == 0 )
295     {
296         curl_easy_setopt(m_handle, CURLOPT_NOBODY, 1L);
297     }
298     else if ( !m_method.empty() )
299     {
300         curl_easy_setopt(m_handle, CURLOPT_CUSTOMREQUEST,
301             static_cast<const char*>(m_method.mb_str()));
302     }
303 
304     for ( wxWebRequestHeaderMap::const_iterator it = m_headers.begin();
305         it != m_headers.end(); ++it )
306     {
307         // TODO: We need to implement RFC 2047 encoding here instead of blindly
308         //       sending UTF-8 which is against the standard.
309         wxString hdrStr = wxString::Format("%s: %s", it->first, it->second);
310         m_headerList = curl_slist_append(m_headerList, hdrStr.utf8_str());
311     }
312     curl_easy_setopt(m_handle, CURLOPT_HTTPHEADER, m_headerList);
313 
314     if ( IsPeerVerifyDisabled() )
315         curl_easy_setopt(m_handle, CURLOPT_SSL_VERIFYPEER, 0);
316 
317     StartRequest();
318 }
319 
StartRequest()320 bool wxWebRequestCURL::StartRequest()
321 {
322     m_bytesSent = 0;
323 
324     if ( !m_sessionImpl.StartRequest(*this) )
325     {
326         SetState(wxWebRequest::State_Failed);
327         return false;
328     }
329 
330     return true;
331 }
332 
DoCancel()333 void wxWebRequestCURL::DoCancel()
334 {
335     m_sessionImpl.CancelRequest(this);
336 }
337 
HandleCompletion()338 void wxWebRequestCURL::HandleCompletion()
339 {
340     int status = m_response ? m_response->GetStatus() : 0;
341 
342     if ( status == 0 )
343     {
344         SetState(wxWebRequest::State_Failed, GetError());
345     }
346     else if ( status == 401 || status == 407 )
347     {
348         m_authChallenge.reset(new wxWebAuthChallengeCURL(
349             (status == 407) ? wxWebAuthChallenge::Source_Proxy : wxWebAuthChallenge::Source_Server, *this));
350         SetState(wxWebRequest::State_Unauthorized, m_response->GetStatusText());
351     }
352     else
353     {
354         SetFinalStateFromStatus();
355     }
356 }
357 
GetError() const358 wxString wxWebRequestCURL::GetError() const
359 {
360     // We don't know what encoding is used for libcurl errors, so do whatever
361     // is needed in order to interpret this data at least somehow.
362     return wxString(m_errorBuffer, wxConvWhateverWorks);
363 }
364 
CURLOnRead(char * buffer,size_t size)365 size_t wxWebRequestCURL::CURLOnRead(char* buffer, size_t size)
366 {
367     if ( m_dataStream )
368     {
369         m_dataStream->Read(buffer, size);
370         size_t readSize = m_dataStream->LastRead();
371         m_bytesSent += readSize;
372         return readSize;
373     }
374     else
375         return 0;
376 }
377 
DestroyHeaderList()378 void wxWebRequestCURL::DestroyHeaderList()
379 {
380     if ( m_headerList )
381     {
382         curl_slist_free_all(m_headerList);
383         m_headerList = NULL;
384     }
385 }
386 
GetBytesSent() const387 wxFileOffset wxWebRequestCURL::GetBytesSent() const
388 {
389     return m_bytesSent;
390 }
391 
GetBytesExpectedToSend() const392 wxFileOffset wxWebRequestCURL::GetBytesExpectedToSend() const
393 {
394     return m_dataSize;
395 }
396 
397 //
398 // wxWebAuthChallengeCURL
399 //
400 
wxWebAuthChallengeCURL(wxWebAuthChallenge::Source source,wxWebRequestCURL & request)401 wxWebAuthChallengeCURL::wxWebAuthChallengeCURL(wxWebAuthChallenge::Source source,
402                                                wxWebRequestCURL& request) :
403     wxWebAuthChallengeImpl(source),
404     m_request(request)
405 {
406 }
407 
SetCredentials(const wxWebCredentials & cred)408 void wxWebAuthChallengeCURL::SetCredentials(const wxWebCredentials& cred)
409 {
410     const wxSecretString authStr =
411         wxString::Format
412         (
413             "%s:%s",
414             cred.GetUser(),
415             static_cast<const wxString&>(wxSecretString(cred.GetPassword()))
416         );
417     curl_easy_setopt(m_request.GetHandle(),
418         (GetSource() == wxWebAuthChallenge::Source_Proxy) ? CURLOPT_PROXYUSERPWD : CURLOPT_USERPWD,
419         authStr.utf8_str().data());
420     m_request.StartRequest();
421 }
422 
423 //
424 // SocketPoller - a helper class for wxWebSessionCURL
425 //
426 
427 wxDECLARE_EVENT(wxEVT_SOCKET_POLLER_RESULT, wxThreadEvent);
428 
429 class SocketPollerImpl;
430 
431 class SocketPoller
432 {
433 public:
434     enum PollAction
435     {
436         INVALID_ACTION = 0x00,
437         POLL_FOR_READ = 0x01,
438         POLL_FOR_WRITE = 0x02
439     };
440 
441     enum Result
442     {
443         INVALID_RESULT = 0x00,
444         READY_FOR_READ = 0x01,
445         READY_FOR_WRITE = 0x02,
446         HAS_ERROR = 0x04
447     };
448 
449     SocketPoller(wxEvtHandler*);
450     ~SocketPoller();
451     bool StartPolling(wxSOCKET_T, int);
452     void StopPolling(wxSOCKET_T);
453     void ResumePolling(wxSOCKET_T);
454 
455 private:
456     SocketPollerImpl* m_impl;
457 };
458 
459 wxDEFINE_EVENT(wxEVT_SOCKET_POLLER_RESULT, wxThreadEvent);
460 
461 class SocketPollerImpl
462 {
463 public:
~SocketPollerImpl()464     virtual ~SocketPollerImpl(){}
465     virtual bool StartPolling(wxSOCKET_T, int) = 0;
466     virtual void StopPolling(wxSOCKET_T) = 0;
467     virtual void ResumePolling(wxSOCKET_T) = 0;
468 
469     static SocketPollerImpl* Create(wxEvtHandler*);
470 };
471 
SocketPoller(wxEvtHandler * hndlr)472 SocketPoller::SocketPoller(wxEvtHandler* hndlr)
473 {
474     m_impl = SocketPollerImpl::Create(hndlr);
475 }
476 
~SocketPoller()477 SocketPoller::~SocketPoller()
478 {
479     delete m_impl;
480 }
481 
StartPolling(wxSOCKET_T sock,int pollAction)482 bool SocketPoller::StartPolling(wxSOCKET_T sock, int pollAction)
483 {
484     return m_impl->StartPolling(sock, pollAction);
485 }
StopPolling(wxSOCKET_T sock)486 void SocketPoller::StopPolling(wxSOCKET_T sock)
487 {
488     m_impl->StopPolling(sock);
489 }
490 
ResumePolling(wxSOCKET_T sock)491 void SocketPoller::ResumePolling(wxSOCKET_T sock)
492 {
493     m_impl->ResumePolling(sock);
494 }
495 
496 #ifdef __WINDOWS__
497 
498 class WinSock1SocketPoller: public SocketPollerImpl
499 {
500 public:
501     WinSock1SocketPoller(wxEvtHandler*);
502     virtual ~WinSock1SocketPoller();
503     virtual bool StartPolling(wxSOCKET_T, int) wxOVERRIDE;
504     virtual void StopPolling(wxSOCKET_T) wxOVERRIDE;
505     virtual void ResumePolling(wxSOCKET_T) wxOVERRIDE;
506 
507 private:
508     static LRESULT CALLBACK MsgProc(HWND hwnd, WXUINT uMsg, WXWPARAM wParam,
509                                     WXLPARAM lParam);
510     static const WXUINT SOCKET_MESSAGE;
511 
512     WX_DECLARE_HASH_SET(wxSOCKET_T, wxIntegerHash, wxIntegerEqual, SocketSet);
513 
514     SocketSet m_polledSockets;
515     WXHWND m_hwnd;
516 };
517 
518 const WXUINT WinSock1SocketPoller::SOCKET_MESSAGE = WM_USER + 1;
519 
WinSock1SocketPoller(wxEvtHandler * hndlr)520 WinSock1SocketPoller::WinSock1SocketPoller(wxEvtHandler* hndlr)
521 {
522     // Initialize winsock in case it's not already done.
523     WORD wVersionRequested = MAKEWORD(1,1);
524     WSADATA wsaData;
525     WSAStartup(wVersionRequested, &wsaData);
526 
527     // Create a dummy message only window.
528     m_hwnd = CreateWindowEx(
529         0,              //DWORD     dwExStyle,
530         TEXT("STATIC"), //LPCSTR    lpClassName,
531         NULL,           //LPCSTR    lpWindowName,
532         0,              //DWORD     dwStyle,
533         0,              //int       X,
534         0,              //int       Y,
535         0,              //int       nWidth,
536         0,              //int       nHeight,
537         HWND_MESSAGE,   //HWND      hWndParent,
538         NULL,           //HMENU     hMenu,
539         NULL,           //HINSTANCE hInstance,
540         NULL            //LPVOID    lpParam
541     );
542 
543     if ( m_hwnd == NULL )
544     {
545         wxLogError("Unable to create message window for WinSock1SocketPoller");
546         return;
547     }
548 
549     // Set the event handler to be the message window's user data. Also set the
550     // message window to use our MsgProc to process messages it receives.
551     SetWindowLongPtr(m_hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(hndlr));
552     SetWindowLongPtr(m_hwnd, GWLP_WNDPROC,
553                      reinterpret_cast<LONG_PTR>(WinSock1SocketPoller::MsgProc));
554 }
555 
~WinSock1SocketPoller()556 WinSock1SocketPoller::~WinSock1SocketPoller()
557 {
558     // Stop monitoring any leftover sockets.
559     for ( SocketSet::iterator it = m_polledSockets.begin() ;
560           it != m_polledSockets.end() ; ++it )
561     {
562         WSAAsyncSelect(*it, m_hwnd, 0, 0);
563     }
564 
565     // Close the message window.
566     if ( m_hwnd )
567     {
568         CloseWindow(m_hwnd);
569     }
570 
571     // Cleanup winsock.
572     WSACleanup();
573 }
574 
StartPolling(wxSOCKET_T sock,int pollAction)575 bool WinSock1SocketPoller::StartPolling(wxSOCKET_T sock, int pollAction)
576 {
577     StopPolling(sock);
578 
579     // Convert pollAction to a flag that can be used by winsock.
580     int winActions = 0;
581 
582     if ( pollAction & SocketPoller::POLL_FOR_READ )
583     {
584         winActions |= FD_READ;
585     }
586 
587     if ( pollAction & SocketPoller::POLL_FOR_WRITE )
588     {
589         winActions |= FD_WRITE;
590     }
591 
592     // Have winsock send a message to our window whenever activity is
593     // detected on the socket.
594     WSAAsyncSelect(sock, m_hwnd, SOCKET_MESSAGE, winActions);
595 
596     m_polledSockets.insert(sock);
597     return true;
598 }
599 
StopPolling(wxSOCKET_T sock)600 void WinSock1SocketPoller::StopPolling(wxSOCKET_T sock)
601 {
602     SocketSet::iterator it = m_polledSockets.find(sock);
603 
604     if ( it != m_polledSockets.end() )
605     {
606         // Stop sending messages when there is activity on the socket.
607         WSAAsyncSelect(sock, m_hwnd, 0, 0);
608         m_polledSockets.erase(it);
609     }
610 }
611 
ResumePolling(wxSOCKET_T WXUNUSED (sock))612 void WinSock1SocketPoller::ResumePolling(wxSOCKET_T WXUNUSED(sock))
613 {
614 }
615 
MsgProc(WXHWND hwnd,WXUINT uMsg,WXWPARAM wParam,WXLPARAM lParam)616 LRESULT CALLBACK WinSock1SocketPoller::MsgProc(WXHWND hwnd, WXUINT uMsg,
617                                                WXWPARAM wParam, WXLPARAM lParam)
618 {
619     // We only handle 1 message - the message we told winsock to send when
620     // it notices activity on sockets we are monitoring.
621 
622     if ( uMsg == SOCKET_MESSAGE )
623     {
624         // Extract the result any any errors from lParam.
625         int winResult = LOWORD(lParam);
626         int error = HIWORD(lParam);
627 
628         // Convert the result/errors to a SocketPoller::Result flag.
629         int pollResult = 0;
630 
631         if ( winResult & FD_READ )
632         {
633             pollResult |= SocketPoller::READY_FOR_READ;
634         }
635 
636         if ( winResult & FD_WRITE )
637         {
638             pollResult |= SocketPoller::READY_FOR_WRITE;
639         }
640 
641         if ( error != 0 )
642         {
643             pollResult |= SocketPoller::HAS_ERROR;
644         }
645 
646         // If there is a significant result, send an event.
647         if ( pollResult != 0 )
648         {
649             // The event handler is stored in the window's user data and the
650             // socket with activity is given by wParam.
651             LONG_PTR userData = GetWindowLongPtr(hwnd, GWLP_USERDATA);
652             wxEvtHandler* hndlr = reinterpret_cast<wxEvtHandler*>(userData);
653             wxSOCKET_T sock = wParam;
654 
655             wxThreadEvent* event =
656                 new wxThreadEvent(wxEVT_SOCKET_POLLER_RESULT);
657             event->SetPayload<wxSOCKET_T>(sock);
658             event->SetInt(pollResult);
659 
660             if ( wxThread::IsMain() )
661             {
662                 hndlr->ProcessEvent(*event);
663                 delete event;
664             }
665             else
666             {
667                 wxQueueEvent(hndlr, event);
668             }
669         }
670 
671         return 0;
672     }
673     else
674     {
675         return DefWindowProc(hwnd, uMsg, wParam, lParam);
676     }
677 }
678 
Create(wxEvtHandler * hndlr)679 SocketPollerImpl* SocketPollerImpl::Create(wxEvtHandler* hndlr)
680 {
681     return new WinSock1SocketPoller(hndlr);
682 }
683 
684 #else
685 
686 // SocketPollerSourceHandler - a source handler used by the SocketPoller class.
687 
688 class SocketPollerSourceHandler: public wxEventLoopSourceHandler
689 {
690 public:
691     SocketPollerSourceHandler(wxSOCKET_T, wxEvtHandler*);
692 
693     void OnReadWaiting() wxOVERRIDE;
694     void OnWriteWaiting() wxOVERRIDE;
695     void OnExceptionWaiting() wxOVERRIDE;
~SocketPollerSourceHandler()696     ~SocketPollerSourceHandler(){}
697 private:
698     void SendEvent(int);
699     wxSOCKET_T m_socket;
700     wxEvtHandler* m_handler;
701 };
702 
SocketPollerSourceHandler(wxSOCKET_T sock,wxEvtHandler * hndlr)703 SocketPollerSourceHandler::SocketPollerSourceHandler(wxSOCKET_T sock,
704                                                      wxEvtHandler* hndlr)
705 {
706     m_socket = sock;
707     m_handler = hndlr;
708 }
709 
OnReadWaiting()710 void SocketPollerSourceHandler::OnReadWaiting()
711 {
712     SendEvent(SocketPoller::READY_FOR_READ);
713 }
714 
OnWriteWaiting()715 void SocketPollerSourceHandler::OnWriteWaiting()
716 {
717     SendEvent(SocketPoller::READY_FOR_WRITE);
718 }
719 
OnExceptionWaiting()720 void SocketPollerSourceHandler::OnExceptionWaiting()
721 {
722     SendEvent(SocketPoller::HAS_ERROR);
723 }
724 
SendEvent(int result)725 void SocketPollerSourceHandler::SendEvent(int result)
726 {
727     wxThreadEvent event(wxEVT_SOCKET_POLLER_RESULT);
728     event.SetPayload<wxSOCKET_T>(m_socket);
729     event.SetInt(result);
730     m_handler->ProcessEvent(event);
731 }
732 
733 // SourceSocketPoller - a SocketPollerImpl based on event loop sources.
734 
735 class SourceSocketPoller: public SocketPollerImpl
736 {
737 public:
738     SourceSocketPoller(wxEvtHandler*);
739     ~SourceSocketPoller();
740     bool StartPolling(wxSOCKET_T, int) wxOVERRIDE;
741     void StopPolling(wxSOCKET_T) wxOVERRIDE;
742     void ResumePolling(wxSOCKET_T) wxOVERRIDE;
743 
744 private:
745     WX_DECLARE_HASH_MAP(wxSOCKET_T, wxEventLoopSource*, wxIntegerHash,\
746                         wxIntegerEqual, SocketDataMap);
747 
748     void CleanUpSocketSource(wxEventLoopSource*);
749 
750     SocketDataMap m_socketData;
751     wxEvtHandler* m_handler;
752 };
753 
SourceSocketPoller(wxEvtHandler * hndlr)754 SourceSocketPoller::SourceSocketPoller(wxEvtHandler* hndlr)
755 {
756     m_handler = hndlr;
757 }
758 
~SourceSocketPoller()759 SourceSocketPoller::~SourceSocketPoller()
760 {
761     // Clean up any leftover socket data.
762     for ( SocketDataMap::iterator it = m_socketData.begin() ;
763           it != m_socketData.end() ; ++it )
764     {
765         CleanUpSocketSource(it->second);
766     }
767 }
768 
SocketPoller2EventSource(int pollAction)769 static int SocketPoller2EventSource(int pollAction)
770 {
771     // Convert the SocketPoller::PollAction value to a flag that can be used
772     // by wxEventLoopSource.
773 
774     // Always check for errors.
775     int eventSourceFlag = wxEVENT_SOURCE_EXCEPTION;
776 
777     if ( pollAction & SocketPoller::POLL_FOR_READ )
778     {
779         eventSourceFlag |= wxEVENT_SOURCE_INPUT;
780     }
781 
782     if ( pollAction & SocketPoller::POLL_FOR_WRITE )
783     {
784         eventSourceFlag |= wxEVENT_SOURCE_OUTPUT;
785     }
786 
787     return eventSourceFlag;
788 }
789 
StartPolling(wxSOCKET_T sock,int pollAction)790 bool SourceSocketPoller::StartPolling(wxSOCKET_T sock, int pollAction)
791 {
792     SocketDataMap::iterator it = m_socketData.find(sock);
793     wxEventLoopSourceHandler* srcHandler = NULL;
794 
795     if ( it != m_socketData.end() )
796     {
797         // If this socket is already being polled, reuse the old handler. Also
798         // delete the old source object to stop the old polling operations.
799         wxEventLoopSource* oldSrc = it->second;
800         srcHandler = oldSrc->GetHandler();
801 
802         delete oldSrc;
803     }
804     else
805     {
806         // Otherwise create a new source handler.
807         srcHandler =
808             new SocketPollerSourceHandler(sock, m_handler);
809     }
810 
811     // Get a new source object for these polling checks.
812     bool socketIsPolled = true;
813     int eventSourceFlag = SocketPoller2EventSource(pollAction);
814     wxEventLoopSource* newSrc =
815         wxEventLoopBase::AddSourceForFD(sock, srcHandler, eventSourceFlag);
816 
817     if ( newSrc == NULL )
818     {
819         // We were not able to add a source for this socket.
820         wxLogDebug(wxString::Format(
821                        "Unable to create event loop source for %d",
822                        static_cast<int>(sock)));
823 
824         delete srcHandler;
825         socketIsPolled = false;
826 
827         if ( it != m_socketData.end() )
828         {
829             m_socketData.erase(it);
830         }
831     }
832     else
833     {
834         m_socketData[sock] = newSrc;
835     }
836 
837     return socketIsPolled;
838 }
839 
StopPolling(wxSOCKET_T sock)840 void SourceSocketPoller::StopPolling(wxSOCKET_T sock)
841 {
842     SocketDataMap::iterator it = m_socketData.find(sock);
843 
844     if ( it != m_socketData.end() )
845     {
846         CleanUpSocketSource(it->second);
847         m_socketData.erase(it);
848     }
849 }
850 
ResumePolling(wxSOCKET_T WXUNUSED (sock))851 void SourceSocketPoller::ResumePolling(wxSOCKET_T WXUNUSED(sock))
852 {
853 }
854 
CleanUpSocketSource(wxEventLoopSource * source)855 void SourceSocketPoller::CleanUpSocketSource(wxEventLoopSource* source)
856 {
857     wxEventLoopSourceHandler* srcHandler = source->GetHandler();
858     delete source;
859     delete srcHandler;
860 }
861 
Create(wxEvtHandler * hndlr)862 SocketPollerImpl* SocketPollerImpl::Create(wxEvtHandler* hndlr)
863 {
864     return new SourceSocketPoller(hndlr);
865 }
866 
867 #endif
868 
869 //
870 // wxWebSessionCURL
871 //
872 
873 int wxWebSessionCURL::ms_activeSessions = 0;
874 unsigned int wxWebSessionCURL::ms_runtimeVersion = 0;
875 
wxWebSessionCURL()876 wxWebSessionCURL::wxWebSessionCURL() :
877     m_handle(NULL)
878 {
879     // Initialize CURL globally if no sessions are active
880     if ( ms_activeSessions == 0 )
881     {
882         if ( curl_global_init(CURL_GLOBAL_ALL) )
883         {
884             wxLogError(_("libcurl could not be initialized"));
885         }
886         else
887         {
888             curl_version_info_data* data = curl_version_info(CURLVERSION_NOW);
889             ms_runtimeVersion = data->version_num;
890         }
891     }
892 
893     ms_activeSessions++;
894 
895     m_socketPoller = new SocketPoller(this);
896     m_timeoutTimer.SetOwner(this);
897     Bind(wxEVT_TIMER, &wxWebSessionCURL::TimeoutNotification, this);
898     Bind(wxEVT_SOCKET_POLLER_RESULT,
899          &wxWebSessionCURL::ProcessSocketPollerResult, this);
900 }
901 
~wxWebSessionCURL()902 wxWebSessionCURL::~wxWebSessionCURL()
903 {
904     delete m_socketPoller;
905 
906     if ( m_handle )
907         curl_multi_cleanup(m_handle);
908 
909     // Global CURL cleanup if this is the last session
910     --ms_activeSessions;
911     if ( ms_activeSessions == 0 )
912         curl_global_cleanup();
913 }
914 
915 wxWebRequestImplPtr
CreateRequest(wxWebSession & session,wxEvtHandler * handler,const wxString & url,int id)916 wxWebSessionCURL::CreateRequest(wxWebSession& session,
917                                 wxEvtHandler* handler,
918                                 const wxString& url,
919                                 int id)
920 {
921     // Allocate our handle on demand.
922     if ( !m_handle )
923     {
924         m_handle = curl_multi_init();
925         if ( !m_handle )
926         {
927             wxLogDebug("curl_multi_init() failed");
928             return wxWebRequestImplPtr();
929         }
930         else
931         {
932             curl_multi_setopt(m_handle, CURLMOPT_SOCKETDATA, this);
933             curl_multi_setopt(m_handle, CURLMOPT_SOCKETFUNCTION, SocketCallback);
934             curl_multi_setopt(m_handle, CURLMOPT_TIMERDATA, this);
935             curl_multi_setopt(m_handle, CURLMOPT_TIMERFUNCTION, TimerCallback);
936         }
937     }
938 
939     return wxWebRequestImplPtr(new wxWebRequestCURL(session, *this, handler, url, id));
940 }
941 
StartRequest(wxWebRequestCURL & request)942 bool wxWebSessionCURL::StartRequest(wxWebRequestCURL & request)
943 {
944     // Add request easy handle to multi handle
945     CURL* curl = request.GetHandle();
946     int code = curl_multi_add_handle(m_handle, curl);
947 
948     if ( code == CURLM_OK )
949     {
950         request.SetState(wxWebRequest::State_Active);
951         m_activeTransfers[curl] = &request;
952 
953         // Report a timeout to curl to initiate this transfer.
954         int runningHandles;
955         curl_multi_socket_action(m_handle, CURL_SOCKET_TIMEOUT, 0,
956                                  &runningHandles);
957 
958         return true;
959     }
960     else
961     {
962         return false;
963     }
964 }
965 
CancelRequest(wxWebRequestCURL * request)966 void wxWebSessionCURL::CancelRequest(wxWebRequestCURL* request)
967 {
968     // If this transfer is currently active, stop it.
969     CURL* curl = request->GetHandle();
970     StopActiveTransfer(curl);
971 
972     request->SetState(wxWebRequest::State_Cancelled);
973 }
974 
RequestHasTerminated(wxWebRequestCURL * request)975 void wxWebSessionCURL::RequestHasTerminated(wxWebRequestCURL* request)
976 {
977     // If this transfer is currently active, stop it.
978     CURL* curl = request->GetHandle();
979     StopActiveTransfer(curl);
980 
981     curl_easy_cleanup(curl);
982 }
983 
GetLibraryVersionInfo()984 wxVersionInfo  wxWebSessionCURL::GetLibraryVersionInfo()
985 {
986     const curl_version_info_data* vi = curl_version_info(CURLVERSION_NOW);
987     wxString desc = wxString::Format("libcurl/%s", vi->version);
988     if (vi->ssl_version[0])
989         desc += " " + wxString(vi->ssl_version);
990     return wxVersionInfo("libcurl",
991         vi->version_num >> 16 & 0xff,
992         vi->version_num >> 8 & 0xff,
993         vi->version_num & 0xff,
994         desc);
995 }
996 
CurlRuntimeAtLeastVersion(unsigned int major,unsigned int minor,unsigned int patch)997 bool wxWebSessionCURL::CurlRuntimeAtLeastVersion(unsigned int major,
998                                                  unsigned int minor,
999                                                  unsigned int patch)
1000 {
1001     return (ms_runtimeVersion >= CURL_VERSION_BITS(major, minor, patch));
1002 }
1003 
1004 // curl interacts with the wxWebSessionCURL class through 2 callback functions
1005 // 1) TimerCallback is called whenever curl wants us to start or stop a timer.
1006 // 2) SocketCallback is called when curl wants us to start monitoring a socket
1007 //     for activity.
1008 //
1009 // curl accomplishes the network transfers by calling the
1010 // curl_multi_socket_action function to move pieces of the transfer to or from
1011 // the system's network services. Consequently we call this function when a
1012 // timeout occurs or when activity is detected on a socket.
1013 
TimerCallback(CURLM * WXUNUSED (multi),long timeoutms,void * userp)1014 int wxWebSessionCURL::TimerCallback(CURLM* WXUNUSED(multi), long timeoutms,
1015                                     void *userp)
1016 {
1017     wxWebSessionCURL* session = reinterpret_cast<wxWebSessionCURL*>(userp);
1018     session->ProcessTimerCallback(timeoutms);
1019     return 0;
1020 }
1021 
SocketCallback(CURL * curl,curl_socket_t sock,int what,void * userp,void * WXUNUSED (sp))1022 int wxWebSessionCURL::SocketCallback(CURL* curl, curl_socket_t sock, int what,
1023                                      void* userp, void* WXUNUSED(sp))
1024 {
1025     wxWebSessionCURL* session = reinterpret_cast<wxWebSessionCURL*>(userp);
1026     session->ProcessSocketCallback(curl, sock, what);
1027     return CURLM_OK;
1028 };
1029 
ProcessTimerCallback(long timeoutms)1030 void wxWebSessionCURL::ProcessTimerCallback(long timeoutms)
1031 {
1032     // When this callback is called, curl wants us to start or stop a timer.
1033     // If timeoutms = -1, we should stop the timer. If timeoutms > 0, we should
1034     // start a oneshot timer and when that timer expires, we should call
1035     // curl_multi_socket_action(m_handle, CURL_SOCKET_TIMEOUT,...
1036     //
1037     // In the special case that timeoutms = 0, we should signal a timeout as
1038     // soon as possible (as above by calling curl_multi_socket_action). But
1039     // according to the curl documentation, we can't do that from this callback
1040     // or we might cause an infinite loop. So use CallAfter to report the
1041     // timeout at a slightly later time.
1042 
1043     if ( timeoutms > 0)
1044     {
1045         m_timeoutTimer.StartOnce(timeoutms);
1046     }
1047     else if ( timeoutms < 0 )
1048     {
1049         m_timeoutTimer.Stop();
1050     }
1051     else // timeoutms == 0
1052     {
1053         CallAfter(&wxWebSessionCURL::ProcessTimeoutNotification);
1054     }
1055 }
1056 
TimeoutNotification(wxTimerEvent & WXUNUSED (event))1057 void wxWebSessionCURL::TimeoutNotification(wxTimerEvent& WXUNUSED(event))
1058 {
1059     ProcessTimeoutNotification();
1060 }
1061 
ProcessTimeoutNotification()1062 void wxWebSessionCURL::ProcessTimeoutNotification()
1063 {
1064     int runningHandles;
1065     curl_multi_socket_action(m_handle, CURL_SOCKET_TIMEOUT, 0, &runningHandles);
1066 
1067     CheckForCompletedTransfers();
1068 }
1069 
CurlPoll2SocketPoller(int what)1070 static int CurlPoll2SocketPoller(int what)
1071 {
1072     int pollAction = SocketPoller::INVALID_ACTION;
1073 
1074     if ( what == CURL_POLL_IN )
1075     {
1076         pollAction = SocketPoller::POLL_FOR_READ ;
1077     }
1078     else if ( what == CURL_POLL_OUT )
1079     {
1080         pollAction = SocketPoller::POLL_FOR_WRITE;
1081     }
1082     else if ( what == CURL_POLL_INOUT )
1083     {
1084         pollAction =
1085             SocketPoller::POLL_FOR_READ | SocketPoller::POLL_FOR_WRITE;
1086     }
1087 
1088     return pollAction;
1089 }
1090 
ProcessSocketCallback(CURL * curl,curl_socket_t s,int what)1091 void wxWebSessionCURL::ProcessSocketCallback(CURL* curl, curl_socket_t s,
1092                                              int what)
1093 {
1094     // Have the socket poller start or stop monitoring a socket depending of
1095     // the value of what.
1096 
1097     switch ( what )
1098     {
1099         case CURL_POLL_IN:
1100             wxFALLTHROUGH;
1101         case CURL_POLL_OUT:
1102             wxFALLTHROUGH;
1103         case CURL_POLL_INOUT:
1104             {
1105                 m_activeSockets[curl] = s;
1106 
1107                 int pollAction = CurlPoll2SocketPoller(what);
1108                 bool socketIsMonitored = m_socketPoller->StartPolling(s,
1109                                                                       pollAction);
1110 
1111                 if ( !socketIsMonitored )
1112                 {
1113                     TransferSet::iterator it = m_activeTransfers.find(curl);
1114 
1115                     if ( it != m_activeTransfers.end() )
1116                     {
1117                         FailRequest(curl,
1118                             "wxWebSession failed to monitor a socket for this "
1119                             "transfer");
1120                     }
1121                 }
1122             }
1123             break;
1124         case CURL_POLL_REMOVE:
1125             m_socketPoller->StopPolling(s);
1126             RemoveActiveSocket(curl);
1127             break;
1128         default:
1129             wxLogDebug("Unknown socket action in ProcessSocketCallback");
1130             break;
1131     }
1132 }
1133 
SocketPollerResult2CurlSelect(int socketEventFlag)1134 static int SocketPollerResult2CurlSelect(int socketEventFlag)
1135 {
1136     int curlSelect = 0;
1137 
1138     if ( socketEventFlag & SocketPoller::READY_FOR_READ )
1139     {
1140         curlSelect |= CURL_CSELECT_IN;
1141     }
1142 
1143     if ( socketEventFlag & SocketPoller::READY_FOR_WRITE )
1144     {
1145         curlSelect |= CURL_CSELECT_OUT;
1146     }
1147 
1148     if ( socketEventFlag &  SocketPoller::HAS_ERROR )
1149     {
1150         curlSelect |= CURL_CSELECT_ERR;
1151     }
1152 
1153     return curlSelect;
1154 }
1155 
ProcessSocketPollerResult(wxThreadEvent & event)1156 void wxWebSessionCURL::ProcessSocketPollerResult(wxThreadEvent& event)
1157 {
1158     // Convert the socket poller result to an action flag needed by curl.
1159     // Then call curl_multi_socket_action.
1160     curl_socket_t sock = event.GetPayload<curl_socket_t>();
1161     int action = SocketPollerResult2CurlSelect(event.GetInt());
1162     int runningHandles;
1163     curl_multi_socket_action(m_handle, sock, action, &runningHandles);
1164 
1165     CheckForCompletedTransfers();
1166     m_socketPoller->ResumePolling(sock);
1167 }
1168 
CheckForCompletedTransfers()1169 void wxWebSessionCURL::CheckForCompletedTransfers()
1170 {
1171     // Process CURL message queue
1172     int msgQueueCount;
1173     while ( CURLMsg* msg = curl_multi_info_read(m_handle, &msgQueueCount) )
1174     {
1175         if ( msg->msg == CURLMSG_DONE )
1176         {
1177             CURL* curl = msg->easy_handle;
1178             TransferSet::iterator it = m_activeTransfers.find(curl);
1179 
1180             if ( it != m_activeTransfers.end() )
1181             {
1182                 wxWebRequestCURL* request = it->second;
1183                 curl_multi_remove_handle(m_handle, curl);
1184                 request->HandleCompletion();
1185                 m_activeTransfers.erase(it);
1186                 RemoveActiveSocket(curl);
1187             }
1188         }
1189     }
1190 }
1191 
FailRequest(CURL * curl,const wxString & msg)1192 void wxWebSessionCURL::FailRequest(CURL* curl,const wxString& msg)
1193 {
1194     TransferSet::iterator it = m_activeTransfers.find(curl);
1195 
1196     if ( it != m_activeTransfers.end() )
1197     {
1198         wxWebRequestCURL* request = it->second;
1199         StopActiveTransfer(curl);
1200 
1201         request->SetState(wxWebRequest::State_Failed, msg);
1202     }
1203 }
1204 
StopActiveTransfer(CURL * curl)1205 void wxWebSessionCURL::StopActiveTransfer(CURL* curl)
1206 {
1207     TransferSet::iterator it = m_activeTransfers.find(curl);
1208 
1209     if ( it != m_activeTransfers.end() )
1210     {
1211         // Record the current active socket now since it should be removed from
1212         // the m_activeSockets map when we call curl_multi_remove_handle.
1213         curl_socket_t activeSocket = CURL_SOCKET_BAD;
1214         CurlSocketMap::iterator it2 = m_activeSockets.find(curl);
1215 
1216         if ( it2 != m_activeSockets.end() )
1217         {
1218             activeSocket = it2->second;
1219         }
1220 
1221         // Remove the CURL easy handle from the CURLM multi handle.
1222         curl_multi_remove_handle(m_handle, curl);
1223 
1224         // If the transfer was active, close its socket.
1225         if ( activeSocket != CURL_SOCKET_BAD )
1226         {
1227             wxCloseSocket(activeSocket);
1228         }
1229 
1230         // Clean up the maps.
1231         RemoveActiveSocket(curl);
1232         m_activeTransfers.erase(it);
1233     }
1234 }
1235 
RemoveActiveSocket(CURL * curl)1236 void wxWebSessionCURL::RemoveActiveSocket(CURL* curl)
1237 {
1238     CurlSocketMap::iterator it = m_activeSockets.find(curl);
1239 
1240     if ( it != m_activeSockets.end() )
1241     {
1242         m_activeSockets.erase(it);
1243     }
1244 }
1245 
1246 #endif // wxUSE_WEBREQUEST_CURL
1247