Feature: Add download_file MCP tool (V2 protocol, SHA-256 verified)
download_file pulls a remote file or directory back to the controller over the existing V2 file-transfer protocol. It reuses COMMAND_LIST_DRIVE to open the file-manager sub-link, then sends CMD_DOWN_FILES_V2 so the Windows client streams COMMAND_SEND_FILE_V2 chunks and a per-file COMMAND_FILE_COMPLETE_V2 SHA-256 checksum over its own authenticated sub-connection. The server routes those two packet types into a new per-device FileTransferSession whenever a download is pending, so the headless path never opens the GUI progress dialog; the C2C and existing GUI branches are left untouched. Files are written under a normalized local_dir, and every chunk filename is resolved and verified to stay inside local_dir (directories included) to block .. traversal; overwrite=false skips existing files and counts them as skipped. One transfer per host (mutually exclusive with the one-shot pending registry), a dedicated McpFileTransfer=0-by-default gate surfaced in the settings dialog, and audit logging complete the change. upload_file remains future work. Co-Authored-By: deepseek-v4-pro
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
#include <ctime>
|
||||
@@ -28,6 +29,14 @@
|
||||
class CMy2015RemoteDlg;
|
||||
class context;
|
||||
|
||||
// 文件传输结果条目(download_file 输出 files[] 的一项)。
|
||||
// path 为本机落盘绝对路径(ANSI,输出 JSON 时再转 UTF-8);sha256 为小写 hex。
|
||||
struct FileTransferEntry {
|
||||
std::string path;
|
||||
uint64_t size = 0;
|
||||
std::string sha256;
|
||||
};
|
||||
|
||||
// MCP (Model Context Protocol) 服务端:httplib 封装 + JSON-RPC 2.0 分发 + 静态 token 校验。
|
||||
// 与 CWebService 平级、互不依赖;默认禁用,经「扩展 → MCP设置」开启后监听
|
||||
// (默认 127.0.0.1:6544,仅本机回环)。见 docs/Mcp_Design.md。
|
||||
@@ -194,6 +203,40 @@ public:
|
||||
// 屏幕子连接断开(OfflineProc 调用):擦除该子连接对应的会话 + 路由(幂等)。
|
||||
void OnScreenControlClosed(context* subCtx);
|
||||
|
||||
// ===== P6:MCP 文件传输(download_file,V2 协议)=====
|
||||
// 下载走 CMD_DOWN_FILES_V2 → 客户端新开流式子连接回传 COMMAND_SEND_FILE_V2 /
|
||||
// COMMAND_FILE_COMPLETE_V2(dstClientID==0)。会话按 device_id 键控,单设备单传输;
|
||||
// 复用 list_files 的 COMMAND_LIST_DRIVE → TOKEN_DRIVE_LIST 开文件管理器子链接下发命令。
|
||||
void SetFileTransferEnabled(bool enabled) { m_fileTransferEnabled = enabled; }
|
||||
bool IsFileTransferEnabled() const { return m_fileTransferEnabled; }
|
||||
|
||||
// 该 host 是否有进行中的文件传输会话(MessageHandle 分派守卫)。
|
||||
bool IsFileTransferPending(uint64_t device_id);
|
||||
|
||||
// 登记文件传输会话(false = 该 host 已有文件会话或一次性挂起请求)。
|
||||
bool BeginFileTransferPending(uint64_t device_id, const std::string& tool,
|
||||
const std::string& localDir, const std::string& remotePath,
|
||||
bool overwrite);
|
||||
|
||||
// TOKEN_DRIVE_LIST:识别 download_file,存文件管理器子链接并下发 CMD_DOWN_FILES_V2。
|
||||
// 返回 true=已接管(保持子链接,收尾由 ClearFileTransfer);false=会话已清理(调用方 CancelIO)。
|
||||
bool OnDownloadDriveList(uint64_t device_id, context* fmSubCtx);
|
||||
|
||||
// COMMAND_SEND_FILE_V2:解析 chunk,路径校验 + overwrite 判定 + 落盘(RecvFileChunkV2)。
|
||||
void OnFileChunkV2(uint64_t device_id, context* streamSubCtx, const BYTE* buf, ULONG len);
|
||||
|
||||
// COMMAND_FILE_COMPLETE_V2:SHA-256 校验(HandleFileCompleteV2)+ 完成判定。
|
||||
void OnFileCompleteV2(uint64_t device_id, const BYTE* buf, ULONG len);
|
||||
|
||||
// 工具线程:等待传输完成;true=成功(files 已填,error=0),false=失败/超时(error 填原因)。
|
||||
// 无论成败,files 都会返回已见文件的落盘路径(成功=完整文件,失败=供调用方删半成品)。
|
||||
// 调用方随后必须调 ClearFileTransfer 收尾(擦会话 + CancelIO 子链接)。
|
||||
bool WaitFileTransferDone(uint64_t device_id, int timeoutMs,
|
||||
std::vector<FileTransferEntry>& files, int& skipped, int& error);
|
||||
|
||||
// 收尾:擦会话 + 锁外 CancelIO 文件管理器/流式两条子链接(幂等)。
|
||||
void ClearFileTransfer(uint64_t device_id);
|
||||
|
||||
// 安全配置(启动时由 CMy2015RemoteDlg 读 THIS_CFG 后设置)。
|
||||
void SetReadonly(bool readonly) { m_readonly = readonly; }
|
||||
void SetCmdWhitelist(const std::string& whitelist) { m_cmdWhitelist = whitelist; }
|
||||
@@ -272,10 +315,34 @@ private:
|
||||
std::map<uint64_t, ScreenCtrlSession> m_ScreenCtrlSessions; // device_id → 会话
|
||||
std::map<context*, uint64_t> m_ScreenCtrlContextToDevice; // subCtx → device_id(OfflineProc 反查)
|
||||
|
||||
// ===== P6:文件传输会话(受 m_FileXferMutex 保护;单设备单传输)=====
|
||||
struct FileTransferSession {
|
||||
std::string tool; // 恒为 "download_file"
|
||||
std::string localDir; // 本机保存目录(ANSI,结尾 '\')
|
||||
std::string remotePath; // 客户端远程路径(ANSI,发 CMD_DOWN_FILES_V2 用)
|
||||
bool overwrite = false; // 是否覆盖已存在文件
|
||||
context* fmSubCtx = nullptr; // 文件管理器子链接(下发 CMD_DOWN_FILES_V2)
|
||||
context* streamSubCtx = nullptr; // 流式子链接(首个 chunk 填,收尾 CancelIO)
|
||||
uint64_t transferID = 0; // 客户端回填的传输会话 ID
|
||||
uint32_t totalFiles = 0; // 客户端声明的文件总数(含目录项,见 F4)
|
||||
uint32_t directoryCount = 0; // 目录项计数(目录不发 COMPLETE 包)
|
||||
uint32_t filesDone = 0; // 已完成的文件数(含跳过的)
|
||||
bool done = false; // 全部完成或出错
|
||||
int error = 0; // 0=ok;8=哈希不匹配;1001=路径逃逸;1002=超时
|
||||
std::map<uint32_t, FileTransferEntry> fileEntries; // fileIndex → {path,size,sha256}
|
||||
std::set<uint32_t> skipIndexes; // overwrite=false 跳过的 fileIndex
|
||||
std::vector<FileTransferEntry> skipped; // 跳过的文件(供输出)
|
||||
time_t startAt = 0;
|
||||
};
|
||||
std::mutex m_FileXferMutex;
|
||||
std::condition_variable m_FileXferCv;
|
||||
std::map<uint64_t, FileTransferSession> m_FileXferSessions; // device_id → 会话
|
||||
|
||||
bool m_readonly = true;
|
||||
std::string m_cmdWhitelist;
|
||||
bool m_terminalEnabled = false; // 持久终端开关(默认关;要求 m_readonly=false)
|
||||
bool m_remoteControlEnabled = false; // 远程控制开关(默认关;要求 m_readonly=false)
|
||||
bool m_fileTransferEnabled = false; // 文件传输开关(默认关;download_file)
|
||||
};
|
||||
|
||||
// 全局访问器(仿 WebService(),见 WebService.h 末尾)
|
||||
|
||||
Reference in New Issue
Block a user