Files
FileHost/server/utils/file-signature.ts
T
O96u bc4cba7e52
Publish Docker image / build-and-push (push) Canceled after 0s
ci / ci (22, ubuntu-latest) (push) Canceled after 0s
release: PicHost v1.0.0
Multi-user image hosting with RBAC, upload preferences, and admin settings.
2026-08-22 14:36:57 +08:00

83 lines
2.3 KiB
TypeScript

import type { AllowedMimeType } from './constants'
function matchJpeg(bytes: Uint8Array): boolean {
return bytes.length >= 3
&& bytes[0] === 0xFF
&& bytes[1] === 0xD8
&& bytes[2] === 0xFF
}
function matchPng(bytes: Uint8Array): boolean {
const signature = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]
if (bytes.length < signature.length) return false
return signature.every((byte, index) => bytes[index] === byte)
}
function matchGif(bytes: Uint8Array): boolean {
if (bytes.length < 6) return false
const header = new TextDecoder().decode(bytes.subarray(0, 6))
return header === 'GIF87a' || header === 'GIF89a'
}
function matchWebp(bytes: Uint8Array): boolean {
if (bytes.length < 12) return false
const riff = new TextDecoder().decode(bytes.subarray(0, 4))
const webp = new TextDecoder().decode(bytes.subarray(8, 12))
return riff === 'RIFF' && webp === 'WEBP'
}
/** ICO 文件头:00 00 01 00 */
function matchIco(bytes: Uint8Array): boolean {
return bytes.length >= 6
&& bytes[0] === 0x00
&& bytes[1] === 0x00
&& bytes[2] === 0x01
&& bytes[3] === 0x00
}
/** SVG 是 XML 文本,无二进制签名,按内容开头识别 */
function matchSvg(bytes: Uint8Array): boolean {
if (!bytes.length) return false
const head = new TextDecoder('utf-8', { fatal: false })
.decode(bytes.subarray(0, 1024))
const trimmed = head.replace(/^\uFEFF/, '').trimStart()
if (
!trimmed.startsWith('<?xml')
&& !trimmed.startsWith('<svg')
&& !trimmed.startsWith('<!--')
) {
return false
}
return /<svg[\s>]/i.test(head)
}
const SIGNATURE_CHECKS: Record<AllowedMimeType, (bytes: Uint8Array) => boolean> = {
'image/jpeg': matchJpeg,
'image/png': matchPng,
'image/gif': matchGif,
'image/webp': matchWebp,
'image/x-icon': matchIco,
'image/svg+xml': matchSvg
}
export function isAllowedMimeType(mime: string): mime is AllowedMimeType {
return mime in SIGNATURE_CHECKS
}
export function validateFileSignature(
mime: AllowedMimeType,
bytes: Uint8Array
): boolean {
const checker = SIGNATURE_CHECKS[mime]
return checker(bytes)
}
export function detectMimeFromSignature(bytes: Uint8Array): AllowedMimeType | null {
for (const mime of Object.keys(SIGNATURE_CHECKS) as AllowedMimeType[]) {
if (SIGNATURE_CHECKS[mime](bytes)) {
return mime
}
}
return null
}