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