68 lines
2.0 KiB
JavaScript
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;
|
||
|
});
|
||
|
};
|