3500/scp_client.cpp
2026-02-04 17:58:54 +08:00

481 lines
12 KiB
C++

#include "scp_client.h"
#include <QFile>
#include <QDebug>
#ifdef Q_OS_UNIX
#include <pwd.h>
#include <grp.h>
#endif
#include "NetMgr.h"
#include <QJsonArray>
#include <QJsonObject>
#include <QtNetwork/QNetworkRequest>
#include "global.h"
ScpClient::ScpClient(QObject *parent)
: QObject(parent)
{
#ifdef _WIN32
WSADATA wsadata;
WSAStartup(MAKEWORD(2,2), &wsadata);
#endif
m_sock = -1;
m_session = nullptr;
}
ScpClient::~ScpClient()
{
disconnectFromHost();
#ifdef _WIN32
WSACleanup();
#endif
}
void ScpClient::cancel()
{
m_cancelled.store(true);
}
bool ScpClient::connectToHost(const QString &host,
int port,
const QString &user,
const QString &password,
int timeoutMs)
{
disconnectFromHost();
m_sock = socket(AF_INET, SOCK_STREAM, 0);
if (m_sock < 0) {
emit error("Failed to create socket");
return false;
}
struct sockaddr_in sin{};
sin.sin_family = AF_INET;
#ifdef _WIN32
sin.sin_addr.s_addr = inet_addr(host.toUtf8().constData());
#else
if (inet_pton(AF_INET, host.toUtf8().constData(), &sin.sin_addr) <= 0) {
emit error("Invalid IP address");
return false;
}
#endif
sin.sin_port = htons(port);
if (::connect(m_sock, (struct sockaddr*)&sin, sizeof(sin)) != 0) {
emit error("Failed to connect");
return false;
}
m_session = libssh2_session_init();
if (!m_session) {
emit error("Failed to init libssh2 session");
return false;
}
// 阻塞模式
libssh2_session_set_blocking(m_session, 1);
if (libssh2_session_handshake(m_session, m_sock) != 0) {
emit error("Handshake failed");
return false;
}
if (libssh2_userauth_password(
m_session,
user.toUtf8().constData(),
password.toUtf8().constData()) != 0)
{
emit error("Authentication failed");
return false;
}
return true;
}
void ScpClient::disconnectFromHost()
{
m_cancelled.store(true);
if (m_session) {
libssh2_session_disconnect(m_session, "Bye");
libssh2_session_free(m_session);
m_session = nullptr;
}
#ifdef _WIN32
if (m_sock != -1) closesocket(m_sock);
#else
if (m_sock != -1) close(m_sock);
#endif
m_sock = -1;
}
bool ScpClient::download(const QString &remotePath, const QString &localPath)
{
if (!m_session) return false;
m_cancelled.store(false);
libssh2_struct_stat st;
// 使用 scp_recv2 获取远程文件信息
LIBSSH2_CHANNEL* channel = libssh2_scp_recv2(
m_session,
remotePath.toUtf8().constData(),
&st);
if (!channel) {
char *errmsg;
libssh2_session_last_error(m_session, &errmsg, nullptr, 0);
emit error(QString("SCP recv failed: %1").arg(errmsg));
return false;
}
QFile file(localPath);
if (!file.open(QIODevice::WriteOnly)) {
emit error("Open local file failed");
libssh2_channel_free(channel);
return false;
}
qint64 totalSize = st.st_size;
qint64 received = 0;
char buffer[16384]; // 使用 16KB 固定缓冲区
while (received < totalSize && !m_cancelled.load()) {
if (m_cancelled.load()) {
emit error("Download cancelled");
break;
}
// 计算剩余需要读取的字节数,防止溢出
int toRead = sizeof(buffer);
if (totalSize - received < toRead) {
toRead = totalSize - received;
}
ssize_t rc = libssh2_channel_read(channel, buffer, toRead);
if (rc > 0) {
file.write(buffer, rc);
received += rc;
emit progress(received, totalSize);
} else if (rc < 0) {
emit error("Read error during download");
break;
} else {
// rc == 0 意味着服务器提前关闭了流
break;
}
}
file.flush();
file.close();
// 标准清理流程
libssh2_channel_free(channel);
if (!m_cancelled.load() && received != totalSize) {
emit error("File size mismatch (Incomplete download)");
return false;
}
emit finished();
return received == totalSize;
}
bool ScpClient::upload(const QString &localPath,
const QString &remotePath,const QString& fileName,int type,
int fileMode)
{
if (!m_session) return false;
m_cancelled.store(false);
m_Type = type;
m_fileName = fileName;
QFile file(localPath);
if (!file.open(QIODevice::ReadOnly)) {
emit error("Open local file failed");
return false;
}
qint64 fileSize = file.size();
qint64 sent = 0;
LIBSSH2_CHANNEL* channel = libssh2_scp_send64(
m_session,
remotePath.toUtf8().constData(),
fileMode,
fileSize,
0, 0);
if (!channel) {
emit error("SCP send failed");
return false;
}
char buffer[16384];
while (sent < fileSize) {
if (m_cancelled.load()) {
emit error("Upload cancelled");
break;
}
qint64 n = file.read(buffer, sizeof(buffer));
if (n <= 0) break;
char* ptr = buffer;
while (n > 0) {
ssize_t rc = libssh2_channel_write(channel, ptr, n);
if (rc <= 0) {
emit error("Write failed");
goto cleanup;
}
ptr += rc;
n -= rc;
sent += rc;
emit progress(sent, fileSize);
if(sent == fileSize){
uploadFinish();
}
}
}
cleanup:
libssh2_channel_send_eof(channel);
libssh2_channel_wait_eof(channel);
libssh2_channel_wait_closed(channel);
libssh2_channel_free(channel);
file.close();
emit finished();
return sent == fileSize;
}
void ScpClient::uploadFinish(){
if(m_fileName != ""){
qDebug() << "uploadFinish" ;
QJsonObject allObj,cmdBody,temp;
QJsonArray tempArray;
if(m_fileName.contains(".json")){
allObj.insert("cmd", "90");
temp["fileName"] = m_fileName;
if(m_Type >= 0)
temp["content"] = m_Type;
tempArray.append(temp);
allObj.insert("cmdBody",tempArray);
}else{
allObj.insert("cmd", "46");
allObj.insert("type", m_Type);
temp["fileName"] = m_fileName;
allObj.insert("cmdBody",temp);
}
qDebug() << allObj << endl;
QNetworkRequest req;
QString sUrl = QString("http://%1/cgi-bin/General.cgi/").arg(IP);
req.setUrl(sUrl);
g_NetMgr->PostJson(req,allObj);
m_fileName = "";
m_Type = -1;
}
}
QString ScpClient::uidToName(uint uid)
{
#ifdef Q_OS_UNIX
struct passwd *pw = getpwuid(uid);
if (pw)
return QString::fromLocal8Bit(pw->pw_name);
#endif
return QString::number(uid);
}
QString ScpClient::gidToName(uint gid)
{
#ifdef Q_OS_UNIX
struct group *gr = getgrgid(gid);
if (gr)
return QString::fromLocal8Bit(gr->gr_name);
return QString::number(gid);
#endif
return QString::number(gid);
}
void ScpClient::waitSocket(int sock)
{
fd_set fds;
FD_ZERO(&fds);
FD_SET(sock, &fds);
timeval tv;
tv.tv_sec = 10;
tv.tv_usec = 0;
select(0, &fds, &fds, nullptr, &tv);
}
bool ScpClient::listRemoteDir(const QString &path,
QVector<RemoteFileInfo> &outList)
{
outList.clear();
LIBSSH2_SFTP *sftp = nullptr;
for (;;) {
sftp = libssh2_sftp_init(m_session);
if (sftp)
break;
int err = libssh2_session_last_errno(m_session);
if (err == LIBSSH2_ERROR_EAGAIN) {
waitSocket(m_sock); // 必须
continue;
}
qDebug() << "sftp init failed:" << err;
return false;
}
if (!sftp) {
emit error("sftp init failed");
return false;
}
LIBSSH2_SFTP_HANDLE *dir =
libssh2_sftp_opendir(sftp, path.toUtf8().constData());
if (!dir) {
emit error("sftp opendir failed: " + path);
libssh2_sftp_shutdown(sftp);
return false;
}
char filename[512];
LIBSSH2_SFTP_ATTRIBUTES attrs;
while (true) {
memset(&attrs, 0, sizeof(attrs));
int rc = libssh2_sftp_readdir(dir, filename, sizeof(filename), &attrs);
if (rc <= 0)
break;
QString name = QString::fromUtf8(filename);
if (name == "." || name == "..")
continue;
RemoteFileInfo info;
info.name = name;
info.size = static_cast<qint64>(attrs.filesize);
info.permissions = attrs.permissions;
info.isDir = LIBSSH2_SFTP_S_ISDIR(attrs.permissions);
if (attrs.mtime) {
info.lastModified =
QDateTime::fromSecsSinceEpoch(attrs.mtime);
}
info.uid = attrs.uid;
info.gid = attrs.gid;
info.owner = uidToName(attrs.uid);
info.group = gidToName(attrs.gid);
outList.push_back(info);
}
libssh2_sftp_closedir(dir);
libssh2_sftp_shutdown(sftp);
QString out;
if (exec("pwd", out))
m_currentPath = out.trimmed();
else
m_currentPath = "/";
return true;
}
bool ScpClient::makeRemoteDir(const QString &path, int mode)
{
LIBSSH2_SFTP *sftp = libssh2_sftp_init(m_session);
if (!sftp)
return false;
int rc = libssh2_sftp_mkdir(
sftp, path.toUtf8().constData(), mode);
libssh2_sftp_shutdown(sftp);
return (rc == 0);
}
bool ScpClient::removeRemoteFile(const QString &path)
{
LIBSSH2_SFTP *sftp = libssh2_sftp_init(m_session);
if (!sftp)
return false;
int rc = libssh2_sftp_unlink(
sftp, path.toUtf8().constData());
libssh2_sftp_shutdown(sftp);
return (rc == 0);
}
bool ScpClient::removeRemoteDir(const QString &path)
{
LIBSSH2_SFTP *sftp = libssh2_sftp_init(m_session);
if (!sftp)
return false;
int rc = libssh2_sftp_rmdir(
sftp, path.toUtf8().constData());
libssh2_sftp_shutdown(sftp);
return (rc == 0);
}
bool ScpClient::renameRemote(const QString &oldPath,
const QString &newPath)
{
LIBSSH2_SFTP *sftp = libssh2_sftp_init(m_session);
if (!sftp)
return false;
int rc = libssh2_sftp_rename(
sftp,
oldPath.toUtf8().constData(),
newPath.toUtf8().constData());
libssh2_sftp_shutdown(sftp);
return (rc == 0);
}
bool ScpClient::exec(const QString &cmd, QString &output)
{
LIBSSH2_CHANNEL *channel = libssh2_channel_open_session(m_session);
if (!channel)
return false;
if (libssh2_channel_exec(channel, cmd.toUtf8().constData()) != 0) {
libssh2_channel_free(channel);
return false;
}
char buffer[4096];
for (;;) {
ssize_t n = libssh2_channel_read(channel, buffer, sizeof(buffer));
if (n <= 0)
break;
output.append(QString::fromUtf8(buffer, n));
}
libssh2_channel_close(channel);
libssh2_channel_free(channel);
return true;
}
bool ScpClient::goUp(QString& filePath, QVector<RemoteFileInfo> &files,QString& retPath)
{
if (filePath.isEmpty() || filePath == "/") {
filePath = "/";
} else {
int idx = filePath.lastIndexOf('/');
filePath = (idx <= 0) ? "/" : filePath.left(idx);
}
// 列出新路径内容
files.clear();
if (!listRemoteDir(filePath, files)) {
return false;
}
retPath = filePath;
return true;
}