conduit_git_http_backend/
lib.rs1#![deny(warnings)]
2#![warn(rust_2018_idioms)]
3
4use std::collections::HashMap;
5use std::io;
6use std::io::prelude::*;
7use std::path::PathBuf;
8use std::process::{Command, Stdio};
9
10use conduit::{box_error, header, Body, HandlerResult, RequestExt, Response};
11use flate2::read::GzDecoder;
12
13pub struct Serve(pub PathBuf);
14
15impl Serve {
16 fn doit(&self, req: &mut dyn RequestExt) -> io::Result<Response<Body>> {
17 let mut cmd = Command::new("git");
18 cmd.arg("http-backend");
19
20 cmd.env("REQUEST_METHOD", req.method().as_str());
22 cmd.env("GIT_PROJECT_ROOT", &self.0);
23 cmd.env(
24 "PATH_INFO",
25 if req.path().starts_with('/') {
26 req.path().to_string()
27 } else {
28 format!("/{}", req.path())
29 },
30 );
31 cmd.env("REMOTE_USER", "");
32 cmd.env("REMOTE_ADDR", req.remote_addr().to_string());
33 cmd.env("QUERY_STRING", req.query_string().unwrap_or_default());
34 cmd.env("CONTENT_TYPE", header(req, header::CONTENT_TYPE));
35 cmd.stderr(Stdio::inherit())
36 .stdout(Stdio::piped())
37 .stdin(Stdio::piped());
38 let mut p = cmd.spawn()?;
39
40 if header(req, header::CONTENT_ENCODING) == "gzip" {
46 let mut body = GzDecoder::new(req.body());
47 io::copy(&mut body, &mut p.stdin.take().unwrap())?;
48 } else {
49 io::copy(&mut req.body(), &mut p.stdin.take().unwrap())?;
50 }
51
52 let mut rdr = io::BufReader::new(p.stdout.take().unwrap());
58
59 let mut headers = HashMap::new();
60 for line in rdr.by_ref().lines() {
61 let line = line?;
62 if line == "" || line == "\r" {
63 break;
64 }
65
66 let mut parts = line.splitn(2, ':');
67 let key = parts.next().unwrap();
68 let value = parts.next().unwrap();
69 let value = &value[1..];
70 headers
71 .entry(key.to_string())
72 .or_insert_with(Vec::new)
73 .push(value.to_string());
74 }
75
76 let status_code = {
77 let line = headers.remove("Status").unwrap_or_default();
78 let line = line.into_iter().next().unwrap_or_default();
79 let mut parts = line.splitn(1, ' ');
80 parts.next().unwrap_or("").parse().unwrap_or(200)
81 };
82
83 let mut builder = Response::builder().status(status_code);
84 for (name, vec) in headers.iter() {
85 for value in vec {
86 builder = builder.header(name, value);
87 }
88 }
89
90 let mut body = Vec::new();
91 rdr.read_to_end(&mut body)?;
92 return Ok(builder.body(Body::from_vec(body)).unwrap());
93
94 fn header(req: &dyn RequestExt, name: header::HeaderName) -> &str {
101 req.headers()
102 .get(name)
103 .map(|value| value.to_str().unwrap_or_default())
104 .unwrap_or_default()
105 }
106 }
107}
108
109impl conduit::Handler for Serve {
110 fn call(&self, req: &mut dyn RequestExt) -> HandlerResult {
111 self.doit(req).map_err(box_error)
112 }
113}