mcard-js
Version:
MCard - Content-addressable storage with cryptographic hashing, handle resolution, and vector search for Node.js and browsers
93 lines • 4.21 kB
JavaScript
export class BinarySignatureDetector {
contentTypeName = "binary";
// Signatures map: [Signature Bytes, Mime Type]
static SIGNATURES = [
[new Uint8Array([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]), 'image/png'],
[new Uint8Array([0xFF, 0xD8, 0xFF]), 'image/jpeg'],
[new Uint8Array([0x47, 0x49, 0x46, 0x38, 0x37, 0x61]), 'image/gif'], // GIF87a
[new Uint8Array([0x47, 0x49, 0x46, 0x38, 0x39, 0x61]), 'image/gif'], // GIF89a
[new Uint8Array([0x42, 0x4D]), 'image/bmp'], // BM
[new Uint8Array([0x00, 0x00, 0x01, 0x00]), 'image/x-icon'],
[new Uint8Array([0x00, 0x00, 0x02, 0x00]), 'image/x-icon'],
[new Uint8Array([0x25, 0x50, 0x44, 0x46]), 'application/pdf'], // %PDF
[new Uint8Array([0x50, 0x4B, 0x03, 0x04]), 'application/zip'], // PK..
[new Uint8Array([0x1F, 0x8B, 0x08]), 'application/gzip'],
[new Uint8Array([0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x00]), 'application/x-rar-compressed'],
[new Uint8Array([0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C]), 'application/x-7z-compressed'],
[new Uint8Array([0x53, 0x51, 0x4C, 0x69, 0x74, 0x65, 0x20, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x20, 0x33, 0x00]), 'application/x-sqlite3'],
];
detect(contentSample, lines, firstLine, fileExtension) {
const mime = this.getMimeType(contentSample, lines, firstLine, fileExtension);
return (mime && mime !== 'application/octet-stream') ? 0.95 : 0.0;
}
getMimeType(contentSample, lines, firstLine, fileExtension) {
// Prepare bytes
const bytes = this.toBytes(contentSample);
// Delegate to pure byte detection
const detected = this.detectFromBytes(bytes);
// Return null if generic octet-stream (BaseDetector interface expects string | null)
if (detected === 'application/octet-stream') {
return 'application/octet-stream';
}
return detected;
}
/**
* Detect MIME type directly from bytes.
*/
detectFromBytes(bytes) {
// RIFF check
if (this.startsWith(bytes, new Uint8Array([0x52, 0x49, 0x46, 0x46]))) { // RIFF
return this.detectRiffFormat(bytes);
}
for (const [sig, mime] of BinarySignatureDetector.SIGNATURES) {
if (this.startsWith(bytes, sig)) {
if (mime === 'application/zip') {
return this.detectZipType(bytes);
}
return mime;
}
}
return 'application/octet-stream';
}
toBytes(content) {
if (content instanceof Uint8Array)
return content;
return new TextEncoder().encode(content);
}
startsWith(data, prefix) {
if (data.length < prefix.length)
return false;
for (let i = 0; i < prefix.length; i++) {
if (data[i] !== prefix[i])
return false;
}
return true;
}
detectRiffFormat(bytes) {
if (bytes.length < 12)
return 'application/octet-stream';
// Offset 8: 4 bytes
const format = new TextDecoder().decode(bytes.slice(8, 12));
if (format === 'WAVE')
return 'audio/wav';
if (format === 'WEBP')
return 'image/webp';
return 'application/octet-stream';
}
detectZipType(bytes) {
// Basic contains check for office signatures
// Converting first 2KB to string (ignoring errors) to search
// Note: TextDecoder replacement char is .
const header = new TextDecoder().decode(bytes.slice(0, 2048));
if (header.includes('[Content_Types].xml') && header.includes('_rels/.rels')) {
if (header.includes('word/'))
return 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
if (header.includes('xl/'))
return 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
if (header.includes('ppt/'))
return 'application/vnd.openxmlformats-officedocument.presentationml.presentation';
}
return 'application/zip';
}
}
//# sourceMappingURL=BinaryDetector.js.map