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
HTTPRequest(StringRef Url)27 HTTPRequest::HTTPRequest(StringRef Url) { this->Url = Url.str(); }
28
operator ==(const HTTPRequest & A,const HTTPRequest & B)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:
~HTTPClientCleanup()40 ~HTTPClientCleanup() { HTTPClient::cleanup(); }
41 };
42 static const HTTPClientCleanup Cleanup;
43
44 #ifdef LLVM_ENABLE_CURL
45
isAvailable()46 bool HTTPClient::isAvailable() { return true; }
47
initialize()48 void HTTPClient::initialize() {
49 if (!IsInitialized) {
50 curl_global_init(CURL_GLOBAL_ALL);
51 IsInitialized = true;
52 }
53 }
54
cleanup()55 void HTTPClient::cleanup() {
56 if (IsInitialized) {
57 curl_global_cleanup();
58 IsInitialized = false;
59 }
60 }
61
setTimeout(std::chrono::milliseconds Timeout)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 {
CurlHTTPRequestCurlHTTPRequest72 CurlHTTPRequest(HTTPResponseHandler &Handler) : Handler(Handler) {}
storeErrorCurlHTTPRequest73 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
curlWriteFunction(char * Contents,size_t Size,size_t NMemb,CurlHTTPRequest * CurlRequest)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
HTTPClient()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 }
101
~HTTPClient()102 HTTPClient::~HTTPClient() { curl_easy_cleanup(Curl); }
103
perform(const HTTPRequest & Request,HTTPResponseHandler & Handler)104 Error HTTPClient::perform(const HTTPRequest &Request,
105 HTTPResponseHandler &Handler) {
106 if (Request.Method != HTTPMethod::GET)
107 return createStringError(errc::invalid_argument,
108 "Unsupported CURL request method.");
109
110 SmallString<128> Url = Request.Url;
111 curl_easy_setopt(Curl, CURLOPT_URL, Url.c_str());
112 curl_easy_setopt(Curl, CURLOPT_FOLLOWLOCATION, Request.FollowRedirects);
113
114 curl_slist *Headers = nullptr;
115 for (const std::string &Header : Request.Headers)
116 Headers = curl_slist_append(Headers, Header.c_str());
117 curl_easy_setopt(Curl, CURLOPT_HTTPHEADER, Headers);
118
119 CurlHTTPRequest CurlRequest(Handler);
120 curl_easy_setopt(Curl, CURLOPT_WRITEDATA, &CurlRequest);
121 CURLcode CurlRes = curl_easy_perform(Curl);
122 curl_slist_free_all(Headers);
123 if (CurlRes != CURLE_OK)
124 return joinErrors(std::move(CurlRequest.ErrorState),
125 createStringError(errc::io_error,
126 "curl_easy_perform() failed: %s\n",
127 curl_easy_strerror(CurlRes)));
128 return std::move(CurlRequest.ErrorState);
129 }
130
responseCode()131 unsigned HTTPClient::responseCode() {
132 long Code = 0;
133 curl_easy_getinfo(Curl, CURLINFO_RESPONSE_CODE, &Code);
134 return Code;
135 }
136
137 #else
138
139 HTTPClient::HTTPClient() = default;
140
141 HTTPClient::~HTTPClient() = default;
142
isAvailable()143 bool HTTPClient::isAvailable() { return false; }
144
initialize()145 void HTTPClient::initialize() {}
146
cleanup()147 void HTTPClient::cleanup() {}
148
setTimeout(std::chrono::milliseconds Timeout)149 void HTTPClient::setTimeout(std::chrono::milliseconds Timeout) {}
150
perform(const HTTPRequest & Request,HTTPResponseHandler & Handler)151 Error HTTPClient::perform(const HTTPRequest &Request,
152 HTTPResponseHandler &Handler) {
153 llvm_unreachable("No HTTP Client implementation available.");
154 }
155
responseCode()156 unsigned HTTPClient::responseCode() {
157 llvm_unreachable("No HTTP Client implementation available.");
158 }
159
160 #endif
161