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 Expected<HTTPResponseBuffer> HTTPClient::perform(const HTTPRequest &Request) {
76   BufferedHTTPResponseHandler Handler;
77   if (Error Err = perform(Request, Handler))
78     return std::move(Err);
79   return std::move(Handler.ResponseBuffer);
80 }
81 
82 Expected<HTTPResponseBuffer> HTTPClient::get(StringRef Url) {
83   HTTPRequest Request(Url);
84   return perform(Request);
85 }
86 
87 #ifdef LLVM_ENABLE_CURL
88 
89 bool HTTPClient::isAvailable() { return true; }
90 
91 bool HTTPClient::IsInitialized = false;
92 
93 void HTTPClient::initialize() {
94   if (!IsInitialized) {
95     curl_global_init(CURL_GLOBAL_ALL);
96     IsInitialized = true;
97   }
98 }
99 
100 void HTTPClient::cleanup() {
101   if (IsInitialized) {
102     curl_global_cleanup();
103     IsInitialized = false;
104   }
105 }
106 
107 void HTTPClient::setTimeout(std::chrono::milliseconds Timeout) {
108   if (Timeout < std::chrono::milliseconds(0))
109     Timeout = std::chrono::milliseconds(0);
110   curl_easy_setopt(Curl, CURLOPT_TIMEOUT_MS, Timeout.count());
111 }
112 
113 /// CurlHTTPRequest and the curl{Header,Write}Function are implementation
114 /// details used to work with Curl. Curl makes callbacks with a single
115 /// customizable pointer parameter.
116 struct CurlHTTPRequest {
117   CurlHTTPRequest(HTTPResponseHandler &Handler) : Handler(Handler) {}
118   void storeError(Error Err) {
119     ErrorState = joinErrors(std::move(Err), std::move(ErrorState));
120   }
121   HTTPResponseHandler &Handler;
122   llvm::Error ErrorState = Error::success();
123 };
124 
125 static size_t curlHeaderFunction(char *Contents, size_t Size, size_t NMemb,
126                                  CurlHTTPRequest *CurlRequest) {
127   assert(Size == 1 && "The Size passed by libCURL to CURLOPT_HEADERFUNCTION "
128                       "should always be 1.");
129   if (Error Err =
130           CurlRequest->Handler.handleHeaderLine(StringRef(Contents, NMemb))) {
131     CurlRequest->storeError(std::move(Err));
132     return 0;
133   }
134   return NMemb;
135 }
136 
137 static size_t curlWriteFunction(char *Contents, size_t Size, size_t NMemb,
138                                 CurlHTTPRequest *CurlRequest) {
139   Size *= NMemb;
140   if (Error Err =
141           CurlRequest->Handler.handleBodyChunk(StringRef(Contents, Size))) {
142     CurlRequest->storeError(std::move(Err));
143     return 0;
144   }
145   return Size;
146 }
147 
148 HTTPClient::HTTPClient() {
149   assert(IsInitialized &&
150          "Must call HTTPClient::initialize() at the beginning of main().");
151   if (Curl)
152     return;
153   assert((Curl = curl_easy_init()) && "Curl could not be initialized.");
154   // Set the callback hooks.
155   curl_easy_setopt(Curl, CURLOPT_WRITEFUNCTION, curlWriteFunction);
156   curl_easy_setopt(Curl, CURLOPT_HEADERFUNCTION, curlHeaderFunction);
157 }
158 
159 HTTPClient::~HTTPClient() { curl_easy_cleanup(Curl); }
160 
161 Error HTTPClient::perform(const HTTPRequest &Request,
162                           HTTPResponseHandler &Handler) {
163   if (Request.Method != HTTPMethod::GET)
164     return createStringError(errc::invalid_argument,
165                              "Unsupported CURL request method.");
166 
167   SmallString<128> Url = Request.Url;
168   curl_easy_setopt(Curl, CURLOPT_URL, Url.c_str());
169   curl_easy_setopt(Curl, CURLOPT_FOLLOWLOCATION, Request.FollowRedirects);
170 
171   CurlHTTPRequest CurlRequest(Handler);
172   curl_easy_setopt(Curl, CURLOPT_WRITEDATA, &CurlRequest);
173   curl_easy_setopt(Curl, CURLOPT_HEADERDATA, &CurlRequest);
174   CURLcode CurlRes = curl_easy_perform(Curl);
175   if (CurlRes != CURLE_OK)
176     return joinErrors(std::move(CurlRequest.ErrorState),
177                       createStringError(errc::io_error,
178                                         "curl_easy_perform() failed: %s\n",
179                                         curl_easy_strerror(CurlRes)));
180   if (CurlRequest.ErrorState)
181     return std::move(CurlRequest.ErrorState);
182 
183   unsigned Code;
184   curl_easy_getinfo(Curl, CURLINFO_RESPONSE_CODE, &Code);
185   if (Error Err = Handler.handleStatusCode(Code))
186     return joinErrors(std::move(CurlRequest.ErrorState), std::move(Err));
187 
188   return std::move(CurlRequest.ErrorState);
189 }
190 
191 #else
192 
193 HTTPClient::HTTPClient() = default;
194 
195 HTTPClient::~HTTPClient() = default;
196 
197 bool HTTPClient::isAvailable() { return false; }
198 
199 void HTTPClient::initialize() {}
200 
201 void HTTPClient::cleanup() {}
202 
203 void HTTPClient::setTimeout(std::chrono::milliseconds Timeout) {}
204 
205 Error HTTPClient::perform(const HTTPRequest &Request,
206                           HTTPResponseHandler &Handler) {
207   llvm_unreachable("No HTTP Client implementation available.");
208 }
209 
210 #endif
211