1 | //===-- llvm/Debuginfod/HTTPClient.cpp - HTTP client library ----*- C++ -*-===// |
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 | |
15 | #include "llvm/Debuginfod/HTTPClient.h" |
16 | #include "llvm/ADT/APInt.h" |
17 | #include "llvm/ADT/StringRef.h" |
18 | #include "llvm/Support/Errc.h" |
19 | #include "llvm/Support/Error.h" |
20 | #include "llvm/Support/MemoryBuffer.h" |
21 | #ifdef LLVM_ENABLE_CURL |
22 | #include <curl/curl.h> |
23 | #endif |
24 | |
25 | using namespace llvm; |
26 | |
27 | HTTPRequest::HTTPRequest(StringRef Url) { this->Url = Url.str(); } |
28 | |
29 | bool operator==(const HTTPRequest &A, const HTTPRequest &B) { |
30 | return A.Url == B.Url && A.Method == B.Method && |
31 | A.FollowRedirects == B.FollowRedirects; |
32 | } |
33 | |
34 | HTTPResponseHandler::~HTTPResponseHandler() = default; |
35 | |
36 | bool HTTPClient::IsInitialized = false; |
37 | |
38 | class HTTPClientCleanup { |
39 | public: |
40 | ~HTTPClientCleanup() { HTTPClient::cleanup(); } |
41 | }; |
42 | static const HTTPClientCleanup Cleanup; |
43 | |
44 | #ifdef LLVM_ENABLE_CURL |
45 | |
46 | bool HTTPClient::isAvailable() { return true; } |
47 | |
48 | void HTTPClient::initialize() { |
49 | if (!IsInitialized) { |
50 | curl_global_init(CURL_GLOBAL_ALL); |
51 | IsInitialized = true; |
52 | } |
53 | } |
54 | |
55 | void HTTPClient::cleanup() { |
56 | if (IsInitialized) { |
57 | curl_global_cleanup(); |
58 | IsInitialized = false; |
59 | } |
60 | } |
61 | |
62 | void HTTPClient::setTimeout(std::chrono::milliseconds Timeout) { |
63 | if (Timeout < std::chrono::milliseconds(0)) |
64 | Timeout = std::chrono::milliseconds(0); |
65 | curl_easy_setopt(Curl, CURLOPT_TIMEOUT_MS, Timeout.count()); |
66 | } |
67 | |
68 | /// CurlHTTPRequest and the curl{Header,Write}Function are implementation |
69 | /// details used to work with Curl. Curl makes callbacks with a single |
70 | /// customizable pointer parameter. |
71 | struct CurlHTTPRequest { |
72 | CurlHTTPRequest(HTTPResponseHandler &Handler) : Handler(Handler) {} |
73 | void storeError(Error Err) { |
74 | ErrorState = joinErrors(std::move(Err), std::move(ErrorState)); |
75 | } |
76 | HTTPResponseHandler &Handler; |
77 | llvm::Error ErrorState = Error::success(); |
78 | }; |
79 | |
80 | static size_t curlWriteFunction(char *Contents, size_t Size, size_t NMemb, |
81 | CurlHTTPRequest *CurlRequest) { |
82 | Size *= NMemb; |
83 | if (Error Err = |
84 | CurlRequest->Handler.handleBodyChunk(StringRef(Contents, Size))) { |
85 | CurlRequest->storeError(std::move(Err)); |
86 | return 0; |
87 | } |
88 | return Size; |
89 | } |
90 | |
91 | HTTPClient::HTTPClient() { |
92 | assert(IsInitialized && |
93 | "Must call HTTPClient::initialize() at the beginning of main()." ); |
94 | if (Curl) |
95 | return; |
96 | Curl = curl_easy_init(); |
97 | assert(Curl && "Curl could not be initialized" ); |
98 | // Set the callback hooks. |
99 | curl_easy_setopt(Curl, CURLOPT_WRITEFUNCTION, curlWriteFunction); |
100 | // Detect supported compressed encodings and accept all. |
101 | curl_easy_setopt(Curl, CURLOPT_ACCEPT_ENCODING, "" ); |
102 | } |
103 | |
104 | HTTPClient::~HTTPClient() { curl_easy_cleanup(Curl); } |
105 | |
106 | Error HTTPClient::perform(const HTTPRequest &Request, |
107 | HTTPResponseHandler &Handler) { |
108 | if (Request.Method != HTTPMethod::GET) |
109 | return createStringError(errc::invalid_argument, |
110 | "Unsupported CURL request method." ); |
111 | |
112 | SmallString<128> Url = Request.Url; |
113 | curl_easy_setopt(Curl, CURLOPT_URL, Url.c_str()); |
114 | curl_easy_setopt(Curl, CURLOPT_FOLLOWLOCATION, Request.FollowRedirects); |
115 | |
116 | curl_slist *Headers = nullptr; |
117 | for (const std::string &Header : Request.Headers) |
118 | Headers = curl_slist_append(Headers, Header.c_str()); |
119 | curl_easy_setopt(Curl, CURLOPT_HTTPHEADER, Headers); |
120 | |
121 | CurlHTTPRequest CurlRequest(Handler); |
122 | curl_easy_setopt(Curl, CURLOPT_WRITEDATA, &CurlRequest); |
123 | CURLcode CurlRes = curl_easy_perform(Curl); |
124 | curl_slist_free_all(Headers); |
125 | if (CurlRes != CURLE_OK) |
126 | return joinErrors(std::move(CurlRequest.ErrorState), |
127 | createStringError(errc::io_error, |
128 | "curl_easy_perform() failed: %s\n" , |
129 | curl_easy_strerror(CurlRes))); |
130 | return std::move(CurlRequest.ErrorState); |
131 | } |
132 | |
133 | unsigned HTTPClient::responseCode() { |
134 | long Code = 0; |
135 | curl_easy_getinfo(Curl, CURLINFO_RESPONSE_CODE, &Code); |
136 | return Code; |
137 | } |
138 | |
139 | #else |
140 | |
141 | HTTPClient::HTTPClient() = default; |
142 | |
143 | HTTPClient::~HTTPClient() = default; |
144 | |
145 | bool HTTPClient::isAvailable() { return false; } |
146 | |
147 | void HTTPClient::initialize() {} |
148 | |
149 | void HTTPClient::cleanup() {} |
150 | |
151 | void HTTPClient::setTimeout(std::chrono::milliseconds Timeout) {} |
152 | |
153 | Error HTTPClient::perform(const HTTPRequest &Request, |
154 | HTTPResponseHandler &Handler) { |
155 | llvm_unreachable("No HTTP Client implementation available." ); |
156 | } |
157 | |
158 | unsigned HTTPClient::responseCode() { |
159 | llvm_unreachable("No HTTP Client implementation available." ); |
160 | } |
161 | |
162 | #endif |
163 | |