Author: Stefan Gränitz Date: 2026-03-20T12:47:45+01:00 New Revision: 39d6bb21804d21abe2fa0ec019919d72104827ac
URL: https://github.com/llvm/llvm-project/commit/39d6bb21804d21abe2fa0ec019919d72104827ac DIFF: https://github.com/llvm/llvm-project/commit/39d6bb21804d21abe2fa0ec019919d72104827ac.diff LOG: [lldb] Add HTTP support in SymbolLocatorSymStore (#186986) The initial version of SymbolLocatorSymStore supported servers only on local paths. This patch extends it to HTTP/HTTPS end-points. For that to work on Windows, we add a WinHTTP-based HTTP client backend in LLVM next to the existing CURL-based implementation. We don't add a HTTP server implementation, because there is no use right now. Test coverage for the LLVM part is built on llvm-debuginfod-find and works server-less, since it checks textual output of request headers. The existing CURL-based implementation uses the same approach. The LLDB API test for the specific SymbolLocatorSymStore feature spawns a HTTP server from Python. To keep the size of this patch within reasonable limits, the initial implementation of the SymbolLocatorSymStore feature is dump: There is no caching, no verification of downloaded files and no protection against file corruptions. We use a local implementation of LLVM's HTTPResponseHandler, but should think about extracting and reusing Debuginfod's StreamedHTTPResponseHandler in the future. The goal of this patch is a basic working implementation that is testable in isolation. We will iterate on it to improve it further. This should be fine since downloading from SymStores is not default-enabled yet. Users have to set their server URLs explicitly. --------- Co-authored-by: Alexandre Ganea <[email protected]> Added: lldb/test/API/symstore/TestSymStore.py llvm/test/tools/llvm-debuginfod-find/headers-curl.test llvm/test/tools/llvm-debuginfod-find/headers-winhttp.test Modified: lldb/source/Plugins/SymbolLocator/SymStore/CMakeLists.txt lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp llvm/include/llvm/Support/HTTP/HTTPClient.h llvm/lib/Support/HTTP/CMakeLists.txt llvm/lib/Support/HTTP/HTTPClient.cpp Removed: lldb/test/API/symstore/TestSymStoreLocal.py llvm/test/tools/llvm-debuginfod-find/headers.test ################################################################################ diff --git a/lldb/source/Plugins/SymbolLocator/SymStore/CMakeLists.txt b/lldb/source/Plugins/SymbolLocator/SymStore/CMakeLists.txt index b0da27f26c6a8..775e0284cd8af 100644 --- a/lldb/source/Plugins/SymbolLocator/SymStore/CMakeLists.txt +++ b/lldb/source/Plugins/SymbolLocator/SymStore/CMakeLists.txt @@ -13,6 +13,7 @@ add_lldb_library(lldbPluginSymbolLocatorSymStore PLUGIN lldbCore lldbHost lldbSymbol + LLVMSupportHTTP ) add_dependencies(lldbPluginSymbolLocatorSymStore diff --git a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp index 6d1119b998e4d..884519b54acfd 100644 --- a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp +++ b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp @@ -20,6 +20,8 @@ #include "llvm/ADT/StringExtras.h" #include "llvm/Support/Endian.h" #include "llvm/Support/FileSystem.h" +#include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/HTTP/HTTPClient.h" #include "llvm/Support/Path.h" using namespace lldb; @@ -65,11 +67,11 @@ static PluginProperties &GetGlobalPluginProperties() { SymbolLocatorSymStore::SymbolLocatorSymStore() : SymbolLocator() {} void SymbolLocatorSymStore::Initialize() { - // First version can only locate PDB in local SymStore (no download yet). PluginManager::RegisterPlugin( GetPluginNameStatic(), GetPluginDescriptionStatic(), CreateInstance, nullptr, LocateExecutableSymbolFile, nullptr, nullptr, SymbolLocatorSymStore::DebuggerInitialize); + llvm::HTTPClient::initialize(); } void SymbolLocatorSymStore::DebuggerInitialize(Debugger &debugger) { @@ -85,6 +87,7 @@ void SymbolLocatorSymStore::DebuggerInitialize(Debugger &debugger) { void SymbolLocatorSymStore::Terminate() { PluginManager::UnregisterPlugin(CreateInstance); + llvm::HTTPClient::cleanup(); } llvm::StringRef SymbolLocatorSymStore::GetPluginDescriptionStatic() { @@ -95,6 +98,8 @@ SymbolLocator *SymbolLocatorSymStore::CreateInstance() { return new SymbolLocatorSymStore(); } +namespace { + // RSDS entries store identity as a 20-byte UUID composed of 16-byte GUID and // 4-byte age: // 12345678-1234-5678-9ABC-DEF012345678-00000001 @@ -102,13 +107,150 @@ SymbolLocator *SymbolLocatorSymStore::CreateInstance() { // SymStore key is a string with no separators and age as decimal: // 12345678123456789ABCDEF0123456781 // -static std::string formatSymStoreKey(const UUID &uuid) { +std::string formatSymStoreKey(const UUID &uuid) { llvm::ArrayRef<uint8_t> bytes = uuid.GetBytes(); uint32_t age = llvm::support::endian::read32be(bytes.data() + 16); constexpr bool LowerCase = false; return llvm::toHex(bytes.slice(0, 16), LowerCase) + std::to_string(age); } +// This is a simple version of Debuginfod's StreamedHTTPResponseHandler. We +// should consider reusing that once we introduce caching. +class FileDownloadHandler : public llvm::HTTPResponseHandler { +private: + std::error_code m_ec; + llvm::raw_fd_ostream m_stream; + +public: + FileDownloadHandler(llvm::StringRef file) : m_stream(file.str(), m_ec) {} + virtual ~FileDownloadHandler() = default; + + llvm::Error handleBodyChunk(llvm::StringRef data) override { + // Propagate error from ctor. + if (m_ec) + return llvm::createStringError(m_ec, "Failed to open file for writing"); + m_stream.write(data.data(), data.size()); + if (std::error_code ec = m_stream.error()) + return llvm::createStringError(ec, "Error writing to file"); + + return llvm::Error::success(); + } +}; + +llvm::Error downloadFileHTTP(llvm::StringRef url, FileDownloadHandler dest) { + if (!llvm::HTTPClient::isAvailable()) + return llvm::createStringError( + std::make_error_code(std::errc::not_supported), + "HTTP client is not available"); + llvm::HTTPRequest Request(url); + Request.FollowRedirects = true; + + llvm::HTTPClient Client; + + // TODO: Since PDBs can be huge, we should distinguish between resolve, + // connect, send and receive. + Client.setTimeout(std::chrono::seconds(60)); + + if (llvm::Error Err = Client.perform(Request, dest)) + return Err; + + unsigned ResponseCode = Client.responseCode(); + if (ResponseCode != 200) { + return llvm::createStringError(std::make_error_code(std::errc::io_error), + "HTTP request failed with status code " + + std::to_string(ResponseCode)); + } + + return llvm::Error::success(); +} + +bool has_unsafe_characters(llvm::StringRef s) { + for (unsigned char c : s) { + // RFC 3986 unreserved characters are safe for file names and URLs. + if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || + (c >= '0' && c <= '9') || c == '-' || c == '.' || c == '_' || + c == '~') { + continue; + } + + return true; + } + + // Avoid path semantics issues. + return s == "." || s == ".."; +} + +// TODO: This is a dump initial implementation: It always downloads the file, it +// doesn't validate the result, it doesn't employ proper buffering for large +// files. +std::optional<FileSpec> +requestFileFromSymStoreServerHTTP(llvm::StringRef base_url, llvm::StringRef key, + llvm::StringRef pdb_name) { + using namespace llvm::sys; + Log *log = GetLog(LLDBLog::Symbols); + + // Make sure URL will be valid, portable, and compatible with symbol servers. + if (has_unsafe_characters(pdb_name)) { + Debugger::ReportWarning(llvm::formatv( + "Rejecting HTTP lookup for PDB file due to unsafe characters in " + "name: {0}", + pdb_name)); + return {}; + } + + // Construct the path for local storage. Configurable cache coming soon. + llvm::SmallString<128> cache_file; + if (!path::cache_directory(cache_file)) { + Debugger::ReportWarning("Failed to determine cache directory for SymStore"); + return {}; + } + path::append(cache_file, "lldb", "SymStore", pdb_name, key); + if (std::error_code ec = fs::create_directories(cache_file)) { + Debugger::ReportWarning( + llvm::formatv("Failed to create cache directory '{0}': {1}", cache_file, + ec.message())); + return {}; + } + path::append(cache_file, pdb_name); + + // Server has same directory structure with forward slashes as separators. + std::string source_url = + llvm::formatv("{0}/{1}/{2}/{1}", base_url, pdb_name, key); + if (llvm::Error err = downloadFileHTTP(source_url, cache_file.str())) { + LLDB_LOG_ERROR(log, std::move(err), + "Failed to download from SymStore '{1}': {0}", source_url); + return {}; + } + + return FileSpec(cache_file.str()); +} + +std::optional<FileSpec> findFileInLocalSymStore(llvm::StringRef root_dir, + llvm::StringRef key, + llvm::StringRef pdb_name) { + llvm::SmallString<256> path; + llvm::sys::path::append(path, root_dir, pdb_name, key, pdb_name); + FileSpec spec(path); + if (!FileSystem::Instance().Exists(spec)) + return {}; + + return spec; +} + +std::optional<FileSpec> locateSymStoreEntry(llvm::StringRef base_url, + llvm::StringRef key, + llvm::StringRef pdb_name) { + if (base_url.starts_with("http://") || base_url.starts_with("https://")) + return requestFileFromSymStoreServerHTTP(base_url, key, pdb_name); + + if (base_url.starts_with("file://")) + base_url = base_url.drop_front(7); + + return findFileInLocalSymStore(base_url, key, pdb_name); +} + +} // namespace + std::optional<FileSpec> SymbolLocatorSymStore::LocateExecutableSymbolFile( const ModuleSpec &module_spec, const FileSpecList &default_search_paths) { const UUID &uuid = module_spec.GetUUID(); @@ -135,12 +277,9 @@ std::optional<FileSpec> SymbolLocatorSymStore::LocateExecutableSymbolFile( std::string key = formatSymStoreKey(uuid); Args sym_store_urls = GetGlobalPluginProperties().GetURLs(); for (const Args::ArgEntry &url : sym_store_urls) { - llvm::SmallString<256> path; - llvm::sys::path::append(path, url.ref(), pdb_name, key, pdb_name); - FileSpec spec(path); - if (FileSystem::Instance().Exists(spec)) { + if (auto spec = locateSymStoreEntry(url.ref(), key, pdb_name)) { LLDB_LOG_VERBOSE(log, "Found {0} in SymStore {1}", pdb_name, url.ref()); - return spec; + return *spec; } } diff --git a/lldb/test/API/symstore/TestSymStoreLocal.py b/lldb/test/API/symstore/TestSymStore.py similarity index 68% rename from lldb/test/API/symstore/TestSymStoreLocal.py rename to lldb/test/API/symstore/TestSymStore.py index 98569d2b8c66f..13d0cc1666c84 100644 --- a/lldb/test/API/symstore/TestSymStoreLocal.py +++ b/lldb/test/API/symstore/TestSymStore.py @@ -1,5 +1,9 @@ +import http.server import os import shutil +import socketserver +import threading +from functools import partial import lldb from lldbsuite.test.decorators import * @@ -62,7 +66,31 @@ def __exit__(self, *exc_info): self._test.runCmd("settings clear plugin.symbol-locator.symstore") -class SymStoreLocalTests(TestBase): +class HTTPServer: + """ + Context Manager to serve a local directory tree via HTTP. + """ + + def __init__(self, dir): + address = ("localhost", 0) # auto-select free port + handler = partial(http.server.SimpleHTTPRequestHandler, directory=dir) + self._server = socketserver.ThreadingTCPServer(address, handler) + self._thread = threading.Thread(target=self._server.serve_forever, daemon=True) + + def __enter__(self): + self._thread.start() + host, port = self._server.server_address + return f"http://{host}:{port}" + + def __exit__(self, *exc_info): + if self._server: + self._server.shutdown() + self._server.server_close() + if self._thread: + self._thread.join() + + +class SymStoreTests(TestBase): SHARED_BUILD_TESTCASE = False TEST_WITH_PDB_DEBUG_INFO = True @@ -99,10 +127,8 @@ def test_external_lookup_off(self): Check that breakpoint doesn't resolve with external lookup disabled. """ exe, sym = self.build_inferior() - with MockedSymStore(self, exe, sym) as symstore_dir: - self.runCmd( - f"settings set plugin.symbol-locator.symstore.urls {symstore_dir}" - ) + with MockedSymStore(self, exe, sym) as dir: + self.runCmd(f"settings set plugin.symbol-locator.symstore.urls {dir}") self.try_breakpoint(exe, ext_lookup=False, should_have_loc=False) def test_local_dir(self): @@ -110,8 +136,18 @@ def test_local_dir(self): Check that breakpoint resolves with local SymStore. """ exe, sym = self.build_inferior() - with MockedSymStore(self, exe, sym) as symstore_dir: - self.runCmd( - f"settings set plugin.symbol-locator.symstore.urls {symstore_dir}" - ) + with MockedSymStore(self, exe, sym) as dir: + self.runCmd(f"settings set plugin.symbol-locator.symstore.urls {dir}") self.try_breakpoint(exe, should_have_loc=True) + + # TODO: Add test coverage for common HTTPS security scenarios, e.g. self-signed + # certs, non-HTTPS redirects, etc. + def test_http(self): + """ + Check that breakpoint hits with remote SymStore. + """ + exe, sym = self.build_inferior() + with MockedSymStore(self, exe, sym) as dir: + with HTTPServer(dir) as url: + self.runCmd(f"settings set plugin.symbol-locator.symstore.urls {url}") + self.try_breakpoint(exe, should_have_loc=True) diff --git a/llvm/include/llvm/Support/HTTP/HTTPClient.h b/llvm/include/llvm/Support/HTTP/HTTPClient.h index aa4727ad33024..17b706ff3e083 100644 --- a/llvm/include/llvm/Support/HTTP/HTTPClient.h +++ b/llvm/include/llvm/Support/HTTP/HTTPClient.h @@ -51,8 +51,8 @@ class HTTPResponseHandler { /// A reusable client that can perform HTTPRequests through a network socket. class HTTPClient { -#ifdef LLVM_ENABLE_CURL - void *Curl = nullptr; +#if defined(LLVM_ENABLE_CURL) || defined(_WIN32) + void *Handle = nullptr; #endif public: diff --git a/llvm/lib/Support/HTTP/CMakeLists.txt b/llvm/lib/Support/HTTP/CMakeLists.txt index 9bf1da8c60c88..e7a0e6fe34110 100644 --- a/llvm/lib/Support/HTTP/CMakeLists.txt +++ b/llvm/lib/Support/HTTP/CMakeLists.txt @@ -8,6 +8,11 @@ if (LLVM_ENABLE_HTTPLIB) set(imported_libs ${imported_libs} httplib::httplib) endif() +# Use WinHTTP on Windows +if (WIN32) + set(imported_libs ${imported_libs} winhttp.lib) +endif() + add_llvm_component_library(LLVMSupportHTTP HTTPClient.cpp HTTPServer.cpp diff --git a/llvm/lib/Support/HTTP/HTTPClient.cpp b/llvm/lib/Support/HTTP/HTTPClient.cpp index 6301f86da4086..69780b32d1cf0 100644 --- a/llvm/lib/Support/HTTP/HTTPClient.cpp +++ b/llvm/lib/Support/HTTP/HTTPClient.cpp @@ -23,6 +23,9 @@ #ifdef LLVM_ENABLE_CURL #include <curl/curl.h> #endif +#ifdef _WIN32 +#include "llvm/Support/ConvertUTF.h" +#endif using namespace llvm; @@ -64,7 +67,7 @@ void HTTPClient::cleanup() { void HTTPClient::setTimeout(std::chrono::milliseconds Timeout) { if (Timeout < std::chrono::milliseconds(0)) Timeout = std::chrono::milliseconds(0); - curl_easy_setopt(Curl, CURLOPT_TIMEOUT_MS, Timeout.count()); + curl_easy_setopt(Handle, CURLOPT_TIMEOUT_MS, Timeout.count()); } /// CurlHTTPRequest and the curl{Header,Write}Function are implementation @@ -93,17 +96,17 @@ static size_t curlWriteFunction(char *Contents, size_t Size, size_t NMemb, HTTPClient::HTTPClient() { assert(IsInitialized && "Must call HTTPClient::initialize() at the beginning of main()."); - if (Curl) + if (Handle) return; - Curl = curl_easy_init(); - assert(Curl && "Curl could not be initialized"); + Handle = curl_easy_init(); + assert(Handle && "Curl could not be initialized"); // Set the callback hooks. - curl_easy_setopt(Curl, CURLOPT_WRITEFUNCTION, curlWriteFunction); + curl_easy_setopt(Handle, CURLOPT_WRITEFUNCTION, curlWriteFunction); // Detect supported compressed encodings and accept all. - curl_easy_setopt(Curl, CURLOPT_ACCEPT_ENCODING, ""); + curl_easy_setopt(Handle, CURLOPT_ACCEPT_ENCODING, ""); } -HTTPClient::~HTTPClient() { curl_easy_cleanup(Curl); } +HTTPClient::~HTTPClient() { curl_easy_cleanup(Handle); } Error HTTPClient::perform(const HTTPRequest &Request, HTTPResponseHandler &Handler) { @@ -112,17 +115,17 @@ Error HTTPClient::perform(const HTTPRequest &Request, "Unsupported CURL request method."); SmallString<128> Url = Request.Url; - curl_easy_setopt(Curl, CURLOPT_URL, Url.c_str()); - curl_easy_setopt(Curl, CURLOPT_FOLLOWLOCATION, Request.FollowRedirects); + curl_easy_setopt(Handle, CURLOPT_URL, Url.c_str()); + curl_easy_setopt(Handle, CURLOPT_FOLLOWLOCATION, Request.FollowRedirects); curl_slist *Headers = nullptr; for (const std::string &Header : Request.Headers) Headers = curl_slist_append(Headers, Header.c_str()); - curl_easy_setopt(Curl, CURLOPT_HTTPHEADER, Headers); + curl_easy_setopt(Handle, CURLOPT_HTTPHEADER, Headers); CurlHTTPRequest CurlRequest(Handler); - curl_easy_setopt(Curl, CURLOPT_WRITEDATA, &CurlRequest); - CURLcode CurlRes = curl_easy_perform(Curl); + curl_easy_setopt(Handle, CURLOPT_WRITEDATA, &CurlRequest); + CURLcode CurlRes = curl_easy_perform(Handle); curl_slist_free_all(Headers); if (CurlRes != CURLE_OK) return joinErrors(std::move(CurlRequest.ErrorState), @@ -134,12 +137,240 @@ Error HTTPClient::perform(const HTTPRequest &Request, unsigned HTTPClient::responseCode() { long Code = 0; - curl_easy_getinfo(Curl, CURLINFO_RESPONSE_CODE, &Code); + curl_easy_getinfo(Handle, CURLINFO_RESPONSE_CODE, &Code); return Code; } #else +#ifdef _WIN32 +#include <windows.h> +#include <winhttp.h> +#pragma comment(lib, "winhttp.lib") + +namespace { + +struct WinHTTPSession { + HINTERNET SessionHandle = nullptr; + HINTERNET ConnectHandle = nullptr; + HINTERNET RequestHandle = nullptr; + DWORD ResponseCode = 0; + + ~WinHTTPSession() { + if (RequestHandle) + WinHttpCloseHandle(RequestHandle); + if (ConnectHandle) + WinHttpCloseHandle(ConnectHandle); + if (SessionHandle) + WinHttpCloseHandle(SessionHandle); + } +}; + +bool parseURL(StringRef Url, std::wstring &Host, std::wstring &Path, + INTERNET_PORT &Port, bool &Secure) { + // Parse URL: http://host:port/path + if (Url.starts_with("https://")) { + Secure = true; + Url = Url.drop_front(8); + } else if (Url.starts_with("http://")) { + Secure = false; + Url = Url.drop_front(7); + } else { + return false; + } + + size_t SlashPos = Url.find('/'); + StringRef HostPort = + (SlashPos != StringRef::npos) ? Url.substr(0, SlashPos) : Url; + StringRef PathPart = + (SlashPos != StringRef::npos) ? Url.substr(SlashPos) : StringRef("/"); + + size_t ColonPos = HostPort.find(':'); + StringRef HostStr = + (ColonPos != StringRef::npos) ? HostPort.substr(0, ColonPos) : HostPort; + + if (!llvm::ConvertUTF8toWide(HostStr, Host)) + return false; + if (!llvm::ConvertUTF8toWide(PathPart, Path)) + return false; + + if (ColonPos != StringRef::npos) { + StringRef PortStr = HostPort.substr(ColonPos + 1); + Port = static_cast<INTERNET_PORT>(std::stoi(PortStr.str())); + } else { + Port = Secure ? INTERNET_DEFAULT_HTTPS_PORT : INTERNET_DEFAULT_HTTP_PORT; + } + + return true; +} + +} // namespace + +HTTPClient::HTTPClient() : Handle(new WinHTTPSession()) {} + +HTTPClient::~HTTPClient() { delete static_cast<WinHTTPSession *>(Handle); } + +bool HTTPClient::isAvailable() { return true; } + +void HTTPClient::initialize() { + if (!IsInitialized) { + IsInitialized = true; + } +} + +void HTTPClient::cleanup() { + if (IsInitialized) { + IsInitialized = false; + } +} + +void HTTPClient::setTimeout(std::chrono::milliseconds Timeout) { + WinHTTPSession *Session = static_cast<WinHTTPSession *>(Handle); + if (Session && Session->SessionHandle) { + DWORD TimeoutMs = static_cast<DWORD>(Timeout.count()); + WinHttpSetOption(Session->SessionHandle, WINHTTP_OPTION_CONNECT_TIMEOUT, + &TimeoutMs, sizeof(TimeoutMs)); + WinHttpSetOption(Session->SessionHandle, WINHTTP_OPTION_RECEIVE_TIMEOUT, + &TimeoutMs, sizeof(TimeoutMs)); + WinHttpSetOption(Session->SessionHandle, WINHTTP_OPTION_SEND_TIMEOUT, + &TimeoutMs, sizeof(TimeoutMs)); + } +} + +Error HTTPClient::perform(const HTTPRequest &Request, + HTTPResponseHandler &Handler) { + if (Request.Method != HTTPMethod::GET) + return createStringError(errc::invalid_argument, + "Only GET requests are supported."); + for (const std::string &Header : Request.Headers) + if (Header.find("\r") != std::string::npos || + Header.find("\n") != std::string::npos) { + return createStringError(errc::invalid_argument, + "Unsafe request can lead to header injection."); + } + + WinHTTPSession *Session = static_cast<WinHTTPSession *>(Handle); + + // Parse URL + std::wstring Host, Path; + INTERNET_PORT Port = 0; + bool Secure = false; + if (!parseURL(Request.Url, Host, Path, Port, Secure)) + return createStringError(errc::invalid_argument, + "Invalid URL: " + Request.Url); + + // Create session + Session->SessionHandle = + WinHttpOpen(L"LLVM-HTTPClient/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, + WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); + if (!Session->SessionHandle) + return createStringError(errc::io_error, "Failed to open WinHTTP session"); + + // Prevent fallback to TLS 1.0/1.1 + DWORD SecureProtocols = + WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_3; + if (!WinHttpSetOption(Session->SessionHandle, WINHTTP_OPTION_SECURE_PROTOCOLS, + &SecureProtocols, sizeof(SecureProtocols))) + return createStringError(errc::io_error, "Failed to set secure protocols"); + + // Use HTTP/2 if available + DWORD EnableHttp2 = WINHTTP_PROTOCOL_FLAG_HTTP2; + WinHttpSetOption(Session->SessionHandle, WINHTTP_OPTION_ENABLE_HTTP_PROTOCOL, + &EnableHttp2, sizeof(EnableHttp2)); + + // Create connection + Session->ConnectHandle = + WinHttpConnect(Session->SessionHandle, Host.c_str(), Port, 0); + if (!Session->ConnectHandle) { + return createStringError(errc::io_error, + "Failed to connect to host: " + Request.Url); + } + + // Open request + DWORD Flags = WINHTTP_FLAG_REFRESH; + if (Secure) + Flags |= WINHTTP_FLAG_SECURE; + + Session->RequestHandle = WinHttpOpenRequest( + Session->ConnectHandle, L"GET", Path.c_str(), nullptr, WINHTTP_NO_REFERER, + WINHTTP_DEFAULT_ACCEPT_TYPES, Flags); + if (!Session->RequestHandle) + return createStringError(errc::io_error, "Failed to open HTTP request"); + + // Enforce checks that certificate wasn't revoked. + DWORD EnableRevocationChecks = WINHTTP_ENABLE_SSL_REVOCATION; + if (!WinHttpSetOption(Session->RequestHandle, WINHTTP_OPTION_ENABLE_FEATURE, + &EnableRevocationChecks, + sizeof(EnableRevocationChecks))) + return createStringError(errc::io_error, + "Failed to enable certificate revocation checks"); + + // Explicitly enforce default validation. This protects against insecure + // overrides like SECURITY_FLAG_IGNORE_UNKNOWN_CA. + DWORD SecurityFlags = 0; + if (!WinHttpSetOption(Session->RequestHandle, WINHTTP_OPTION_SECURITY_FLAGS, + &SecurityFlags, sizeof(SecurityFlags))) + return createStringError(errc::io_error, + "Failed to enforce security flags"); + + // Add headers + for (const std::string &Header : Request.Headers) { + std::wstring WideHeader; + if (!llvm::ConvertUTF8toWide(Header, WideHeader)) + continue; + WinHttpAddRequestHeaders(Session->RequestHandle, WideHeader.c_str(), + static_cast<DWORD>(WideHeader.length()), + WINHTTP_ADDREQ_FLAG_ADD); + } + + // Send request + if (!WinHttpSendRequest(Session->RequestHandle, WINHTTP_NO_ADDITIONAL_HEADERS, + 0, nullptr, 0, 0, 0)) + return createStringError(errc::io_error, "Failed to send HTTP request"); + + // Receive response + if (!WinHttpReceiveResponse(Session->RequestHandle, nullptr)) + return createStringError(errc::io_error, "Failed to receive HTTP response"); + + // Get response code + DWORD CodeSize = sizeof(Session->ResponseCode); + if (!WinHttpQueryHeaders(Session->RequestHandle, + WINHTTP_QUERY_STATUS_CODE | + WINHTTP_QUERY_FLAG_NUMBER, + WINHTTP_HEADER_NAME_BY_INDEX, &Session->ResponseCode, + &CodeSize, nullptr)) + Session->ResponseCode = 0; + + // Read response body + DWORD BytesAvailable = 0; + while (WinHttpQueryDataAvailable(Session->RequestHandle, &BytesAvailable)) { + if (BytesAvailable == 0) + break; + + std::vector<char> Buffer(BytesAvailable); + DWORD BytesRead = 0; + if (!WinHttpReadData(Session->RequestHandle, Buffer.data(), BytesAvailable, + &BytesRead)) + return createStringError(errc::io_error, "Failed to read HTTP response"); + + if (BytesRead > 0) { + if (Error Err = + Handler.handleBodyChunk(StringRef(Buffer.data(), BytesRead))) + return Err; + } + } + + return Error::success(); +} + +unsigned HTTPClient::responseCode() { + WinHTTPSession *Session = static_cast<WinHTTPSession *>(Handle); + return Session ? Session->ResponseCode : 0; +} + +#else // _WIN32 + +// Non-Windows, non-libcurl stub implementations HTTPClient::HTTPClient() = default; HTTPClient::~HTTPClient() = default; @@ -161,4 +392,6 @@ unsigned HTTPClient::responseCode() { llvm_unreachable("No HTTP Client implementation available."); } +#endif // _WIN32 + #endif diff --git a/llvm/test/tools/llvm-debuginfod-find/headers.test b/llvm/test/tools/llvm-debuginfod-find/headers-curl.test similarity index 100% rename from llvm/test/tools/llvm-debuginfod-find/headers.test rename to llvm/test/tools/llvm-debuginfod-find/headers-curl.test diff --git a/llvm/test/tools/llvm-debuginfod-find/headers-winhttp.test b/llvm/test/tools/llvm-debuginfod-find/headers-winhttp.test new file mode 100644 index 0000000000000..96a73c6bf373a --- /dev/null +++ b/llvm/test/tools/llvm-debuginfod-find/headers-winhttp.test @@ -0,0 +1,31 @@ +REQUIRES: system-windows + +RUN: rm -rf %t +RUN: mkdir -p %t/debuginfod-cache +RUN: %python %S/Inputs/capture_req.py llvm-debuginfod-find --debuginfo 0 \ +RUN: | FileCheck --check-prefix NO-HEADERS %s +RUN: env DEBUGINFOD_CACHE=%t/debuginfod-cache DEBUGINFOD_HEADERS_FILE=bad %python %S/Inputs/capture_req.py \ +RUN: llvm-debuginfod-find --debuginfo 0 \ +RUN: | FileCheck --check-prefix NO-HEADERS %s +RUN: rm -rf %t/debuginfod-cache/* +RUN: env DEBUGINFOD_CACHE=%t/debuginfod-cache DEBUGINFOD_HEADERS_FILE=%S/Inputs/headers %python %S/Inputs/capture_req.py \ +RUN: llvm-debuginfod-find --debuginfo 0 \ +RUN: | FileCheck --check-prefix HEADERS %s +RUN: rm -rf %t/debuginfod-cache/* +RUN: env DEBUGINFOD_CACHE=%t/debuginfod-cache DEBUGINFOD_HEADERS_FILE=%S/Inputs/headers DEBUGINFOD_URLS=fake not llvm-debuginfod-find --debuginfo 0 2>&1 \ +RUN: | FileCheck --check-prefix ERR -DHEADER_FILE=%S/Inputs/headers %s + +NO-HEADERS: User-Agent: LLVM-HTTPClient/1.0 +NO-HEADERS-NEXT: Host: localhost:{{[0-9]+}} + +HEADERS: User-Agent: LLVM-HTTPClient/1.0 +HEADERS-NEXT: A: B +HEADERS-NEXT: C: D +HEADERS-NEXT: E: F +HEADERS-NEXT: hi!$: j k +HEADERS-NEXT: Host: localhost:{{[0-9]+}} + +ERR: warning: could not parse debuginfod header: [[HEADER_FILE]]:3 +ERR-NEXT: warning: could not parse debuginfod header: [[HEADER_FILE]]:4 +ERR-NEXT: warning: could not parse debuginfod header: [[HEADER_FILE]]:5 +ERR-NEXT: warning: could not parse debuginfod header: [[HEADER_FILE]]:6 _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
