mirror of
https://github.com/TautvydasDerzinskas/Thingport.git
synced 2026-10-19 05:10:01 -05:00
fix: file upload file name utf8 issues
This commit is contained in:
1 parent
b73299222a
commit
6d34243b89
3 files changed
+38
-3
No files matched your search
@@ -12,8 +12,17 @@ const diskStorage = multer.diskStorage({
|
||||
},
|
||||
});
|
||||
|
||||
// Browsers send multipart filenames as raw UTF-8 bytes, but multer decodes them as Latin-1 by
|
||||
// default -- `哨子.3mf` would arrive as mojibake, and sanitizeFilename then strips the C1 bytes
|
||||
// needed to recover it.
|
||||
const defParamCharset = "utf8";
|
||||
|
||||
/** Disk-backed multer instance for model file uploads (never memoryStorage — files can be large). */
|
||||
export const modelUpload = multer({ storage: diskStorage, limits: { fileSize: IMPORT_MAX_BYTES } });
|
||||
export const modelUpload = multer({ storage: diskStorage, limits: { fileSize: IMPORT_MAX_BYTES }, defParamCharset });
|
||||
|
||||
/** Small in-memory multer instance for the 8MB-capped client-rendered thumbnail upload. */
|
||||
export const thumbnailUpload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 8 * 1024 * 1024 + 1 } });
|
||||
export const thumbnailUpload = multer({
|
||||
storage: multer.memoryStorage(),
|
||||
limits: { fileSize: 8 * 1024 * 1024 + 1 },
|
||||
defParamCharset,
|
||||
});
|
||||
@@ -28,7 +28,9 @@ export function sanitizeFilename(name: string | null | undefined): string {
|
||||
* alone instead of being mangled by the fix.
|
||||
*/
|
||||
function decodeLatin1AsUtf8(value: string): string {
|
||||
if (!/[\u0080-\u00ff]/.test(value)) return value;
|
||||
// Only a pure byte string can be one; anything above U+00FF was already decoded properly, and
|
||||
// Buffer.from(..., "latin1") would silently truncate it.
|
||||
if (!/[\u0080-\u00ff]/.test(value) || /[\u0100-\uffff]/.test(value)) return value;
|
||||
const bytes = Buffer.from(value, "latin1");
|
||||
const decoded = bytes.toString("utf8");
|
||||
return Buffer.from(decoded, "utf8").equals(bytes) ? decoded : value;
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import express from "express";
|
||||
import request from "supertest";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { modelUpload } from "../src/uploadMiddleware";
|
||||
import { buildImportFilename, parseContentDisposition, sanitizeFilename } from "../src/utils/fileUtils";
|
||||
|
||||
// HTTP header values are ISO-8859-1 (RFC 7230 section 3.2.4). A server that puts raw UTF-8 bytes
|
||||
@@ -59,3 +62,24 @@ describe("buildImportFilename", () => {
|
||||
expect(/[\u0000-\u001f\u007f-\u009f]/.test(name)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseContentDisposition with already-decoded input", () => {
|
||||
it("leaves a string with characters above U+00FF alone", () => {
|
||||
// Not a byte string, so there is nothing to re-decode -- and latin1 would truncate `哨`.
|
||||
expect(parseContentDisposition('attachment; filename="café哨.stl"')).toBe("café哨.stl");
|
||||
});
|
||||
});
|
||||
|
||||
describe("modelUpload", () => {
|
||||
it("decodes a browser's raw UTF-8 multipart filename as UTF-8, not Latin-1", async () => {
|
||||
const app = express();
|
||||
app.post("/upload", modelUpload.single("file"), (req, res) => {
|
||||
res.json({ name: sanitizeFilename(req.file?.originalname) });
|
||||
});
|
||||
const res = await request(app)
|
||||
.post("/upload")
|
||||
.attach("file", Buffer.from("solid x\nendsolid x\n"), { filename: "哨子.stl", contentType: "model/stl" });
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.name).toBe("哨子.stl");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user