Uploaded filenames are decoded as latin1, so every non-ASCII name arrives as mojibake
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 2/5
- Tiempo estimado
- 1-3 horas
- Aptitud para principiantes
- 82/100
- Tipo de issue
- Error
- Claridad
- Bien especificado
- Estado de actividad
- Activo
- Stack tecnológico
- node.js, typescript
- Área
- backend
Línea de trabajo
Empieza por la llamada a Busboy y el manejo de nombres de archivo en src/backend/content-routes.ts, cerca de las líneas 1315 y 1329; después, lee las pruebas de la ruta de subida si están disponibles. Comprueba cómo se decodifican los parámetros de nombre de archivo multipart y compara la ruta del flujo de subida con la ruta por fragmentos. Se considera terminado cuando los nombres no ASCII sobreviven a las subidas multipart pequeñas; el issue también describe un cambio relacionado en Content-Disposition en src/backend/download-routes.ts, cerca de la línea 318.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
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.
- Lenguaje dominante
- TypeScript
- Estrellas
- 1
- Forks
- 3
- Merge medio
- 1 d 4 h
- PR fusionados (30 d)
- 6
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Sin plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de Termix-SSH/Plugin-File-Manager
-
bug platform-desktop
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
Termix-SSH/Plugin-File-Manager#19 · 2 comentarios ·
-
bug platform-desktop platform-docker
Dificultad 2/5 1-3 horas Aptitud para principiantes 65/100
Termix-SSH/Plugin-File-Manager#16 · 2 comentarios ·
-
bug platform-desktop platform-docker
Dificultad 2/5 1-3 horas Aptitud para principiantes 66/100
Termix-SSH/Plugin-File-Manager#15 · 2 comentarios ·
-
Unable to start subsystem: sftpAbiertobug platform-docker
Dificultad 4/5 3-5 días Aptitud para principiantes 45/100
Termix-SSH/Plugin-File-Manager#18 · 1 comentario ·
-
bug
Dificultad 3/5 1-2 días Aptitud para principiantes 60/100
Termix-SSH/Plugin-File-Manager#14 · 1 comentario ·
Todos los issues de Termix-SSH/Plugin-File-Manager
Issues similares
-
area:plugins area:security area:skills bug has repro
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
anthropics/claude-code#105911 ·
Los mantenedores suelen responder en 1 día
-
openspec-apply-change skill: when blocked, may select a ready artifact unrelated to missingArtifactsAbierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
Fission-AI/OpenSpec#2165 ·
Los mantenedores suelen responder en 1 día
-
🐞 bug 🧐 unconfirmed
Dificultad 2/5 1-3 horas Aptitud para principiantes 66/100
Tencent/tdesign-vue-next#7032 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
gnosis/gnosis_vpn#661 ·
Los mantenedores suelen responder en 1 día
-
content-freshness: staleOwnedContent doc comments still say 'complete receipt' (follow-up to #7912)Abiertotriage:bug-verified
Dificultad 1/5 Menos de una hora Aptitud para principiantes 92/100
Los mantenedores suelen responder en 1 día