189 lines
6.6 KiB
Java
189 lines
6.6 KiB
Java
package com.sshmanager.service;
|
|
|
|
import com.jcraft.jsch.ChannelSftp;
|
|
import com.jcraft.jsch.JSch;
|
|
import com.jcraft.jsch.Session;
|
|
import com.sshmanager.entity.Connection;
|
|
import org.springframework.stereotype.Service;
|
|
|
|
import java.io.ByteArrayInputStream;
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.io.InputStream;
|
|
import java.io.PipedInputStream;
|
|
import java.io.PipedOutputStream;
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.Vector;
|
|
import java.util.concurrent.ExecutionException;
|
|
import java.util.concurrent.ExecutorService;
|
|
import java.util.concurrent.Executors;
|
|
import java.util.concurrent.Future;
|
|
import java.util.concurrent.TimeUnit;
|
|
import java.util.concurrent.TimeoutException;
|
|
|
|
@Service
|
|
public class SftpService {
|
|
|
|
public SftpSession connect(Connection conn, String password, String privateKey, String passphrase)
|
|
throws Exception {
|
|
JSch jsch = new JSch();
|
|
|
|
if (conn.getAuthType() == Connection.AuthType.PRIVATE_KEY && privateKey != null && !privateKey.isEmpty()) {
|
|
byte[] keyBytes = privateKey.getBytes(StandardCharsets.UTF_8);
|
|
byte[] passphraseBytes = (passphrase != null && !passphrase.isEmpty())
|
|
? passphrase.getBytes(StandardCharsets.UTF_8) : null;
|
|
jsch.addIdentity("key", keyBytes, null, passphraseBytes);
|
|
}
|
|
|
|
Session session = jsch.getSession(conn.getUsername(), conn.getHost(), conn.getPort());
|
|
session.setConfig("StrictHostKeyChecking", "no");
|
|
if (conn.getAuthType() == Connection.AuthType.PASSWORD && password != null) {
|
|
session.setPassword(password);
|
|
}
|
|
session.connect(10000);
|
|
|
|
ChannelSftp channel = (ChannelSftp) session.openChannel("sftp");
|
|
channel.connect(5000);
|
|
|
|
return new SftpSession(session, channel);
|
|
}
|
|
|
|
public static class SftpSession {
|
|
private final Session session;
|
|
private final ChannelSftp channel;
|
|
|
|
public SftpSession(Session session, ChannelSftp channel) {
|
|
this.session = session;
|
|
this.channel = channel;
|
|
}
|
|
|
|
public ChannelSftp getChannel() {
|
|
return channel;
|
|
}
|
|
|
|
public void disconnect() {
|
|
if (channel != null && channel.isConnected()) {
|
|
channel.disconnect();
|
|
}
|
|
if (session != null && session.isConnected()) {
|
|
session.disconnect();
|
|
}
|
|
}
|
|
|
|
public boolean isConnected() {
|
|
return channel != null && channel.isConnected();
|
|
}
|
|
}
|
|
|
|
public static class FileInfo {
|
|
public String name;
|
|
public boolean directory;
|
|
public long size;
|
|
public long mtime;
|
|
|
|
public FileInfo(String name, boolean directory, long size, long mtime) {
|
|
this.name = name;
|
|
this.directory = directory;
|
|
this.size = size;
|
|
this.mtime = mtime;
|
|
}
|
|
}
|
|
|
|
public List<FileInfo> listFiles(SftpSession sftpSession, String path) throws Exception {
|
|
Vector<?> entries = sftpSession.getChannel().ls(path);
|
|
List<FileInfo> 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;
|
|
}
|
|
|
|
public byte[] download(SftpSession sftpSession, String remotePath) throws Exception {
|
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
|
sftpSession.getChannel().get(remotePath, out);
|
|
return out.toByteArray();
|
|
}
|
|
|
|
public void upload(SftpSession sftpSession, String remotePath, byte[] data) throws Exception {
|
|
sftpSession.getChannel().put(new ByteArrayInputStream(data), remotePath);
|
|
}
|
|
|
|
public void delete(SftpSession sftpSession, String remotePath, boolean isDir) throws Exception {
|
|
if (isDir) {
|
|
sftpSession.getChannel().rmdir(remotePath);
|
|
} else {
|
|
sftpSession.getChannel().rm(remotePath);
|
|
}
|
|
}
|
|
|
|
public void mkdir(SftpSession sftpSession, String remotePath) throws Exception {
|
|
sftpSession.getChannel().mkdir(remotePath);
|
|
}
|
|
|
|
public void rename(SftpSession sftpSession, String oldPath, String newPath) throws Exception {
|
|
sftpSession.getChannel().rename(oldPath, newPath);
|
|
}
|
|
|
|
public String pwd(SftpSession sftpSession) throws Exception {
|
|
return sftpSession.getChannel().pwd();
|
|
}
|
|
|
|
public void cd(SftpSession sftpSession, String path) throws Exception {
|
|
sftpSession.getChannel().cd(path);
|
|
}
|
|
|
|
/**
|
|
* Transfer a single file from source session to target session (streaming, no full file in memory).
|
|
* Fails if sourcePath is a directory.
|
|
*/
|
|
public void transferRemote(SftpSession source, String sourcePath, SftpSession target, String targetPath)
|
|
throws Exception {
|
|
if (source.getChannel().stat(sourcePath).isDir()) {
|
|
throw new IllegalArgumentException("Source path is a directory; only single file transfer is supported");
|
|
}
|
|
final int pipeBufferSize = 65536;
|
|
PipedOutputStream pos = new PipedOutputStream();
|
|
PipedInputStream pis = new PipedInputStream(pos, pipeBufferSize);
|
|
|
|
ExecutorService executor = Executors.newSingleThreadExecutor();
|
|
try {
|
|
Future<?> putFuture = executor.submit(() -> {
|
|
try {
|
|
target.getChannel().put(pis, targetPath);
|
|
} catch (Exception e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
});
|
|
source.getChannel().get(sourcePath, pos);
|
|
pos.close();
|
|
putFuture.get(5, TimeUnit.MINUTES);
|
|
} catch (ExecutionException e) {
|
|
Throwable cause = e.getCause();
|
|
if (cause instanceof RuntimeException && cause.getCause() instanceof Exception) {
|
|
throw (Exception) cause.getCause();
|
|
}
|
|
if (cause instanceof Exception) {
|
|
throw (Exception) cause;
|
|
}
|
|
throw new RuntimeException(cause);
|
|
} catch (TimeoutException e) {
|
|
throw new RuntimeException("Transfer timeout", e);
|
|
} finally {
|
|
executor.shutdownNow();
|
|
try {
|
|
pis.close();
|
|
} catch (Exception ignored) {
|
|
}
|
|
}
|
|
}
|
|
}
|