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