import {fromFile} from 'file-type'; import {extension, lookup} from 'mime-types'; import * as path from 'path'; /** * Get our best guess of the file extension, based on its original extension (as received from the * user), mimeType (as reported by the browser upload, or perhaps some API), and the file * contents. * * The resulting extension is used to choose a parser for imports, and to present the file back * to the user for attachments. */ export async function guessExt(filePath: string, fileName: string, mimeType: string|null): Promise { const origExt = path.extname(fileName).toLowerCase(); // Has the form ".xls" let mimeExt = extension(mimeType); // Has the form "xls" mimeExt = mimeExt ? "." + mimeExt : null; // Use the more comparable form ".xls" if (mimeExt === ".json") { // It's common for JSON APIs to specify MIME type, but origExt might come from a URL with // periods that don't indicate a meaningful extension. Trust mime-type here. return mimeExt; } if (origExt === ".csv" || origExt === ".xls") { // File type detection doesn't work for these, and mime type can't be trusted. E.g. Windows // may report "application/vnd.ms-excel" for .csv files. See // https://github.com/ManifoldScholar/manifold/issues/2409#issuecomment-545152220 return origExt; } // If extension and mime type agree, let's call it a day. if (origExt && (origExt === mimeExt || lookup(origExt.slice(1)) === mimeType)) { return origExt; } // If not, let's take a look at the file contents. const detected = await fromFile(filePath); const detectedExt = detected ? "." + detected.ext : null; if (detectedExt) { // For the types for which detection works, we think we should prefer it. return detectedExt; } if (mimeExt === '.txt' || mimeExt === '.bin') { // text/plain (txt) and application/octet-stream (bin) are too generic, only use them if we // don't have anything better. return origExt || mimeExt; } // In other cases, it's a tough call. return origExt || mimeExt; }