use std::collections::HashMap; use std::io; use std::io::ErrorKind; use std::process::Stdio; use actix_web::{HttpRequest, web, HttpResponse}; use actix_web::http::{header, StatusCode}; use actix_web::web::Buf; use actix_web_httpauth::extractors::basic::BasicAuth; use futures::StreamExt; use tokio::io::{AsyncBufReadExt, AsyncWriteExt}; use tokio::process::{Child, Command}; use crate::gitust::Gitust; use crate::reader::ToStream; //#[get("/git/{owner}/{repo}.git/{path:.*}")] pub async fn git_proto( mut payload : web::Payload, web::Path((owner, reponame, path)): web::Path<(String, String, String)>, mut req: HttpRequest, gitust : web::Data, auth : BasicAuth, ) -> io::Result{ //println!("enter git_proto"); let mut cmd = Command::new("git"); cmd.arg("http-backend"); // Required environment variables cmd.env("REQUEST_METHOD", req.method().as_str()); cmd.env("GIT_PROJECT_ROOT", &gitust.repo_root_path); cmd.env("PATH_INFO", format!("/{}/{}.git/{}",owner, reponame, path)); cmd.env("REMOTE_USER", auth.user_id().to_string()); //cmd.env("REMOTE_ADDR", req.remote_addr().to_string()); cmd.env("QUERY_STRING", req.query_string()); cmd.env("CONTENT_TYPE", header(&req, header::CONTENT_TYPE)); cmd.env("GIT_HTTP_EXPORT_ALL", ""); cmd.stderr(Stdio::inherit()) .stdout(Stdio::piped()) .stdin(Stdio::piped()); let mut p: Child = cmd.spawn()?; let mut input = p.stdin.take().unwrap(); //println!("Displaying request..."); while let Some(Ok(bytes)) = payload.next().await { //println!("request body : {}", String::from_utf8_lossy(bytes.bytes())); input.write_all(bytes.bytes()).await; } //println!("input sent"); let mut rdr = tokio::io::BufReader::new(p.stdout.take().unwrap()); let mut headers = HashMap::new(); loop { let mut line = String::new(); let len = rdr.read_line(&mut line).await?; // println!("line : \"{}\"", line); if line.len() == 2 { break; } let mut parts = line.splitn(2, ':'); let key = parts.next().unwrap(); let value = parts.next().unwrap(); let value_len = value.len(); let value = &value[1..value_len-2]; headers .entry(key.to_string()) .or_insert_with(Vec::new) .push(value.to_string()); } //println!("response headers : {:?}", headers); let status_code : u16 = { let line = headers.remove("Status").unwrap_or_default(); // println!("{:?}", &line); let line = line.into_iter().next().unwrap_or_default(); let parts : Vec<&str> = line.split(' ').collect(); parts.into_iter().next().unwrap_or("").parse().unwrap_or(200) }; // println!("status code {}", status_code); let statusCode = match StatusCode::from_u16(status_code) { Ok(v) => {Ok(v)} Err(ioe) => {Err(io::Error::new(ErrorKind::Other, "Invalid HTTP status code"))} }; let mut builder = HttpResponse::build(statusCode?); for (name, vec) in headers.iter() { for value in vec { // println!("entry : ({}, {})", name, value.clone()); builder.header(name, value.clone()); } } // println!("Write body..."); let response = builder.streaming(ToStream(rdr)); return Ok(response); } fn header(req: &HttpRequest, name: header::HeaderName) -> &str { req.headers() .get(name) .map(|value| value.to_str().unwrap_or_default()) .unwrap_or_default() }