From c3cb1c1d1b5d00a4e58f8dc4f244cd1583360592 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Fri, 19 Dec 2025 18:01:15 +0100 Subject: [PATCH] Allow reusing POST data when using wxWebRequestCURL Set seek callback to allow libcurl to reuse the data being uploaded when it has to redo the HTTP request, e.g. because of redirection after a 301 or resubmitting it with correct authentication data after a 401. Add a test checking that this works correctly, although it has to be disabled for WinHTTP for now as it doesn't handle 307/308 redirects automatically (see #26046). --- include/wx/private/webrequest_curl.h | 3 +- src/common/webrequest_curl.cpp | 45 +++++++++++++++++++++++++--- tests/net/webrequest.cpp | 24 +++++++++++++++ 3 files changed, 67 insertions(+), 5 deletions(-) diff --git a/include/wx/private/webrequest_curl.h b/include/wx/private/webrequest_curl.h index ae184061e3..e05dc48828 100644 --- a/include/wx/private/webrequest_curl.h +++ b/include/wx/private/webrequest_curl.h @@ -89,8 +89,9 @@ public: wxString GetError() const; - // Method called from libcurl callback + // These functions implement libcurl callbacks. size_t CURLOnRead(char* buffer, size_t size); + int CURLOnSeek(curl_off_t offset, int origin); private: // Common initialization for sync and async requests performed when the diff --git a/src/common/webrequest_curl.cpp b/src/common/webrequest_curl.cpp index 6ad4d18828..a913474158 100644 --- a/src/common/webrequest_curl.cpp +++ b/src/common/webrequest_curl.cpp @@ -50,6 +50,10 @@ #define CURLOPT_ACCEPT_ENCODING CURLOPT_ENCODING #endif +#if wxUSE_LOG_TRACE +constexpr const char* TRACE_CURL = "curl"; +#endif + // Define libcurl timeout constants static constexpr int LIBCURL_DEFAULT_CONNECT_TIMEOUT = 300000; // 5m in ms. @@ -335,6 +339,13 @@ static size_t wxCURLRead(char *buffer, size_t size, size_t nitems, void *userdat return static_cast(userdata)->CURLOnRead(buffer, size * nitems); } +static int wxCURLSeek(void* userdata, curl_off_t offset, int origin) +{ + wxCHECK_MSG( userdata, CURL_SEEKFUNC_CANTSEEK, "invalid curl seek callback data" ); + + return static_cast(userdata)->CURLOnSeek(offset, origin); +} + wxWebRequestCURL::wxWebRequestCURL(wxWebSession & session, wxWebSessionCURL& sessionImpl, wxEvtHandler* handler, @@ -377,6 +388,8 @@ void wxWebRequestCURL::DoStartPrepare(const wxString& url) wxCURLSetOpt(m_handle, CURLOPT_HEADERFUNCTION, wxCURLHeader); wxCURLSetOpt(m_handle, CURLOPT_READFUNCTION, wxCURLRead); wxCURLSetOpt(m_handle, CURLOPT_READDATA, this); + wxCURLSetOpt(m_handle, CURLOPT_SEEKFUNCTION, wxCURLSeek); + wxCURLSetOpt(m_handle, CURLOPT_SEEKDATA, this); // Enable gzip, etc decompression wxCURLSetOpt(m_handle, CURLOPT_ACCEPT_ENCODING, ""); // Enable redirection handling @@ -618,6 +631,34 @@ size_t wxWebRequestCURL::CURLOnRead(char* buffer, size_t size) return 0; } +int wxWebRequestCURL::CURLOnSeek(curl_off_t offset, int origin) +{ + wxSeekMode mode = wxFromStart; + switch ( origin ) + { + case SEEK_SET: + mode = wxFromStart; + break; + + case SEEK_CUR: + mode = wxFromCurrent; + break; + + case SEEK_END: + mode = wxFromEnd; + break; + + default: + wxLogTrace(TRACE_CURL, "Seek function: unknown origin %d", origin); + return CURL_SEEKFUNC_CANTSEEK; + } + + if ( m_dataStream->SeekI(offset, mode) == wxInvalidOffset ) + return CURL_SEEKFUNC_CANTSEEK; + + return CURL_SEEKFUNC_OK; +} + wxFileOffset wxWebRequestCURL::GetBytesSent() const { return m_bytesSent; @@ -875,10 +916,6 @@ using SocketPollerBase = WinSock1SocketPoller; #else -#if wxUSE_LOG_TRACE -constexpr const char* TRACE_CURL = "curl"; -#endif - // SocketPollerSourceHandler - a source handler used by the SocketPoller class. class SourceSocketPoller; diff --git a/tests/net/webrequest.cpp b/tests/net/webrequest.cpp index 65a6cf446d..a2f90a0368 100644 --- a/tests/net/webrequest.cpp +++ b/tests/net/webrequest.cpp @@ -1064,6 +1064,30 @@ TEST_CASE_METHOD(SyncRequestFixture, CHECK( response.GetStatus() == 200 ); } +TEST_CASE_METHOD(SyncRequestFixture, + "WebRequest::Sync::PostAfterRedirect", "[net][webrequest][sync]") +{ + if ( !InitBaseURL() ) + return; + + // We can't test this when using WinHTTP because we need to use either 307 + // or 308 redirect status code to preserve the POST method across the + // redirect (all backends switch to GET for 301 and 302, although it would + // be possible to configure this to preserve POST when using libcurl) and + // WinHTTP doesn't handle them automatically. + const auto& versionInfo = wxWebSession::GetDefault().GetLibraryVersionInfo(); + if ( versionInfo.GetName() == "WinHTTP" ) + { + WARN("Skipping POST with redirect test with WinHTTP backend"); + return; + } + + Create("redirect-to?url=post&status_code=307"); + request.SetData("app=WebRequestRedirect&version=1", "application/x-www-form-urlencoded"); + REQUIRE( Execute() ); + CHECK( response.GetStatus() == 200 ); +} + TEST_CASE_METHOD(SyncRequestFixture, "WebRequest::Sync::Put", "[net][webrequest][sync]") {