Autofill: Port changes to iOS JS code.
[chromium-blink-merge.git] / media / blink / webcontentdecryptionmodulesession_impl.cc
blobc80a1d52d550443b7b014b729586dc89a0a8d38c
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/cenc_utils.h"
24 #include "media/cdm/json_web_key.h"
25 #include "media/cdm/key_system_names.h"
26 #include "third_party/WebKit/public/platform/WebData.h"
27 #include "third_party/WebKit/public/platform/WebEncryptedMediaKeyInformation.h"
28 #include "third_party/WebKit/public/platform/WebString.h"
29 #include "third_party/WebKit/public/platform/WebURL.h"
30 #include "third_party/WebKit/public/platform/WebVector.h"
32 namespace media {
34 const char kCloseSessionUMAName[] = "CloseSession";
35 const char kGenerateRequestUMAName[] = "GenerateRequest";
36 const char kLoadSessionUMAName[] = "LoadSession";
37 const char kRemoveSessionUMAName[] = "RemoveSession";
38 const char kUpdateSessionUMAName[] = "UpdateSession";
40 static blink::WebContentDecryptionModuleSession::Client::MessageType
41 convertMessageType(MediaKeys::MessageType message_type) {
42 switch (message_type) {
43 case media::MediaKeys::LICENSE_REQUEST:
44 return blink::WebContentDecryptionModuleSession::Client::MessageType::
45 LicenseRequest;
46 case media::MediaKeys::LICENSE_RENEWAL:
47 return blink::WebContentDecryptionModuleSession::Client::MessageType::
48 LicenseRenewal;
49 case media::MediaKeys::LICENSE_RELEASE:
50 return blink::WebContentDecryptionModuleSession::Client::MessageType::
51 LicenseRelease;
54 NOTREACHED();
55 return blink::WebContentDecryptionModuleSession::Client::MessageType::
56 LicenseRequest;
59 static blink::WebEncryptedMediaKeyInformation::KeyStatus convertStatus(
60 media::CdmKeyInformation::KeyStatus status) {
61 switch (status) {
62 case media::CdmKeyInformation::USABLE:
63 return blink::WebEncryptedMediaKeyInformation::KeyStatus::Usable;
64 case media::CdmKeyInformation::INTERNAL_ERROR:
65 return blink::WebEncryptedMediaKeyInformation::KeyStatus::InternalError;
66 case media::CdmKeyInformation::EXPIRED:
67 return blink::WebEncryptedMediaKeyInformation::KeyStatus::Expired;
68 case media::CdmKeyInformation::OUTPUT_NOT_ALLOWED:
69 return blink::WebEncryptedMediaKeyInformation::KeyStatus::
70 OutputNotAllowed;
71 case media::CdmKeyInformation::OUTPUT_DOWNSCALED:
72 return blink::WebEncryptedMediaKeyInformation::KeyStatus::
73 OutputDownscaled;
74 case media::CdmKeyInformation::KEY_STATUS_PENDING:
75 return blink::WebEncryptedMediaKeyInformation::KeyStatus::StatusPending;
78 NOTREACHED();
79 return blink::WebEncryptedMediaKeyInformation::KeyStatus::InternalError;
82 static MediaKeys::SessionType convertSessionType(
83 blink::WebEncryptedMediaSessionType session_type) {
84 switch (session_type) {
85 case blink::WebEncryptedMediaSessionType::Temporary:
86 return MediaKeys::TEMPORARY_SESSION;
87 case blink::WebEncryptedMediaSessionType::PersistentLicense:
88 return MediaKeys::PERSISTENT_LICENSE_SESSION;
89 case blink::WebEncryptedMediaSessionType::PersistentReleaseMessage:
90 return MediaKeys::PERSISTENT_RELEASE_MESSAGE_SESSION;
91 case blink::WebEncryptedMediaSessionType::Unknown:
92 break;
95 NOTREACHED();
96 return MediaKeys::TEMPORARY_SESSION;
99 static bool SanitizeInitData(EmeInitDataType init_data_type,
100 const unsigned char* init_data,
101 size_t init_data_length,
102 std::vector<uint8>* sanitized_init_data,
103 std::string* error_message) {
104 if (init_data_length > limits::kMaxInitDataLength) {
105 error_message->assign("Initialization data too long.");
106 return false;
109 switch (init_data_type) {
110 case EmeInitDataType::WEBM:
111 sanitized_init_data->assign(init_data, init_data + init_data_length);
112 return true;
114 case EmeInitDataType::CENC:
115 sanitized_init_data->assign(init_data, init_data + init_data_length);
116 if (!ValidatePsshInput(*sanitized_init_data)) {
117 error_message->assign("Initialization data for CENC is incorrect.");
118 return false;
120 return true;
122 case EmeInitDataType::KEYIDS: {
123 // Extract the keys and then rebuild the message. This ensures that any
124 // extra data in the provided JSON is dropped.
125 std::string init_data_string(init_data, init_data + init_data_length);
126 KeyIdList key_ids;
127 if (!ExtractKeyIdsFromKeyIdsInitData(init_data_string, &key_ids,
128 error_message))
129 return false;
131 for (const auto& key_id : key_ids) {
132 if (key_id.size() < limits::kMinKeyIdLength ||
133 key_id.size() > limits::kMaxKeyIdLength) {
134 error_message->assign("Incorrect key size.");
135 return false;
139 CreateKeyIdsInitData(key_ids, sanitized_init_data);
140 return true;
143 case EmeInitDataType::UNKNOWN:
144 break;
147 NOTREACHED();
148 error_message->assign("Initialization data type is not supported.");
149 return false;
152 static bool SanitizeSessionId(const blink::WebString& session_id,
153 std::string* sanitized_session_id) {
154 // The user agent should thoroughly validate the sessionId value before
155 // passing it to the CDM. At a minimum, this should include checking that
156 // the length and value (e.g. alphanumeric) are reasonable.
157 if (!base::IsStringASCII(session_id))
158 return false;
160 sanitized_session_id->assign(base::UTF16ToASCII(session_id));
161 if (sanitized_session_id->length() > limits::kMaxSessionIdLength)
162 return false;
164 for (const char c : *sanitized_session_id) {
165 if (!IsAsciiAlpha(c) && !IsAsciiDigit(c))
166 return false;
169 return true;
172 static bool SanitizeResponse(const std::string& key_system,
173 const uint8* response,
174 size_t response_length,
175 std::vector<uint8>* sanitized_response) {
176 // The user agent should thoroughly validate the response before passing it
177 // to the CDM. This may include verifying values are within reasonable limits,
178 // stripping irrelevant data or fields, pre-parsing it, sanitizing it,
179 // and/or generating a fully sanitized version. The user agent should check
180 // that the length and values of fields are reasonable. Unknown fields should
181 // be rejected or removed.
182 if (response_length > limits::kMaxSessionResponseLength)
183 return false;
185 if (IsClearKey(key_system) || IsExternalClearKey(key_system)) {
186 std::string key_string(response, response + response_length);
187 KeyIdAndKeyPairs keys;
188 MediaKeys::SessionType session_type = MediaKeys::TEMPORARY_SESSION;
189 if (!ExtractKeysFromJWKSet(key_string, &keys, &session_type))
190 return false;
192 // Must contain at least one key.
193 if (keys.empty())
194 return false;
196 for (const auto key_pair : keys) {
197 if (key_pair.first.size() < limits::kMinKeyIdLength ||
198 key_pair.first.size() > limits::kMaxKeyIdLength) {
199 return false;
203 std::string sanitized_data = GenerateJWKSet(keys, session_type);
204 sanitized_response->assign(sanitized_data.begin(), sanitized_data.end());
205 return true;
208 // TODO(jrummell): Verify responses for Widevine.
209 sanitized_response->assign(response, response + response_length);
210 return true;
213 WebContentDecryptionModuleSessionImpl::WebContentDecryptionModuleSessionImpl(
214 const scoped_refptr<CdmSessionAdapter>& adapter)
215 : adapter_(adapter), is_closed_(false), weak_ptr_factory_(this) {
218 WebContentDecryptionModuleSessionImpl::
219 ~WebContentDecryptionModuleSessionImpl() {
220 if (!session_id_.empty())
221 adapter_->UnregisterSession(session_id_);
224 void WebContentDecryptionModuleSessionImpl::setClientInterface(Client* client) {
225 client_ = client;
228 blink::WebString WebContentDecryptionModuleSessionImpl::sessionId() const {
229 return blink::WebString::fromUTF8(session_id_);
232 void WebContentDecryptionModuleSessionImpl::initializeNewSession(
233 blink::WebEncryptedMediaInitDataType init_data_type,
234 const unsigned char* init_data,
235 size_t init_data_length,
236 blink::WebEncryptedMediaSessionType session_type,
237 blink::WebContentDecryptionModuleResult result) {
238 DCHECK(init_data);
239 DCHECK(session_id_.empty());
241 // From https://w3c.github.io/encrypted-media/#generateRequest.
242 // 5. If the Key System implementation represented by this object's cdm
243 // implementation value does not support initDataType as an Initialization
244 // Data Type, return a promise rejected with a new DOMException whose name
245 // is NotSupportedError. String comparison is case-sensitive.
246 EmeInitDataType eme_init_data_type = ConvertToEmeInitDataType(init_data_type);
247 if (!IsSupportedKeySystemWithInitDataType(adapter_->GetKeySystem(),
248 eme_init_data_type)) {
249 std::string message =
250 "The initialization data type is not supported by the key system.";
251 result.completeWithError(
252 blink::WebContentDecryptionModuleExceptionNotSupportedError, 0,
253 blink::WebString::fromUTF8(message));
254 return;
257 // 9.1 If the init data is not valid for initDataType, reject promise with a
258 // new DOMException whose name is InvalidAccessError.
259 // 9.2 Let sanitized init data be a validated and sanitized version of init
260 // data. The user agent must thoroughly validate the Initialization Data
261 // before passing it to the CDM. This includes verifying that the length
262 // and values of fields are reasonable, verifying that values are within
263 // reasonable limits, and stripping irrelevant, unsupported, or unknown
264 // data or fields. It is recommended that user agents pre-parse, sanitize,
265 // and/or generate a fully sanitized version of the Initialization Data.
266 // If the Initialization Data format specified by initDataType support
267 // multiple entries, the user agent should remove entries that are not
268 // needed by the CDM.
269 // 9.3 If the previous step failed, reject promise with a new DOMException
270 // whose name is InvalidAccessError.
271 std::vector<uint8> sanitized_init_data;
272 std::string message;
273 if (!SanitizeInitData(eme_init_data_type, init_data, init_data_length,
274 &sanitized_init_data, &message)) {
275 result.completeWithError(
276 blink::WebContentDecryptionModuleExceptionInvalidAccessError, 0,
277 blink::WebString::fromUTF8(message));
278 return;
281 // 9.4 Let session id be the empty string.
282 // (Done in constructor.)
284 // 9.5 Let message be null.
285 // (Done by CDM.)
287 // 9.6 Let cdm be the CDM instance represented by this object's cdm
288 // instance value.
289 // 9.7 Use the cdm to execute the following steps:
290 adapter_->InitializeNewSession(
291 eme_init_data_type, sanitized_init_data, convertSessionType(session_type),
292 scoped_ptr<NewSessionCdmPromise>(new NewSessionCdmResultPromise(
293 result, adapter_->GetKeySystemUMAPrefix() + kGenerateRequestUMAName,
294 base::Bind(
295 &WebContentDecryptionModuleSessionImpl::OnSessionInitialized,
296 base::Unretained(this)))));
299 void WebContentDecryptionModuleSessionImpl::load(
300 const blink::WebString& session_id,
301 blink::WebContentDecryptionModuleResult result) {
302 DCHECK(!session_id.isEmpty());
303 DCHECK(session_id_.empty());
305 std::string sanitized_session_id;
306 if (!SanitizeSessionId(session_id, &sanitized_session_id)) {
307 result.completeWithError(
308 blink::WebContentDecryptionModuleExceptionInvalidAccessError, 0,
309 "Invalid session ID.");
310 return;
313 // TODO(jrummell): Now that there are 2 types of persistent sessions, the
314 // session type should be passed from blink. Type should also be passed in the
315 // constructor (and removed from initializeNewSession()).
316 adapter_->LoadSession(
317 MediaKeys::PERSISTENT_LICENSE_SESSION, sanitized_session_id,
318 scoped_ptr<NewSessionCdmPromise>(new NewSessionCdmResultPromise(
319 result, adapter_->GetKeySystemUMAPrefix() + kLoadSessionUMAName,
320 base::Bind(
321 &WebContentDecryptionModuleSessionImpl::OnSessionInitialized,
322 base::Unretained(this)))));
325 void WebContentDecryptionModuleSessionImpl::update(
326 const uint8* response,
327 size_t response_length,
328 blink::WebContentDecryptionModuleResult result) {
329 DCHECK(response);
330 DCHECK(!session_id_.empty());
332 std::vector<uint8> sanitized_response;
333 if (!SanitizeResponse(adapter_->GetKeySystem(), response, response_length,
334 &sanitized_response)) {
335 result.completeWithError(
336 blink::WebContentDecryptionModuleExceptionInvalidAccessError, 0,
337 "Invalid response.");
338 return;
341 adapter_->UpdateSession(
342 session_id_, sanitized_response,
343 scoped_ptr<SimpleCdmPromise>(new CdmResultPromise<>(
344 result, adapter_->GetKeySystemUMAPrefix() + kUpdateSessionUMAName)));
347 void WebContentDecryptionModuleSessionImpl::close(
348 blink::WebContentDecryptionModuleResult result) {
349 DCHECK(!session_id_.empty());
350 adapter_->CloseSession(
351 session_id_,
352 scoped_ptr<SimpleCdmPromise>(new CdmResultPromise<>(
353 result, adapter_->GetKeySystemUMAPrefix() + kCloseSessionUMAName)));
356 void WebContentDecryptionModuleSessionImpl::remove(
357 blink::WebContentDecryptionModuleResult result) {
358 DCHECK(!session_id_.empty());
359 adapter_->RemoveSession(
360 session_id_,
361 scoped_ptr<SimpleCdmPromise>(new CdmResultPromise<>(
362 result, adapter_->GetKeySystemUMAPrefix() + kRemoveSessionUMAName)));
365 void WebContentDecryptionModuleSessionImpl::OnSessionMessage(
366 MediaKeys::MessageType message_type,
367 const std::vector<uint8>& message) {
368 DCHECK(client_) << "Client not set before message event";
369 client_->message(convertMessageType(message_type), vector_as_array(&message),
370 message.size());
373 void WebContentDecryptionModuleSessionImpl::OnSessionKeysChange(
374 bool has_additional_usable_key,
375 CdmKeysInfo keys_info) {
376 blink::WebVector<blink::WebEncryptedMediaKeyInformation> keys(
377 keys_info.size());
378 for (size_t i = 0; i < keys_info.size(); ++i) {
379 const auto& key_info = keys_info[i];
380 keys[i].setId(blink::WebData(reinterpret_cast<char*>(&key_info->key_id[0]),
381 key_info->key_id.size()));
382 keys[i].setStatus(convertStatus(key_info->status));
383 keys[i].setSystemCode(key_info->system_code);
386 // Now send the event to blink.
387 client_->keysStatusesChange(keys, has_additional_usable_key);
390 void WebContentDecryptionModuleSessionImpl::OnSessionExpirationUpdate(
391 const base::Time& new_expiry_time) {
392 client_->expirationChanged(new_expiry_time.ToJsTime());
395 void WebContentDecryptionModuleSessionImpl::OnSessionClosed() {
396 if (is_closed_)
397 return;
399 is_closed_ = true;
400 client_->close();
403 blink::WebContentDecryptionModuleResult::SessionStatus
404 WebContentDecryptionModuleSessionImpl::OnSessionInitialized(
405 const std::string& session_id) {
406 // CDM will return NULL if the session to be loaded can't be found.
407 if (session_id.empty())
408 return blink::WebContentDecryptionModuleResult::SessionNotFound;
410 DCHECK(session_id_.empty()) << "Session ID may not be changed once set.";
411 session_id_ = session_id;
412 return adapter_->RegisterSession(session_id_, weak_ptr_factory_.GetWeakPtr())
413 ? blink::WebContentDecryptionModuleResult::NewSession
414 : blink::WebContentDecryptionModuleResult::SessionAlreadyExists;
417 } // namespace media