LLVM 23.0.0git
HTTPClient.cpp
Go to the documentation of this file.
1//===--- HTTPClient.cpp - HTTP client library -----------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8///
9/// \file
10/// This file defines the implementation of the HTTPClient library for issuing
11/// HTTP requests and handling the responses.
12///
13//===----------------------------------------------------------------------===//
14
16
17#include "llvm/ADT/APInt.h"
18#include "llvm/ADT/StringRef.h"
19#include "llvm/Support/Errc.h"
20#include "llvm/Support/Error.h"
23#ifdef LLVM_ENABLE_CURL
24#include <curl/curl.h>
25#endif
26#ifdef _WIN32
28#endif
29
30using namespace llvm;
31
33
34bool operator==(const HTTPRequest &A, const HTTPRequest &B) {
35 return A.Url == B.Url && A.Method == B.Method &&
36 A.FollowRedirects == B.FollowRedirects &&
37 A.PinnedCertFingerprint == B.PinnedCertFingerprint;
38}
39
41
42bool HTTPClient::IsInitialized = false;
43
49
50#ifdef LLVM_ENABLE_CURL
51
52bool HTTPClient::isAvailable() { return true; }
53
55 if (!IsInitialized) {
56 curl_global_init(CURL_GLOBAL_ALL);
57 IsInitialized = true;
58 }
59}
60
62 if (IsInitialized) {
63 curl_global_cleanup();
64 IsInitialized = false;
65 }
66}
67
68void HTTPClient::setTimeout(std::chrono::milliseconds Timeout) {
69 if (Timeout < std::chrono::milliseconds(0))
70 Timeout = std::chrono::milliseconds(0);
71 curl_easy_setopt(Handle, CURLOPT_TIMEOUT_MS, Timeout.count());
72}
73
74/// CurlHTTPRequest and the curl{Header,Write}Function are implementation
75/// details used to work with Curl. Curl makes callbacks with a single
76/// customizable pointer parameter.
77struct CurlHTTPRequest {
78 CurlHTTPRequest(HTTPResponseHandler &Handler) : Handler(Handler) {}
79 void storeError(Error Err) {
80 ErrorState = joinErrors(std::move(Err), std::move(ErrorState));
81 }
82 HTTPResponseHandler &Handler;
83 llvm::Error ErrorState = Error::success();
84};
85
86static size_t curlWriteFunction(char *Contents, size_t Size, size_t NMemb,
87 CurlHTTPRequest *CurlRequest) {
88 Size *= NMemb;
89 if (Error Err =
90 CurlRequest->Handler.handleBodyChunk(StringRef(Contents, Size))) {
91 CurlRequest->storeError(std::move(Err));
92 return 0;
93 }
94 return Size;
95}
96
99 "Must call HTTPClient::initialize() at the beginning of main().");
100 if (Handle)
101 return;
102 Handle = curl_easy_init();
103 assert(Handle && "Curl could not be initialized");
104 // Set the callback hooks.
105 curl_easy_setopt(Handle, CURLOPT_WRITEFUNCTION, curlWriteFunction);
106 // Detect supported compressed encodings and accept all.
107 curl_easy_setopt(Handle, CURLOPT_ACCEPT_ENCODING, "");
108}
109
110HTTPClient::~HTTPClient() { curl_easy_cleanup(Handle); }
111
113 HTTPResponseHandler &Handler) {
114 if (Request.Method != HTTPMethod::GET)
116 "Unsupported CURL request method.");
117
118 SmallString<128> Url = Request.Url;
119 curl_easy_setopt(Handle, CURLOPT_URL, Url.c_str());
120 curl_easy_setopt(Handle, CURLOPT_FOLLOWLOCATION, Request.FollowRedirects);
121
122 curl_slist *Headers = nullptr;
123 for (const std::string &Header : Request.Headers)
124 Headers = curl_slist_append(Headers, Header.c_str());
125 curl_easy_setopt(Handle, CURLOPT_HTTPHEADER, Headers);
126
127 CurlHTTPRequest CurlRequest(Handler);
128 curl_easy_setopt(Handle, CURLOPT_WRITEDATA, &CurlRequest);
129 CURLcode CurlRes = curl_easy_perform(Handle);
130 curl_slist_free_all(Headers);
131 if (CurlRes != CURLE_OK)
132 return joinErrors(std::move(CurlRequest.ErrorState),
134 "curl_easy_perform() failed: %s\n",
135 curl_easy_strerror(CurlRes)));
136 return std::move(CurlRequest.ErrorState);
137}
138
139unsigned HTTPClient::responseCode() {
140 long Code = 0;
141 curl_easy_getinfo(Handle, CURLINFO_RESPONSE_CODE, &Code);
142 return Code;
143}
144
145#else
146
147#ifdef _WIN32
148
149// We cannot sort these headers alphabetically.
150// clang-format off
151#include <windows.h>
152#include <wincrypt.h>
153#include <winhttp.h>
154// clang-format on
155
156namespace {
157
158struct WinHTTPSession {
159 HINTERNET SessionHandle = nullptr;
160 HINTERNET ConnectHandle = nullptr;
161 HINTERNET RequestHandle = nullptr;
162 DWORD ResponseCode = 0;
163
164 ~WinHTTPSession() {
165 if (RequestHandle)
166 WinHttpCloseHandle(RequestHandle);
167 if (ConnectHandle)
168 WinHttpCloseHandle(ConnectHandle);
169 if (SessionHandle)
170 WinHttpCloseHandle(SessionHandle);
171 }
172};
173
174bool parseURL(StringRef Url, std::wstring &Host, std::wstring &Path,
175 INTERNET_PORT &Port, bool &Secure) {
176 // Parse URL: http://host:port/path
177 if (Url.starts_with("https://")) {
178 Secure = true;
179 Url = Url.drop_front(8);
180 } else if (Url.starts_with("http://")) {
181 Secure = false;
182 Url = Url.drop_front(7);
183 } else {
184 return false;
185 }
186
187 size_t SlashPos = Url.find('/');
188 StringRef HostPort =
189 (SlashPos != StringRef::npos) ? Url.substr(0, SlashPos) : Url;
190 StringRef PathPart =
191 (SlashPos != StringRef::npos) ? Url.substr(SlashPos) : StringRef("/");
192
193 size_t ColonPos = HostPort.find(':');
194 StringRef HostStr =
195 (ColonPos != StringRef::npos) ? HostPort.substr(0, ColonPos) : HostPort;
196
197 if (!llvm::ConvertUTF8toWide(HostStr, Host))
198 return false;
199 if (!llvm::ConvertUTF8toWide(PathPart, Path))
200 return false;
201
202 if (ColonPos != StringRef::npos) {
203 StringRef PortStr = HostPort.substr(ColonPos + 1);
204 Port = static_cast<INTERNET_PORT>(std::stoi(PortStr.str()));
205 } else {
206 Port = Secure ? INTERNET_DEFAULT_HTTPS_PORT : INTERNET_DEFAULT_HTTP_PORT;
207 }
208
209 return true;
210}
211
212} // namespace
213
214HTTPClient::HTTPClient() : Handle(new WinHTTPSession()) {}
215
216HTTPClient::~HTTPClient() { delete static_cast<WinHTTPSession *>(Handle); }
217
218bool HTTPClient::isAvailable() { return true; }
219
221 if (!IsInitialized) {
222 IsInitialized = true;
223 }
224}
225
226void HTTPClient::cleanup() {
227 if (IsInitialized) {
228 IsInitialized = false;
229 }
230}
231
232void HTTPClient::setTimeout(std::chrono::milliseconds Timeout) {
233 WinHTTPSession *Session = static_cast<WinHTTPSession *>(Handle);
234 if (Session && Session->SessionHandle) {
235 DWORD TimeoutMs = static_cast<DWORD>(Timeout.count());
236 WinHttpSetOption(Session->SessionHandle, WINHTTP_OPTION_CONNECT_TIMEOUT,
237 &TimeoutMs, sizeof(TimeoutMs));
238 WinHttpSetOption(Session->SessionHandle, WINHTTP_OPTION_RECEIVE_TIMEOUT,
239 &TimeoutMs, sizeof(TimeoutMs));
240 WinHttpSetOption(Session->SessionHandle, WINHTTP_OPTION_SEND_TIMEOUT,
241 &TimeoutMs, sizeof(TimeoutMs));
242 }
243}
244
245static Error VerifyTLSCertWinHTTP(HINTERNET RequestHandle,
246 const std::string &PinnedFingerprint) {
247 // Decode the expected fingerprint from hex into binary.
248 BYTE Expected[32];
249 DWORD ExpectedSize = sizeof(Expected);
250 if (!CryptStringToBinaryA(
251 PinnedFingerprint.c_str(), (DWORD)PinnedFingerprint.size(),
252 CRYPT_STRING_HEXRAW, Expected, &ExpectedSize, nullptr, nullptr))
254 "Invalid certificate fingerprint format");
255
256 // Retrieve the server certificate and compute its SHA-256 hash.
257 PCCERT_CONTEXT CertCtx = nullptr;
258 DWORD CertCtxSize = sizeof(CertCtx);
259 if (!WinHttpQueryOption(RequestHandle, WINHTTP_OPTION_SERVER_CERT_CONTEXT,
260 &CertCtx, &CertCtxSize))
262 "Failed to retrieve server certificate");
263
264 std::array<BYTE, 32> Actual;
265 DWORD ActualSize = Actual.size();
266 bool GotHash = CertGetCertificateContextProperty(
267 CertCtx, CERT_SHA256_HASH_PROP_ID, Actual.data(), &ActualSize);
268 CertFreeCertificateContext(CertCtx);
269 if (!GotHash)
271 "Failed to compute certificate fingerprint");
272
273 if (memcmp(Actual.data(), Expected, Actual.size()) != 0)
275 "Certificate fingerprint mismatch");
276
277 return Error::success();
278}
279
281 HTTPResponseHandler &Handler) {
282 if (Request.Method != HTTPMethod::GET)
284 "Only GET requests are supported.");
285 for (const std::string &Header : Request.Headers)
286 if (Header.find("\r") != std::string::npos ||
287 Header.find("\n") != std::string::npos) {
289 "Unsafe request can lead to header injection.");
290 }
291
292 WinHTTPSession *Session = static_cast<WinHTTPSession *>(Handle);
293
294 // Parse URL
295 std::wstring Host, Path;
296 INTERNET_PORT Port = 0;
297 bool Secure = false;
298 if (!parseURL(Request.Url, Host, Path, Port, Secure))
300 "Invalid URL: " + Request.Url);
301
302 // Create session
303 Session->SessionHandle =
304 WinHttpOpen(L"LLVM-HTTPClient/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
305 WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
306 if (!Session->SessionHandle)
307 return createStringError(errc::io_error, "Failed to open WinHTTP session");
308
309 // Prevent fallback to TLS 1.0/1.1
310 DWORD SecureProtocols =
311 WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_3;
312 if (!WinHttpSetOption(Session->SessionHandle, WINHTTP_OPTION_SECURE_PROTOCOLS,
313 &SecureProtocols, sizeof(SecureProtocols)))
314 return createStringError(errc::io_error, "Failed to set secure protocols");
315
316 // Disallow redirects in general or HTTPS to HTTP only.
317 DWORD RedirectPolicy = WINHTTP_OPTION_REDIRECT_POLICY_DISALLOW_HTTPS_TO_HTTP;
318 if (!Request.FollowRedirects)
319 RedirectPolicy = WINHTTP_OPTION_REDIRECT_POLICY_NEVER;
320 if (!WinHttpSetOption(Session->SessionHandle, WINHTTP_OPTION_REDIRECT_POLICY,
321 &RedirectPolicy, sizeof(RedirectPolicy)))
322 return createStringError(errc::io_error, "Failed to set redirect policy");
323
324 // Use HTTP/2 if available
325 DWORD EnableHttp2 = WINHTTP_PROTOCOL_FLAG_HTTP2;
326 WinHttpSetOption(Session->SessionHandle, WINHTTP_OPTION_ENABLE_HTTP_PROTOCOL,
327 &EnableHttp2, sizeof(EnableHttp2));
328
329 // Create connection
330 Session->ConnectHandle =
331 WinHttpConnect(Session->SessionHandle, Host.c_str(), Port, 0);
332 if (!Session->ConnectHandle) {
334 "Failed to connect to host: " + Request.Url);
335 }
336
337 // Open request
338 DWORD Flags = WINHTTP_FLAG_REFRESH;
339 if (Secure)
340 Flags |= WINHTTP_FLAG_SECURE;
341
342 Session->RequestHandle = WinHttpOpenRequest(
343 Session->ConnectHandle, L"GET", Path.c_str(), nullptr, WINHTTP_NO_REFERER,
344 WINHTTP_DEFAULT_ACCEPT_TYPES, Flags);
345 if (!Session->RequestHandle)
346 return createStringError(errc::io_error, "Failed to open HTTP request");
347
348 DWORD SecurityFlags = 0;
349 if (Secure) {
350 // Enforce checks that certificate wasn't revoked.
351 DWORD EnableRevocationChecks = WINHTTP_ENABLE_SSL_REVOCATION;
352 if (!WinHttpSetOption(Session->RequestHandle, WINHTTP_OPTION_ENABLE_FEATURE,
353 &EnableRevocationChecks,
354 sizeof(EnableRevocationChecks)))
355 return createStringError(
356 errc::io_error, "Failed to enable certificate revocation checks");
357
358 // Bypass certificate chain validation with pinned certificates so
359 // that self-signed certificates are accepted at the WinHTTP level. Manual
360 // verification happens right after receiving the response.
361 if (Request.PinnedCertFingerprint)
362 SecurityFlags = (SecurityFlags | SECURITY_FLAG_IGNORE_UNKNOWN_CA);
363 if (!WinHttpSetOption(Session->RequestHandle, WINHTTP_OPTION_SECURITY_FLAGS,
364 &SecurityFlags, sizeof(SecurityFlags)))
366 "Failed to enforce security flags");
367 }
368
369 // Add headers
370 for (const std::string &Header : Request.Headers) {
371 std::wstring WideHeader;
372 if (!llvm::ConvertUTF8toWide(Header, WideHeader))
373 continue;
374 WinHttpAddRequestHeaders(Session->RequestHandle, WideHeader.c_str(),
375 static_cast<DWORD>(WideHeader.length()),
376 WINHTTP_ADDREQ_FLAG_ADD);
377 }
378
379 // Send request
380 if (!WinHttpSendRequest(Session->RequestHandle, WINHTTP_NO_ADDITIONAL_HEADERS,
381 0, nullptr, 0, 0, 0))
382 return createStringError(errc::io_error, "Failed to send HTTP request");
383
384 // Receive response
385 if (!WinHttpReceiveResponse(Session->RequestHandle, nullptr))
386 return createStringError(errc::io_error, "Failed to receive HTTP response");
387
388 // Verify the server certificate fingerprint if one was pinned.
389 if ((SecurityFlags & SECURITY_FLAG_IGNORE_UNKNOWN_CA) != 0)
390 if (Error Err = VerifyTLSCertWinHTTP(Session->RequestHandle,
391 *Request.PinnedCertFingerprint))
392 return Err;
393
394 // Get response code
395 DWORD CodeSize = sizeof(Session->ResponseCode);
396 if (!WinHttpQueryHeaders(Session->RequestHandle,
397 WINHTTP_QUERY_STATUS_CODE |
398 WINHTTP_QUERY_FLAG_NUMBER,
399 WINHTTP_HEADER_NAME_BY_INDEX, &Session->ResponseCode,
400 &CodeSize, nullptr))
401 Session->ResponseCode = 0;
402
403 // Read response body
404 DWORD BytesAvailable = 0;
405 while (WinHttpQueryDataAvailable(Session->RequestHandle, &BytesAvailable)) {
406 if (BytesAvailable == 0)
407 break;
408
409 std::vector<char> Buffer(BytesAvailable);
410 DWORD BytesRead = 0;
411 if (!WinHttpReadData(Session->RequestHandle, Buffer.data(), BytesAvailable,
412 &BytesRead))
413 return createStringError(errc::io_error, "Failed to read HTTP response");
414
415 if (BytesRead > 0) {
416 if (Error Err =
417 Handler.handleBodyChunk(StringRef(Buffer.data(), BytesRead)))
418 return Err;
419 }
420 }
421
422 return Error::success();
423}
424
425unsigned HTTPClient::responseCode() {
426 WinHTTPSession *Session = static_cast<WinHTTPSession *>(Handle);
427 return Session ? Session->ResponseCode : 0;
428}
429
430#else // _WIN32
431
432// Non-Windows, non-libcurl stub implementations
433HTTPClient::HTTPClient() = default;
434
435HTTPClient::~HTTPClient() = default;
436
437bool HTTPClient::isAvailable() { return false; }
438
440
442
443void HTTPClient::setTimeout(std::chrono::milliseconds Timeout) {}
444
446 HTTPResponseHandler &Handler) {
447 llvm_unreachable("No HTTP Client implementation available.");
448}
449
451 llvm_unreachable("No HTTP Client implementation available.");
452}
453
454#endif // _WIN32
455
456#endif
assert(UImm &&(UImm !=~static_cast< T >(0)) &&"Invalid immediate!")
This file implements a class to represent arbitrary precision integral constant values and operations...
static GCRegistry::Add< ErlangGC > A("erlang", "erlang-compatible garbage collector")
static GCRegistry::Add< OcamlGC > B("ocaml", "ocaml 3.10-compatible GC")
ManagedStatic< HTTPClientCleanup > Cleanup
This file contains the declarations of the HTTPClient library for issuing HTTP requests and handling ...
Lightweight error class with error context and mandatory checking.
Definition Error.h:159
static ErrorSuccess success()
Create a success value.
Definition Error.h:336
Tagged union holding either a T or a Error.
Definition Error.h:485
static bool isAvailable()
Returns true only if LLVM has been compiled with a working HTTPClient.
static bool IsInitialized
Definition HTTPClient.h:66
unsigned responseCode()
Returns the last received response code or zero if none.
static void initialize()
Must be called at the beginning of a program, while it is a single thread.
Error perform(const HTTPRequest &Request, HTTPResponseHandler &Handler)
Performs the Request, passing response data to the Handler.
void setTimeout(std::chrono::milliseconds Timeout)
Sets the timeout for the entire request, in milliseconds.
static void cleanup()
Must be called at the end of a program, while it is a single thread.
A handler for state updates occurring while an HTTPRequest is performed.
Definition HTTPClient.h:47
virtual Error handleBodyChunk(StringRef BodyChunk)=0
Processes an additional chunk of bytes of the HTTP response body.
ManagedStatic - This transparently changes the behavior of global statics to be lazily constructed on...
const char * c_str()
StringRef - Represent a constant reference to a string, i.e.
Definition StringRef.h:55
static constexpr size_t npos
Definition StringRef.h:57
std::string str() const
str - Get the contents as an std::string.
Definition StringRef.h:222
constexpr StringRef substr(size_t Start, size_t N=npos) const
Return a reference to the substring from [Start, Start + N).
Definition StringRef.h:591
bool starts_with(StringRef Prefix) const
Check if this string starts with the given Prefix.
Definition StringRef.h:258
StringRef drop_front(size_t N=1) const
Return a StringRef equal to 'this' but with the first N elements dropped.
Definition StringRef.h:629
size_t find(char C, size_t From=0) const
Search for the first character C in the string.
Definition StringRef.h:290
#define llvm_unreachable(msg)
Marks that the current location is not supposed to be reachable.
NodeAddr< CodeNode * > Code
Definition RDFGraph.h:388
This is an optimization pass for GlobalISel generic memory operations.
Error createStringError(std::error_code EC, char const *Fmt, const Ts &... Vals)
Create formatted StringError object.
Definition Error.h:1321
bool operator==(const AddressRangeValuePair &LHS, const AddressRangeValuePair &RHS)
@ io_error
Definition Errc.h:58
@ invalid_argument
Definition Errc.h:56
@ permission_denied
Definition Errc.h:71
Error joinErrors(Error E1, Error E2)
Concatenate errors.
Definition Error.h:442
@ Timeout
Reached timeout while waiting for the owner to release the lock.
LLVM_ABI bool ConvertUTF8toWide(unsigned WideCharWidth, llvm::StringRef Source, char *&ResultPtr, const UTF8 *&ErrorPtr)
Convert an UTF8 StringRef to UTF8, UTF16, or UTF32 depending on WideCharWidth.
A stateless description of an outbound HTTP request.
Definition HTTPClient.h:31
std::optional< std::string > PinnedCertFingerprint
Definition HTTPClient.h:38
SmallVector< std::string, 0 > Headers
Definition HTTPClient.h:33
HTTPRequest(StringRef Url)
SmallString< 128 > Url
Definition HTTPClient.h:32
HTTPMethod Method
Definition HTTPClient.h:34