Files.app: Remove wait for startup animation.
[chromium-blink-merge.git] / media / blink / webcontentdecryptionmodulesession_impl.cc
blob3d773215d87b86f26e32c3db80b5cde96e3a95ea
1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "webcontentdecryptionmodulesession_impl.h"
7 #include "base/bind.h"
8 #include "base/callback_helpers.h"
9 #include "base/logging.h"
10 #include "base/numerics/safe_conversions.h"
11 #include "base/stl_util.h"
12 #include "base/strings/string_util.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "media/base/cdm_key_information.h"
15 #include "media/base/cdm_promise.h"
16 #include "media/base/key_systems.h"
17 #include "media/base/limits.h"
18 #include "media/base/media_keys.h"
19 #include "media/blink/cdm_result_promise.h"
20 #include "media/blink/cdm_session_adapter.h"
21 #include "media/blink/new_session_cdm_result_promise.h"
22 #include "media/blink/webmediaplayer_util.h"
23 #include "media/cdm/json_web_key.h"
24 #include "media/cdm/key_system_names.h"
25 #include "third_party/WebKit/public/platform/WebData.h"
26 #include "third_party/WebKit/public/platform/WebEncryptedMediaKeyInformation.h"
27 #include "third_party/WebKit/public/platform/WebString.h"
28 #include "third_party/WebKit/public/platform/WebURL.h"
29 #include "third_party/WebKit/public/platform/WebVector.h"
31 #if defined(USE_PROPRIETARY_CODECS)
32 #include "media/cdm/cenc_utils.h"
33 #endif
35 namespace media {
37 const char kCloseSessionUMAName[] = "CloseSession";
38 const char kGenerateRequestUMAName[] = "GenerateRequest";
39 const char kLoadSessionUMAName[] = "LoadSession";
40 const char kRemoveSessionUMAName[] = "RemoveSession";
41 const char kUpdateSessionUMAName[] = "UpdateSession";
43 static blink::WebContentDecryptionModuleSession::Client::MessageType
44 convertMessageType(MediaKeys::MessageType message_type) {
45 switch (message_type) {
46 case media::MediaKeys::LICENSE_REQUEST:
47 return blink::WebContentDecryptionModuleSession::Client::MessageType::
48 LicenseRequest;
49 case media::MediaKeys::LICENSE_RENEWAL:
50 return blink::WebContentDecryptionModuleSession::Client::MessageType::
51 LicenseRenewal;
52 case media::MediaKeys::LICENSE_RELEASE:
53 return blink::WebContentDecryptionModuleSession::Client::MessageType::
54 LicenseRelease;
57 NOTREACHED();
58 return blink::WebContentDecryptionModuleSession::Client::MessageType::
59 LicenseRequest;
62 static blink::WebEncryptedMediaKeyInformation::KeyStatus convertStatus(
63 media::CdmKeyInformation::KeyStatus status) {
64 switch (status) {
65 case media::CdmKeyInformation::USABLE:
66 return blink::WebEncryptedMediaKeyInformation::KeyStatus::Usable;
67 case media::CdmKeyInformation::INTERNAL_ERROR:
68 return blink::WebEncryptedMediaKeyInformation::KeyStatus::InternalError;
69 case media::CdmKeyInformation::EXPIRED:
70 return blink::WebEncryptedMediaKeyInformation::KeyStatus::Expired;
71 case media::CdmKeyInformation::OUTPUT_NOT_ALLOWED:
72 return blink::WebEncryptedMediaKeyInformation::KeyStatus::
73 OutputNotAllowed;
74 case media::CdmKeyInformation::OUTPUT_DOWNSCALED:
75 return blink::WebEncryptedMediaKeyInformation::KeyStatus::
76 OutputDownscaled;
77 case media::CdmKeyInformation::KEY_STATUS_PENDING:
78 return blink::WebEncryptedMediaKeyInformation::KeyStatus::StatusPending;
81 NOTREACHED();
82 return blink::WebEncryptedMediaKeyInformation::KeyStatus::InternalError;
85 static MediaKeys::SessionType convertSessionType(
86 blink::WebEncryptedMediaSessionType session_type) {
87 switch (session_type) {
88 case blink::WebEncryptedMediaSessionType::Temporary:
89 return MediaKeys::TEMPORARY_SESSION;
90 case blink::WebEncryptedMediaSessionType::PersistentLicense:
91 return MediaKeys::PERSISTENT_LICENSE_SESSION;
92 case blink::WebEncryptedMediaSessionType::PersistentReleaseMessage:
93 return MediaKeys::PERSISTENT_RELEASE_MESSAGE_SESSION;
94 case blink::WebEncryptedMediaSessionType::Unknown:
95 break;
98 NOTREACHED();
99 return MediaKeys::TEMPORARY_SESSION;
102 static bool SanitizeInitData(EmeInitDataType init_data_type,
103 const unsigned char* init_data,
104 size_t init_data_length,
105 std::vector<uint8>* sanitized_init_data,
106 std::string* error_message) {
107 if (init_data_length > limits::kMaxInitDataLength) {
108 error_message->assign("Initialization data too long.");
109 return false;
112 switch (init_data_type) {
113 case EmeInitDataType::WEBM:
114 sanitized_init_data->assign(init_data, init_data + init_data_length);
115 return true;
117 case EmeInitDataType::CENC:
118 #if defined(USE_PROPRIETARY_CODECS)
119 sanitized_init_data->assign(init_data, init_data + init_data_length);
120 if (!ValidatePsshInput(*sanitized_init_data)) {
121 error_message->assign("Initialization data for CENC is incorrect.");
122 return false;
124 return true;
125 #else
126 error_message->assign("Initialization data type CENC is not supported.");
127 return false;
128 #endif
130 case EmeInitDataType::KEYIDS: {
131 // Extract the keys and then rebuild the message. This ensures that any
132 // extra data in the provided JSON is dropped.
133 std::string init_data_string(init_data, init_data + init_data_length);
134 KeyIdList key_ids;
135 if (!ExtractKeyIdsFromKeyIdsInitData(init_data_string, &key_ids,
136 error_message))
137 return false;
139 for (const auto& key_id : key_ids) {
140 if (key_id.size() < limits::kMinKeyIdLength ||
141 key_id.size() > limits::kMaxKeyIdLength) {
142 error_message->assign("Incorrect key size.");
143 return false;
147 CreateKeyIdsInitData(key_ids, sanitized_init_data);
148 return true;
151 case EmeInitDataType::UNKNOWN:
152 break;
155 NOTREACHED();
156 error_message->assign("Initialization data type is not supported.");
157 return false;
160 static bool SanitizeSessionId(const blink::WebString& session_id,
161 std::string* sanitized_session_id) {
162 // The user agent should thoroughly validate the sessionId value before
163 // passing it to the CDM. At a minimum, this should include checking that
164 // the length and value (e.g. alphanumeric) are reasonable.
165 if (!base::IsStringASCII(session_id))
166 return false;
168 sanitized_session_id->assign(base::UTF16ToASCII(session_id));
169 if (sanitized_session_id->length() > limits::kMaxSessionIdLength)
170 return false;
172 for (const char c : *sanitized_session_id) {
173 if (!IsAsciiAlpha(c) && !IsAsciiDigit(c))
174 return false;
177 return true;
180 static bool SanitizeResponse(const std::string& key_system,
181 const uint8* response,
182 size_t response_length,
183 std::vector<uint8>* sanitized_response) {
184 // The user agent should thoroughly validate the response before passing it
185 // to the CDM. This may include verifying values are within reasonable limits,
186 // stripping irrelevant data or fields, pre-parsing it, sanitizing it,
187 // and/or generating a fully sanitized version. The user agent should check
188 // that the length and values of fields are reasonable. Unknown fields should
189 // be rejected or removed.
190 if (response_length > limits::kMaxSessionResponseLength)
191 return false;
193 if (IsClearKey(key_system) || IsExternalClearKey(key_system)) {
194 std::string key_string(response, response + response_length);
195 KeyIdAndKeyPairs keys;
196 MediaKeys::SessionType session_type = MediaKeys::TEMPORARY_SESSION;
197 if (!ExtractKeysFromJWKSet(key_string, &keys, &session_type))
198 return false;
200 // Must contain at least one key.
201 if (keys.empty())
202 return false;
204 for (const auto key_pair : keys) {
205 if (key_pair.first.size() < limits::kMinKeyIdLength ||
206 key_pair.first.size() > limits::kMaxKeyIdLength) {
207 return false;
211 std::string sanitized_data = GenerateJWKSet(keys, session_type);
212 sanitized_response->assign(sanitized_data.begin(), sanitized_data.end());
213 return true;
216 // TODO(jrummell): Verify responses for Widevine.
217 sanitized_response->assign(response, response + response_length);
218 return true;
221 WebContentDecryptionModuleSessionImpl::WebContentDecryptionModuleSessionImpl(
222 const scoped_refptr<CdmSessionAdapter>& adapter)
223 : adapter_(adapter), is_closed_(false), weak_ptr_factory_(this) {
226 WebContentDecryptionModuleSessionImpl::
227 ~WebContentDecryptionModuleSessionImpl() {
228 if (!session_id_.empty())
229 adapter_->UnregisterSession(session_id_);
232 void WebContentDecryptionModuleSessionImpl::setClientInterface(Client* client) {
233 client_ = client;
236 blink::WebString WebContentDecryptionModuleSessionImpl::sessionId() const {
237 return blink::WebString::fromUTF8(session_id_);
240 void WebContentDecryptionModuleSessionImpl::initializeNewSession(
241 blink::WebEncryptedMediaInitDataType init_data_type,
242 const unsigned char* init_data,
243 size_t init_data_length,
244 blink::WebEncryptedMediaSessionType session_type,
245 blink::WebContentDecryptionModuleResult result) {
246 DCHECK(init_data);
247 DCHECK(session_id_.empty());
249 // From https://w3c.github.io/encrypted-media/#generateRequest.
250 // 5. If the Key System implementation represented by this object's cdm
251 // implementation value does not support initDataType as an Initialization
252 // Data Type, return a promise rejected with a new DOMException whose name
253 // is NotSupportedError. String comparison is case-sensitive.
254 EmeInitDataType eme_init_data_type = ConvertToEmeInitDataType(init_data_type);
255 if (!IsSupportedKeySystemWithInitDataType(adapter_->GetKeySystem(),
256 eme_init_data_type)) {
257 std::string message =
258 "The initialization data type is not supported by the key system.";
259 result.completeWithError(
260 blink::WebContentDecryptionModuleExceptionNotSupportedError, 0,
261 blink::WebString::fromUTF8(message));
262 return;
265 // 9.1 If the init data is not valid for initDataType, reject promise with a
266 // new DOMException whose name is InvalidAccessError.
267 // 9.2 Let sanitized init data be a validated and sanitized version of init
268 // data. The user agent must thoroughly validate the Initialization Data
269 // before passing it to the CDM. This includes verifying that the length
270 // and values of fields are reasonable, verifying that values are within
271 // reasonable limits, and stripping irrelevant, unsupported, or unknown
272 // data or fields. It is recommended that user agents pre-parse, sanitize,
273 // and/or generate a fully sanitized version of the Initialization Data.
274 // If the Initialization Data format specified by initDataType support
275 // multiple entries, the user agent should remove entries that are not
276 // needed by the CDM.
277 // 9.3 If the previous step failed, reject promise with a new DOMException
278 // whose name is InvalidAccessError.
279 std::vector<uint8> sanitized_init_data;
280 std::string message;
281 if (!SanitizeInitData(eme_init_data_type, init_data, init_data_length,
282 &sanitized_init_data, &message)) {
283 result.completeWithError(
284 blink::WebContentDecryptionModuleExceptionInvalidAccessError, 0,
285 blink::WebString::fromUTF8(message));
286 return;
289 // 9.4 Let session id be the empty string.
290 // (Done in constructor.)
292 // 9.5 Let message be null.
293 // (Done by CDM.)
295 // 9.6 Let cdm be the CDM instance represented by this object's cdm
296 // instance value.
297 // 9.7 Use the cdm to execute the following steps:
298 adapter_->InitializeNewSession(
299 eme_init_data_type, sanitized_init_data, convertSessionType(session_type),
300 scoped_ptr<NewSessionCdmPromise>(new NewSessionCdmResultPromise(
301 result, adapter_->GetKeySystemUMAPrefix() + kGenerateRequestUMAName,
302 base::Bind(
303 &WebContentDecryptionModuleSessionImpl::OnSessionInitialized,
304 base::Unretained(this)))));
307 void WebContentDecryptionModuleSessionImpl::load(
308 const blink::WebString& session_id,
309 blink::WebContentDecryptionModuleResult result) {
310 DCHECK(!session_id.isEmpty());
311 DCHECK(session_id_.empty());
313 std::string sanitized_session_id;
314 if (!SanitizeSessionId(session_id, &sanitized_session_id)) {
315 result.completeWithError(
316 blink::WebContentDecryptionModuleExceptionInvalidAccessError, 0,
317 "Invalid session ID.");
318 return;
321 // TODO(jrummell): Now that there are 2 types of persistent sessions, the
322 // session type should be passed from blink. Type should also be passed in the
323 // constructor (and removed from initializeNewSession()).
324 adapter_->LoadSession(
325 MediaKeys::PERSISTENT_LICENSE_SESSION, sanitized_session_id,
326 scoped_ptr<NewSessionCdmPromise>(new NewSessionCdmResultPromise(
327 result, adapter_->GetKeySystemUMAPrefix() + kLoadSessionUMAName,
328 base::Bind(
329 &WebContentDecryptionModuleSessionImpl::OnSessionInitialized,
330 base::Unretained(this)))));
333 void WebContentDecryptionModuleSessionImpl::update(
334 const uint8* response,
335 size_t response_length,
336 blink::WebContentDecryptionModuleResult result) {
337 DCHECK(response);
338 DCHECK(!session_id_.empty());
340 std::vector<uint8> sanitized_response;
341 if (!SanitizeResponse(adapter_->GetKeySystem(), response, response_length,
342 &sanitized_response)) {
343 result.completeWithError(
344 blink::WebContentDecryptionModuleExceptionInvalidAccessError, 0,
345 "Invalid response.");
346 return;
349 adapter_->UpdateSession(
350 session_id_, sanitized_response,
351 scoped_ptr<SimpleCdmPromise>(new CdmResultPromise<>(
352 result, adapter_->GetKeySystemUMAPrefix() + kUpdateSessionUMAName)));
355 void WebContentDecryptionModuleSessionImpl::close(
356 blink::WebContentDecryptionModuleResult result) {
357 DCHECK(!session_id_.empty());
358 adapter_->CloseSession(
359 session_id_,
360 scoped_ptr<SimpleCdmPromise>(new CdmResultPromise<>(
361 result, adapter_->GetKeySystemUMAPrefix() + kCloseSessionUMAName)));
364 void WebContentDecryptionModuleSessionImpl::remove(
365 blink::WebContentDecryptionModuleResult result) {
366 DCHECK(!session_id_.empty());
367 adapter_->RemoveSession(
368 session_id_,
369 scoped_ptr<SimpleCdmPromise>(new CdmResultPromise<>(
370 result, adapter_->GetKeySystemUMAPrefix() + kRemoveSessionUMAName)));
373 void WebContentDecryptionModuleSessionImpl::OnSessionMessage(
374 MediaKeys::MessageType message_type,
375 const std::vector<uint8>& message) {
376 DCHECK(client_) << "Client not set before message event";
377 client_->message(convertMessageType(message_type), vector_as_array(&message),
378 message.size());
381 void WebContentDecryptionModuleSessionImpl::OnSessionKeysChange(
382 bool has_additional_usable_key,
383 CdmKeysInfo keys_info) {
384 blink::WebVector<blink::WebEncryptedMediaKeyInformation> keys(
385 keys_info.size());
386 for (size_t i = 0; i < keys_info.size(); ++i) {
387 const auto& key_info = keys_info[i];
388 keys[i].setId(blink::WebData(reinterpret_cast<char*>(&key_info->key_id[0]),
389 key_info->key_id.size()));
390 keys[i].setStatus(convertStatus(key_info->status));
391 keys[i].setSystemCode(key_info->system_code);
394 // Now send the event to blink.
395 client_->keysStatusesChange(keys, has_additional_usable_key);
398 void WebContentDecryptionModuleSessionImpl::OnSessionExpirationUpdate(
399 const base::Time& new_expiry_time) {
400 client_->expirationChanged(new_expiry_time.ToJsTime());
403 void WebContentDecryptionModuleSessionImpl::OnSessionClosed() {
404 if (is_closed_)
405 return;
407 is_closed_ = true;
408 client_->close();
411 blink::WebContentDecryptionModuleResult::SessionStatus
412 WebContentDecryptionModuleSessionImpl::OnSessionInitialized(
413 const std::string& session_id) {
414 // CDM will return NULL if the session to be loaded can't be found.
415 if (session_id.empty())
416 return blink::WebContentDecryptionModuleResult::SessionNotFound;
418 DCHECK(session_id_.empty()) << "Session ID may not be changed once set.";
419 session_id_ = session_id;
420 return adapter_->RegisterSession(session_id_, weak_ptr_factory_.GetWeakPtr())
421 ? blink::WebContentDecryptionModuleResult::NewSession
422 : blink::WebContentDecryptionModuleResult::SessionAlreadyExists;
425 } // namespace media