use std::io::{self, Cursor, Read, Write};
use crate::error::{Error, Result};
use crate::objects::ObjectId;
use crate::transport::http::HttpClient;
use crate::transport::rpc_channel::RpcChannel;
use crate::transport::{Connection, Service};
struct ErrRead {
err: io::Error,
}
impl Read for ErrRead {
fn read(&mut self, _buf: &mut [u8]) -> io::Result<usize> {
Err(std::mem::replace(
&mut self.err,
io::Error::other("transport error already reported"),
))
}
}
pub struct StatelessHttpConnection<C: HttpClient> {
client: C,
service: Service,
repo_url: String,
post_url: String,
content_type: String,
accept: String,
git_protocol: Option<String>,
adv_refs: Vec<(String, ObjectId)>,
caps: Vec<String>,
head_symref: Option<String>,
protocol_version: u8,
object_format: String,
sent_state: Vec<u8>,
pending: Vec<u8>,
response: Option<Box<dyn Read + Send>>,
fail_read: Option<ErrRead>,
empty_response: Cursor<Vec<u8>>,
recv_body: Vec<u8>,
recv_posted: bool,
}
impl<C: HttpClient> StatelessHttpConnection<C> {
#[allow(clippy::too_many_arguments)]
pub fn new(
client: C,
service: Service,
repo_url: String,
protocol_version: u8,
adv_refs: Vec<(String, ObjectId)>,
caps: Vec<String>,
head_symref: Option<String>,
object_format: String,
git_protocol: Option<String>,
) -> Self {
let wire = service.wire_name();
let base = repo_url.trim_end_matches('/');
let post_url = format!("{base}/{wire}");
let content_type = format!("application/x-{wire}-request");
let accept = format!("application/x-{wire}-result");
Self {
client,
service,
repo_url,
post_url,
content_type,
accept,
git_protocol,
adv_refs,
caps,
head_symref,
protocol_version,
object_format,
sent_state: Vec::new(),
pending: Vec::new(),
response: None,
fail_read: None,
empty_response: Cursor::new(Vec::new()),
recv_body: Vec::new(),
recv_posted: false,
}
}
#[must_use]
pub fn repo_url(&self) -> &str {
&self.repo_url
}
#[must_use]
pub fn object_format(&self) -> &str {
&self.object_format
}
fn drain_response(&mut self) {
if let Some(mut r) = self.response.take() {
let _ = crate::pack_receive::copy_reader_to_writer(&mut r, &mut io::sink());
}
}
fn set_transport_err(&mut self, err: &Error) {
self.fail_read = Some(ErrRead {
err: io::Error::other(err.to_string()),
});
self.response = None;
}
fn upload_pack_post_due(&self) -> bool {
if self.pending.is_empty() {
return false;
}
self.pending.ends_with(b"0000") || self.pending.ends_with(b"done\n")
}
fn maybe_post_on_flush(&mut self) -> Result<()> {
if self.service != Service::UploadPack {
return Ok(());
}
if self.upload_pack_post_due() {
self.post_upload_pack_round()?;
}
Ok(())
}
fn post_upload_pack_round(&mut self) -> Result<()> {
if self.pending.is_empty() {
return Ok(());
}
let body = if self.protocol_version >= 2 {
std::mem::take(&mut self.pending)
} else {
let mut body = self.sent_state.clone();
body.extend_from_slice(&self.pending);
self.sent_state.extend_from_slice(&self.pending);
self.pending.clear();
body
};
self.response = Some(self.request(&body)?);
Ok(())
}
fn post_receive_pack(&mut self) -> Result<()> {
if self.recv_posted {
return Ok(());
}
self.response = Some(
self.client
.post_into_reader(
&self.post_url,
&self.content_type,
&self.accept,
&self.recv_body,
None,
)
.inspect_err(|e| {
self.set_transport_err(e);
})?,
);
self.recv_posted = true;
Ok(())
}
fn ensure_response_for_read(&mut self) -> Result<()> {
if self.service == Service::ReceivePack && !self.recv_posted {
self.post_receive_pack()?;
}
Ok(())
}
}
impl<C: HttpClient> RpcChannel for StatelessHttpConnection<C> {
fn request(&mut self, body: &[u8]) -> Result<Box<dyn Read + Send>> {
self.drain_response();
self.client
.post_into_reader(
&self.post_url,
&self.content_type,
&self.accept,
body,
self.git_protocol.as_deref(),
)
.inspect_err(|e| {
self.set_transport_err(e);
})
}
}
impl<C: HttpClient> Write for StatelessHttpConnection<C> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
match self.service {
Service::UploadPack => {
self.pending.extend_from_slice(buf);
self.maybe_post_on_flush()
.map_err(|e| io::Error::other(e.to_string()))?;
}
Service::ReceivePack => self.recv_body.extend_from_slice(buf),
}
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
if self.service == Service::UploadPack {
self.maybe_post_on_flush()
.map_err(|e| io::Error::other(e.to_string()))?;
}
Ok(())
}
}
impl<C: HttpClient> Connection for StatelessHttpConnection<C> {
fn reader(&mut self) -> &mut dyn Read {
if self.fail_read.is_none() {
if let Err(e) = self.ensure_response_for_read() {
self.fail_read = Some(ErrRead {
err: io::Error::other(e.to_string()),
});
}
}
if let Some(r) = self.fail_read.as_mut() {
return r;
}
match self.response.as_mut() {
Some(r) => r.as_mut(),
None => &mut self.empty_response,
}
}
fn writer(&mut self) -> &mut dyn Write {
self
}
fn advertised_refs(&self) -> &[(String, ObjectId)] {
&self.adv_refs
}
fn capabilities(&self) -> &[String] {
&self.caps
}
fn head_symref(&self) -> Option<&str> {
self.head_symref.as_deref()
}
fn protocol_version(&self) -> u8 {
self.protocol_version
}
fn stateless_rpc(&self) -> bool {
true
}
fn finish_send(&mut self) {
if self.service == Service::ReceivePack && !self.recv_posted {
if let Err(e) = self.post_receive_pack() {
self.set_transport_err(&e);
}
}
}
}