Skip to main content
  • Home
  • Development
  • Documentation
  • Donate
  • Operational login
  • Browse the archive

swh logo
SoftwareHeritage
Software
Heritage
Archive
Features
  • Search

  • Downloads

  • Save code now

  • Add forge now

  • Help

swh:1:snp:788e82c31486091711070ad2dbf62bc94d01f1c2
  • Code
  • Branches (1)
  • Releases (0)
    • Branches
    • Releases
    • HEAD
    • refs/heads/master
    No releases to show
  • 0b5dcdf
  • /
  • eme_interception.js
Raw File Download

To reference or cite the objects present in the Software Heritage archive, permalinks based on SoftWare Hash IDentifiers (SWHIDs) must be used.
Select below a type of object currently browsed in order to display its associated SWHID and permalink.

  • content
  • directory
  • revision
  • snapshot
content badge
swh:1:cnt:d6768811a49445d9ec245bf35589aaacfd7ca31c
directory badge
swh:1:dir:0b5dcdf81332eeb0fe1eb21a4f18ee0f4753815d
revision badge
swh:1:rev:12768782e4b8d9428bb2a973a09f909ce74e5a2b
snapshot badge
swh:1:snp:788e82c31486091711070ad2dbf62bc94d01f1c2

This interface enables to generate software citations, provided that the root directory of browsed objects contains a citation.cff or codemeta.json file.
Select below a type of object currently browsed in order to generate citations for them.

  • content
  • directory
  • revision
  • snapshot
(requires biblatex-software package)
Generating citation ...
(requires biblatex-software package)
Generating citation ...
(requires biblatex-software package)
Generating citation ...
(requires biblatex-software package)
Generating citation ...
Tip revision: 12768782e4b8d9428bb2a973a09f909ce74e5a2b authored by Tomer on 29 December 2022, 12:33:18 UTC
Added archived original repo contents
Tip revision: 1276878
eme_interception.js
/**
 * Hooks EME calls and forwards them for analysis and decryption.
 * 
 * Most of the code here was borrowed from https://github.com/google/eme_logger/blob/master/eme_listeners.js
 */

 var lastReceivedLicenseRequest = null;
 var lastReceivedLicenseResponse = null;

 /** Set up the EME listeners. */
function startEMEInterception() 
{
  var listener = new EmeInterception();
  listener.setUpListeners();
}

 /**
 * Gets called whenever an EME method is getting called or an EME event fires
 */
EmeInterception.onOperation = function(operationType, args) 
{
    if (operationType == "GenerateRequestCall")
    {
       // got initData
       // console.log(args);
    }
    else if (operationType == "MessageEvent")
    {
        var licenseRequest = args.message;
        lastReceivedLicenseRequest = licenseRequest;
    }
    else if (operationType == "UpdateCall")
    {
        var licenseResponse = args[0];
        lastReceivedLicenseResponse = licenseResponse;

        // OK, let's try to decrypt it, assuming the response correlates to the request
        WidevineCrypto.decryptContentKey(lastReceivedLicenseRequest, lastReceivedLicenseResponse);
    }
};


/**
 * Manager for EME event and method listeners.
 * @constructor
 */
function EmeInterception() 
{
  this.unprefixedEmeEnabled = Navigator.prototype.requestMediaKeySystemAccess ? true : false;
  this.prefixedEmeEnabled = HTMLMediaElement.prototype.webkitGenerateKeyRequest ? true : false;
}


/**
 * The number of types of HTML Media Elements to track.
 * @const {number}
 */
EmeInterception.NUM_MEDIA_ELEMENT_TYPES = 3;


/**
 * Sets up EME listeners for whichever type of EME is enabled.
 */
EmeInterception.prototype.setUpListeners = function() 
{
  if (!this.unprefixedEmeEnabled && !this.prefixedEmeEnabled) {
    // EME is not enabled, just ignore
    return;
  }
  if (this.unprefixedEmeEnabled) {
    this.addListenersToNavigator_();
  }
  if (this.prefixedEmeEnabled) {
    // Prefixed EME is enabled
  }
  this.addListenersToAllEmeElements_();
};


