[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 <aganea@havenstudios.com>
This commit is contained in:
Stefan Gränitz 2026-03-20 12:47:45 +01:00 committed by GitHub
parent 22f5b8db12
commit 39d6bb2180
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 476 additions and 31 deletions

View File

@ -13,6 +13,7 @@ add_lldb_library(lldbPluginSymbolLocatorSymStore PLUGIN
lldbCore
lldbHost
lldbSymbol
LLVMSupportHTTP
)
add_dependencies(lldbPluginSymbolLocatorSymStore

View File

@ -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;
}
}

View File

@ -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 @@ class MockedSymStore:
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 @@ class SymStoreLocalTests(TestBase):
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 @@ class SymStoreLocalTests(TestBase):
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)

View File

@ -51,8 +51,8 @@ protected:
/// 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:

View File

@ -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

View File

@ -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

View File

@ -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