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 // Detect supported compressed encodings and accept all. 101 curl_easy_setopt(Curl, CURLOPT_ACCEPT_ENCODING, ""); 102 } 103 104 HTTPClient::~HTTPClient() { curl_easy_cleanup(Curl); } 105 106 Error HTTPClient::perform(const HTTPRequest &Request, 107 HTTPResponseHandler &Handler) { 108 if (Request.Method != HTTPMethod::GET) 109 return createStringError(errc::invalid_argument, 110 "Unsupported CURL request method."); 111 112 SmallString<128> Url = Request.Url; 113 curl_easy_setopt(Curl, CURLOPT_URL, Url.c_str()); 114 curl_easy_setopt(Curl, CURLOPT_FOLLOWLOCATION, Request.FollowRedirects); 115 116 curl_slist *Headers = nullptr; 117 for (const std::string &Header : Request.Headers) 118 Headers = curl_slist_append(Headers, Header.c_str()); 119 curl_easy_setopt(Curl, CURLOPT_HTTPHEADER, Headers); 120 121 CurlHTTPRequest CurlRequest(Handler); 122 curl_easy_setopt(Curl, CURLOPT_WRITEDATA, &CurlRequest); 123 CURLcode CurlRes = curl_easy_perform(Curl); 124 curl_slist_free_all(Headers); 125 if (CurlRes != CURLE_OK) 126 return joinErrors(std::move(CurlRequest.ErrorState), 127 createStringError(errc::io_error, 128 "curl_easy_perform() failed: %s\n", 129 curl_easy_strerror(CurlRes))); 130 return std::move(CurlRequest.ErrorState); 131 } 132 133 unsigned HTTPClient::responseCode() { 134 long Code = 0; 135 curl_easy_getinfo(Curl, CURLINFO_RESPONSE_CODE, &Code); 136 return Code; 137 } 138 139 #else 140 141 HTTPClient::HTTPClient() = default; 142 143 HTTPClient::~HTTPClient() = default; 144 145 bool HTTPClient::isAvailable() { return false; } 146 147 void HTTPClient::initialize() {} 148 149 void HTTPClient::cleanup() {} 150 151 void HTTPClient::setTimeout(std::chrono::milliseconds Timeout) {} 152 153 Error HTTPClient::perform(const HTTPRequest &Request, 154 HTTPResponseHandler &Handler) { 155 llvm_unreachable("No HTTP Client implementation available."); 156 } 157 158 unsigned HTTPClient::responseCode() { 159 llvm_unreachable("No HTTP Client implementation available."); 160 } 161 162 #endif 163