1use std::io;
4use std::net::SocketAddr;
5use std::time::Duration;
6use tokio::io::{AsyncReadExt, AsyncWriteExt};
7use tokio::net::TcpStream;
8use tokio::time::timeout;
9
10pub const MAX_RESPONSE: usize = 64 * 1024 * 1024;
11
12pub async fn post(addr: SocketAddr, path: &str, bearer: Option<&str>, body: &[u8], deadline: Duration) -> io::Result<(u16, Vec<u8>)> {
14 let io = async {
15 let mut stream = TcpStream::connect(addr).await?;
16 let auth = bearer.map(|t| format!("Authorization: Bearer {t}\r\n")).unwrap_or_default();
17 let head = format!(
18 "POST {path} HTTP/1.1\r\nHost: {addr}\r\n{auth}Content-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n",
19 body.len()
20 );
21 stream.write_all(head.as_bytes()).await?;
22 stream.write_all(body).await?;
23 let mut raw = Vec::new();
24 (&mut stream).take(MAX_RESPONSE as u64).read_to_end(&mut raw).await?;
25 Ok::<_, io::Error>(raw)
26 };
27 let raw = timeout(deadline, io).await.map_err(|_| io::Error::new(io::ErrorKind::TimedOut, "request timed out"))??;
28 let split = raw.windows(4).position(|w| w == b"\r\n\r\n").ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "no header end"))?;
29 let status = std::str::from_utf8(&raw[..split])
30 .ok()
31 .and_then(|h| h.split_whitespace().nth(1))
32 .and_then(|s| s.parse().ok())
33 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "bad status line"))?;
34 Ok((status, raw[split + 4..].to_vec()))
35}
36
37pub async fn read_request(stream: &mut TcpStream, max_body: usize) -> io::Result<(String, String, Option<String>, Vec<u8>)> {
39 let mut bytes = Vec::with_capacity(2048);
40 let mut chunk = [0_u8; 2048];
41 let header_end = loop {
42 let n = stream.read(&mut chunk).await?;
43 if n == 0 {
44 return Err(io::ErrorKind::UnexpectedEof.into());
45 }
46 bytes.extend_from_slice(&chunk[..n]);
47 if bytes.len() > max_body + 8192 {
48 return Err(io::Error::new(io::ErrorKind::InvalidData, "too large"));
49 }
50 if let Some(pos) = bytes.windows(4).position(|w| w == b"\r\n\r\n") {
51 break pos + 4;
52 }
53 };
54 let head = std::str::from_utf8(&bytes[..header_end]).map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "utf8"))?;
55 let mut lines = head.split("\r\n");
56 let mut first = lines.next().unwrap_or_default().split_whitespace();
57 let method = first.next().unwrap_or_default().to_owned();
58 let path = first.next().unwrap_or_default().to_owned();
59 let (mut length, mut bearer) = (0_usize, None);
60 for line in lines {
61 if let Some((name, value)) = line.split_once(':') {
62 if name.eq_ignore_ascii_case("content-length") {
63 length = value.trim().parse().map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "length"))?;
64 } else if name.eq_ignore_ascii_case("authorization") {
65 bearer = value.trim().strip_prefix("Bearer ").map(str::to_owned);
66 }
67 }
68 }
69 if length > max_body {
70 return Err(io::Error::new(io::ErrorKind::InvalidData, "body too large"));
71 }
72 let mut body = bytes[header_end..].to_vec();
73 while body.len() < length {
74 let n = stream.read(&mut chunk).await?;
75 if n == 0 {
76 return Err(io::ErrorKind::UnexpectedEof.into());
77 }
78 body.extend_from_slice(&chunk[..n]);
79 }
80 body.truncate(length);
81 Ok((method, path, bearer, body))
82}
83
84pub async fn respond(stream: &mut TcpStream, status: u16, body: &[u8]) -> io::Result<()> {
85 let reason = match status {
86 200 => "OK",
87 400 => "Bad Request",
88 401 => "Unauthorized",
89 403 => "Forbidden",
90 404 => "Not Found",
91 422 => "Unprocessable Entity",
92 429 => "Too Many Requests",
93 502 => "Bad Gateway",
94 504 => "Gateway Timeout",
95 _ => "Internal Server Error",
96 };
97 let head = format!(
98 "HTTP/1.1 {status} {reason}\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n",
99 body.len()
100 );
101 stream.write_all(head.as_bytes()).await?;
102 stream.write_all(body).await?;
103 stream.shutdown().await
104}
105
106pub fn secret_eq(a: &str, b: &str) -> bool {
108 let (a, b) = (a.as_bytes(), b.as_bytes());
109 let mut diff = (a.len() ^ b.len()) as u8;
110 for i in 0..a.len().max(b.len()) {
111 diff |= a.get(i).copied().unwrap_or(0) ^ b.get(i).copied().unwrap_or(0);
112 }
113 diff == 0
114}