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