diff --git a/backend/src/main/java/com/sshmanager/controller/SftpController.java b/backend/src/main/java/com/sshmanager/controller/SftpController.java index d99bddf..f97f230 100644 --- a/backend/src/main/java/com/sshmanager/controller/SftpController.java +++ b/backend/src/main/java/com/sshmanager/controller/SftpController.java @@ -1,5 +1,6 @@ package com.sshmanager.controller; +import com.jcraft.jsch.SftpException; import com.sshmanager.dto.SftpFileInfo; import com.sshmanager.entity.Connection; import com.sshmanager.entity.User; @@ -78,13 +79,28 @@ public class SftpController { .collect(Collectors.toList()); return ResponseEntity.ok(dtos); } catch (Exception e) { - log.warn("SFTP list failed: connectionId={}, path={}", connectionId, path, e); + String errorMsg = toSftpErrorMessage(e, path, "list"); + log.warn("SFTP list failed: connectionId={}, path={}, error={}", connectionId, path, errorMsg, e); Map err = new HashMap<>(); - err.put("error", e.getMessage() != null ? e.getMessage() : "List failed"); + err.put("error", errorMsg); return ResponseEntity.status(500).body(err); } } + private String toSftpErrorMessage(Exception e, String path, String operation) { + if (e.getMessage() != null && !e.getMessage().trim().isEmpty()) { + return e.getMessage(); + } + Throwable cause = e.getCause(); + if (cause instanceof SftpException) { + return SftpService.formatSftpExceptionMessage((SftpException) cause, path, operation); + } + if (e instanceof SftpException) { + return SftpService.formatSftpExceptionMessage((SftpException) e, path, operation); + } + return operation + " failed"; + } + @GetMapping("/pwd") public ResponseEntity> pwd( @RequestParam Long connectionId, diff --git a/backend/src/main/java/com/sshmanager/service/SftpService.java b/backend/src/main/java/com/sshmanager/service/SftpService.java index e348838..8e8bd9d 100644 --- a/backend/src/main/java/com/sshmanager/service/SftpService.java +++ b/backend/src/main/java/com/sshmanager/service/SftpService.java @@ -3,6 +3,7 @@ package com.sshmanager.service; import com.jcraft.jsch.ChannelSftp; import com.jcraft.jsch.JSch; import com.jcraft.jsch.Session; +import com.jcraft.jsch.SftpException; import com.sshmanager.entity.Connection; import org.springframework.stereotype.Service; @@ -93,20 +94,59 @@ public class SftpService { } public List listFiles(SftpSession sftpSession, String path) throws Exception { - Vector entries = sftpSession.getChannel().ls(path); - List result = new ArrayList<>(); - for (Object obj : entries) { - ChannelSftp.LsEntry entry = (ChannelSftp.LsEntry) obj; - String name = entry.getFilename(); - if (".".equals(name) || "..".equals(name)) continue; - result.add(new FileInfo( - name, - entry.getAttrs().isDir(), - entry.getAttrs().getSize(), - entry.getAttrs().getMTime() * 1000L - )); + String listPath = (path == null || path.trim().isEmpty()) ? "." : path.trim(); + try { + Vector entries = sftpSession.getChannel().ls(listPath); + List result = new ArrayList<>(); + for (Object obj : entries) { + ChannelSftp.LsEntry entry = (ChannelSftp.LsEntry) obj; + String name = entry.getFilename(); + if (".".equals(name) || "..".equals(name)) continue; + result.add(new FileInfo( + name, + entry.getAttrs().isDir(), + entry.getAttrs().getSize(), + entry.getAttrs().getMTime() * 1000L + )); + } + return result; + } catch (SftpException e) { + String msg = formatSftpExceptionMessage(e, listPath, "list"); + throw new RuntimeException(msg, e); + } + } + + /** + * Build a user-visible message from JSch SftpException (getMessage() is often null). + */ + public static String formatSftpExceptionMessage(SftpException e, String path, String operation) { + int id = e.getId(); + String serverMsg = e.getMessage(); + String reason = sftpErrorCodeToMessage(id); + StringBuilder sb = new StringBuilder(); + sb.append(reason); + if (path != null && !path.isEmpty()) { + sb.append(": ").append(path); + } + if (serverMsg != null && !serverMsg.trim().isEmpty()) { + sb.append(" (").append(serverMsg).append(")"); + } else { + sb.append(" [SFTP status ").append(id).append("]"); + } + return sb.toString(); + } + + private static String sftpErrorCodeToMessage(int id) { + switch (id) { + case 2: return "No such file or directory"; + case 3: return "Permission denied"; + case 4: return "Operation failed"; + case 5: return "Bad message"; + case 6: return "No connection"; + case 7: return "Connection lost"; + case 8: return "Operation not supported"; + default: return "SFTP error"; } - return result; } public byte[] download(SftpSession sftpSession, String remotePath) throws Exception {