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 | |
47 | namespace WebCore { |
48 | |
49 | bool 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 | |
58 | Ref<CDM> CDM::create(Document& document, const String& keySystem) |
59 | { |
60 | return adoptRef(*new CDM(document, keySystem)); |
61 | } |
62 | |
63 | CDM::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 | |
76 | CDM::~CDM() = default; |
77 | |
78 | void 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 | |
93 | void 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 | |
143 | bool 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 | |
165 | Optional<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 | |
404 | Optional<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 | |
502 | void 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 | |
587 | void CDM::loadAndInitialize() |
588 | { |
589 | if (m_private) |
590 | m_private->loadAndInitialize(); |
591 | } |
592 | |
593 | RefPtr<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 | |
602 | bool CDM::supportsServerCertificates() const |
603 | { |
604 | return m_private && m_private->supportsServerCertificates(); |
605 | } |
606 | |
607 | bool CDM::supportsSessions() const |
608 | { |
609 | return m_private && m_private->supportsSessions(); |
610 | } |
611 | |
612 | bool CDM::supportsInitDataType(const AtomicString& initDataType) const |
613 | { |
614 | return m_private && m_private->supportsInitDataType(initDataType); |
615 | } |
616 | |
617 | RefPtr<SharedBuffer> CDM::sanitizeInitData(const AtomicString& initDataType, const SharedBuffer& initData) |
618 | { |
619 | return InitDataRegistry::shared().sanitizeInitData(initDataType, initData); |
620 | } |
621 | |
622 | bool CDM::supportsInitData(const AtomicString& initDataType, const SharedBuffer& initData) |
623 | { |
624 | return m_private && m_private->supportsInitData(initDataType, initData); |
625 | } |
626 | |
627 | RefPtr<SharedBuffer> CDM::sanitizeResponse(const SharedBuffer& response) |
628 | { |
629 | if (!m_private) |
630 | return nullptr; |
631 | return m_private->sanitizeResponse(response); |
632 | } |
633 | |
634 | Optional<String> CDM::sanitizeSessionId(const String& sessionId) |
635 | { |
636 | if (!m_private) |
637 | return WTF::nullopt; |
638 | return m_private->sanitizeSessionId(sessionId); |
639 | } |
640 | |
641 | String 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 | |