From a4279752f87ede82b203457916b0130820cb2461 Mon Sep 17 00:00:00 2001 From: Tobias Taschner Date: Sat, 20 Oct 2018 19:21:35 +0200 Subject: [PATCH] Start wxWebRequestWinHTTP implementation Can already run the GET request in the sample app --- build/cmake/lib/net/CMakeLists.txt | 4 + configure | 3 + configure.in | 3 + include/wx/msw/webrequest_winhttp.h | 89 +++++- include/wx/webrequest.h | 66 ++++- interface/wx/webrequest.h | 9 +- samples/webrequest/webrequest.cpp | 13 +- src/common/webrequest.cpp | 88 +++++- src/msw/webrequest_winhttp.cpp | 427 +++++++++++++++++++++++++++- 9 files changed, 676 insertions(+), 26 deletions(-) diff --git a/build/cmake/lib/net/CMakeLists.txt b/build/cmake/lib/net/CMakeLists.txt index 3d38412803..fa0af82227 100644 --- a/build/cmake/lib/net/CMakeLists.txt +++ b/build/cmake/lib/net/CMakeLists.txt @@ -25,6 +25,10 @@ wx_add_library(net IS_BASE ${NET_FILES}) if(WIN32) wx_lib_link_libraries(net PRIVATE ws2_32) + + if(wxUSE_WEBREQUEST) + wx_lib_link_libraries(net PRIVATE Winhttp) + endif() endif() wx_finalize_lib(net) diff --git a/configure b/configure index 5fcda9185d..dffae7b78c 100755 --- a/configure +++ b/configure @@ -23773,6 +23773,9 @@ fi if test "$wxUSE_ACCESSIBILITY" = "yes" ; then LIBS="$LIBS -loleacc" fi + if test "$wxUSE_WEBREQUEST" = "yes" ; then + LIBS="$LIBS -lwinhttp" + fi case "${host}" in *-*-cygwin* ) diff --git a/configure.in b/configure.in index 40fb6ab0e0..3ca104240e 100644 --- a/configure.in +++ b/configure.in @@ -2766,6 +2766,9 @@ if test "$USE_WIN32" = 1 ; then if test "$wxUSE_ACCESSIBILITY" = "yes" ; then LIBS="$LIBS -loleacc" fi + if test "$wxUSE_WEBREQUEST" = "yes" ; then + LIBS="$LIBS -lwinhttp" + fi case "${host}" in *-*-cygwin* ) diff --git a/include/wx/msw/webrequest_winhttp.h b/include/wx/msw/webrequest_winhttp.h index 87f327eb32..493c984bf6 100644 --- a/include/wx/msw/webrequest_winhttp.h +++ b/include/wx/msw/webrequest_winhttp.h @@ -10,17 +10,102 @@ #ifndef _WX_MSW_WEBREQUEST_WINHTTP_H #define _WX_MSW_WEBREQUEST_WINHTTP_H +#include "wx/msw/wrapwin.h" +#include +#include "wx/buffer.h" + +class wxWebSessionWinHTTP; +class wxWebRequestWinHTTP; + +class WXDLLIMPEXP_NET wxWebResponseWinHTTP : public wxWebResponse +{ +public: + wxWebResponseWinHTTP(wxWebRequestWinHTTP& request); + + wxInt64 GetContentLength() const wxOVERRIDE { return m_contentLength; } + + wxString GetURL() const wxOVERRIDE; + + wxString GetHeader(const wxString& name) const wxOVERRIDE; + + int GetStatus() const wxOVERRIDE; + + wxString GetStatusText() const wxOVERRIDE; + + wxInputStream* GetStream() const wxOVERRIDE; + + wxString AsString(wxMBConv* conv = NULL) const wxOVERRIDE; + + bool ReadData(); + + bool ReportAvailableData(DWORD dataLen); + + void ReportDataComplete(); + +private: + wxWebRequestWinHTTP& m_request; + wxInt64 m_contentLength; + long m_readSize; + wxMemoryBuffer m_readBuffer; + wxScopedPtr m_stream; + + wxDECLARE_NO_COPY_CLASS(wxWebResponseWinHTTP); +}; + +class WXDLLIMPEXP_NET wxWebRequestWinHTTP : public wxWebRequest +{ +public: + wxWebRequestWinHTTP(int id, wxWebSessionWinHTTP& session, const wxString& url); + + ~wxWebRequestWinHTTP(); + + void SetMethod(const wxString& method) wxOVERRIDE; + + void SetData(const wxString& text, const wxString& contentType) wxOVERRIDE; + + void SetData(const wxInputStream& dataStream, const wxString& contentType) wxOVERRIDE; + + void Start() wxOVERRIDE; + + void Cancel() wxOVERRIDE; + + wxWebResponse* GetResponse() wxOVERRIDE; + + void HandleCallback(DWORD dwInternetStatus, LPVOID lpvStatusInformation, + DWORD dwStatusInformationLength); + + HINTERNET GetHandle() const { return m_request; } + +private: + wxWebSessionWinHTTP& m_session; + wxString m_url; + HINTERNET m_connect; + HINTERNET m_request; + wxScopedPtr m_response; + + void SetFailedWithLastError(); + + wxDECLARE_NO_COPY_CLASS(wxWebRequestWinHTTP); +}; + class WXDLLIMPEXP_NET wxWebSessionWinHTTP: public wxWebSession { public: - wxWebSessionWinHTTP() { } + wxWebSessionWinHTTP(); + + ~wxWebSessionWinHTTP(); wxWebRequest* CreateRequest(const wxString& url, int id = wxID_ANY) wxOVERRIDE; - void SetHeader(const wxString& name, const wxString& value) wxOVERRIDE; + HINTERNET GetHandle() const { return m_handle; } private: + bool m_initialized; + HINTERNET m_handle; + void Init(); + + wxDECLARE_NO_COPY_CLASS(wxWebSessionWinHTTP); }; class WXDLLIMPEXP_NET wxWebSessionFactoryWinHTTP: public wxWebSessionFactory diff --git a/include/wx/webrequest.h b/include/wx/webrequest.h index d505fbbf9e..c72c1a21f9 100644 --- a/include/wx/webrequest.h +++ b/include/wx/webrequest.h @@ -18,15 +18,29 @@ #include "wx/object.h" #include "wx/scopedptr.h" #include "wx/sharedptr.h" +#include "wx/stream.h" #include "wx/vector.h" class wxWebResponse; class wxWebSession; +WX_DECLARE_STRING_HASH_MAP(wxString, wxWebRequestHeaderMap); + class WXDLLIMPEXP_NET wxWebRequest : public wxEvtHandler, public wxRefCounter { public: - virtual void SetHeader(const wxString& name, const wxString& value) = 0; + enum State + { + State_Idle, + State_Active, + State_Ready, + State_Failed + }; + + virtual ~wxWebRequest() { } + + virtual void SetHeader(const wxString& name, const wxString& value) + { m_headers[name] = value; } virtual void SetMethod(const wxString& method) = 0; @@ -34,21 +48,50 @@ public: virtual void SetData(const wxInputStream& dataStream, const wxString& contentType) = 0; - virtual void SetIgnoreServerErrorStatus(bool ignore) = 0; + void SetIgnoreServerErrorStatus(bool ignore) { m_ignoreServerErrorStatus = ignore; } virtual void Start() = 0; virtual void Cancel() = 0; - virtual const wxWebResponse* GetResponse() const = 0; + virtual wxWebResponse* GetResponse() = 0; + + int GetId() const { return m_id; } + + State GetState() const { return m_state; } + +protected: + wxWebRequestHeaderMap m_headers; + + wxWebRequest(int id): + m_id(id), + m_state(State_Idle), + m_ignoreServerErrorStatus(false) { } + + void SetState(State state, const wxString& failMsg = ""); + + bool CheckServerStatus(); private: + int m_id; + State m_state; + wxString m_failMessage; + bool m_ignoreServerErrorStatus; + + void ProcessReadyEvent(); + + void ProcessFailedEvent(); + wxDECLARE_NO_COPY_CLASS(wxWebRequest); }; class WXDLLIMPEXP_NET wxWebResponse { public: + virtual ~wxWebResponse() { } + + virtual wxInt64 GetContentLength() const = 0; + virtual wxString GetURL() const = 0; virtual wxString GetHeader(const wxString& name) const = 0; @@ -57,10 +100,13 @@ public: virtual wxString GetStatusText() const = 0; - virtual wxInputStream& GetStream() const = 0; + virtual wxInputStream* GetStream() const = 0; virtual wxString AsString(wxMBConv* conv = NULL) const = 0; +protected: + wxWebResponse() { } + private: wxDECLARE_NO_COPY_CLASS(wxWebResponse); }; @@ -69,6 +115,8 @@ class WXDLLIMPEXP_NET wxWebSessionFactory { public: virtual wxWebSession* Create() = 0; + + virtual ~wxWebSessionFactory() { } }; WX_DECLARE_STRING_HASH_MAP(wxSharedPtr, wxStringWebSessionFactoryMap); @@ -85,7 +133,10 @@ public: virtual wxWebRequest* CreateRequest(const wxString& url, int id = wxID_ANY) = 0; - virtual void SetHeader(const wxString& name, const wxString& value) = 0; + virtual void SetHeader(const wxString& name, const wxString& value) + { m_headers[name] = value; } + + const wxWebRequestHeaderMap& GetHeaders() const { return m_headers; } static wxWebSession& GetDefault(); @@ -96,9 +147,10 @@ public: static bool IsBackendAvailable(const wxString& backend); protected: - wxWebSession() { } + wxWebSession(); private: + wxWebRequestHeaderMap m_headers; static wxScopedPtr ms_defaultSession; static wxStringWebSessionFactoryMap ms_factoryMap; @@ -120,6 +172,8 @@ public: const wxString& GetErrorDescription() const { return m_errorDescription; } + wxEvent* Clone() const wxOVERRIDE { return new wxWebRequestEvent(*this); } + private: wxWebResponse* m_response; wxString m_errorDescription; diff --git a/interface/wx/webrequest.h b/interface/wx/webrequest.h index 0aaeb92d3a..8428636c22 100644 --- a/interface/wx/webrequest.h +++ b/interface/wx/webrequest.h @@ -31,7 +31,7 @@ // Bind events request->Bind(wxEVT_WEBREQUEST_READY, [](wxWebRequestEvent& evt) { - wxImage logoImage(evt->GetResponse()->GetStream()); + wxImage logoImage(*evt->GetResponse()->GetStream()); if (logoImage.IsOK()) wxLogInfo("Image loaded"); }); @@ -192,6 +192,11 @@ public: @c NULL. */ const wxWebResponse* GetResponse() const; + + /** + Returns the id specified while creating this request. + */ + int GetId() const; }; /** @@ -235,7 +240,7 @@ public: /** Returns a stream which represents the response data sent by the server. */ - wxInputStream& GetStream() const; + wxInputStream* GetStream() const; /** Returns all response data as a string. diff --git a/samples/webrequest/webrequest.cpp b/samples/webrequest/webrequest.cpp index f7b6407df3..f2e11617d4 100644 --- a/samples/webrequest/webrequest.cpp +++ b/samples/webrequest/webrequest.cpp @@ -55,12 +55,12 @@ public: getLoadButton->Bind(wxEVT_BUTTON, &WebRequestFrame::OnGetLoadButton, this); getSizer->Add(getLoadButton, wxSizerFlags().Border()); - wxStaticBoxSizer* getImageBox = + m_getImageBox = new wxStaticBoxSizer(wxVERTICAL, getPanel, "Image"); - m_getStaticBitmap = new wxStaticBitmap(getImageBox->GetStaticBox(), + m_getStaticBitmap = new wxStaticBitmap(m_getImageBox->GetStaticBox(), wxID_ANY, wxArtProvider::GetBitmap(wxART_MISSING_IMAGE)); - getImageBox->Add(m_getStaticBitmap, wxSizerFlags(1).Expand()); - getSizer->Add(getImageBox, wxSizerFlags(1).Expand().Border()); + m_getImageBox->Add(m_getStaticBitmap, wxSizerFlags(1).Expand()); + getSizer->Add(m_getImageBox, wxSizerFlags(1).Expand().Border()); getPanel->SetSizer(getSizer); notebook->AddPage(getPanel, "GET Image", true); @@ -132,8 +132,10 @@ public: void OnGetWebRequestReady(wxWebRequestEvent& evt) { - wxImage img(evt.GetResponse()->GetStream()); + wxImage img(*evt.GetResponse()->GetStream()); m_getStaticBitmap->SetBitmap(img); + m_getImageBox->Layout(); + GetStatusBar()->SetStatusText(wxString::Format("Loaded %lld bytes image data", evt.GetResponse()->GetContentLength())); } void OnWebRequestFailed(wxWebRequestEvent& evt) @@ -149,6 +151,7 @@ public: private: wxTextCtrl* m_getURLTextCtrl; + wxStaticBoxSizer* m_getImageBox; wxStaticBitmap* m_getStaticBitmap; wxTextCtrl* m_postURLTextCtrl; diff --git a/src/common/webrequest.cpp b/src/common/webrequest.cpp index ce7fe4c130..10c5e701ef 100644 --- a/src/common/webrequest.cpp +++ b/src/common/webrequest.cpp @@ -18,6 +18,11 @@ #include "wx/webrequest.h" +#ifndef WX_PRECOMP + #include "wx/app.h" + #include "wx/translation.h" +#endif + #if defined(__WINDOWS__) #include "wx/msw/webrequest_winhttp.h" #endif @@ -34,13 +39,86 @@ wxDEFINE_EVENT(wxEVT_WEBREQUEST_READY, wxWebRequestEvent); wxDEFINE_EVENT(wxEVT_WEBREQUEST_FAILED, wxWebRequestEvent); wxDEFINE_EVENT(wxEVT_WEBREQUEST_AUTH_REQUIRED, wxWebRequestEvent); +// +// wxWebRequest +// +bool wxWebRequest::CheckServerStatus() +{ + const wxWebResponse* resp = GetResponse(); + if ( resp && resp->GetStatus() >= 400 && !m_ignoreServerErrorStatus ) + { + SetState(State_Failed, wxString::Format(_("Error: %s (%d)"), + resp->GetStatusText(), resp->GetStatus())); + return false; + } + else + return true; +} + +void wxWebRequest::SetState(State state, const wxString & failMsg) +{ + switch (state) + { + case State_Active: + // Add a reference while the request is active + if ( m_state != State_Active ) + { + IncRef(); + m_state = state; + } + break; + case State_Ready: + // Trigger the ready event in main thread + CallAfter(&wxWebRequest::ProcessReadyEvent); + break; + case State_Failed: + m_failMessage = failMsg; + // Trigger the failed event in main thread + CallAfter(&wxWebRequest::ProcessFailedEvent); + break; + } +} + +void wxWebRequest::ProcessReadyEvent() +{ + wxWebRequestEvent evt(wxEVT_WEBREQUEST_READY, GetId(), GetResponse()); + ProcessEvent(evt); + // Remove reference after the request is no longer active + if ( m_state == State_Active ) + DecRef(); + m_state = State_Ready; +} + +void wxWebRequest::ProcessFailedEvent() +{ + wxWebRequestEvent evt(wxEVT_WEBREQUEST_FAILED, GetId(), NULL, + m_failMessage); + ProcessEvent(evt); + // Remove reference after the request is no longer active + if ( m_state == State_Active ) + DecRef(); + m_state = State_Failed; +} + +// +// wxWebSession +// + wxScopedPtr wxWebSession::ms_defaultSession; wxStringWebSessionFactoryMap wxWebSession::ms_factoryMap; +wxWebSession::wxWebSession() +{ + // Initialize the user-Agent header with a reasonable default + SetHeader("User-Agent", wxString::Format("%s/1 wxWidgets/%d.%d.%d", + wxTheApp->GetAppName(), + wxMAJOR_VERSION, wxMINOR_VERSION, wxRELEASE_NUMBER)); +} + // static wxWebSession& wxWebSession::GetDefault() { - if (ms_defaultSession == NULL) + if ( ms_defaultSession == NULL ) ms_defaultSession.reset(wxWebSession::New()); return *ms_defaultSession; @@ -49,11 +127,11 @@ wxWebSession& wxWebSession::GetDefault() // static wxWebSession* wxWebSession::New(const wxString& backend) { - if (ms_factoryMap.empty()) + if ( ms_factoryMap.empty() ) InitFactoryMap(); wxStringWebSessionFactoryMap::iterator factory = ms_factoryMap.find(backend); - if (factory != ms_factoryMap.end()) + if ( factory != ms_factoryMap.end() ) return factory->second->Create(); else return NULL; @@ -77,13 +155,11 @@ void wxWebSession::InitFactoryMap() // static bool wxWebSession::IsBackendAvailable(const wxString& backend) { - if (ms_factoryMap.empty()) + if ( ms_factoryMap.empty() ) InitFactoryMap(); wxStringWebSessionFactoryMap::iterator factory = ms_factoryMap.find(backend); return factory != ms_factoryMap.end(); } - - #endif // wxUSE_WEBREQUEST diff --git a/src/msw/webrequest_winhttp.cpp b/src/msw/webrequest_winhttp.cpp index 2d81617fa2..118cacde06 100644 --- a/src/msw/webrequest_winhttp.cpp +++ b/src/msw/webrequest_winhttp.cpp @@ -18,16 +18,433 @@ #if wxUSE_WEBREQUEST +#include "wx/mstream.h" +#include "wx/uri.h" #include "wx/msw/webrequest_winhttp.h" +#ifndef WX_PRECOMP + #include "wx/log.h" + #include "wx/utils.h" + #include "wx/translation.h" +#endif + +// For MSVC we can link in the required library explicitly, for the other +// compilers (e.g. MinGW) this needs to be done at makefiles level. +#ifdef __VISUALC__ +#pragma comment(lib, "Winhttp") +#endif + +// Define constants potentially missing in old SDKs +#ifndef WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY +#define WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY 4 +#endif +#ifndef WINHTTP_PROTOCOL_FLAG_HTTP2 +#define WINHTTP_PROTOCOL_FLAG_HTTP2 0x1 +#endif +#ifndef WINHTTP_OPTION_ENABLE_HTTP_PROTOCOL +#define WINHTTP_OPTION_ENABLE_HTTP_PROTOCOL 133 +#endif +#ifndef WINHTTP_DECOMPRESSION_FLAG_ALL +#define WINHTTP_DECOMPRESSION_FLAG_GZIP 0x00000001 +#define WINHTTP_DECOMPRESSION_FLAG_DEFLATE 0x00000002 +#define WINHTTP_DECOMPRESSION_FLAG_ALL ( \ + WINHTTP_DECOMPRESSION_FLAG_GZIP | \ + WINHTTP_DECOMPRESSION_FLAG_DEFLATE) +#endif +#ifndef WINHTTP_OPTION_DECOMPRESSION +#define WINHTTP_OPTION_DECOMPRESSION 118 +#endif +#ifndef WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 +#define WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 0x00000200 +#endif +#ifndef WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 +#define WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 0x00000800 +#endif + +// Helper functions + +static wxString wxWinHTTPErrorToString(DWORD errorCode) +{ + wxString errorString; + + LPVOID msgBuf; + if ( FormatMessageW( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS | + FORMAT_MESSAGE_FROM_HMODULE, + GetModuleHandle(TEXT("WINHTTP")), + errorCode, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPWSTR)&msgBuf, + 0, NULL) ) + { + errorString.assign((LPWSTR)msgBuf); + LocalFree(msgBuf); + // Truncate trailing \n\r + if ( errorString.size() > 2 ) + errorString.resize(errorString.size()); + } + return errorString; +} + +static wxString wxWinHTTPQueryHeaderString(HINTERNET hRequest, DWORD dwInfoLevel, + LPCWSTR pwszName = WINHTTP_HEADER_NAME_BY_INDEX) +{ + wxString result; + DWORD bufferLen = 0; + ::WinHttpQueryHeaders(hRequest, dwInfoLevel, pwszName, NULL, &bufferLen, + WINHTTP_NO_HEADER_INDEX); + if ( ::GetLastError() == ERROR_INSUFFICIENT_BUFFER ) + { + wxWCharBuffer resBuf(bufferLen); + if ( ::WinHttpQueryHeaders(hRequest, dwInfoLevel, pwszName, + resBuf.data(), &bufferLen, WINHTTP_NO_HEADER_INDEX) ) + result.assign(resBuf); + } + + return result; +} + +static wxString wxWinHTTPQueryOptionString(HINTERNET hInternet, DWORD dwOption) +{ + wxString result; + DWORD bufferLen = 0; + ::WinHttpQueryOption(hInternet, dwOption, NULL, &bufferLen); + if ( ::GetLastError() == ERROR_INSUFFICIENT_BUFFER ) + { + wxWCharBuffer resBuf(bufferLen); + if ( ::WinHttpQueryOption(hInternet, dwOption, resBuf.data(), &bufferLen) ) + result.assign(resBuf); + } + + return result; +} + +static void CALLBACK wxRequestStatusCallback( + HINTERNET WXUNUSED(hInternet), + DWORD_PTR dwContext, + DWORD dwInternetStatus, + LPVOID lpvStatusInformation, + DWORD dwStatusInformationLength +) +{ + if ( dwContext ) + { + wxWebRequestWinHTTP* request = + reinterpret_cast(dwContext); + request->HandleCallback(dwInternetStatus, lpvStatusInformation, + dwStatusInformationLength); + } +} + +// +// wxWebRequestWinHTTP +// + +wxWebRequestWinHTTP::wxWebRequestWinHTTP(int id, wxWebSessionWinHTTP& session, const wxString& url): + wxWebRequest(id), + m_session(session), + m_url(url), + m_connect(NULL), + m_request(NULL) +{ + m_headers = session.GetHeaders(); +} + +wxWebRequestWinHTTP::~wxWebRequestWinHTTP() +{ + if ( m_request ) + ::WinHttpCloseHandle(m_request); + if ( m_connect ) + ::WinHttpCloseHandle(m_connect); +} + +void wxWebRequestWinHTTP::HandleCallback(DWORD dwInternetStatus, + LPVOID lpvStatusInformation, DWORD dwStatusInformationLength) +{ + bool handleLastError = false; + static const int readSize = 8 * 1024; + + switch ( dwInternetStatus ) + { + case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE: + if ( ::WinHttpReceiveResponse(m_request, NULL) ) + { + m_response.reset(new wxWebResponseWinHTTP(*this)); + if ( CheckServerStatus() ) + { + // Start reading the response + if ( !m_response->ReadData() ) + handleLastError = true; + } + } + else + handleLastError = true; + break; + case WINHTTP_CALLBACK_STATUS_READ_COMPLETE: + if ( dwStatusInformationLength > 0 ) + { + if ( !m_response->ReportAvailableData(dwStatusInformationLength) ) + handleLastError = true; + } + else + { + m_response->ReportDataComplete(); + SetState(State_Ready); + } + break; + case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR: + { + LPWINHTTP_ASYNC_RESULT asyncResult = reinterpret_cast(lpvStatusInformation); + SetState(State_Failed, wxWinHTTPErrorToString(asyncResult->dwError)); + break; + } + } + + if (handleLastError) + SetFailedWithLastError(); +} + +void wxWebRequestWinHTTP::SetFailedWithLastError() +{ + wxString failMessage = wxWinHTTPErrorToString(::GetLastError()); + SetState(State_Failed, failMessage); +} + +void wxWebRequestWinHTTP::SetMethod(const wxString& method) +{ + // TODO: implement +} + +void wxWebRequestWinHTTP::SetData(const wxString& text, const wxString& contentType) +{ + // TODO: implement +} + +void wxWebRequestWinHTTP::SetData(const wxInputStream& dataStream, const wxString& contentType) +{ + // TODO: implement +} + +void wxWebRequestWinHTTP::Start() +{ + if ( GetState() != State_Idle ) // Completed requests can not be restarted + return; + + // Parse the URL + wxURI uri(m_url); + bool isSecure = uri.GetScheme().IsSameAs("HTTPS", false); + + int port; + if ( !uri.HasPort() ) + port = (isSecure) ? 443 : 80; + else + port = wxAtoi(uri.GetPort()); + + // Open a connction + m_connect = ::WinHttpConnect(m_session.GetHandle(), uri.GetServer().wc_str(), + port, 0); + if ( m_connect == NULL ) + { + SetFailedWithLastError(); + return; + } + + // Open a request + m_request = ::WinHttpOpenRequest(m_connect, + L"GET", uri.GetPath().wc_str(), + NULL, WINHTTP_NO_REFERER, + WINHTTP_DEFAULT_ACCEPT_TYPES, + (isSecure) ? WINHTTP_FLAG_SECURE : 0); + if ( m_request == NULL ) + { + SetFailedWithLastError(); + return; + } + + // Register callback + if ( ::WinHttpSetStatusCallback(m_request, + (WINHTTP_STATUS_CALLBACK)wxRequestStatusCallback, + WINHTTP_CALLBACK_FLAG_READ_COMPLETE | + WINHTTP_CALLBACK_FLAG_SENDREQUEST_COMPLETE | + WINHTTP_CALLBACK_FLAG_REQUEST_ERROR, + 0) == WINHTTP_INVALID_STATUS_CALLBACK ) + { + SetFailedWithLastError(); + return; + } + + // Combine all headers to a string + wxString allHeaders; + for (wxWebRequestHeaderMap::const_iterator header = m_headers.begin(); header != m_headers.end(); ++header) + allHeaders.append(wxString::Format("%s: %s\n", header->first, header->second)); + + // Send request + if ( WinHttpSendRequest(m_request, + allHeaders.wc_str(), allHeaders.length(), + NULL, 0, 0, + (DWORD_PTR)this) ) + { + SetState(State_Active); + } + else + SetFailedWithLastError(); +} + +void wxWebRequestWinHTTP::Cancel() +{ + // TODO: implement +} + +wxWebResponse* wxWebRequestWinHTTP::GetResponse() +{ + return m_response.get(); +} + +// +// wxWebResponseWinHTTP +// + +wxWebResponseWinHTTP::wxWebResponseWinHTTP(wxWebRequestWinHTTP& request): + m_request(request), + m_readSize(8 * 1024) +{ + wxString contentLengthStr = wxWinHTTPQueryHeaderString(m_request.GetHandle(), + WINHTTP_QUERY_CONTENT_LENGTH); + if ( contentLengthStr.empty() || + !contentLengthStr.ToLongLong(&m_contentLength) ) + m_contentLength = -1; +} + +wxString wxWebResponseWinHTTP::GetURL() const +{ + return wxWinHTTPQueryOptionString(m_request.GetHandle(), WINHTTP_OPTION_URL); +} + +wxString wxWebResponseWinHTTP::GetHeader(const wxString& name) const +{ + return wxWinHTTPQueryHeaderString(m_request.GetHandle(), + WINHTTP_QUERY_CUSTOM, name.wc_str()); +} + +int wxWebResponseWinHTTP::GetStatus() const +{ + DWORD status = 0; + DWORD statusSize = sizeof(status); + if ( !::WinHttpQueryHeaders(m_request.GetHandle(), + WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, + WINHTTP_HEADER_NAME_BY_INDEX, &status, &statusSize, 0) ) + { + status = 0; + wxLogLastError("WinHttpQueryHeaders/status"); + } + + return status; +} + +wxString wxWebResponseWinHTTP::GetStatusText() const +{ + return wxWinHTTPQueryHeaderString(m_request.GetHandle(), WINHTTP_QUERY_STATUS_TEXT); +} + +wxInputStream* wxWebResponseWinHTTP::GetStream() const +{ + return m_stream.get(); +} + +wxString wxWebResponseWinHTTP::AsString(wxMBConv* conv) const +{ + // TODO: try to determine encoding type from content-type header + if ( !conv ) + conv = &wxConvUTF8; + + size_t outLen = 0; + return conv->cMB2WC((const char*) m_readBuffer.GetData(), m_readBuffer.GetDataLen(), &outLen); +} + +bool wxWebResponseWinHTTP::ReadData() +{ + if ( ::WinHttpReadData(m_request.GetHandle(), + m_readBuffer.GetAppendBuf(m_readSize), m_readSize, NULL) ) + return true; + else + return false; +} + +bool wxWebResponseWinHTTP::ReportAvailableData(DWORD dataLen) +{ + m_readBuffer.UngetAppendBuf(dataLen); + return ReadData(); +} + +void wxWebResponseWinHTTP::ReportDataComplete() +{ + m_stream.reset(new wxMemoryInputStream(m_readBuffer.GetData(), m_readBuffer.GetDataLen())); +} + +// +// wxWebSessionWinHTTP +// + +wxWebSessionWinHTTP::wxWebSessionWinHTTP(): + m_initialized(false), + m_handle(NULL) +{ +} + +wxWebSessionWinHTTP::~wxWebSessionWinHTTP() +{ + if ( m_handle != INVALID_HANDLE_VALUE ) + ::WinHttpCloseHandle(m_handle); +} + +void wxWebSessionWinHTTP::Init() +{ + DWORD accessType; + if ( wxCheckOsVersion(6, 3) ) + accessType = WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY; + else + accessType = WINHTTP_ACCESS_TYPE_DEFAULT_PROXY; + + m_handle = ::WinHttpOpen(GetHeaders().find("User-Agent")->second.wc_str(), accessType, + WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, + WINHTTP_FLAG_ASYNC); + if ( m_handle != NULL ) + { + // Try to enable HTTP/2 (available since Win 10 1607) + DWORD protFlags = WINHTTP_PROTOCOL_FLAG_HTTP2; + ::WinHttpSetOption(m_handle, WINHTTP_OPTION_ENABLE_HTTP_PROTOCOL, + &protFlags, sizeof(protFlags)); + + // Try to enable GZIP and DEFLATE (available since Win 8.1) + DWORD decompressFlags = WINHTTP_DECOMPRESSION_FLAG_ALL; + ::WinHttpSetOption(m_handle, WINHTTP_OPTION_DECOMPRESSION, + &decompressFlags, sizeof(decompressFlags)); + + // Try to enable modern TLS for older Windows versions + if ( !wxCheckOsVersion(6, 3) ) + { + DWORD securityFlags = WINHTTP_FLAG_SECURE_PROTOCOL_SSL3 | + WINHTTP_FLAG_SECURE_PROTOCOL_TLS1 | + WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 | + WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2; + ::WinHttpSetOption(m_handle, WINHTTP_OPTION_SECURE_PROTOCOLS, + &securityFlags, sizeof(securityFlags)); + } + } + else + wxLogLastError("WinHttpOpen"); + + m_initialized = true; +} + wxWebRequest* wxWebSessionWinHTTP::CreateRequest(const wxString& url, int id) { - return NULL; -} - -void wxWebSessionWinHTTP::SetHeader(const wxString& name, const wxString& value) -{ + if ( !m_initialized ) + Init(); + return new wxWebRequestWinHTTP(id, *this, url); } #endif // wxUSE_WEBREQUEST