1*0eae32dcSDimitry Andric //===-- llvm/Debuginfod/HTTPClient.cpp - HTTP client library ----*- C++ -*-===// 2*0eae32dcSDimitry Andric // 3*0eae32dcSDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4*0eae32dcSDimitry Andric // See https://llvm.org/LICENSE.txt for license information. 5*0eae32dcSDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6*0eae32dcSDimitry Andric // 7*0eae32dcSDimitry Andric //===----------------------------------------------------------------------===// 8*0eae32dcSDimitry Andric /// 9*0eae32dcSDimitry Andric /// \file 10*0eae32dcSDimitry Andric /// 11*0eae32dcSDimitry Andric /// This file defines the methods of the HTTPRequest, HTTPClient, and 12*0eae32dcSDimitry Andric /// BufferedHTTPResponseHandler classes. 13*0eae32dcSDimitry Andric /// 14*0eae32dcSDimitry Andric //===----------------------------------------------------------------------===// 15*0eae32dcSDimitry Andric 16*0eae32dcSDimitry Andric #include "llvm/Debuginfod/HTTPClient.h" 17*0eae32dcSDimitry Andric #include "llvm/ADT/APInt.h" 18*0eae32dcSDimitry Andric #include "llvm/ADT/StringRef.h" 19*0eae32dcSDimitry Andric #include "llvm/Support/Errc.h" 20*0eae32dcSDimitry Andric #include "llvm/Support/Error.h" 21*0eae32dcSDimitry Andric #include "llvm/Support/MemoryBuffer.h" 22*0eae32dcSDimitry Andric #ifdef LLVM_ENABLE_CURL 23*0eae32dcSDimitry Andric #include <curl/curl.h> 24*0eae32dcSDimitry Andric #endif 25*0eae32dcSDimitry Andric 26*0eae32dcSDimitry Andric using namespace llvm; 27*0eae32dcSDimitry Andric 28*0eae32dcSDimitry Andric HTTPRequest::HTTPRequest(StringRef Url) { this->Url = Url.str(); } 29*0eae32dcSDimitry Andric 30*0eae32dcSDimitry Andric bool operator==(const HTTPRequest &A, const HTTPRequest &B) { 31*0eae32dcSDimitry Andric return A.Url == B.Url && A.Method == B.Method && 32*0eae32dcSDimitry Andric A.FollowRedirects == B.FollowRedirects; 33*0eae32dcSDimitry Andric } 34*0eae32dcSDimitry Andric 35*0eae32dcSDimitry Andric HTTPResponseHandler::~HTTPResponseHandler() = default; 36*0eae32dcSDimitry Andric 37*0eae32dcSDimitry Andric static inline bool parseContentLengthHeader(StringRef LineRef, 38*0eae32dcSDimitry Andric size_t &ContentLength) { 39*0eae32dcSDimitry Andric // Content-Length is a mandatory header, and the only one we handle. 40*0eae32dcSDimitry Andric return LineRef.consume_front("Content-Length: ") && 41*0eae32dcSDimitry Andric to_integer(LineRef.trim(), ContentLength, 10); 42*0eae32dcSDimitry Andric } 43*0eae32dcSDimitry Andric 44*0eae32dcSDimitry Andric Error BufferedHTTPResponseHandler::handleHeaderLine(StringRef HeaderLine) { 45*0eae32dcSDimitry Andric if (ResponseBuffer.Body) 46*0eae32dcSDimitry Andric return Error::success(); 47*0eae32dcSDimitry Andric 48*0eae32dcSDimitry Andric size_t ContentLength; 49*0eae32dcSDimitry Andric if (parseContentLengthHeader(HeaderLine, ContentLength)) 50*0eae32dcSDimitry Andric ResponseBuffer.Body = 51*0eae32dcSDimitry Andric WritableMemoryBuffer::getNewUninitMemBuffer(ContentLength); 52*0eae32dcSDimitry Andric 53*0eae32dcSDimitry Andric return Error::success(); 54*0eae32dcSDimitry Andric } 55*0eae32dcSDimitry Andric 56*0eae32dcSDimitry Andric Error BufferedHTTPResponseHandler::handleBodyChunk(StringRef BodyChunk) { 57*0eae32dcSDimitry Andric if (!ResponseBuffer.Body) 58*0eae32dcSDimitry Andric return createStringError(errc::io_error, 59*0eae32dcSDimitry Andric "Unallocated response buffer. HTTP Body data " 60*0eae32dcSDimitry Andric "received before Content-Length header."); 61*0eae32dcSDimitry Andric if (Offset + BodyChunk.size() > ResponseBuffer.Body->getBufferSize()) 62*0eae32dcSDimitry Andric return createStringError(errc::io_error, 63*0eae32dcSDimitry Andric "Content size exceeds buffer size."); 64*0eae32dcSDimitry Andric memcpy(ResponseBuffer.Body->getBufferStart() + Offset, BodyChunk.data(), 65*0eae32dcSDimitry Andric BodyChunk.size()); 66*0eae32dcSDimitry Andric Offset += BodyChunk.size(); 67*0eae32dcSDimitry Andric return Error::success(); 68*0eae32dcSDimitry Andric } 69*0eae32dcSDimitry Andric 70*0eae32dcSDimitry Andric Error BufferedHTTPResponseHandler::handleStatusCode(unsigned Code) { 71*0eae32dcSDimitry Andric ResponseBuffer.Code = Code; 72*0eae32dcSDimitry Andric return Error::success(); 73*0eae32dcSDimitry Andric } 74*0eae32dcSDimitry Andric 75*0eae32dcSDimitry Andric bool HTTPClient::IsInitialized = false; 76*0eae32dcSDimitry Andric 77*0eae32dcSDimitry Andric class HTTPClientCleanup { 78*0eae32dcSDimitry Andric public: 79*0eae32dcSDimitry Andric ~HTTPClientCleanup() { HTTPClient::cleanup(); } 80*0eae32dcSDimitry Andric }; 81*0eae32dcSDimitry Andric static const HTTPClientCleanup Cleanup; 82*0eae32dcSDimitry Andric 83*0eae32dcSDimitry Andric Expected<HTTPResponseBuffer> HTTPClient::perform(const HTTPRequest &Request) { 84*0eae32dcSDimitry Andric BufferedHTTPResponseHandler Handler; 85*0eae32dcSDimitry Andric if (Error Err = perform(Request, Handler)) 86*0eae32dcSDimitry Andric return std::move(Err); 87*0eae32dcSDimitry Andric return std::move(Handler.ResponseBuffer); 88*0eae32dcSDimitry Andric } 89*0eae32dcSDimitry Andric 90*0eae32dcSDimitry Andric Expected<HTTPResponseBuffer> HTTPClient::get(StringRef Url) { 91*0eae32dcSDimitry Andric HTTPRequest Request(Url); 92*0eae32dcSDimitry Andric return perform(Request); 93*0eae32dcSDimitry Andric } 94*0eae32dcSDimitry Andric 95*0eae32dcSDimitry Andric #ifdef LLVM_ENABLE_CURL 96*0eae32dcSDimitry Andric 97*0eae32dcSDimitry Andric bool HTTPClient::isAvailable() { return true; } 98*0eae32dcSDimitry Andric 99*0eae32dcSDimitry Andric void HTTPClient::initialize() { 100*0eae32dcSDimitry Andric if (!IsInitialized) { 101*0eae32dcSDimitry Andric curl_global_init(CURL_GLOBAL_ALL); 102*0eae32dcSDimitry Andric IsInitialized = true; 103*0eae32dcSDimitry Andric } 104*0eae32dcSDimitry Andric } 105*0eae32dcSDimitry Andric 106*0eae32dcSDimitry Andric void HTTPClient::cleanup() { 107*0eae32dcSDimitry Andric if (IsInitialized) { 108*0eae32dcSDimitry Andric curl_global_cleanup(); 109*0eae32dcSDimitry Andric IsInitialized = false; 110*0eae32dcSDimitry Andric } 111*0eae32dcSDimitry Andric } 112*0eae32dcSDimitry Andric 113*0eae32dcSDimitry Andric void HTTPClient::setTimeout(std::chrono::milliseconds Timeout) { 114*0eae32dcSDimitry Andric if (Timeout < std::chrono::milliseconds(0)) 115*0eae32dcSDimitry Andric Timeout = std::chrono::milliseconds(0); 116*0eae32dcSDimitry Andric curl_easy_setopt(Curl, CURLOPT_TIMEOUT_MS, Timeout.count()); 117*0eae32dcSDimitry Andric } 118*0eae32dcSDimitry Andric 119*0eae32dcSDimitry Andric /// CurlHTTPRequest and the curl{Header,Write}Function are implementation 120*0eae32dcSDimitry Andric /// details used to work with Curl. Curl makes callbacks with a single 121*0eae32dcSDimitry Andric /// customizable pointer parameter. 122*0eae32dcSDimitry Andric struct CurlHTTPRequest { 123*0eae32dcSDimitry Andric CurlHTTPRequest(HTTPResponseHandler &Handler) : Handler(Handler) {} 124*0eae32dcSDimitry Andric void storeError(Error Err) { 125*0eae32dcSDimitry Andric ErrorState = joinErrors(std::move(Err), std::move(ErrorState)); 126*0eae32dcSDimitry Andric } 127*0eae32dcSDimitry Andric HTTPResponseHandler &Handler; 128*0eae32dcSDimitry Andric llvm::Error ErrorState = Error::success(); 129*0eae32dcSDimitry Andric }; 130*0eae32dcSDimitry Andric 131*0eae32dcSDimitry Andric static size_t curlHeaderFunction(char *Contents, size_t Size, size_t NMemb, 132*0eae32dcSDimitry Andric CurlHTTPRequest *CurlRequest) { 133*0eae32dcSDimitry Andric assert(Size == 1 && "The Size passed by libCURL to CURLOPT_HEADERFUNCTION " 134*0eae32dcSDimitry Andric "should always be 1."); 135*0eae32dcSDimitry Andric if (Error Err = 136*0eae32dcSDimitry Andric CurlRequest->Handler.handleHeaderLine(StringRef(Contents, NMemb))) { 137*0eae32dcSDimitry Andric CurlRequest->storeError(std::move(Err)); 138*0eae32dcSDimitry Andric return 0; 139*0eae32dcSDimitry Andric } 140*0eae32dcSDimitry Andric return NMemb; 141*0eae32dcSDimitry Andric } 142*0eae32dcSDimitry Andric 143*0eae32dcSDimitry Andric static size_t curlWriteFunction(char *Contents, size_t Size, size_t NMemb, 144*0eae32dcSDimitry Andric CurlHTTPRequest *CurlRequest) { 145*0eae32dcSDimitry Andric Size *= NMemb; 146*0eae32dcSDimitry Andric if (Error Err = 147*0eae32dcSDimitry Andric CurlRequest->Handler.handleBodyChunk(StringRef(Contents, Size))) { 148*0eae32dcSDimitry Andric CurlRequest->storeError(std::move(Err)); 149*0eae32dcSDimitry Andric return 0; 150*0eae32dcSDimitry Andric } 151*0eae32dcSDimitry Andric return Size; 152*0eae32dcSDimitry Andric } 153*0eae32dcSDimitry Andric 154*0eae32dcSDimitry Andric HTTPClient::HTTPClient() { 155*0eae32dcSDimitry Andric assert(IsInitialized && 156*0eae32dcSDimitry Andric "Must call HTTPClient::initialize() at the beginning of main()."); 157*0eae32dcSDimitry Andric if (Curl) 158*0eae32dcSDimitry Andric return; 159*0eae32dcSDimitry Andric assert((Curl = curl_easy_init()) && "Curl could not be initialized."); 160*0eae32dcSDimitry Andric // Set the callback hooks. 161*0eae32dcSDimitry Andric curl_easy_setopt(Curl, CURLOPT_WRITEFUNCTION, curlWriteFunction); 162*0eae32dcSDimitry Andric curl_easy_setopt(Curl, CURLOPT_HEADERFUNCTION, curlHeaderFunction); 163*0eae32dcSDimitry Andric } 164*0eae32dcSDimitry Andric 165*0eae32dcSDimitry Andric HTTPClient::~HTTPClient() { curl_easy_cleanup(Curl); } 166*0eae32dcSDimitry Andric 167*0eae32dcSDimitry Andric Error HTTPClient::perform(const HTTPRequest &Request, 168*0eae32dcSDimitry Andric HTTPResponseHandler &Handler) { 169*0eae32dcSDimitry Andric if (Request.Method != HTTPMethod::GET) 170*0eae32dcSDimitry Andric return createStringError(errc::invalid_argument, 171*0eae32dcSDimitry Andric "Unsupported CURL request method."); 172*0eae32dcSDimitry Andric 173*0eae32dcSDimitry Andric SmallString<128> Url = Request.Url; 174*0eae32dcSDimitry Andric curl_easy_setopt(Curl, CURLOPT_URL, Url.c_str()); 175*0eae32dcSDimitry Andric curl_easy_setopt(Curl, CURLOPT_FOLLOWLOCATION, Request.FollowRedirects); 176*0eae32dcSDimitry Andric 177*0eae32dcSDimitry Andric CurlHTTPRequest CurlRequest(Handler); 178*0eae32dcSDimitry Andric curl_easy_setopt(Curl, CURLOPT_WRITEDATA, &CurlRequest); 179*0eae32dcSDimitry Andric curl_easy_setopt(Curl, CURLOPT_HEADERDATA, &CurlRequest); 180*0eae32dcSDimitry Andric CURLcode CurlRes = curl_easy_perform(Curl); 181*0eae32dcSDimitry Andric if (CurlRes != CURLE_OK) 182*0eae32dcSDimitry Andric return joinErrors(std::move(CurlRequest.ErrorState), 183*0eae32dcSDimitry Andric createStringError(errc::io_error, 184*0eae32dcSDimitry Andric "curl_easy_perform() failed: %s\n", 185*0eae32dcSDimitry Andric curl_easy_strerror(CurlRes))); 186*0eae32dcSDimitry Andric if (CurlRequest.ErrorState) 187*0eae32dcSDimitry Andric return std::move(CurlRequest.ErrorState); 188*0eae32dcSDimitry Andric 189*0eae32dcSDimitry Andric unsigned Code; 190*0eae32dcSDimitry Andric curl_easy_getinfo(Curl, CURLINFO_RESPONSE_CODE, &Code); 191*0eae32dcSDimitry Andric if (Error Err = Handler.handleStatusCode(Code)) 192*0eae32dcSDimitry Andric return joinErrors(std::move(CurlRequest.ErrorState), std::move(Err)); 193*0eae32dcSDimitry Andric 194*0eae32dcSDimitry Andric return std::move(CurlRequest.ErrorState); 195*0eae32dcSDimitry Andric } 196*0eae32dcSDimitry Andric 197*0eae32dcSDimitry Andric #else 198*0eae32dcSDimitry Andric 199*0eae32dcSDimitry Andric HTTPClient::HTTPClient() = default; 200*0eae32dcSDimitry Andric 201*0eae32dcSDimitry Andric HTTPClient::~HTTPClient() = default; 202*0eae32dcSDimitry Andric 203*0eae32dcSDimitry Andric bool HTTPClient::isAvailable() { return false; } 204*0eae32dcSDimitry Andric 205*0eae32dcSDimitry Andric void HTTPClient::initialize() {} 206*0eae32dcSDimitry Andric 207*0eae32dcSDimitry Andric void HTTPClient::cleanup() {} 208*0eae32dcSDimitry Andric 209*0eae32dcSDimitry Andric void HTTPClient::setTimeout(std::chrono::milliseconds Timeout) {} 210*0eae32dcSDimitry Andric 211*0eae32dcSDimitry Andric Error HTTPClient::perform(const HTTPRequest &Request, 212*0eae32dcSDimitry Andric HTTPResponseHandler &Handler) { 213*0eae32dcSDimitry Andric llvm_unreachable("No HTTP Client implementation available."); 214*0eae32dcSDimitry Andric } 215*0eae32dcSDimitry Andric 216*0eae32dcSDimitry Andric #endif 217