git_internal/protocol/
http.rs

1use super::core::{AuthenticationService, GitProtocol, RepositoryAccess};
2use super::types::{ProtocolError, ProtocolStream};
3/// HTTP transport adapter for Git protocol
4///
5/// This module provides HTTP-specific handling for Git smart protocol operations.
6/// It's a thin wrapper around the core GitProtocol that handles HTTP-specific
7/// request/response formatting and uses the utility functions for proper HTTP handling.
8use serde::Deserialize;
9use std::collections::HashMap;
10
11/// HTTP Git protocol handler
12pub struct HttpGitHandler<R: RepositoryAccess, A: AuthenticationService> {
13    protocol: GitProtocol<R, A>,
14}
15
16impl<R: RepositoryAccess, A: AuthenticationService> HttpGitHandler<R, A> {
17    /// Create a new HTTP Git handler
18    pub fn new(repo_access: R, auth_service: A) -> Self {
19        let mut protocol = GitProtocol::new(repo_access, auth_service);
20        protocol.set_transport(super::types::TransportProtocol::Http);
21        Self { protocol }
22    }
23
24    /// Authenticate the HTTP request using provided headers
25    /// Call this before invoking handle_* methods if your server requires auth
26    pub async fn authenticate_http(
27        &self,
28        headers: &HashMap<String, String>,
29    ) -> Result<(), ProtocolError> {
30        self.protocol.authenticate_http(headers).await
31    }
32
33    /// Handle HTTP info/refs request
34    ///
35    /// Processes GET requests to /{repo}/info/refs?service=git-{service}
36    /// Uses extract_repo_path and get_service_from_query for proper parsing
37    pub async fn handle_info_refs(
38        &mut self,
39        request_path: &str,
40        query: &str,
41    ) -> Result<(Vec<u8>, &'static str), ProtocolError> {
42        // Validate repository path exists in request
43        extract_repo_path(request_path)
44            .ok_or_else(|| ProtocolError::InvalidRequest("Invalid repository path".to_string()))?;
45
46        // Get service from query parameters
47        let service = get_service_from_query(query).ok_or_else(|| {
48            ProtocolError::InvalidRequest("Missing service parameter".to_string())
49        })?;
50
51        // Validate it's a Git request
52        if !is_git_request(request_path) {
53            return Err(ProtocolError::InvalidRequest(
54                "Not a Git request".to_string(),
55            ));
56        }
57
58        let response_data = self.protocol.info_refs(service).await?;
59        let content_type = get_advertisement_content_type(service);
60
61        Ok((response_data, content_type))
62    }
63
64    /// Handle HTTP upload-pack request
65    ///
66    /// Processes POST requests to /{repo}/git-upload-pack
67    pub async fn handle_upload_pack(
68        &mut self,
69        request_path: &str,
70        request_body: &[u8],
71    ) -> Result<(ProtocolStream, &'static str), ProtocolError> {
72        // Validate repository path exists in request
73        extract_repo_path(request_path)
74            .ok_or_else(|| ProtocolError::InvalidRequest("Invalid repository path".to_string()))?;
75
76        // Validate it's a Git request
77        if !is_git_request(request_path) {
78            return Err(ProtocolError::InvalidRequest(
79                "Not a Git request".to_string(),
80            ));
81        }
82
83        let response_stream = self.protocol.upload_pack(request_body).await?;
84        let content_type = get_content_type("git-upload-pack");
85
86        Ok((response_stream, content_type))
87    }
88
89    /// Handle HTTP receive-pack request
90    ///
91    /// Processes POST requests to /{repo}/git-receive-pack
92    pub async fn handle_receive_pack(
93        &mut self,
94        request_path: &str,
95        request_stream: ProtocolStream,
96    ) -> Result<(ProtocolStream, &'static str), ProtocolError> {
97        // Validate repository path exists in request
98        extract_repo_path(request_path)
99            .ok_or_else(|| ProtocolError::InvalidRequest("Invalid repository path".to_string()))?;
100
101        // Validate it's a Git request
102        if !is_git_request(request_path) {
103            return Err(ProtocolError::InvalidRequest(
104                "Not a Git request".to_string(),
105            ));
106        }
107
108        let response_stream = self.protocol.receive_pack(request_stream).await?;
109        let content_type = get_content_type("git-receive-pack");
110
111        Ok((response_stream, content_type))
112    }
113}
114
115/// HTTP-specific utility functions
116/// Get content type for Git HTTP responses
117pub fn get_content_type(service: &str) -> &'static str {
118    match service {
119        "git-upload-pack" => "application/x-git-upload-pack-result",
120        "git-receive-pack" => "application/x-git-receive-pack-result",
121        _ => "application/x-git-upload-pack-advertisement",
122    }
123}
124
125/// Get content type for Git HTTP info/refs advertisement
126pub fn get_advertisement_content_type(service: &str) -> &'static str {
127    match service {
128        "git-upload-pack" => "application/x-git-upload-pack-advertisement",
129        "git-receive-pack" => "application/x-git-receive-pack-advertisement",
130        _ => "application/x-git-upload-pack-advertisement",
131    }
132}
133
134/// Check if request is a Git smart protocol request
135pub fn is_git_request(path: &str) -> bool {
136    path.ends_with("/info/refs")
137        || path.ends_with("/git-upload-pack")
138        || path.ends_with("/git-receive-pack")
139}
140
141/// Extract repository path from HTTP request path
142pub fn extract_repo_path(path: &str) -> Option<&str> {
143    if let Some(pos) = path.rfind("/info/refs") {
144        Some(&path[..pos])
145    } else if let Some(pos) = path.rfind("/git-upload-pack") {
146        Some(&path[..pos])
147    } else if let Some(pos) = path.rfind("/git-receive-pack") {
148        Some(&path[..pos])
149    } else {
150        None
151    }
152}
153
154/// Get Git service from query parameters
155pub fn get_service_from_query(query: &str) -> Option<&str> {
156    for param in query.split('&') {
157        if let Some(("service", value)) = param.split_once('=') {
158            return Some(value);
159        }
160    }
161    None
162}
163
164/// Parameters for git info-refs request
165#[derive(Debug, Deserialize)]
166pub struct InfoRefsParams {
167    pub service: String,
168}