481 lines
12 KiB
C++
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;
|
|
}
|
|
|