7 #include <packager/file/http_file.h>
9 #include <absl/flags/declare.h>
10 #include <absl/flags/flag.h>
11 #include <absl/log/check.h>
12 #include <absl/log/log.h>
13 #include <absl/strings/escaping.h>
14 #include <absl/strings/str_format.h>
15 #include <curl/curl.h>
17 #include <packager/file/file_closer.h>
18 #include <packager/file/thread_pool.h>
19 #include <packager/macros/compiler.h>
20 #include <packager/macros/logging.h>
21 #include <packager/version/version.h>
23 ABSL_FLAG(std::string,
26 "Set a custom User-Agent string for HTTP requests.");
27 ABSL_FLAG(std::string,
30 "Absolute path to the Certificate Authority file for the "
31 "server cert. PEM format");
32 ABSL_FLAG(std::string,
35 "Absolute path to client certificate file.");
36 ABSL_FLAG(std::string,
37 client_cert_private_key_file,
39 "Absolute path to the Private Key file.");
40 ABSL_FLAG(std::string,
41 client_cert_private_key_password,
43 "Password to the private key file.");
45 disable_peer_verification,
47 "Disable peer verification. This is needed to talk to servers "
48 "without valid certificates.");
50 ignore_http_output_failures,
52 "Ignore HTTP output failures. Can help recover from live stream "
55 ABSL_DECLARE_FLAG(uint64_t, io_cache_size);
61 constexpr
const char* kBinaryContentType =
"application/octet-stream";
62 constexpr
const int kMinLogLevelForCurlDebugFunction = 2;
64 size_t CurlWriteCallback(
char* buffer,
size_t size,
size_t nmemb,
void* user) {
65 IoCache* cache =
reinterpret_cast<IoCache*
>(user);
66 size_t length = size * nmemb;
68 length = cache->Write(buffer, length);
69 VLOG(3) <<
"CurlWriteCallback length=" << length;
77 size_t CurlReadCallback(
char* buffer,
size_t size,
size_t nitems,
void* user) {
78 IoCache* cache =
reinterpret_cast<IoCache*
>(user);
79 size_t length = cache->Read(buffer, size * nitems);
80 VLOG(3) <<
"CurlRead length=" << length;
84 int CurlDebugCallback(CURL* ,
89 const char* type_text;
94 type_text =
"== Info";
95 log_level = kMinLogLevelForCurlDebugFunction + 1;
98 case CURLINFO_HEADER_IN:
99 type_text =
"<= Recv header";
100 log_level = kMinLogLevelForCurlDebugFunction;
103 case CURLINFO_HEADER_OUT:
104 type_text =
"=> Send header";
105 log_level = kMinLogLevelForCurlDebugFunction;
108 case CURLINFO_DATA_IN:
109 type_text =
"<= Recv data";
110 log_level = kMinLogLevelForCurlDebugFunction + 1;
113 case CURLINFO_DATA_OUT:
114 type_text =
"=> Send data";
115 log_level = kMinLogLevelForCurlDebugFunction + 1;
118 case CURLINFO_SSL_DATA_IN:
119 type_text =
"<= Recv SSL data";
120 log_level = kMinLogLevelForCurlDebugFunction + 2;
123 case CURLINFO_SSL_DATA_OUT:
124 type_text =
"=> Send SSL data";
125 log_level = kMinLogLevelForCurlDebugFunction + 2;
133 const std::string data_string(data, size);
134 VLOG(log_level) <<
"\n\n"
135 << type_text <<
" (0x" << std::hex << size << std::dec
137 << (in_hex ? absl::BytesToHexString(data_string)
142 class LibCurlInitializer {
144 LibCurlInitializer() {
145 curl_global_init(CURL_GLOBAL_DEFAULT);
148 ~LibCurlInitializer() {
149 curl_global_cleanup();
152 LibCurlInitializer(
const LibCurlInitializer&) =
delete;
153 LibCurlInitializer& operator=(
const LibCurlInitializer&) =
delete;
156 template <
typename List>
157 bool AppendHeader(
const std::string& header, List* list) {
158 auto* temp = curl_slist_append(list->get(), header.c_str());
170 HttpFile::HttpFile(HttpMethod method,
const std::string& url)
171 : HttpFile(method, url, kBinaryContentType, {}, 0) {}
173 HttpFile::HttpFile(HttpMethod method,
174 const std::string& url,
175 const std::string& upload_content_type,
176 const std::vector<std::string>& headers,
177 int32_t timeout_in_seconds)
180 upload_content_type_(upload_content_type),
181 timeout_in_seconds_(timeout_in_seconds),
183 isUpload_(method == HttpMethod::kPut || method == HttpMethod::kPost),
184 download_cache_(absl::GetFlag(FLAGS_io_cache_size)),
185 upload_cache_(absl::GetFlag(FLAGS_io_cache_size)),
186 curl_(curl_easy_init()),
188 user_agent_(absl::GetFlag(FLAGS_user_agent)),
189 ca_file_(absl::GetFlag(FLAGS_ca_file)),
190 client_cert_file_(absl::GetFlag(FLAGS_client_cert_file)),
191 client_cert_private_key_file_(
192 absl::GetFlag(FLAGS_client_cert_private_key_file)),
193 client_cert_private_key_password_(
194 absl::GetFlag(FLAGS_client_cert_private_key_password)) {
195 static LibCurlInitializer lib_curl_initializer;
196 if (user_agent_.empty()) {
197 user_agent_ +=
"ShakaPackager/" + GetPackagerVersion();
204 std::unique_ptr<curl_slist, CurlDelete> temp_headers;
205 if (!AppendHeader(
"Expect:", &temp_headers))
207 if (!upload_content_type.empty() &&
208 !AppendHeader(
"Content-Type: " + upload_content_type_, &temp_headers)) {
211 if (isUpload_ && !AppendHeader(
"Transfer-Encoding: chunked", &temp_headers)) {
214 for (
const auto& item : headers) {
215 if (!AppendHeader(item, &temp_headers)) {
219 request_headers_ = std::move(temp_headers);
222 HttpFile::~HttpFile() {}
225 bool HttpFile::Delete(
const std::string& url) {
226 std::unique_ptr<HttpFile, FileCloser> file(
227 new HttpFile(HttpMethod::kDelete, url));
231 return file.release()->Close();
234 bool HttpFile::Open() {
235 VLOG(2) <<
"Opening " << url_;
237 if (!curl_ || !request_headers_) {
238 LOG(ERROR) <<
"curl_easy_init() failed.";
246 ThreadPool::instance.PostTask(std::bind(&HttpFile::ThreadMain,
this));
251 Status HttpFile::CloseWithStatus() {
252 VLOG(2) <<
"Closing " << url_;
259 upload_cache_.Close();
260 task_exit_event_.WaitForNotification();
262 const Status result = status_;
263 LOG_IF(ERROR, !result.ok()) <<
"HttpFile request failed: " << result;
265 return absl::GetFlag(FLAGS_ignore_http_output_failures) ? Status::OK : result;
268 bool HttpFile::Close() {
269 return CloseWithStatus().ok();
272 int64_t HttpFile::Read(
void* buffer, uint64_t length) {
273 VLOG(2) <<
"Reading from " << url_ <<
", length=" << length;
274 return download_cache_.Read(buffer, length);
277 int64_t HttpFile::Write(
const void* buffer, uint64_t length) {
278 DCHECK(!upload_cache_.closed());
279 VLOG(2) <<
"Writing to " << url_ <<
", length=" << length;
280 return upload_cache_.Write(buffer, length);
283 void HttpFile::CloseForWriting() {
284 VLOG(2) <<
"Closing further writes to " << url_;
285 upload_cache_.Close();
288 int64_t HttpFile::Size() {
289 VLOG(1) <<
"HttpFile does not support Size().";
293 bool HttpFile::Flush() {
295 upload_cache_.WaitUntilEmptyOrClosed();
299 bool HttpFile::Seek(uint64_t position) {
301 LOG(ERROR) <<
"HttpFile does not support Seek().";
305 bool HttpFile::Tell(uint64_t* position) {
307 LOG(ERROR) <<
"HttpFile does not support Tell().";
311 void HttpFile::CurlDelete::operator()(CURL* curl) {
312 curl_easy_cleanup(curl);
315 void HttpFile::CurlDelete::operator()(curl_slist* headers) {
316 curl_slist_free_all(headers);
319 void HttpFile::SetupRequest() {
320 auto* curl = curl_.get();
323 case HttpMethod::kGet:
324 curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);
326 case HttpMethod::kPost:
327 curl_easy_setopt(curl, CURLOPT_POST, 1L);
329 case HttpMethod::kPut:
330 curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
332 case HttpMethod::kDelete:
333 curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST,
"DELETE");
337 curl_easy_setopt(curl, CURLOPT_URL, url_.c_str());
338 curl_easy_setopt(curl, CURLOPT_USERAGENT, user_agent_.c_str());
339 curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout_in_seconds_);
340 curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L);
341 curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
342 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &CurlWriteCallback);
343 curl_easy_setopt(curl, CURLOPT_WRITEDATA, &download_cache_);
345 curl_easy_setopt(curl, CURLOPT_READFUNCTION, &CurlReadCallback);
346 curl_easy_setopt(curl, CURLOPT_READDATA, &upload_cache_);
349 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, request_headers_.get());
351 if (absl::GetFlag(FLAGS_disable_peer_verification))
352 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
355 if (!client_cert_private_key_file_.empty() && !client_cert_file_.empty()) {
356 curl_easy_setopt(curl, CURLOPT_SSLKEY,
357 client_cert_private_key_file_.data());
358 curl_easy_setopt(curl, CURLOPT_SSLCERT, client_cert_file_.data());
359 curl_easy_setopt(curl, CURLOPT_SSLKEYTYPE,
"PEM");
360 curl_easy_setopt(curl, CURLOPT_SSLCERTTYPE,
"PEM");
362 if (!client_cert_private_key_password_.empty()) {
363 curl_easy_setopt(curl, CURLOPT_KEYPASSWD,
364 client_cert_private_key_password_.data());
367 if (!ca_file_.empty()) {
368 curl_easy_setopt(curl, CURLOPT_CAINFO, ca_file_.data());
371 if (VLOG_IS_ON(kMinLogLevelForCurlDebugFunction)) {
372 curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, CurlDebugCallback);
373 curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
377 void HttpFile::ThreadMain() {
380 CURLcode res = curl_easy_perform(curl_.get());
381 if (res != CURLE_OK) {
382 std::string error_message = curl_easy_strerror(res);
383 if (res == CURLE_HTTP_RETURNED_ERROR) {
384 long response_code = 0;
385 curl_easy_getinfo(curl_.get(), CURLINFO_RESPONSE_CODE, &response_code);
386 error_message += absl::StrFormat(
", response code: %ld.", response_code);
390 res == CURLE_OPERATION_TIMEDOUT ? error::TIME_OUT : error::HTTP_FAILURE,
399 upload_cache_.Close();
400 download_cache_.Close();
401 task_exit_event_.Notify();
All the methods that are virtual are virtual for mocking.