git_internal/protocol/
ssh.rs1use super::{
6 core::{AuthenticationService, GitProtocol, RepositoryAccess},
7 types::{ProtocolError, ProtocolStream},
8};
9
10pub struct SshGitHandler<R: RepositoryAccess, A: AuthenticationService> {
12 protocol: GitProtocol<R, A>,
13}
14
15impl<R: RepositoryAccess, A: AuthenticationService> SshGitHandler<R, A> {
16 pub fn new(repo_access: R, auth_service: A) -> Self {
18 let mut protocol = GitProtocol::new(repo_access, auth_service);
19 protocol.set_transport(super::types::TransportProtocol::Ssh);
20 Self { protocol }
21 }
22
23 pub async fn authenticate_ssh(
26 &self,
27 username: &str,
28 public_key: &[u8],
29 ) -> Result<(), ProtocolError> {
30 self.protocol.authenticate_ssh(username, public_key).await
31 }
32
33 pub async fn handle_upload_pack(
35 &mut self,
36 request_data: &[u8],
37 ) -> Result<ProtocolStream, ProtocolError> {
38 self.protocol.upload_pack(request_data).await
39 }
40
41 pub async fn handle_receive_pack(
43 &mut self,
44 request_stream: ProtocolStream,
45 ) -> Result<ProtocolStream, ProtocolError> {
46 self.protocol.receive_pack(request_stream).await
47 }
48
49 pub async fn handle_info_refs(&mut self, service: &str) -> Result<Vec<u8>, ProtocolError> {
51 self.protocol.info_refs(service).await
52 }
53}
54
55pub fn parse_ssh_command(command_line: &str) -> Option<(String, Vec<String>)> {
58 let parts: Vec<&str> = command_line.split_whitespace().collect();
59 if parts.is_empty() {
60 return None;
61 }
62
63 let command = parts[0].to_string();
64 let args = parts[1..].iter().map(|s| s.to_string()).collect();
65
66 Some((command, args))
67}
68
69pub fn is_git_ssh_command(command: &str) -> bool {
71 matches!(command, "git-upload-pack" | "git-receive-pack")
72}
73
74pub fn extract_repo_path_from_args(args: &[String]) -> Option<&str> {
76 args.first().map(|s| s.as_str())
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82
83 #[test]
85 fn parse_command_with_args() {
86 let input = "git-upload-pack /repos/demo.git";
87 let parsed = parse_ssh_command(input).expect("command should parse");
88 assert_eq!(parsed.0, "git-upload-pack");
89 assert_eq!(parsed.1, vec!["/repos/demo.git".to_string()]);
90 }
91
92 #[test]
94 fn parse_command_empty_returns_none() {
95 assert!(parse_ssh_command("").is_none());
96 assert!(parse_ssh_command(" ").is_none());
97 }
98
99 #[test]
101 fn validate_git_ssh_commands() {
102 assert!(is_git_ssh_command("git-upload-pack"));
103 assert!(is_git_ssh_command("git-receive-pack"));
104 assert!(!is_git_ssh_command("git-upload-archive"));
105 assert!(!is_git_ssh_command("other"));
106 }
107
108 #[test]
110 fn extract_repo_path_from_first_arg() {
111 let args = vec!["/repos/demo.git".to_string(), "--stateless-rpc".to_string()];
112 assert_eq!(extract_repo_path_from_args(&args), Some("/repos/demo.git"));
113 let empty: Vec<String> = vec![];
114 assert_eq!(extract_repo_path_from_args(&empty), None);
115 }
116}