76 lines
2.1 KiB
JavaScript
76 lines
2.1 KiB
JavaScript
// @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;
|
|
})
|
|
));
|
|
};
|