2025-07-31 13:09:23 +02:00
/*
* Copyright ( c ) 2025 , stelar7 < dudedbz @ gmail . com >
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
# include <LibWeb/EncryptedMediaExtensions/Algorithms.h>
# include <LibWeb/MimeSniff/MimeType.h>
namespace Web : : EncryptedMediaExtensions {
bool supports_container ( [[maybe_unused]] Utf16String const & container )
{
// FIXME: Check FFmpeg?
return true ;
}
// https://w3c.github.io/encrypted-media/#get-supported-capabilities-for-audio-video-type
Optional < Vector < Bindings : : MediaKeySystemMediaCapability > > get_supported_capabilities_for_audio_video_type ( KeySystem const & implementation , CapabilitiesType type , Vector < Bindings : : MediaKeySystemMediaCapability > requested_capabilities , Bindings : : MediaKeySystemConfiguration config , MediaKeyRestrictions restrictions )
{
// 1. Let local accumulated configuration be a local copy of accumulated configuration.
Bindings : : MediaKeySystemConfiguration accumulated_configuration = config ;
// 2. Let supported media capabilities be an empty sequence of MediaKeySystemMediaCapability dictionaries.
Vector < Bindings : : MediaKeySystemMediaCapability > supported_media_capabilities ;
// 3. For each requested media capability in requested media capabilities:
for ( auto & capability : requested_capabilities ) {
// 1. Let content type be requested media capability's contentType member.
auto const & content_type = capability . content_type ;
// 2. Let encryption scheme be requested media capability’ s encryptionScheme member.
auto const & encryption_scheme = capability . encryption_scheme ;
// 3. Let robustness be requested media capability's robustness member.
auto const & robustness = capability . robustness ;
// 4. If content type is the empty string, return null.
if ( content_type . is_empty ( ) )
return { } ;
// 5. Let mimeType be the result of running parse a MIME type with content type.
auto mime_type = MimeSniff : : MimeType : : parse ( content_type . to_utf8 ( ) ) ;
// 6. If mimeType is failure or is unrecognized, continue to the next iteration.
if ( ! mime_type . has_value ( ) )
continue ;
// 7. Let container be the container type specified by mimeType.
auto const & container = Utf16String : : from_utf8 ( mime_type - > essence ( ) ) ;
// 8. If the user agent does not support container, continue to the next iteration.
// The case-sensitivity of string comparisons is determined by the appropriate RFC.
if ( ! supports_container ( container ) )
continue ;
// 9. Let parameters be the "codecs" and "profiles" RFC 6381 [RFC6381] parameters, if any, of mimeType.
auto parameters = mime_type - > parameters ( ) ;
// FIXME: 10. If the user agent does not recognize one or more parameters, or
// if any parameters are not valid per the relevant specification, continue to the next iteration.
// 11. Let media types be the set of codecs and codec constraints specified by parameters.
// The case-sensitivity of string comparisons is determined by the appropriate RFC or other specification.
auto media_types = Utf16String : : from_utf8 ( parameters . get ( " codecs " sv ) . value_or ( { } ) ) ;
// 12. If media types is empty:
if ( media_types . is_empty ( ) ) {
// FIXME: If container normatively implies a specific set of codecs and codec constraints:
if ( false ) {
// Let parameters be that set.
}
// Otherwise:
else {
// Continue to the next iteration.
continue ;
}
}
// 13. If mimeType is not strictly an audio/video type, continue to the next iteration.
if ( ! mime_type - > is_audio_or_video ( ) )
continue ;
// 14. If encryption scheme is non-null and is not recognized or not supported by implementation,
// continue to the next iteration.
if ( encryption_scheme . has_value ( ) & & ! implementation . supports_encryption_scheme ( * encryption_scheme ) )
continue ;
// 15. If robustness is not the empty string and contains an unrecognized value or a value not supported
// by implementation, continue to the next iteration. String comparison is case-sensitive.
if ( ! robustness . is_empty ( ) & & ! implementation . supports_robustness ( robustness ) )
continue ;
// 16. If the user agent and implementation definitely support playback of encrypted media data for
// the combination of container, media types, encryption scheme, robustness and
// local accumulated configuration in combination with restrictions:
if ( implementation . definitely_supports_playback ( container , media_types , encryption_scheme , robustness , accumulated_configuration , restrictions ) ) {
// 1. Add requested media capability to supported media capabilities.
supported_media_capabilities . append ( move ( capability ) ) ;
// 2. If audio/video type is Video:
if ( type = = CapabilitiesType : : Video ) {
// Add requested media capability to the videoCapabilities member of local accumulated configuration.
accumulated_configuration . video_capabilities . append ( capability ) ;
}
// If audio/video type is Audio:
if ( type = = CapabilitiesType : : Audio ) {
// Add requested media capability to the audioCapabilities member of local accumulated configuration.
accumulated_configuration . audio_capabilities . append ( capability ) ;
}
}
}
// 4. If supported media capabilities is empty, return null.
if ( supported_media_capabilities . is_empty ( ) )
return { } ;
// 5. Return supported media capabilities.
return supported_media_capabilities ;
}
2025-07-31 13:26:32 +02:00
// https://w3c.github.io/encrypted-media/#dfn-is-persistent-session-type
bool is_persistent_session_type ( Utf16String const & session_type )
{
// 1. Let the session type be the specified MediaKeySessionType value.
// 2. Follow the steps for the value of session type from the following list:
// * "temporary"
if ( session_type = = " temporary " sv ) {
// Return false.
return false ;
}
// * "persistent-license"
if ( session_type = = " persistent-license " sv ) {
// Return true.
return true ;
}
VERIFY_NOT_REACHED ( ) ;
}
// https://w3c.github.io/encrypted-media/#get-consent-status
ConsentStatus get_consent_status ( Bindings : : MediaKeySystemConfiguration const & accumulated_configuration , MediaKeyRestrictions & restrictions , URL : : Origin const & origin )
{
// FIXME: Implement this
( void ) accumulated_configuration ;
( void ) restrictions ;
( void ) origin ;
dbgln ( " get_consent_status: Not implemented, returning Allowed by default " ) ;
return ConsentStatus : : Allowed ;
}
// https://w3c.github.io/encrypted-media/#get-supported-configuration-and-consent
Optional < ConsentConfiguration > get_supported_configuration_and_consent ( KeySystem const & implementation , Bindings : : MediaKeySystemConfiguration const & candidate_configuration , MediaKeyRestrictions & restrictions , URL : : Origin const & origin )
{
// 1. Let accumulated configuration be a new MediaKeySystemConfiguration dictionary.
Bindings : : MediaKeySystemConfiguration accumulated_configuration ;
// 2. Set the label member of accumulated configuration to equal the label member of candidate configuration.
accumulated_configuration . label = candidate_configuration . label ;
// 3. If the initDataTypes member of candidate configuration is non-empty, run the following steps:
if ( ! candidate_configuration . init_data_types . is_empty ( ) ) {
// 1. Let supported types be an empty sequence of DOMStrings.
Vector < Utf16String > supported_types ;
// 2. For each value in candidate configuration's initDataTypes member:
for ( auto const & init_data_type : candidate_configuration . init_data_types ) {
// 1. Let initDataType be the value.
// 2. If the implementation supports generating requests based on initDataType, add initDataType to supported types.
// String comparison is case-sensitive. The empty string is never supported.
if ( implementation . supports_init_data_type ( init_data_type ) )
supported_types . append ( init_data_type ) ;
}
// 3. If supported types is empty, return NotSupported.
if ( supported_types . is_empty ( ) )
return { } ;
// 4. Set the initDataTypes member of accumulated configuration to supported types.
accumulated_configuration . init_data_types = move ( supported_types ) ;
}
// 4. Let distinctive identifier requirement be the value of candidate configuration's distinctiveIdentifier member.
auto distinctive_identifier_requirement = candidate_configuration . distinctive_identifier ;
// 5. If distinctive identifier requirement is "optional" and Distinctive Identifiers are not allowed according to restrictions,
// set distinctive identifier requirement to "not-allowed".
if ( distinctive_identifier_requirement = = Bindings : : MediaKeysRequirement : : Optional & & ! restrictions . distinctive_identifiers )
distinctive_identifier_requirement = Bindings : : MediaKeysRequirement : : NotAllowed ;
// 6. Follow the steps for distinctive identifier requirement from the following list:
switch ( distinctive_identifier_requirement ) {
case Bindings : : MediaKeysRequirement : : Required :
// FIXME: If the implementation does not support use of Distinctive Identifier(s) in combination
// with accumulated configuration and restrictions, return NotSupported.
break ;
case Bindings : : MediaKeysRequirement : : Optional :
// Continue with the following steps.
break ;
case Bindings : : MediaKeysRequirement : : NotAllowed :
// FIXME: If the implementation requires use of Distinctive Identifier(s) or Distinctive Permanent Identifier(s)
// in combination with accumulated configuration and restrictions, return NotSupported.
break ;
}
// 7. Set the distinctiveIdentifier member of accumulated configuration to equal distinctive identifier requirement.
accumulated_configuration . distinctive_identifier = distinctive_identifier_requirement ;
// 8. Let persistent state requirement be equal to the value of candidate configuration's persistentState member.
auto persistent_state_requirement = candidate_configuration . persistent_state ;
// 9. If persistent state requirement is "optional" and persisting state is not allowed according to restrictions,
// set persistent state requirement to "not-allowed".
if ( persistent_state_requirement = = Bindings : : MediaKeysRequirement : : Optional & & ! restrictions . persist_state )
persistent_state_requirement = Bindings : : MediaKeysRequirement : : NotAllowed ;
// 10. Follow the steps for persistent state requirement from the following list:
switch ( persistent_state_requirement ) {
case Bindings : : MediaKeysRequirement : : Required :
// FIXME: If the implementation does not support persisting state in combination with accumulated configuration
// and restrictions, return NotSupported.
break ;
case Bindings : : MediaKeysRequirement : : Optional :
// Continue with the following steps.
break ;
case Bindings : : MediaKeysRequirement : : NotAllowed :
// FIXME: If the implementation requires persisting state in combination with accumulated configuration
// and restrictions, return NotSupported.
break ;
}
// 12. Set the persistentState member of accumulated configuration to equal the value of persistent state requirement.
accumulated_configuration . persistent_state = persistent_state_requirement ;
Vector < Utf16String > session_types ;
// 1. Follow the steps for the first matching condition from the following list:
// * If the sessionTypes member is present in candidate configuration
if ( candidate_configuration . session_types . has_value ( ) ) {
// Let session types be candidate configuration's sessionTypes member.
session_types = * candidate_configuration . session_types ;
}
// * Otherwise
else {
// Let session types be [ "temporary" ].
session_types . append ( " temporary " _utf16 ) ;
}
// 13. For each value in session types:
for ( auto const & session_type : session_types ) {
// 1. Let session type be the value.
// 2. If accumulated configuration's persistentState value is "not-allowed" and the Is persistent session type? algorithm
// returns true for session type return NotSupported.
if ( accumulated_configuration . persistent_state = = Bindings : : MediaKeysRequirement : : NotAllowed & & is_persistent_session_type ( session_type ) )
return { } ;
// 3. FIXME: If the implementation does not support session type in combination with accumulated configuration and restrictions for other reasons, return NotSupported.
// 4. If accumulated configuration's persistentState value is "optional" and the result of running the Is persistent session type? algorithm
// on session type is true, change accumulated configuration's persistentState value to "required".
if ( accumulated_configuration . persistent_state = = Bindings : : MediaKeysRequirement : : Optional & & is_persistent_session_type ( session_type ) )
accumulated_configuration . persistent_state = Bindings : : MediaKeysRequirement : : Required ;
}
// 14. Set the sessionTypes member of accumulated configuration to session types.
accumulated_configuration . session_types = move ( session_types ) ;
// 15. If the videoCapabilities and audioCapabilities members in candidate configuration are both empty, return NotSupported.
if ( candidate_configuration . video_capabilities . is_empty ( ) & & candidate_configuration . audio_capabilities . is_empty ( ) )
return { } ;
// 16. If the videoCapabilities member in candidate configuration is non-empty:
if ( ! candidate_configuration . video_capabilities . is_empty ( ) ) {
// 1. Let video capabilities be the result of executing the Get Supported Capabilities for Audio/Video Type algorithm
// on Video, candidate configuration's videoCapabilities member, accumulated configuration, and restrictions.
auto video_capabilities = get_supported_capabilities_for_audio_video_type ( implementation , CapabilitiesType : : Video , candidate_configuration . video_capabilities , accumulated_configuration , restrictions ) ;
// 2. If video capabilities is null, return NotSupported.
if ( ! video_capabilities . has_value ( ) )
return { } ;
// 3. Set the videoCapabilities member of accumulated configuration to video capabilities.
accumulated_configuration . video_capabilities = * video_capabilities ;
}
// Otherwise:
else {
// 1. Set the videoCapabilities member of accumulated configuration to an empty sequence.
accumulated_configuration . video_capabilities = Vector < Bindings : : MediaKeySystemMediaCapability > { } ;
}
// 1. If the audioCapabilities member in candidate configuration is non-empty:
if ( ! candidate_configuration . audio_capabilities . is_empty ( ) ) {
// 1. Let audio capabilities be the result of executing the Get Supported Capabilities for Audio/Video Type algorithm
// on Audio, candidate configuration's audioCapabilities member, accumulated configuration, and restrictions.
auto audio_capabilities = get_supported_capabilities_for_audio_video_type ( implementation , CapabilitiesType : : Audio , candidate_configuration . audio_capabilities , accumulated_configuration , restrictions ) ;
// 2. If audio capabilities is null, return NotSupported.
if ( ! audio_capabilities . has_value ( ) )
return { } ;
// 3. Set the audioCapabilities member of accumulated configuration to audio capabilities.
accumulated_configuration . audio_capabilities = * audio_capabilities ;
}
// Otherwise:
else {
// 1. Set the audioCapabilities member of accumulated configuration to an empty sequence.
accumulated_configuration . audio_capabilities = Vector < Bindings : : MediaKeySystemMediaCapability > { } ;
}
// 18. If accumulated configuration's distinctiveIdentifier value is "optional", follow the steps for the first matching condition from the following list:
if ( accumulated_configuration . distinctive_identifier = = Bindings : : MediaKeysRequirement : : Optional ) {
// FIXME: 1. If the implementation requires use of Distinctive Identifier(s) or Distinctive Permanent Identifier(s) for any of the combinations in accumulated configuration:
if ( false ) {
// 1. Change accumulated configuration's distinctiveIdentifier value to "required".
accumulated_configuration . distinctive_identifier = Bindings : : MediaKeysRequirement : : Required ;
}
// Otherwise
else {
// 1. Change accumulated configuration's distinctiveIdentifier value to "not-allowed".
accumulated_configuration . distinctive_identifier = Bindings : : MediaKeysRequirement : : NotAllowed ;
}
}
// 19. If accumulated configuration's persistentState value is "optional", follow the steps for the first matching condition from the following list:
if ( accumulated_configuration . persistent_state = = Bindings : : MediaKeysRequirement : : Optional ) {
// FIXME: 1. If the implementation requires persisting state for any of the combinations in accumulated configuration
if ( false ) {
// 1. Change accumulated configuration's persistentState value to "required".
accumulated_configuration . persistent_state = Bindings : : MediaKeysRequirement : : Required ;
}
// Otherwise
else {
// 1. Change accumulated configuration's persistentState value to "not-allowed".
accumulated_configuration . persistent_state = Bindings : : MediaKeysRequirement : : NotAllowed ;
}
}
// FIXME: 20. If implementation in the configuration specified by the combination of the values in accumulated configuration
// is not supported or not allowed in the origin, return NotSupported.
// FIXME: 21. If accumulated configuration's distinctiveIdentifier value is "required" and the Distinctive Identifier(s)
// associated with accumulated configuration are not unique per origin and profile and clearable:
// FIXME: 1. Update restrictions to reflect that all configurations described by accumulated configuration do not have user consent.
// FIXME: 2. Return ConsentDenied and restrictions.
// 22. Let consent status and updated restrictions be the result of running the Get Consent Status algorithm
// on accumulated configuration, restrictions and origin and follow the steps for the value of consent status from the following list:
auto consent_status = get_consent_status ( accumulated_configuration , restrictions , origin ) ;
switch ( consent_status ) {
case ConsentStatus : : ConsentDenied :
// Return ConsentDenied and updated restrictions.
return { } ;
case ConsentStatus : : InformUser :
// FIXME: Inform the user that accumulated configuration is in use in the origin including, specifically,
// the information that Distinctive Identifier(s) and/or Distinctive Permanent Identifier(s) as
// appropriate will be used if the distinctiveIdentifier member of accumulated configuration is "required".
// Continue to the next step.
break ;
case ConsentStatus : : Allowed :
// Continue to the next step.
break ;
}
// 23. Return accumulated configuration.
return ConsentConfiguration { consent_status , accumulated_configuration } ;
}
2025-07-31 13:27:32 +02:00
// https://w3c.github.io/encrypted-media/#get-supported-configuration
Optional < ConsentConfiguration > get_supported_configuration ( KeySystem const & implementation , Bindings : : MediaKeySystemConfiguration const & candidate_configuration , URL : : Origin const & origin )
{
// 1. Let supported configuration be ConsentDenied.
Optional < ConsentConfiguration > supported_configuration = ConsentConfiguration { ConsentStatus : : ConsentDenied , { } } ;
// 2. Initialize restrictions to indicate that no configurations have had user consent denied.
MediaKeyRestrictions restrictions ;
size_t loop_count = 0 ;
// 3. Repeat the following step while supported configuration is ConsentDenied:
while ( supported_configuration . has_value ( ) & & supported_configuration - > status = = ConsentStatus : : ConsentDenied ) {
// 1. Let supported configuration and, if provided, restrictions be the result of executing
// the Get Supported Configuration and Consent algorithm with implementation, candidate configuration, restrictions and origin.
supported_configuration = get_supported_configuration_and_consent ( implementation , candidate_configuration , restrictions , origin ) ;
// AD-HOC: While this is being implemented, we use this to avoid a possible infinite loop
if ( loop_count + + > 5 ) {
break ;
}
}
// 4. Return supported configuration.
return supported_configuration ;
}
2025-07-31 13:28:31 +02:00
// https://w3c.github.io/encrypted-media/#dfn-common-key-systems
bool is_supported_key_system ( Utf16String const & key_system )
{
constexpr Array < Utf16View , 1 > supported_key_systems = {
// https://w3c.github.io/encrypted-media/#clear-key
" org.w3.clearkey " sv ,
} ;
return supported_key_systems . contains_slow ( key_system ) ;
}
NonnullOwnPtr < KeySystem > key_system_from_string ( Utf16String const & key_system )
{
if ( key_system = = " org.w3.clearkey " _utf16 ) {
return adopt_own ( * new ClearKeySystem ( ) ) ;
}
VERIFY_NOT_REACHED ( ) ;
}
2025-07-31 13:09:23 +02:00
}