Shaka Packager SDK
Loading...
Searching...
No Matches
http_file.cc
1// Copyright 2020 Google LLC. All rights reserved.
2//
3// Use of this source code is governed by a BSD-style
4// license that can be found in the LICENSE file or at
5// https://developers.google.com/open-source/licenses/bsd
6
7#include <packager/file/http_file.h>
8
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>
16
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>
22
23ABSL_FLAG(std::string,
24 user_agent,
25 "",
26 "Set a custom User-Agent string for HTTP requests.");
27ABSL_FLAG(std::string,
28 ca_file,
29 "",
30 "Absolute path to the Certificate Authority file for the "
31 "server cert. PEM format");
32ABSL_FLAG(std::string,
33 client_cert_file,
34 "",
35 "Absolute path to client certificate file.");
36ABSL_FLAG(std::string,
37 client_cert_private_key_file,
38 "",
39 "Absolute path to the Private Key file.");
40ABSL_FLAG(std::string,
41 client_cert_private_key_password,
42 "",
43 "Password to the private key file.");
44ABSL_FLAG(bool,
45 disable_peer_verification,
46 false,
47 "Disable peer verification. This is needed to talk to servers "
48 "without valid certificates.");
49ABSL_FLAG(bool,
50 ignore_http_output_failures,
51 false,
52 "Ignore HTTP output failures. Can help recover from live stream "
53 "upload errors.");
54
55ABSL_DECLARE_FLAG(uint64_t, io_cache_size);
56
57namespace shaka {
58
59namespace {
60
61constexpr const char* kBinaryContentType = "application/octet-stream";
62constexpr const int kMinLogLevelForCurlDebugFunction = 2;
63
64size_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;
67 if (cache) {
68 length = cache->Write(buffer, length);
69 VLOG(3) << "CurlWriteCallback length=" << length;
70 } else {
71 // For the case of HTTP Put, the returned data may not be consumed. Return
72 // the size of the data to avoid curl errors.
73 }
74 return length;
75}
76
77size_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;
81 return length;
82}
83
84int CurlDebugCallback(CURL* /* handle */,
85 curl_infotype type,
86 const char* data,
87 size_t size,
88 void* /* userptr */) {
89 const char* type_text;
90 int log_level;
91 bool in_hex;
92 switch (type) {
93 case CURLINFO_TEXT:
94 type_text = "== Info";
95 log_level = kMinLogLevelForCurlDebugFunction + 1;
96 in_hex = false;
97 break;
98 case CURLINFO_HEADER_IN:
99 type_text = "<= Recv header";
100 log_level = kMinLogLevelForCurlDebugFunction;
101 in_hex = false;
102 break;
103 case CURLINFO_HEADER_OUT:
104 type_text = "=> Send header";
105 log_level = kMinLogLevelForCurlDebugFunction;
106 in_hex = false;
107 break;
108 case CURLINFO_DATA_IN:
109 type_text = "<= Recv data";
110 log_level = kMinLogLevelForCurlDebugFunction + 1;
111 in_hex = true;
112 break;
113 case CURLINFO_DATA_OUT:
114 type_text = "=> Send data";
115 log_level = kMinLogLevelForCurlDebugFunction + 1;
116 in_hex = true;
117 break;
118 case CURLINFO_SSL_DATA_IN:
119 type_text = "<= Recv SSL data";
120 log_level = kMinLogLevelForCurlDebugFunction + 2;
121 in_hex = true;
122 break;
123 case CURLINFO_SSL_DATA_OUT:
124 type_text = "=> Send SSL data";
125 log_level = kMinLogLevelForCurlDebugFunction + 2;
126 in_hex = true;
127 break;
128 default:
129 // Ignore other debug data.
130 return 0;
131 }
132
133 const std::string data_string(data, size);
134 VLOG(log_level) << "\n\n"
135 << type_text << " (0x" << std::hex << size << std::dec
136 << " bytes)\n"
137 << (in_hex ? absl::BytesToHexString(data_string)
138 : data_string);
139 return 0;
140}
141
142class LibCurlInitializer {
143 public:
144 LibCurlInitializer() {
145 curl_global_init(CURL_GLOBAL_DEFAULT);
146 }
147
148 ~LibCurlInitializer() {
149 curl_global_cleanup();
150 }
151
152 LibCurlInitializer(const LibCurlInitializer&) = delete;
153 LibCurlInitializer& operator=(const LibCurlInitializer&) = delete;
154};
155
156template <typename List>
157bool AppendHeader(const std::string& header, List* list) {
158 auto* temp = curl_slist_append(list->get(), header.c_str());
159 if (temp) {
160 list->release(); // Don't free old list since it's part of the new one.
161 list->reset(temp);
162 return true;
163 } else {
164 return false;
165 }
166}
167
168} // namespace
169
170HttpFile::HttpFile(HttpMethod method, const std::string& url)
171 : HttpFile(method, url, kBinaryContentType, {}, 0) {}
172
173HttpFile::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)
178 : File(url.c_str()),
179 url_(url),
180 upload_content_type_(upload_content_type),
181 timeout_in_seconds_(timeout_in_seconds),
182 method_(method),
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()),
187 status_(Status::OK),
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();
198 }
199
200 // We will have at least one header, so use a null header to signal error
201 // to Open.
202
203 // Don't wait for 100-Continue.
204 std::unique_ptr<curl_slist, CurlDelete> temp_headers;
205 if (!AppendHeader("Expect:", &temp_headers))
206 return;
207 if (!upload_content_type.empty() &&
208 !AppendHeader("Content-Type: " + upload_content_type_, &temp_headers)) {
209 return;
210 }
211 if (isUpload_ && !AppendHeader("Transfer-Encoding: chunked", &temp_headers)) {
212 return;
213 }
214 for (const auto& item : headers) {
215 if (!AppendHeader(item, &temp_headers)) {
216 return;
217 }
218 }
219 request_headers_ = std::move(temp_headers);
220}
221
222HttpFile::~HttpFile() {}
223
224// static
225bool HttpFile::Delete(const std::string& url) {
226 std::unique_ptr<HttpFile, FileCloser> file(
227 new HttpFile(HttpMethod::kDelete, url));
228 if (!file->Open()) {
229 return false;
230 }
231 return file.release()->Close();
232}
233
234bool HttpFile::Open() {
235 VLOG(2) << "Opening " << url_;
236
237 if (!curl_ || !request_headers_) {
238 LOG(ERROR) << "curl_easy_init() failed.";
239 return false;
240 }
241 // TODO: Try to connect initially so we can return connection error here.
242
243 // TODO: Implement retrying with exponential backoff, see
244 // "widevine_key_source.cc"
245
246 ThreadPool::instance.PostTask(std::bind(&HttpFile::ThreadMain, this));
247
248 return true;
249}
250
251Status HttpFile::CloseWithStatus() {
252 VLOG(2) << "Closing " << url_;
253
254 // Close the upload cache first so the thread will finish uploading.
255 // Otherwise it will wait for more data forever.
256 // Don't close the download cache, so that the server's response (HTTP status
257 // code at minimum) can still be written after uploading is complete.
258 // The task will close the download cache when it is complete.
259 upload_cache_.Close();
260 task_exit_event_.WaitForNotification();
261
262 const Status result = status_;
263 LOG_IF(ERROR, !result.ok()) << "HttpFile request failed: " << result;
264 delete this;
265 return absl::GetFlag(FLAGS_ignore_http_output_failures) ? Status::OK : result;
266}
267
268bool HttpFile::Close() {
269 return CloseWithStatus().ok();
270}
271
272int64_t HttpFile::Read(void* buffer, uint64_t length) {
273 VLOG(2) << "Reading from " << url_ << ", length=" << length;
274 return download_cache_.Read(buffer, length);
275}
276
277int64_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);
281}
282
283void HttpFile::CloseForWriting() {
284 VLOG(2) << "Closing further writes to " << url_;
285 upload_cache_.Close();
286}
287
288int64_t HttpFile::Size() {
289 VLOG(1) << "HttpFile does not support Size().";
290 return -1;
291}
292
293bool HttpFile::Flush() {
294 // Wait for curl to read any data we may have buffered.
295 upload_cache_.WaitUntilEmptyOrClosed();
296 return true;
297}
298
299bool HttpFile::Seek(uint64_t position) {
300 UNUSED(position);
301 LOG(ERROR) << "HttpFile does not support Seek().";
302 return false;
303}
304
305bool HttpFile::Tell(uint64_t* position) {
306 UNUSED(position);
307 LOG(ERROR) << "HttpFile does not support Tell().";
308 return false;
309}
310
311void HttpFile::CurlDelete::operator()(CURL* curl) {
312 curl_easy_cleanup(curl);
313}
314
315void HttpFile::CurlDelete::operator()(curl_slist* headers) {
316 curl_slist_free_all(headers);
317}
318
319void HttpFile::SetupRequest() {
320 auto* curl = curl_.get();
321
322 switch (method_) {
323 case HttpMethod::kGet:
324 curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);
325 break;
326 case HttpMethod::kPost:
327 curl_easy_setopt(curl, CURLOPT_POST, 1L);
328 break;
329 case HttpMethod::kPut:
330 curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
331 break;
332 case HttpMethod::kDelete:
333 curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
334 break;
335 }
336
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_);
344 if (isUpload_) {
345 curl_easy_setopt(curl, CURLOPT_READFUNCTION, &CurlReadCallback);
346 curl_easy_setopt(curl, CURLOPT_READDATA, &upload_cache_);
347 }
348
349 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, request_headers_.get());
350
351 if (absl::GetFlag(FLAGS_disable_peer_verification))
352 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
353
354 // Client authentication
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");
361
362 if (!client_cert_private_key_password_.empty()) {
363 curl_easy_setopt(curl, CURLOPT_KEYPASSWD,
364 client_cert_private_key_password_.data());
365 }
366 }
367 if (!ca_file_.empty()) {
368 curl_easy_setopt(curl, CURLOPT_CAINFO, ca_file_.data());
369 }
370
371 if (VLOG_IS_ON(kMinLogLevelForCurlDebugFunction)) {
372 curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, CurlDebugCallback);
373 curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
374 }
375}
376
377void HttpFile::ThreadMain() {
378 SetupRequest();
379
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);
387 }
388
389 status_ = Status(
390 res == CURLE_OPERATION_TIMEDOUT ? error::TIME_OUT : error::HTTP_FAILURE,
391 error_message);
392 }
393
394 // In some cases it is possible that the server has already closed the
395 // connection without reading the request body. This can for example happen
396 // when the server responds with a non-successful status code. In this case we
397 // need to make sure to close the upload cache here, otherwise some other
398 // thread may block forever on Flush().
399 upload_cache_.Close();
400 download_cache_.Close();
401 task_exit_event_.Notify();
402}
403
404} // namespace shaka
All the methods that are virtual are virtual for mocking.