/** * Copyright (C) 2018 MongoDB Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * * As a special exception, the copyright holders give permission to link the * code of portions of this program with the OpenSSL library under certain * conditions as described in each individual source file and distribute * linked combinations including the program with the OpenSSL library. You * must comply with the GNU Affero General Public License in all respects * for all of the code used other than as permitted herein. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you do not * wish to do so, delete this exception statement from your version. If you * delete this exception statement from all source files in the program, * then also delete it in the license file. */ #ifndef _WIN32 #error This file should only be built on Windows #endif #ifndef _UNICODE #error This file assumes a UNICODE WIN32 build #endif #define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kNetwork #include "mongo/platform/basic.h" #include #include #include #include #include "mongo/base/status.h" #include "mongo/base/string_data.h" #include "mongo/bson/bsonobj.h" #include "mongo/util/assert_util.h" #include "mongo/util/errno_util.h" #include "mongo/util/log.h" #include "mongo/util/mongoutils/str.h" #include "mongo/util/net/http_client.h" #include "mongo/util/scopeguard.h" #include "mongo/util/text.h" #include "mongo/util/winutil.h" namespace mongo { namespace { const LPCWSTR kAcceptTypes[] = { L"application/octet-stream", nullptr, }; struct ProcessedUrl { bool https; INTERNET_PORT port; std::wstring username; std::wstring password; std::wstring hostname; std::wstring path; std::wstring query; }; StatusWith parseUrl(const std::wstring& url) { URL_COMPONENTS comp; ZeroMemory(&comp, sizeof(comp)); comp.dwStructSize = sizeof(comp); // Request user, password, host, port, path, and extra(query). comp.dwUserNameLength = 1; comp.dwPasswordLength = 1; comp.dwHostNameLength = 1; comp.dwUrlPathLength = 1; comp.dwExtraInfoLength = 1; if (!WinHttpCrackUrl(url.c_str(), url.size(), 0, &comp)) { return {ErrorCodes::BadValue, "Unable to parse URL"}; } ProcessedUrl ret; ret.https = (comp.nScheme == INTERNET_SCHEME_HTTPS); if (comp.lpszUserName) { ret.username = std::wstring(comp.lpszUserName, comp.dwUserNameLength); } if (comp.lpszPassword) { ret.password = std::wstring(comp.lpszPassword, comp.dwPasswordLength); } if (comp.lpszHostName) { ret.hostname = std::wstring(comp.lpszHostName, comp.dwHostNameLength); } if (comp.nPort) { ret.port = comp.nPort; } else if (ret.https) { ret.port = INTERNET_DEFAULT_HTTPS_PORT; } else { ret.port = INTERNET_DEFAULT_HTTP_PORT; } if (comp.lpszUrlPath) { ret.path = std::wstring(comp.lpszUrlPath, comp.dwUrlPathLength); } if (comp.lpszExtraInfo) { ret.query = std::wstring(comp.lpszExtraInfo, comp.dwExtraInfoLength); } return ret; } class WinHttpClient : public HttpClient { public: ~WinHttpClient() final = default; void allowInsecureHTTP(bool allow) final { _allowInsecureHTTP = allow; } void setHeaders(const std::vector& headers) final { // 1. Concatenate all headers with \r\n line endings. StringBuilder sb; for (const auto& header : headers) { sb << header << "\r\n"; } auto header = sb.str(); // 2. Remove final \r\n delimiter. if (header.size() >= 2) { header.pop_back(); header.pop_back(); } // 3. Expand to Windows Unicode wide string. _headers = toNativeString(header.c_str()); } void setConnectTimeout(Seconds timeout) final { _connectTimeout = timeout; } void setTimeout(Seconds timeout) final { _timeout = timeout; } DataBuilder post(StringData url, ConstDataRange cdr) const final { return doRequest( L"POST", url, const_cast(static_cast(cdr.data())), cdr.length()); } DataBuilder get(StringData url) const final { return doRequest(L"GET", url, nullptr, 0); } private: DataBuilder doRequest(LPCWSTR method, StringData urlSD, LPVOID data, DWORD data_len) const { const auto uassertWithErrno = [](StringData reason, bool ok) { const auto msg = errnoWithDescription(GetLastError()); uassert(ErrorCodes::OperationFailed, str::stream() << reason << ": " << msg, ok); }; // Break down URL for handling below. const auto urlString = toNativeString(urlSD.toString().c_str()); auto url = uassertStatusOK(parseUrl(urlString)); uassert( ErrorCodes::BadValue, "URL endpoint must be https://", url.https || _allowInsecureHTTP); // Cleanup handled in a guard rather than UniquePtrs to ensure order. HINTERNET session = nullptr, connect = nullptr, request = nullptr; auto guard = MakeGuard([&] { if (request) { WinHttpCloseHandle(request); } if (connect) { WinHttpCloseHandle(connect); } if (session) { WinHttpCloseHandle(session); } }); DWORD accessType = WINHTTP_ACCESS_TYPE_DEFAULT_PROXY; if (IsWindows8Point1OrGreater()) { accessType = WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY; } session = WinHttpOpen(L"MongoDB HTTP Client/Windows", accessType, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); uassertWithErrno("Failed creating an HTTP session", session); DWORD setting; DWORD settingLength = sizeof(setting); setting = WINHTTP_OPTION_REDIRECT_POLICY_NEVER; uassertWithErrno( "Failed setting HTTP session option", WinHttpSetOption(session, WINHTTP_OPTION_REDIRECT_POLICY, &setting, settingLength)); DWORD connectTimeout = durationCount(_connectTimeout); DWORD totalTimeout = durationCount(_timeout); uassertWithErrno("Failed setting HTTP timeout", WinHttpSetTimeouts( session, connectTimeout, connectTimeout, totalTimeout, totalTimeout)); connect = WinHttpConnect(session, url.hostname.c_str(), url.port, 0); uassertWithErrno("Failed connecting to remote host", connect); request = WinHttpOpenRequest(connect, method, (url.path + url.query).c_str(), nullptr, WINHTTP_NO_REFERER, const_cast(kAcceptTypes), url.https ? WINHTTP_FLAG_SECURE : 0); uassertWithErrno("Failed initializing HTTP request", request); if (!url.username.empty() || !url.password.empty()) { auto result = WinHttpSetCredentials(request, WINHTTP_AUTH_TARGET_SERVER, WINHTTP_AUTH_SCHEME_DIGEST, url.username.c_str(), url.password.c_str(), 0); uassertWithErrno("Failed setting authentication credentials", result); } uassertWithErrno( "Failed sending HTTP request", WinHttpSendRequest(request, _headers.c_str(), -1L, data, data_len, data_len, 0)); if (!WinHttpReceiveResponse(request, nullptr)) { // Carve out timeout which doesn't translate well. const auto err = GetLastError(); if (err == ERROR_WINHTTP_TIMEOUT) { uasserted(ErrorCodes::OperationFailed, "Timeout was reached"); } const auto msg = errnoWithDescription(err); uasserted(ErrorCodes::OperationFailed, str::stream() << "Failed receiving response from server" << ": " << msg); } DWORD statusCode = 0; DWORD statusCodeLength = sizeof(statusCode); uassertWithErrno("Error querying status from server", WinHttpQueryHeaders(request, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &statusCode, &statusCodeLength, WINHTTP_NO_HEADER_INDEX)); uassert(ErrorCodes::OperationFailed, str::stream() << "Unexpected http status code from server: " << statusCode, statusCode == 200); std::vector buffer; DataBuilder ret(4096); for (;;) { DWORD len = 0; uassertWithErrno("Failed receiving response data", WinHttpQueryDataAvailable(request, &len)); if (!len) { break; } buffer.resize(len); uassertWithErrno("Failed reading response data", WinHttpReadData(request, buffer.data(), len, &len)); ConstDataRange cdr(buffer.data(), len); ret.writeAndAdvance(cdr); } return ret; } private: bool _allowInsecureHTTP = false; std::wstring _headers; Seconds _connectTimeout = kConnectionTimeout; Seconds _timeout = kTotalRequestTimeout; }; } // namespace std::unique_ptr HttpClient::create() { return std::make_unique(); } } // namespace mongo