use std::io::{Read, Write};
use std::path::Path;
use std::sync::atomic::AtomicBool;
use super::{Protocol, ReceivePack, ServeError, UploadPack};
use crate::config::ConfigInputs;
use crate::push::PushReport;
use crate::{Repository, packet};
pub const NO_CACHE_HEADERS: [(&str, &str); 3] = [
("Expires", "Fri, 01 Jan 1980 00:00:00 GMT"),
("Pragma", "no-cache"),
("Cache-Control", "no-cache, max-age=0, must-revalidate"),
];
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Service {
UploadPack,
ReceivePack,
}
#[derive(Debug)]
#[non_exhaustive]
pub enum Outcome {
Fetch,
Push(PushReport),
}
impl Service {
pub fn from_query_value(value: &[u8]) -> Option<Self> {
match value {
b"git-upload-pack" => Some(Self::UploadPack),
b"git-receive-pack" => Some(Self::ReceivePack),
_ => None,
}
}
pub fn name(self) -> &'static str {
match self {
Self::UploadPack => "git-upload-pack",
Self::ReceivePack => "git-receive-pack",
}
}
pub fn advertisement_content_type(self) -> &'static str {
match self {
Self::UploadPack => "application/x-git-upload-pack-advertisement",
Self::ReceivePack => "application/x-git-receive-pack-advertisement",
}
}
pub fn request_content_type(self) -> &'static str {
match self {
Self::UploadPack => "application/x-git-upload-pack-request",
Self::ReceivePack => "application/x-git-receive-pack-request",
}
}
pub fn result_content_type(self) -> &'static str {
match self {
Self::UploadPack => "application/x-git-upload-pack-result",
Self::ReceivePack => "application/x-git-receive-pack-result",
}
}
pub fn advertise(
self,
path: &Path,
config_inputs: &ConfigInputs,
protocol: Protocol,
output: &mut impl Write,
cancel: &AtomicBool,
) -> Result<(), ServeError> {
if protocol == Protocol::V0 {
let line = format!("# service={}\n", self.name());
packet::packet(output, line.as_bytes(), cancel)?;
packet::put(output, b"0000", cancel)?;
}
match self {
Self::UploadPack => {
let repository = open(path, config_inputs)?;
let upload = UploadPack::new(&repository)?;
match protocol {
Protocol::V0 => upload.advertise_v0(true, output, cancel),
Protocol::V2 => upload.advertise(Protocol::V2, output, cancel),
}
}
Self::ReceivePack => {
ReceivePack::new(path, config_inputs.clone()).advertise(output, cancel)
}
}
}
pub fn respond(
self,
path: &Path,
config_inputs: &ConfigInputs,
protocol: Protocol,
body: &mut impl Read,
output: &mut impl Write,
cancel: &AtomicBool,
) -> Result<Outcome, ServeError> {
match self {
Self::UploadPack => {
let repository = open(path, config_inputs)?;
let upload = UploadPack::new(&repository)?;
respond_upload(&upload, protocol, body, output, cancel)?;
Ok(Outcome::Fetch)
}
Self::ReceivePack => {
let receive = ReceivePack::new(path, config_inputs.clone());
Ok(Outcome::Push(respond_receive(
&receive, body, output, cancel,
)?))
}
}
}
}
pub fn respond_upload(
upload: &UploadPack<'_>,
protocol: Protocol,
body: &mut impl Read,
output: &mut impl Write,
cancel: &AtomicBool,
) -> Result<(), ServeError> {
match protocol {
Protocol::V2 => upload.respond(body, output, cancel),
Protocol::V0 => upload.respond_v0(body, output, cancel),
}
}
pub fn respond_receive(
receive: &ReceivePack,
body: &mut impl Read,
output: &mut impl Write,
cancel: &AtomicBool,
) -> Result<PushReport, ServeError> {
receive.respond(body, output, cancel)
}
pub fn request_body<'a, R: Read + 'a>(
content_encoding: Option<&[u8]>,
body: R,
) -> Result<Box<dyn Read + 'a>, ServeError> {
match content_encoding {
None | Some(b"identity") => Ok(Box::new(body)),
Some(b"gzip" | b"x-gzip") => Ok(Box::new(flate2::read::GzDecoder::new(body))),
Some(_) => Err(ServeError::Unsupported("request content encoding")),
}
}
fn open(path: &Path, config_inputs: &ConfigInputs) -> Result<Repository, ServeError> {
Repository::open_with_config(path, config_inputs)
.map_err(|error| ServeError::Open(Box::new(error)))
}