7#include <packager/file/http_file.h>
18#include <absl/flags/declare.h>
19#include <absl/flags/flag.h>
20#include <absl/log/check.h>
21#include <absl/log/log.h>
22#include <absl/log/vlog_is_on.h>
23#include <absl/strings/escaping.h>
24#include <absl/strings/str_format.h>
28#include <packager/file.h>
29#include <packager/file/file_closer.h>
30#include <packager/file/io_cache.h>
31#include <packager/file/thread_pool.h>
32#include <packager/macros/compiler.h>
33#include <packager/status.h>
34#include <packager/version/version.h>
39 "Set a custom User-Agent string for HTTP requests.");
43 "Absolute path to the Certificate Authority file for the "
44 "server cert. PEM format");
48 "Absolute path to client certificate file.");
50 client_cert_private_key_file,
52 "Absolute path to the Private Key file.");
54 client_cert_private_key_password,
56 "Password to the private key file.");
58 disable_peer_verification,
60 "Disable peer verification. This is needed to talk to servers "
61 "without valid certificates.");
63 ignore_http_output_failures,
65 "Ignore HTTP output failures. Can help recover from live stream "
68ABSL_DECLARE_FLAG(uint64_t, io_cache_size);
74constexpr const char* kBinaryContentType =
"application/octet-stream";
75constexpr const int kMinLogLevelForCurlDebugFunction = 2;
77size_t CurlWriteCallback(
char* buffer,
size_t size,
size_t nmemb,
void* user) {
78 IoCache* cache =
reinterpret_cast<IoCache*
>(user);
79 size_t length = size * nmemb;
81 length = cache->Write(buffer, length);
82 VLOG(3) <<
"CurlWriteCallback length=" << length;
90size_t CurlReadCallback(
char* buffer,
size_t size,
size_t nitems,
void* user) {
91 IoCache* cache =
reinterpret_cast<IoCache*
>(user);
92 size_t length = cache->Read(buffer, size * nitems);
93 VLOG(3) <<
"CurlRead length=" << length;
97int CurlDebugCallback(CURL* ,
102 const char* type_text;
107 type_text =
"== Info";
108 log_level = kMinLogLevelForCurlDebugFunction + 1;
111 case CURLINFO_HEADER_IN:
112 type_text =
"<= Recv header";
113 log_level = kMinLogLevelForCurlDebugFunction;
116 case CURLINFO_HEADER_OUT:
117 type_text =
"=> Send header";
118 log_level = kMinLogLevelForCurlDebugFunction;
121 case CURLINFO_DATA_IN:
122 type_text =
"<= Recv data";
123 log_level = kMinLogLevelForCurlDebugFunction + 1;
126 case CURLINFO_DATA_OUT:
127 type_text =
"=> Send data";
128 log_level = kMinLogLevelForCurlDebugFunction + 1;
131 case CURLINFO_SSL_DATA_IN:
132 type_text =
"<= Recv SSL data";
133 log_level = kMinLogLevelForCurlDebugFunction + 2;
136 case CURLINFO_SSL_DATA_OUT:
137 type_text =
"=> Send SSL data";
138 log_level = kMinLogLevelForCurlDebugFunction + 2;
146 const std::string data_string(data, size);
147 VLOG(log_level) <<
"\n\n"
148 << type_text <<
" (0x" << std::hex << size << std::dec
150 << (in_hex ? absl::BytesToHexString(data_string)
155class LibCurlInitializer {
157 LibCurlInitializer() { curl_global_init(CURL_GLOBAL_DEFAULT); }
159 ~LibCurlInitializer() { curl_global_cleanup(); }
161 LibCurlInitializer(
const LibCurlInitializer&) =
delete;
162 LibCurlInitializer& operator=(
const LibCurlInitializer&) =
delete;
165template <
typename List>
166bool AppendHeader(
const std::string& header, List* list) {
167 auto* temp = curl_slist_append(list->get(), header.c_str());
179HttpFile::HttpFile(HttpMethod method,
const std::string& url)
180 : HttpFile(method, url, kBinaryContentType, {}, 0) {}
182HttpFile::HttpFile(HttpMethod method,
183 const std::string& url,
184 const std::string& upload_content_type,
185 const std::vector<std::string>& headers,
186 int32_t timeout_in_seconds)
189 upload_content_type_(upload_content_type),
190 timeout_in_seconds_(timeout_in_seconds),
192 isUpload_(method == HttpMethod::kPut || method == HttpMethod::kPost),
193 download_cache_(absl::GetFlag(FLAGS_io_cache_size)),
194 upload_cache_(absl::GetFlag(FLAGS_io_cache_size)),
195 curl_(curl_easy_init()),
197 user_agent_(absl::GetFlag(FLAGS_user_agent)),
198 ca_file_(absl::GetFlag(FLAGS_ca_file)),
199 client_cert_file_(absl::GetFlag(FLAGS_client_cert_file)),
200 client_cert_private_key_file_(
201 absl::GetFlag(FLAGS_client_cert_private_key_file)),
202 client_cert_private_key_password_(
203 absl::GetFlag(FLAGS_client_cert_private_key_password)) {
204 static LibCurlInitializer lib_curl_initializer;
205 if (user_agent_.empty()) {
206 user_agent_ +=
"ShakaPackager/" + GetPackagerVersion();
213 std::unique_ptr<curl_slist, CurlDelete> temp_headers;
214 if (!AppendHeader(
"Expect:", &temp_headers))
216 if (!upload_content_type.empty() &&
217 !AppendHeader(
"Content-Type: " + upload_content_type_, &temp_headers)) {
220 if (isUpload_ && !AppendHeader(
"Transfer-Encoding: chunked", &temp_headers)) {
223 for (
const auto& item : headers) {
224 if (!AppendHeader(item, &temp_headers)) {
228 request_headers_ = std::move(temp_headers);
231HttpFile::~HttpFile() {}
234bool HttpFile::Delete(
const std::string& url) {
235 std::unique_ptr<HttpFile, FileCloser> file(
236 new HttpFile(HttpMethod::kDelete, url));
240 return file.release()->Close();
243bool HttpFile::Open() {
244 VLOG(2) <<
"Opening " << url_;
246 if (!curl_ || !request_headers_) {
247 LOG(ERROR) <<
"curl_easy_init() failed.";
255 ThreadPool::instance.PostTask(std::bind(&HttpFile::ThreadMain,
this));
260Status HttpFile::CloseWithStatus() {
261 VLOG(2) <<
"Closing " << url_;
268 upload_cache_.Close();
269 task_exit_event_.WaitForNotification();
271 const Status result = status_;
272 LOG_IF(ERROR, !result.ok()) <<
"HttpFile request failed: " << result;
274 return absl::GetFlag(FLAGS_ignore_http_output_failures) ? Status::OK : result;
277bool HttpFile::Close() {
278 return CloseWithStatus().ok();
281int64_t HttpFile::Read(
void* buffer, uint64_t length) {
282 VLOG(2) <<
"Reading from " << url_ <<
", length=" << length;
283 return download_cache_.Read(buffer, length);
286int64_t HttpFile::Write(
const void* buffer, uint64_t length) {
287 DCHECK(!upload_cache_.closed());
288 VLOG(2) <<
"Writing to " << url_ <<
", length=" << length;
289 return upload_cache_.Write(buffer, length);
292void HttpFile::CloseForWriting() {
293 VLOG(2) <<
"Closing further writes to " << url_;
294 upload_cache_.Close();
297int64_t HttpFile::Size() {
298 VLOG(1) <<
"HttpFile does not support Size().";
302bool HttpFile::Flush() {
304 upload_cache_.WaitUntilEmptyOrClosed();
308bool HttpFile::Seek(uint64_t position) {
310 LOG(ERROR) <<
"HttpFile does not support Seek().";
314bool HttpFile::Tell(uint64_t* position) {
316 LOG(ERROR) <<
"HttpFile does not support Tell().";
320void HttpFile::CurlDelete::operator()(CURL* curl) {
321 curl_easy_cleanup(curl);
324void HttpFile::CurlDelete::operator()(curl_slist* headers) {
325 curl_slist_free_all(headers);
328void HttpFile::SetupRequest() {
329 auto* curl = curl_.get();
332 case HttpMethod::kGet:
333 curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);
335 case HttpMethod::kPost:
336 curl_easy_setopt(curl, CURLOPT_POST, 1L);
338 case HttpMethod::kPut:
339 curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
341 case HttpMethod::kDelete:
342 curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST,
"DELETE");
346 curl_easy_setopt(curl, CURLOPT_URL, url_.c_str());
347 curl_easy_setopt(curl, CURLOPT_USERAGENT, user_agent_.c_str());
348 curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout_in_seconds_);
349 curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L);
350 curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
351 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &CurlWriteCallback);
352 curl_easy_setopt(curl, CURLOPT_WRITEDATA, &download_cache_);
354 curl_easy_setopt(curl, CURLOPT_READFUNCTION, &CurlReadCallback);
355 curl_easy_setopt(curl, CURLOPT_READDATA, &upload_cache_);
358 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, request_headers_.get());
360 if (absl::GetFlag(FLAGS_disable_peer_verification))
361 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
364 if (!client_cert_private_key_file_.empty() && !client_cert_file_.empty()) {
365 curl_easy_setopt(curl, CURLOPT_SSLKEY,
366 client_cert_private_key_file_.data());
367 curl_easy_setopt(curl, CURLOPT_SSLCERT, client_cert_file_.data());
368 curl_easy_setopt(curl, CURLOPT_SSLKEYTYPE,
"PEM");
369 curl_easy_setopt(curl, CURLOPT_SSLCERTTYPE,
"PEM");
371 if (!client_cert_private_key_password_.empty()) {
372 curl_easy_setopt(curl, CURLOPT_KEYPASSWD,
373 client_cert_private_key_password_.data());
376 if (!ca_file_.empty()) {
377 curl_easy_setopt(curl, CURLOPT_CAINFO, ca_file_.data());
380 if (VLOG_IS_ON(kMinLogLevelForCurlDebugFunction)) {
381 curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, CurlDebugCallback);
382 curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
386void HttpFile::ThreadMain() {
389 CURLcode res = curl_easy_perform(curl_.get());
390 if (res != CURLE_OK) {
391 std::string error_message = curl_easy_strerror(res);
392 if (res == CURLE_HTTP_RETURNED_ERROR) {
393 long response_code = 0;
394 curl_easy_getinfo(curl_.get(), CURLINFO_RESPONSE_CODE, &response_code);
395 error_message += absl::StrFormat(
", response code: %ld.", response_code);
399 res == CURLE_OPERATION_TIMEDOUT ? error::TIME_OUT : error::HTTP_FAILURE,
408 upload_cache_.Close();
409 download_cache_.Close();
410 task_exit_event_.Notify();
All the methods that are virtual are virtual for mocking.