| //===-- 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 <curl/curl.h> |
| #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 |