From 1dbaf86c6b50e46c49d6fc03f60d338b68d8e8e9 Mon Sep 17 00:00:00 2001 From: Jordan Doyle Date: Thu, 21 Jul 2022 00:09:09 +0100 Subject: [PATCH] Primitive handling of info/refs & git-upload-pack --- 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(mut request: Request) -> Response { +pub async fn service( + mut request: Request, +) -> Response +where + ::Data: Send + Sync, + ::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, + Query(query): Query, +) -> 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, + 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)] -- rgit 0.1.3