forked from OSchip/llvm-project
217 lines
6.9 KiB
C++
217 lines
6.9 KiB
C++
//===-- 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 methods of the HTTPRequest, HTTPClient, and
|
|
/// BufferedHTTPResponseHandler classes.
|
|
///
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#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;
|
|
|
|
static inline bool parseContentLengthHeader(StringRef LineRef,
|
|
size_t &ContentLength) {
|
|
// Content-Length is a mandatory header, and the only one we handle.
|
|
return LineRef.consume_front("Content-Length: ") &&
|
|
to_integer(LineRef.trim(), ContentLength, 10);
|
|
}
|
|
|
|
Error BufferedHTTPResponseHandler::handleHeaderLine(StringRef HeaderLine) {
|
|
if (ResponseBuffer.Body)
|
|
return Error::success();
|
|
|
|
size_t ContentLength;
|
|
if (parseContentLengthHeader(HeaderLine, ContentLength))
|
|
ResponseBuffer.Body =
|
|
WritableMemoryBuffer::getNewUninitMemBuffer(ContentLength);
|
|
|
|
return Error::success();
|
|
}
|
|
|
|
Error BufferedHTTPResponseHandler::handleBodyChunk(StringRef BodyChunk) {
|
|
if (!ResponseBuffer.Body)
|
|
return createStringError(errc::io_error,
|
|
"Unallocated response buffer. HTTP Body data "
|
|
"received before Content-Length header.");
|
|
if (Offset + BodyChunk.size() > ResponseBuffer.Body->getBufferSize())
|
|
return createStringError(errc::io_error,
|
|
"Content size exceeds buffer size.");
|
|
memcpy(ResponseBuffer.Body->getBufferStart() + Offset, BodyChunk.data(),
|
|
BodyChunk.size());
|
|
Offset += BodyChunk.size();
|
|
return Error::success();
|
|
}
|
|
|
|
Error BufferedHTTPResponseHandler::handleStatusCode(unsigned Code) {
|
|
ResponseBuffer.Code = Code;
|
|
return Error::success();
|
|
}
|
|
|
|
bool HTTPClient::IsInitialized = false;
|
|
|
|
class HTTPClientCleanup {
|
|
public:
|
|
~HTTPClientCleanup() { HTTPClient::cleanup(); }
|
|
};
|
|
static const HTTPClientCleanup Cleanup;
|
|
|
|
Expected<HTTPResponseBuffer> HTTPClient::perform(const HTTPRequest &Request) {
|
|
BufferedHTTPResponseHandler Handler;
|
|
if (Error Err = perform(Request, Handler))
|
|
return std::move(Err);
|
|
return std::move(Handler.ResponseBuffer);
|
|
}
|
|
|
|
Expected<HTTPResponseBuffer> HTTPClient::get(StringRef Url) {
|
|
HTTPRequest Request(Url);
|
|
return perform(Request);
|
|
}
|
|
|
|
#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 curlHeaderFunction(char *Contents, size_t Size, size_t NMemb,
|
|
CurlHTTPRequest *CurlRequest) {
|
|
assert(Size == 1 && "The Size passed by libCURL to CURLOPT_HEADERFUNCTION "
|
|
"should always be 1.");
|
|
if (Error Err =
|
|
CurlRequest->Handler.handleHeaderLine(StringRef(Contents, NMemb))) {
|
|
CurlRequest->storeError(std::move(Err));
|
|
return 0;
|
|
}
|
|
return NMemb;
|
|
}
|
|
|
|
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;
|
|
assert((Curl = curl_easy_init()) && "Curl could not be initialized.");
|
|
// Set the callback hooks.
|
|
curl_easy_setopt(Curl, CURLOPT_WRITEFUNCTION, curlWriteFunction);
|
|
curl_easy_setopt(Curl, CURLOPT_HEADERFUNCTION, curlHeaderFunction);
|
|
}
|
|
|
|
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);
|
|
|
|
CurlHTTPRequest CurlRequest(Handler);
|
|
curl_easy_setopt(Curl, CURLOPT_WRITEDATA, &CurlRequest);
|
|
curl_easy_setopt(Curl, CURLOPT_HEADERDATA, &CurlRequest);
|
|
CURLcode CurlRes = curl_easy_perform(Curl);
|
|
if (CurlRes != CURLE_OK)
|
|
return joinErrors(std::move(CurlRequest.ErrorState),
|
|
createStringError(errc::io_error,
|
|
"curl_easy_perform() failed: %s\n",
|
|
curl_easy_strerror(CurlRes)));
|
|
if (CurlRequest.ErrorState)
|
|
return std::move(CurlRequest.ErrorState);
|
|
|
|
unsigned Code;
|
|
curl_easy_getinfo(Curl, CURLINFO_RESPONSE_CODE, &Code);
|
|
if (Error Err = Handler.handleStatusCode(Code))
|
|
return joinErrors(std::move(CurlRequest.ErrorState), std::move(Err));
|
|
|
|
return std::move(CurlRequest.ErrorState);
|
|
}
|
|
|
|
#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.");
|
|
}
|
|
|
|
#endif
|