Shaka Packager SDK
http_key_fetcher.cc
1 // Copyright 2014 Google Inc. 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/media/base/http_key_fetcher.h"
8 
9 #include <curl/curl.h>
10 
11 #include "packager/base/logging.h"
12 #include "packager/base/strings/string_number_conversions.h"
13 #include "packager/base/strings/stringprintf.h"
14 #include "packager/base/synchronization/lock.h"
15 
16 namespace shaka {
17 
18 namespace {
19 const char kUserAgentString[] = "shaka-packager-http_fetcher/1.0";
20 const char kSoapActionHeader[] =
21  "SOAPAction: \"http://schemas.microsoft.com/DRM/2007/03/protocols/"
22  "AcquirePackagingData\"";
23 const char kXmlContentTypeHeader[] = "Content-Type: text/xml; charset=UTF-8";
24 const char kJsonContentTypeHeader[] = "Content-Type: application/json";
25 
26 const int kMinLogLevelForCurlDebugFunction = 2;
27 
28 int CurlDebugFunction(CURL* /* handle */,
29  curl_infotype type,
30  const char* data,
31  size_t size,
32  void* /* userptr */) {
33  const char* type_text;
34  int log_level = kMinLogLevelForCurlDebugFunction;
35  switch (type) {
36  case CURLINFO_TEXT:
37  type_text = "== Info";
38  log_level = kMinLogLevelForCurlDebugFunction + 1;
39  break;
40  case CURLINFO_HEADER_IN:
41  type_text = "<= Recv header";
42  log_level = kMinLogLevelForCurlDebugFunction;
43  break;
44  case CURLINFO_HEADER_OUT:
45  type_text = "=> Send header";
46  log_level = kMinLogLevelForCurlDebugFunction;
47  break;
48  case CURLINFO_DATA_IN:
49  type_text = "<= Recv data";
50  log_level = kMinLogLevelForCurlDebugFunction + 1;
51  break;
52  case CURLINFO_DATA_OUT:
53  type_text = "=> Send data";
54  log_level = kMinLogLevelForCurlDebugFunction + 1;
55  break;
56  case CURLINFO_SSL_DATA_IN:
57  type_text = "<= Recv SSL data";
58  log_level = kMinLogLevelForCurlDebugFunction + 2;
59  break;
60  case CURLINFO_SSL_DATA_OUT:
61  type_text = "=> Send SSL data";
62  log_level = kMinLogLevelForCurlDebugFunction + 2;
63  break;
64  default:
65  // Ignore other debug data.
66  return 0;
67  }
68 
69  VLOG(log_level) << "\n\n"
70  << type_text << " (0x" << std::hex << size << std::dec
71  << " bytes)"
72  << "\n"
73  << std::string(data, size) << "\nHex Format: \n"
74  << base::HexEncode(data, size);
75  return 0;
76 }
77 
78 // Scoped CURL implementation which cleans up itself when goes out of scope.
79 class ScopedCurl {
80  public:
81  ScopedCurl() { ptr_ = curl_easy_init(); }
82  ~ScopedCurl() {
83  if (ptr_)
84  curl_easy_cleanup(ptr_);
85  }
86 
87  CURL* get() { return ptr_; }
88 
89  private:
90  CURL* ptr_;
91  DISALLOW_COPY_AND_ASSIGN(ScopedCurl);
92 };
93 
94 size_t AppendToString(char* ptr, size_t size, size_t nmemb, std::string* response) {
95  DCHECK(ptr);
96  DCHECK(response);
97  const size_t total_size = size * nmemb;
98  response->append(ptr, total_size);
99  return total_size;
100 }
101 
102 class LibCurlInitializer {
103  public:
104  LibCurlInitializer() : initialized_(false) {
105  base::AutoLock lock(lock_);
106  if (!initialized_) {
107  curl_global_init(CURL_GLOBAL_DEFAULT);
108  initialized_ = true;
109  }
110  }
111 
112  ~LibCurlInitializer() {
113  base::AutoLock lock(lock_);
114  if (initialized_) {
115  curl_global_cleanup();
116  initialized_ = false;
117  }
118  }
119 
120  private:
121  base::Lock lock_;
122  bool initialized_;
123 
124  DISALLOW_COPY_AND_ASSIGN(LibCurlInitializer);
125 };
126 
127 } // namespace
128 
129 namespace media {
130 
131 HttpKeyFetcher::HttpKeyFetcher() : timeout_in_seconds_(0) {}
132 
133 HttpKeyFetcher::HttpKeyFetcher(uint32_t timeout_in_seconds)
134  : timeout_in_seconds_(timeout_in_seconds) {}
135 
136 HttpKeyFetcher::~HttpKeyFetcher() {}
137 
138 Status HttpKeyFetcher::FetchKeys(const std::string& url,
139  const std::string& request,
140  std::string* response) {
141  return Post(url, request, response);
142 }
143 
144 Status HttpKeyFetcher::Get(const std::string& path, std::string* response) {
145  return FetchInternal(GET, path, "", response);
146 }
147 
148 Status HttpKeyFetcher::Post(const std::string& path,
149  const std::string& data,
150  std::string* response) {
151  return FetchInternal(POST, path, data, response);
152 }
153 
154 Status HttpKeyFetcher::FetchInternal(HttpMethod method,
155  const std::string& path,
156  const std::string& data,
157  std::string* response) {
158  DCHECK(method == GET || method == POST);
159  static LibCurlInitializer lib_curl_initializer;
160 
161  ScopedCurl scoped_curl;
162  CURL* curl = scoped_curl.get();
163  if (!curl) {
164  LOG(ERROR) << "curl_easy_init() failed.";
165  return Status(error::HTTP_FAILURE, "curl_easy_init() failed.");
166  }
167  response->clear();
168 
169  curl_easy_setopt(curl, CURLOPT_URL, path.c_str());
170  curl_easy_setopt(curl, CURLOPT_USERAGENT, kUserAgentString);
171  curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout_in_seconds_);
172  curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L);
173  curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
174  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, AppendToString);
175  curl_easy_setopt(curl, CURLOPT_WRITEDATA, response);
176 
177  if (!client_cert_private_key_file_.empty() && !client_cert_file_.empty()) {
178  // Some PlayReady packaging servers only allow connects via HTTPS with
179  // client certificates.
180  curl_easy_setopt(curl, CURLOPT_SSLKEY,
181  client_cert_private_key_file_.data());
182  if (!client_cert_private_key_password_.empty()) {
183  curl_easy_setopt(curl, CURLOPT_KEYPASSWD,
184  client_cert_private_key_password_.data());
185  }
186  curl_easy_setopt(curl, CURLOPT_SSLKEYTYPE, "PEM");
187  curl_easy_setopt(curl, CURLOPT_SSLCERTTYPE, "PEM");
188  curl_easy_setopt(curl, CURLOPT_SSLCERT, client_cert_file_.data());
189  }
190  if (!ca_file_.empty()) {
191  // Host validation needs to be off when using self-signed certificates.
192  curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
193  curl_easy_setopt(curl, CURLOPT_CAINFO, ca_file_.data());
194  }
195  if (method == POST) {
196  curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str());
197  curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, data.size());
198 
199  curl_slist* chunk = nullptr;
200  if (data.find("soap:Envelope") != std::string::npos) {
201  // Adds Http headers for SOAP requests.
202  chunk = curl_slist_append(chunk, kXmlContentTypeHeader);
203  chunk = curl_slist_append(chunk, kSoapActionHeader);
204  } else {
205  chunk = curl_slist_append(chunk, kJsonContentTypeHeader);
206  }
207  curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk);
208  }
209 
210  if (VLOG_IS_ON(kMinLogLevelForCurlDebugFunction)) {
211  curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, CurlDebugFunction);
212  curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
213  }
214 
215  CURLcode res = curl_easy_perform(curl);
216  if (res != CURLE_OK) {
217  std::string error_message = base::StringPrintf(
218  "curl_easy_perform() failed: %s.", curl_easy_strerror(res));
219  if (res == CURLE_HTTP_RETURNED_ERROR) {
220  long response_code = 0;
221  curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
222  error_message += base::StringPrintf(" Response code: %ld.", response_code);
223  }
224 
225  LOG(ERROR) << error_message;
226  return Status(
227  res == CURLE_OPERATION_TIMEDOUT ? error::TIME_OUT : error::HTTP_FAILURE,
228  error_message);
229  }
230  return Status::OK;
231 }
232 
233 } // namespace media
234 } // namespace shaka
All the methods that are virtual are virtual for mocking.
virtual Status Get(const std::string &url, std::string *response)
Status FetchKeys(const std::string &url, const std::string &request, std::string *response) override
HttpKeyFetcher()
Creates a fetcher with no timeout.
virtual Status Post(const std::string &url, const std::string &data, std::string *response)