1/*
2 * Copyright (C) 2016 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "CDM.h"
28
29#if ENABLE(ENCRYPTED_MEDIA)
30
31#include "CDMFactory.h"
32#include "CDMPrivate.h"
33#include "Document.h"
34#include "InitDataRegistry.h"
35#include "MediaKeysRequirement.h"
36#include "MediaPlayer.h"
37#include "NotImplemented.h"
38#include "Page.h"
39#include "ParsedContentType.h"
40#include "ScriptExecutionContext.h"
41#include "SecurityOrigin.h"
42#include "SecurityOriginData.h"
43#include "Settings.h"
44#include <wtf/FileSystem.h>
45#include <wtf/NeverDestroyed.h>
46
47namespace WebCore {
48
49bool CDM::supportsKeySystem(const String& keySystem)
50{
51 for (auto* factory : CDMFactory::registeredFactories()) {
52 if (factory->supportsKeySystem(keySystem))
53 return true;
54 }
55 return false;
56}
57
58Ref<CDM> CDM::create(Document& document, const String& keySystem)
59{
60 return adoptRef(*new CDM(document, keySystem));
61}
62
63CDM::CDM(Document& document, const String& keySystem)
64 : ContextDestructionObserver(&document)
65 , m_keySystem(keySystem)
66{
67 ASSERT(supportsKeySystem(keySystem));
68 for (auto* factory : CDMFactory::registeredFactories()) {
69 if (factory->supportsKeySystem(keySystem)) {
70 m_private = factory->createCDM(keySystem);
71 break;
72 }
73 }
74}
75
76CDM::~CDM() = default;
77
78void CDM::getSupportedConfiguration(MediaKeySystemConfiguration&& candidateConfiguration, SupportedConfigurationCallback&& callback)
79{
80 // https://w3c.github.io/encrypted-media/#get-supported-configuration
81 // W3C Editor's Draft 09 November 2016
82
83 // 3.1.1.1 Get Supported Configuration
84 // Given a Key Systems implementation implementation, MediaKeySystemConfiguration candidate configuration, and origin,
85 // this algorithm returns a supported configuration or NotSupported as appropriate.
86
87 // 1. Let supported configuration be ConsentDenied.
88 // 2. Initialize restrictions to indicate that no configurations have had user consent denied.
89 MediaKeysRestrictions restrictions { };
90 doSupportedConfigurationStep(WTFMove(candidateConfiguration), WTFMove(restrictions), WTFMove(callback));
91}
92
93void CDM::doSupportedConfigurationStep(MediaKeySystemConfiguration&& candidateConfiguration, MediaKeysRestrictions&& restrictions, SupportedConfigurationCallback&& callback)
94{
95 // https://w3c.github.io/encrypted-media/#get-supported-configuration
96 // W3C Editor's Draft 09 November 2016, ctd.
97
98 // 3.1.1.1 Get Supported Configuration
99 // 3. Repeat the following step while supported configuration is ConsentDenied:
100 // 3.1. Let supported configuration and, if provided, restrictions be the result of executing the
101 // Get Supported Configuration and Consent algorithm with implementation, candidate configuration,
102 // restrictions and origin.
103 auto optionalConfiguration = getSupportedConfiguration(candidateConfiguration, restrictions);
104 if (!optionalConfiguration) {
105 callback(WTF::nullopt);
106 return;
107 }
108
109 auto consentCallback = [weakThis = makeWeakPtr(*this), callback = WTFMove(callback)] (ConsentStatus status, MediaKeySystemConfiguration&& configuration, MediaKeysRestrictions&& restrictions) mutable {
110 if (!weakThis) {
111 callback(WTF::nullopt);
112 return;
113 }
114 // 3.1.1.2 Get Supported Configuration and Consent, ctd.
115 // 22. Let consent status and updated restrictions be the result of running the Get Consent Status algorithm on accumulated configuration,
116 // restrictions and origin and follow the steps for the value of consent status from the following list:
117 switch (status) {
118 case ConsentStatus::ConsentDenied:
119 // ↳ ConsentDenied:
120 // Return ConsentDenied and updated restrictions.
121 weakThis->doSupportedConfigurationStep(WTFMove(configuration), WTFMove(restrictions), WTFMove(callback));
122 return;
123
124 case ConsentStatus::InformUser:
125 // ↳ InformUser
126 // Inform the user that accumulated configuration is in use in the origin including, specifically, the information that
127 // Distinctive Identifier(s) and/or Distinctive Permanent Identifier(s) as appropriate will be used if the
128 // distinctiveIdentifier member of accumulated configuration is "required". Continue to the next step.
129 // NOTE: Implement.
130 break;
131
132 case ConsentStatus::Allowed:
133 // ↳ Allowed:
134 // Continue to the next step.
135 break;
136 }
137 // 23. Return accumulated configuration.
138 callback(WTFMove(configuration));
139 };
140 getConsentStatus(WTFMove(optionalConfiguration.value()), WTFMove(restrictions), WTFMove(consentCallback));
141}
142
143bool CDM::isPersistentType(MediaKeySessionType sessionType)
144{
145 // https://w3c.github.io/encrypted-media/#is-persistent-session-type
146 // W3C Editor's Draft 09 November 2016
147
148 // 5.1.1. Is persistent session type?
149 // 1. Let the session type be the specified MediaKeySessionType value.
150 // 2. Follow the steps for the value of session type from the following list:
151 switch (sessionType) {
152 case MediaKeySessionType::Temporary:
153 // ↳ "temporary"
154 return false;
155 case MediaKeySessionType::PersistentLicense:
156 case MediaKeySessionType::PersistentUsageRecord:
157 // ↳ "persistent-license"
158 return true;
159 }
160
161 ASSERT_NOT_REACHED();
162 return false;
163}
164
165Optional<MediaKeySystemConfiguration> CDM::getSupportedConfiguration(const MediaKeySystemConfiguration& candidateConfiguration, MediaKeysRestrictions& restrictions)
166{
167 // https://w3c.github.io/encrypted-media/#get-supported-configuration-and-consent
168 // W3C Editor's Draft 09 November 2016
169
170 ASSERT(m_private);
171 if (!m_private)
172 return WTF::nullopt;
173
174 // 3.1.1.2 Get Supported Configuration and Consent
175 // Given a Key Systems implementation implementation, MediaKeySystemConfiguration candidate configuration,
176 // restrictions and origin, this algorithm returns a supported configuration, NotSupported, or ConsentDenied
177 // as appropriate and, in the ConsentDenied case, restrictions.
178
179 // 1. Let accumulated configuration be a new MediaKeySystemConfiguration dictionary.
180 MediaKeySystemConfiguration accumulatedConfiguration { };
181
182 // 2. Set the label member of accumulated configuration to equal the label member of candidate configuration.
183 accumulatedConfiguration.label = candidateConfiguration.label;
184
185 // 3. If the initDataTypes member of candidate configuration is non-empty, run the following steps:
186 if (!candidateConfiguration.initDataTypes.isEmpty()) {
187 // 3.1. Let supported types be an empty sequence of DOMStrings.
188 Vector<String> supportedTypes;
189
190 // 3.2. For each value in candidate configuration's initDataTypes member:
191 for (auto initDataType : candidateConfiguration.initDataTypes) {
192 // 3.2.1. Let initDataType be the value.
193 // 3.2.2. If the implementation supports generating requests based on initDataType, add initDataType
194 // to supported types. String comparison is case-sensitive. The empty string is never supported.
195 if (initDataType.isEmpty())
196 continue;
197
198 if (m_private && m_private->supportsInitDataType(initDataType))
199 supportedTypes.append(initDataType);
200 }
201
202 // 3.3. If supported types is empty, return NotSupported.
203 if (supportedTypes.isEmpty())
204 return WTF::nullopt;
205
206 // 3.4. Set the initDataTypes member of accumulated configuration to supported types.
207 accumulatedConfiguration.initDataTypes = WTFMove(supportedTypes);
208 }
209
210 // 4. Let distinctive identifier requirement be the value of candidate configuration's distinctiveIdentifier member.
211 MediaKeysRequirement distinctiveIdentifierRequirement = candidateConfiguration.distinctiveIdentifier;
212
213 // 5. If distinctive identifier requirement is "optional" and Distinctive Identifiers are not allowed according to
214 // restrictions, set distinctive identifier requirement to "not-allowed".
215 if (distinctiveIdentifierRequirement == MediaKeysRequirement::Optional && restrictions.distinctiveIdentifierDenied)
216 distinctiveIdentifierRequirement = MediaKeysRequirement::NotAllowed;
217
218 // 6. Follow the steps for distinctive identifier requirement from the following list:
219 switch (distinctiveIdentifierRequirement) {
220 case MediaKeysRequirement::Required:
221 // ↳ "required"
222 // If the implementation does not support use of Distinctive Identifier(s) in combination
223 // with accumulated configuration and restrictions, return NotSupported.
224 if (m_private->distinctiveIdentifiersRequirement(accumulatedConfiguration, restrictions) == MediaKeysRequirement::NotAllowed)
225 return WTF::nullopt;
226 break;
227
228 case MediaKeysRequirement::Optional:
229 // ↳ "optional"
230 // Continue with the following steps.
231 break;
232
233 case MediaKeysRequirement::NotAllowed:
234 // ↳ "not-allowed"
235 // If the implementation requires use Distinctive Identifier(s) or Distinctive Permanent Identifier(s)
236 // in combination with accumulated configuration and restrictions, return NotSupported.
237 if (m_private->distinctiveIdentifiersRequirement(accumulatedConfiguration, restrictions) == MediaKeysRequirement::Required)
238 return WTF::nullopt;
239 break;
240 }
241
242 // 7. Set the distinctiveIdentifier member of accumulated configuration to equal distinctive identifier requirement.
243 accumulatedConfiguration.distinctiveIdentifier = distinctiveIdentifierRequirement;
244
245 // 8. Let persistent state requirement be equal to the value of candidate configuration's persistentState member.
246 MediaKeysRequirement persistentStateRequirement = candidateConfiguration.persistentState;
247
248 // 9. If persistent state requirement is "optional" and persisting state is not allowed according to restrictions,
249 // set persistent state requirement to "not-allowed".
250 if (persistentStateRequirement == MediaKeysRequirement::Optional && restrictions.persistentStateDenied)
251 persistentStateRequirement = MediaKeysRequirement::NotAllowed;
252
253 // 10. Follow the steps for persistent state requirement from the following list:
254 switch (persistentStateRequirement) {
255 case MediaKeysRequirement::Required:
256 // ↳ "required"
257 // If the implementation does not support persisting state in combination with accumulated configuration
258 // and restrictions, return NotSupported.
259 if (m_private->persistentStateRequirement(accumulatedConfiguration, restrictions) == MediaKeysRequirement::NotAllowed)
260 return WTF::nullopt;
261 break;
262
263 case MediaKeysRequirement::Optional:
264 // ↳ "optional"
265 // Continue with the following steps.
266 break;
267
268 case MediaKeysRequirement::NotAllowed:
269 // ↳ "not-allowed"
270 // If the implementation requires persisting state in combination with accumulated configuration
271 // and restrictions, return NotSupported
272 if (m_private->persistentStateRequirement(accumulatedConfiguration, restrictions) == MediaKeysRequirement::Required)
273 return WTF::nullopt;
274 break;
275 }
276
277 // 11. Set the persistentState member of accumulated configuration to equal the value of persistent state requirement.
278 accumulatedConfiguration.persistentState = persistentStateRequirement;
279
280 // 12. Follow the steps for the first matching condition from the following list:
281 Vector<MediaKeySessionType> sessionTypes;
282
283 if (!candidateConfiguration.sessionTypes.isEmpty()) {
284 // ↳ If the sessionTypes member is present [WebIDL] in candidate configuration
285 // Let session types be candidate configuration's sessionTypes member.
286 sessionTypes = candidateConfiguration.sessionTypes;
287 } else {
288 // ↳ Otherwise
289 // Let session types be [ "temporary" ].
290 sessionTypes = { MediaKeySessionType::Temporary };
291 }
292
293 // 13. For each value in session types:
294 for (auto& sessionType : sessionTypes) {
295 // 13.1. Let session type be the value.
296 // 13.2. If accumulated configuration's persistentState value is "not-allowed" and the
297 // Is persistent session type? algorithm returns true for session type return NotSupported.
298 if (accumulatedConfiguration.persistentState == MediaKeysRequirement::NotAllowed && isPersistentType(sessionType))
299 return WTF::nullopt;
300
301 // 13.3. If the implementation does not support session type in combination with accumulated configuration
302 // and restrictions for other reasons, return NotSupported.
303 if (!m_private->supportsSessionTypeWithConfiguration(sessionType, accumulatedConfiguration))
304 return WTF::nullopt;
305
306 // 13.4 If accumulated configuration's persistentState value is "optional" and the result of running the Is
307 // persistent session type? algorithm on session type is true, change accumulated configuration's persistentState
308 // value to "required".
309 if (accumulatedConfiguration.persistentState == MediaKeysRequirement::Optional && isPersistentType(sessionType))
310 accumulatedConfiguration.persistentState = MediaKeysRequirement::Required;
311 }
312
313 // 14. Set the sessionTypes member of accumulated configuration to session types.
314 accumulatedConfiguration.sessionTypes = sessionTypes;
315
316 // 15. If the videoCapabilities and audioCapabilities members in candidate configuration are both empty, return NotSupported.
317 if (candidateConfiguration.videoCapabilities.isEmpty() && candidateConfiguration.audioCapabilities.isEmpty())
318 return WTF::nullopt;
319
320 // 16. ↳ If the videoCapabilities member in candidate configuration is non-empty:
321 if (!candidateConfiguration.videoCapabilities.isEmpty()) {
322 // 16.1. Let video capabilities be the result of executing the Get Supported Capabilities for Audio/Video Type algorithm on
323 // Video, candidate configuration's videoCapabilities member, accumulated configuration, and restrictions.
324 auto videoCapabilities = getSupportedCapabilitiesForAudioVideoType(AudioVideoType::Video, candidateConfiguration.videoCapabilities, accumulatedConfiguration, restrictions);
325
326 // 16.2. If video capabilities is null, return NotSupported.
327 if (!videoCapabilities)
328 return WTF::nullopt;
329
330 // 16.3 Set the videoCapabilities member of accumulated configuration to video capabilities.
331 accumulatedConfiguration.videoCapabilities = WTFMove(videoCapabilities.value());
332 } else {
333 // 16. ↳ Otherwise:
334 // Set the videoCapabilities member of accumulated configuration to an empty sequence.
335 accumulatedConfiguration.videoCapabilities = { };
336 }
337
338 // 17. ↳ If the audioCapabilities member in candidate configuration is non-empty:
339 if (!candidateConfiguration.audioCapabilities.isEmpty()) {
340 // 17.1. Let audio capabilities be the result of executing the Get Supported Capabilities for Audio/Video Type algorithm on
341 // Audio, candidate configuration's audioCapabilities member, accumulated configuration, and restrictions.
342 auto audioCapabilities = getSupportedCapabilitiesForAudioVideoType(AudioVideoType::Audio, candidateConfiguration.audioCapabilities, accumulatedConfiguration, restrictions);
343
344 // 17.2. If audio capabilities is null, return NotSupported.
345 if (!audioCapabilities)
346 return WTF::nullopt;
347
348 // 17.3 Set the audioCapabilities member of accumulated configuration to audio capabilities.
349 accumulatedConfiguration.audioCapabilities = WTFMove(audioCapabilities.value());
350 } else {
351 // 17. ↳ Otherwise:
352 // Set the audioCapabilities member of accumulated configuration to an empty sequence.
353 accumulatedConfiguration.audioCapabilities = { };
354 }
355
356 // 18. If accumulated configuration's distinctiveIdentifier value is "optional", follow the steps for the first matching
357 // condition from the following list:
358 if (accumulatedConfiguration.distinctiveIdentifier == MediaKeysRequirement::Optional) {
359 // ↳ If the implementation requires use Distinctive Identifier(s) or Distinctive Permanent Identifier(s) for any of the
360 // combinations in accumulated configuration
361 if (m_private->distinctiveIdentifiersRequirement(accumulatedConfiguration, restrictions) == MediaKeysRequirement::Required) {
362 // Change accumulated configuration's distinctiveIdentifier value to "required".
363 accumulatedConfiguration.distinctiveIdentifier = MediaKeysRequirement::Required;
364 } else {
365 // ↳ Otherwise
366 // Change accumulated configuration's distinctiveIdentifier value to "not-allowed".
367 accumulatedConfiguration.distinctiveIdentifier = MediaKeysRequirement::NotAllowed;
368 }
369 }
370
371 // 19. If accumulated configuration's persistentState value is "optional", follow the steps for the first matching
372 // condition from the following list:
373 if (accumulatedConfiguration.persistentState == MediaKeysRequirement::Optional) {
374 // ↳ If the implementation requires persisting state for any of the combinations in accumulated configuration
375 if (m_private->persistentStateRequirement(accumulatedConfiguration, restrictions) == MediaKeysRequirement::Required) {
376 // Change accumulated configuration's persistentState value to "required".
377 accumulatedConfiguration.persistentState = MediaKeysRequirement::Required;
378 } else {
379 // ↳ Otherwise
380 // Change accumulated configuration's persistentState value to "not-allowed".
381 accumulatedConfiguration.persistentState = MediaKeysRequirement::NotAllowed;
382 }
383 }
384
385 // 20. If implementation in the configuration specified by the combination of the values in accumulated configuration
386 // is not supported or not allowed in the origin, return NotSupported.
387 if (!m_private->supportsConfiguration(accumulatedConfiguration))
388 return WTF::nullopt;
389
390 Document* document = downcast<Document>(m_scriptExecutionContext);
391 if (!document)
392 return WTF::nullopt;
393
394 SecurityOrigin& origin = document->securityOrigin();
395 SecurityOrigin& topOrigin = document->topOrigin();
396
397 if ((accumulatedConfiguration.distinctiveIdentifier == MediaKeysRequirement::Required || accumulatedConfiguration.persistentState == MediaKeysRequirement::Required) && !origin.canAccessLocalStorage(&topOrigin))
398 return WTF::nullopt;
399
400 return accumulatedConfiguration;
401 // NOTE: Continued in getConsentStatus().
402}
403
404Optional<Vector<MediaKeySystemMediaCapability>> CDM::getSupportedCapabilitiesForAudioVideoType(CDM::AudioVideoType type, const Vector<MediaKeySystemMediaCapability>& requestedCapabilities, const MediaKeySystemConfiguration& partialConfiguration, MediaKeysRestrictions& restrictions)
405{
406 // https://w3c.github.io/encrypted-media/#get-supported-capabilities-for-audio-video-type
407 // W3C Editor's Draft 09 November 2016
408
409 ASSERT(m_private);
410 if (!m_private)
411 return WTF::nullopt;
412
413 // 3.1.1.3 Get Supported Capabilities for Audio/Video Type
414
415 // Given an audio/video type, MediaKeySystemMediaCapability sequence requested media capabilities, MediaKeySystemConfiguration
416 // partial configuration, and restrictions, this algorithm returns a sequence of supported MediaKeySystemMediaCapability values
417 // for this audio/video type or null as appropriate.
418
419 // 1. Let local accumulated configuration be a local copy of partial configuration.
420 MediaKeySystemConfiguration accumulatedConfiguration = partialConfiguration;
421
422 // 2. Let supported media capabilities be an empty sequence of MediaKeySystemMediaCapability dictionaries.
423 Vector<MediaKeySystemMediaCapability> supportedMediaCapabilities { };
424
425 // 3. For each requested media capability in requested media capabilities:
426 for (auto& requestedCapability : requestedCapabilities) {
427 // 3.1. Let content type be requested media capability's contentType member.
428 // 3.2. Let robustness be requested media capability's robustness member.
429 String robustness = requestedCapability.robustness;
430
431 // 3.3. If content type is the empty string, return null.
432 if (requestedCapability.contentType.isEmpty())
433 return WTF::nullopt;
434
435 // 3.4. If content type is an invalid or unrecognized MIME type, continue to the next iteration.
436 Optional<ParsedContentType> contentType = ParsedContentType::create(requestedCapability.contentType, Mode::Rfc2045);
437 if (!contentType)
438 continue;
439
440 // 3.5. Let container be the container type specified by content type.
441 String container = contentType->mimeType();
442
443 // 3.6. If the user agent does not support container, continue to the next iteration. The case-sensitivity
444 // of string comparisons is determined by the appropriate RFC.
445 // 3.7. Let parameters be the RFC 6381 [RFC6381] parameters, if any, specified by content type.
446 // 3.8. If the user agent does not recognize one or more parameters, continue to the next iteration.
447 // 3.9. Let media types be the set of codecs and codec constraints specified by parameters. The case-sensitivity
448 // of string comparisons is determined by the appropriate RFC or other specification.
449 String codecs = contentType->parameterValueForName("codecs");
450 if (contentType->parameterCount() > (codecs.isEmpty() ? 0 : 1))
451 continue;
452
453 // 3.10. If media types is empty:
454 if (codecs.isEmpty()) {
455 // ↳ If container normatively implies a specific set of codecs and codec constraints:
456 // ↳ Otherwise:
457 notImplemented();
458 }
459
460 // 3.11. If content type is not strictly a audio/video type, continue to the next iteration.
461 // 3.12. If robustness is not the empty string and contains an unrecognized value or a value not supported by
462 // implementation, continue to the next iteration. String comparison is case-sensitive.
463 if (!robustness.isEmpty() && !m_private->supportsRobustness(robustness))
464 continue;
465
466 // 3.13. If the user agent and implementation definitely support playback of encrypted media data for the
467 // combination of container, media types, robustness and local accumulated configuration in combination
468 // with restrictions:
469 MediaEngineSupportParameters parameters;
470 parameters.type = ContentType(contentType->mimeType());
471 if (!MediaPlayer::supportsType(parameters)) {
472 // Try with Media Source:
473 parameters.isMediaSource = true;
474 if (!MediaPlayer::supportsType(parameters))
475 continue;
476 }
477
478 if (!m_private->supportsConfigurationWithRestrictions(accumulatedConfiguration, restrictions))
479 continue;
480
481 // 3.13.1. Add requested media capability to supported media capabilities.
482 supportedMediaCapabilities.append(requestedCapability);
483
484 // 3.13.2. ↳ If audio/video type is Video:
485 // Add requested media capability to the videoCapabilities member of local accumulated configuration.
486 if (type == AudioVideoType::Video)
487 accumulatedConfiguration.videoCapabilities.append(requestedCapability);
488 // 3.13.2. ↳ If audio/video type is Audio:
489 // Add requested media capability to the audioCapabilities member of local accumulated configuration.
490 else
491 accumulatedConfiguration.audioCapabilities.append(requestedCapability);
492 }
493
494 // 4. If supported media capabilities is empty, return null.
495 if (supportedMediaCapabilities.isEmpty())
496 return WTF::nullopt;
497
498 // 5. Return supported media capabilities.
499 return supportedMediaCapabilities;
500}
501
502void CDM::getConsentStatus(MediaKeySystemConfiguration&& accumulatedConfiguration, MediaKeysRestrictions&& restrictions, ConsentStatusCallback&& callback)
503{
504 // https://w3c.github.io/encrypted-media/#get-supported-configuration-and-consent
505 // W3C Editor's Draft 09 November 2016
506 if (!m_scriptExecutionContext) {
507 callback(ConsentStatus::ConsentDenied, WTFMove(accumulatedConfiguration), WTFMove(restrictions));
508 return;
509 }
510
511 // NOTE: In the future, these checks belowe will involve asking the page client, possibly across a process boundary.
512 // They will by necessity be asynchronous with callbacks. For now, imply this behavior by performing it in an async task.
513
514 m_scriptExecutionContext->postTask([this, weakThis = makeWeakPtr(*this), accumulatedConfiguration = WTFMove(accumulatedConfiguration), restrictions = WTFMove(restrictions), callback = WTFMove(callback)] (ScriptExecutionContext&) mutable {
515 if (!weakThis || !m_private) {
516 callback(ConsentStatus::ConsentDenied, WTFMove(accumulatedConfiguration), WTFMove(restrictions));
517 return;
518 }
519
520 Document* document = downcast<Document>(m_scriptExecutionContext);
521 if (!document) {
522 callback(ConsentStatus::ConsentDenied, WTFMove(accumulatedConfiguration), WTFMove(restrictions));
523 return;
524 }
525
526 SecurityOrigin& origin = document->securityOrigin();
527 SecurityOrigin& topOrigin = document->topOrigin();
528
529 // 3.1.1.2 Get Supported Configuration and Consent, ctd.
530 // 21. If accumulated configuration's distinctiveIdentifier value is "required" and the Distinctive Identifier(s) associated
531 // with accumulated configuration are not unique per origin and profile and clearable:
532 if (accumulatedConfiguration.distinctiveIdentifier == MediaKeysRequirement::Required && !m_private->distinctiveIdentifiersAreUniquePerOriginAndClearable(accumulatedConfiguration)) {
533 // 21.1. Update restrictions to reflect that all configurations described by accumulated configuration do not have user consent.
534 restrictions.distinctiveIdentifierDenied = true;
535 callback(ConsentStatus::ConsentDenied, WTFMove(accumulatedConfiguration), WTFMove(restrictions));
536 return;
537 }
538
539 // https://w3c.github.io/encrypted-media/#get-consent-status
540 // 3.1.1.4 Get Consent Status
541 // Given an accumulated configuration, restrictions and origin, this algorithm returns the consent status for accumulated
542 // configuration and origin as one of ConsentDenied, InformUser or Allowed, together with an updated value for restrictions
543 // in the ConsentDenied case.
544
545 // 1. If there is persisted denial for origin indicating that accumulated configuration is not allowed, run the following steps:
546 // 1.1. Update restrictions to reflect the configurations for which consent has been denied.
547 // 1.2. Return ConsentDenied and restrictions.
548 // 2. If there is persisted consent for origin indicating accumulated configuration is allowed, return Allowed.
549 // NOTE: persisted denial / consent unimplemented.
550
551 // 3. If any of the following are true:
552 // ↳ The distinctiveIdentifier member of accumulated configuration is not "not-allowed" and the combination of the User Agent,
553 // implementation and accumulated configuration does not follow all the recommendations of Allow Persistent Data to Be Cleared
554 // with respect to Distinctive Identifier(s).
555 // NOTE: assume that implementations follow all recommendations.
556
557 // ↳ The user agent requires explicit user consent for the accumulated configuration for other reasons.
558 // NOTE: assume the user agent does not require explicit user consent.
559
560 // 3.1. Request user consent to use accumulated configuration in the origin and wait for the user response.
561 // The consent must include consent to use a Distinctive Identifier(s) and/or Distinctive Permanent Identifier(s) as appropriate
562 // if accumulated configuration's distinctiveIdentifier member is "required".
563 // 3.2. If consent was denied, run the following steps:
564 // 3.2.1. Update restrictions to reflect the configurations for which consent was denied.
565 // 3.2.1. Return ConsentDenied and restrictions.
566 // NOTE: assume implied consent if the combination of origin and topOrigin allows it.
567 if (accumulatedConfiguration.distinctiveIdentifier == MediaKeysRequirement::Required && !origin.canAccessLocalStorage(&topOrigin)) {
568 restrictions.distinctiveIdentifierDenied = true;
569 callback(ConsentStatus::ConsentDenied, WTFMove(accumulatedConfiguration), WTFMove(restrictions));
570 return;
571 }
572
573 // 4. If the distinctiveIdentifier member of accumulated configuration is not "not-allowed", return InformUser.
574 if (accumulatedConfiguration.distinctiveIdentifier != MediaKeysRequirement::NotAllowed) {
575 callback(ConsentStatus::InformUser, WTFMove(accumulatedConfiguration), WTFMove(restrictions));
576 return;
577 }
578
579 // 5. If the user agent requires informing the user for the accumulated configuration for other reasons, return InformUser.
580 // NOTE: assume the user agent does not require informing the user.
581
582 // 6. Return Allowed.
583 callback(ConsentStatus::Allowed, WTFMove(accumulatedConfiguration), WTFMove(restrictions));
584 });
585}
586
587void CDM::loadAndInitialize()
588{
589 if (m_private)
590 m_private->loadAndInitialize();
591}
592
593RefPtr<CDMInstance> CDM::createInstance()
594{
595 if (!m_private)
596 return nullptr;
597 auto instance = m_private->createInstance();
598 instance->setStorageDirectory(storageDirectory());
599 return instance;
600}
601
602bool CDM::supportsServerCertificates() const
603{
604 return m_private && m_private->supportsServerCertificates();
605}
606
607bool CDM::supportsSessions() const
608{
609 return m_private && m_private->supportsSessions();
610}
611
612bool CDM::supportsInitDataType(const AtomicString& initDataType) const
613{
614 return m_private && m_private->supportsInitDataType(initDataType);
615}
616
617RefPtr<SharedBuffer> CDM::sanitizeInitData(const AtomicString& initDataType, const SharedBuffer& initData)
618{
619 return InitDataRegistry::shared().sanitizeInitData(initDataType, initData);
620}
621
622bool CDM::supportsInitData(const AtomicString& initDataType, const SharedBuffer& initData)
623{
624 return m_private && m_private->supportsInitData(initDataType, initData);
625}
626
627RefPtr<SharedBuffer> CDM::sanitizeResponse(const SharedBuffer& response)
628{
629 if (!m_private)
630 return nullptr;
631 return m_private->sanitizeResponse(response);
632}
633
634Optional<String> CDM::sanitizeSessionId(const String& sessionId)
635{
636 if (!m_private)
637 return WTF::nullopt;
638 return m_private->sanitizeSessionId(sessionId);
639}
640
641String CDM::storageDirectory() const
642{
643 auto* document = downcast<Document>(scriptExecutionContext());
644 if (!document)
645 return emptyString();
646
647 auto* page = document->page();
648 if (!page || page->usesEphemeralSession())
649 return emptyString();
650
651 auto storageDirectory = document->settings().mediaKeysStorageDirectory();
652 if (storageDirectory.isEmpty())
653 return emptyString();
654
655 return FileSystem::pathByAppendingComponent(storageDirectory, document->securityOrigin().data().databaseIdentifier());
656}
657
658}
659
660#endif
661