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"
28#if qStroika_Foundation_Common_Platform_Windows
29#include "Stroika/Foundation/Execution/Platform/Windows/Exception.h"
30#include "Stroika/Foundation/Execution/Platform/Windows/HRESULTErrorException.h"
33#include "Connection_WinHTTP.h"
37using namespace Stroika::Foundation::IO;
41using namespace Stroika::Foundation::Memory;
42using namespace Stroika::Foundation::Time;
44#if qStroika_HasComponent_WinHTTP
45using IO::Network::Transfer::WinHTTP::Connection::Options;
51#if qStroika_Foundation_Common_Platform_Windows
66#if qStroika_HasComponent_WinHTTP
69#pragma comment(lib, "Winhttp.lib")
72#if qStroika_HasComponent_WinHTTP
74 struct AutoWinHINTERNET_ {
76 explicit AutoWinHINTERNET_ (HINTERNET handle)
81 AutoWinHINTERNET_ () =
delete;
82 AutoWinHINTERNET_ (
const AutoWinHINTERNET_&) =
delete;
85 Verify (::WinHttpCloseHandle (fHandle));
91 nonvirtual
const AutoWinHINTERNET_& operator= (
const AutoWinHINTERNET_&) =
delete;
94 class Rep_ :
public Connection::IRep {
96 Rep_ (
const Connection::Options& options)
100 Rep_ (
const Rep_&) =
delete;
103 fConnectionHandle_.reset ();
104 fSessionHandle_.reset ();
108 nonvirtual Rep_& operator= (
const Rep_&) =
delete;
111 virtual Options GetOptions ()
const override
123 virtual URI GetSchemeAndAuthority ()
const override
127 virtual void SetSchemeAndAuthority (
const URI& schemeAndAuthority)
override
129#if USE_NOISY_TRACE_IN_THIS_MODULE_
130 DbgTrace (
"Connection_WinHTTP::Rep_::SetSchemeAndAuthority ('{}')"_f, schemeAndAuthority);
134 newURL.SetAuthority (schemeAndAuthority.
GetAuthority ());
135 if (fURL_ != newURL) {
136 fConnectionHandle_.reset ();
140 virtual void Close ()
override
142 fConnectionHandle_.reset ();
143 fSessionHandle_.reset ();
147#if USE_NOISY_TRACE_IN_THIS_MODULE_
152 SetAuthorityRelativeURL_ (useRequest.fAuthorityRelativeURL);
157 optional<Response::SSLResultInfo> serverEndpointSSLInfo;
168 String userAgent = fOptions_.fUserAgent;
173 useHeadersMap.
Add (HeaderName::kAcceptEncoding,
String{});
176 if (fOptions_.fCache !=
nullptr) {
177 if (
auto r = fOptions_.fCache->OnBeforeFetch (&cacheContext, fURL_.GetSchemeAndAuthority (), &useRequest)) {
183 if (useHeadersMap.
Lookup (HeaderName::kUserAgent, &userAgent)) {
184 useHeadersMap.
Remove (HeaderName::kUserAgent);
187 if (fOptions_.fAuthentication and
188 fOptions_.fAuthentication->GetOptions () == Connection::Options::Authentication::Options::eProactivelySendAuthentication) {
189 useHeadersMap.
Add (HeaderName::kAuthorization, fOptions_.fAuthentication->GetAuthToken ());
193 for (
auto i = useHeadersMap.
begin (); i != useHeadersMap.
end (); ++i) {
194 useHeaderStrBuf += i->fKey +
": "sv + i->fValue +
"\r\n"sv;
198 AssureHasSessionHandle_ (userAgent);
199 Assert (fSessionHandle_ !=
nullptr);
200 AssureHasConnectionHandle_ ();
201 Assert (fConnectionHandle_ !=
nullptr);
203 if (fOptions_.fTCPKeepAlives) {
210 bool useSecureHTTP = fURL_.GetScheme () and fURL_.GetScheme ()->IsSecure ();
212 AutoWinHINTERNET_ hRequest{::WinHttpOpenRequest (*fConnectionHandle_, useRequest.fMethod.As<wstring> ().c_str (),
213 fURL_.GetAuthorityRelativeResource ().As<wstring> ().c_str (),
nullptr, WINHTTP_NO_REFERER,
214 WINHTTP_DEFAULT_ACCEPT_TYPES, useSecureHTTP ? WINHTTP_FLAG_SECURE : 0)};
218 constexpr bool kDefault_FailConnectionIfSSLCertificateInvalid{
true};
219 if (not fOptions_.fReturnSSLInfo and
220 not fOptions_.fFailConnectionIfSSLCertificateInvalid.value_or (kDefault_FailConnectionIfSSLCertificateInvalid)) {
221 DWORD dwOptions = SECURITY_FLAG_IGNORE_CERT_CN_INVALID | SECURITY_FLAG_IGNORE_CERT_DATE_INVALID |
222 SECURITY_FLAG_IGNORE_UNKNOWN_CA | SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE;
223 Verify (::WinHttpSetOption (hRequest, WINHTTP_OPTION_SECURITY_FLAGS, &dwOptions,
sizeof (dwOptions)));
225 if (not fOptions_.fSupportSessionCookies) {
230 DWORD dwOptions = WINHTTP_DISABLE_COOKIES;
231 Verify (::WinHttpSetOption (hRequest, WINHTTP_OPTION_DISABLE_FEATURE, &dwOptions,
sizeof (dwOptions)));
236 bool sslExceptionProblem =
false;
237 RetryWithNoCERTCheck:
245 if (fOptions_.fReturnSSLInfo and
246 not fOptions_.fFailConnectionIfSSLCertificateInvalid.value_or (kDefault_FailConnectionIfSSLCertificateInvalid) and sslExceptionProblem) {
247 DWORD dwOptions = SECURITY_FLAG_IGNORE_CERT_CN_INVALID | SECURITY_FLAG_IGNORE_CERT_DATE_INVALID |
248 SECURITY_FLAG_IGNORE_UNKNOWN_CA | SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE;
249 Verify (::WinHttpSetOption (hRequest, WINHTTP_OPTION_SECURITY_FLAGS, &dwOptions,
sizeof (dwOptions)));
254 if (useRequest.fData.size () > numeric_limits<DWORD>::max ()) {
258 ::WinHttpSendRequest (hRequest, useHeaderStrBuf.
As<wstring> ().c_str (),
static_cast<DWORD
> (-1),
259 useRequest.fData.empty () ?
nullptr : const_cast<byte*> (useRequest.fData.begin ()),
260 static_cast<DWORD> (useRequest.fData.size ()), static_cast<DWORD> (useRequest.fData.size ()), NULL));
265 catch (
const system_error& e) {
266 if (fOptions_.fReturnSSLInfo) {
267 bool looksLikeSSLError = (e.code () == error_code (ERROR_WINHTTP_SECURE_FAILURE, system_category ()));
268 if (looksLikeSSLError and not sslExceptionProblem) {
269 DbgTrace (
"Got {} ssl error so retrying with flags to disable cert checking"_f, e.code ().value ());
270 sslExceptionProblem =
true;
271 goto RetryWithNoCERTCheck;
277 list<vector<byte>> bytesRead;
278 unsigned int totalBytes = 0;
283 if (Time::GetTickCount () > endBy) [[unlikely]] {
291 memset (outBuffer.data (), 0, dwSize);
292 DWORD dwDownloaded = 0;
294 Assert (dwDownloaded <= dwSize);
295 totalBytes += dwDownloaded;
296 bytesRead.push_back (vector<byte>{outBuffer.begin (), outBuffer.begin () + dwDownloaded});
297 }
while (dwSize > 0);
307 for (
auto i = bytesRead.begin (); i != bytesRead.end (); ++i) {
309 for (
auto ii = v2.begin (); ii != v2.end (); ++ii) {
310 bytesArray[iii] = *ii;
314 Assert (iii == totalBytes);
315 data =
BLOB{bytesArray.
begin (), bytesArray.end ()};
321 wstring statusStr = Extract_WinHttpHeader_ (hRequest, WINHTTP_QUERY_STATUS_CODE, WINHTTP_HEADER_NAME_BY_INDEX, WINHTTP_NO_HEADER_INDEX);
322 wstring statusText = Extract_WinHttpHeader_ (hRequest, WINHTTP_QUERY_STATUS_TEXT, WINHTTP_HEADER_NAME_BY_INDEX, WINHTTP_NO_HEADER_INDEX);
323 status =
static_cast<HTTP::Status> (_wtoi (statusStr.c_str ()));
324#if USE_NOISY_TRACE_IN_THIS_MODULE_
329 if (status == 401 and not got401 and fOptions_.fAuthentication and
330 fOptions_.fAuthentication->GetOptions () == Connection::Options::Authentication::Options::eRespondToWWWAuthenticate) {
332 DWORD dwSupportedSchemes{};
333 DWORD dwFirstScheme{};
335 if (::WinHttpQueryAuthSchemes (hRequest, &dwSupportedSchemes, &dwFirstScheme, &dwTarget)) {
336 auto chooseAuthScheme = [] (DWORD supportedSchemes) -> DWORD {
339 if (supportedSchemes & WINHTTP_AUTH_SCHEME_NEGOTIATE)
340 return WINHTTP_AUTH_SCHEME_NEGOTIATE;
341 else if (supportedSchemes & WINHTTP_AUTH_SCHEME_NTLM)
342 return WINHTTP_AUTH_SCHEME_NTLM;
343 else if (supportedSchemes & WINHTTP_AUTH_SCHEME_PASSPORT)
344 return WINHTTP_AUTH_SCHEME_PASSPORT;
345 else if (supportedSchemes & WINHTTP_AUTH_SCHEME_DIGEST)
346 return WINHTTP_AUTH_SCHEME_DIGEST;
347 else if (supportedSchemes & WINHTTP_AUTH_SCHEME_BASIC)
348 return WINHTTP_AUTH_SCHEME_BASIC;
352 DWORD dwSelectedScheme = chooseAuthScheme (dwSupportedSchemes);
353 if (dwSelectedScheme != 0) {
354 auto nameAndPassword = *fOptions_.fAuthentication->GetUsernameAndPassword ();
355 Verify (::WinHttpSetCredentials (hRequest, dwTarget, dwSelectedScheme, nameAndPassword.first.AsSDKString ().c_str (),
356 nameAndPassword.second.AsSDKString ().c_str (),
nullptr));
368 if (useSecureHTTP and fOptions_.fReturnSSLInfo) {
369 WINHTTP_CERTIFICATE_INFO certInfo{};
370 DWORD dwCertInfoSize =
sizeof (certInfo);
371 certInfo.dwKeySize =
sizeof (certInfo);
372 ThrowIfZeroGetLastError (::WinHttpQueryOption (hRequest, WINHTTP_OPTION_SECURITY_CERTIFICATE_STRUCT, &certInfo, &dwCertInfoSize));
374 if (certInfo.lpszSubjectInfo !=
nullptr) {
375 ::LocalFree (certInfo.lpszSubjectInfo);
377 if (certInfo.lpszIssuerInfo !=
nullptr) {
378 ::LocalFree (certInfo.lpszIssuerInfo);
380 if (certInfo.lpszEncryptionAlgName !=
nullptr) {
381 ::LocalFree (certInfo.lpszEncryptionAlgName);
383 if (certInfo.lpszProtocolName !=
nullptr) {
384 ::LocalFree (certInfo.lpszProtocolName);
386 if (certInfo.lpszSignatureAlgName !=
nullptr) {
387 ::LocalFree (certInfo.lpszSignatureAlgName);
392 resultSSLInfo.fValidationStatus = sslExceptionProblem ? Response::SSLResultInfo::ValidationStatus::eSSLFailure
393 : Response::SSLResultInfo::ValidationStatus::eSSLOK;
394 if (certInfo.lpszSubjectInfo !=
nullptr) {
395 wstring subject = certInfo.lpszSubjectInfo;
396 resultSSLInfo.fSubjectCommonName = subject;
398 size_t i = subject.find (
'\r');
399 if (i != wstring::npos) {
400 resultSSLInfo.fSubjectCommonName = resultSSLInfo.fSubjectCommonName.substr (0, i);
404 if (certInfo.lpszIssuerInfo !=
nullptr) {
405 resultSSLInfo.fIssuer = certInfo.lpszIssuerInfo;
408 Date startCertDate = DateTime{certInfo.ftStart}.GetDate ();
409 Date endCertDate = DateTime{certInfo.ftExpiry}.GetDate ();
410 Date now = DateTime::GetToday ();
411 if (now < startCertDate) {
412 resultSSLInfo.fValidationStatus = Response::SSLResultInfo::ValidationStatus::eCertNotYetValid;
414 else if (endCertDate < now) {
415 resultSSLInfo.fValidationStatus = Response::SSLResultInfo::ValidationStatus::eCertExpired;
418 if (not fURL_.GetAuthority () or not fURL_.GetAuthority ()->GetHost () or not fURL_.GetAuthority ()->GetHost ()->AsRegisteredName ()) {
423 if (not equalsComparer (*fURL_.GetAuthority ()->GetHost ()->AsRegisteredName (), resultSSLInfo.fSubjectCommonName) and
424 not equalsComparer (*fURL_.GetAuthority ()->GetHost ()->AsRegisteredName (),
"www."sv + resultSSLInfo.fSubjectCommonName)) {
425 resultSSLInfo.fValidationStatus = Response::SSLResultInfo::ValidationStatus::eHostnameMismatch;
428 serverEndpointSSLInfo = resultSSLInfo;
433 wstring rr = Extract_WinHttpHeader_ (hRequest, WINHTTP_QUERY_RAW_HEADERS_CRLF, WINHTTP_HEADER_NAME_BY_INDEX, WINHTTP_NO_HEADER_INDEX);
435 for (
size_t i = 0; i < rr.length ();) {
436 size_t endOfRegion = rr.find_first_of (L
"\r\n", i);
437 if (endOfRegion == wstring::npos) {
438 endOfRegion = rr.length ();
440 wstring thisLine = rr.substr (i, endOfRegion - i);
442 if (thisLine.length () > 5) {
444 size_t colonI = thisLine.find (
':');
445 if (colonI != wstring::npos) {
446 wstring key = Characters::CString::Trim (thisLine.substr (0, colonI));
447 wstring val = Characters::CString::Trim (thisLine.substr (colonI + 1));
448 if (not key.empty ()) {
449 headers.
Add (key, val);
457 Response result{data, status, headers, serverEndpointSSLInfo};
458 if (fOptions_.fCache !=
nullptr) {
459 fOptions_.fCache->OnAfterFetch (cacheContext, &result);
466 nonvirtual
void SetAuthorityRelativeURL_ (
const URI& url)
470 newURL.SetAuthority (fURL_.GetAuthority ());
472 fConnectionHandle_.reset ();
478 nonvirtual
void AssureHasSessionHandle_ (
const String& userAgent)
480 if (fSessionHandle_UserAgent_ != userAgent) {
481 fConnectionHandle_.reset ();
482 fSessionHandle_.reset ();
484 if (fSessionHandle_ ==
nullptr) {
485 fSessionHandle_ = make_shared<AutoWinHINTERNET_> (::WinHttpOpen (userAgent.
As<wstring> ().c_str (), WINHTTP_ACCESS_TYPE_NO_PROXY,
486 WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0));
487 fSessionHandle_UserAgent_ = userAgent;
488 if (fOptions_.fMaxAutomaticRedirects == 0) {
489 DWORD dwOptions = WINHTTP_OPTION_REDIRECT_POLICY_NEVER;
490 Verify (::WinHttpSetOption (*fSessionHandle_, WINHTTP_OPTION_REDIRECT_POLICY, &dwOptions,
sizeof (dwOptions)));
493 DWORD dwOptions = WINHTTP_OPTION_REDIRECT_POLICY_DISALLOW_HTTPS_TO_HTTP;
494 Verify (::WinHttpSetOption (*fSessionHandle_, WINHTTP_OPTION_REDIRECT_POLICY, &dwOptions,
sizeof (dwOptions)));
500 nonvirtual
void AssureHasConnectionHandle_ ()
503 if (fConnectionHandle_ ==
nullptr) {
504 if (not fURL_.GetAuthority () or not fURL_.GetAuthority ()->GetHost ()) {
509 fConnectionHandle_ = make_shared<AutoWinHINTERNET_> (::WinHttpConnect (
510 *fSessionHandle_, fURL_.GetAuthority ()->GetHost ()->As<
String> (URI::StringPCTEncodedFlag::ePCTEncoded).
As<wstring> ().c_str (),
511 fURL_.GetPortValue (), 0));
516 static wstring Extract_WinHttpHeader_ (HINTERNET hRequest, DWORD dwInfoLevel, LPCWSTR pwszName, LPDWORD lpdwIndex)
519 (void)::WinHttpQueryHeaders (hRequest, dwInfoLevel, pwszName, WINHTTP_NO_OUTPUT_BUFFER, &size, lpdwIndex);
520 DWORD error = GetLastError ();
521 if (error == ERROR_INSUFFICIENT_BUFFER) {
523 (void)::memset (buf.data (), 0, buf.GetSize ());
524 ThrowIfZeroGetLastError (::WinHttpQueryHeaders (hRequest, dwInfoLevel, pwszName, buf.data (), &size, lpdwIndex));
528 Execution::ThrowSystemErrNo (error);
534 Connection::Options fOptions_;
537 shared_ptr<AutoWinHINTERNET_> fSessionHandle_;
538 String fSessionHandle_UserAgent_;
539 shared_ptr<AutoWinHINTERNET_> fConnectionHandle_;
549Connection::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...
auto Finally(FUNCTION &&f) -> Private_::FinallySentry< FUNCTION >