1use super::{SOCKET_FLAGS, SocketWriter, apply_timeouts, close_on_exec, receive, socket};
3use core::{
4 fmt::Write as _,
5 net::{Ipv4Addr, SocketAddr, SocketAddrV4},
6 time::Duration,
7};
8use git_zero::{
9 Error, Result,
10 http::{Request, Response},
11 io::{SliceWriter, Write},
12};
13use rustix::{
14 fd::{AsFd, OwnedFd},
15 net::{self, sockopt},
16};
17
18mod chunked;
19pub use chunked::ChunkedLimits;
20mod deadline;
21use deadline::{DeadlineReader, DeadlineWriter};
22pub use deadline::{RequestDeadlines, ResponseDeadlines};
23mod header;
24use header::{Headers, parse_request, read_header, request_line};
25
26#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
29pub struct RequestLimits {
30 pub max_wire_bytes: Option<usize>,
32 pub max_body_bytes: Option<usize>,
34}
35
36#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
40pub struct HeaderLimits {
41 pub max_bytes: Option<usize>,
43 pub max_fields: Option<usize>,
45 pub max_name_bytes: Option<usize>,
46 pub max_value_bytes: Option<usize>,
47}
48
49#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
52pub struct RequestPolicy {
53 pub request: RequestLimits,
54 pub headers: HeaderLimits,
55}
56
57#[derive(Clone, Copy, Debug, Eq, PartialEq)]
61pub struct AcceptedSocketTimeouts {
62 pub read: Option<Duration>,
63 pub write: Option<Duration>,
64}
65
66impl Default for AcceptedSocketTimeouts {
67 fn default() -> Self {
68 Self {
69 read: Some(Duration::from_secs(10)),
70 write: Some(Duration::from_secs(10)),
71 }
72 }
73}
74
75pub fn listen(port: u16) -> Result<OwnedFd> {
76 listen_on(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, port)))
77}
78
79pub fn listen_on(address: SocketAddr) -> Result<OwnedFd> {
83 let family = if address.is_ipv4() {
84 net::AddressFamily::INET
85 } else {
86 net::AddressFamily::INET6
87 };
88 let socket = socket(family)?;
89 #[cfg(not(target_os = "openbsd"))]
91 if address.is_ipv6() {
92 sockopt::set_ipv6_v6only(&socket, true).map_err(|_| Error::Backend)?;
93 }
94 sockopt::set_socket_reuseaddr(&socket, true).map_err(|_| Error::Backend)?;
95 net::bind(&socket, &address).map_err(|_| Error::Backend)?;
96 net::listen(&socket, 128).map_err(|_| Error::Backend)?;
97 Ok(socket)
98}
99
100pub fn local_port(socket: &impl AsFd) -> Result<u16> {
101 Ok(local_address(socket)?.port())
102}
103
104pub fn local_address(socket: &impl AsFd) -> Result<SocketAddr> {
106 let address = net::getsockname(socket).map_err(|_| Error::Backend)?;
107 SocketAddr::try_from(address).map_err(|_| Error::Backend)
108}
109
110pub fn accept(socket: &impl AsFd) -> Result<OwnedFd> {
111 accept_with_timeouts(socket, AcceptedSocketTimeouts::default())
112}
113
114pub fn accept_with_timeouts(
118 socket: &impl AsFd,
119 timeouts: AcceptedSocketTimeouts,
120) -> Result<OwnedFd> {
121 if [timeouts.read, timeouts.write]
122 .into_iter()
123 .flatten()
124 .any(|value| value.is_zero())
125 {
126 return Err(Error::InvalidRequest);
127 }
128 accept_configured(socket, |client| {
129 apply_timeouts(client, timeouts.read, timeouts.write)
130 })
131}
132
133fn accept_configured(
135 socket: &impl AsFd,
136 configure: impl FnOnce(&OwnedFd) -> Result<()>,
137) -> Result<OwnedFd> {
138 let client =
139 close_on_exec(net::accept_with(socket, SOCKET_FLAGS).map_err(|_| Error::Backend)?)?;
140 configure(&client)?;
141 Ok(client)
142}
143
144pub fn read_request<'a>(socket: &impl AsFd, storage: &'a mut [u8]) -> Result<Request<'a>> {
148 read_request_with_limits(socket, storage, RequestLimits::default())
149}
150
151pub fn read_request_with_limits<'a>(
157 socket: &impl AsFd,
158 storage: &'a mut [u8],
159 limits: RequestLimits,
160) -> Result<Request<'a>> {
161 read_request_with_policy(
162 socket,
163 storage,
164 RequestPolicy {
165 request: limits,
166 headers: HeaderLimits::default(),
167 },
168 )
169}
170
171pub fn read_request_with_policy<'a>(
176 socket: &impl AsFd,
177 storage: &'a mut [u8],
178 policy: RequestPolicy,
179) -> Result<Request<'a>> {
180 read_request_from(&mut |bytes| receive(socket, bytes), storage, policy)
181}
182
183pub fn read_request_with_deadlines<'a>(
192 socket: &impl AsFd,
193 storage: &'a mut [u8],
194 policy: RequestPolicy,
195 deadlines: RequestDeadlines,
196) -> Result<Request<'a>> {
197 read_socket_request(socket, storage, policy, deadlines, None)
198}
199
200pub fn read_request_from<'a>(
206 receive: &mut impl FnMut(&mut [u8]) -> Result<usize>,
207 storage: &'a mut [u8],
208 policy: RequestPolicy,
209) -> Result<Request<'a>> {
210 read_request_framed(receive, storage, policy, None)
211}
212
213pub fn read_request_from_chunked<'a>(
218 receive: &mut impl FnMut(&mut [u8]) -> Result<usize>,
219 storage: &'a mut [u8],
220 policy: RequestPolicy,
221 chunks: ChunkedLimits,
222) -> Result<Request<'a>> {
223 read_request_framed(receive, storage, policy, Some(chunks))
224}
225
226pub fn read_request_chunked<'a>(
228 socket: &impl AsFd,
229 storage: &'a mut [u8],
230 policy: RequestPolicy,
231 deadlines: RequestDeadlines,
232 chunks: ChunkedLimits,
233) -> Result<Request<'a>> {
234 read_socket_request(socket, storage, policy, deadlines, Some(chunks))
235}
236
237fn read_socket_request<'a>(
238 socket: &impl AsFd,
239 storage: &'a mut [u8],
240 policy: RequestPolicy,
241 deadlines: RequestDeadlines,
242 chunks: Option<ChunkedLimits>,
243) -> Result<Request<'a>> {
244 if deadlines == RequestDeadlines::default() {
245 return read_request_framed(&mut |bytes| receive(socket, bytes), storage, policy, chunks);
246 }
247 let mut reader = DeadlineReader::new(socket, deadlines)?;
248 let result = read_request_framed(&mut |bytes| reader.receive(bytes), storage, policy, chunks);
249 reader.check()?;
250 result
251}
252
253fn read_request_framed<'a>(
254 receive: &mut impl FnMut(&mut [u8]) -> Result<usize>,
255 storage: &'a mut [u8],
256 policy: RequestPolicy,
257 chunks: Option<ChunkedLimits>,
258) -> Result<Request<'a>> {
259 let limits = policy.request;
260 let (end, length) = read_header(
261 receive,
262 storage,
263 limits.max_wire_bytes,
264 policy.headers.max_bytes,
265 )?;
266 let (header, storage) = storage.split_at_mut(end);
267 let length = length - end;
268 let header = core::str::from_utf8(header).map_err(|_| Error::InvalidRequest)?;
269 let line = request_line(header)?;
270 let headers = if chunks.is_some() {
271 Headers::parse_framed(header, policy.headers, true)?
272 } else {
273 Headers::parse(header, policy.headers)?
274 };
275 let mut request = parse_request(line, &headers)?;
276 let identity = headers
277 .content_encoding
278 .is_none_or(|value| value.eq_ignore_ascii_case("identity"));
279 let (wire_length, body_length) = if headers.chunked {
280 if !line.ends_with(" HTTP/1.1") {
281 return Err(Error::InvalidRequest);
282 }
283 chunked::read(
284 receive,
285 storage,
286 length,
287 limits.max_wire_bytes.map(|limit| limit - end),
288 if identity {
289 limits.max_body_bytes
290 } else {
291 None
292 },
293 chunks.unwrap(),
294 )?
295 } else {
296 let total = headers
297 .content_length
298 .map(super::parse_length)
299 .transpose()?
300 .unwrap_or(0);
301 let total = read_fixed_body(
302 receive,
303 storage,
304 length,
305 total,
306 RequestLimits {
307 max_wire_bytes: limits.max_wire_bytes.map(|limit| limit - end),
308 max_body_bytes: if identity {
309 limits.max_body_bytes
310 } else {
311 None
312 },
313 },
314 )?;
315 (total, total)
316 };
317 let (wire, expanded) = storage.split_at_mut(wire_length);
318 request.body = decode_body(
319 &wire[..body_length],
320 expanded,
321 headers.content_encoding,
322 limits.max_body_bytes,
323 )?;
324 Ok(request)
325}
326
327fn read_fixed_body(
328 receive: &mut impl FnMut(&mut [u8]) -> Result<usize>,
329 storage: &mut [u8],
330 mut length: usize,
331 total: usize,
332 limits: RequestLimits,
333) -> Result<usize> {
334 if limits.max_wire_bytes.is_some_and(|limit| total > limit)
335 || limits.max_body_bytes.is_some_and(|limit| total > limit)
336 {
337 return Err(Error::LimitExceeded);
338 }
339 if total > storage.len() {
340 return Err(Error::BufferTooSmall);
341 }
342 while length < total {
343 let count = receive(&mut storage[length..total])?;
344 if count == 0 {
345 return Err(Error::InvalidRequest);
346 }
347 if count > total - length {
348 return Err(Error::Backend);
349 }
350 length += count;
351 }
352 Ok(total)
353}
354
355fn decode_body<'a>(
356 body: &'a [u8],
357 expanded: &'a mut [u8],
358 encoding: Option<&str>,
359 max_body_bytes: Option<usize>,
360) -> Result<&'a [u8]> {
361 match encoding {
362 None => Ok(body),
363 Some(value) if value.eq_ignore_ascii_case("identity") => Ok(body),
364 Some(value) if value.eq_ignore_ascii_case("gzip") => {
365 use git_zero::zlib::{InflateLimits, WINDOW_SIZE, decode_gzip};
366 let length = decode_gzip(
367 body,
368 expanded,
369 &mut [0; WINDOW_SIZE],
370 InflateLimits {
371 max_output: max_body_bytes
372 .map_or(expanded.len(), |limit| limit.min(expanded.len()))
373 as u64,
374 max_ratio: 128,
375 ratio_slack: expanded.len() as u64,
376 },
377 )
378 .map_err(|error| {
379 if error == Error::BufferTooSmall
382 && max_body_bytes.is_some_and(|limit| limit <= expanded.len())
383 {
384 Error::LimitExceeded
385 } else {
386 error
387 }
388 })?;
389 Ok(&expanded[..length])
390 }
391 _ => Err(Error::Unsupported),
392 }
393}
394
395pub fn respond(socket: &impl AsFd, response: Response, body: &[u8]) -> Result<()> {
396 respond_headers(socket, response, body.len() as u64)?;
397 SocketWriter(socket.as_fd()).write_all(body)
398}
399
400pub fn respond_stream(
404 socket: &impl AsFd,
405 repository: &impl git_zero::Repository,
406 encoder: &mut git_zero::http::stream::ResponseEncoder<'_>,
407 staging: &mut [u8],
408) -> Result<()> {
409 respond_stream_using(
410 &mut SocketWriter(socket.as_fd()),
411 repository,
412 encoder,
413 staging,
414 )
415}
416
417pub fn respond_stream_with_deadlines(
422 socket: &impl AsFd,
423 repository: &impl git_zero::Repository,
424 encoder: &mut git_zero::http::stream::ResponseEncoder<'_>,
425 staging: &mut [u8],
426 deadlines: ResponseDeadlines,
427) -> Result<()> {
428 if deadlines == ResponseDeadlines::default() {
429 return respond_stream(socket, repository, encoder, staging);
430 }
431 let mut writer = match DeadlineWriter::new(socket, deadlines) {
432 Ok(writer) => writer,
433 Err(error) => {
434 encoder.cancel();
435 return Err(error);
436 }
437 };
438 respond_stream_using(&mut writer, repository, encoder, staging)
439}
440
441fn respond_stream_using(
442 writer: &mut impl Write,
443 repository: &impl git_zero::Repository,
444 encoder: &mut git_zero::http::stream::ResponseEncoder<'_>,
445 staging: &mut [u8],
446) -> Result<()> {
447 let result = respond_body(writer, encoder.response(), None, staging, |bytes| {
448 encoder.read(repository, bytes)
449 });
450 if result.is_err() {
451 encoder.cancel();
452 }
453 result
454}
455
456fn respond_body(
457 writer: &mut impl Write,
458 response: Response,
459 mut remaining: Option<u64>,
460 staging: &mut [u8],
461 mut read: impl FnMut(&mut [u8]) -> Result<usize>,
462) -> Result<()> {
463 if staging.is_empty() {
464 return Err(Error::BufferTooSmall);
465 }
466 let mut storage = [0; 512];
467 let mut header = SliceWriter::new(&mut storage);
468 write!(
469 &mut header,
470 "HTTP/1.0 {} Response\r\nContent-Type: {}\r\n",
471 response.status, response.content_type
472 )
473 .map_err(|_| Error::BufferTooSmall)?;
474 if let Some(size) = remaining {
475 write!(&mut header, "Content-Length: {size}\r\n").map_err(|_| Error::BufferTooSmall)?;
476 }
477 header.write_all(b"Cache-Control: no-store\r\nConnection: close\r\n\r\n")?;
478 writer.write_all(header.as_bytes())?;
479 loop {
480 writer.write_all(&[])?; let count = read(staging)?;
482 if let Some(left) = &mut remaining {
483 *left = left.checked_sub(count as u64).ok_or(Error::Backend)?;
484 }
485 writer.write_all(&staging[..count])?; if count == 0 {
487 return if remaining.unwrap_or(0) == 0 {
488 Ok(())
489 } else {
490 Err(Error::Backend)
491 };
492 }
493 }
494}
495
496pub fn respond_published<R: git_zero::Repository>(
500 socket: &impl AsFd,
501 encoder: &mut git_zero::http::dumb::PublishedResponse<'_, R>,
502 staging: &mut [u8],
503) -> Result<()> {
504 let result = respond_known(
505 socket,
506 encoder.response(),
507 encoder.size(),
508 staging,
509 |bytes| encoder.read(bytes),
510 );
511 if result.is_err() {
512 encoder.cancel();
513 }
514 result
515}
516
517pub fn respond_published_with_deadlines<R: git_zero::Repository>(
520 socket: &impl AsFd,
521 encoder: &mut git_zero::http::dumb::PublishedResponse<'_, R>,
522 staging: &mut [u8],
523 deadlines: ResponseDeadlines,
524) -> Result<()> {
525 if deadlines == ResponseDeadlines::default() {
526 return respond_published(socket, encoder, staging);
527 }
528 let result = DeadlineWriter::new(socket, deadlines).and_then(|mut writer| {
529 respond_body(
530 &mut writer,
531 encoder.response(),
532 Some(encoder.size()),
533 staging,
534 |bytes| encoder.read(bytes),
535 )
536 });
537 if result.is_err() {
538 encoder.cancel();
539 }
540 result
541}
542
543#[cfg(feature = "storage")]
546pub fn respond_file_published<R: git_zero::Repository>(
547 socket: &impl AsFd,
548 encoder: &mut crate::FilePublishedResponse<'_, R>,
549 staging: &mut [u8],
550) -> Result<()> {
551 let result = respond_known(
552 socket,
553 encoder.response(),
554 encoder.size(),
555 staging,
556 |bytes| encoder.read(bytes),
557 );
558 if result.is_err() {
559 encoder.cancel();
560 }
561 result
562}
563
564fn respond_known(
565 socket: &impl AsFd,
566 response: Response,
567 size: u64,
568 staging: &mut [u8],
569 read: impl FnMut(&mut [u8]) -> Result<usize>,
570) -> Result<()> {
571 respond_body(
572 &mut SocketWriter(socket.as_fd()),
573 response,
574 Some(size),
575 staging,
576 read,
577 )
578}
579
580pub fn respond_headers(socket: &impl AsFd, response: Response, body_length: u64) -> Result<()> {
583 let mut storage = [0; 512];
584 let mut header = SliceWriter::new(&mut storage);
585 write!(&mut header, "HTTP/1.0 {} Response\r\nContent-Type: {}\r\nContent-Length: {}\r\nCache-Control: no-store\r\nConnection: close\r\n\r\n", response.status, response.content_type, body_length).map_err(|_| Error::BufferTooSmall)?;
586 SocketWriter(socket.as_fd()).write_all(header.as_bytes())
587}
588
589#[cfg(test)]
590mod tests {
591 extern crate std;
592 use super::*;
593
594 #[test]
595 fn explicit_ip_listeners_report_bound_addresses_and_isolate_ipv6() {
596 for value in ["127.0.0.1:0", "[::1]:0"] {
597 let address: SocketAddr = value.parse().unwrap();
598 let listener = listen_on(address).unwrap();
599 let bound = local_address(&listener).unwrap();
600 assert_eq!(bound.ip(), address.ip());
601 assert_ne!(bound.port(), 0);
602 assert_eq!(local_port(&listener).unwrap(), bound.port());
603 assert!(
604 rustix::io::fcntl_getfd(&listener)
605 .unwrap()
606 .contains(rustix::io::FdFlags::CLOEXEC)
607 );
608 let family = if address.is_ipv4() {
609 net::AddressFamily::INET
610 } else {
611 assert!(sockopt::ipv6_v6only(&listener).unwrap());
612 net::AddressFamily::INET6
613 };
614 let client = socket(family).unwrap();
615 net::connect(&client, &bound).unwrap();
616 let accepted = accept(&listener).unwrap();
617 assert!(
618 rustix::io::fcntl_getfd(&accepted)
619 .unwrap()
620 .contains(rustix::io::FdFlags::CLOEXEC)
621 );
622 for timeout in [sockopt::Timeout::Recv, sockopt::Timeout::Send] {
623 assert_eq!(
624 sockopt::socket_timeout(&accepted, timeout).unwrap(),
625 Some(core::time::Duration::from_secs(10))
626 );
627 }
628 }
629 }
630
631 #[test]
632 fn disconnected_stream_cancels_before_any_more_output() {
633 let (socket, peer) = net::socketpair(
634 net::AddressFamily::UNIX,
635 net::SocketType::STREAM,
636 SOCKET_FLAGS,
637 None,
638 )
639 .unwrap();
640 let repository = git_zero::EmptyRepository::new("refs/heads/main").unwrap();
641 let request = Request {
642 method: "GET",
643 path: "HEAD",
644 query: None,
645 content_type: None,
646 git_protocol: None,
647 body: &[],
648 };
649 let mut control = [0; 128];
650 let mut encoder =
651 git_zero::http::stream::ResponseEncoder::new(&repository, request, &mut control)
652 .unwrap();
653 drop(peer);
654 assert_eq!(
655 respond_stream(&socket, &repository, &mut encoder, &mut [0; 3]),
656 Err(Error::Backend)
657 );
658 assert_eq!(
659 encoder.read(&repository, &mut [0; 3]),
660 Err(Error::InvalidRequest)
661 );
662 }
663
664 #[test]
665 fn failed_accepted_configuration_closes_the_child() {
666 use std::io::Read;
667 let listener = listen(0).unwrap();
668 let mut peer = std::net::TcpStream::connect(local_address(&listener).unwrap()).unwrap();
669 peer.set_read_timeout(Some(Duration::from_secs(2))).unwrap();
670 assert!(matches!(
671 accept_configured(&listener, |_| Err(Error::Backend)),
672 Err(Error::Backend)
673 ));
674 assert_eq!(peer.read(&mut [0; 1]).unwrap(), 0);
675 let _peer = std::net::TcpStream::connect(local_address(&listener).unwrap()).unwrap();
677 assert!(accept(&listener).is_ok());
678 }
679}