fix: file manager incorrectly decoding/encoding when editing files #476
@@ -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 {
|
||||||
|
|||||||
@@ -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}`;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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 },
|
||||||
|
|||||||
Reference in New Issue
Block a user