git_http_backend/actix/
git_upload_pack.rs1use crate::GitConfig;
2use actix_web::http::header::HeaderValue;
3use actix_web::http::{header, StatusCode};
4use actix_web::web::Payload;
5use actix_web::{web, HttpRequest, HttpResponse, HttpResponseBuilder, Responder};
6use flate2::read::GzDecoder;
7use futures_util::StreamExt;
8use std::io;
9use std::io::{Cursor, Read, Write};
10use std::process::{Command, Stdio};
11
12pub async fn git_upload_pack(
13 request: HttpRequest,
14 mut payload: Payload,
15 service: web::Data<impl GitConfig>,
16) -> impl Responder {
17 let uri = request.uri();
18 let path = uri.path().to_string().replace("/git-upload-pack", "");
19 let path = service.rewrite(path).await;
20 let version = request
21 .headers()
22 .get("Git-Protocol")
23 .unwrap_or(&HeaderValue::from_str("").unwrap())
24 .to_str()
25 .map(|s| s.to_string())
26 .unwrap_or("".to_string());
27
28 let mut resp = HttpResponseBuilder::new(StatusCode::OK);
29 resp.append_header(("Content-Type", "application/x-git-upload-pack-advertise"));
30 resp.append_header(("Connection", "Keep-Alive"));
31 resp.append_header(("Transfer-Encoding", "chunked"));
32 resp.append_header(("X-Content-Type-Options", "nosniff"));
33 let mut cmd = Command::new("git");
34 cmd.arg("upload-pack");
35 cmd.arg("--stateless-rpc");
36 cmd.arg(".");
37 if !version.is_empty() {
38 cmd.env("GIT_PROTOCOL", version.clone());
39 }
40 cmd.stderr(Stdio::piped());
41 cmd.stdin(Stdio::piped());
42 cmd.stdout(Stdio::piped());
43 cmd.current_dir(path);
44
45 let span = cmd.spawn();
46 let mut span = match span {
47 Ok(span) => span,
48 Err(e) => {
49 eprintln!("Error running command: {}", e);
50 return HttpResponse::InternalServerError().body("Error running command");
51 }
52 };
53
54 let mut stdin = span.stdin.take().unwrap();
55 let mut stdout = span.stdout.take().unwrap();
56 let _stderr = span.stderr.take().unwrap();
57 let mut bytes = web::BytesMut::new();
58 while let Some(chunk) = payload.next().await {
59 match chunk {
60 Ok(data) => bytes.extend_from_slice(&data),
61 Err(e) => {
62 return HttpResponse::InternalServerError()
63 .body(format!("Failed to read request body: {}", e))
64 }
65 }
66 }
67 let body_data = match request
68 .headers()
69 .get(header::CONTENT_ENCODING)
70 .and_then(|v| v.to_str().ok())
71 {
72 Some("gzip") => {
73 let mut decoder = GzDecoder::new(Cursor::new(bytes));
74 let mut decoded_data = Vec::new();
75 if let Err(e) = io::copy(&mut decoder, &mut decoded_data) {
76 return HttpResponse::InternalServerError()
77 .body(format!("Failed to decode gzip body: {}", e));
78 }
79 decoded_data
80 }
81 _ => bytes.to_vec(),
82 };
83 if let Err(e) = stdin.write_all(&body_data) {
84 return HttpResponse::InternalServerError()
85 .body(format!("Failed to write to git process: {}", e));
86 }
87 drop(stdin);
88
89 let body_stream = actix_web::body::BodyStream::new(async_stream::stream! {
90 let mut buffer = [0; 8192];
91 loop {
92 match stdout.read(&mut buffer) {
93 Ok(0) => break, Ok(n) => {
95 yield Ok::<_, io::Error>(web::Bytes::copy_from_slice(&buffer[..n]))
96 },
97 Err(e) => {
98 eprintln!("Error reading stdout: {}", e);
99 break;
100 }
101 }
102 }
103 });
104 resp.body(body_stream)
105}