xref: /freebsd/contrib/llvm-project/llvm/lib/Debuginfod/HTTPClient.cpp (revision 5f757f3ff9144b609b3c433dfd370cc6bdc191ad)
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