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