mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-23 17:52:49 +00:00
fix(files): report document MIME types for binary files
File reads now carry the document's MIME type for binary files, so a file panel can hand the bytes to a viewer that needs the real type (application/pdf).
This commit is contained in:
@@ -19,6 +19,8 @@ import {
|
||||
deleteExplorerEntry,
|
||||
duplicateExplorerEntry,
|
||||
getExplorerFileVersion,
|
||||
getDownloadableFileInfo,
|
||||
readExplorerFileBytes,
|
||||
readExplorerFile,
|
||||
renameExplorerEntry,
|
||||
streamExplorerFile,
|
||||
@@ -192,6 +194,36 @@ describe("file explorer service", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it.each([
|
||||
["report.pdf", Buffer.from("%PDF-1.4\n%\x00\xff\n%%EOF", "binary")],
|
||||
["report.PDF", Buffer.from("%PDF-1.4\n%%EOF")],
|
||||
])("reports application/pdf for %s across file reads and downloads", async (name, bytes) => {
|
||||
const root = await createTempDir("paseo-file-pdf-");
|
||||
try {
|
||||
await writeFile(path.join(root, name), bytes);
|
||||
const target = { root, relativePath: name };
|
||||
expect(await readExplorerFile(target)).toMatchObject({
|
||||
kind: "binary",
|
||||
mimeType: "application/pdf",
|
||||
});
|
||||
expect(await readExplorerFileBytes(target)).toMatchObject({
|
||||
kind: "binary",
|
||||
mimeType: "application/pdf",
|
||||
bytes,
|
||||
});
|
||||
await streamExplorerFile(target, async (file) => {
|
||||
expect(file.kind).toBe("binary");
|
||||
expect(file.mimeType).toBe("application/pdf");
|
||||
const chunks: Uint8Array[] = [];
|
||||
for await (const chunk of file.chunks) chunks.push(chunk);
|
||||
expect(Buffer.concat(chunks)).toEqual(bytes);
|
||||
});
|
||||
expect(await getDownloadableFileInfo(target)).toMatchObject({ mimeType: "application/pdf" });
|
||||
} finally {
|
||||
await rm(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("classifies files with null bytes as binary", async () => {
|
||||
const root = await createTempDir("paseo-file-explorer-");
|
||||
|
||||
|
||||
@@ -92,6 +92,15 @@ const TEXT_MIME_TYPES: Record<string, string> = {
|
||||
".json": "application/json",
|
||||
};
|
||||
|
||||
const BINARY_MIME_TYPES: Record<string, string> = {
|
||||
".pdf": "application/pdf",
|
||||
".zip": "application/zip",
|
||||
".gz": "application/gzip",
|
||||
".mp3": "audio/mpeg",
|
||||
".mp4": "video/mp4",
|
||||
".woff2": "font/woff2",
|
||||
};
|
||||
|
||||
const DEFAULT_TEXT_MIME_TYPE = "text/plain";
|
||||
const FILE_TYPE_SAMPLE_BYTES = 8192;
|
||||
export const FILE_EXPLORER_STREAM_CHUNK_BYTES = 256 * 1024;
|
||||
@@ -266,13 +275,13 @@ export async function readExplorerFileBytes({
|
||||
};
|
||||
}
|
||||
|
||||
if (isLikelyBinary(buffer) || !isValidUtf8(buffer)) {
|
||||
if (ext in BINARY_MIME_TYPES || isLikelyBinary(buffer) || !isValidUtf8(buffer)) {
|
||||
return {
|
||||
...basePayload,
|
||||
kind: "binary",
|
||||
encoding: "binary",
|
||||
bytes: buffer,
|
||||
mimeType: "application/octet-stream",
|
||||
mimeType: BINARY_MIME_TYPES[ext] ?? "application/octet-stream",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -305,7 +314,8 @@ export async function streamExplorerFile(
|
||||
const advertisedRevision = fileRevision(stats);
|
||||
const ext = path.extname(filePath.resolvedPath).toLowerCase();
|
||||
const isImage = ext in IMAGE_MIME_TYPES;
|
||||
const isBinary = isImage || (await isFileHandleBinary(handle, advertisedSize));
|
||||
const isBinary =
|
||||
isImage || ext in BINARY_MIME_TYPES || (await isFileHandleBinary(handle, advertisedSize));
|
||||
let kind: ExplorerFileKind = "text";
|
||||
let mimeType = textMimeTypeForExtension(ext);
|
||||
if (isImage) {
|
||||
@@ -313,7 +323,7 @@ export async function streamExplorerFile(
|
||||
mimeType = IMAGE_MIME_TYPES[ext];
|
||||
} else if (isBinary) {
|
||||
kind = "binary";
|
||||
mimeType = "application/octet-stream";
|
||||
mimeType = BINARY_MIME_TYPES[ext] ?? "application/octet-stream";
|
||||
}
|
||||
|
||||
await consume({
|
||||
@@ -549,10 +559,10 @@ export async function getDownloadableFileInfo({ root, relativePath }: ReadFilePa
|
||||
}
|
||||
|
||||
const ext = path.extname(filePath.resolvedPath).toLowerCase();
|
||||
let mimeType = "application/octet-stream";
|
||||
let mimeType = BINARY_MIME_TYPES[ext] ?? "application/octet-stream";
|
||||
if (ext in IMAGE_MIME_TYPES) {
|
||||
mimeType = IMAGE_MIME_TYPES[ext];
|
||||
} else {
|
||||
} else if (!(ext in BINARY_MIME_TYPES)) {
|
||||
const sample = Buffer.alloc(FILE_TYPE_SAMPLE_BYTES);
|
||||
const { bytesRead } = await handle.read(sample, 0, sample.length, 0);
|
||||
const chunk = bytesRead < sample.length ? sample.subarray(0, bytesRead) : sample;
|
||||
|
||||
Reference in New Issue
Block a user