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 27 HTTPRequest::HTTPRequest(StringRef Url) { this->Url = Url.str(); } 28 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: 40 ~HTTPClientCleanup() { HTTPClient::cleanup(); } 41 }; 42 static const HTTPClientCleanup Cleanup; 43 44 #ifdef LLVM_ENABLE_CURL 45 46 bool HTTPClient::isAvailable() { return true; } 47 48 void HTTPClient::initialize() { 49 if (!IsInitialized) { 50 curl_global_init(CURL_GLOBAL_ALL); 51 IsInitialized = true; 52 } 53 } 54 55 void HTTPClient::cleanup() { 56 if (IsInitialized) { 57 curl_global_cleanup(); 58 IsInitialized = false; 59 } 60 } 61 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 { 72 CurlHTTPRequest(HTTPResponseHandler &Handler) : Handler(Handler) {} 73 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 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 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 102 HTTPClient::~HTTPClient() { curl_easy_cleanup(Curl); } 103 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 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 143 bool HTTPClient::isAvailable() { return false; } 144 145 void HTTPClient::initialize() {} 146 147 void HTTPClient::cleanup() {} 148 149 void HTTPClient::setTimeout(std::chrono::milliseconds Timeout) {} 150 151 Error HTTPClient::perform(const HTTPRequest &Request, 152 HTTPResponseHandler &Handler) { 153 llvm_unreachable("No HTTP Client implementation available."); 154 } 155 156 unsigned HTTPClient::responseCode() { 157 llvm_unreachable("No HTTP Client implementation available."); 158 } 159 160 #endif 161