conduit_git_http_backend/
lib.rs

1#![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        // Required environment variables
21        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        // Pass in the body of the request (if any)
41        //
42        // As part of the CGI interface we're required to take care of gzip'd
43        // requests. I'm not totally sure that this sequential copy is the best
44        // thing to do or actually correct...
45        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        // Parse the headers coming out, and the pass through the rest of the
53        // process back down the stack.
54        //
55        // Note that we have to be careful to not drop the process which will wait
56        // for the process to exit (and we haven't read stdout)
57        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        /// Obtain the value of a header
95        ///
96        /// If multiple headers have the same name, only one will be returned.
97        ///
98        /// If there is no header, of if there is an error parsings it as utf8
99        /// then an empty slice will be returned.
100        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}