7 #include "packager/file/http_file.h"
10 #include <gflags/gflags.h>
12 #include "packager/base/bind.h"
13 #include "packager/base/files/file_util.h"
14 #include "packager/base/logging.h"
15 #include "packager/base/strings/string_number_conversions.h"
16 #include "packager/base/strings/stringprintf.h"
17 #include "packager/base/threading/worker_pool.h"
18 #include "packager/version/version.h"
20 DEFINE_string(user_agent,
"",
21 "Set a custom User-Agent string for HTTP requests.");
22 DEFINE_string(ca_file,
24 "Absolute path to the Certificate Authority file for the "
25 "server cert. PEM format");
26 DEFINE_string(client_cert_file,
28 "Absolute path to client certificate file.");
29 DEFINE_string(client_cert_private_key_file,
31 "Absolute path to the Private Key file.");
32 DEFINE_string(client_cert_private_key_password,
34 "Password to the private key file.");
35 DEFINE_bool(disable_peer_verification,
37 "Disable peer verification. This is needed to talk to servers "
38 "without valid certificates.");
39 DECLARE_uint64(io_cache_size);
45 constexpr
const char* kBinaryContentType =
"application/octet-stream";
46 constexpr
const int kMinLogLevelForCurlDebugFunction = 2;
48 size_t CurlWriteCallback(
char* buffer,
size_t size,
size_t nmemb,
void* user) {
49 IoCache* cache =
reinterpret_cast<IoCache*
>(user);
50 size_t length = size * nmemb;
52 length = cache->Write(buffer, length);
53 VLOG(3) <<
"CurlWriteCallback length=" << length;
61 size_t CurlReadCallback(
char* buffer,
size_t size,
size_t nitems,
void* user) {
62 IoCache* cache =
reinterpret_cast<IoCache*
>(user);
63 size_t length = cache->Read(buffer, size * nitems);
64 VLOG(3) <<
"CurlRead length=" << length;
68 int CurlDebugCallback(CURL* ,
73 const char* type_text;
78 type_text =
"== Info";
79 log_level = kMinLogLevelForCurlDebugFunction + 1;
82 case CURLINFO_HEADER_IN:
83 type_text =
"<= Recv header";
84 log_level = kMinLogLevelForCurlDebugFunction;
87 case CURLINFO_HEADER_OUT:
88 type_text =
"=> Send header";
89 log_level = kMinLogLevelForCurlDebugFunction;
92 case CURLINFO_DATA_IN:
93 type_text =
"<= Recv data";
94 log_level = kMinLogLevelForCurlDebugFunction + 1;
97 case CURLINFO_DATA_OUT:
98 type_text =
"=> Send data";
99 log_level = kMinLogLevelForCurlDebugFunction + 1;
102 case CURLINFO_SSL_DATA_IN:
103 type_text =
"<= Recv SSL data";
104 log_level = kMinLogLevelForCurlDebugFunction + 2;
107 case CURLINFO_SSL_DATA_OUT:
108 type_text =
"=> Send SSL data";
109 log_level = kMinLogLevelForCurlDebugFunction + 2;
117 VLOG(log_level) <<
"\n\n"
118 << type_text <<
" (0x" << std::hex << size << std::dec
120 << (in_hex ? base::HexEncode(data, size)
121 : std::string(data, size));
125 class LibCurlInitializer {
127 LibCurlInitializer() {
128 curl_global_init(CURL_GLOBAL_DEFAULT);
131 ~LibCurlInitializer() {
132 curl_global_cleanup();
135 LibCurlInitializer(
const LibCurlInitializer&) =
delete;
136 LibCurlInitializer& operator=(
const LibCurlInitializer&) =
delete;
139 template <
typename List>
140 bool AppendHeader(
const std::string& header, List* list) {
141 auto* temp = curl_slist_append(list->get(), header.c_str());
153 HttpFile::HttpFile(HttpMethod method,
const std::string& url)
154 : HttpFile(method, url, kBinaryContentType, {}, 0) {}
156 HttpFile::HttpFile(HttpMethod method,
157 const std::string& url,
158 const std::string& upload_content_type,
159 const std::vector<std::string>& headers,
160 uint32_t timeout_in_seconds)
163 upload_content_type_(upload_content_type),
164 timeout_in_seconds_(timeout_in_seconds),
166 download_cache_(FLAGS_io_cache_size),
167 upload_cache_(FLAGS_io_cache_size),
168 curl_(curl_easy_init()),
170 user_agent_(FLAGS_user_agent),
171 task_exit_event_(base::WaitableEvent::ResetPolicy::MANUAL,
172 base::WaitableEvent::InitialState::NOT_SIGNALED) {
173 static LibCurlInitializer lib_curl_initializer;
174 if (user_agent_.empty()) {
175 user_agent_ +=
"ShakaPackager/" + GetPackagerVersion();
182 std::unique_ptr<curl_slist, CurlDelete> temp_headers;
183 if (!AppendHeader(
"Expect:", &temp_headers))
185 if (!upload_content_type.empty() &&
186 !AppendHeader(
"Content-Type: " + upload_content_type_, &temp_headers)) {
189 if (method != HttpMethod::kGet &&
190 !AppendHeader(
"Transfer-Encoding: chunked", &temp_headers)) {
193 for (
const auto& item : headers) {
194 if (!AppendHeader(item, &temp_headers)) {
198 request_headers_ = std::move(temp_headers);
201 HttpFile::~HttpFile() {}
203 bool HttpFile::Open() {
204 VLOG(2) <<
"Opening " << url_;
206 if (!curl_ || !request_headers_) {
207 LOG(ERROR) <<
"curl_easy_init() failed.";
215 base::WorkerPool::PostTask(
216 FROM_HERE, base::Bind(&HttpFile::ThreadMain, base::Unretained(
this)),
222 Status HttpFile::CloseWithStatus() {
223 VLOG(2) <<
"Closing " << url_;
226 download_cache_.Close();
227 upload_cache_.Close();
228 task_exit_event_.Wait();
230 const Status result = status_;
231 LOG_IF(ERROR, !result.ok()) <<
"HttpFile request failed: " << result;
236 bool HttpFile::Close() {
237 return CloseWithStatus().ok();
240 int64_t HttpFile::Read(
void* buffer, uint64_t length) {
241 VLOG(2) <<
"Reading from " << url_ <<
", length=" << length;
242 return download_cache_.Read(buffer, length);
245 int64_t HttpFile::Write(
const void* buffer, uint64_t length) {
246 VLOG(2) <<
"Writing to " << url_ <<
", length=" << length;
247 return upload_cache_.Write(buffer, length);
250 int64_t HttpFile::Size() {
251 VLOG(1) <<
"HttpFile does not support Size().";
255 bool HttpFile::Flush() {
256 upload_cache_.Close();
260 bool HttpFile::Seek(uint64_t position) {
261 LOG(ERROR) <<
"HttpFile does not support Seek().";
265 bool HttpFile::Tell(uint64_t* position) {
266 LOG(ERROR) <<
"HttpFile does not support Tell().";
270 void HttpFile::CurlDelete::operator()(CURL* curl) {
271 curl_easy_cleanup(curl);
274 void HttpFile::CurlDelete::operator()(curl_slist* headers) {
275 curl_slist_free_all(headers);
278 void HttpFile::SetupRequest() {
279 auto* curl = curl_.get();
282 case HttpMethod::kGet:
283 curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);
285 case HttpMethod::kPost:
286 curl_easy_setopt(curl, CURLOPT_POST, 1L);
288 case HttpMethod::kPut:
289 curl_easy_setopt(curl, CURLOPT_PUT, 1L);
293 curl_easy_setopt(curl, CURLOPT_URL, url_.c_str());
294 curl_easy_setopt(curl, CURLOPT_USERAGENT, user_agent_.c_str());
295 curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout_in_seconds_);
296 curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L);
297 curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
298 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &CurlWriteCallback);
299 curl_easy_setopt(curl, CURLOPT_WRITEDATA,
300 method_ == HttpMethod::kPut ?
nullptr : &download_cache_);
301 if (method_ != HttpMethod::kGet) {
302 curl_easy_setopt(curl, CURLOPT_READFUNCTION, &CurlReadCallback);
303 curl_easy_setopt(curl, CURLOPT_READDATA, &upload_cache_);
306 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, request_headers_.get());
308 if (FLAGS_disable_peer_verification)
309 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
312 if (!FLAGS_client_cert_private_key_file.empty() &&
313 !FLAGS_client_cert_file.empty()) {
314 curl_easy_setopt(curl, CURLOPT_SSLKEY,
315 FLAGS_client_cert_private_key_file.data());
316 curl_easy_setopt(curl, CURLOPT_SSLCERT, FLAGS_client_cert_file.data());
317 curl_easy_setopt(curl, CURLOPT_SSLKEYTYPE,
"PEM");
318 curl_easy_setopt(curl, CURLOPT_SSLCERTTYPE,
"PEM");
320 if (!FLAGS_client_cert_private_key_password.empty()) {
321 curl_easy_setopt(curl, CURLOPT_KEYPASSWD,
322 FLAGS_client_cert_private_key_password.data());
325 if (!FLAGS_ca_file.empty()) {
326 curl_easy_setopt(curl, CURLOPT_CAINFO, FLAGS_ca_file.data());
329 if (VLOG_IS_ON(kMinLogLevelForCurlDebugFunction)) {
330 curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, CurlDebugCallback);
331 curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
335 void HttpFile::ThreadMain() {
338 CURLcode res = curl_easy_perform(curl_.get());
339 if (res != CURLE_OK) {
340 std::string error_message = curl_easy_strerror(res);
341 if (res == CURLE_HTTP_RETURNED_ERROR) {
342 long response_code = 0;
343 curl_easy_getinfo(curl_.get(), CURLINFO_RESPONSE_CODE, &response_code);
345 base::StringPrintf(
", response code: %ld.", response_code);
349 res == CURLE_OPERATION_TIMEDOUT ? error::TIME_OUT : error::HTTP_FAILURE,
353 download_cache_.Close();
354 task_exit_event_.Signal();
All the methods that are virtual are virtual for mocking.