| 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 | |