//===-- llvm/Debuginfod/HTTPClient.cpp - HTTP client library ----*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// /// /// \file /// This file defines the implementation of the HTTPClient library for issuing /// HTTP requests and handling the responses. /// //===----------------------------------------------------------------------===// #include "llvm/Debuginfod/HTTPClient.h" #include "llvm/ADT/APInt.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Errc.h" #include "llvm/Support/Error.h" #include "llvm/Support/MemoryBuffer.h" #ifdef LLVM_ENABLE_CURL #include #endif using namespace llvm; HTTPRequest::HTTPRequest(StringRef Url) { this->Url = Url.str(); } bool operator==(const HTTPRequest &A, const HTTPRequest &B) { return A.Url == B.Url && A.Method == B.Method && A.FollowRedirects == B.FollowRedirects; } HTTPResponseHandler::~HTTPResponseHandler() = default; bool HTTPClient::IsInitialized = false; class HTTPClientCleanup { public: ~HTTPClientCleanup() { HTTPClient::cleanup(); } }; static const HTTPClientCleanup Cleanup; #ifdef LLVM_ENABLE_CURL bool HTTPClient::isAvailable() { return true; } void HTTPClient::initialize() { if (!IsInitialized) { curl_global_init(CURL_GLOBAL_ALL); IsInitialized = true; } } void HTTPClient::cleanup() { if (IsInitialized) { curl_global_cleanup(); IsInitialized = false; } } void HTTPClient::setTimeout(std::chrono::milliseconds Timeout) { if (Timeout < std::chrono::milliseconds(0)) Timeout = std::chrono::milliseconds(0); curl_easy_setopt(Curl, CURLOPT_TIMEOUT_MS, Timeout.count()); } /// CurlHTTPRequest and the curl{Header,Write}Function are implementation /// details used to work with Curl. Curl makes callbacks with a single /// customizable pointer parameter. struct CurlHTTPRequest { CurlHTTPRequest(HTTPResponseHandler &Handler) : Handler(Handler) {} void storeError(Error Err) { ErrorState = joinErrors(std::move(Err), std::move(ErrorState)); } HTTPResponseHandler &Handler; llvm::Error ErrorState = Error::success(); }; static size_t curlWriteFunction(char *Contents, size_t Size, size_t NMemb, CurlHTTPRequest *CurlRequest) { Size *= NMemb; if (Error Err = CurlRequest->Handler.handleBodyChunk(StringRef(Contents, Size))) { CurlRequest->storeError(std::move(Err)); return 0; } return Size; } HTTPClient::HTTPClient() { assert(IsInitialized && "Must call HTTPClient::initialize() at the beginning of main()."); if (Curl) return; Curl = curl_easy_init(); assert(Curl && "Curl could not be initialized"); // Set the callback hooks. curl_easy_setopt(Curl, CURLOPT_WRITEFUNCTION, curlWriteFunction); } HTTPClient::~HTTPClient() { curl_easy_cleanup(Curl); } Error HTTPClient::perform(const HTTPRequest &Request, HTTPResponseHandler &Handler) { if (Request.Method != HTTPMethod::GET) return createStringError(errc::invalid_argument, "Unsupported CURL request method."); SmallString<128> Url = Request.Url; curl_easy_setopt(Curl, CURLOPT_URL, Url.c_str()); curl_easy_setopt(Curl, CURLOPT_FOLLOWLOCATION, Request.FollowRedirects); curl_slist *Headers = nullptr; for (const std::string &Header : Request.Headers) Headers = curl_slist_append(Headers, Header.c_str()); curl_easy_setopt(Curl, CURLOPT_HTTPHEADER, Headers); CurlHTTPRequest CurlRequest(Handler); curl_easy_setopt(Curl, CURLOPT_WRITEDATA, &CurlRequest); CURLcode CurlRes = curl_easy_perform(Curl); curl_slist_free_all(Headers); if (CurlRes != CURLE_OK) return joinErrors(std::move(CurlRequest.ErrorState), createStringError(errc::io_error, "curl_easy_perform() failed: %s\n", curl_easy_strerror(CurlRes))); return std::move(CurlRequest.ErrorState); } unsigned HTTPClient::responseCode() { long Code = 0; curl_easy_getinfo(Curl, CURLINFO_RESPONSE_CODE, &Code); return Code; } #else HTTPClient::HTTPClient() = default; HTTPClient::~HTTPClient() = default; bool HTTPClient::isAvailable() { return false; } void HTTPClient::initialize() {} void HTTPClient::cleanup() {} void HTTPClient::setTimeout(std::chrono::milliseconds Timeout) {} Error HTTPClient::perform(const HTTPRequest &Request, HTTPResponseHandler &Handler) { llvm_unreachable("No HTTP Client implementation available."); } unsigned HTTPClient::responseCode() { llvm_unreachable("No HTTP Client implementation available."); } #endif