mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-26 17:46:26 +03:00
a731eba804
Harden the XmlUtils.parseXmlString function against XML documents that embed elements from the HTML or SVG namespaces, which could trigger script execution and cause XSS vulnerabilities. Also migrate direct users of the DOMParser.parseFromString function to XmlUtils, and add appropriate unit tests.
440 lines
15 KiB
JavaScript
440 lines
15 KiB
JavaScript
/*! @license
|
|
* Shaka Player
|
|
* Copyright 2016 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
describe('XmlUtils', () => {
|
|
// A number that cannot be represented as a Javascript number.
|
|
const HUGE_NUMBER_STRING = new Array(500).join('7');
|
|
|
|
const XmlUtils = shaka.util.XmlUtils;
|
|
|
|
describe('findChild', () => {
|
|
it('finds a child node', () => {
|
|
const xmlString = [
|
|
'<?xml version="1.0"?>',
|
|
'<Root>',
|
|
' <Child></Child>',
|
|
'</Root>',
|
|
].join('\n');
|
|
const xml = new DOMParser().parseFromString(xmlString, 'application/xml');
|
|
goog.asserts.assert(xml, 'parseFromString should succeed');
|
|
|
|
const root = XmlUtils.findChild(xml, 'Root');
|
|
goog.asserts.assert(root, 'findChild should find element');
|
|
|
|
expect(XmlUtils.findChild(root, 'Child')).toBeTruthy();
|
|
expect(XmlUtils.findChild(root, 'DoesNotExist')).toBeNull();
|
|
});
|
|
|
|
it('handles duplicate child nodes', () => {
|
|
const xmlString = [
|
|
'<?xml version="1.0"?>',
|
|
'<Root>',
|
|
' <Child></Child>',
|
|
' <Child></Child>',
|
|
'</Root>',
|
|
].join('\n');
|
|
const xml = new DOMParser().parseFromString(xmlString, 'application/xml');
|
|
goog.asserts.assert(xml, 'parseFromString should succeed');
|
|
|
|
const root = XmlUtils.findChild(xml, 'Root');
|
|
goog.asserts.assert(root, 'findChild should find element');
|
|
|
|
expect(XmlUtils.findChild(root, 'Child')).toBeNull();
|
|
});
|
|
});
|
|
|
|
it('findChildren', () => {
|
|
const xmlString = [
|
|
'<?xml version="1.0"?>',
|
|
'<Root>',
|
|
' <Child></Child>',
|
|
' <Child></Child>',
|
|
'</Root>',
|
|
].join('\n');
|
|
const xml = new DOMParser().parseFromString(xmlString, 'application/xml');
|
|
goog.asserts.assert(xml, 'parseFromString should succeed');
|
|
|
|
const roots = XmlUtils.findChildren(xml, 'Root');
|
|
expect(roots).toBeTruthy();
|
|
expect(roots.length).toBe(1);
|
|
|
|
let children = XmlUtils.findChildren(roots[0], 'Child');
|
|
expect(children.length).toBe(2);
|
|
|
|
children = XmlUtils.findChildren(roots[0], 'DoesNotExist');
|
|
expect(children.length).toBe(0);
|
|
});
|
|
|
|
describe('getContents', () => {
|
|
it('returns node contents', () => {
|
|
const xmlString = [
|
|
'<?xml version="1.0"?>',
|
|
'<Root>',
|
|
' foo bar',
|
|
'</Root>',
|
|
].join('\n');
|
|
const xml = new DOMParser().parseFromString(xmlString, 'application/xml');
|
|
goog.asserts.assert(xml, 'parseFromString should succeed');
|
|
|
|
const root = XmlUtils.findChild(xml, 'Root');
|
|
goog.asserts.assert(root, 'findChild should find element');
|
|
expect(XmlUtils.getContents(root)).toBe('foo bar');
|
|
});
|
|
|
|
it('handles empty node contents', () => {
|
|
const xmlString = [
|
|
'<?xml version="1.0"?>',
|
|
'<Root>',
|
|
'</Root>',
|
|
].join('\n');
|
|
const xml = new DOMParser().parseFromString(xmlString, 'application/xml');
|
|
goog.asserts.assert(xml, 'parseFromString should succeed');
|
|
|
|
const root = XmlUtils.findChild(xml, 'Root');
|
|
goog.asserts.assert(root, 'findChild should find element');
|
|
expect(XmlUtils.getContents(root)).toBe('');
|
|
});
|
|
|
|
it('handles null node contents', () => {
|
|
const xmlString = [
|
|
'<?xml version="1.0"?>',
|
|
'<Root>',
|
|
'</Root>',
|
|
].join('\n');
|
|
const xml = new DOMParser().parseFromString(xmlString, 'application/xml');
|
|
goog.asserts.assert(xml, 'parseFromString should succeed');
|
|
|
|
expect(XmlUtils.getContents(xml)).toBeNull();
|
|
});
|
|
|
|
it('handles CDATA sections', () => {
|
|
const xmlString = [
|
|
'<?xml version="1.0"?>',
|
|
'<Root>',
|
|
'<![CDATA[<Foo> Bar]]>',
|
|
'</Root>',
|
|
].join('\n');
|
|
const xml = new DOMParser().parseFromString(xmlString, 'application/xml');
|
|
goog.asserts.assert(xml, 'parseFromString should succeed');
|
|
|
|
const root = XmlUtils.findChild(xml, 'Root');
|
|
goog.asserts.assert(root, 'findChild should find element');
|
|
expect(XmlUtils.getContents(root)).toBe('<Foo> Bar');
|
|
});
|
|
});
|
|
|
|
describe('parseAttr', () => {
|
|
/** @type {!Document} */
|
|
let xml;
|
|
|
|
beforeEach(() => {
|
|
const xmlString = [
|
|
'<?xml version="1.0"?>',
|
|
'<Root a="2-7" b="-5" c="">',
|
|
'</Root>',
|
|
].join('\n');
|
|
xml = /** @type {!Document} */ (
|
|
new DOMParser().parseFromString(xmlString, 'application/xml'));
|
|
});
|
|
|
|
it('delegates to parser function', () => {
|
|
const root = XmlUtils.findChild(xml, 'Root');
|
|
goog.asserts.assert(root, 'findChild should find element');
|
|
expect(XmlUtils.parseAttr(root, 'a', XmlUtils.parseRange)).toEqual(
|
|
{start: 2, end: 7});
|
|
expect(XmlUtils.parseAttr(root, 'b', XmlUtils.parseInt)).toBe(-5);
|
|
expect(XmlUtils.parseAttr(root, 'c', XmlUtils.parseInt)).toBe(0);
|
|
expect(XmlUtils.parseAttr(root, 'd', XmlUtils.parseInt)).toBeNull();
|
|
});
|
|
|
|
it('supports default values', () => {
|
|
const root = XmlUtils.findChild(xml, 'Root');
|
|
goog.asserts.assert(root, 'findChild should find element');
|
|
expect(XmlUtils.parseAttr(root, 'd', XmlUtils.parseInt, 9)).toBe(9);
|
|
});
|
|
});
|
|
|
|
it('parseDate', () => {
|
|
// Should be parsed as UTC independent of local timezone.
|
|
expect(XmlUtils.parseDate('2015-11-30T12:46:33')).toBe(1448887593);
|
|
// Should be parsed using the given timezone, not the local timezone.
|
|
expect(XmlUtils.parseDate('2015-11-30T12:46:33+06:00')).toBe(1448865993);
|
|
|
|
expect(XmlUtils.parseDate('November 30, 2015')).toBeTruthy();
|
|
expect(XmlUtils.parseDate('Apple')).toBeNull();
|
|
expect(XmlUtils.parseDate('')).toBeNull();
|
|
});
|
|
|
|
it('parseDuration', () => {
|
|
// No time.
|
|
expect(XmlUtils.parseDuration('P')).toBe(0);
|
|
expect(XmlUtils.parseDuration('PT')).toBe(0);
|
|
|
|
// Years only. 1 year has 365 or 366 days.
|
|
expect(XmlUtils.parseDuration('P3Y')).toBeLessThan(
|
|
3 * (60 * 60 * 24 * 366) + 1);
|
|
expect(XmlUtils.parseDuration('P3Y')).toBeGreaterThan(
|
|
3 * (60 * 60 * 24 * 365) - 1);
|
|
|
|
// Months only. 1 month has 28 to 31 days.
|
|
expect(XmlUtils.parseDuration('P2M')).toBeLessThan(
|
|
2 * (60 * 60 * 24 * 31) + 1);
|
|
expect(XmlUtils.parseDuration('P2M')).toBeGreaterThan(
|
|
2 * (60 * 60 * 24 * 28) - 1);
|
|
|
|
// Days only.
|
|
expect(XmlUtils.parseDuration('P7D')).toBe(604800);
|
|
|
|
// Hours only.
|
|
expect(XmlUtils.parseDuration('PT1H')).toBe(3600);
|
|
|
|
// Minutes only.
|
|
expect(XmlUtils.parseDuration('PT1M')).toBe(60);
|
|
|
|
// Seconds only (with no fractional part).
|
|
expect(XmlUtils.parseDuration('PT1S')).toBe(1);
|
|
|
|
// Seconds only (with no whole part).
|
|
expect(XmlUtils.parseDuration('PT0.1S')).toBe(0.1);
|
|
expect(XmlUtils.parseDuration('PT.1S')).toBe(0.1);
|
|
|
|
// Seconds only (with whole part and fractional part).
|
|
expect(XmlUtils.parseDuration('PT1.1S')).toBe(1.1);
|
|
|
|
// Hours, and minutes.
|
|
expect(XmlUtils.parseDuration('PT1H2M')).toBe(3720);
|
|
|
|
// Hours, and seconds.
|
|
expect(XmlUtils.parseDuration('PT1H2S')).toBe(3602);
|
|
expect(XmlUtils.parseDuration('PT1H2.2S')).toBe(3602.2);
|
|
|
|
// Minutes, and seconds.
|
|
expect(XmlUtils.parseDuration('PT1M2S')).toBe(62);
|
|
expect(XmlUtils.parseDuration('PT1M2.2S')).toBe(62.2);
|
|
|
|
// Hours, minutes, and seconds.
|
|
expect(XmlUtils.parseDuration('PT1H2M3S')).toBe(3723);
|
|
expect(XmlUtils.parseDuration('PT1H2M3.3S')).toBe(3723.3);
|
|
|
|
// Days, hours, minutes, and seconds.
|
|
expect(XmlUtils.parseDuration('P1DT1H2M3S')).toBe(90123);
|
|
expect(XmlUtils.parseDuration('P1DT1H2M3.3S')).toBe(90123.3);
|
|
|
|
// Months, hours, minutes, and seconds.
|
|
expect(XmlUtils.parseDuration('P1M1DT1H2M3S')).toBeLessThan(
|
|
(60 * 60 * 24 * 31) + 90123 + 1);
|
|
expect(XmlUtils.parseDuration('P1M1DT1H2M3S')).toBeGreaterThan(
|
|
(60 * 60 * 24 * 28) + 90123 - 1);
|
|
|
|
// Years, Months, hours, minutes, and seconds.
|
|
expect(XmlUtils.parseDuration('P1Y1M1DT1H2M3S')).toBeLessThan(
|
|
(60 * 60 * 24 * 366) + (60 * 60 * 24 * 31) + 90123 + 1);
|
|
expect(XmlUtils.parseDuration('P1Y1M1DT1H2M3S')).toBeGreaterThan(
|
|
(60 * 60 * 24 * 365) + (60 * 60 * 24 * 28) + 90123 - 1);
|
|
|
|
expect(XmlUtils.parseDuration('PT')).toBe(0);
|
|
expect(XmlUtils.parseDuration('P')).toBe(0);
|
|
|
|
// Error cases.
|
|
expect(XmlUtils.parseDuration('-PT3S')).toBeNull();
|
|
expect(XmlUtils.parseDuration('PT-3S')).toBeNull();
|
|
expect(XmlUtils.parseDuration('P1Sasdf')).toBeNull();
|
|
expect(XmlUtils.parseDuration('1H2M3S')).toBeNull();
|
|
expect(XmlUtils.parseDuration('123')).toBeNull();
|
|
expect(XmlUtils.parseDuration('abc')).toBeNull();
|
|
expect(XmlUtils.parseDuration('')).toBeNull();
|
|
|
|
expect(XmlUtils.parseDuration('P' + HUGE_NUMBER_STRING + 'Y')).toBeNull();
|
|
expect(XmlUtils.parseDuration('P' + HUGE_NUMBER_STRING + 'M')).toBeNull();
|
|
expect(XmlUtils.parseDuration('P' + HUGE_NUMBER_STRING + 'D')).toBeNull();
|
|
expect(XmlUtils.parseDuration('PT' + HUGE_NUMBER_STRING + 'H')).toBeNull();
|
|
expect(XmlUtils.parseDuration('PT' + HUGE_NUMBER_STRING + 'M')).toBeNull();
|
|
expect(XmlUtils.parseDuration('PT' + HUGE_NUMBER_STRING + 'S')).toBeNull();
|
|
});
|
|
|
|
it('parseRange', () => {
|
|
expect(XmlUtils.parseRange('0-0')).toEqual({start: 0, end: 0});
|
|
expect(XmlUtils.parseRange('1-1')).toEqual({start: 1, end: 1});
|
|
expect(XmlUtils.parseRange('1-50')).toEqual({start: 1, end: 50});
|
|
expect(XmlUtils.parseRange('50-1')).toEqual({start: 50, end: 1});
|
|
|
|
expect(XmlUtils.parseRange('-1')).toBeNull();
|
|
expect(XmlUtils.parseRange('1-')).toBeNull();
|
|
expect(XmlUtils.parseRange('1')).toBeNull();
|
|
expect(XmlUtils.parseRange('-')).toBeNull();
|
|
expect(XmlUtils.parseRange('')).toBeNull();
|
|
|
|
expect(XmlUtils.parseRange('abc')).toBeNull();
|
|
expect(XmlUtils.parseRange('a-')).toBeNull();
|
|
expect(XmlUtils.parseRange('-b')).toBeNull();
|
|
expect(XmlUtils.parseRange('a-b')).toBeNull();
|
|
|
|
expect(XmlUtils.parseRange(HUGE_NUMBER_STRING + '-1')).toBeNull();
|
|
expect(XmlUtils.parseRange('1-' + HUGE_NUMBER_STRING)).toBeNull();
|
|
});
|
|
|
|
it('parseInt', () => {
|
|
expect(XmlUtils.parseInt('0')).toBe(0);
|
|
expect(XmlUtils.parseInt('1')).toBe(1);
|
|
expect(XmlUtils.parseInt('191')).toBe(191);
|
|
|
|
expect(XmlUtils.parseInt('-0')).toBe(0);
|
|
expect(XmlUtils.parseInt('-1')).toBe(-1);
|
|
expect(XmlUtils.parseInt('-191')).toBe(-191);
|
|
|
|
expect(XmlUtils.parseInt('abc')).toBeNull();
|
|
expect(XmlUtils.parseInt('1abc')).toBeNull();
|
|
expect(XmlUtils.parseInt('abc1')).toBeNull();
|
|
|
|
expect(XmlUtils.parseInt('0.0')).toBe(0);
|
|
expect(XmlUtils.parseInt('-0.0')).toBe(0);
|
|
|
|
expect(XmlUtils.parseInt('0.1')).toBeNull();
|
|
expect(XmlUtils.parseInt('1.1')).toBeNull();
|
|
|
|
expect(XmlUtils.parseInt(HUGE_NUMBER_STRING)).toBeNull();
|
|
expect(XmlUtils.parseInt('-' + HUGE_NUMBER_STRING)).toBeNull();
|
|
});
|
|
|
|
it('parsePositiveInt', () => {
|
|
expect(XmlUtils.parsePositiveInt('0')).toBeNull();
|
|
expect(XmlUtils.parsePositiveInt('1')).toBe(1);
|
|
expect(XmlUtils.parsePositiveInt('191')).toBe(191);
|
|
|
|
expect(XmlUtils.parsePositiveInt('-0')).toBeNull();
|
|
expect(XmlUtils.parsePositiveInt('-1')).toBeNull();
|
|
expect(XmlUtils.parsePositiveInt('-191')).toBeNull();
|
|
|
|
expect(XmlUtils.parsePositiveInt('abc')).toBeNull();
|
|
expect(XmlUtils.parsePositiveInt('1abc')).toBeNull();
|
|
expect(XmlUtils.parsePositiveInt('abc1')).toBeNull();
|
|
|
|
expect(XmlUtils.parsePositiveInt('0.0')).toBeNull();
|
|
expect(XmlUtils.parsePositiveInt('-0.0')).toBeNull();
|
|
|
|
expect(XmlUtils.parsePositiveInt('0.1')).toBeNull();
|
|
expect(XmlUtils.parsePositiveInt('1.1')).toBeNull();
|
|
|
|
expect(XmlUtils.parsePositiveInt(HUGE_NUMBER_STRING)).toBeNull();
|
|
expect(XmlUtils.parsePositiveInt('-' + HUGE_NUMBER_STRING)).toBeNull();
|
|
});
|
|
|
|
it('parseNonNegativeInt', () => {
|
|
expect(XmlUtils.parseNonNegativeInt('0')).toBe(0);
|
|
expect(XmlUtils.parseNonNegativeInt('1')).toBe(1);
|
|
expect(XmlUtils.parseNonNegativeInt('191')).toBe(191);
|
|
|
|
expect(XmlUtils.parseNonNegativeInt('-0')).toBe(0);
|
|
expect(XmlUtils.parseNonNegativeInt('-1')).toBeNull();
|
|
expect(XmlUtils.parseNonNegativeInt('-191')).toBeNull();
|
|
|
|
expect(XmlUtils.parseNonNegativeInt('abc')).toBeNull();
|
|
expect(XmlUtils.parseNonNegativeInt('1abc')).toBeNull();
|
|
expect(XmlUtils.parseNonNegativeInt('abc1')).toBeNull();
|
|
|
|
expect(XmlUtils.parseNonNegativeInt('0.0')).toBe(0);
|
|
expect(XmlUtils.parseNonNegativeInt('-0.0')).toBe(0);
|
|
|
|
expect(XmlUtils.parseNonNegativeInt('0.1')).toBeNull();
|
|
expect(XmlUtils.parseNonNegativeInt('1.1')).toBeNull();
|
|
|
|
expect(XmlUtils.parseNonNegativeInt(HUGE_NUMBER_STRING)).toBeNull();
|
|
expect(XmlUtils.parseNonNegativeInt('-' + HUGE_NUMBER_STRING)).toBeNull();
|
|
});
|
|
|
|
it('parseFloat', () => {
|
|
expect(XmlUtils.parseFloat('0')).toBe(0);
|
|
expect(XmlUtils.parseFloat('1')).toBe(1);
|
|
expect(XmlUtils.parseFloat('191')).toBe(191);
|
|
|
|
expect(XmlUtils.parseFloat('-0')).toBe(0);
|
|
expect(XmlUtils.parseFloat('-1')).toBe(-1);
|
|
expect(XmlUtils.parseFloat('-191')).toBe(-191);
|
|
|
|
expect(XmlUtils.parseFloat('abc')).toBeNull();
|
|
expect(XmlUtils.parseFloat('1abc')).toBeNull();
|
|
expect(XmlUtils.parseFloat('abc1')).toBeNull();
|
|
|
|
expect(XmlUtils.parseFloat('0.0')).toBe(0);
|
|
expect(XmlUtils.parseFloat('-0.0')).toBe(0);
|
|
|
|
expect(XmlUtils.parseFloat('0.1')).toBeCloseTo(0.1);
|
|
expect(XmlUtils.parseFloat('1.1')).toBeCloseTo(1.1);
|
|
|
|
expect(XmlUtils.parseFloat('19.1134')).toBeCloseTo(19.1134);
|
|
expect(XmlUtils.parseFloat('4e2')).toBeCloseTo(4e2);
|
|
expect(XmlUtils.parseFloat('4e-2')).toBeCloseTo(4e-2);
|
|
|
|
expect(XmlUtils.parseFloat(HUGE_NUMBER_STRING)).toBe(Infinity);
|
|
expect(XmlUtils.parseFloat('-' + HUGE_NUMBER_STRING)).toBe(-Infinity);
|
|
});
|
|
|
|
describe('parseXmlString', () => {
|
|
it('parses a simple XML document', () => {
|
|
const xmlString = [
|
|
'<?xml version="1.0"?>',
|
|
'<Root>',
|
|
' <Child></Child>',
|
|
'</Root>',
|
|
].join('\n');
|
|
const doc = XmlUtils.parseXmlString(xmlString, 'Root');
|
|
expect(doc).not.toBeNull();
|
|
expect(doc.tagName).toBe('Root');
|
|
});
|
|
|
|
it('returns null on an empty XML document', () => {
|
|
const xmlString = '';
|
|
const doc = XmlUtils.parseXmlString(xmlString, 'Root');
|
|
expect(doc).toBeNull();
|
|
});
|
|
|
|
it('returns null on malformed XML', () => {
|
|
const xmlString = [
|
|
'<?xml version="1.0"?>',
|
|
'<Root>',
|
|
' <Child</Child>',
|
|
'</Root>',
|
|
].join('\n');
|
|
const doc = XmlUtils.parseXmlString(xmlString, 'Root');
|
|
expect(doc).toBeNull();
|
|
});
|
|
|
|
it('returns null on root element mismatch', () => {
|
|
const xmlString = [
|
|
'<?xml version="1.0"?>',
|
|
'<Root>',
|
|
' <Child></Child>',
|
|
'</Root>',
|
|
].join('\n');
|
|
const doc = XmlUtils.parseXmlString(xmlString, 'Document');
|
|
expect(doc).toBeNull();
|
|
});
|
|
|
|
it('returns null on XML that embeds HTML', () => {
|
|
const xmlString = [
|
|
'<?xml version="1.0"?>',
|
|
'<Root>',
|
|
' <Child xmlns="http://www.w3.org/1999/xhtml"></Child>',
|
|
'</Root>',
|
|
].join('\n');
|
|
const doc = XmlUtils.parseXmlString(xmlString, 'Root');
|
|
expect(doc).toBeNull();
|
|
});
|
|
|
|
it('returns null on XML that embeds SVG', () => {
|
|
const xmlString = [
|
|
'<?xml version="1.0"?>',
|
|
'<Root>',
|
|
' <Child xmlns="http://www.w3.org/2000/svg"></Child>',
|
|
'</Root>',
|
|
].join('\n');
|
|
const doc = XmlUtils.parseXmlString(xmlString, 'Root');
|
|
expect(doc).toBeNull();
|
|
});
|
|
});
|
|
});
|
|
|