🏡 index : ~doyle/rgit.git

author Jordan Doyle <jordan@doyle.la> 2022-07-21 0:09:09.0 +01:00:00
committer Jordan Doyle <jordan@doyle.la> 2022-07-21 0:09:09.0 +01:00:00
commit
1dbaf86c6b50e46c49d6fc03f60d338b68d8e8e9 [patch]
tree
67437faa0eb5d24d47ede4f6a9a146e85ac818d1
parent
6be32501a040072d24b8fe358c82a48a38cecf20
download
1dbaf86c6b50e46c49d6fc03f60d338b68d8e8e9.tar.gz

Primitive handling of info/refs & git-upload-pack



Diff

 Cargo.lock          |  2 ++
 Cargo.toml          |  2 ++
 src/git_cgi.rs      | 24 ++++++++++++++++++++++++
 src/main.rs         |  1 +
 src/methods/repo.rs | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
 5 files changed, 93 insertions(+), 3 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 0101d84..459df44 100644
--- a/Cargo.lock
+++ a/Cargo.lock
@@ -1716,9 +1716,11 @@
 "futures",
 "git2",
 "hex",
 "httparse",
 "humantime",
 "md5",
 "moka",
 "nom",
 "parking_lot 0.12.1",
 "path-clean",
 "rsass",
diff --git a/Cargo.toml b/Cargo.toml
index 5b6ad9d..d234d9c 100644
--- a/Cargo.toml
+++ a/Cargo.toml
@@ -16,6 +16,7 @@
git2 = "0.14"
hex = "0.4"
humantime = "2.1"
nom = "7.1"
md5 = "0.7"
moka = { version = "0.9", features = ["future"] }
path-clean = "0.1"
@@ -33,6 +34,7 @@
tracing-subscriber = "0.3"
unix_mode = "0.1"
uuid = { version = "1.1", features = ["v4"] }
httparse = "1.7"
yoke = { version = "0.6", features = ["derive"] }

[build-dependencies]
diff --git a/src/git_cgi.rs b/src/git_cgi.rs
new file mode 100644
index 0000000..c0443b2 100644
--- /dev/null
+++ a/src/git_cgi.rs
@@ -1,0 +1,24 @@
use axum::body::{boxed, Body};
use axum::http::header::HeaderName;
use axum::http::HeaderValue;
use axum::response::Response;
use std::str::FromStr;

pub fn cgi_to_response(buffer: &[u8]) -> Response {
    let mut headers = [httparse::EMPTY_HEADER; 10];
    let (body_offset, headers) = httparse::parse_headers(buffer, &mut headers)
        .unwrap()
        .unwrap();

    let mut response = Response::new(boxed(Body::from(buffer[body_offset..].to_vec())));

    // TODO: extract status header
    for header in headers {
        response.headers_mut().insert(
            HeaderName::from_str(header.name).unwrap(),
            HeaderValue::from_bytes(header.value).unwrap(),
        );
    }

    response
}
diff --git a/src/main.rs b/src/main.rs
index e5babb7..e5cb6ac 100644
--- a/src/main.rs
+++ a/src/main.rs
@@ -18,6 +18,7 @@

mod database;
mod git;
mod git_cgi;
mod layers;
mod methods;

diff --git a/src/methods/repo.rs b/src/methods/repo.rs
index 1b78848..5d6422e 100644
--- a/src/methods/repo.rs
+++ a/src/methods/repo.rs
@@ -1,20 +1,24 @@
use std::fmt::{Display, Formatter};
use std::{
    fmt::{Debug, Display, Formatter},
    io::Write,
    ops::Deref,
    path::{Path, PathBuf},
    process::Stdio,
    sync::Arc,
};

use askama::Template;
use axum::http::HeaderValue;
use axum::{
    body::HttpBody,
    extract::Query,
    handler::Handler,
    http,
    http::HeaderValue,
    http::Request,
    response::{IntoResponse, Response},
    Extension,
};
use bytes::Bytes;
use path_clean::PathClean;
use serde::Deserialize;
use tower::{util::BoxCloneService, Service};
@@ -50,7 +54,13 @@
}

// this is some wicked, wicked abuse of axum right here...
pub async fn service<ReqBody: Send + 'static>(mut request: Request<ReqBody>) -> Response {
pub async fn service<ReqBody: HttpBody + Send + Debug + 'static>(
    mut request: Request<ReqBody>,
) -> Response
where
    <ReqBody as HttpBody>::Data: Send + Sync,
    <ReqBody as HttpBody>::Error: std::error::Error + Send + Sync,
{
    let mut uri_parts: Vec<&str> = request
        .uri()
        .path()
@@ -63,6 +73,13 @@

    let mut service = match uri_parts.pop() {
        Some("about") => BoxCloneService::new(handle_about.into_service()),
        // TODO: https://man.archlinux.org/man/git-http-backend.1.en
        // TODO: GIT_PROTOCOL
        Some("refs") if uri_parts.last() == Some(&"info") => {
            uri_parts.pop();
            BoxCloneService::new(handle_info_refs.into_service())
        }
        Some("git-upload-pack") => BoxCloneService::new(handle_git_upload_pack.into_service()),
        Some("refs") => BoxCloneService::new(handle_refs.into_service()),
        Some("log") => BoxCloneService::new(handle_log.into_service()),
        Some("tree") => BoxCloneService::new(handle_tree.into_service()),
@@ -210,6 +227,50 @@
        next_offset,
        branch: query.branch,
    })
}

#[derive(Deserialize)]
pub struct SmartGitQuery {
    service: String,
}

pub async fn handle_info_refs(
    Extension(RepositoryPath(repository_path)): Extension<RepositoryPath>,
    Query(query): Query<SmartGitQuery>,
) -> Response {
    // todo: tokio command
    let out = std::process::Command::new("git")
        .arg("http-backend")
        .env("REQUEST_METHOD", "GET")
        .env("PATH_INFO", "/info/refs")
        .env("GIT_PROJECT_ROOT", repository_path)
        .env("QUERY_STRING", format!("service={}", query.service))
        .output()
        .unwrap();

    crate::git_cgi::cgi_to_response(&out.stdout)
}

pub async fn handle_git_upload_pack(
    Extension(RepositoryPath(repository_path)): Extension<RepositoryPath>,
    body: Bytes,
) -> Response {
    // todo: tokio command
    let mut child = std::process::Command::new("git")
        .arg("http-backend")
        // todo: read all this from request
        .env("REQUEST_METHOD", "POST")
        .env("CONTENT_TYPE", "application/x-git-upload-pack-request")
        .env("PATH_INFO", "/git-upload-pack")
        .env("GIT_PROJECT_ROOT", repository_path)
        .stdout(Stdio::piped())
        .stdin(Stdio::piped())
        .spawn()
        .unwrap();
    child.stdin.as_mut().unwrap().write_all(&body).unwrap();
    let out = child.wait_with_output().unwrap();

    crate::git_cgi::cgi_to_response(&out.stdout)
}

#[derive(Template)]