Source: lib/util/xml_utils.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2016 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.util.XmlUtils');
  7. goog.require('goog.asserts');
  8. goog.require('shaka.log');
  9. goog.require('shaka.util.Lazy');
  10. goog.require('shaka.util.StringUtils');
  11. /**
  12. * @summary A set of XML utility functions.
  13. */
  14. shaka.util.XmlUtils = class {
  15. /**
  16. * Parse a string and return the resulting root element if it was valid XML.
  17. *
  18. * @param {string} xmlString
  19. * @param {string} expectedRootElemName
  20. * @return {Element}
  21. */
  22. static parseXmlString(xmlString, expectedRootElemName) {
  23. const parser = new DOMParser();
  24. const unsafeXmlString =
  25. shaka.util.XmlUtils.trustedHTMLFromString_.value()(xmlString);
  26. let unsafeXml = null;
  27. try {
  28. unsafeXml = parser.parseFromString(unsafeXmlString, 'text/xml');
  29. } catch (exception) {
  30. shaka.log.error('XML parsing exception:', exception);
  31. return null;
  32. }
  33. // According to MDN, parseFromString never returns null.
  34. goog.asserts.assert(unsafeXml, 'Parsed XML document cannot be null!');
  35. // Check for empty documents.
  36. const rootElem = unsafeXml.documentElement;
  37. if (!rootElem) {
  38. shaka.log.error('XML document was empty!');
  39. return null;
  40. }
  41. // Check for parser errors.
  42. // cspell:disable-next-line
  43. const parserErrorElements = rootElem.getElementsByTagName('parsererror');
  44. if (parserErrorElements.length) {
  45. shaka.log.error('XML parser error found:', parserErrorElements[0]);
  46. return null;
  47. }
  48. // The top-level element in the loaded XML should have the name we expect.
  49. if (rootElem.tagName != expectedRootElemName) {
  50. shaka.log.error(
  51. `XML tag name does not match expected "${expectedRootElemName}":`,
  52. rootElem.tagName);
  53. return null;
  54. }
  55. // Cobalt browser doesn't support document.createNodeIterator.
  56. if (!('createNodeIterator' in document)) {
  57. return rootElem;
  58. }
  59. // SECURITY: Verify that the document does not contain elements from the
  60. // HTML or SVG namespaces, which could trigger script execution and XSS.
  61. const iterator = document.createNodeIterator(
  62. unsafeXml,
  63. NodeFilter.SHOW_ALL,
  64. );
  65. let currentNode;
  66. while (currentNode = iterator.nextNode()) {
  67. if (currentNode instanceof HTMLElement ||
  68. currentNode instanceof SVGElement) {
  69. shaka.log.error('XML document embeds unsafe content!');
  70. return null;
  71. }
  72. }
  73. return rootElem;
  74. }
  75. /**
  76. * Parse some data (auto-detecting the encoding) and return the resulting
  77. * root element if it was valid XML.
  78. * @param {BufferSource} data
  79. * @param {string} expectedRootElemName
  80. * @return {Element}
  81. */
  82. static parseXml(data, expectedRootElemName) {
  83. try {
  84. const string = shaka.util.StringUtils.fromBytesAutoDetect(data);
  85. return shaka.util.XmlUtils.parseXmlString(string, expectedRootElemName);
  86. } catch (exception) {
  87. shaka.log.error('parseXmlString threw!', exception);
  88. return null;
  89. }
  90. }
  91. /**
  92. * Converts a Element to BufferSource.
  93. * @param {!Element} elem
  94. * @return {!ArrayBuffer}
  95. */
  96. static toArrayBuffer(elem) {
  97. return shaka.util.StringUtils.toUTF8(elem.outerHTML);
  98. }
  99. };
  100. /**
  101. * Promote a string to TrustedHTML. This function is security-sensitive and
  102. * should only be used with security approval where the string is guaranteed not
  103. * to cause an XSS vulnerability.
  104. *
  105. * @private {!shaka.util.Lazy.<function(!string): (!TrustedHTML|!string)>}
  106. */
  107. shaka.util.XmlUtils.trustedHTMLFromString_ = new shaka.util.Lazy(() => {
  108. if (typeof trustedTypes !== 'undefined') {
  109. // Create a Trusted Types policy for promoting the string to TrustedHTML.
  110. // The Lazy wrapper ensures this policy is only created once.
  111. const policy = trustedTypes.createPolicy('shaka-player#xml', {
  112. createHTML: (s) => s,
  113. });
  114. return (s) => policy.createHTML(s);
  115. }
  116. // Fall back to strings in environments that don't support Trusted Types.
  117. return (s) => s;
  118. });