1//===--- HTTPClient.cpp - HTTP client library -----------------------------===//
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/Support/HTTP/HTTPClient.h"
16
17#include "llvm/ADT/APInt.h"
18#include "llvm/ADT/StringRef.h"
19#include "llvm/Support/Errc.h"
20#include "llvm/Support/Error.h"
21#include "llvm/Support/ManagedStatic.h"
22#include "llvm/Support/MemoryBuffer.h"
23#ifdef LLVM_ENABLE_CURL
24#include <curl/curl.h>
25#endif
26
27using namespace llvm;
28
29HTTPRequest::HTTPRequest(StringRef Url) { this->Url = Url.str(); }
30
31bool operator==(const HTTPRequest &A, const HTTPRequest &B) {
32 return A.Url == B.Url && A.Method == B.Method &&
33 A.FollowRedirects == B.FollowRedirects;
34}
35
36HTTPResponseHandler::~HTTPResponseHandler() = default;
37
38bool HTTPClient::IsInitialized = false;
39
40class HTTPClientCleanup {
41public:
42 ~HTTPClientCleanup() { HTTPClient::cleanup(); }
43};
44ManagedStatic<HTTPClientCleanup> Cleanup;
45
46#ifdef LLVM_ENABLE_CURL
47
48bool HTTPClient::isAvailable() { return true; }
49
50void HTTPClient::initialize() {
51 if (!IsInitialized) {
52 curl_global_init(CURL_GLOBAL_ALL);
53 IsInitialized = true;
54 }
55}
56
57void HTTPClient::cleanup() {
58 if (IsInitialized) {
59 curl_global_cleanup();
60 IsInitialized = false;
61 }
62}
63
64void HTTPClient::setTimeout(std::chrono::milliseconds Timeout) {
65 if (Timeout < std::chrono::milliseconds(0))
66 Timeout = std::chrono::milliseconds(0);
67 curl_easy_setopt(Curl, CURLOPT_TIMEOUT_MS, Timeout.count());
68}
69
70/// CurlHTTPRequest and the curl{Header,Write}Function are implementation
71/// details used to work with Curl. Curl makes callbacks with a single
72/// customizable pointer parameter.
73struct CurlHTTPRequest {
74 CurlHTTPRequest(HTTPResponseHandler &Handler) : Handler(Handler) {}
75 void storeError(Error Err) {
76 ErrorState = joinErrors(std::move(Err), std::move(ErrorState));
77 }
78 HTTPResponseHandler &Handler;
79 llvm::Error ErrorState = Error::success();
80};
81
82static size_t curlWriteFunction(char *Contents, size_t Size, size_t NMemb,
83 CurlHTTPRequest *CurlRequest) {
84 Size *= NMemb;
85 if (Error Err =
86 CurlRequest->Handler.handleBodyChunk(StringRef(Contents, Size))) {
87 CurlRequest->storeError(std::move(Err));
88 return 0;
89 }
90 return Size;
91}
92
93HTTPClient::HTTPClient() {
94 assert(IsInitialized &&
95 "Must call HTTPClient::initialize() at the beginning of main().");
96 if (Curl)
97 return;
98 Curl = curl_easy_init();
99 assert(Curl && "Curl could not be initialized");
100 // Set the callback hooks.
101 curl_easy_setopt(Curl, CURLOPT_WRITEFUNCTION, curlWriteFunction);
102 // Detect supported compressed encodings and accept all.
103 curl_easy_setopt(Curl, CURLOPT_ACCEPT_ENCODING, "");
104}
105
106HTTPClient::~HTTPClient() { curl_easy_cleanup(Curl); }
107
108Error HTTPClient::perform(const HTTPRequest &Request,
109 HTTPResponseHandler &Handler) {
110 if (Request.Method != HTTPMethod::GET)
111 return createStringError(errc::invalid_argument,
112 "Unsupported CURL request method.");
113
114 SmallString<128> Url = Request.Url;
115 curl_easy_setopt(Curl, CURLOPT_URL, Url.c_str());
116 curl_easy_setopt(Curl, CURLOPT_FOLLOWLOCATION, Request.FollowRedirects);
117
118 curl_slist *Headers = nullptr;
119 for (const std::string &Header : Request.Headers)
120 Headers = curl_slist_append(Headers, Header.c_str());
121 curl_easy_setopt(Curl, CURLOPT_HTTPHEADER, Headers);
122
123 CurlHTTPRequest CurlRequest(Handler);
124 curl_easy_setopt(Curl, CURLOPT_WRITEDATA, &CurlRequest);
125 CURLcode CurlRes = curl_easy_perform(Curl);
126 curl_slist_free_all(Headers);
127 if (CurlRes != CURLE_OK)
128 return joinErrors(std::move(CurlRequest.ErrorState),
129 createStringError(errc::io_error,
130 "curl_easy_perform() failed: %s\n",
131 curl_easy_strerror(CurlRes)));
132 return std::move(CurlRequest.ErrorState);
133}
134
135unsigned HTTPClient::responseCode() {
136 long Code = 0;
137 curl_easy_getinfo(Curl, CURLINFO_RESPONSE_CODE, &Code);
138 return Code;
139}
140
141#else
142
143HTTPClient::HTTPClient() = default;
144
145HTTPClient::~HTTPClient() = default;
146
147bool HTTPClient::isAvailable() { return false; }
148
149void HTTPClient::initialize() {}
150
151void HTTPClient::cleanup() {}
152
153void HTTPClient::setTimeout(std::chrono::milliseconds Timeout) {}
154
155Error HTTPClient::perform(const HTTPRequest &Request,
156 HTTPResponseHandler &Handler) {
157 llvm_unreachable("No HTTP Client implementation available.");
158}
159
160unsigned HTTPClient::responseCode() {
161 llvm_unreachable("No HTTP Client implementation available.");
162}
163
164#endif
165