#include "scp_client.h" #include #include #ifdef Q_OS_UNIX #include #include #endif #ifdef _WIN32 #include #include #else #include #include #include #include #endif #include "NetMgr.h" #include #include #include #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(SOCKET 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 &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(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() { if (m_currentPath.isEmpty() || m_currentPath == "/") return true; // 已经在根目录,仍返回 true int idx = m_currentPath.lastIndexOf('/'); if (idx <= 0) { m_currentPath = "/"; } else { m_currentPath = m_currentPath.left(idx); } // 测试能否列出目录,确保路径有效 QString dummy; QString cmd = QString("ls -l %1").arg(m_currentPath); if (!exec(cmd, dummy)) return false; return true; }