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