/**
 * Adds listeners to the EME methods on the Navigator object.
 * @private
 */
EmeInterception.prototype.addListenersToNavigator_ = function() 
{
  if (navigator.listenersAdded_) 
    return;

  var originalRequestMediaKeySystemAccessFn = EmeInterception.extendEmeMethod(
      navigator,
      navigator.requestMediaKeySystemAccess,
      "RequestMediaKeySystemAccessCall");

  navigator.requestMediaKeySystemAccess = function() 
  {
    var options = arguments[1];

    // slice "It is recommended that a robustness level be specified" warning
    var modifiedArguments = arguments;
    var modifiedOptions = EmeInterception.addRobustnessLevelIfNeeded(options);
    modifiedArguments[1] = modifiedOptions;

    var result = originalRequestMediaKeySystemAccessFn.apply(null, modifiedArguments);
    // Attach listeners to returned MediaKeySystemAccess object
    return result.then(function(mediaKeySystemAccess) 
    {
      this.addListenersToMediaKeySystemAccess_(mediaKeySystemAccess);
      return Promise.resolve(mediaKeySystemAccess);
    }.bind(this));

  }.bind(this);

  navigator.listenersAdded_ = true;
};


/**
 * Adds listeners to the EME methods on a MediaKeySystemAccess object.
 * @param {MediaKeySystemAccess} mediaKeySystemAccess A MediaKeySystemAccess
 *     object to add listeners to.
 * @private
 */
EmeInterception.prototype.addListenersToMediaKeySystemAccess_ = function(mediaKeySystemAccess) 
{
  if (mediaKeySystemAccess.listenersAdded_) {
    return;
  }
  mediaKeySystemAccess.originalGetConfiguration = mediaKeySystemAccess.getConfiguration;
  mediaKeySystemAccess.getConfiguration = EmeInterception.extendEmeMethod(
      mediaKeySystemAccess,
      mediaKeySystemAccess.getConfiguration,
      "GetConfigurationCall");

  var originalCreateMediaKeysFn = EmeInterception.extendEmeMethod(
      mediaKeySystemAccess,
      mediaKeySystemAccess.createMediaKeys,
      "CreateMediaKeysCall");

  mediaKeySystemAccess.createMediaKeys = function() 
  {
    var result = originalCreateMediaKeysFn.apply(null, arguments);
    // Attach listeners to returned MediaKeys object
    return result.then(function(mediaKeys) {
      mediaKeys.keySystem_ = mediaKeySystemAccess.keySystem;
      this.addListenersToMediaKeys_(mediaKeys);
      return Promise.resolve(mediaKeys);
    }.bind(this));

  }.bind(this);

  mediaKeySystemAccess.listenersAdded_ = true;
};


/**
 * Adds listeners to the EME methods on a MediaKeys object.
 * @param {MediaKeys} mediaKeys A MediaKeys object to add listeners to.
 * @private
 */
EmeInterception.prototype.addListenersToMediaKeys_ = function(mediaKeys) 
{
  if (mediaKeys.listenersAdded_) {
    return;
  }
  var originalCreateSessionFn = EmeInterception.extendEmeMethod(mediaKeys, mediaKeys.createSession, "CreateSessionCall");
  mediaKeys.createSession = function() 
  {
    var result = originalCreateSessionFn.apply(null, arguments);
    result.keySystem_ = mediaKeys.keySystem_;
    // Attach listeners to returned MediaKeySession object
    this.addListenersToMediaKeySession_(result);
    return result;
  }.bind(this);

  mediaKeys.setServerCertificate = EmeInterception.extendEmeMethod(mediaKeys, mediaKeys.setServerCertificate, "SetServerCertificateCall");
  mediaKeys.listenersAdded_ = true;
};


/** Adds listeners to the EME methods and events on a MediaKeySession object.
 * @param {MediaKeySession} session A MediaKeySession object to add
 *     listeners to.
 * @private
 */
