4#include "Stroika/Foundation/StroikaPreComp.h"
8#if qStroika_HasComponent_WinHTTP
21#include "Stroika/Foundation/Execution/Throw.h"
22#include "Stroika/Foundation/Execution/TimeOutException.h"
23#include "Stroika/Foundation/IO/Network/HTTP/Exception.h"
24#include "Stroika/Foundation/IO/Network/HTTP/Headers.h"
29#if qStroika_Foundation_Common_Platform_Windows
30#include "Stroika/Foundation/Execution/Platform/Windows/Exception.h"
31#include "Stroika/Foundation/Execution/Platform/Windows/HRESULTErrorException.h"
34#include "Connection_WinHTTP.h"
38using namespace Stroika::Foundation::IO;
42using namespace Stroika::Foundation::Memory;
43using namespace Stroika::Foundation::Time;
45#if qStroika_HasComponent_WinHTTP
46using IO::Network::Transfer::WinHTTP::Connection::Options;
52#if qStroika_Foundation_Common_Platform_Windows
67#if qStroika_HasComponent_WinHTTP
70#pragma comment(lib, "Winhttp.lib")
73#if qStroika_HasComponent_WinHTTP
75 struct AutoWinHINTERNET_ {
77 explicit AutoWinHINTERNET_ (HINTERNET handle)
82 AutoWinHINTERNET_ () =
delete;
83 AutoWinHINTERNET_ (
const AutoWinHINTERNET_&) =
delete;
86 Verify (::WinHttpCloseHandle (fHandle));
92 nonvirtual
const AutoWinHINTERNET_& operator= (
const AutoWinHINTERNET_&) =
delete;
95 class Rep_ :
public Connection::IRep {
97 Rep_ (
const Connection::Options& options)
101 Rep_ (
const Rep_&) =
delete;
104 fConnectionHandle_.reset ();
105 fSessionHandle_.reset ();
109 nonvirtual Rep_& operator= (
const Rep_&) =
delete;
112 virtual Options GetOptions ()
const override
124 virtual URI GetSchemeAndAuthority ()
const override
128 virtual void SetSchemeAndAuthority (
const URI& schemeAndAuthority)
override
130#if USE_NOISY_TRACE_IN_THIS_MODULE_
131 DbgTrace (
"Connection_WinHTTP::Rep_::SetSchemeAndAuthority ('{}')"_f, schemeAndAuthority);
135 newURL.SetAuthority (schemeAndAuthority.
GetAuthority ());
136 if (fURL_ != newURL) {
137 fConnectionHandle_.reset ();
141 virtual void Close ()
override
143 fConnectionHandle_.reset ();
144 fSessionHandle_.reset ();
148#if USE_NOISY_TRACE_IN_THIS_MODULE_
153 SetAuthorityRelativeURL_ (useRequest.fAuthorityRelativeURL);
158 optional<Response::SSLResultInfo> serverEndpointSSLInfo;
169 String userAgent = fOptions_.fUserAgent;
174 useHeadersMap.
Add (HeaderName::kAcceptEncoding,
String{});
177 if (fOptions_.fCache !=
nullptr) {
178 if (
auto r = fOptions_.fCache->OnBeforeFetch (&cacheContext, fURL_.GetSchemeAndAuthority (), &useRequest)) {
184 if (useHeadersMap.
Lookup (HeaderName::kUserAgent, &userAgent)) {
185 useHeadersMap.
Remove (HeaderName::kUserAgent);
188 if (fOptions_.fAuthentication and
189 fOptions_.fAuthentication->GetOptions () == Connection::Options::Authentication::Options::eProactivelySendAuthentication) {
190 useHeadersMap.
Add (HeaderName::kAuthorization, fOptions_.fAuthentication->GetAuthToken ());
194 for (
auto i = useHeadersMap.
begin (); i != useHeadersMap.
end (); ++i) {
195 useHeaderStrBuf += i->fKey +
": "sv + i->fValue +
"\r\n"sv;
199 AssureHasSessionHandle_ (userAgent);
200 Assert (fSessionHandle_ !=
nullptr);
201 AssureHasConnectionHandle_ ();
202 Assert (fConnectionHandle_ !=
nullptr);
204 if (fOptions_.fTCPKeepAlives) {
211 bool useSecureHTTP = fURL_.GetScheme () and fURL_.GetScheme ()->IsSecure ();
213 AutoWinHINTERNET_ hRequest{::WinHttpOpenRequest (*fConnectionHandle_, useRequest.fMethod.As<wstring> ().c_str (),
214 fURL_.GetAuthorityRelativeResource ().As<wstring> ().c_str (),
nullptr, WINHTTP_NO_REFERER,
215 WINHTTP_DEFAULT_ACCEPT_TYPES, useSecureHTTP ? WINHTTP_FLAG_SECURE : 0)};
219 constexpr bool kDefault_FailConnectionIfSSLCertificateInvalid{
true};
220 if (not fOptions_.fReturnSSLInfo and
221 not fOptions_.fFailConnectionIfSSLCertificateInvalid.value_or (kDefault_FailConnectionIfSSLCertificateInvalid)) {
222 DWORD dwOptions = SECURITY_FLAG_IGNORE_CERT_CN_INVALID | SECURITY_FLAG_IGNORE_CERT_DATE_INVALID |
223 SECURITY_FLAG_IGNORE_UNKNOWN_CA | SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE;
224 Verify (::WinHttpSetOption (hRequest, WINHTTP_OPTION_SECURITY_FLAGS, &dwOptions,
sizeof (dwOptions)));
226 if (not fOptions_.fSupportSessionCookies) {
231 DWORD dwOptions = WINHTTP_DISABLE_COOKIES;
232 Verify (::WinHttpSetOption (hRequest, WINHTTP_OPTION_DISABLE_FEATURE, &dwOptions,
sizeof (dwOptions)));
237 bool sslExceptionProblem =
false;
238 RetryWithNoCERTCheck:
246 if (fOptions_.fReturnSSLInfo and
247 not fOptions_.fFailConnectionIfSSLCertificateInvalid.value_or (kDefault_FailConnectionIfSSLCertificateInvalid) and sslExceptionProblem) {
248 DWORD dwOptions = SECURITY_FLAG_IGNORE_CERT_CN_INVALID | SECURITY_FLAG_IGNORE_CERT_DATE_INVALID |
249 SECURITY_FLAG_IGNORE_UNKNOWN_CA | SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE;
250 Verify (::WinHttpSetOption (hRequest, WINHTTP_OPTION_SECURITY_FLAGS, &dwOptions,
sizeof (dwOptions)));
255 if (useRequest.fData.size () > numeric_limits<DWORD>::max ()) {
259 ::WinHttpSendRequest (hRequest, useHeaderStrBuf.
As<wstring> ().c_str (),
static_cast<DWORD
> (-1),
260 useRequest.fData.empty () ?
nullptr : const_cast<byte*> (useRequest.fData.begin ()),
261 static_cast<DWORD> (useRequest.fData.size ()), static_cast<DWORD> (useRequest.fData.size ()), NULL));
266 catch (
const system_error& e) {
267 if (fOptions_.fReturnSSLInfo) {
268 bool looksLikeSSLError = (e.code () == error_code (ERROR_WINHTTP_SECURE_FAILURE, system_category ()));
269 if (looksLikeSSLError and not sslExceptionProblem) {
270 DbgTrace (
"Got {} ssl error so retrying with flags to disable cert checking"_f, e.code ().value ());
271 sslExceptionProblem =
true;
272 goto RetryWithNoCERTCheck;
275 Execution::ReThrow ();
278 list<vector<byte>> bytesRead;
279 unsigned int totalBytes = 0;
284 if (Time::GetTickCount () > endBy) [[unlikely]] {
292 memset (outBuffer.data (), 0, dwSize);
293 DWORD dwDownloaded = 0;
295 Assert (dwDownloaded <= dwSize);
296 totalBytes += dwDownloaded;
297 bytesRead.push_back (vector<byte>{outBuffer.begin (), outBuffer.begin () + dwDownloaded});
298 }
while (dwSize > 0);
308 for (
auto i = bytesRead.begin (); i != bytesRead.end (); ++i) {
310 for (
auto ii = v2.begin (); ii != v2.end (); ++ii) {
311 bytesArray[iii] = *ii;
315 Assert (iii == totalBytes);
316 data =
BLOB{bytesArray.
begin (), bytesArray.end ()};
322 wstring statusStr = Extract_WinHttpHeader_ (hRequest, WINHTTP_QUERY_STATUS_CODE, WINHTTP_HEADER_NAME_BY_INDEX, WINHTTP_NO_HEADER_INDEX);
323 wstring statusText = Extract_WinHttpHeader_ (hRequest, WINHTTP_QUERY_STATUS_TEXT, WINHTTP_HEADER_NAME_BY_INDEX, WINHTTP_NO_HEADER_INDEX);
324 status =
static_cast<HTTP::Status> (_wtoi (statusStr.c_str ()));
325#if USE_NOISY_TRACE_IN_THIS_MODULE_
330 if (status == 401 and not got401 and fOptions_.fAuthentication and
331 fOptions_.fAuthentication->GetOptions () == Connection::Options::Authentication::Options::eRespondToWWWAuthenticate) {
333 DWORD dwSupportedSchemes{};
334 DWORD dwFirstScheme{};
336 if (::WinHttpQueryAuthSchemes (hRequest, &dwSupportedSchemes, &dwFirstScheme, &dwTarget)) {
337 auto chooseAuthScheme = [] (DWORD supportedSchemes) -> DWORD {
340 if (supportedSchemes & WINHTTP_AUTH_SCHEME_NEGOTIATE)
341 return WINHTTP_AUTH_SCHEME_NEGOTIATE;
342 else if (supportedSchemes & WINHTTP_AUTH_SCHEME_NTLM)
343 return WINHTTP_AUTH_SCHEME_NTLM;
344 else if (supportedSchemes & WINHTTP_AUTH_SCHEME_PASSPORT)
345 return WINHTTP_AUTH_SCHEME_PASSPORT;
346 else if (supportedSchemes & WINHTTP_AUTH_SCHEME_DIGEST)
347 return WINHTTP_AUTH_SCHEME_DIGEST;
348 else if (supportedSchemes & WINHTTP_AUTH_SCHEME_BASIC)
349 return WINHTTP_AUTH_SCHEME_BASIC;
353 DWORD dwSelectedScheme = chooseAuthScheme (dwSupportedSchemes);
354 if (dwSelectedScheme != 0) {
355 auto nameAndPassword = *fOptions_.fAuthentication->GetUsernameAndPassword ();
356 Verify (::WinHttpSetCredentials (hRequest, dwTarget, dwSelectedScheme, nameAndPassword.first.AsSDKString ().c_str (),
357 nameAndPassword.second.AsSDKString ().c_str (),
nullptr));
369 if (useSecureHTTP and fOptions_.fReturnSSLInfo) {
370 WINHTTP_CERTIFICATE_INFO certInfo{};
371 DWORD dwCertInfoSize =
sizeof (certInfo);
372 certInfo.dwKeySize =
sizeof (certInfo);
373 ThrowIfZeroGetLastError (::WinHttpQueryOption (hRequest, WINHTTP_OPTION_SECURITY_CERTIFICATE_STRUCT, &certInfo, &dwCertInfoSize));
374 [[maybe_unused]]
auto&& cleanup = Execution::Finally ([certInfo] ()
noexcept {
375 if (certInfo.lpszSubjectInfo !=
nullptr) {
376 ::LocalFree (certInfo.lpszSubjectInfo);
378 if (certInfo.lpszIssuerInfo !=
nullptr) {
379 ::LocalFree (certInfo.lpszIssuerInfo);
381 if (certInfo.lpszEncryptionAlgName !=
nullptr) {
382 ::LocalFree (certInfo.lpszEncryptionAlgName);
384 if (certInfo.lpszProtocolName !=
nullptr) {
385 ::LocalFree (certInfo.lpszProtocolName);
387 if (certInfo.lpszSignatureAlgName !=
nullptr) {
388 ::LocalFree (certInfo.lpszSignatureAlgName);
393 resultSSLInfo.fValidationStatus = sslExceptionProblem ? Response::SSLResultInfo::ValidationStatus::eSSLFailure
394 : Response::SSLResultInfo::ValidationStatus::eSSLOK;
395 if (certInfo.lpszSubjectInfo !=
nullptr) {
396 wstring subject = certInfo.lpszSubjectInfo;
397 resultSSLInfo.fSubjectCommonName = subject;
399 size_t i = subject.find (
'\r');
400 if (i != wstring::npos) {
401 resultSSLInfo.fSubjectCommonName = resultSSLInfo.fSubjectCommonName.substr (0, i);
405 if (certInfo.lpszIssuerInfo !=
nullptr) {
406 resultSSLInfo.fIssuer = certInfo.lpszIssuerInfo;
409 Date startCertDate = DateTime{certInfo.ftStart}.GetDate ();
410 Date endCertDate = DateTime{certInfo.ftExpiry}.GetDate ();
411 Date now = DateTime::GetToday ();
412 if (now < startCertDate) {
413 resultSSLInfo.fValidationStatus = Response::SSLResultInfo::ValidationStatus::eCertNotYetValid;
415 else if (endCertDate < now) {
416 resultSSLInfo.fValidationStatus = Response::SSLResultInfo::ValidationStatus::eCertExpired;
419 if (not fURL_.GetAuthority () or not fURL_.GetAuthority ()->GetHost () or not fURL_.GetAuthority ()->GetHost ()->AsRegisteredName ()) {
421 Execution::Throw (kException_);
424 if (not equalsComparer (*fURL_.GetAuthority ()->GetHost ()->AsRegisteredName (), resultSSLInfo.fSubjectCommonName) and
425 not equalsComparer (*fURL_.GetAuthority ()->GetHost ()->AsRegisteredName (),
"www."sv + resultSSLInfo.fSubjectCommonName)) {
426 resultSSLInfo.fValidationStatus = Response::SSLResultInfo::ValidationStatus::eHostnameMismatch;
429 serverEndpointSSLInfo = resultSSLInfo;
434 wstring rr = Extract_WinHttpHeader_ (hRequest, WINHTTP_QUERY_RAW_HEADERS_CRLF, WINHTTP_HEADER_NAME_BY_INDEX, WINHTTP_NO_HEADER_INDEX);
436 for (
size_t i = 0; i < rr.length ();) {
437 size_t endOfRegion = rr.find_first_of (L
"\r\n", i);
438 if (endOfRegion == wstring::npos) {
439 endOfRegion = rr.length ();
441 wstring thisLine = rr.substr (i, endOfRegion - i);
443 if (thisLine.length () > 5) {
445 size_t colonI = thisLine.find (
':');
446 if (colonI != wstring::npos) {
447 wstring key = Characters::CString::Trim (thisLine.substr (0, colonI));
448 wstring val = Characters::CString::Trim (thisLine.substr (colonI + 1));
449 if (not key.empty ()) {
450 headers.
Add (key, val);
458 Response result{data, status, headers, serverEndpointSSLInfo};
459 if (fOptions_.fCache !=
nullptr) {
460 fOptions_.fCache->OnAfterFetch (cacheContext, &result);
467 nonvirtual
void SetAuthorityRelativeURL_ (
const URI& url)
471 newURL.SetAuthority (fURL_.GetAuthority ());
473 fConnectionHandle_.reset ();
479 nonvirtual
void AssureHasSessionHandle_ (
const String& userAgent)
481 if (fSessionHandle_UserAgent_ != userAgent) {
482 fConnectionHandle_.reset ();
483 fSessionHandle_.reset ();
485 if (fSessionHandle_ ==
nullptr) {
486 fSessionHandle_ = Memory::MakeSharedPtr<AutoWinHINTERNET_> (::WinHttpOpen (
487 userAgent.
As<wstring> ().c_str (), WINHTTP_ACCESS_TYPE_NO_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0));
488 fSessionHandle_UserAgent_ = userAgent;
489 if (fOptions_.fMaxAutomaticRedirects == 0) {
490 DWORD dwOptions = WINHTTP_OPTION_REDIRECT_POLICY_NEVER;
491 Verify (::WinHttpSetOption (*fSessionHandle_, WINHTTP_OPTION_REDIRECT_POLICY, &dwOptions,
sizeof (dwOptions)));
494 DWORD dwOptions = WINHTTP_OPTION_REDIRECT_POLICY_DISALLOW_HTTPS_TO_HTTP;
495 Verify (::WinHttpSetOption (*fSessionHandle_, WINHTTP_OPTION_REDIRECT_POLICY, &dwOptions,
sizeof (dwOptions)));
501 nonvirtual
void AssureHasConnectionHandle_ ()
504 if (fConnectionHandle_ ==
nullptr) {
505 if (not fURL_.GetAuthority () or not fURL_.GetAuthority ()->GetHost ()) {
507 Execution::Throw (kException_);
510 fConnectionHandle_ = Memory::MakeSharedPtr<AutoWinHINTERNET_> (::WinHttpConnect (
511 *fSessionHandle_, fURL_.GetAuthority ()->GetHost ()->As<
String> (URI::StringPCTEncodedFlag::ePCTEncoded).
As<wstring> ().c_str (),
512 fURL_.GetPortValue (), 0));
517 static wstring Extract_WinHttpHeader_ (HINTERNET hRequest, DWORD dwInfoLevel, LPCWSTR pwszName, LPDWORD lpdwIndex)
520 (void)::WinHttpQueryHeaders (hRequest, dwInfoLevel, pwszName, WINHTTP_NO_OUTPUT_BUFFER, &size, lpdwIndex);
521 DWORD error = GetLastError ();
522 if (error == ERROR_INSUFFICIENT_BUFFER) {
524 (void)::memset (buf.data (), 0, buf.GetSize ());
525 ThrowIfZeroGetLastError (::WinHttpQueryHeaders (hRequest, dwInfoLevel, pwszName, buf.data (), &size, lpdwIndex));
529 Execution::ThrowSystemErrNo (error);
535 Connection::Options fOptions_;
538 shared_ptr<AutoWinHINTERNET_> fSessionHandle_;
539 String fSessionHandle_UserAgent_;
540 shared_ptr<AutoWinHINTERNET_> fConnectionHandle_;
550Connection::Ptr Transfer::WinHTTP::Connection::New (
const Options& options)
#define RequireNotNull(p)
#define CompileTimeFlagChecker_SOURCE(NS_PREFIX, NAME, VALUE)
time_point< RealtimeClock, DurationSeconds > TimePointSeconds
TimePointSeconds is a simpler approach to chrono::time_point, which doesn't require using templates e...
chrono::duration< double > DurationSeconds
chrono::duration<double> - a time span (length of time) measured in seconds, but high precision.
Similar to String, but intended to more efficiently construct a String. Mutable type (String is large...
nonvirtual RESULT_T As() const
String is like std::u32string, except it is much easier to use, often much more space efficient,...
nonvirtual bool Add(ArgByValueType< key_type > key, ArgByValueType< mapped_type > newElt, AddReplaceMode addReplaceMode=AddReplaceMode::eAddReplaces)
nonvirtual optional< mapped_type > Lookup(ArgByValueType< key_type > key) const
nonvirtual void Remove(ArgByValueType< key_type > key)
Remove the given item (which must exist).
Exception<> is a replacement (subclass) for any std c++ exception class (e.g. the default 'std::excep...
static const TimeOutException kThe
nonvirtual URI GetSchemeAndAuthority() const
nonvirtual optional< SchemeType > GetScheme() const
nonvirtual optional< Authority > GetAuthority() const
nonvirtual void SetScheme(const optional< SchemeType > &scheme)
nonvirtual const byte * begin() const
Logically halfway between std::array and std::vector; Smart 'direct memory array' - which when needed...
nonvirtual Iterator< T > begin() const
Support for ranged for, and STL syntax in general.
static constexpr default_sentinel_t end() noexcept
Support for ranged for, and STL syntax in general.
void Throw(T &&e2Throw)
identical to builtin C++ 'throw' except that it does helpful, type dependent DbgTrace() messages firs...