/////////////////////////////////////////////////////////////////////////////// // Name: wx/webrequest_curl.h // Purpose: wxWebRequest implementation using libcurl // Author: Tobias Taschner // Created: 2018-10-25 // Copyright: (c) 2018 wxWidgets development team // Licence: wxWindows licence /////////////////////////////////////////////////////////////////////////////// #ifndef _WX_WEBREQUEST_CURL_H #define _WX_WEBREQUEST_CURL_H #if wxUSE_WEBREQUEST_CURL #include "wx/private/webrequest.h" #include "wx/thread.h" #include "wx/vector.h" #include "wx/timer.h" #include "curl/curl.h" #include <unordered_map> #include <vector> class wxWebRequestCURL; class wxWebResponseCURL; class wxWebSessionCURL; class wxWebSessionSyncCURL; class SocketPoller; class wxWebAuthChallengeCURL : public wxWebAuthChallengeImpl { public: wxWebAuthChallengeCURL(wxWebAuthChallenge::Source source, wxWebRequestCURL& request); void SetCredentials(const wxWebCredentials& cred) override; private: wxWebRequestCURL& m_request; wxDECLARE_NO_COPY_CLASS(wxWebAuthChallengeCURL); }; class wxWebRequestCURL : public wxWebRequestImpl { public: // Ctor for async requests: creates a new libcurl handle and owns it. wxWebRequestCURL(wxWebSession& session, wxWebSessionCURL& sessionImpl, wxEvtHandler* handler, const wxString& url, int id); // Ctor for sync requests: uses the libcurl handle from the session and // doesn't own it. wxWebRequestCURL(wxWebSessionSyncCURL& sessionImpl, const wxString& url); ~wxWebRequestCURL(); wxWebRequest::Result Execute() override; void Start() override; wxWebResponseImplPtr GetResponse() const override { return m_response; } wxWebAuthChallengeImplPtr GetAuthChallenge() const override { return m_authChallenge; } wxFileOffset GetBytesSent() const override; wxFileOffset GetBytesExpectedToSend() const override; CURL* GetHandle() const { return m_handle; } wxWebRequestHandle GetNativeHandle() const override { return (wxWebRequestHandle)GetHandle(); } bool StartRequest(); void HandleCompletion(); wxString GetError() const; // Method called from libcurl callback size_t CURLOnRead(char* buffer, size_t size); private: // Common initialization for sync and async requests performed when the // request is created. void DoStartPrepare(const wxString& url); // This function is again common for sync and async requests, but is called // right before starting, or executing, the request. // // If it returns result with State_Failed, the request should be aborted. wxWebRequest::Result DoFinishPrepare(); // Convert the status of the completed request to our result structure and, // if necessary, initialize m_authChallenge. wxWebRequest::Result DoHandleCompletion(); void DoCancel() override; // This is only used for async requests. wxWebSessionCURL* const m_sessionCURL; // This pointer is only owned by this object when using async requests. CURL* const m_handle; char m_errorBuffer[CURL_ERROR_SIZE]; struct curl_slist *m_headerList = nullptr; wxObjectDataPtr<wxWebResponseCURL> m_response; wxObjectDataPtr<wxWebAuthChallengeCURL> m_authChallenge; wxFileOffset m_bytesSent; wxDECLARE_NO_COPY_CLASS(wxWebRequestCURL); }; class wxWebResponseCURL : public wxWebResponseImpl { public: explicit wxWebResponseCURL(wxWebRequestCURL& request); wxFileOffset GetContentLength() const override; wxString GetURL() const override; wxString GetHeader(const wxString& name) const override; std::vector<wxString> GetAllHeaderValues(const wxString& name) const override; int GetStatus() const override; wxString GetStatusText() const override { return m_statusText; } // Methods called from libcurl callbacks size_t CURLOnWrite(void *buffer, size_t size); size_t CURLOnHeader(const char* buffer, size_t size); int CURLOnProgress(curl_off_t); private: // We can receive multiple headers with the same name (classic example is // "Set-Cookie:"), so we can't use wxWebRequestHeaderMap here and need to // define our own "multi-map" for headers: it maps the header name to a // collection of all its values, possibly from multiple header lines. using AllHeadersMap = std::unordered_map<wxString, std::vector<wxString>>; AllHeadersMap m_headers; wxString m_statusText; wxFileOffset m_knownDownloadSize; CURL* GetHandle() const { return static_cast<wxWebRequestCURL&>(m_request).GetHandle(); } wxDECLARE_NO_COPY_CLASS(wxWebResponseCURL); }; // Common base class for synchronous and asynchronous sessions. class wxWebSessionBaseCURL : public wxWebSessionImpl { public: explicit wxWebSessionBaseCURL(Mode mode); ~wxWebSessionBaseCURL(); wxVersionInfo GetLibraryVersionInfo() const override; static bool CurlRuntimeAtLeastVersion(unsigned int, unsigned int, unsigned int); protected: static int ms_activeSessions; static unsigned int ms_runtimeVersion; }; // Sync session implementation uses libcurl "easy" API. class wxWebSessionSyncCURL : public wxWebSessionBaseCURL { public: wxWebSessionSyncCURL(); ~wxWebSessionSyncCURL(); wxWebRequestImplPtr CreateRequest(wxWebSession& WXUNUSED(session), wxEvtHandler* WXUNUSED(handler), const wxString& WXUNUSED(url), int WXUNUSED(id)) override { wxFAIL_MSG("This method should not be called for synchronous sessions"); return wxWebRequestImplPtr{}; } wxWebRequestImplPtr CreateRequestSync(wxWebSessionSync& session, const wxString& url) override; wxWebSessionHandle GetNativeHandle() const override { return (wxWebSessionHandle)m_handle; } CURL* GetHandle() const { return m_handle; } private: CURL* m_handle = nullptr; wxDECLARE_NO_COPY_CLASS(wxWebSessionSyncCURL); }; // Async session implementation uses libcurl "multi" API. class wxWebSessionCURL : public wxEvtHandler, public wxWebSessionBaseCURL { public: wxWebSessionCURL(); ~wxWebSessionCURL(); wxWebRequestImplPtr CreateRequest(wxWebSession& session, wxEvtHandler* handler, const wxString& url, int id = wxID_ANY) override; wxWebRequestImplPtr CreateRequestSync(wxWebSessionSync& WXUNUSED(session), const wxString& WXUNUSED(url)) override { wxFAIL_MSG("This method should not be called for asynchronous sessions"); return wxWebRequestImplPtr{}; } wxWebSessionHandle GetNativeHandle() const override { return (wxWebSessionHandle)m_handle; } bool StartRequest(wxWebRequestCURL& request); void CancelRequest(wxWebRequestCURL* request); void RequestHasTerminated(wxWebRequestCURL* request); private: static int TimerCallback(CURLM*, long, void*); static int SocketCallback(CURL*, curl_socket_t, int, void*, void*); void ProcessTimerCallback(long); void TimeoutNotification(wxTimerEvent&); void ProcessTimeoutNotification(); void ProcessSocketCallback(CURL*, curl_socket_t, int); void ProcessSocketPollerResult(wxThreadEvent&); void CheckForCompletedTransfers(); void FailRequest(CURL*, const wxString&); void StopActiveTransfer(CURL*); void RemoveActiveSocket(CURL*); using TransferSet = std::unordered_map<CURL*, wxWebRequestCURL*>; using CurlSocketMap = std::unordered_map<CURL*, curl_socket_t>; TransferSet m_activeTransfers; CurlSocketMap m_activeSockets; SocketPoller* m_socketPoller = nullptr; wxTimer m_timeoutTimer; CURLM* m_handle = nullptr; wxDECLARE_NO_COPY_CLASS(wxWebSessionCURL); }; class wxWebSessionFactoryCURL : public wxWebSessionFactory { public: wxWebSessionImpl* Create() override { return new wxWebSessionCURL(); } wxWebSessionImpl* CreateSync() override { return new wxWebSessionSyncCURL(); } }; #endif // wxUSE_WEBREQUEST_CURL #endif