/** * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Aws::Client; using namespace Aws::Http; using namespace Aws::Http::Standard; using namespace Aws::Utils; using namespace Aws::Utils::Logging; static const uint32_t HTTP_REQUEST_WRITE_BUFFER_LENGTH = 8192; static void WinINetEnableHttp2(void* handle) { #ifdef WININET_HAS_H2 DWORD http2 = HTTP_PROTOCOL_FLAG_HTTP2; if (!InternetSetOptionA(handle, INTERNET_OPTION_ENABLE_HTTP_PROTOCOL, &http2, sizeof(http2))) { AWS_LOGSTREAM_ERROR("WinINetHttp2", "Failed to enable HTTP/2 on WinInet handle: " << handle << ". Falling back to HTTP/1.1."); } else { AWS_LOGSTREAM_DEBUG("WinINetHttp2", "HTTP/2 enabled on WinInet handle: " << handle << "."); } #else AWS_UNREFERENCED_PARAM(handle); #endif } WinINetSyncHttpClient::WinINetSyncHttpClient(const ClientConfiguration& config) : Base() { AWS_LOGSTREAM_INFO(GetLogTag(), "Creating http client with user agent " << config.userAgent << " with max connections " << config.maxConnections << ", request timeout " << config.requestTimeoutMs << ",and connect timeout " << config.connectTimeoutMs); if (config.followRedirects == FollowRedirectsPolicy::NEVER || (config.followRedirects == FollowRedirectsPolicy::DEFAULT && config.region == Aws::Region::AWS_GLOBAL)) { m_allowRedirects = false; } else { m_allowRedirects = true; } DWORD inetFlags = INTERNET_OPEN_TYPE_DIRECT; const char* proxyHosts = nullptr; Aws::String strProxyHosts; m_usingProxy = !config.proxyHost.empty(); //setup initial proxy config. if (m_usingProxy) { const char* const proxySchemeString = Aws::Http::SchemeMapper::ToString(config.proxyScheme); AWS_LOGSTREAM_INFO(GetLogTag(), "Http Client is using a proxy. Setting up proxy with settings scheme " << proxySchemeString << ", host " << config.proxyHost << ", port " << config.proxyPort << ", username " << config.proxyUserName << "."); inetFlags = INTERNET_OPEN_TYPE_PROXY; Aws::StringStream ss; const char* schemeString = Aws::Http::SchemeMapper::ToString(config.scheme); ss << StringUtils::ToUpper(schemeString) << "=" << proxySchemeString << "://" << config.proxyHost << ":" << config.proxyPort; strProxyHosts.assign(ss.str()); proxyHosts = strProxyHosts.c_str(); AWS_LOGSTREAM_DEBUG(GetLogTag(), "Adding proxy host string to wininet " << proxyHosts); m_proxyUserName = config.proxyUserName; m_proxyPassword = config.proxyPassword; } SetOpenHandle(InternetOpenA(config.userAgent.c_str(), inetFlags, proxyHosts, nullptr, 0)); //override offline mode. InternetSetOptionA(GetOpenHandle(), INTERNET_OPTION_IGNORE_OFFLINE, nullptr, 0); WinINetEnableHttp2(GetOpenHandle()); if (!config.verifySSL) { AWS_LOGSTREAM_WARN(GetLogTag(), "Turning ssl unknown ca verification off."); DWORD flags = SECURITY_FLAG_IGNORE_UNKNOWN_CA | INTERNET_FLAG_IGNORE_CERT_CN_INVALID; if (!InternetSetOptionA(GetOpenHandle(), INTERNET_OPTION_SECURITY_FLAGS, &flags, sizeof(flags))) AWS_LOGSTREAM_FATAL(GetLogTag(), "Failed to turn ssl cert ca verification off."); } AWS_LOGSTREAM_DEBUG(GetLogTag(), "API handle " << GetOpenHandle()); SetConnectionPoolManager(Aws::New(GetLogTag(), GetOpenHandle(), config.maxConnections, config.requestTimeoutMs, config.connectTimeoutMs, config.enableTcpKeepAlive, config.tcpKeepAliveIntervalMs)); } WinINetSyncHttpClient::~WinINetSyncHttpClient() { InternetCloseHandle(GetOpenHandle()); SetOpenHandle(nullptr); // the handle is already closed, annul it to avoid double-closing the handle (in the base class' destructor) } void* WinINetSyncHttpClient::OpenRequest(const std::shared_ptr& request, void* connection, const Aws::StringStream& ss) const { DWORD requestFlags = INTERNET_FLAG_NO_AUTH | INTERNET_FLAG_RELOAD | INTERNET_FLAG_KEEP_CONNECTION | (request->GetUri().GetScheme() == Scheme::HTTPS ? INTERNET_FLAG_SECURE : 0) | INTERNET_FLAG_NO_CACHE_WRITE | (m_allowRedirects ? 0 : INTERNET_FLAG_NO_AUTO_REDIRECT); LPCSTR accept[2] = { nullptr, nullptr }; Aws::String acceptHeader("*/*"); if (request->HasHeader(Aws::Http::ACCEPT_HEADER)) { acceptHeader = request->GetHeaderValue(Aws::Http::ACCEPT_HEADER); } accept[0] = acceptHeader.c_str(); HINTERNET hHttpRequest = HttpOpenRequestA(connection, HttpMethodMapper::GetNameForHttpMethod(request->GetMethod()), ss.str().c_str(), nullptr, nullptr, accept, requestFlags, 0); AWS_LOGSTREAM_DEBUG(GetLogTag(), "HttpOpenRequestA returned handle " << hHttpRequest); //add proxy auth credentials to everything using this handle. if (m_usingProxy) { if (!m_proxyUserName.empty() && !InternetSetOptionA(hHttpRequest, INTERNET_OPTION_PROXY_USERNAME, (LPVOID)m_proxyUserName.c_str(), (DWORD)m_proxyUserName.length())) AWS_LOGSTREAM_FATAL(GetLogTag(), "Failed setting username for proxy with error code: " << GetLastError()); if (!m_proxyPassword.empty() && !InternetSetOptionA(hHttpRequest, INTERNET_OPTION_PROXY_PASSWORD, (LPVOID)m_proxyPassword.c_str(), (DWORD)m_proxyPassword.length())) AWS_LOGSTREAM_FATAL(GetLogTag(), "Failed setting password for proxy with error code: " << GetLastError()); } WinINetEnableHttp2(hHttpRequest); return hHttpRequest; } void WinINetSyncHttpClient::DoAddHeaders(void* hHttpRequest, Aws::String& headerStr) const { if (!HttpAddRequestHeadersA(hHttpRequest, headerStr.c_str(), (DWORD)headerStr.length(), HTTP_ADDREQ_FLAG_REPLACE | HTTP_ADDREQ_FLAG_ADD)) AWS_LOGSTREAM_ERROR(GetLogTag(), "Failed to add HTTP request headers: " << headerStr << ", with error code: " << GetLastError()); } uint64_t WinINetSyncHttpClient::DoWriteData(void* hHttpRequest, char* streamBuffer, uint64_t bytesRead, bool isChunked) const { DWORD bytesWritten = 0; uint64_t totalBytesWritten = 0; const char CRLF[] = "\r\n"; if (isChunked) { Aws::String chunkSizeHexString = StringUtils::ToHexString(bytesRead) + CRLF; if (!InternetWriteFile(hHttpRequest, chunkSizeHexString.c_str(), (DWORD)chunkSizeHexString.size(), &bytesWritten)) { return totalBytesWritten; } totalBytesWritten += bytesWritten; if (!InternetWriteFile(hHttpRequest, streamBuffer, (DWORD)bytesRead, &bytesWritten)) { return totalBytesWritten; } totalBytesWritten += bytesWritten; if (!InternetWriteFile(hHttpRequest, CRLF, (DWORD)(sizeof(CRLF) - 1), &bytesWritten)) { return totalBytesWritten; } totalBytesWritten += bytesWritten; } else { if (!InternetWriteFile(hHttpRequest, streamBuffer, (DWORD)bytesRead, &bytesWritten)) { return totalBytesWritten; } totalBytesWritten += bytesWritten; } return totalBytesWritten; } uint64_t WinINetSyncHttpClient::FinalizeWriteData(void* hHttpRequest) const { DWORD bytesWritten = 0; const char trailingCRLF[] = "0\r\n\r\n"; if (!InternetWriteFile(hHttpRequest, trailingCRLF, (DWORD)(sizeof(trailingCRLF) - 1), &bytesWritten)) { return 0; } return bytesWritten; } bool WinINetSyncHttpClient::DoReceiveResponse(void* hHttpRequest) const { return (HttpEndRequest(hHttpRequest, nullptr, 0, 0) != 0); } bool WinINetSyncHttpClient::DoQueryHeaders(void* hHttpRequest, std::shared_ptr& response, Aws::StringStream& ss, uint64_t& read) const { char dwStatusCode[256]; DWORD dwSize = sizeof(dwStatusCode); HttpQueryInfoA(hHttpRequest, HTTP_QUERY_STATUS_CODE, &dwStatusCode, &dwSize, 0); response->SetResponseCode((HttpResponseCode)atoi(dwStatusCode)); AWS_LOGSTREAM_DEBUG(GetLogTag(), "Received response code " << dwStatusCode); char contentTypeStr[1024]; dwSize = sizeof(contentTypeStr); HttpQueryInfoA(hHttpRequest, HTTP_QUERY_CONTENT_TYPE, &contentTypeStr, &dwSize, 0); if (contentTypeStr[0] != NULL) { response->SetContentType(contentTypeStr); AWS_LOGSTREAM_DEBUG(GetLogTag(), "Received content type " << contentTypeStr); } char headerStr[1024]; dwSize = sizeof(headerStr); AWS_LOGSTREAM_DEBUG(GetLogTag(), "Received headers:"); while (HttpQueryInfoA(hHttpRequest, HTTP_QUERY_RAW_HEADERS_CRLF, headerStr, &dwSize, (LPDWORD)&read) && dwSize > 0) { AWS_LOGSTREAM_DEBUG(GetLogTag(), headerStr); ss << headerStr; } return (read != 0); } bool WinINetSyncHttpClient::DoSendRequest(void* hHttpRequest) const { return (HttpSendRequestEx(hHttpRequest, NULL, NULL, 0, 0) != 0); } bool WinINetSyncHttpClient::DoReadData(void* hHttpRequest, char* body, uint64_t size, uint64_t& read) const { return (InternetReadFile(hHttpRequest, body, (DWORD)size, (LPDWORD)&read) != 0); } void* WinINetSyncHttpClient::GetClientModule() const { return GetModuleHandle(TEXT("wininet.dll")); }