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