formats.exposed/public/index/fileRouter.js

68 lines
2.0 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 const routeFile = 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))
);
};
/**
* 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 = SUPPORTED_FILE_TYPES
.map((t) => t.mimeSniffBytePattern.length)
.reduce((a, b) => Math.max(a, b), 0);
const sampleAsBlob = file.slice(0, sampleLength);
const sampleAsBuffer = await sampleAsBlob.arrayBuffer();
const sample = new Uint8Array(sampleAsBuffer);
return SUPPORTED_FILE_TYPES.find((fileType) => {
// Roughly follows the [pattern matching algorithm from the spec][1].
// [1]: https://mimesniff.spec.whatwg.org/#pattern-matching-algorithm
const { mimeSniffBytePattern, mimeSniffPatternMask } = fileType;
console.assert(mimeSniffBytePattern.length === mimeSniffPatternMask.length);
if (sample.length < mimeSniffBytePattern.length) return false;
for (let p = 0; p < mimeSniffBytePattern.length; p++) {
const maskedData = sample[p] & mimeSniffPatternMask[p];
if (maskedData !== mimeSniffBytePattern[p]) return false;
}
return true;
});
};