use crate::http_token_utils::{http_token, is_rfc_tchar, is_rfc_whitespace, HttpToken};
pub const LF: u8 = b'\n';
pub const SP: u8 = b' ';
pub const COLON: u8 = b':';
const CR: u8 = b'\r';
const EMPTY_TOKEN: HttpToken = http_token("");
pub struct RequestBuffer<'a> {
buf: HttpToken<'a>,
pos: usize,
end: usize,
}
impl<'a> RequestBuffer<'a> {
#[inline]
pub fn new(buf: HttpToken<'a>) -> Self {
Self {
buf,
pos: 0,
end: buf.len(),
}
}
#[inline]
fn slice(&self, token_begin: usize, token_end: usize) -> Self {
Self {
buf: self.buf,
pos: token_begin,
end: token_end,
}
}
#[inline]
pub fn is_done(&self) -> bool {
self.pos >= self.end
}
#[inline]
pub fn is_partial(&self) -> bool {
self.pos > self.end
}
#[inline]
pub fn starts_with_rfc_whitespace(&self) -> bool {
debug_assert!(self.pos < self.end);
is_rfc_whitespace(self.buf[self.pos])
}
#[inline]
pub fn as_http_token(&self) -> HttpToken<'a> {
if self.pos < self.end {
&self.buf[self.pos..self.end]
} else {
EMPTY_TOKEN
}
}
#[inline]
pub fn trim_last_cr(&mut self) -> bool {
if self.end > self.pos && self.buf[self.end - 1] == CR {
self.end -= 1;
true
} else {
false
}
}
#[inline]
pub fn next_token(&mut self, delimiter: u8) -> Self {
let mut current = self.pos;
while current < self.end && self.buf[current] != delimiter {
current += 1;
}
let token_begin = self.pos;
let token_end = current;
self.pos = current + 1;
self.slice(token_begin, token_end)
}
pub fn last_token(&mut self, delimiter: u8) -> Self {
let mut current = if self.end > self.pos {
self.end - 1
} else {
self.pos
};
let mut skip_non_tchars = true;
while self.pos < current && (self.buf[current] != delimiter || skip_non_tchars) {
skip_non_tchars &= !is_rfc_tchar(self.buf[current]);
current -= 1;
}
let token_begin;
let token_end;
if current >= self.pos && current < self.end {
token_begin = if self.buf[current] == delimiter {
current + 1
} else {
current
};
token_end = self.end;
self.end = token_begin - 1;
} else {
token_begin = 1;
token_end = 0;
}
self.slice(token_begin, token_end)
}
}
#[cfg(test)]
mod tests {
use crate::http_token_utils::{http_token, to_quoted_ascii, HttpToken};
use crate::raw_request_parser::{RequestBuffer, EMPTY_TOKEN, LF, SP};
const GET_REQUEST: HttpToken = http_token("GET /foo/bar HTTP/1.1\r\nHost: localhost\r\n\r\n");
#[test]
fn test_parse_request_line() {
let mut buffer = RequestBuffer::new(GET_REQUEST);
let mut request_line = buffer.next_token(LF);
assert_eq!(
http_token("GET /foo/bar HTTP/1.1\r"),
request_line.as_http_token(),
"{}",
to_quoted_ascii(request_line.as_http_token())
);
assert!(request_line.trim_last_cr());
assert_eq!(
http_token("GET /foo/bar HTTP/1.1"),
request_line.as_http_token(),
"{}",
to_quoted_ascii(request_line.as_http_token())
);
let method = request_line.next_token(SP);
let version = request_line.last_token(SP);
let uri = request_line;
assert_eq!(
http_token("GET"),
method.as_http_token(),
"{}",
to_quoted_ascii(method.as_http_token())
);
assert_eq!(
http_token("/foo/bar"),
uri.as_http_token(),
"{}",
to_quoted_ascii(uri.as_http_token())
);
assert_eq!(
http_token("HTTP/1.1"),
version.as_http_token(),
"{}",
to_quoted_ascii(version.as_http_token())
);
}
#[test]
fn test_parse_line_by_line() {
let mut buffer = RequestBuffer::new(GET_REQUEST);
let mut request_line = buffer.next_token(LF);
assert_eq!(
http_token("GET /foo/bar HTTP/1.1\r"),
request_line.as_http_token(),
"{}",
to_quoted_ascii(request_line.as_http_token())
);
assert!(request_line.trim_last_cr());
assert_eq!(
http_token("GET /foo/bar HTTP/1.1"),
request_line.as_http_token(),
"{}",
to_quoted_ascii(request_line.as_http_token())
);
let mut header = buffer.next_token(LF);
assert_eq!(
http_token("Host: localhost\r"),
header.as_http_token(),
"{}",
to_quoted_ascii(header.as_http_token())
);
assert!(header.trim_last_cr());
assert_eq!(
http_token("Host: localhost"),
header.as_http_token(),
"{}",
to_quoted_ascii(header.as_http_token())
);
let mut empty = buffer.next_token(LF);
assert_eq!(
http_token("\r"),
empty.as_http_token(),
"{}",
to_quoted_ascii(empty.as_http_token())
);
assert!(empty.trim_last_cr());
assert_eq!(
http_token(""),
empty.as_http_token(),
"{}",
to_quoted_ascii(empty.as_http_token())
);
assert!(buffer.is_done());
}
#[test]
fn test_parse_non_compliant_request_line() {
let mut buffer = RequestBuffer::new(http_token(
"GET /foo/bar HTTP/1.1 \t \r\nHost: localhost\r\n\r\n",
));
let mut request_line = buffer.next_token(LF);
assert!(request_line.trim_last_cr());
let method = request_line.next_token(SP);
let version = request_line.last_token(SP);
let uri = request_line;
assert_eq!(
http_token("GET"),
method.as_http_token(),
"{}",
to_quoted_ascii(method.as_http_token())
);
assert_eq!(
http_token("/foo/bar"),
uri.as_http_token(),
"{}",
to_quoted_ascii(uri.as_http_token())
);
assert_eq!(
http_token("HTTP/1.1 \t "),
version.as_http_token(),
"{}",
to_quoted_ascii(version.as_http_token())
);
}
#[test]
fn test_parse_non_tchar_request_line() {
let mut buffer = RequestBuffer::new(http_token(
"GET /foo/bar HTTP/1.1 абра кадабра\r\nHost: localhost\r\n\r\n",
));
let mut request_line = buffer.next_token(LF);
assert!(request_line.trim_last_cr());
let method = request_line.next_token(SP);
let version = request_line.last_token(SP);
let uri = request_line;
assert_eq!(
http_token("GET"),
method.as_http_token(),
"{}",
to_quoted_ascii(method.as_http_token())
);
assert_eq!(
http_token("/foo/bar"),
uri.as_http_token(),
"{}",
to_quoted_ascii(uri.as_http_token())
);
assert!(
version.as_http_token().starts_with(http_token("HTTP/1.1")),
"{}",
to_quoted_ascii(version.as_http_token())
);
}
#[test]
fn test_parse_non_compliant_terminator_request_line() {
let mut buffer = RequestBuffer::new(http_token(
"GET /foo/bar HTTP/1.1 \t \nHost: localhost\r\n\r\n",
));
let mut request_line = buffer.next_token(LF);
assert!(!request_line.trim_last_cr());
let method = request_line.next_token(SP);
let version = request_line.last_token(SP);
let uri = request_line;
assert_eq!(
http_token("GET"),
method.as_http_token(),
"{}",
to_quoted_ascii(method.as_http_token())
);
assert_eq!(
http_token("/foo/bar"),
uri.as_http_token(),
"{}",
to_quoted_ascii(uri.as_http_token())
);
assert_eq!(
http_token("HTTP/1.1 \t "),
version.as_http_token(),
"{}",
to_quoted_ascii(version.as_http_token())
);
}
#[test]
fn test_parse_non_missing_empty_line() {
let mut buffer =
RequestBuffer::new(http_token("GET /foo/bar HTTP/1.1\r\nHost: localhost\r\n"));
let mut request_line = buffer.next_token(LF);
assert!(request_line.trim_last_cr());
let mut header_line = buffer.next_token(LF);
assert!(header_line.trim_last_cr());
assert!(!header_line.is_done());
assert!(buffer.is_done());
}
#[test]
fn test_parse_missing_uri() {
let mut buffer = RequestBuffer::new(http_token("GET HTTP/1.1"));
let method = buffer.next_token(SP);
assert_eq!(
http_token("GET"),
method.as_http_token(),
"{}",
to_quoted_ascii(method.as_http_token())
);
let version = buffer.last_token(SP);
assert_eq!(
http_token("HTTP/1.1"),
version.as_http_token(),
"{}",
to_quoted_ascii(version.as_http_token())
);
assert!(buffer.is_partial());
}
#[test]
fn test_parse_empty_uri() {
let mut buffer = RequestBuffer::new(http_token("GET HTTP/1.1"));
let method = buffer.next_token(SP);
assert_eq!(
http_token("GET"),
method.as_http_token(),
"{}",
to_quoted_ascii(method.as_http_token())
);
let version = buffer.last_token(SP);
assert_eq!(
http_token("HTTP/1.1"),
version.as_http_token(),
"{}",
to_quoted_ascii(version.as_http_token())
);
assert!(buffer.is_done());
}
#[test]
fn test_parse_empty_uri_bad_delimiters() {
let mut buffer = RequestBuffer::new(http_token("GET \tHTTP/1.1"));
let method = buffer.next_token(SP);
assert_eq!(
http_token("GET"),
method.as_http_token(),
"{}",
to_quoted_ascii(method.as_http_token())
);
let version = buffer.last_token(SP);
assert_eq!(
http_token("\tHTTP/1.1"),
version.as_http_token(),
"{}",
to_quoted_ascii(version.as_http_token())
);
assert!(buffer.is_partial());
}
#[test]
fn test_parse_partial() {
let mut buffer = RequestBuffer::new(http_token("GET /foo/bar HTTP/1.1\r\nHost: localhost"));
let mut request_line = buffer.next_token(LF);
assert!(request_line.trim_last_cr());
let mut header_line = buffer.next_token(LF);
assert!(!header_line.trim_last_cr());
assert!(!header_line.is_done());
assert!(buffer.is_partial());
}
#[test]
fn test_parse_request_line_http_09() {
let mut buffer = RequestBuffer::new(http_token("GET /foo/bar\r\nHost: localhost\r\n\r\n"));
let mut request_line = buffer.next_token(LF);
assert!(request_line.trim_last_cr());
let method = request_line.next_token(SP);
let mut version = request_line.last_token(SP);
let mut uri = request_line;
assert!(uri.is_partial());
uri = version;
version = RequestBuffer::new(EMPTY_TOKEN);
assert_eq!(
http_token("GET"),
method.as_http_token(),
"{}",
to_quoted_ascii(method.as_http_token())
);
assert_eq!(
http_token("/foo/bar"),
uri.as_http_token(),
"{}",
to_quoted_ascii(uri.as_http_token())
);
assert_eq!(
http_token(""),
version.as_http_token(),
"{}",
to_quoted_ascii(version.as_http_token())
);
}
}