diff --git a/src/gitutils/gitproto.rs b/src/gitutils/gitproto.rs new file mode 100644 index 0000000..6f77ee7 --- /dev/null +++ b/src/gitutils/gitproto.rs @@ -0,0 +1,105 @@ +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() +} diff --git a/src/gitutils/mod.rs b/src/gitutils/mod.rs index 0ba6f8c..c73d0e4 100644 --- a/src/gitutils/mod.rs +++ b/src/gitutils/mod.rs @@ -2,3 +2,4 @@ pub mod gitdir; pub mod gitrepo; pub mod gitcommit; pub mod gitfile; +pub mod gitproto; diff --git a/src/main.rs b/src/main.rs index 62f774e..c9149ef 100644 --- a/src/main.rs +++ b/src/main.rs @@ -31,6 +31,7 @@ use tokio::process::{Child, ChildStdout, Command}; use gitutils::gitcommit::GitRef; use gitutils::gitdir::GitDir; use gitutils::gitfile::GitFile; +use gitutils::gitproto; use gitutils::gitrepo::GitRepo; use web::repo; @@ -108,95 +109,6 @@ async fn chunk() -> HttpResponse { HttpResponse::Ok().streaming(rx) } -//#[get("/git/{owner}/{repo}.git/{path:.*}")] -async fn git_proto( - mut payload : webx::Payload, - webx::Path((owner, reponame, path)): webx::Path<(String, String, String)>, - mut req: HttpRequest, - gitust : webx::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() -} - #[post("/echo")] async fn echo(req_body: String) -> impl Responder { HttpResponse::Ok().body(req_body) @@ -271,7 +183,7 @@ async fn main() -> std::io::Result<()> { .service( webx::resource("/git/{user}/{repo}.git/{path:.*}") // .wrap(auth) - .route(webx::route().to(git_proto)) + .route(webx::route().to(gitproto::git_proto)) ) .service( Files::new("/static", "static")