mirror of
https://github.com/screentinker/screentinker.git
synced 2026-05-15 07:32:23 -06:00
The previous comment claimed defParamCharset:'utf8' fixed multipart
filename header decoding. It doesn't - that option only fires for the
RFC 5987 encoded filename*=utf-8''... form, which clients rarely send.
The actual UTF-8 recovery happens in the storage.filename callback
(added in d679ca8) via Buffer.from(name,'latin1').toString('utf8').
The option is kept set for the rare RFC 5987 case but the comment no
longer overclaims what it does.
58 lines
2.4 KiB
JavaScript
58 lines
2.4 KiB
JavaScript
const multer = require('multer');
|
|
const path = require('path');
|
|
const { v4: uuidv4 } = require('uuid');
|
|
const config = require('../config');
|
|
|
|
const storage = multer.diskStorage({
|
|
destination: (req, file, cb) => {
|
|
cb(null, config.contentDir);
|
|
},
|
|
filename: (req, file, cb) => {
|
|
// busboy decodes the Content-Disposition filename header as latin1 by
|
|
// default. Modern clients send raw UTF-8 bytes for non-ASCII filenames
|
|
// (e.g. browsers + curl on UTF-8 locales send "Begrussungsscreens.jpg"
|
|
// with c3 bc for u-umlaut). Reading those bytes as latin1 produces the
|
|
// string "A-tilde + quarter-mark" which JS then re-encodes as 4 UTF-8
|
|
// bytes on the way to the DB - classic double-encoding mojibake.
|
|
//
|
|
// The `defParamCharset: 'utf8'` option below only takes effect for
|
|
// RFC 5987 encoded `filename*=...` params, which most clients don't send.
|
|
// For the plain `filename="..."` case, re-decode here to recover the
|
|
// original UTF-8 byte sequence. Mutating originalname here propagates to
|
|
// every downstream consumer (route handlers reading req.file.originalname).
|
|
if (file.originalname) {
|
|
file.originalname = Buffer.from(file.originalname, 'latin1').toString('utf8');
|
|
}
|
|
const ext = path.extname(file.originalname);
|
|
cb(null, `${uuidv4()}${ext}`);
|
|
}
|
|
});
|
|
|
|
const fileFilter = (req, file, cb) => {
|
|
const allowedTypes = [
|
|
'video/mp4', 'video/webm', 'video/avi', 'video/mkv', 'video/mov',
|
|
'video/x-msvideo', 'video/quicktime', 'video/x-matroska',
|
|
'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/bmp'
|
|
];
|
|
if (allowedTypes.includes(file.mimetype) || file.mimetype.startsWith('video/') || file.mimetype.startsWith('image/')) {
|
|
cb(null, true);
|
|
} else {
|
|
cb(new Error('Only video and image files are allowed'), false);
|
|
}
|
|
};
|
|
|
|
// `defParamCharset: 'utf8'` only takes effect for RFC 5987 encoded
|
|
// `filename*=utf-8''...` params. Most real clients (browsers, curl, programmatic
|
|
// HTTP) send the plain `filename="..."` form, where busboy still reads the bytes
|
|
// as latin1 regardless of this option. The actual UTF-8 recovery happens in the
|
|
// storage.filename callback above via Buffer.from(name,'latin1').toString('utf8').
|
|
// Kept here as defense-in-depth for the rare RFC 5987 case.
|
|
const upload = multer({
|
|
storage,
|
|
fileFilter,
|
|
limits: { fileSize: config.maxFileSize },
|
|
defParamCharset: 'utf8'
|
|
});
|
|
|
|
module.exports = upload;
|