Files
wxWidgets/src/common/webrequest.cpp
Vadim Zeitlin 65aad890e3 Add GetNativeHandle() to wxWebSession and wxWebRequest
This allows to retrieve the handles used internally in order to do
something not supported by the public API yet.
2021-01-16 00:21:00 +01:00

980 lines
22 KiB
C++

///////////////////////////////////////////////////////////////////////////////
// Name: src/common/webrequest.cpp
// Purpose: wxWebRequest base class implementations
// Author: Tobias Taschner
// Created: 2018-10-17
// Copyright: (c) 2018 wxWidgets development team
// Licence: wxWindows licence
///////////////////////////////////////////////////////////////////////////////
// For compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"
#if wxUSE_WEBREQUEST
#include "wx/webrequest.h"
#include "wx/mstream.h"
#include "wx/module.h"
#include "wx/uri.h"
#include "wx/filefn.h"
#include "wx/filename.h"
#include "wx/stdpaths.h"
#include "wx/wfstream.h"
#ifndef WX_PRECOMP
#include "wx/app.h"
#include "wx/translation.h"
#include "wx/utils.h"
#endif
#include "wx/private/webrequest.h"
#if wxUSE_WEBREQUEST_WINHTTP
#include "wx/msw/private/webrequest_winhttp.h"
#endif
#if wxUSE_WEBREQUEST_URLSESSION
#include "wx/osx/private/webrequest_urlsession.h"
#endif
#if wxUSE_WEBREQUEST_CURL
#include "wx/private/webrequest_curl.h"
#endif
extern WXDLLIMPEXP_DATA_NET(const char) wxWebSessionBackendWinHTTP[] = "WinHTTP";
extern WXDLLIMPEXP_DATA_NET(const char) wxWebSessionBackendURLSession[] = "URLSession";
extern WXDLLIMPEXP_DATA_NET(const char) wxWebSessionBackendCURL[] = "CURL";
wxDEFINE_EVENT(wxEVT_WEBREQUEST_STATE, wxWebRequestEvent);
wxDEFINE_EVENT(wxEVT_WEBREQUEST_DATA, wxWebRequestEvent);
static const wxStringCharType* wxNO_IMPL_MSG
= wxS("can't be used with an invalid/uninitialized object");
#define wxCHECK_IMPL(rc) wxCHECK_MSG( m_impl, (rc), wxNO_IMPL_MSG )
#define wxCHECK_IMPL_VOID() wxCHECK_RET( m_impl, wxNO_IMPL_MSG )
//
// wxWebRequestImpl
//
wxWebRequestImpl::wxWebRequestImpl(wxWebSession& session,
wxWebSessionImpl& sessionImpl,
wxEvtHandler* handler,
int id)
: m_storage(wxWebRequest::Storage_Memory),
m_headers(sessionImpl.GetHeaders()),
m_dataSize(0),
m_session(session),
m_handler(handler),
m_id(id),
m_state(wxWebRequest::State_Idle),
m_bytesReceived(0)
{
}
void wxWebRequestImpl::SetFinalStateFromStatus()
{
const wxWebResponseImplPtr& resp = GetResponse();
if ( !resp || resp->GetStatus() >= 400 )
{
wxString err;
if ( resp )
{
err.Printf(_("Error: %s (%d)"),
resp->GetStatusText(), resp->GetStatus());
}
SetState(wxWebRequest::State_Failed, err);
}
else
{
SetState(wxWebRequest::State_Completed);
}
}
bool wxWebRequestImpl::IsActiveState(wxWebRequest::State state)
{
return (state == wxWebRequest::State_Active || state == wxWebRequest::State_Unauthorized);
}
void wxWebRequestImpl::SetData(const wxString& text, const wxString& contentType, const wxMBConv& conv)
{
m_dataText = text.mb_str(conv);
wxScopedPtr<wxInputStream>
stream(new wxMemoryInputStream(m_dataText, m_dataText.length()));
SetData(stream, contentType);
}
bool
wxWebRequestImpl::SetData(wxScopedPtr<wxInputStream>& dataStream,
const wxString& contentType,
wxFileOffset dataSize)
{
m_dataStream.reset(dataStream.release());
if ( m_dataStream )
{
wxCHECK_MSG( m_dataStream->IsOk(), false, "can't use invalid stream" );
if ( dataSize == wxInvalidOffset )
{
// Determine data size
m_dataSize = m_dataStream->SeekI(0, wxFromEnd);
if ( m_dataSize == wxInvalidOffset )
return false;
m_dataStream->SeekI(0);
}
else
m_dataSize = dataSize;
}
else
m_dataSize = 0;
SetHeader("Content-Type", contentType);
return true;
}
wxFileOffset wxWebRequestImpl::GetBytesReceived() const
{
return m_bytesReceived;
}
wxFileOffset wxWebRequestImpl::GetBytesExpectedToReceive() const
{
if ( GetResponse() )
return GetResponse()->GetContentLength();
else
return -1;
}
namespace
{
// Functor used with CallAfter() below.
//
// TODO-C++11: Replace with a lambda.
struct StateEventProcessor
{
StateEventProcessor(wxWebRequestImpl& request,
wxWebRequest::State state,
const wxString& failMsg)
: m_request(request), m_state(state), m_failMsg(failMsg)
{
// Ensure that the request object stays alive until this event is
// processed.
m_request.IncRef();
}
StateEventProcessor(const StateEventProcessor& other)
: m_request(other.m_request), m_state(other.m_state), m_failMsg(other.m_failMsg)
{
m_request.IncRef();
}
void operator()()
{
m_request.ProcessStateEvent(m_state, m_failMsg);
}
~StateEventProcessor()
{
m_request.DecRef();
}
wxWebRequestImpl& m_request;
const wxWebRequest::State m_state;
const wxString m_failMsg;
};
} // anonymous namespace
void wxWebRequestImpl::SetState(wxWebRequest::State state, const wxString & failMsg)
{
wxLogTrace(wxTRACE_WEBREQUEST, "Request %p: state => %d", this, state);
m_state = state;
// Trigger the event in the main thread
m_handler->CallAfter(StateEventProcessor(*this, state, failMsg));
}
void wxWebRequestImpl::ReportDataReceived(size_t sizeReceived)
{
m_bytesReceived += sizeReceived;
}
// The SplitParamaters implementation is adapted to wxWidgets
// from Poco::Net::MessageHeader::splitParameters
// This function is used in a unit test, so define it inside wxPrivate
// namespace and an anonymous one.
namespace wxPrivate
{
WXDLLIMPEXP_NET wxString
SplitParameters(const wxString& s, wxWebRequestHeaderMap& parameters)
{
wxString value;
wxString::const_iterator it = s.begin();
wxString::const_iterator end = s.end();
while ( it != end && wxIsspace(*it) )
++it;
while ( it != end && *it != ';' )
value += *it++;
value.Trim();
if ( it != end )
++it;
parameters.clear();
wxString pname;
wxString pvalue;
pname.reserve(32);
pvalue.reserve(64);
while ( it != end )
{
pname.clear();
pvalue.clear();
while ( it != end && wxIsspace(*it) )
++it;
while ( it != end && *it != '=' && *it != ';' )
pname += *it++;
pname.Trim();
if ( it != end && *it != ';' )
++it;
while ( it != end && wxIsspace(*it) )
++it;
while ( it != end && *it != ';' )
{
if ( *it == '"' )
{
++it;
while ( it != end && *it != '"' )
{
if ( *it == '\\' )
{
++it;
if ( it != end )
pvalue += *it++;
}
else
pvalue += *it++;
}
if ( it != end )
++it;
}
else if ( *it == '\\' )
{
++it;
if ( it != end )
pvalue += *it++;
}
else
pvalue += *it++;
}
pvalue.Trim();
if ( !pname.empty() )
parameters[pname] = pvalue;
if ( it != end )
++it;
}
return value;
}
} // namespace wxPrivate
void wxWebRequestImpl::ProcessStateEvent(wxWebRequest::State state, const wxString& failMsg)
{
if ( !IsActiveState(state) && GetResponse() )
GetResponse()->Finalize();
wxString dataFile;
wxWebRequestEvent evt(wxEVT_WEBREQUEST_STATE, GetId(), state,
wxWebResponse(GetResponse()), failMsg);
if ( state == wxWebRequest::State_Completed && m_storage == wxWebRequest::Storage_File )
{
dataFile = GetResponse()->GetDataFile();
evt.SetDataFile(dataFile);
}
m_handler->ProcessEvent(evt);
// Remove temporary file if we're using one and if it still exists: it
// could have been deleted or moved away by the event handler.
if ( !dataFile.empty() && wxFileExists(dataFile) )
wxRemoveFile(dataFile);
}
//
// wxWebRequest
//
wxWebRequest::wxWebRequest()
{
}
wxWebRequest::wxWebRequest(const wxWebRequestImplPtr& impl)
: m_impl(impl)
{
}
wxWebRequest::wxWebRequest(const wxWebRequest& other)
: m_impl(other.m_impl)
{
}
wxWebRequest& wxWebRequest::operator=(const wxWebRequest& other)
{
m_impl = other.m_impl;
return *this;
}
wxWebRequest::~wxWebRequest()
{
}
void wxWebRequest::SetHeader(const wxString& name, const wxString& value)
{
wxCHECK_IMPL_VOID();
m_impl->SetHeader(name, value);
}
void wxWebRequest::SetMethod(const wxString& method)
{
wxCHECK_IMPL_VOID();
m_impl->SetMethod(method);
}
void wxWebRequest::SetData(const wxString& text, const wxString& contentType, const wxMBConv& conv)
{
wxCHECK_IMPL_VOID();
m_impl->SetData(text, contentType, conv);
}
bool
wxWebRequest::SetData(wxInputStream* dataStream,
const wxString& contentType,
wxFileOffset dataSize)
{
// Ensure that the stream is destroyed even we return below.
wxScopedPtr<wxInputStream> streamPtr(dataStream);
wxCHECK_IMPL( false );
return m_impl->SetData(streamPtr, contentType, dataSize);
}
void wxWebRequest::SetStorage(Storage storage)
{
wxCHECK_IMPL_VOID();
m_impl->SetStorage(storage);
}
wxWebRequest::Storage wxWebRequest::GetStorage() const
{
wxCHECK_IMPL( Storage_None );
return m_impl->GetStorage();
}
void wxWebRequest::Start()
{
wxCHECK_IMPL_VOID();
wxCHECK_RET( m_impl->GetState() == wxWebRequest::State_Idle,
"Completed requests can not be restarted" );
m_impl->Start();
}
void wxWebRequest::Cancel()
{
wxCHECK_IMPL_VOID();
m_impl->Cancel();
}
wxWebResponse wxWebRequest::GetResponse() const
{
wxCHECK_IMPL( wxWebResponse() );
return wxWebResponse(m_impl->GetResponse());
}
wxWebAuthChallenge wxWebRequest::GetAuthChallenge() const
{
wxCHECK_IMPL( wxWebAuthChallenge() );
return wxWebAuthChallenge(m_impl->GetAuthChallenge());
}
int wxWebRequest::GetId() const
{
wxCHECK_IMPL( wxID_ANY );
return m_impl->GetId();
}
wxWebSession& wxWebRequest::GetSession() const
{
wxCHECK_IMPL( wxWebSession::GetDefault() );
return m_impl->GetSession();
}
wxWebRequest::State wxWebRequest::GetState() const
{
wxCHECK_IMPL( State_Failed );
return m_impl->GetState();
}
wxFileOffset wxWebRequest::GetBytesSent() const
{
wxCHECK_IMPL( wxInvalidOffset );
return m_impl->GetBytesSent();
}
wxFileOffset wxWebRequest::GetBytesExpectedToSend() const
{
wxCHECK_IMPL( wxInvalidOffset );
return m_impl->GetBytesExpectedToSend();
}
wxFileOffset wxWebRequest::GetBytesReceived() const
{
wxCHECK_IMPL( wxInvalidOffset );
return m_impl->GetBytesReceived();
}
wxFileOffset wxWebRequest::GetBytesExpectedToReceive() const
{
wxCHECK_IMPL( wxInvalidOffset );
return m_impl->GetBytesExpectedToReceive();
}
wxWebRequestHandle wxWebRequest::GetNativeHandle() const
{
return m_impl ? m_impl->GetNativeHandle() : NULL;
}
//
// wxWebAuthChallenge
//
wxWebAuthChallenge::wxWebAuthChallenge()
{
}
wxWebAuthChallenge::wxWebAuthChallenge(const wxWebAuthChallengeImplPtr& impl)
: m_impl(impl)
{
}
wxWebAuthChallenge::wxWebAuthChallenge(const wxWebAuthChallenge& other)
: m_impl(other.m_impl)
{
}
wxWebAuthChallenge& wxWebAuthChallenge::operator=(const wxWebAuthChallenge& other)
{
m_impl = other.m_impl;
return *this;
}
wxWebAuthChallenge::~wxWebAuthChallenge()
{
}
wxWebAuthChallenge::Source wxWebAuthChallenge::GetSource() const
{
wxCHECK_IMPL( Source_Server );
return m_impl->GetSource();
}
void
wxWebAuthChallenge::SetCredentials(const wxWebCredentials& cred)
{
wxCHECK_IMPL_VOID();
m_impl->SetCredentials(cred);
}
//
// wxWebResponseImpl
//
wxWebResponseImpl::wxWebResponseImpl(wxWebRequestImpl& request) :
m_request(request),
m_readSize(wxWEBREQUEST_BUFFER_SIZE)
{
}
wxWebResponseImpl::~wxWebResponseImpl()
{
if ( wxFileExists(m_file.GetName()) )
wxRemoveFile(m_file.GetName());
}
void wxWebResponseImpl::Init()
{
if ( m_request.GetStorage() == wxWebRequest::Storage_File )
{
wxFileName tmpPrefix;
tmpPrefix.AssignDir(m_request.GetSession().GetTempDir());
if ( GetContentLength() > 0 )
{
// Check available disk space
wxLongLong freeSpace;
if ( wxGetDiskSpace(tmpPrefix.GetFullPath(), NULL, &freeSpace) &&
GetContentLength() > freeSpace )
{
m_request.SetState(wxWebRequest::State_Failed, _("Not enough free disk space for download."));
return;
}
}
tmpPrefix.SetName("wxd");
wxFileName::CreateTempFileName(tmpPrefix.GetFullPath(), &m_file);
}
}
wxString wxWebResponseImpl::GetMimeType() const
{
return GetHeader("Mime-Type");
}
wxInputStream * wxWebResponseImpl::GetStream() const
{
if ( !m_stream.get() )
{
// Create stream
switch ( m_request.GetStorage() )
{
case wxWebRequest::Storage_Memory:
m_stream.reset(new wxMemoryInputStream(m_readBuffer.GetData(), m_readBuffer.GetDataLen()));
break;
case wxWebRequest::Storage_File:
m_stream.reset(new wxFFileInputStream(m_file));
m_stream->SeekI(0);
break;
case wxWebRequest::Storage_None:
// No stream available
break;
}
}
return m_stream.get();
}
wxString wxWebResponseImpl::GetSuggestedFileName() const
{
wxString suggestedFilename;
// Try to determine from Content-Disposition header
wxString contentDisp = GetHeader("Content-Disposition");
wxWebRequestHeaderMap params;
const wxString disp = wxPrivate::SplitParameters(contentDisp, params);
if ( disp == "attachment" )
{
// Parse as filename to filter potential path names
wxFileName fn(params["filename"]);
suggestedFilename = fn.GetFullName();
}
if ( suggestedFilename.empty() )
{
wxURI uri(GetURL());
if ( uri.HasPath() )
{
wxFileName fn(uri.GetPath());
suggestedFilename = fn.GetFullName();
}
else
suggestedFilename = uri.GetServer();
}
return suggestedFilename;
}
wxString wxWebResponseImpl::AsString() const
{
if ( m_request.GetStorage() == wxWebRequest::Storage_Memory )
{
// TODO: try to determine encoding type from content-type header
size_t outLen = 0;
return wxConvWhateverWorks.cMB2WC((const char*)m_readBuffer.GetData(), m_readBuffer.GetDataLen(), &outLen);
}
else
return wxString();
}
void* wxWebResponseImpl::GetDataBuffer(size_t sizeNeeded)
{
return m_readBuffer.GetAppendBuf(sizeNeeded);
}
void wxWebResponseImpl::ReportDataReceived(size_t sizeReceived)
{
m_readBuffer.UngetAppendBuf(sizeReceived);
m_request.ReportDataReceived(sizeReceived);
if ( m_request.GetStorage() == wxWebRequest::Storage_File )
{
m_file.Write(m_readBuffer.GetData(), m_readBuffer.GetDataLen());
}
else if ( m_request.GetStorage() == wxWebRequest::Storage_None )
{
wxWebRequestEvent evt(wxEVT_WEBREQUEST_DATA, m_request.GetId(), wxWebRequest::State_Active);
evt.SetDataBuffer(m_readBuffer.GetData(), m_readBuffer.GetDataLen());
m_request.GetHandler()->ProcessEvent(evt);
}
if ( m_request.GetStorage() != wxWebRequest::Storage_Memory )
m_readBuffer.Clear();
}
wxString wxWebResponseImpl::GetDataFile() const
{
return m_file.GetName();
}
void wxWebResponseImpl::Finalize()
{
if ( m_request.GetStorage() == wxWebRequest::Storage_File )
m_file.Close();
}
//
// wxWebResponse
//
wxWebResponse::wxWebResponse()
{
}
wxWebResponse::wxWebResponse(const wxWebResponseImplPtr& impl)
: m_impl(impl)
{
}
wxWebResponse::wxWebResponse(const wxWebResponse& other)
: m_impl(other.m_impl)
{
}
wxWebResponse& wxWebResponse::operator=(const wxWebResponse& other)
{
m_impl = other.m_impl;
return *this;
}
wxWebResponse::~wxWebResponse()
{
}
wxFileOffset wxWebResponse::GetContentLength() const
{
wxCHECK_IMPL( -1 );
return m_impl->GetContentLength();
}
wxString wxWebResponse::GetURL() const
{
wxCHECK_IMPL( wxString() );
return m_impl->GetURL();
}
wxString wxWebResponse::GetHeader(const wxString& name) const
{
wxCHECK_IMPL( wxString() );
return m_impl->GetHeader(name);
}
wxString wxWebResponse::GetMimeType() const
{
wxCHECK_IMPL( wxString() );
return m_impl->GetMimeType();
}
int wxWebResponse::GetStatus() const
{
wxCHECK_IMPL( -1 );
return m_impl->GetStatus();
}
wxString wxWebResponse::GetStatusText() const
{
wxCHECK_IMPL( wxString() );
return m_impl->GetStatusText();
}
wxInputStream* wxWebResponse::GetStream() const
{
wxCHECK_IMPL( NULL );
return m_impl->GetStream();
}
wxString wxWebResponse::GetSuggestedFileName() const
{
wxCHECK_IMPL( wxString() );
return m_impl->GetSuggestedFileName();
}
wxString wxWebResponse::AsString() const
{
wxCHECK_IMPL( wxString() );
return m_impl->AsString();
}
wxString wxWebResponse::GetDataFile() const
{
wxCHECK_IMPL( wxString() );
return m_impl->GetDataFile();
}
//
// wxWebSessionImpl
//
WX_DECLARE_STRING_HASH_MAP(wxWebSessionFactory*, wxStringWebSessionFactoryMap);
namespace
{
wxWebSession gs_defaultSession;
wxStringWebSessionFactoryMap gs_factoryMap;
} // anonymous namespace
wxWebSessionImpl::wxWebSessionImpl()
{
// Initialize the user-Agent header with a reasonable default
AddCommonHeader("User-Agent", wxString::Format("%s/1 wxWidgets/%d.%d.%d",
wxTheApp->GetAppName(),
wxMAJOR_VERSION, wxMINOR_VERSION, wxRELEASE_NUMBER));
}
wxString wxWebSessionImpl::GetTempDir() const
{
if ( m_tempDir.empty() )
return wxStandardPaths::Get().GetTempDir();
else
return m_tempDir;
}
//
// wxWebSession
//
wxWebSession::wxWebSession()
{
}
wxWebSession::wxWebSession(const wxWebSessionImplPtr& impl)
: m_impl(impl)
{
}
wxWebSession::wxWebSession(const wxWebSession& other)
: m_impl(other.m_impl)
{
}
wxWebSession& wxWebSession::operator=(const wxWebSession& other)
{
m_impl = other.m_impl;
return *this;
}
wxWebSession::~wxWebSession()
{
}
// static
wxWebSession& wxWebSession::GetDefault()
{
if ( !gs_defaultSession.IsOpened() )
gs_defaultSession = wxWebSession::New();
return gs_defaultSession;
}
// static
wxWebSession wxWebSession::New(const wxString& backendOrig)
{
if ( gs_factoryMap.empty() )
InitFactoryMap();
wxString backend = backendOrig;
if ( backend.empty() )
{
if ( !wxGetEnv("WXWEBREQUEST_BACKEND", &backend) )
{
#if wxUSE_WEBREQUEST_WINHTTP
backend = wxWebSessionBackendWinHTTP;
#elif wxUSE_WEBREQUEST_URLSESSION
backend = wxWebSessionBackendURLSession;
#elif wxUSE_WEBREQUEST_CURL
backend = wxWebSessionBackendCURL;
#endif
}
}
wxStringWebSessionFactoryMap::iterator factory = gs_factoryMap.find(backend);
wxWebSessionImplPtr impl;
if ( factory != gs_factoryMap.end() )
impl = factory->second->Create();
return wxWebSession(impl);
}
// static
void
wxWebSession::RegisterFactory(const wxString& backend,
wxWebSessionFactory* factory)
{
// Note that we don't have to check here that there is no registered
// backend with the same name yet because we're only called from
// InitFactoryMap() below. If this function becomes public, we'd need to
// free the previous pointer stored for this backend first here.
gs_factoryMap[backend] = factory;
}
// static
void wxWebSession::InitFactoryMap()
{
#if wxUSE_WEBREQUEST_WINHTTP
RegisterFactory(wxWebSessionBackendWinHTTP, new wxWebSessionFactoryWinHTTP());
#endif
#if wxUSE_WEBREQUEST_URLSESSION
RegisterFactory(wxWebSessionBackendURLSession, new wxWebSessionFactoryURLSession());
#endif
#if wxUSE_WEBREQUEST_CURL
RegisterFactory(wxWebSessionBackendCURL, new wxWebSessionFactoryCURL());
#endif
}
// static
bool wxWebSession::IsBackendAvailable(const wxString& backend)
{
if ( gs_factoryMap.empty() )
InitFactoryMap();
wxStringWebSessionFactoryMap::iterator factory = gs_factoryMap.find(backend);
return factory != gs_factoryMap.end();
}
wxWebRequest
wxWebSession::CreateRequest(wxEvtHandler* handler, const wxString& url, int id)
{
wxCHECK_IMPL( wxWebRequest() );
return wxWebRequest(m_impl->CreateRequest(*this, handler, url, id));
}
wxVersionInfo wxWebSession::GetLibraryVersionInfo()
{
wxCHECK_IMPL( wxVersionInfo() );
return m_impl->GetLibraryVersionInfo();
}
void wxWebSession::AddCommonHeader(const wxString& name, const wxString& value)
{
wxCHECK_IMPL_VOID();
m_impl->AddCommonHeader(name, value);
}
void wxWebSession::SetTempDir(const wxString& dir)
{
wxCHECK_IMPL_VOID();
m_impl->SetTempDir(dir);
}
wxString wxWebSession::GetTempDir() const
{
wxCHECK_IMPL( wxString() );
return m_impl->GetTempDir();
}
bool wxWebSession::IsOpened() const
{
return m_impl.get() != NULL;
}
void wxWebSession::Close()
{
m_impl.reset(NULL);
}
wxWebSessionHandle wxWebSession::GetNativeHandle() const
{
return m_impl ? m_impl->GetNativeHandle() : NULL;
}
// ----------------------------------------------------------------------------
// Module ensuring all global/singleton objects are destroyed on shutdown.
// ----------------------------------------------------------------------------
class WebRequestModule : public wxModule
{
public:
WebRequestModule()
{
}
virtual bool OnInit() wxOVERRIDE
{
return true;
}
virtual void OnExit() wxOVERRIDE
{
for ( wxStringWebSessionFactoryMap::iterator it = gs_factoryMap.begin();
it != gs_factoryMap.end();
++it )
{
delete it->second;
}
gs_factoryMap.clear();
gs_defaultSession.Close();
}
private:
wxDECLARE_DYNAMIC_CLASS(WebRequestModule);
};
wxIMPLEMENT_DYNAMIC_CLASS(WebRequestModule, wxModule);
#endif // wxUSE_WEBREQUEST