xref: /freebsd-src/contrib/llvm-project/llvm/lib/Debuginfod/HTTPClient.cpp (revision 0eae32dcef82f6f06de6419a0d623d7def0cc8f6)
1*0eae32dcSDimitry Andric //===-- llvm/Debuginfod/HTTPClient.cpp - HTTP client library ----*- C++ -*-===//
2*0eae32dcSDimitry Andric //
3*0eae32dcSDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4*0eae32dcSDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
5*0eae32dcSDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6*0eae32dcSDimitry Andric //
7*0eae32dcSDimitry Andric //===----------------------------------------------------------------------===//
8*0eae32dcSDimitry Andric ///
9*0eae32dcSDimitry Andric /// \file
10*0eae32dcSDimitry Andric ///
11*0eae32dcSDimitry Andric /// This file defines the methods of the HTTPRequest, HTTPClient, and
12*0eae32dcSDimitry Andric /// BufferedHTTPResponseHandler classes.
13*0eae32dcSDimitry Andric ///
14*0eae32dcSDimitry Andric //===----------------------------------------------------------------------===//
15*0eae32dcSDimitry Andric 
16*0eae32dcSDimitry Andric #include "llvm/Debuginfod/HTTPClient.h"
17*0eae32dcSDimitry Andric #include "llvm/ADT/APInt.h"
18*0eae32dcSDimitry Andric #include "llvm/ADT/StringRef.h"
19*0eae32dcSDimitry Andric #include "llvm/Support/Errc.h"
20*0eae32dcSDimitry Andric #include "llvm/Support/Error.h"
21*0eae32dcSDimitry Andric #include "llvm/Support/MemoryBuffer.h"
22*0eae32dcSDimitry Andric #ifdef LLVM_ENABLE_CURL
23*0eae32dcSDimitry Andric #include <curl/curl.h>
24*0eae32dcSDimitry Andric #endif
25*0eae32dcSDimitry Andric 
26*0eae32dcSDimitry Andric using namespace llvm;
27*0eae32dcSDimitry Andric 
28*0eae32dcSDimitry Andric HTTPRequest::HTTPRequest(StringRef Url) { this->Url = Url.str(); }
29*0eae32dcSDimitry Andric 
30*0eae32dcSDimitry Andric bool operator==(const HTTPRequest &A, const HTTPRequest &B) {
31*0eae32dcSDimitry Andric   return A.Url == B.Url && A.Method == B.Method &&
32*0eae32dcSDimitry Andric          A.FollowRedirects == B.FollowRedirects;
33*0eae32dcSDimitry Andric }
34*0eae32dcSDimitry Andric 
35*0eae32dcSDimitry Andric HTTPResponseHandler::~HTTPResponseHandler() = default;
36*0eae32dcSDimitry Andric 
37*0eae32dcSDimitry Andric static inline bool parseContentLengthHeader(StringRef LineRef,
38*0eae32dcSDimitry Andric                                             size_t &ContentLength) {
39*0eae32dcSDimitry Andric   // Content-Length is a mandatory header, and the only one we handle.
40*0eae32dcSDimitry Andric   return LineRef.consume_front("Content-Length: ") &&
41*0eae32dcSDimitry Andric          to_integer(LineRef.trim(), ContentLength, 10);
42*0eae32dcSDimitry Andric }
43*0eae32dcSDimitry Andric 
44*0eae32dcSDimitry Andric Error BufferedHTTPResponseHandler::handleHeaderLine(StringRef HeaderLine) {
45*0eae32dcSDimitry Andric   if (ResponseBuffer.Body)
46*0eae32dcSDimitry Andric     return Error::success();
47*0eae32dcSDimitry Andric 
48*0eae32dcSDimitry Andric   size_t ContentLength;
49*0eae32dcSDimitry Andric   if (parseContentLengthHeader(HeaderLine, ContentLength))
50*0eae32dcSDimitry Andric     ResponseBuffer.Body =
51*0eae32dcSDimitry Andric         WritableMemoryBuffer::getNewUninitMemBuffer(ContentLength);
52*0eae32dcSDimitry Andric 
53*0eae32dcSDimitry Andric   return Error::success();
54*0eae32dcSDimitry Andric }
55*0eae32dcSDimitry Andric 
56*0eae32dcSDimitry Andric Error BufferedHTTPResponseHandler::handleBodyChunk(StringRef BodyChunk) {
57*0eae32dcSDimitry Andric   if (!ResponseBuffer.Body)
58*0eae32dcSDimitry Andric     return createStringError(errc::io_error,
59*0eae32dcSDimitry Andric                              "Unallocated response buffer. HTTP Body data "
60*0eae32dcSDimitry Andric                              "received before Content-Length header.");
61*0eae32dcSDimitry Andric   if (Offset + BodyChunk.size() > ResponseBuffer.Body->getBufferSize())
62*0eae32dcSDimitry Andric     return createStringError(errc::io_error,
63*0eae32dcSDimitry Andric                              "Content size exceeds buffer size.");
64*0eae32dcSDimitry Andric   memcpy(ResponseBuffer.Body->getBufferStart() + Offset, BodyChunk.data(),
65*0eae32dcSDimitry Andric          BodyChunk.size());
66*0eae32dcSDimitry Andric   Offset += BodyChunk.size();
67*0eae32dcSDimitry Andric   return Error::success();
68*0eae32dcSDimitry Andric }
69*0eae32dcSDimitry Andric 
70*0eae32dcSDimitry Andric Error BufferedHTTPResponseHandler::handleStatusCode(unsigned Code) {
71*0eae32dcSDimitry Andric   ResponseBuffer.Code = Code;
72*0eae32dcSDimitry Andric   return Error::success();
73*0eae32dcSDimitry Andric }
74*0eae32dcSDimitry Andric 
75*0eae32dcSDimitry Andric bool HTTPClient::IsInitialized = false;
76*0eae32dcSDimitry Andric 
77*0eae32dcSDimitry Andric class HTTPClientCleanup {
78*0eae32dcSDimitry Andric public:
79*0eae32dcSDimitry Andric   ~HTTPClientCleanup() { HTTPClient::cleanup(); }
80*0eae32dcSDimitry Andric };
81*0eae32dcSDimitry Andric static const HTTPClientCleanup Cleanup;
82*0eae32dcSDimitry Andric 
83*0eae32dcSDimitry Andric Expected<HTTPResponseBuffer> HTTPClient::perform(const HTTPRequest &Request) {
84*0eae32dcSDimitry Andric   BufferedHTTPResponseHandler Handler;
85*0eae32dcSDimitry Andric   if (Error Err = perform(Request, Handler))
86*0eae32dcSDimitry Andric     return std::move(Err);
87*0eae32dcSDimitry Andric   return std::move(Handler.ResponseBuffer);
88*0eae32dcSDimitry Andric }
89*0eae32dcSDimitry Andric 
90*0eae32dcSDimitry Andric Expected<HTTPResponseBuffer> HTTPClient::get(StringRef Url) {
91*0eae32dcSDimitry Andric   HTTPRequest Request(Url);
92*0eae32dcSDimitry Andric   return perform(Request);
93*0eae32dcSDimitry Andric }
94*0eae32dcSDimitry Andric 
95*0eae32dcSDimitry Andric #ifdef LLVM_ENABLE_CURL
96*0eae32dcSDimitry Andric 
97*0eae32dcSDimitry Andric bool HTTPClient::isAvailable() { return true; }
98*0eae32dcSDimitry Andric 
99*0eae32dcSDimitry Andric void HTTPClient::initialize() {
100*0eae32dcSDimitry Andric   if (!IsInitialized) {
101*0eae32dcSDimitry Andric     curl_global_init(CURL_GLOBAL_ALL);
102*0eae32dcSDimitry Andric     IsInitialized = true;
103*0eae32dcSDimitry Andric   }
104*0eae32dcSDimitry Andric }
105*0eae32dcSDimitry Andric 
106*0eae32dcSDimitry Andric void HTTPClient::cleanup() {
107*0eae32dcSDimitry Andric   if (IsInitialized) {
108*0eae32dcSDimitry Andric     curl_global_cleanup();
109*0eae32dcSDimitry Andric     IsInitialized = false;
110*0eae32dcSDimitry Andric   }
111*0eae32dcSDimitry Andric }
112*0eae32dcSDimitry Andric 
113*0eae32dcSDimitry Andric void HTTPClient::setTimeout(std::chrono::milliseconds Timeout) {
114*0eae32dcSDimitry Andric   if (Timeout < std::chrono::milliseconds(0))
115*0eae32dcSDimitry Andric     Timeout = std::chrono::milliseconds(0);
116*0eae32dcSDimitry Andric   curl_easy_setopt(Curl, CURLOPT_TIMEOUT_MS, Timeout.count());
117*0eae32dcSDimitry Andric }
118*0eae32dcSDimitry Andric 
119*0eae32dcSDimitry Andric /// CurlHTTPRequest and the curl{Header,Write}Function are implementation
120*0eae32dcSDimitry Andric /// details used to work with Curl. Curl makes callbacks with a single
121*0eae32dcSDimitry Andric /// customizable pointer parameter.
122*0eae32dcSDimitry Andric struct CurlHTTPRequest {
123*0eae32dcSDimitry Andric   CurlHTTPRequest(HTTPResponseHandler &Handler) : Handler(Handler) {}
124*0eae32dcSDimitry Andric   void storeError(Error Err) {
125*0eae32dcSDimitry Andric     ErrorState = joinErrors(std::move(Err), std::move(ErrorState));
126*0eae32dcSDimitry Andric   }
127*0eae32dcSDimitry Andric   HTTPResponseHandler &Handler;
128*0eae32dcSDimitry Andric   llvm::Error ErrorState = Error::success();
129*0eae32dcSDimitry Andric };
130*0eae32dcSDimitry Andric 
131*0eae32dcSDimitry Andric static size_t curlHeaderFunction(char *Contents, size_t Size, size_t NMemb,
132*0eae32dcSDimitry Andric                                  CurlHTTPRequest *CurlRequest) {
133*0eae32dcSDimitry Andric   assert(Size == 1 && "The Size passed by libCURL to CURLOPT_HEADERFUNCTION "
134*0eae32dcSDimitry Andric                       "should always be 1.");
135*0eae32dcSDimitry Andric   if (Error Err =
136*0eae32dcSDimitry Andric           CurlRequest->Handler.handleHeaderLine(StringRef(Contents, NMemb))) {
137*0eae32dcSDimitry Andric     CurlRequest->storeError(std::move(Err));
138*0eae32dcSDimitry Andric     return 0;
139*0eae32dcSDimitry Andric   }
140*0eae32dcSDimitry Andric   return NMemb;
141*0eae32dcSDimitry Andric }
142*0eae32dcSDimitry Andric 
143*0eae32dcSDimitry Andric static size_t curlWriteFunction(char *Contents, size_t Size, size_t NMemb,
144*0eae32dcSDimitry Andric                                 CurlHTTPRequest *CurlRequest) {
145*0eae32dcSDimitry Andric   Size *= NMemb;
146*0eae32dcSDimitry Andric   if (Error Err =
147*0eae32dcSDimitry Andric           CurlRequest->Handler.handleBodyChunk(StringRef(Contents, Size))) {
148*0eae32dcSDimitry Andric     CurlRequest->storeError(std::move(Err));
149*0eae32dcSDimitry Andric     return 0;
150*0eae32dcSDimitry Andric   }
151*0eae32dcSDimitry Andric   return Size;
152*0eae32dcSDimitry Andric }
153*0eae32dcSDimitry Andric 
154*0eae32dcSDimitry Andric HTTPClient::HTTPClient() {
155*0eae32dcSDimitry Andric   assert(IsInitialized &&
156*0eae32dcSDimitry Andric          "Must call HTTPClient::initialize() at the beginning of main().");
157*0eae32dcSDimitry Andric   if (Curl)
158*0eae32dcSDimitry Andric     return;
159*0eae32dcSDimitry Andric   assert((Curl = curl_easy_init()) && "Curl could not be initialized.");
160*0eae32dcSDimitry Andric   // Set the callback hooks.
161*0eae32dcSDimitry Andric   curl_easy_setopt(Curl, CURLOPT_WRITEFUNCTION, curlWriteFunction);
162*0eae32dcSDimitry Andric   curl_easy_setopt(Curl, CURLOPT_HEADERFUNCTION, curlHeaderFunction);
163*0eae32dcSDimitry Andric }
164*0eae32dcSDimitry Andric 
165*0eae32dcSDimitry Andric HTTPClient::~HTTPClient() { curl_easy_cleanup(Curl); }
166*0eae32dcSDimitry Andric 
167*0eae32dcSDimitry Andric Error HTTPClient::perform(const HTTPRequest &Request,
168*0eae32dcSDimitry Andric                           HTTPResponseHandler &Handler) {
169*0eae32dcSDimitry Andric   if (Request.Method != HTTPMethod::GET)
170*0eae32dcSDimitry Andric     return createStringError(errc::invalid_argument,
171*0eae32dcSDimitry Andric                              "Unsupported CURL request method.");
172*0eae32dcSDimitry Andric 
173*0eae32dcSDimitry Andric   SmallString<128> Url = Request.Url;
174*0eae32dcSDimitry Andric   curl_easy_setopt(Curl, CURLOPT_URL, Url.c_str());
175*0eae32dcSDimitry Andric   curl_easy_setopt(Curl, CURLOPT_FOLLOWLOCATION, Request.FollowRedirects);
176*0eae32dcSDimitry Andric 
177*0eae32dcSDimitry Andric   CurlHTTPRequest CurlRequest(Handler);
178*0eae32dcSDimitry Andric   curl_easy_setopt(Curl, CURLOPT_WRITEDATA, &CurlRequest);
179*0eae32dcSDimitry Andric   curl_easy_setopt(Curl, CURLOPT_HEADERDATA, &CurlRequest);
180*0eae32dcSDimitry Andric   CURLcode CurlRes = curl_easy_perform(Curl);
181*0eae32dcSDimitry Andric   if (CurlRes != CURLE_OK)
182*0eae32dcSDimitry Andric     return joinErrors(std::move(CurlRequest.ErrorState),
183*0eae32dcSDimitry Andric                       createStringError(errc::io_error,
184*0eae32dcSDimitry Andric                                         "curl_easy_perform() failed: %s\n",
185*0eae32dcSDimitry Andric                                         curl_easy_strerror(CurlRes)));
186*0eae32dcSDimitry Andric   if (CurlRequest.ErrorState)
187*0eae32dcSDimitry Andric     return std::move(CurlRequest.ErrorState);
188*0eae32dcSDimitry Andric 
189*0eae32dcSDimitry Andric   unsigned Code;
190*0eae32dcSDimitry Andric   curl_easy_getinfo(Curl, CURLINFO_RESPONSE_CODE, &Code);
191*0eae32dcSDimitry Andric   if (Error Err = Handler.handleStatusCode(Code))
192*0eae32dcSDimitry Andric     return joinErrors(std::move(CurlRequest.ErrorState), std::move(Err));
193*0eae32dcSDimitry Andric 
194*0eae32dcSDimitry Andric   return std::move(CurlRequest.ErrorState);
195*0eae32dcSDimitry Andric }
196*0eae32dcSDimitry Andric 
197*0eae32dcSDimitry Andric #else
198*0eae32dcSDimitry Andric 
199*0eae32dcSDimitry Andric HTTPClient::HTTPClient() = default;
200*0eae32dcSDimitry Andric 
201*0eae32dcSDimitry Andric HTTPClient::~HTTPClient() = default;
202*0eae32dcSDimitry Andric 
203*0eae32dcSDimitry Andric bool HTTPClient::isAvailable() { return false; }
204*0eae32dcSDimitry Andric 
205*0eae32dcSDimitry Andric void HTTPClient::initialize() {}
206*0eae32dcSDimitry Andric 
207*0eae32dcSDimitry Andric void HTTPClient::cleanup() {}
208*0eae32dcSDimitry Andric 
209*0eae32dcSDimitry Andric void HTTPClient::setTimeout(std::chrono::milliseconds Timeout) {}
210*0eae32dcSDimitry Andric 
211*0eae32dcSDimitry Andric Error HTTPClient::perform(const HTTPRequest &Request,
212*0eae32dcSDimitry Andric                           HTTPResponseHandler &Handler) {
213*0eae32dcSDimitry Andric   llvm_unreachable("No HTTP Client implementation available.");
214*0eae32dcSDimitry Andric }
215*0eae32dcSDimitry Andric 
216*0eae32dcSDimitry Andric #endif
217