Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

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

オープン 初心者向け
#17 コメント 1 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

評価

難易度
2/5
見積もり時間
1〜3時間
初心者へのやさしさ
82/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
node.js, typescript
領域
backend

調査の方向性

まず src/backend/content-routes.ts の1315行目と1329行目付近にあるBusboyの呼び出しとファイル名の処理を確認し、次に利用可能であればアップロードルートのテストを読みます。multipartのファイル名パラメーターがどのようにデコードされるかを確認し、アップロードストリームの経路とチャンク処理の経路を比較します。小さなmultipartアップロードでも非ASCIIの名前が保持されれば完了です。issueには、318行目付近の src/backend/download-routes.ts におけるContent-Dispositionの関連変更も記載されています。

索引モデルが issue の本文から書いたものです。

説明

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.

主要言語
TypeScript
スター
1
フォーク
3
平均マージ
1日 4時間
マージ済み PR(30日)
6

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

Termix-SSH/Plugin-File-Manager のほかの issue

Termix-SSH/Plugin-File-Manager の issue をすべて見る

似ている issue

TypeScript の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。