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
150namespace {
151
152struct WinHTTPSession {
153 HINTERNET SessionHandle = nullptr;
154 HINTERNET ConnectHandle = nullptr;
155 HINTERNET RequestHandle = nullptr;
156 DWORD ResponseCode = 0;
157
158 ~WinHTTPSession() {
159 if (RequestHandle)
160 WinHttpCloseHandle(RequestHandle);
161 if (ConnectHandle)
162 WinHttpCloseHandle(ConnectHandle);
163 if (SessionHandle)
164 WinHttpCloseHandle(SessionHandle);
165 }
166};
167
168bool parseURL(StringRef Url, std::wstring &Host, std::wstring &Path,
169 INTERNET_PORT &Port, bool &Secure) {
170 // Parse URL: http://host:port/path
171 if (Url.starts_with("https://")) {
172 Secure = true;
173 Url = Url.drop_front(8);
174 } else if (Url.starts_with("http://")) {
175 Secure = false;
176 Url = Url.drop_front(7);
177 } else {
178 return false;
179 }
180
181 size_t SlashPos = Url.find('/');
182 StringRef HostPort =
183 (SlashPos != StringRef::npos) ? Url.substr(0, SlashPos) : Url;
184 StringRef PathPart =
185 (SlashPos != StringRef::npos) ? Url.substr(SlashPos) : StringRef("/");
186
187 size_t ColonPos = HostPort.find(':');
188 StringRef HostStr =
189 (ColonPos != StringRef::npos) ? HostPort.substr(0, ColonPos) : HostPort;
190
191 if (!llvm::ConvertUTF8toWide(HostStr, Host))
192 return false;
193 if (!llvm::ConvertUTF8toWide(PathPart, Path))
194 return false;
195
196 if (ColonPos != StringRef::npos) {
197 StringRef PortStr = HostPort.substr(ColonPos + 1);
198 Port = static_cast<INTERNET_PORT>(std::stoi(PortStr.str()));
199 } else {
200 Port = Secure ? INTERNET_DEFAULT_HTTPS_PORT : INTERNET_DEFAULT_HTTP_PORT;
201 }
202
203 return true;
204}
205
206} // namespace
207
208HTTPClient::HTTPClient() : Handle(new WinHTTPSession()) {}
209
210HTTPClient::~HTTPClient() { delete static_cast<WinHTTPSession *>(Handle); }
211
212bool HTTPClient::isAvailable() { return true; }
213
215 if (!IsInitialized) {
216 IsInitialized = true;
217 }
218}
219
220void HTTPClient::cleanup() {
221 if (IsInitialized) {
222 IsInitialized = false;
223 }
224}
225
226void HTTPClient::setTimeout(std::chrono::milliseconds Timeout) {
227 WinHTTPSession *Session = static_cast<WinHTTPSession *>(Handle);
228 if (Session && Session->SessionHandle) {
229 DWORD TimeoutMs = static_cast<DWORD>(Timeout.count());
230 WinHttpSetOption(Session->SessionHandle, WINHTTP_OPTION_CONNECT_TIMEOUT,
231 &TimeoutMs, sizeof(TimeoutMs));
232 WinHttpSetOption(Session->SessionHandle, WINHTTP_OPTION_RECEIVE_TIMEOUT,
233 &TimeoutMs, sizeof(TimeoutMs));
234 WinHttpSetOption(Session->SessionHandle, WINHTTP_OPTION_SEND_TIMEOUT,
235 &TimeoutMs, sizeof(TimeoutMs));
236 }
237}
238
240 HTTPResponseHandler &Handler) {
241 if (Request.Method != HTTPMethod::GET)
243 "Only GET requests are supported.");
244 for (const std::string &Header : Request.Headers)
245 if (Header.find("\r") != std::string::npos ||
246 Header.find("\n") != std::string::npos) {
248 "Unsafe request can lead to header injection.");
249 }
250
251 WinHTTPSession *Session = static_cast<WinHTTPSession *>(Handle);
252
253 // Parse URL
254 std::wstring Host, Path;
255 INTERNET_PORT Port = 0;
256 bool Secure = false;
257 if (!parseURL(Request.Url, Host, Path, Port, Secure))
259 "Invalid URL: " + Request.Url);
260
261 // Create session
262 Session->SessionHandle =
263 WinHttpOpen(L"LLVM-HTTPClient/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
264 WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
265 if (!Session->SessionHandle)
266 return createStringError(errc::io_error, "Failed to open WinHTTP session");
267
268 // Prevent fallback to TLS 1.0/1.1
269 DWORD SecureProtocols =
270 WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_3;
271 if (!WinHttpSetOption(Session->SessionHandle, WINHTTP_OPTION_SECURE_PROTOCOLS,
272 &SecureProtocols, sizeof(SecureProtocols)))
273 return createStringError(errc::io_error, "Failed to set secure protocols");
274
275 // Use HTTP/2 if available
276 DWORD EnableHttp2 = WINHTTP_PROTOCOL_FLAG_HTTP2;
277 WinHttpSetOption(Session->SessionHandle, WINHTTP_OPTION_ENABLE_HTTP_PROTOCOL,
278 &EnableHttp2, sizeof(EnableHttp2));
279
280 // Create connection
281 Session->ConnectHandle =
282 WinHttpConnect(Session->SessionHandle, Host.c_str(), Port, 0);
283 if (!Session->ConnectHandle) {
285 "Failed to connect to host: " + Request.Url);
286 }
287
288 // Open request
289 DWORD Flags = WINHTTP_FLAG_REFRESH;
290 if (Secure)
291 Flags |= WINHTTP_FLAG_SECURE;
292
293 Session->RequestHandle = WinHttpOpenRequest(
294 Session->ConnectHandle, L"GET", Path.c_str(), nullptr, WINHTTP_NO_REFERER,
295 WINHTTP_DEFAULT_ACCEPT_TYPES, Flags);
296 if (!Session->RequestHandle)
297 return createStringError(errc::io_error, "Failed to open HTTP request");
298
299 if (Secure) {
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)))
305 return createStringError(
306 errc::io_error, "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
317 // Add headers
318 for (const std::string &Header : Request.Headers) {
319 std::wstring WideHeader;
320 if (!llvm::ConvertUTF8toWide(Header, WideHeader))
321 continue;
322 WinHttpAddRequestHeaders(Session->RequestHandle, WideHeader.c_str(),
323 static_cast<DWORD>(WideHeader.length()),
324 WINHTTP_ADDREQ_FLAG_ADD);
325 }
326
327 // Send request
328 if (!WinHttpSendRequest(Session->RequestHandle, WINHTTP_NO_ADDITIONAL_HEADERS,
329 0, nullptr, 0, 0, 0))
330 return createStringError(errc::io_error, "Failed to send HTTP request");
331
332 // Receive response
333 if (!WinHttpReceiveResponse(Session->RequestHandle, nullptr))
334 return createStringError(errc::io_error, "Failed to receive HTTP response");
335
336 // Get response code
337 DWORD CodeSize = sizeof(Session->ResponseCode);
338 if (!WinHttpQueryHeaders(Session->RequestHandle,
339 WINHTTP_QUERY_STATUS_CODE |
340 WINHTTP_QUERY_FLAG_NUMBER,
341 WINHTTP_HEADER_NAME_BY_INDEX, &Session->ResponseCode,
342 &CodeSize, nullptr))
343 Session->ResponseCode = 0;
344
345 // Read response body
346 DWORD BytesAvailable = 0;
347 while (WinHttpQueryDataAvailable(Session->RequestHandle, &BytesAvailable)) {
348 if (BytesAvailable == 0)
349 break;
350
351 std::vector<char> Buffer(BytesAvailable);
352 DWORD BytesRead = 0;
353 if (!WinHttpReadData(Session->RequestHandle, Buffer.data(), BytesAvailable,
354 &BytesRead))
355 return createStringError(errc::io_error, "Failed to read HTTP response");
356
357 if (BytesRead > 0) {
358 if (Error Err =
359 Handler.handleBodyChunk(StringRef(Buffer.data(), BytesRead)))
360 return Err;
361 }
362 }
363
364 return Error::success();
365}
366
367unsigned HTTPClient::responseCode() {
368 WinHTTPSession *Session = static_cast<WinHTTPSession *>(Handle);
369 return Session ? Session->ResponseCode : 0;
370}
371
372#else // _WIN32
373
374// Non-Windows, non-libcurl stub implementations
375HTTPClient::HTTPClient() = default;
376
377HTTPClient::~HTTPClient() = default;
378
379bool HTTPClient::isAvailable() { return false; }
380
382
384
385void HTTPClient::setTimeout(std::chrono::milliseconds Timeout) {}
386
388 HTTPResponseHandler &Handler) {
389 llvm_unreachable("No HTTP Client implementation available.");
390}
391
393 llvm_unreachable("No HTTP Client implementation available.");
394}
395
396#endif // _WIN32
397
398#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.
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