EmeInterception.prototype.addListenersToMediaKeySession_ = function(session) 
{
  if (session.listenersAdded_) {
    return;
  }

  session.generateRequest = EmeInterception.extendEmeMethod(session,session.generateRequest, "GenerateRequestCall");
  session.load = EmeInterception.extendEmeMethod(session, session.load, "LoadCall");
  session.update = EmeInterception.extendEmeMethod(session,session.update, "UpdateCall");
  session.close = EmeInterception.extendEmeMethod(session, session.close, "CloseCall");
  session.remove = EmeInterception.extendEmeMethod(session, session.remove, "RemoveCall");

  session.addEventListener('message', function(e) 
  {
    e.keySystem = session.keySystem_;
    EmeInterception.interceptEvent("MessageEvent", e);
  });

  session.addEventListener('keystatuseschange', EmeInterception.interceptEvent.bind(null, "KeyStatusesChangeEvent"));

  session.listenersAdded_ = true;
};


/**
 * Adds listeners to all currently created media elements (audio, video) and sets up a
 * mutation-summary observer to add listeners to any newly created media
 * elements.
 * @private
 */
EmeInterception.prototype.addListenersToAllEmeElements_ = function() 
{
  this.addEmeInterceptionToInitialMediaElements_();

  // TODO: Use MutationObserver directry
  // var observer = new MutationSummary({
  //   callback: function(summaries) {
  //     applyListeners(summaries);
  //   },
  //   queries: [{element: 'video'}, {element: 'audio'}, {element: 'media'}]
  // });

  // var applyListeners = function(summaries) {
  //   for (var i = 0; i < EmeInterception.NUM_MEDIA_ELEMENT_TYPES; i++) {
  //     var elements = summaries[i];
  //     elements.added.forEach(function(element) {
  //       this.addListenersToEmeElement_(element, true);
  //     }.bind(this));
  //   }
  // }.bind(this);
};


/**
 * Adds listeners to the EME elements currently in the document.
 * @private
 */
EmeInterception.prototype.addEmeInterceptionToInitialMediaElements_ = function() 
{
  var audioElements = document.getElementsByTagName('audio');
  for (var i = 0; i < audioElements.length; ++i) {
    this.addListenersToEmeElement_(audioElements[i], false);
  }
  var videoElements = document.getElementsByTagName('video');
  for (var i = 0; i < videoElements.length; ++i) {
    this.addListenersToEmeElement_(videoElements[i], false);
  }
  var mediaElements = document.getElementsByTagName('media');
  for (var i = 0; i < mediaElements.length; ++i) {
    this.addListenersToEmeElement_(mediaElements[i], false);
  }
};


/**
 * Adds method and event listeners to media element.
 * @param {HTMLMediaElement} element A HTMLMedia element to add listeners to.
 * @private
 */
EmeInterception.prototype.addListenersToEmeElement_ = function(element) 
{
  this.addEmeEventListeners_(element);
  this.addEmeMethodListeners_(element);
  console.info('EME listeners successfully added to:', element);
};


/**
 * Adds event listeners to a media element.
 * @param {HTMLMediaElement} element A HTMLMedia element to add listeners to.
 * @private
 */
EmeInterception.prototype.addEmeEventListeners_ = function(element) 
{
  if (element.eventListenersAdded_) {
    return;
  }

  if (this.prefixedEmeEnabled) 
  {
    element.addEventListener('webkitneedkey', EmeInterception.interceptEvent.bind(null, "NeedKeyEvent"));
    element.addEventListener('webkitkeymessage', EmeInterception.interceptEvent.bind(null, "KeyMessageEvent"));
    element.addEventListener('webkitkeyadded', EmeInterception.interceptEvent.bind(null, "KeyAddedEvent"));
    element.addEventListener('webkitkeyerror', EmeInterception.interceptEvent.bind(null, "KeyErrorEvent"));
  }

  element.addEventListener('encrypted', EmeInterception.interceptEvent.bind(null, "EncryptedEvent"));
  element.addEventListener('play', EmeInterception.interceptEvent.bind(null, "PlayEvent"));

  element.addEventListener('error', function(e) {
    console.error('Error Event');
    EmeInterception.interceptEvent("ErrorEvent", e);
  });

  element.eventListenersAdded_ = true;
};


