formats.exposed/public/index/routeFile.js

76 lines
2.1 KiB
JavaScript
Raw Normal View History

// @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<null | string>} 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;
})
));
};