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