git_zero_rustix/network/
client.rs1use super::{SocketWriter, content_length, header_end, receive, socket, timeouts};
3use core::{fmt::Write as _, net::SocketAddrV4};
4use git_zero::{
5 Error, Result,
6 client::Transport,
7 http::Request,
8 io::{SliceWriter, Write},
9};
10use rustix::{fd::AsFd, net};
11
12pub struct HttpTransport<'a> {
15 pub address: SocketAddrV4,
16 pub repository_path: &'a str,
17 pub wire_storage: &'a mut [u8],
18}
19
20impl Transport for HttpTransport<'_> {
21 fn request(&mut self, request: Request<'_>, response: &mut [u8]) -> Result<(u16, usize)> {
22 let socket = socket(net::AddressFamily::INET)?;
23 timeouts(&socket)?;
24 net::connect(&socket, &self.address).map_err(|_| Error::Backend)?;
25 self.send_request(&socket, request)?;
26 self.read_response(&socket, response)
27 }
28}
29
30impl HttpTransport<'_> {
31 fn send_request(&mut self, socket: &impl AsFd, request: Request<'_>) -> Result<()> {
32 let mut header = SliceWriter::new(self.wire_storage);
33 write!(
34 &mut header,
35 "{} {}/{}",
36 request.method,
37 self.repository_path.trim_end_matches('/'),
38 request.path.trim_start_matches('/')
39 )
40 .map_err(|_| Error::BufferTooSmall)?;
41 if let Some(query) = request.query {
42 write!(&mut header, "?{query}").map_err(|_| Error::BufferTooSmall)?;
43 }
44 write!(
45 &mut header,
46 " HTTP/1.0\r\nHost: {}\r\nContent-Length: {}\r\n",
47 self.address,
48 request.body.len()
49 )
50 .map_err(|_| Error::BufferTooSmall)?;
51 for (name, value) in [
52 ("Content-Type", request.content_type),
53 ("Git-Protocol", request.git_protocol),
54 ] {
55 if let Some(value) = value {
56 if value.bytes().any(|b| matches!(b, 0 | b'\r' | b'\n')) {
57 return Err(Error::InvalidRequest);
58 }
59 write!(&mut header, "{name}: {value}\r\n").map_err(|_| Error::BufferTooSmall)?;
60 }
61 }
62 header.write_all(b"Connection: close\r\n\r\n")?;
63 let mut output = SocketWriter(socket.as_fd());
64 output.write_all(header.as_bytes())?;
65 output.write_all(request.body)?;
66 Ok(())
67 }
68
69 fn read_response(&mut self, socket: &impl AsFd, response: &mut [u8]) -> Result<(u16, usize)> {
70 let length = read_to_end(socket, self.wire_storage)?;
71 let end = header_end(&self.wire_storage[..length]).ok_or(Error::InvalidRequest)?;
72 let header =
73 core::str::from_utf8(&self.wire_storage[..end]).map_err(|_| Error::InvalidRequest)?;
74 let status = header
75 .lines()
76 .next()
77 .and_then(|line| line.split_ascii_whitespace().nth(1))
78 .ok_or(Error::InvalidRequest)?
79 .parse()
80 .map_err(|_| Error::InvalidRequest)?;
81 let body = &self.wire_storage[end..length];
82 if content_length(header)?.is_some_and(|expected| expected != body.len()) {
83 return Err(Error::InvalidRequest);
84 }
85 response
86 .get_mut(..body.len())
87 .ok_or(Error::BufferTooSmall)?
88 .copy_from_slice(body);
89 Ok((status, body.len()))
90 }
91}
92
93fn read_to_end(socket: &impl AsFd, storage: &mut [u8]) -> Result<usize> {
95 let mut length = 0;
96 loop {
97 if length == storage.len() {
98 let mut extra = [0; 1];
99 if receive(socket, &mut extra)? != 0 {
100 return Err(Error::BufferTooSmall);
101 }
102 break;
103 }
104 let count = receive(socket, &mut storage[length..])?;
105 if count == 0 {
106 break;
107 }
108 length += count;
109 }
110 Ok(length)
111}