use {
alloc::string::String,
crate::{HeaderMap, Method, Version},
};
#[cfg(feature="std")]
use {
alloc::vec::Vec,
core::convert::TryFrom,
std::{
io::{Error, ErrorKind, Read, Write},
},
crate::{
IoResult, LINE_BREAK,
io,
},
};
#[derive(Debug, Clone)]
pub struct Request {
pub version: Version,
pub method: Method,
pub url: String,
pub headers: HeaderMap,
}
impl Request {
#[cfg(feature="std")]
pub fn send<R, W>(&self, data: &mut R, target: &mut W) -> IoResult<u64> where R: Read, W: Write {
let mut buf = format!("{method} {url} {version}{lb}", method=self.method, url=self.url, version=self.version, lb=LINE_BREAK);
self.headers.iter().for_each(|(k, v)| buf += &format!("{k}: {v}{lb}", k=k, v=v, lb=LINE_BREAK));
buf += LINE_BREAK;
target.write_all(buf.as_bytes())?;
let mut result = u64::try_from(buf.len()).map_err(|_| Error::new(ErrorKind::Other, __!("Failed converting `{}` to u64", buf.len())))?;
let copied = std::io::copy(data, target)?;
result = result.checked_add(copied).ok_or_else(|| Error::new(ErrorKind::InvalidData, __!("Overflow: {a} + {b}", a=result, b=copied)))?;
target.flush().map(|()| result)
}
#[cfg(feature="std")]
pub fn recv<R, W>(src: &mut R, data: &mut W) -> IoResult<Self> where R: Read, W: Write {
const BUF_SIZE: usize = 2048;
let mut line_buffer = [0; BUF_SIZE];
let (method, url, version, bytes) = read_start_line(src, &mut line_buffer)?;
let (headers, bytes) = io::read_headers(src, &mut line_buffer, bytes)?;
if let Some(bytes) = bytes {
data.write_all(&bytes)?;
}
std::io::copy(src, data)?;
data.flush()?;
Ok(Self {
version,
method,
url,
headers,
})
}
}
#[cfg(feature="std")]
fn read_start_line<R>(r: &mut R, line_buffer: &mut [u8]) -> IoResult<(Method, String, Version, Option<Vec<u8>>)> where R: Read {
let lf = LINE_BREAK.as_bytes().last();
let mut total_read = 0;
let start_line = loop {
let read = match r.read(&mut line_buffer[total_read..])? {
0 => return Err(Error::new(ErrorKind::UnexpectedEof, __!("Invalid start line"))),
other => other,
};
let last_idx = total_read;
total_read += read;
match line_buffer[last_idx..total_read].iter().position(|b| lf.map(|lf| lf == b).unwrap_or(false)).map(|p| p + last_idx) {
Some(p) => match p > LINE_BREAK.len() && &line_buffer[p - LINE_BREAK.len() + 1 ..= p] == LINE_BREAK.as_bytes() {
true => break &line_buffer[..=p],
false => return Err(Error::new(ErrorKind::InvalidData, __!("Start line: invalid line break"))),
},
None => if total_read >= line_buffer.len() {
return Err(Error::new(ErrorKind::InvalidData, __!("Start line too long")));
},
};
};
let start_line_len = start_line.len();
let mut parts = start_line[..start_line_len - LINE_BREAK.len()].split(|b| b == &b' ');
match (parts.next(), parts.next(), parts.next(), parts.next()) {
(Some(method), Some(url), Some(version), None) => Ok((
Method::try_from(method)?,
String::from_utf8(url.to_vec()).map_err(|_| Error::new(ErrorKind::InvalidData, __!("Start line: invalid URL")))?,
Version::try_from(version)?,
match total_read > start_line_len {
true => Some(line_buffer[start_line_len..total_read].to_vec()),
false => None,
},
)),
_ => Err(Error::new(ErrorKind::InvalidData, __!("Invalid start line"))),
}
}