2026-03-11 17:45:28 +01:00
|
|
|
import { assert, expect } from 'chai';
|
2022-01-27 15:40:10 +01:00
|
|
|
import * as mm from '../lib/index.js';
|
|
|
|
|
import path from 'node:path';
|
2026-01-14 11:02:07 +01:00
|
|
|
import AsfGuid from '../lib/asf/AsfGuid.js';
|
2024-08-08 10:19:14 +02:00
|
|
|
import { getParserForAttr } from '../lib/asf/AsfUtil.js';
|
2026-03-11 17:45:28 +01:00
|
|
|
import { AsfContentParseError, DataType } from '../lib/asf/AsfObject.js';
|
2022-01-27 15:40:10 +01:00
|
|
|
import { Parsers } from './metadata-parsers.js';
|
|
|
|
|
|
|
|
|
|
import { samplePath } from './util.js';
|
2024-08-08 10:19:14 +02:00
|
|
|
import type { IPicture } from '../lib/index.js';
|
2017-06-26 11:43:25 +02:00
|
|
|
|
2026-03-11 17:45:28 +01:00
|
|
|
const asfFilePath = path.join(samplePath, 'asf');
|
|
|
|
|
|
2021-03-13 17:11:43 +01:00
|
|
|
describe('Parse ASF', () => {
|
2017-06-26 11:43:25 +02:00
|
|
|
|
2021-03-13 17:11:43 +01:00
|
|
|
describe('GUID', () => {
|
|
|
|
|
it('should construct GUID from string', () => {
|
2017-06-26 11:43:25 +02:00
|
|
|
|
2024-07-09 01:53:02 +08:00
|
|
|
const Header_GUID = Uint8Array.from([
|
2017-07-06 22:20:41 +02:00
|
|
|
0x30, 0x26, 0xB2, 0x75, 0x8E, 0x66, 0xCF, 0x11,
|
|
|
|
|
0xA6, 0xD9, 0x00, 0xAA, 0x00, 0x62, 0xCE, 0x6C
|
|
|
|
|
]);
|
2017-06-26 11:43:25 +02:00
|
|
|
|
2026-01-14 11:02:07 +01:00
|
|
|
assert.deepEqual(AsfGuid.HeaderObject.toBin(), Header_GUID);
|
2017-07-06 22:20:41 +02:00
|
|
|
});
|
2021-03-13 17:11:43 +01:00
|
|
|
|
|
|
|
|
it('should construct GUID from string', () => {
|
|
|
|
|
|
2024-07-09 01:53:02 +08:00
|
|
|
const guid_data = new Uint8Array([48, 38, 178, 117, 142, 102, 207, 17, 166, 217, 0, 170, 0, 98, 206, 108]);
|
2026-01-14 11:02:07 +01:00
|
|
|
assert.deepEqual(AsfGuid.fromBin(guid_data).str, '75B22630-668E-11CF-A6D9-00AA0062CE6C');
|
2021-03-13 17:11:43 +01:00
|
|
|
});
|
2017-07-06 22:20:41 +02:00
|
|
|
});
|
|
|
|
|
|
2017-09-06 22:28:12 +02:00
|
|
|
/**
|
|
|
|
|
* Trying Buffer.readUIntLE(0, 8)
|
|
|
|
|
* Where 8 is 2 bytes longer then maximum allowed of 6
|
|
|
|
|
*/
|
2021-03-13 17:11:43 +01:00
|
|
|
it('should be able to roughly decode a 64-bit QWord', () => {
|
2017-09-06 22:28:12 +02:00
|
|
|
|
2024-07-09 01:53:02 +08:00
|
|
|
const tests: { raw: number[], expected: number, description: string }[] = [
|
2018-09-29 13:36:08 +02:00
|
|
|
{
|
2024-07-09 01:53:02 +08:00
|
|
|
raw: [0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
|
2017-09-06 22:28:12 +02:00
|
|
|
expected: 0xFF,
|
2021-03-13 17:11:43 +01:00
|
|
|
description: '8-bit'
|
2018-09-29 13:36:08 +02:00
|
|
|
},
|
|
|
|
|
{
|
2024-07-09 01:53:02 +08:00
|
|
|
raw: [0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
|
2017-09-06 22:28:12 +02:00
|
|
|
expected: 0xFFFF,
|
2021-03-13 17:11:43 +01:00
|
|
|
description: '16-bit'
|
2018-09-29 13:36:08 +02:00
|
|
|
},
|
|
|
|
|
{
|
2024-07-09 01:53:02 +08:00
|
|
|
raw: [0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00],
|
2017-09-06 22:28:12 +02:00
|
|
|
expected: 0xFFFFFFFF,
|
2021-03-13 17:11:43 +01:00
|
|
|
description: '32-bit'
|
2018-09-29 13:36:08 +02:00
|
|
|
},
|
|
|
|
|
{
|
2024-07-09 01:53:02 +08:00
|
|
|
raw: [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00],
|
2017-09-06 22:28:12 +02:00
|
|
|
expected: 0xFFFFFFFFFF,
|
2021-03-13 17:11:43 +01:00
|
|
|
description: '40-bit'
|
2018-09-29 13:36:08 +02:00
|
|
|
},
|
|
|
|
|
{
|
2024-07-09 01:53:02 +08:00
|
|
|
raw: [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00],
|
2017-09-06 22:28:12 +02:00
|
|
|
expected: 0xFFFFFFFFFFFF,
|
2021-03-13 17:11:43 +01:00
|
|
|
description: '48-bit'
|
2018-09-29 13:36:08 +02:00
|
|
|
},
|
|
|
|
|
{
|
2024-07-09 01:53:02 +08:00
|
|
|
raw: [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x00],
|
2017-09-06 22:28:12 +02:00
|
|
|
expected: 0xFFFFFFFFFFFFF,
|
2021-03-13 17:11:43 +01:00
|
|
|
description: '52-bit'
|
2018-09-29 13:36:08 +02:00
|
|
|
}
|
2017-09-06 22:28:12 +02:00
|
|
|
];
|
|
|
|
|
|
2017-09-09 11:58:58 +02:00
|
|
|
tests.forEach(test => {
|
2024-07-09 01:53:02 +08:00
|
|
|
const buf = Uint8Array.from(test.raw);
|
2024-08-08 10:19:14 +02:00
|
|
|
assert.strictEqual(Number(getParserForAttr(DataType.QWord)(buf)), test.expected, test.description);
|
2017-09-06 22:28:12 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
2021-03-13 17:11:43 +01:00
|
|
|
describe('parse', () => {
|
2017-07-06 22:20:41 +02:00
|
|
|
|
|
|
|
|
function checkFormat(format) {
|
2020-01-05 12:09:00 +01:00
|
|
|
assert.strictEqual(format.container, 'ASF/audio', 'format.container');
|
|
|
|
|
assert.strictEqual(format.codec, 'Windows Media Audio 9.1', 'format.codec');
|
2021-03-13 17:11:43 +01:00
|
|
|
assert.approximately(format.duration, 243.306, 1 / 10000, 'format.duration');
|
2020-01-05 12:09:00 +01:00
|
|
|
assert.strictEqual(format.bitrate, 192639, 'format.bitrate');
|
2025-06-28 20:21:18 +02:00
|
|
|
assert.isTrue(format.hasAudio, 'format.hasAudio');
|
|
|
|
|
assert.isFalse(format.hasVideo, 'format.hasVideo');
|
2017-07-06 22:20:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function checkCommon(common) {
|
2021-03-13 17:11:43 +01:00
|
|
|
assert.strictEqual(common.title, 'Don\'t Bring Me Down', 'common.title');
|
2020-01-05 12:09:00 +01:00
|
|
|
assert.deepEqual(common.artist, 'Electric Light Orchestra', 'common.artist');
|
|
|
|
|
assert.deepEqual(common.albumartist, 'Electric Light Orchestra', 'common.albumartist');
|
|
|
|
|
assert.strictEqual(common.album, 'Discovery', 'common.album');
|
|
|
|
|
assert.strictEqual(common.year, 2001, 'common.year');
|
|
|
|
|
assert.deepEqual(common.track, {no: 9, of: null}, 'common.track 9/0');
|
|
|
|
|
assert.deepEqual(common.disk, {no: null, of: null}, 'common.disk 0/0');
|
|
|
|
|
assert.deepEqual(common.genre, ['Rock'], 'common.genre');
|
2017-07-06 22:20:41 +02:00
|
|
|
}
|
|
|
|
|
|
2019-04-12 08:33:54 +02:00
|
|
|
function checkNative(native: mm.INativeTagDict) {
|
2017-07-06 22:20:41 +02:00
|
|
|
|
2020-01-05 12:09:00 +01:00
|
|
|
assert.deepEqual(native['WM/AlbumTitle'], ['Discovery'], 'native: WM/AlbumTitle');
|
|
|
|
|
assert.deepEqual(native['WM/BeatsPerMinute'], [117], 'native: WM/BeatsPerMinute');
|
|
|
|
|
assert.deepEqual(native.REPLAYGAIN_TRACK_GAIN, ['-4.7 dB'], 'native: REPLAYGAIN_TRACK_GAIN');
|
2017-07-06 22:20:41 +02:00
|
|
|
}
|
|
|
|
|
|
2021-03-13 17:11:43 +01:00
|
|
|
describe('should decode an ASF audio file (.wma)', () => {
|
2017-07-06 22:20:41 +02:00
|
|
|
|
2018-09-10 21:20:00 +02:00
|
|
|
Parsers.forEach(parser => {
|
2024-07-08 20:56:07 +02:00
|
|
|
it(parser.description, async function(){
|
2025-01-25 19:05:46 +01:00
|
|
|
const {format, common, native} = await parser.parse(() => this.skip(), path.join(asfFilePath, 'asf.wma'), 'audio/x-ms-wma');
|
2025-01-18 15:03:05 +01:00
|
|
|
checkFormat(format);
|
|
|
|
|
checkCommon(common);
|
|
|
|
|
assert.isDefined(native, 'metadata.native');
|
|
|
|
|
assert.isDefined(native.asf, 'should include native ASF tags');
|
|
|
|
|
checkNative(mm.orderTags(native.asf));
|
2018-09-10 21:20:00 +02:00
|
|
|
});
|
2017-08-13 21:31:06 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
2021-03-13 17:11:43 +01:00
|
|
|
describe('should decode picture from', () => {
|
2018-09-10 21:20:00 +02:00
|
|
|
|
|
|
|
|
Parsers.forEach(parser => {
|
2024-07-08 20:56:07 +02:00
|
|
|
it(parser.description, async function(){
|
2020-01-05 20:16:43 +01:00
|
|
|
const filePath = path.join(asfFilePath, 'issue_57.wma');
|
2025-01-25 19:05:46 +01:00
|
|
|
const {native} = await parser.parse(() => this.skip(), filePath, 'audio/x-ms-wma');
|
2025-01-18 15:03:05 +01:00
|
|
|
const asf = mm.orderTags(native.asf);
|
2019-11-17 17:10:02 +01:00
|
|
|
assert.exists(asf['WM/Picture'][0], 'ASF WM/Picture should be set');
|
|
|
|
|
const nativePicture = asf['WM/Picture'][0];
|
2024-08-08 10:19:14 +02:00
|
|
|
assert.exists((nativePicture as IPicture).data);
|
2018-09-10 21:20:00 +02:00
|
|
|
});
|
2017-08-13 21:31:06 +02:00
|
|
|
});
|
2017-07-06 22:20:41 +02:00
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
2018-03-20 21:33:51 +01:00
|
|
|
/**
|
|
|
|
|
* Related issue: https://github.com/Borewit/music-metadata/issues/68
|
|
|
|
|
*/
|
2021-03-13 17:11:43 +01:00
|
|
|
it('should be able to parse truncated .wma file', async () => {
|
2018-03-20 21:33:51 +01:00
|
|
|
|
2020-01-05 20:16:43 +01:00
|
|
|
const filePath = path.join(asfFilePath, '13 Thirty Dirty Birds.wma');
|
2018-03-20 21:33:51 +01:00
|
|
|
|
2020-01-05 12:09:00 +01:00
|
|
|
const {format, common, native} = await mm.parseFile(filePath);
|
|
|
|
|
|
|
|
|
|
assert.strictEqual(format.container, 'ASF/audio', 'format.container');
|
|
|
|
|
assert.strictEqual(format.codec, 'Windows Media Audio 9', 'format.codec');
|
2021-04-04 12:59:55 +02:00
|
|
|
assert.approximately(format.duration, 14.466, 1 / 10000, 'format.duration');
|
2020-01-05 12:09:00 +01:00
|
|
|
assert.approximately(format.bitrate, 128639, 1, 'format.bitrate');
|
2025-06-28 20:21:18 +02:00
|
|
|
assert.isTrue(format.hasAudio, 'format.hasAudio');
|
2026-03-11 17:45:28 +01:00
|
|
|
assert.isFalse(format.hasVideo, 'format.hasVideo'); });
|
2018-03-20 21:33:51 +01:00
|
|
|
|
2026-03-11 17:45:28 +01:00
|
|
|
});
|
2018-03-20 21:33:51 +01:00
|
|
|
|
2026-03-26 15:59:14 +01:00
|
|
|
describe('security hardening', () => {
|
|
|
|
|
|
|
|
|
|
it('Avoid infinite loop CWE-835', async () => {
|
|
|
|
|
const filePath = path.join(asfFilePath, 'CWE-835.wma');
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await mm.parseFile(filePath);
|
|
|
|
|
expect.fail('Expected parseFile to throw AsfContentParseError');
|
|
|
|
|
} catch (err) {
|
|
|
|
|
expect(err).to.be.instanceOf(AsfContentParseError);
|
|
|
|
|
expect((err as Error).message).to.match(/Invalid ASF header object size/);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('numberOfObjectHeaders=4294967295', async () => {
|
|
|
|
|
const filePath = path.join(asfFilePath, 'max-numberOfObjectHeaders.wma');
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await mm.parseFile(filePath);
|
|
|
|
|
expect.fail('Expected parseFile to throw AsfContentParseError');
|
|
|
|
|
} catch (err) {
|
|
|
|
|
expect(err).to.be.instanceOf(AsfContentParseError);
|
|
|
|
|
expect((err as Error).message).to.match(/Unrealistic number of ASF header objects/);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2017-07-06 22:20:41 +02:00
|
|
|
});
|
2017-06-26 11:43:25 +02:00
|
|
|
|
|
|
|
|
});
|