// @ts-check import { SUPPORTED_FILE_TYPES } from "./constants.js"; /** * Figure out the route for a given file. * * For example, PNG files should get routed to `/png`. * * @param {File} file * @returns {Promise} The route for the file, or null if the file is not supported. */ export default async (file) => { const fileType = findFileTypeUsingMimeType(file.type) ?? fineFileTypeUsingFileName(file.name) ?? (await findFileTypeWithMimeSniffing(file)); return fileType ? fileType.route : null; }; /** * @param {string} mimeType */ const findFileTypeUsingMimeType = (mimeType) => SUPPORTED_FILE_TYPES.find((t) => t.mimeType === mimeType); /** * @param {string} name */ const fineFileTypeUsingFileName = (name) => { if (!name.includes(".")) return; return SUPPORTED_FILE_TYPES.find((fileType) => fileType.extensions.some((extension) => name.endsWith(extension)) ); }; /** * @returns {number} */ const getLongestMimeSniffPattern = () => { let result = 0; for (const fileType of SUPPORTED_FILE_TYPES) { for (const pattern of fileType.mimeSniffPatterns) { result = Math.max(result, pattern.bytes.length); } } return result; }; /** * See the ["MIME Sniffing" spec][0]. * [0]: https://mimesniff.spec.whatwg.org * @param {Blob} file */ const findFileTypeWithMimeSniffing = async (file) => { // We only need to sample the first few bytes. const sampleLength = getLongestMimeSniffPattern(); const sampleAsBlob = file.slice(0, sampleLength); const sampleAsBuffer = await sampleAsBlob.arrayBuffer(); const sample = new Uint8Array(sampleAsBuffer); return SUPPORTED_FILE_TYPES.find(({ mimeSniffPatterns }) => ( mimeSniffPatterns.some(({ bytes, mask }) => { // Roughly follows the [pattern matching algorithm from the spec][1]. // [1]: https://mimesniff.spec.whatwg.org/#pattern-matching-algorithm if (sample.length < bytes.length) return false; for (let p = 0; p < bytes.length; p++) { const maskedData = sample[p] & mask[p]; if (maskedData !== bytes[p]) return false; } return true; }) )); };