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}
38
40
41bool HTTPClient::IsInitialized = false;
42
48
49#ifdef LLVM_ENABLE_CURL
50
51bool HTTPClient::isAvailable() { return true; }
52
54 if (!IsInitialized) {
55 curl_global_init(CURL_GLOBAL_ALL);
56 IsInitialized = true;
57 }
58}
59
61 if (IsInitialized) {
62 curl_global_cleanup();
63 IsInitialized = false;
64 }
65}
66
67void HTTPClient::setTimeout(std::chrono::milliseconds Timeout) {
68 if (Timeout < std::chrono::milliseconds(0))
69 Timeout = std::chrono::milliseconds(0);
70 curl_easy_setopt(Handle, CURLOPT_TIMEOUT_MS, Timeout.count());
71}
72
73/// CurlHTTPRequest and the curl{Header,Write}Function are implementation
74/// details used to work with Curl. Curl makes callbacks with a single
75/// customizable pointer parameter.
76struct CurlHTTPRequest {
77 CurlHTTPRequest(HTTPResponseHandler &Handler) : Handler(Handler) {}
78 void storeError(Error Err) {
79 ErrorState = joinErrors(std::move(Err), std::move(ErrorState));
80 }
81 HTTPResponseHandler &Handler;
82 llvm::Error ErrorState = Error::success();
83};
84
85static size_t curlWriteFunction(char *Contents, size_t Size, size_t NMemb,
86 CurlHTTPRequest *CurlRequest) {
87 Size *= NMemb;
88 if (Error Err =
89 CurlRequest->Handler.handleBodyChunk(StringRef(Contents, Size))) {
90 CurlRequest->storeError(std::move(Err));
91 return 0;
92 }
93 return Size;
94}
95
98 "Must call HTTPClient::initialize() at the beginning of main().");
99 if (Handle)
100 return;
101 Handle = curl_easy_init();
102 assert(Handle && "Curl could not be initialized");
103 // Set the callback hooks.
104 curl_easy_setopt(Handle, CURLOPT_WRITEFUNCTION, curlWriteFunction);
105 // Detect supported compressed encodings and accept all.
106 curl_easy_setopt(Handle, CURLOPT_ACCEPT_ENCODING, "");
107}
108
109HTTPClient::~HTTPClient() { curl_easy_cleanup(Handle); }
110
112 HTTPResponseHandler &Handler) {
113 if (Request.Method != HTTPMethod::GET)
115 "Unsupported CURL request method.");
116
117 SmallString<128> Url = Request.Url;
118 curl_easy_setopt(Handle, CURLOPT_URL, Url.c_str());
119 curl_easy_setopt(Handle, CURLOPT_FOLLOWLOCATION, Request.FollowRedirects);
120
121 curl_slist *Headers = nullptr;
122 for (const std::string &Header : Request.Headers)
123 Headers = curl_slist_append(Headers, Header.c_str());
124 curl_easy_setopt(Handle, CURLOPT_HTTPHEADER, Headers);
125
126 CurlHTTPRequest CurlRequest(Handler);
127 curl_easy_setopt(Handle, CURLOPT_WRITEDATA, &CurlRequest);
128 CURLcode CurlRes = curl_easy_perform(Handle);
129 curl_slist_free_all(Headers);
130 if (CurlRes != CURLE_OK)
131 return joinErrors(std::move(CurlRequest.ErrorState),
133 "curl_easy_perform() failed: %s\n",
134 curl_easy_strerror(CurlRes)));
135 return std::move(CurlRequest.ErrorState);
136}
137
138unsigned HTTPClient::responseCode() {
139 long Code = 0;
140 curl_easy_getinfo(Handle, CURLINFO_RESPONSE_CODE, &Code);
141 return Code;
142}
143
144#else
145
146#ifdef _WIN32
147#include <windows.h>
148#include <winhttp.h>
149#pragma comment(lib, "winhttp.lib")
150
151namespace {
152
153struct WinHTTPSession {
154 HINTERNET SessionHandle = nullptr;
155 HINTERNET ConnectHandle = nullptr;
156 HINTERNET RequestHandle = nullptr;
157 DWORD ResponseCode = 0;
158
159 ~WinHTTPSession() {
160 if (RequestHandle)
161 WinHttpCloseHandle(RequestHandle);
162 if (ConnectHandle)
163 WinHttpCloseHandle(ConnectHandle);
164 if (SessionHandle)
165 WinHttpCloseHandle(SessionHandle);
166 }
167};
168
169bool parseURL(StringRef Url, std::wstring &Host, std::wstring &Path,
170 INTERNET_PORT &Port, bool &Secure) {
171 // Parse URL: http://host:port/path
172 if (Url.starts_with("https://")) {
173 Secure = true;
174 Url = Url.drop_front(8);
175 } else if (Url.starts_with("http://")) {
176 Secure = false;
177 Url = Url.drop_front(7);
178 } else {
179 return false;
180 }
181
182 size_t SlashPos = Url.find('/');
183 StringRef HostPort =
184 (SlashPos != StringRef::npos) ? Url.substr(0, SlashPos) : Url;
185 StringRef PathPart =
186 (SlashPos != StringRef::npos) ? Url.substr(SlashPos) : StringRef("/");
187
188 size_t ColonPos = HostPort.find(':');
189 StringRef HostStr =
190 (ColonPos != StringRef::npos) ? HostPort.substr(0, ColonPos) : HostPort;
191
192 if (!llvm::ConvertUTF8toWide(HostStr, Host))
193 return false;
194 if (!llvm::ConvertUTF8toWide(PathPart, Path))
195 return false;
196
197 if (ColonPos != StringRef::npos) {
198 StringRef PortStr = HostPort.substr(ColonPos + 1);
199 Port = static_cast<INTERNET_PORT>(std::stoi(PortStr.str()));
200 } else {
201 Port = Secure ? INTERNET_DEFAULT_HTTPS_PORT : INTERNET_DEFAULT_HTTP_PORT;
202 }
203
204 return true;
205}
206
207} // namespace
208
209HTTPClient::HTTPClient() : Handle(new WinHTTPSession()) {}
210
211HTTPClient::~HTTPClient() { delete static_cast<WinHTTPSession *>(Handle); }
212
213bool HTTPClient::isAvailable() { return true; }
214
216 if (!IsInitialized) {
217 IsInitialized = true;
218 }
219}
220
221void HTTPClient::cleanup() {
222 if (IsInitialized) {
223 IsInitialized = false;
224 }
225}
226
227void HTTPClient::setTimeout(std::chrono::milliseconds Timeout) {
228 WinHTTPSession *Session = static_cast<WinHTTPSession *>(Handle);
229 if (Session && Session->SessionHandle) {
230 DWORD TimeoutMs = static_cast<DWORD>(Timeout.count());
231 WinHttpSetOption(Session->SessionHandle, WINHTTP_OPTION_CONNECT_TIMEOUT,
232 &TimeoutMs, sizeof(TimeoutMs));
233 WinHttpSetOption(Session->SessionHandle, WINHTTP_OPTION_RECEIVE_TIMEOUT,
234 &TimeoutMs, sizeof(TimeoutMs));
235 WinHttpSetOption(Session->SessionHandle, WINHTTP_OPTION_SEND_TIMEOUT,
236 &TimeoutMs, sizeof(TimeoutMs));
237 }
238}
239
241 HTTPResponseHandler &Handler) {
242 if (Request.Method != HTTPMethod::GET)
244 "Only GET requests are supported.");
245 for (const std::string &Header : Request.Headers)
246 if (Header.find("\r") != std::string::npos ||
247 Header.find("\n") != std::string::npos) {
249 "Unsafe request can lead to header injection.");
250 }
251
252 WinHTTPSession *Session = static_cast<WinHTTPSession *>(Handle);
253
254 // Parse URL
255 std::wstring Host, Path;
256 INTERNET_PORT Port = 0;
257 bool Secure = false;
258 if (!parseURL(Request.Url, Host, Path, Port, Secure))
260 "Invalid URL: " + Request.Url);
261
262 // Create session
263 Session->SessionHandle =
264 WinHttpOpen(L"LLVM-HTTPClient/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
265 WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
266 if (!Session->SessionHandle)
267 return createStringError(errc::io_error, "Failed to open WinHTTP session");
268
269 // Prevent fallback to TLS 1.0/1.1
270 DWORD SecureProtocols =
271 WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_3;
272 if (!WinHttpSetOption(Session->SessionHandle, WINHTTP_OPTION_SECURE_PROTOCOLS,
273 &SecureProtocols, sizeof(SecureProtocols)))
274 return createStringError(errc::io_error, "Failed to set secure protocols");
275
276 // Use HTTP/2 if available
277 DWORD EnableHttp2 = WINHTTP_PROTOCOL_FLAG_HTTP2;
278 WinHttpSetOption(Session->SessionHandle, WINHTTP_OPTION_ENABLE_HTTP_PROTOCOL,
279 &EnableHttp2, sizeof(EnableHttp2));
280
281 // Create connection
282 Session->ConnectHandle =
283 WinHttpConnect(Session->SessionHandle, Host.c_str(), Port, 0);
284 if (!Session->ConnectHandle) {
286 "Failed to connect to host: " + Request.Url);
287 }
288
289 // Open request
290 DWORD Flags = WINHTTP_FLAG_REFRESH;
291 if (Secure)
292 Flags |= WINHTTP_FLAG_SECURE;
293
294 Session->RequestHandle = WinHttpOpenRequest(
295 Session->ConnectHandle, L"GET", Path.c_str(), nullptr, WINHTTP_NO_REFERER,
296 WINHTTP_DEFAULT_ACCEPT_TYPES, Flags);
297 if (!Session->RequestHandle)
298 return createStringError(errc::io_error, "Failed to open HTTP request");
299
300 // Enforce checks that certificate wasn't revoked.
301 DWORD EnableRevocationChecks = WINHTTP_ENABLE_SSL_REVOCATION;
302 if (!WinHttpSetOption(Session->RequestHandle, WINHTTP_OPTION_ENABLE_FEATURE,
303 &EnableRevocationChecks,
304 sizeof(EnableRevocationChecks)))
306 "Failed to enable certificate revocation checks");
307
308 // Explicitly enforce default validation. This protects against insecure
309 // overrides like SECURITY_FLAG_IGNORE_UNKNOWN_CA.
310 DWORD SecurityFlags = 0;
311 if (!WinHttpSetOption(Session->RequestHandle, WINHTTP_OPTION_SECURITY_FLAGS,
312 &SecurityFlags, sizeof(SecurityFlags)))
314 "Failed to enforce security flags");
315
316 // Add headers
317 for (const std::string &Header : Request.Headers) {
318 std::wstring WideHeader;
319 if (!llvm::ConvertUTF8toWide(Header, WideHeader))
320 continue;
321 WinHttpAddRequestHeaders(Session->RequestHandle, WideHeader.c_str(),
322 static_cast<DWORD>(WideHeader.length()),
323 WINHTTP_ADDREQ_FLAG_ADD);
324 }
325
326 // Send request
327 if (!WinHttpSendRequest(Session->RequestHandle, WINHTTP_NO_ADDITIONAL_HEADERS,
328 0, nullptr, 0, 0, 0))
329 return createStringError(errc::io_error, "Failed to send HTTP request");
330
331 // Receive response
332 if (!WinHttpReceiveResponse(Session->RequestHandle, nullptr))
333 return createStringError(errc::io_error, "Failed to receive HTTP response");
334
335 // Get response code
336 DWORD CodeSize = sizeof(Session->ResponseCode);
337 if (!WinHttpQueryHeaders(Session->RequestHandle,
338 WINHTTP_QUERY_STATUS_CODE |
339 WINHTTP_QUERY_FLAG_NUMBER,
340 WINHTTP_HEADER_NAME_BY_INDEX, &Session->ResponseCode,
341 &CodeSize, nullptr))
342 Session->ResponseCode = 0;
343
344 // Read response body
345 DWORD BytesAvailable = 0;
346 while (WinHttpQueryDataAvailable(Session->RequestHandle, &BytesAvailable)) {
347 if (BytesAvailable == 0)
348 break;
349
350 std::vector<char> Buffer(BytesAvailable);
351 DWORD BytesRead = 0;
352 if (!WinHttpReadData(Session->RequestHandle, Buffer.data(), BytesAvailable,
353 &BytesRead))
354 return createStringError(errc::io_error, "Failed to read HTTP response");
355
356 if (BytesRead > 0) {
357 if (Error Err =
358 Handler.handleBodyChunk(StringRef(Buffer.data(), BytesRead)))
359 return Err;
360 }
361 }
362
363 return Error::success();
364}
365
366unsigned HTTPClient::responseCode() {
367 WinHTTPSession *Session = static_cast<WinHTTPSession *>(Handle);
368 return Session ? Session->ResponseCode : 0;
369}
370
371#else // _WIN32
372
373// Non-Windows, non-libcurl stub implementations
374HTTPClient::HTTPClient() = default;
375
376HTTPClient::~HTTPClient() = default;
377
378bool HTTPClient::isAvailable() { return false; }
379
381
383
384void HTTPClient::setTimeout(std::chrono::milliseconds Timeout) {}
385
387 HTTPResponseHandler &Handler) {
388 llvm_unreachable("No HTTP Client implementation available.");
389}
390
392 llvm_unreachable("No HTTP Client implementation available.");
393}
394
395#endif // _WIN32
396
397#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
static bool isAvailable()
Returns true only if LLVM has been compiled with a working HTTPClient.
static bool IsInitialized
Definition HTTPClient.h:62
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:43
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.
Definition Types.h:26
Error createStringError(std::error_code EC, char const *Fmt, const Ts &... Vals)
Create formatted StringError object.
Definition Error.h:1305
bool operator==(const AddressRangeValuePair &LHS, const AddressRangeValuePair &RHS)
@ io_error
Definition Errc.h:58
@ invalid_argument
Definition Errc.h:56
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:30
SmallVector< std::string, 0 > Headers
Definition HTTPClient.h:32
HTTPRequest(StringRef Url)
SmallString< 128 > Url
Definition HTTPClient.h:31
HTTPMethod Method
Definition HTTPClient.h:33