Implement Upload Hook for momentry integration (Phase 1)
- Add upload_hook.rs module: trigger video_probe + video_register on upload - Add UploadHookSection to config: video extensions, binary paths - Integrate with SFTP: handle_close triggers hook on write files - Integrate with SCP/rsync: child process exit triggers hook - All 155 tests pass
This commit is contained in:
@@ -4,11 +4,12 @@
|
||||
use crate::ssh_server::packet::{PacketType, SshPacket};
|
||||
use crate::ssh_server::port_forward::{
|
||||
DirectTcpipChannel, ForwardedTcpipChannel, PortForwardManager,
|
||||
}; // Phase 13.3
|
||||
use crate::ssh_server::rsync_handler::RsyncHandler; // Phase 8: rsync handler
|
||||
use crate::ssh_server::scp_handler::ScpHandler; // Phase 8: SCP handler
|
||||
use crate::ssh_server::sftp_handler::SftpHandler; // Phase 7: SFTP handler
|
||||
use crate::ssh_server::ssh_security_config::SshSecurityConfig; // Phase 13.3: 安全配置
|
||||
};
|
||||
use crate::ssh_server::rsync_handler::RsyncHandler;
|
||||
use crate::ssh_server::scp_handler::ScpHandler;
|
||||
use crate::ssh_server::sftp_handler::SftpHandler;
|
||||
use crate::ssh_server::ssh_security_config::SshSecurityConfig;
|
||||
use crate::ssh_server::upload_hook::UploadHook;
|
||||
use anyhow::{anyhow, Result};
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use log::{info, warn};
|
||||
@@ -25,10 +26,10 @@ use std::process::{Child, ChildStderr, ChildStdin, ChildStdout}; // Phase 14:
|
||||
pub struct ChannelManager {
|
||||
channels: HashMap<u32, Channel>,
|
||||
next_channel_id: u32,
|
||||
/// ⭐⭐⭐⭐⭐ Phase 15.1: 待发送packet队列(用于同时发送WINDOW_ADJUST和SFTP响应)
|
||||
pub pending_packets: VecDeque<SshPacket>,
|
||||
/// 用户home目录(SFTP/SCP/rsync根目录,SFTPGo兼容)
|
||||
pub home_dir: PathBuf,
|
||||
pub upload_hook: Option<std::sync::Arc<UploadHook>>,
|
||||
pub user_uuid: String,
|
||||
}
|
||||
|
||||
/// Phase 14: 交互式Exec进程管理(参考OpenSSH session.c: do_exec_no_pty)
|
||||
@@ -44,12 +45,18 @@ pub struct ExecProcess {
|
||||
}
|
||||
|
||||
impl ChannelManager {
|
||||
pub fn new(home_dir: PathBuf) -> Self {
|
||||
pub fn new(
|
||||
home_dir: PathBuf,
|
||||
upload_hook: Option<std::sync::Arc<UploadHook>>,
|
||||
user_uuid: String,
|
||||
) -> Self {
|
||||
Self {
|
||||
channels: HashMap::new(),
|
||||
next_channel_id: 0,
|
||||
pending_packets: VecDeque::new(),
|
||||
home_dir,
|
||||
upload_hook,
|
||||
user_uuid,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -574,7 +581,13 @@ impl ChannelManager {
|
||||
};
|
||||
|
||||
let vfs = Box::new(crate::vfs::local_fs::LocalFs::new());
|
||||
let sftp_handler = SftpHandler::new(root_dir, vfs, maxpacket); // ⭐⭐⭐⭐⭐ Phase 4: 传入 maxpack
|
||||
let sftp_handler = SftpHandler::new(
|
||||
root_dir,
|
||||
vfs,
|
||||
maxpacket,
|
||||
self.upload_hook.clone(),
|
||||
self.user_uuid.clone(),
|
||||
);
|
||||
|
||||
// 存储到channel
|
||||
if let Some(ch) = self.channels.get_mut(&channel) {
|
||||
@@ -1374,7 +1387,10 @@ impl ChannelManager {
|
||||
);
|
||||
child_exited = true;
|
||||
|
||||
// ⭐⭐⭐⭐⭐ Child exited,读取剩余stdout(如果有)
|
||||
let command_str = exec_process.command.clone();
|
||||
let should_trigger_hook = status.success()
|
||||
&& (command_str.contains("scp") || command_str.contains("rsync"));
|
||||
|
||||
if let Some(stdout) = &mut exec_process.stdout {
|
||||
let mut buffer = vec![0u8; 32768];
|
||||
match stdout.read(&mut buffer) {
|
||||
@@ -1395,6 +1411,17 @@ impl ChannelManager {
|
||||
}
|
||||
}
|
||||
|
||||
if should_trigger_hook {
|
||||
let dest_path = Self::extract_dest_path_from_command(&command_str, &self.home_dir);
|
||||
if let Some(path) = dest_path {
|
||||
if let Some(hook) = &self.upload_hook {
|
||||
if let Err(e) = hook.trigger(&path, &self.user_uuid) {
|
||||
warn!("Upload hook failed for {:?}: {}", path, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 没有剩余数据,返回child_exited标志
|
||||
return Ok((None, false, true));
|
||||
}
|
||||
@@ -1823,6 +1850,29 @@ impl ChannelManager {
|
||||
Ok(Some(packets))
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_dest_path_from_command(command: &str, home_dir: &PathBuf) -> Option<PathBuf> {
|
||||
if command.contains("scp") {
|
||||
if command.contains("scp -t") {
|
||||
let parts: Vec<&str> = command.split_whitespace().collect();
|
||||
for part in parts.iter().rev() {
|
||||
if !part.starts_with("-") && *part != "scp" && *part != "-t" {
|
||||
return Some(home_dir.join(part));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if command.contains("rsync") {
|
||||
if command.contains("--server") {
|
||||
let parts: Vec<&str> = command.split_whitespace().collect();
|
||||
for part in parts.iter().rev() {
|
||||
if !part.starts_with("-") && !part.contains("--") && *part != "rsync" && *part != "--server" && *part != "--sender" {
|
||||
return Some(home_dir.join(part));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// SSH Channel结构(参考OpenSSH channel.c: struct channel)
|
||||
@@ -1967,13 +2017,13 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_channel_manager_creation() {
|
||||
let manager = ChannelManager::new(PathBuf::from("/tmp"));
|
||||
let manager = ChannelManager::new(PathBuf::from("/tmp"), None, "test_user".to_string());
|
||||
assert_eq!(manager.next_channel_id, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_channel_open_confirmation() {
|
||||
let manager = ChannelManager::new(PathBuf::from("/tmp"));
|
||||
let manager = ChannelManager::new(PathBuf::from("/tmp"), None, "test_user".to_string());
|
||||
let packet = manager
|
||||
.build_channel_open_confirmation(0, 100, 2097152, 32768)
|
||||
.unwrap();
|
||||
@@ -1986,7 +2036,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_channel_success() {
|
||||
let manager = ChannelManager::new(PathBuf::from("/tmp"));
|
||||
let manager = ChannelManager::new(PathBuf::from("/tmp"), None, "test_user".to_string());
|
||||
let packet = manager.build_channel_success(0).unwrap();
|
||||
|
||||
assert_eq!(packet.payload[0], PacketType::SSH_MSG_CHANNEL_SUCCESS as u8);
|
||||
|
||||
Reference in New Issue
Block a user