Hacktoberfest 2026: những issue maintainer đã đánh dấu cho tháng Mười, đang mở và phù hợp người mới. Xem issue Hacktoberfest

Uploaded filenames are decoded as latin1, so every non-ASCII name arrives as mojibake

Đã đóng Phù hợp với người mới
#17 1 bình luận 0 reaction 0 người được giao Xem trên GitHub

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ả

bug

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

  1. Termix 26.10.5, File Manager enabled, any SSH host (reproduced on Debian and on Windows OpenSSH).
  2. Upload a small file named 中文文件.txt through the UI.
  3. Run ls on the host: the name is 中文文件.txt.
  4. Upload the same name on a file larger than 1.5 GiB: the name is correct.
  5. 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 main at 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

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. 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.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của Termix-SSH/Plugin-File-Manager

Tất cả issue của Termix-SSH/Plugin-File-Manager

Issue tương tự

Thêm issue về TypeScript

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.