Stroika Library 3.0d23x
 
Loading...
Searching...
No Matches
Connection_WinHTTP.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#include "Stroika/Foundation/StroikaPreComp.h"
5
6#include <optional>
7
8#if qStroika_HasComponent_WinHTTP
9#include <windows.h>
10
11#include <Winhttp.h>
12#endif
13
14#include <list>
15
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
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"
32#endif
33
34#include "Connection_WinHTTP.h"
35
36using namespace Stroika::Foundation;
38using namespace Stroika::Foundation::IO;
42using namespace Stroika::Foundation::Memory;
43using namespace Stroika::Foundation::Time;
44
45#if qStroika_HasComponent_WinHTTP
46using IO::Network::Transfer::WinHTTP::Connection::Options;
47#endif
48using Memory::BLOB;
49
50using std::byte;
51
52#if qStroika_Foundation_Common_Platform_Windows
54#endif
56
57CompileTimeFlagChecker_SOURCE (Stroika::Foundation::IO::Network::Transfer, qStroika_HasComponent_WinHTTP, qStroika_HasComponent_WinHTTP);
58
59// Comment this in to turn on aggressive noisy DbgTrace in this module
60// #define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
61
62/*
63 * TODO:
64 *
65 */
66
67#if qStroika_HasComponent_WinHTTP
68// otherwise modules linking with this code will tend to get link errors without explicitly linking
69// to this module...
70#pragma comment(lib, "Winhttp.lib")
71#endif
72
73#if qStroika_HasComponent_WinHTTP
74namespace {
75 struct AutoWinHINTERNET_ {
76 HINTERNET fHandle;
77 explicit AutoWinHINTERNET_ (HINTERNET handle)
78 : fHandle{handle}
79 {
81 }
82 AutoWinHINTERNET_ () = delete;
83 AutoWinHINTERNET_ (const AutoWinHINTERNET_&) = delete;
84 ~AutoWinHINTERNET_ ()
85 {
86 Verify (::WinHttpCloseHandle (fHandle));
87 }
88 operator HINTERNET ()
89 {
90 return fHandle;
91 }
92 nonvirtual const AutoWinHINTERNET_& operator= (const AutoWinHINTERNET_&) = delete;
93 };
94
95 class Rep_ : public Connection::IRep {
96 public:
97 Rep_ (const Connection::Options& options)
98 : fOptions_{options}
99 {
100 }
101 Rep_ (const Rep_&) = delete;
102 virtual ~Rep_ ()
103 {
104 fConnectionHandle_.reset ();
105 fSessionHandle_.reset ();
106 }
107
108 public:
109 nonvirtual Rep_& operator= (const Rep_&) = delete;
110
111 public:
112 virtual Options GetOptions () const override
113 {
114 return fOptions_;
115 }
116 virtual Time::DurationSeconds GetTimeout () const override
117 {
118 return fTimeout_;
119 }
120 virtual void SetTimeout (Time::DurationSeconds timeout) override
121 {
122 fTimeout_ = timeout; // affects subsequent calls to send...
123 }
124 virtual URI GetSchemeAndAuthority () const override
125 {
126 return fURL_.GetSchemeAndAuthority ();
127 }
128 virtual void SetSchemeAndAuthority (const URI& schemeAndAuthority) override
129 {
130#if USE_NOISY_TRACE_IN_THIS_MODULE_
131 DbgTrace ("Connection_WinHTTP::Rep_::SetSchemeAndAuthority ('{}')"_f, schemeAndAuthority);
132#endif
133 URI newURL = fURL_;
134 newURL.SetScheme (schemeAndAuthority.GetScheme ());
135 newURL.SetAuthority (schemeAndAuthority.GetAuthority ());
136 if (fURL_ != newURL) {
137 fConnectionHandle_.reset ();
138 fURL_ = newURL;
139 }
140 }
141 virtual void Close () override
142 {
143 fConnectionHandle_.reset ();
144 fSessionHandle_.reset ();
145 }
146 virtual Response Send (const Request& request) override
147 {
148#if USE_NOISY_TRACE_IN_THIS_MODULE_
149 Debug::TraceContextBumper ctx{"Connection_WinHTTP::Rep_::Send", "request={}"_f, request};
150#endif
151 Request useRequest = request;
152
153 SetAuthorityRelativeURL_ (useRequest.fAuthorityRelativeURL);
154
155 BLOB data; // usually empty, but provided for some methods like POST
157 HTTP::Status status{};
158 optional<Response::SSLResultInfo> serverEndpointSSLInfo;
159
160 Time::TimePointSeconds startOfSendAt = Time::GetTickCount ();
161 Time::TimePointSeconds endBy = fTimeout_ == Time::kInfinity ? Time::TimePointSeconds{Time::kInfinity} : (startOfSendAt + fTimeout_);
162
163 /*
164 * Though we could create a DIFFERENT API - that managed a session object - like the WinHTTP session object, for now,
165 * just KISS. We DON'T cache a single session, because this code could be used by multiple 'users' -
166 * when called within HealthFrameWorks - for example.
167 *
168 */
169 String userAgent = fOptions_.fUserAgent;
170 Mapping<String, String> useHeadersMap = useRequest.fOverrideHeaders;
171 {
172 // We must have an empty 'accept-encoding' to prevent being sent stuff in gzip/deflate format, which WinHTTP
173 // appears to not decode (and neither do I).
174 useHeadersMap.Add (HeaderName::kAcceptEncoding, String{});
175 }
176 Cache::EvalContext cacheContext;
177 if (fOptions_.fCache != nullptr) {
178 if (auto r = fOptions_.fCache->OnBeforeFetch (&cacheContext, fURL_.GetSchemeAndAuthority (), &useRequest)) {
179 // shortcut - we already have a cached answer
180 return *r;
181 }
182 }
183 {
184 if (useHeadersMap.Lookup (HeaderName::kUserAgent, &userAgent)) {
185 useHeadersMap.Remove (HeaderName::kUserAgent);
186 }
187 }
188 if (fOptions_.fAuthentication and
189 fOptions_.fAuthentication->GetOptions () == Connection::Options::Authentication::Options::eProactivelySendAuthentication) {
190 useHeadersMap.Add (HeaderName::kAuthorization, fOptions_.fAuthentication->GetAuthToken ());
191 }
192 StringBuilder useHeaderStrBuf;
193 {
194 for (auto i = useHeadersMap.begin (); i != useHeadersMap.end (); ++i) {
195 useHeaderStrBuf += i->fKey + ": "sv + i->fValue + "\r\n"sv;
196 }
197 }
198
199 AssureHasSessionHandle_ (userAgent);
200 Assert (fSessionHandle_ != nullptr);
201 AssureHasConnectionHandle_ ();
202 Assert (fConnectionHandle_ != nullptr);
203
204 if (fOptions_.fTCPKeepAlives) {
205 // https://msdn.microsoft.com/en-us/library/windows/desktop/aa384066(v=vs.85).aspx - MSFT says must be > 30 seconds, and cannot be disabled, so just set to long timeout before sending keepalive
206 // MSFT docs appear to indicate this wont work with a handle so I'm not really sure how to use/or if to use
207 //DWORD dwOptionsTimeout = fOptions_.fTCPKeepAlives->fEnabled ? 30000 : 1000 * 1000;
208 //Verify (::WinHttpSetOption (*fConnectionHandle_, WINHTTP_OPTION_WEB_SOCKET_KEEPALIVE_INTERVAL, &dwOptionsTimeout, sizeof (dwOptionsTimeout)));
209 }
210
211 bool useSecureHTTP = fURL_.GetScheme () and fURL_.GetScheme ()->IsSecure ();
212
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)};
216
217 // See http://stroika-bugs.sophists.com/browse/STK-442 - we pre-set to avoid double try on failure, but
218 // we cannot IF we want to know if SSL connect failed (until I figure out how)
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)));
225 }
226 if (not fOptions_.fSupportSessionCookies) {
227 /*
228 * From https://msdn.microsoft.com/en-us/library/windows/desktop/aa384066(v=vs.85).aspx
229 * Be aware that this feature should only be passed to WinHttpSetOption on request handles after the request handle is created with WinHttpOpenRequest, and before the request is sent with WinHttpSendRequest.
230 */
231 DWORD dwOptions = WINHTTP_DISABLE_COOKIES;
232 Verify (::WinHttpSetOption (hRequest, WINHTTP_OPTION_DISABLE_FEATURE, &dwOptions, sizeof (dwOptions)));
233 }
234
235 bool got401 = false; // if we get one, we add credentials, but if we get two, its time to give up
236
237 bool sslExceptionProblem = false;
238 RetryWithNoCERTCheck:
239
240 //
241 // REALLY - don't want these flags here - but have a CALLBACK whcih checks arbitrary rules and THROWS if unhappy - and doesn't do rest of fetch...
242 // TODO!!!
243 //
244 // See http://stroika-bugs.sophists.com/browse/STK-442
245 //
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)));
251 }
252
253 RetryWithAuth:
254 try {
255 if (useRequest.fData.size () > numeric_limits<DWORD>::max ()) {
256 Throw (Execution::Exception{"Too large a message to send using WinHTTP"sv});
257 }
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));
262
263 // this must be called before the 'body' goes out of scope!
264 ThrowIfZeroGetLastError (::WinHttpReceiveResponse (hRequest, nullptr));
265 }
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;
273 }
274 }
275 Execution::ReThrow ();
276 }
277
278 list<vector<byte>> bytesRead;
279 unsigned int totalBytes = 0;
280 {
281 // Keep reading data til all done
282 DWORD dwSize = 0;
283 do {
284 if (Time::GetTickCount () > endBy) [[unlikely]] {
285 Execution::Throw (Execution::TimeOutException::kThe);
286 }
287
288 // Check for available data.
289 dwSize = 0;
290 ThrowIfZeroGetLastError (::WinHttpQueryDataAvailable (hRequest, &dwSize));
291 StackBuffer<byte> outBuffer{Memory::eUninitialized, dwSize};
292 memset (outBuffer.data (), 0, dwSize);
293 DWORD dwDownloaded = 0;
294 ThrowIfZeroGetLastError (::WinHttpReadData (hRequest, outBuffer.data (), dwSize, &dwDownloaded));
295 Assert (dwDownloaded <= dwSize);
296 totalBytes += dwDownloaded;
297 bytesRead.push_back (vector<byte>{outBuffer.begin (), outBuffer.begin () + dwDownloaded});
298 } while (dwSize > 0);
299 }
300
301 // Here - we must convert the chunks of bytes to a big blob and a string
302 // This API assumes the HTTP-result is a string
303 //
304 // probably should check header content-type for codepage, but this SB OK for now...
305 {
306 StackBuffer<byte> bytesArray{Memory::eUninitialized, totalBytes};
307 size_t iii = 0;
308 for (auto i = bytesRead.begin (); i != bytesRead.end (); ++i) {
309 auto v2 = *i;
310 for (auto ii = v2.begin (); ii != v2.end (); ++ii) {
311 bytesArray[iii] = *ii;
312 ++iii;
313 }
314 }
315 Assert (iii == totalBytes);
316 data = BLOB{bytesArray.begin (), bytesArray.end ()};
317 }
318
319 // don't throw here - record the bad status in the response. The reason is we often wish to read the whole body of the response.
320 // It can contain an explanation of the error (such as soap fault) more detailed than the status line response
321 {
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_
326 DbgTrace ("Status = {}"_f, status);
327#endif
328 }
329
330 if (status == 401 and not got401 and fOptions_.fAuthentication and
331 fOptions_.fAuthentication->GetOptions () == Connection::Options::Authentication::Options::eRespondToWWWAuthenticate) {
332 got401 = true;
333 DWORD dwSupportedSchemes{};
334 DWORD dwFirstScheme{};
335 DWORD dwTarget{};
336 if (::WinHttpQueryAuthSchemes (hRequest, &dwSupportedSchemes, &dwFirstScheme, &dwTarget)) {
337 auto chooseAuthScheme = [] (DWORD supportedSchemes) -> DWORD {
338 // see https://msdn.microsoft.com/en-us/library/windows/desktop/aa383144(v=vs.85).aspx
339 // ChooseAuthScheme
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;
350 else
351 return 0;
352 };
353 DWORD dwSelectedScheme = chooseAuthScheme (dwSupportedSchemes);
354 if (dwSelectedScheme != 0) {
355 auto nameAndPassword = *fOptions_.fAuthentication->GetUsernameAndPassword (); // if eRespondToWWWAuthenticate we must have username/password (Options CTOR requirement)
356 Verify (::WinHttpSetCredentials (hRequest, dwTarget, dwSelectedScheme, nameAndPassword.first.AsSDKString ().c_str (),
357 nameAndPassword.second.AsSDKString ().c_str (), nullptr));
358 goto RetryWithAuth;
359 }
360 }
361 }
362
363 /*
364 * We COULD check (and this code does if enabled) check to see if the cert was valid - expired - or whatever,
365 * but in an advisory fasion. If we want to provide some kind of optional arg to this function to optionally
366 * return that info - we could use this. BUT - we need to manually figure out if its expired or
367 * whatever.
368 */
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);
377 }
378 if (certInfo.lpszIssuerInfo != nullptr) {
379 ::LocalFree (certInfo.lpszIssuerInfo);
380 }
381 if (certInfo.lpszEncryptionAlgName != nullptr) {
382 ::LocalFree (certInfo.lpszEncryptionAlgName);
383 }
384 if (certInfo.lpszProtocolName != nullptr) {
385 ::LocalFree (certInfo.lpszProtocolName);
386 }
387 if (certInfo.lpszSignatureAlgName != nullptr) {
388 ::LocalFree (certInfo.lpszSignatureAlgName);
389 }
390 });
391
392 Response::SSLResultInfo resultSSLInfo;
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;
398 {
399 size_t i = subject.find ('\r');
400 if (i != wstring::npos) {
401 resultSSLInfo.fSubjectCommonName = resultSSLInfo.fSubjectCommonName.substr (0, i);
402 }
403 }
404 }
405 if (certInfo.lpszIssuerInfo != nullptr) {
406 resultSSLInfo.fIssuer = certInfo.lpszIssuerInfo;
407 }
408 // check dates
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;
414 }
415 else if (endCertDate < now) {
416 resultSSLInfo.fValidationStatus = Response::SSLResultInfo::ValidationStatus::eCertExpired;
417 }
418
419 if (not fURL_.GetAuthority () or not fURL_.GetAuthority ()->GetHost () or not fURL_.GetAuthority ()->GetHost ()->AsRegisteredName ()) {
420 static const Execution::RuntimeErrorException kException_{"Cannot validate TLS without a host name"sv};
421 Execution::Throw (kException_);
422 }
423 auto equalsComparer = String::EqualsComparer{eCaseInsensitive};
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;
427 }
428
429 serverEndpointSSLInfo = resultSSLInfo;
430 }
431
432 // copy/fill in result.fHeaders....
433 {
434 wstring rr = Extract_WinHttpHeader_ (hRequest, WINHTTP_QUERY_RAW_HEADERS_CRLF, WINHTTP_HEADER_NAME_BY_INDEX, WINHTTP_NO_HEADER_INDEX);
435 // now break into lines
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 ();
440 }
441 wstring thisLine = rr.substr (i, endOfRegion - i);
442 // now parse thisLine
443 if (thisLine.length () > 5) { // apx min useful length - not real magic here - just optimization - avoid blank lines (cuz of how we parse crlf)
444 // stuff before the colon is the key, and stuff after the colon (both trimmed) is the value
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);
451 }
452 }
453 }
454 i = endOfRegion + 1;
455 }
456 }
457
458 Response result{data, status, headers, serverEndpointSSLInfo};
459 if (fOptions_.fCache != nullptr) {
460 fOptions_.fCache->OnAfterFetch (cacheContext, &result);
461 }
462
463 return result;
464 }
465
466 private:
467 nonvirtual void SetAuthorityRelativeURL_ (const URI& url)
468 {
469 URI newURL = url; // almost but not quite the same as fURL_.Combine (url)
470 newURL.SetScheme (fURL_.GetScheme ());
471 newURL.SetAuthority (fURL_.GetAuthority ());
472 if (fURL_ != url) {
473 fConnectionHandle_.reset ();
474 fURL_ = newURL;
475 }
476 }
477
478 private:
479 nonvirtual void AssureHasSessionHandle_ (const String& userAgent)
480 {
481 if (fSessionHandle_UserAgent_ != userAgent) {
482 fConnectionHandle_.reset ();
483 fSessionHandle_.reset ();
484 }
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)));
492 }
493 else {
494 DWORD dwOptions = WINHTTP_OPTION_REDIRECT_POLICY_DISALLOW_HTTPS_TO_HTTP; // sb WINHTTP_OPTION_REDIRECT_POLICY_ALWAYS?
495 Verify (::WinHttpSetOption (*fSessionHandle_, WINHTTP_OPTION_REDIRECT_POLICY, &dwOptions, sizeof (dwOptions)));
496 // According to https://msdn.microsoft.com/en-us/library/windows/desktop/aa384066%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396,
497 // WINHTTP_OPTION_MAX_HTTP_AUTOMATIC_REDIRECTS is obsolete
498 }
499 }
500 }
501 nonvirtual void AssureHasConnectionHandle_ ()
502 {
503 RequireNotNull (fSessionHandle_);
504 if (fConnectionHandle_ == nullptr) {
505 if (not fURL_.GetAuthority () or not fURL_.GetAuthority ()->GetHost ()) {
506 static const Execution::RuntimeErrorException kException_{"Cannot connect without a host"sv};
507 Execution::Throw (kException_);
508 }
509 // NOT SURE - for IPv6 address - if we want to pass encoded value here?
510 fConnectionHandle_ = Memory::MakeSharedPtr<AutoWinHINTERNET_> (::WinHttpConnect (
511 *fSessionHandle_, fURL_.GetAuthority ()->GetHost ()->As<String> (URI::StringPCTEncodedFlag::ePCTEncoded).As<wstring> ().c_str (),
512 fURL_.GetPortValue (), 0));
513 }
514 }
515
516 private:
517 static wstring Extract_WinHttpHeader_ (HINTERNET hRequest, DWORD dwInfoLevel, LPCWSTR pwszName, LPDWORD lpdwIndex)
518 {
519 DWORD size = 0;
520 (void)::WinHttpQueryHeaders (hRequest, dwInfoLevel, pwszName, WINHTTP_NO_OUTPUT_BUFFER, &size, lpdwIndex);
521 DWORD error = GetLastError ();
522 if (error == ERROR_INSUFFICIENT_BUFFER) {
523 StackBuffer<wchar_t> buf{Memory::eUninitialized, size + 1};
524 (void)::memset (buf.data (), 0, buf.GetSize ());
525 ThrowIfZeroGetLastError (::WinHttpQueryHeaders (hRequest, dwInfoLevel, pwszName, buf.data (), &size, lpdwIndex));
526 return buf.begin ();
527 }
528 else {
529 Execution::ThrowSystemErrNo (error);
530 return wstring{};
531 }
532 }
533
534 private:
535 Connection::Options fOptions_;
536 Time::DurationSeconds fTimeout_{Time::kInfinity};
537 URI fURL_;
538 shared_ptr<AutoWinHINTERNET_> fSessionHandle_;
539 String fSessionHandle_UserAgent_;
540 shared_ptr<AutoWinHINTERNET_> fConnectionHandle_;
541 };
542
543}
544
545/*
546 ********************************************************************************
547 ********************** Transfer::WinHTTP::Connection ***************************
548 ********************************************************************************
549 */
550Connection::Ptr Transfer::WinHTTP::Connection::New (const Options& options)
551{
552 return Connection::Ptr{Memory::MakeSharedPtr<Rep_> (options)};
553}
554#endif
#define RequireNotNull(p)
Definition Assertions.h:347
#define Verify(c)
Definition Assertions.h:419
#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...
Definition Realtime.h:82
chrono::duration< double > DurationSeconds
chrono::duration<double> - a time span (length of time) measured in seconds, but high precision.
Definition Realtime.h:57
#define DbgTrace
Definition Trace.h:309
Similar to String, but intended to more efficiently construct a String. Mutable type (String is large...
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
nonvirtual bool Add(ArgByValueType< key_type > key, ArgByValueType< mapped_type > newElt, AddReplaceMode addReplaceMode=AddReplaceMode::eAddReplaces)
Definition Mapping.inl:188
nonvirtual optional< mapped_type > Lookup(ArgByValueType< key_type > key) const
Definition Mapping.inl:142
nonvirtual void Remove(ArgByValueType< key_type > key)
Remove the given item (which must exist).
Definition Mapping.inl:223
Exception<> is a replacement (subclass) for any std c++ exception class (e.g. the default 'std::excep...
Definition Exceptions.h:157
nonvirtual URI GetSchemeAndAuthority() const
Definition URI.inl:90
nonvirtual optional< SchemeType > GetScheme() const
Definition URI.inl:36
nonvirtual optional< Authority > GetAuthority() const
Definition URI.inl:55
nonvirtual void SetScheme(const optional< SchemeType > &scheme)
Definition URI.inl:41
nonvirtual const byte * begin() const
Definition BLOB.inl:258
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...
Definition Throw.inl:43