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