httplz 0.0.0

A sans-io, no-std HTTP implementation
Documentation
use crate::{
	fail_details,
	parts::{RequestLine, Version},
	Error, ErrorKind, Header, StatusLine, Tup,
};

pub type Parse<'a, T> = Result<(&'a [u8], T), (&'a [u8], Error)>;

pub fn split_crlf(d: &[u8]) -> Option<(&[u8], &[u8])> {
	let p = d.windows(2).position(|w| w == b"\r\n")?;

	Some((&d[..p], &d[p + 2..]))
}

pub fn request_line(d: &[u8]) -> Parse<RequestLine> {
	let (line, rest) = split_crlf(d).ok_or((d, ErrorKind::NeedMoreData.into()))?;

	let go = || {
		let mut it = line
			.split(|b| b.is_ascii_whitespace())
			.filter(|bs| !bs.is_empty());

		let (method, target, version) = match (it.next(), it.next(), it.next()) {
			(Some(m), Some(t), Some(v)) => (m, t, v),
			_ => {
				return fail_details(
					ErrorKind::Parse,
					"request line doesn't have required number of elements",
				);
			},
		};

		let method = match core::str::from_utf8(method) {
			Ok(m) => m,
			_ => {
				return fail_details(ErrorKind::Parse, "expected method to be ascii");
			},
		};

		let target = match core::str::from_utf8(target) {
			Ok(m) => m,
			_ => {
				return fail_details(ErrorKind::Parse, "expected target to be ascii");
			},
		};

		let version = match () {
			_ if version.eq_ignore_ascii_case(b"http/1.1") => Version::HTTP1_1,
			_ => {
				return fail_details(ErrorKind::Parse, "unknown http version");
			},
		};

		Ok(RequestLine {
			method,
			target,
			version,
		})
	};

	go().tup(rest)
}

pub fn status_line(d: &[u8]) -> Parse<StatusLine> {
	let (line, rest) = split_crlf(d).ok_or((d, ErrorKind::NeedMoreData.into()))?;

	let go = || {
		let mut it = line
			.split(|b| b.is_ascii_whitespace())
			.filter(|bs| !bs.is_empty());

		let (version, status_code, status_text) = match (it.next(), it.next(), it.next()) {
			(Some(m), Some(t), Some(v)) => (m, t, v),
			_ => {
				return fail_details(
					ErrorKind::Parse,
					"status line doesn't have required number of elements",
				);
			},
		};

		let version = match () {
			_ if version.eq_ignore_ascii_case(b"http/1.1") => Version::HTTP1_1,
			_ => {
				return fail_details(ErrorKind::Parse, "unknown http version");
			},
		};

		let status_code = core::str::from_utf8(status_code)
			.ok()
			.and_then(|s| s.parse().ok())
			.ok_or_else(|| Error::with_details(ErrorKind::Parse, "invalid status code"))?;

		let status_text = core::str::from_utf8(status_text)
			.ok()
			.ok_or_else(|| Error::with_details(ErrorKind::Parse, "invalid status text"))?;

		Ok(StatusLine {
			version,
			status_code,
			status_text,
		})
	};

	go().tup(rest)
}

pub fn header(d: &[u8]) -> Parse<Header> {
	let (line, rest) = split_crlf(d).ok_or((d, ErrorKind::NeedMoreData.into()))?;

	let go = || {
		let mut it = line.split(|b| *b == b':').filter(|bs| !bs.is_empty());

		let (name, value) = match (it.next(), it.next()) {
			(Some(n), Some(v)) => (n, v),
			_ => {
				return fail_details(
					ErrorKind::Parse,
					"header doesn't have required number of elements",
				);
			},
		};

		let name = match core::str::from_utf8(name) {
			Ok(m) => m,
			_ => return fail_details(ErrorKind::Parse, "expected target to be ascii"),
		};
		let name = name.trim();

		let value = value.trim_ascii();

		Ok(Header::from((name, value)))
	};

	go().tup(rest)
}