file-type
Version:
Detect the file type of a file, stream, or data
128 lines (113 loc) • 3.22 kB
JavaScript
import * as Token from 'token-types';
import * as strtok3 from 'strtok3/core';
import {
maximumUntrustedSkipSizeInBytes,
ParserHardLimitError,
checkBytes,
safeReadBuffer,
safeIgnore,
hasUnknownFileSize,
hasExceededUnknownSizeScanBudget,
} from '../parser.js';
const maximumAsfHeaderObjectCount = 512;
const maximumAsfHeaderPayloadSizeInBytes = 1024 * 1024;
export async function detectAsf(tokenizer) {
let isMalformedAsf = false;
try {
async function readHeader() {
const guid = new Uint8Array(16);
await safeReadBuffer(tokenizer, guid, undefined, {
maximumLength: guid.length,
reason: 'ASF header GUID',
});
return {
id: guid,
size: Number(await tokenizer.readToken(Token.UINT64_LE)),
};
}
await safeIgnore(tokenizer, 30, {
maximumLength: 30,
reason: 'ASF header prelude',
});
const isUnknownFileSize = hasUnknownFileSize(tokenizer);
const asfHeaderScanStart = tokenizer.position;
let asfHeaderObjectCount = 0;
while (tokenizer.position + 24 < tokenizer.fileInfo.size) {
asfHeaderObjectCount++;
if (asfHeaderObjectCount > maximumAsfHeaderObjectCount) {
break;
}
if (hasExceededUnknownSizeScanBudget(tokenizer, asfHeaderScanStart, maximumUntrustedSkipSizeInBytes)) {
break;
}
const previousPosition = tokenizer.position;
const header = await readHeader();
let payload = header.size - 24;
if (
!Number.isFinite(payload)
|| payload < 0
) {
isMalformedAsf = true;
break;
}
if (checkBytes(header.id, [0x91, 0x07, 0xDC, 0xB7, 0xB7, 0xA9, 0xCF, 0x11, 0x8E, 0xE6, 0x00, 0xC0, 0x0C, 0x20, 0x53, 0x65])) {
// Sync on Stream-Properties-Object (B7DC0791-A9B7-11CF-8EE6-00C00C205365)
const typeId = new Uint8Array(16);
payload -= await safeReadBuffer(tokenizer, typeId, undefined, {
maximumLength: typeId.length,
reason: 'ASF stream type GUID',
});
if (checkBytes(typeId, [0x40, 0x9E, 0x69, 0xF8, 0x4D, 0x5B, 0xCF, 0x11, 0xA8, 0xFD, 0x00, 0x80, 0x5F, 0x5C, 0x44, 0x2B])) {
// Found audio:
return {
ext: 'asf',
mime: 'audio/x-ms-asf',
};
}
if (checkBytes(typeId, [0xC0, 0xEF, 0x19, 0xBC, 0x4D, 0x5B, 0xCF, 0x11, 0xA8, 0xFD, 0x00, 0x80, 0x5F, 0x5C, 0x44, 0x2B])) {
// Found video:
return {
ext: 'asf',
mime: 'video/x-ms-asf',
};
}
break;
}
if (
isUnknownFileSize
&& payload > maximumAsfHeaderPayloadSizeInBytes
) {
isMalformedAsf = true;
break;
}
await safeIgnore(tokenizer, payload, {
maximumLength: isUnknownFileSize ? maximumAsfHeaderPayloadSizeInBytes : tokenizer.fileInfo.size,
reason: 'ASF header payload',
});
// Safeguard against malformed files: break if the position did not advance.
if (tokenizer.position <= previousPosition) {
isMalformedAsf = true;
break;
}
}
} catch (error) {
if (
error instanceof strtok3.EndOfStreamError
|| error instanceof ParserHardLimitError
) {
if (hasUnknownFileSize(tokenizer)) {
isMalformedAsf = true;
}
} else {
throw error;
}
}
if (isMalformedAsf) {
return;
}
// Default to ASF generic extension
return {
ext: 'asf',
mime: 'application/vnd.ms-asf',
};
}