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