Uploaded filenames are decoded as latin1, so every non-ASCII name arrives as mojibake
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 2/5
- Thời gian dự kiến
- 1-3 giờ
- Mức phù hợp với người mới
- 82/100
- Loại issue
- Lỗi
- Độ rõ ràng
- Đặc tả rõ ràng
- Mức độ hoạt động
- Sôi nổi
- Công nghệ
- node.js, typescript
- Lĩnh vực
- backend
Hướng nghiên cứu
Bắt đầu với lời gọi Busboy và phần xử lý tên tệp trong src/backend/content-routes.ts quanh dòng 1315 và 1329, sau đó đọc các bài kiểm thử của route tải lên nếu có. Kiểm tra cách giải mã các tham số tên tệp multipart và so sánh đường xử lý luồng tải lên với đường xử lý theo từng phần. Hoàn tất khi tên không thuộc ASCII vẫn được giữ nguyên trong các lượt tải lên multipart nhỏ; issue cũng mô tả một thay đổi liên quan đến Content-Disposition trong src/backend/download-routes.ts quanh dòng 318.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Repository: Termix-SSH/Plugin-File-Manager
Version: plugin 1.1.1 / Termix 26.10.5
Where: src/backend/content-routes.ts L1315 and L1329; related: src/backend/download-routes.ts L318-319
What happens
Uploading a file whose name is not pure ASCII through the File Manager creates it on the remote
host under a garbled name. Uploading 中文文件.txt leaves 䏿–‡æ–‡ä»¶.txt; uploading
新建 文本文档.txt leaves æ°å»º ææ¬ææ¡£.txt.
That is exactly Buffer.from(name, "utf8").toString("latin1") — every UTF-8 byte becomes its own
character. Hebrew, Cyrillic, Greek, accented Latin, Vietnamese and emoji names are all affected.
Everything else in the plugin handles these names fine: the directory listing renders them, rename
works, create folder works. Only the multipart upload path corrupts them.
Why
// src/backend/content-routes.ts L1315 — route POST /uploadFileStream
const bb = Busboy({ headers: req.headers });
bb.on("file", (fieldname, fileStream, info) => {
fileName = info.filename; // L1329 — already mojibake here
...
const fullPath = remotePath.endsWith("/")
? remotePath + fileName
: remotePath + "/" + fileName;
Busboy is called without defParamCharset. From the busboy README:
defParamCharset- string - For multipart forms, the default character set to use for values
of part header parameters (e.g. filename) that are not extended parameters (that contain an
explicit charset). Default:'latin1'.
In busboy's source that becomes:
const paramDecoder = typeof cfg.defParamCharset === "string" && cfg.defParamCharset
? getDecoder(cfg.defParamCharset)
: nullDecoder; // nullDecoder returns the value unchanged
function nullDecoder(val, hint) { return val; }
The browser sends the part header as raw UTF-8 bytes with no filename*:
// src/frontend/api/ssh-file-operations-api.ts
const form = new FormData();
form.append("sessionId", sessionId);
form.append("path", path);
form.append("file", file, fileName); // filename= holds the UTF-8 bytes
await getFileManagerApiForSession(sessionId).postForm("/uploadFileStream", form, ...);
Decoding those bytes as latin1 is what produces the mojibake.
Fingerprint: large files get the name right
The chunked path is unaffected because the name travels as a query parameter, not a multipart
header:
const CHUNK_THRESHOLD_BYTES = 1.5 * 1024 * 1024 * 1024; // 1.5 GiB
if (file.size > CHUNK_THRESHOLD_BYTES) {
...
post("/uploadFileChunk", chunkBlob, {
params: { sessionId, path, fileName, offset: start, totalSize: file.size },
});
| File size | Route | How the name arrives | Result |
|---|---|---|---|
| ≤ 1.5 GiB | POST /uploadFileStream |
multipart header → latin1 | garbled |
| > 1.5 GiB | POST /uploadFileChunk |
query parameter | correct |
The same non-ASCII name comes out correct for a 2 GiB file and garbled for a 2 MB one. That was the
clue that pointed at the parser rather than at SSH or the filesystem.
Localised to one route
createFile, createFolder and renameItem read the name from req.body
(operation-routes.ts), not from a multipart header, and are unaffected. So the bug is exactly one
Busboy call.
Steps to reproduce
- Termix 26.10.5, File Manager enabled, any SSH host (reproduced on Debian and on Windows OpenSSH).
- Upload a small file named
中文文件.txtthrough the UI. - Run
lson the host: the name is䏿–‡æ–‡ä»¶.txt. - Upload the same name on a file larger than 1.5 GiB: the name is correct.
- Rename or create a file with the same name through the UI: correct.
Byte-level confirmation:
Buffer.from("中文文件.txt", "utf8").toString("latin1") // "䏿–‡æ–‡ä»¶.txt"
What I expected
The file appears with the name that was uploaded.
Suggested fix
- const bb = Busboy({ headers: req.headers });
+ const bb = Busboy({ headers: req.headers, defParamCharset: "utf8" });
Browsers always send part-header parameters as UTF-8 and never attach a charset to them, so
defParamCharset: "utf8" is correct for this API. preservePath stays false as before.
Related: the download side sends a non-standard Content-Disposition
// src/backend/download-routes.ts L318
res.setHeader(
"Content-Disposition",
`attachment; filename="${encodeURIComponent(fileName)}"`,
);
RFC 6266 does not allow percent-encoded text in a bare filename=. The standard form is
filename*=UTF-8''<pct-encoded>. Chromium deliberately percent-decodes this legacy shape, so Chrome
and Edge save the file correctly, but curl, several download managers and some mobile clients save
it literally as %E4%B8%AD%E6%96%87%E6%96%87%E4%BB%B6.txt. Emitting both parameters keeps every
client happy:
res.setHeader(
"Content-Disposition",
`attachment; filename="download"; filename*=UTF-8''${encodeURIComponent(fileName)}`,
);
filename="download" stays as a plain ASCII fallback for pre-RFC-6266 clients.
Environment
- Termix 26.10.5, Plugin File Manager 1.1.1 — still present on
mainat the lines quoted above - Docker on Linux,
ghcr.io/termix-ssh/termix:latest, SQLite - Remote hosts: Debian (OpenSSH) and Windows Server (OpenSSH) — same behaviour on both
- Clients: Chrome and Firefox on Windows and Linux, plus the macOS desktop app
Anything else
Found it in the running container's bundled dist/backend.js, then confirmed the same code on
main. Happy to open a PR with both changes.
- Ngôn ngữ chính
- TypeScript
- Star
- 1
- Fork
- 3
- Merge trung bình
- 1 ngày 4 giờ
- Pull request đã merge (30 ngày)
- 7
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Không có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của Termix-SSH/Plugin-File-Manager
-
bug platform-desktop platform-docker
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 55/100
Termix-SSH/Plugin-File-Manager#21 · 2 bình luận ·
-
feature platform-desktop platform-windows
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 25/100
Termix-SSH/Plugin-File-Manager#3 · 2 bình luận ·
-
[FEATURE] FTP supportĐang mởfeature platform-web
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 25/100
Termix-SSH/Plugin-File-Manager#5 · 5 bình luận ·
Tất cả issue của Termix-SSH/Plugin-File-Manager
Issue tương tự
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 66/100
CopilotKit/openmuse#215 ·
Maintainer thường phản hồi trong vòng 3 ngày
-
b:minor bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
software-mansion/argent#1698 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
e2e-failure ready-to-code
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
redhat-developer/rhdh-plugin-export-overlays#4624 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày