gitscope
← rgit / Diff · 1dbaf86 → 1dbaf86
Changes 1dbaf86..1dbaf86
Patch commit
diff --git a/Cargo.lock b/Cargo.lock
index 0101d84..459df44 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1716,9 +1716,11 @@ dependencies = [
"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
+++ b/Cargo.toml
@@ -16,6 +16,7 @@ futures = "0.3"
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 = "0.1"
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
--- /dev/null
+++ b/src/git_cgi.rs
@@ -0,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
+++ b/src/main.rs
@@ -18,6 +18,7 @@ use crate::{git::Git, layers::logger::LoggingMiddleware};
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
+++ b/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 @@ impl Deref for RepositoryPath {
}
// 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 @@ pub async fn service<ReqBody: Send + 'static>(mut request: Request<ReqBody>) ->
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()),
@@ -212,6 +229,50 @@ pub async fn handle_log(
})
}
+#[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)]
#[template(path = "repo/refs.html")]
pub struct RefsView {