/**
 * Adds method listeners to a media element.
 * @param {HTMLMediaElement} element A HTMLMedia element to add listeners to.
 * @private
 */
EmeInterception.prototype.addEmeMethodListeners_ = function(element) 
{
  if (element.methodListenersAdded_) {
    return;
  }

  element.play = EmeInterception.extendEmeMethod(element, element.play, "PlayCall");

  if (this.prefixedEmeEnabled) {
    element.canPlayType = EmeInterception.extendEmeMethod(element, element.canPlayType, "CanPlayTypeCall");

    element.webkitGenerateKeyRequest = EmeInterception.extendEmeMethod(element, element.webkitGenerateKeyRequest, "GenerateKeyRequestCall");
    element.webkitAddKey = EmeInterception.extendEmeMethod(element, element.webkitAddKey, "AddKeyCall");
    element.webkitCancelKeyRequest = EmeInterception.extendEmeMethod(element, element.webkitCancelKeyRequest, "CancelKeyRequestCall");

  }

  if (this.unprefixedEmeEnabled) {
    element.setMediaKeys = EmeInterception.extendEmeMethod(element, element.setMediaKeys, "SetMediaKeysCall");
  }

  element.methodListenersAdded_ = true;
};


/**
 * Creates a wrapper function that logs calls to the given method.
 * @param {!Object} element An element or object whose function
 *    call will be logged.
 * @param {!Function} originalFn The function to log.
 * @param {!Function} type The constructor for a logger class that will
 *    be instantiated to log the originalFn call.
 * @return {!Function} The new version, with logging, of orginalFn.
 */
EmeInterception.extendEmeMethod = function(element, originalFn, type) 
{
  return function() 
  {
    try
    {
        var result = originalFn.apply(element, arguments);
        var args = [].slice.call(arguments);
        EmeInterception.interceptCall(type, args, result, element);
    }
    catch (e)
    {
      console.error(e);
    }
    

    return result;
  };
};


/**
 * Intercepts a method call to the console and a separate frame.
 * @param {!Function} constructor The constructor for a logger class that will
 *    be instantiated to log this call.
 * @param {Array} args The arguments this call was made with.
 * @param {Object} result The result of this method call.
 * @param {!Object} target The element this method was called on.
 * @return {!eme.EmeMethodCall} The data that has been logged.
 */
EmeInterception.interceptCall = function(type, args, result, target) 
{
  EmeInterception.onOperation(type, args);
  return args;
};

/**
 * Intercepts an event to the console and a separate frame.
 * @param {!Function} constructor The constructor for a logger class that will
 *    be instantiated to log this event.
 * @param {!Event} event An EME event.
 * @return {!eme.EmeEvent} The data that has been logged.
 */
EmeInterception.interceptEvent = function(type, event) 
{
  EmeInterception.onOperation(type, event);
  return event;
};

EmeInterception.addRobustnessLevelIfNeeded = function(options)
{
  for (var i = 0; i < options.length; i++)
  {
    var option = options[i];
    var videoCapabilities = option["videoCapabilities"];
    var audioCapabilties = option["audioCapabilities"];
    if (videoCapabilities != null)
    {
      for (var j = 0; j < videoCapabilities.length; j++)
        if (videoCapabilities[j]["robustness"] == undefined) videoCapabilities[j]["robustness"] = "SW_SECURE_CRYPTO";
    }

    if (audioCapabilties != null)
    {
      for (var j = 0; j < audioCapabilties.length; j++)
        if (audioCapabilties[j]["robustness"] == undefined) audioCapabilties[j]["robustness"] = "SW_SECURE_CRYPTO";
    }
    
    option["videoCapabilities"] = videoCapabilities;
    option["audioCapabilities"] = audioCapabilties;
    options[i] = option;
  }

  return options;
}

startEMEInterception();

back to top

Software Heritage — Copyright (C) 2015–2026, The Software Heritage developers. License: GNU AGPLv3+.
The source code of Software Heritage itself is available on our development forge.
The source code files archived by Software Heritage are available under their own copyright and licenses.
Terms of use: Archive access, API— Content policy— Contact— JavaScript license information— Web API