httplz/src/lib.rs
2024-10-14 22:19:45 -04:00

289 lines
6.6 KiB
Rust

#![no_std]
pub mod common;
pub mod parse;
pub mod parts;
pub mod util;
pub mod write;
pub use common::*;
pub use parse::Parse;
pub use parts::*;
pub use util::*;
pub use write::{Write, WriteCursor, Written};
#[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum Event<'a> {
#[default]
Empty,
RequestLine(RequestLine<'a>),
Header(Header<'a>),
HeadersDone,
BodyChunk(&'a [u8]),
RecvDone,
StatusLine(StatusLine<'a>),
SendDone,
}
impl<'a> core::fmt::Display for Event<'a> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Event::Empty | Event::HeadersDone | Event::RecvDone | Event::SendDone => Ok(()),
Event::RequestLine(r) => write!(f, "{} {} {}", r.method, r.target, r.version),
Event::StatusLine(s) => {
write!(f, "{} {} {}", s.version, s.status_code, s.status_text)
},
Event::Header(h) => match h {
Header::Other(HeaderOther { name, value }) => {
write!(
f,
"{}: {}",
name,
core::str::from_utf8(value).unwrap_or("<binary>")
)
},
Header::Special(h) => match h {
HeaderSpecial::ContentLength(cl) => write!(f, "Content-Length: {cl}"),
HeaderSpecial::TransferEncodingChunked => {
write!(f, "Transfer-Encoding: chunked")
},
},
},
Event::BodyChunk(b) => {
write!(f, "{}", core::str::from_utf8(b).unwrap_or("<binary>"))
},
}
}
}
#[derive(Debug, Copy, Clone)]
pub enum Role {
Client,
Server,
}
#[derive(Debug, Copy, Clone)]
enum BodyState {
ContentLength(usize),
Chunked,
}
#[derive(Debug, Copy, Clone)]
enum StateRecv {
StartLine,
Headers(Option<BodyState>),
Body(BodyState),
ValidateDone,
}
#[derive(Debug, Copy, Clone)]
enum StateSend {
StartLine,
Headers(Option<BodyState>),
Body(BodyState),
ValidateDone,
}
#[derive(Debug, Copy, Clone)]
enum StateConnection {
Recv(StateRecv),
Send(StateSend),
}
#[derive(Debug, Copy, Clone)]
pub struct Connection {
role: Role,
state: StateConnection,
}
impl Connection {
pub fn new(role: Role) -> Self {
let state = match role {
Role::Server => StateConnection::Recv(StateRecv::StartLine),
Role::Client => StateConnection::Send(StateSend::StartLine),
};
Self { state, role }
}
pub fn is_sending(&self) -> bool {
matches!(self.state, StateConnection::Send(_))
}
pub fn is_recving(&self) -> bool {
matches!(self.state, StateConnection::Recv(_))
}
pub fn handle_recv<'a>(&mut self, bytes: &'a [u8]) -> Parse<'a, Event<'a>> {
let recv = match &self.state {
StateConnection::Recv(r) => r,
_ => {
return Err((bytes, ErrorKind::InvalidConnectionState.into()));
},
};
let n = match (self.role, recv) {
(Role::Server, StateRecv::StartLine) => parse::request_line(bytes).map2(|rl| {
(
Event::RequestLine(rl),
StateConnection::Recv(StateRecv::Headers(None)),
)
}),
(Role::Client, StateRecv::StartLine) => parse::status_line(bytes).map2(|rl| {
(
Event::StatusLine(rl),
StateConnection::Recv(StateRecv::Headers(None)),
)
}),
(_, StateRecv::Headers(body_state)) => {
if bytes.starts_with(b"\r\n") {
Ok((
&bytes[2..],
(
Event::HeadersDone,
match body_state {
None => StateConnection::Recv(StateRecv::ValidateDone),
Some(b) => StateConnection::Recv(StateRecv::Body(*b)),
},
),
))
} else {
parse::header(bytes).map2(|h| {
let b = match h {
Header::Special(HeaderSpecial::TransferEncodingChunked) => {
Some(BodyState::Chunked)
},
Header::Special(HeaderSpecial::ContentLength(c)) => {
Some(BodyState::ContentLength(c))
},
_ => *body_state,
};
(
Event::Header(h),
StateConnection::Recv(StateRecv::Headers(b)),
)
})
}
},
(_, StateRecv::Body(body_state)) => match body_state {
BodyState::ContentLength(remaining) => {
if bytes.len() < *remaining {
Ok((
&[] as &[u8],
(
Event::BodyChunk(bytes),
StateConnection::Recv(StateRecv::Body(
BodyState::ContentLength(remaining - bytes.len()),
)),
),
))
} else {
Ok((
&bytes[*remaining..],
(
Event::BodyChunk(&bytes[..*remaining]),
StateConnection::Recv(StateRecv::ValidateDone),
),
))
}
},
_ => todo!(),
},
(_, StateRecv::ValidateDone) => {
if bytes.is_empty() {
Ok((
bytes,
(Event::RecvDone, StateConnection::Send(StateSend::StartLine)),
))
} else {
fail(ErrorKind::TrailingBytes).tup(bytes)
}
},
};
n.map2(move |(ev, next_state)| {
self.state = next_state;
ev
})
}
pub fn handle_send(&mut self, event: &Event, mut w: impl Write) -> Written {
let state = match self.state {
StateConnection::Send(s) => s,
_ => return fail(ErrorKind::InvalidConnectionState),
};
let next = match (self.role, state, event) {
(Role::Server, StateSend::StartLine, Event::StatusLine(sl)) => {
write::status_line(sl, w)
.map(|_| StateConnection::Send(StateSend::Headers(None)))
},
(Role::Client, StateSend::StartLine, Event::RequestLine(rl)) => {
write::request_line(rl, w)
.map(|_| StateConnection::Send(StateSend::Headers(None)))
},
(_, StateSend::Headers(body_state), Event::Header(h)) => {
write::header(h, w)?;
let bs = match h {
Header::Other(_) => body_state,
Header::Special(h) => match h {
HeaderSpecial::TransferEncodingChunked => Some(BodyState::Chunked),
HeaderSpecial::ContentLength(cl) => {
Some(BodyState::ContentLength(*cl))
},
},
};
Ok(StateConnection::Send(StateSend::Headers(bs)))
},
(_, StateSend::Headers(body_state), Event::HeadersDone) => {
write!(w, "\r\n")?;
match body_state {
Some(bs) => Ok(StateConnection::Send(StateSend::Body(bs))),
None => Ok(StateConnection::Send(StateSend::ValidateDone)),
}
},
(_, StateSend::Body(b), Event::BodyChunk(c)) => match b {
BodyState::ContentLength(cl) => match () {
_ if c.len() < cl => {
w.write(c)?;
Ok(StateConnection::Send(StateSend::Body(
BodyState::ContentLength(cl - c.len()),
)))
},
_ if c.len() == cl => {
w.write(c)?;
Ok(StateConnection::Send(StateSend::ValidateDone))
},
_ => {
return fail(ErrorKind::BodySizeMismatch);
},
},
BodyState::Chunked => todo!(),
},
(_, StateSend::ValidateDone, Event::SendDone) => {
Ok(StateConnection::Recv(StateRecv::StartLine))
},
_ => return Err(Error::from(ErrorKind::InvalidEventForConnectionState)),
}?;
self.state = next;
Ok(())
}
}