Hacktoberfest 2026: los issues que los mantenedores marcaron para octubre, abiertos y aptos para principiantes. Explorar issues de Hacktoberfest

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

Abierto Apto para principiantes
#17 1 comentario 0 reacciones 0 asignados Ver en GitHub

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

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.

Lenguaje dominante
TypeScript
Estrellas
1
Forks
3
Merge medio
1 d 4 h
PR fusionados (30 d)
6

Preparar el entorno

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de Termix-SSH/Plugin-File-Manager

Todos los issues de Termix-SSH/Plugin-File-Manager

Issues similares

Más issues de TypeScript

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.