fix: file manager incorrectly decoding/encoding when editing files #476

Merged
LukeGus merged 6 commits from dev-1.10.1 into main 2026-01-02 06:33:10 +00:00
4 changed files with 85 additions and 13 deletions
Showing only changes of commit 177e783f92 - Show all commits

View File

@@ -343,6 +343,27 @@ function getMimeType(fileName: string): string {
return mimeTypes[ext || ""] || "application/octet-stream"; return mimeTypes[ext || ""] || "application/octet-stream";
} }
function detectBinary(buffer: Buffer): boolean {
if (buffer.length === 0) return false;
const sampleSize = Math.min(buffer.length, 8192);
let nullBytes = 0;
for (let i = 0; i < sampleSize; i++) {
const byte = buffer[i];
if (byte === 0) {
nullBytes++;
}
if (byte < 32 && byte !== 9 && byte !== 10 && byte !== 13) {
if (++nullBytes > 1) return true;
}
}
return nullBytes / sampleSize > 0.01;
}
app.post("/ssh/file_manager/ssh/connect", async (req, res) => { app.post("/ssh/file_manager/ssh/connect", async (req, res) => {
const { const {
sessionId, sessionId,
@@ -1368,11 +1389,11 @@ app.get("/ssh/file_manager/ssh/readFile", (req, res) => {
return res.status(500).json({ error: err.message }); return res.status(500).json({ error: err.message });
} }
let data = ""; let binaryData = Buffer.alloc(0);
let errorData = ""; let errorData = "";
stream.on("data", (chunk: Buffer) => { stream.on("data", (chunk: Buffer) => {
data += chunk.toString(); binaryData = Buffer.concat([binaryData, chunk]);
}); });
stream.stderr.on("data", (chunk: Buffer) => { stream.stderr.on("data", (chunk: Buffer) => {
@@ -1396,7 +1417,23 @@ app.get("/ssh/file_manager/ssh/readFile", (req, res) => {
}); });
} }
res.json({ content: data, path: filePath }); const isBinary = detectBinary(binaryData);
if (isBinary) {
const base64Content = binaryData.toString("base64");
res.json({
content: base64Content,
path: filePath,
encoding: "base64",
});
} else {
const textContent = binaryData.toString("utf8");
res.json({
content: textContent,
path: filePath,
encoding: "utf8",
});
}
}); });
}); });
}); });
@@ -1440,7 +1477,16 @@ app.post("/ssh/file_manager/ssh/writeFile", async (req, res) => {
let fileBuffer; let fileBuffer;
try { try {
if (typeof content === "string") { if (typeof content === "string") {
fileBuffer = Buffer.from(content, "base64"); try {
const testBuffer = Buffer.from(content, "base64");
if (testBuffer.toString("base64") === content) {
fileBuffer = testBuffer;
} else {
fileBuffer = Buffer.from(content, "utf8");
}
} catch {
fileBuffer = Buffer.from(content, "utf8");
}
} else if (Buffer.isBuffer(content)) { } else if (Buffer.isBuffer(content)) {
fileBuffer = content; fileBuffer = content;
} else { } else {

View File

@@ -333,13 +333,7 @@ export function FileViewer({
const ext = fileName.split(".").pop()?.toLowerCase() || ""; const ext = fileName.split(".").pop()?.toLowerCase() || "";
if (ext === "svg") { if (ext === "svg") {
try { return `data:image/svg+xml;base64,${content}`;
const base64 = btoa(unescape(encodeURIComponent(content)));
return `data:image/svg+xml;base64,${base64}`;
} catch (e) {
console.error("Failed to encode SVG:", e);
return "";
}
} }
return `data:image/*;base64,${content}`; return `data:image/*;base64,${content}`;

View File

@@ -47,6 +47,22 @@ interface FileWindowProps {
onFileNotFound?: (file: FileItem) => void; onFileNotFound?: (file: FileItem) => void;
} }
function isDisplayableText(str: string): boolean {
let printable = 0;
for (let i = 0; i < Math.min(str.length, 1000); i++) {
const code = str.charCodeAt(i);
if (
(code >= 32 && code <= 126) ||
code === 9 ||
code === 10 ||
code === 13
) {
printable++;
}
}
return printable / Math.min(str.length, 1000) > 0.85;
}
export function FileWindow({ export function FileWindow({
windowId, windowId,
file, file,
@@ -106,7 +122,19 @@ export function FileWindow({
await ensureSSHConnection(); await ensureSSHConnection();
const response = await readSSHFile(sshSessionId, file.path); const response = await readSSHFile(sshSessionId, file.path);
const fileContent = response.content || ""; let fileContent = response.content || "";
if (response.encoding === "base64") {
try {
const decoded = atob(fileContent);
if (isDisplayableText(decoded)) {
fileContent = decoded;
}
} catch (err) {
console.error("Failed to decode base64 content:", err);
}
}
setContent(fileContent); setContent(fileContent);
setPendingContent(fileContent); setPendingContent(fileContent);

View File

@@ -1421,7 +1421,11 @@ export async function identifySSHSymlink(
export async function readSSHFile( export async function readSSHFile(
sessionId: string, sessionId: string,
path: string, path: string,
): Promise<{ content: string; path: string }> { ): Promise<{
content: string;
path: string;
encoding?: "base64" | "utf8";
}> {
try { try {
const response = await fileManagerApi.get("/ssh/readFile", { const response = await fileManagerApi.get("/ssh/readFile", {
params: { sessionId, path }, params: { sessionId, path },