Shaka Packager SDK
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 
23 ABSL_FLAG(std::string,
24  user_agent,
25  "",
26  "Set a custom User-Agent string for HTTP requests.");
27 ABSL_FLAG(std::string,
28  ca_file,
29  "",
30  "Absolute path to the Certificate Authority file for the "
31  "server cert. PEM format");
32 ABSL_FLAG(std::string,
33  client_cert_file,
34  "",
35  "Absolute path to client certificate file.");
36 ABSL_FLAG(std::string,
37  client_cert_private_key_file,
38  "",
39  "Absolute path to the Private Key file.");
40 ABSL_FLAG(std::string,
41  client_cert_private_key_password,
42  "",
43  "Password to the private key file.");
44 ABSL_FLAG(bool,
45  disable_peer_verification,
46  false,
47  "Disable peer verification. This is needed to talk to servers "
48  "without valid certificates.");
49 ABSL_FLAG(bool,
50  ignore_http_output_failures,
51  false,
52  "Ignore HTTP output failures. Can help recover from live stream "
53  "upload errors.");
54 
55 ABSL_DECLARE_FLAG(uint64_t, io_cache_size);
56 
57 namespace shaka {
58 
59 namespace {
60 
61 constexpr const char* kBinaryContentType = "application/octet-stream";
62 constexpr const int kMinLogLevelForCurlDebugFunction = 2;
63 
64 size_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 
77 size_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 
84 int 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 
142 class 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 
156 template <typename List>
157 bool 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 
170 HttpFile::HttpFile(HttpMethod method, const std::string& url)
171  : HttpFile(method, url, kBinaryContentType, {}, 0) {}
172 
173 HttpFile::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 
222 HttpFile::~HttpFile() {}
223 
224 // static
225 bool 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 
234 bool 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 
251 Status 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 
268 bool HttpFile::Close() {
269  return CloseWithStatus().ok();
270 }
271 
272 int64_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 
277 int64_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 
283 void HttpFile::CloseForWriting() {
284  VLOG(2) << "Closing further writes to " << url_;
285  upload_cache_.Close();
286 }
287 
288 int64_t HttpFile::Size() {
289  VLOG(1) << "HttpFile does not support Size().";
290  return -1;
291 }
292 
293 bool HttpFile::Flush() {
294  // Wait for curl to read any data we may have buffered.
295  upload_cache_.WaitUntilEmptyOrClosed();
296  return true;
297 }
298 
299 bool HttpFile::Seek(uint64_t position) {
300  UNUSED(position);
301  LOG(ERROR) << "HttpFile does not support Seek().";
302  return false;
303 }
304 
305 bool HttpFile::Tell(uint64_t* position) {
306  UNUSED(position);
307  LOG(ERROR) << "HttpFile does not support Tell().";
308  return false;
309 }
310 
311 void HttpFile::CurlDelete::operator()(CURL* curl) {
312  curl_easy_cleanup(curl);
313 }
314 
315 void HttpFile::CurlDelete::operator()(curl_slist* headers) {
316  curl_slist_free_all(headers);
317 }
318 
319 void 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 
377 void 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.
Definition: crypto_flags.cc:66