use nom::{
bytes::{
complete::{tag as tag_complete, take_while as take_while_complete},
streaming::{tag, take, take_while},
},
character::{
complete::char as char_complete,
streaming::{char, hex_digit1, one_of},
},
combinator::opt,
error::{make_error, ErrorKind as NomErrorKind, ParseError},
AsChar, Err as NomError, IResult, Parser,
};
use crate::{
compile_lookup, make_char_table,
protocol::utils::compare_no_case,
storage::{Store, Version},
};
fn error_position<I, E: ParseError<I>>(i: I, kind: NomErrorKind) -> NomError<E> {
NomError::Error(make_error(i, kind))
}
pub struct CharLookup {
ranges: CharRanges,
table: CharTable,
len: i32,
}
#[repr(align(16))]
pub struct CharTable([bool; 256]);
impl std::ops::Deref for CharTable {
type Target = [bool; 256];
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[repr(align(16))]
pub struct CharRanges([u8; 16]);
impl std::ops::Deref for CharRanges {
type Target = [u8; 16];
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[cfg(not(feature = "tolerant-parsing"))]
const LAST_INVALID_CHAR: u8 = 0xFF;
#[cfg(feature = "tolerant-parsing")]
const LAST_INVALID_CHAR: u8 = 0x9F;
compile_lookup!(pub tchar => [0x00..0x20, b'('..b')', b'['..b']', b'{', b'}', b',', b':'..b'@', 0x7F..LAST_INVALID_CHAR]);
compile_lookup!(pub vchar => [0x00..0x20, 0x7F..LAST_INVALID_CHAR]);
compile_lookup!(pub ck_char => [0x00..0x1F, b';', b'=', 0x7F..LAST_INVALID_CHAR]);
compile_lookup!(pub cv_char => [0x00..0x1F, b';', 0x7F..LAST_INVALID_CHAR]);
compile_lookup!(pub achar => [0x00..0x08, 0x0A..0x1F, 0x7F..LAST_INVALID_CHAR]);
#[inline]
fn space(i: &[u8]) -> IResult<&[u8], char> {
char(' ').parse(i)
}
#[inline]
pub fn crlf(i: &[u8]) -> IResult<&[u8], &[u8]> {
tag(&b"\r\n"[..]).parse(i)
}
#[inline]
fn http_version(i: &[u8]) -> IResult<&[u8], Version> {
let (i, _) = tag(&b"HTTP/1."[..]).parse(i)?;
let (i, minor) = one_of("01").parse(i)?;
Ok((
i,
if minor == '0' {
Version::V10
} else {
Version::V11
},
))
}
#[inline]
fn http_status(i: &[u8]) -> IResult<&[u8], (&[u8], u16)> {
let (i, status) = take(3usize).parse(i)?;
let code = std::str::from_utf8(status)
.ok()
.and_then(|status| status.parse::<u16>().ok());
match code {
Some(code) => Ok((i, (status, code))),
None => Err(error_position(i, NomErrorKind::MapRes)),
}
}
#[inline]
#[allow(clippy::type_complexity)]
pub fn parse_request_line(i: &[u8]) -> IResult<&[u8], (&[u8], &[u8], Version)> {
let (i, method) = tchar::take_while_fast(i)?;
let (i, _) = space(i)?;
let (i, uri) = vchar::take_while_fast(i)?;
let (i, _) = space(i)?;
let (i, version) = http_version(i)?;
let (i, _) = crlf(i)?;
Ok((i, (method, uri, version)))
}
#[inline]
#[allow(clippy::type_complexity)]
pub fn parse_response_line(i: &[u8]) -> IResult<&[u8], (Version, &[u8], u16, &[u8])> {
let (i, version) = http_version(i)?;
let (i, _) = space(i)?;
let (i, (status, code)) = http_status(i)?;
let (i, _) = space(i)?;
let (i, reason) = achar::take_while_fast(i)?;
let (i, _) = crlf(i)?;
Ok((i, (version, status, code, reason)))
}
#[inline]
#[allow(clippy::type_complexity)]
pub fn parse_header_or_cookie(i: &[u8]) -> IResult<&[u8], Option<(&[u8], &[u8])>> {
let (i, key) = tchar::take_while_fast(i)?;
let (i, _) = tag(&b":"[..]).parse(i)?;
let (i, _) = take_while(AsChar::is_space).parse(i)?;
if compare_no_case(key, b"cookie") {
return Ok((i, None));
}
let (i, val) = achar::take_while_fast(i)?;
let (i, _) = crlf(i)?;
Ok((i, Some((key, val))))
}
#[inline]
pub fn parse_header(i: &[u8]) -> IResult<&[u8], (&[u8], &[u8])> {
let (i, key) = tchar::take_while_fast(i)?;
let (i, _) = tag(&b":"[..]).parse(i)?;
let (i, _) = take_while(AsChar::is_space).parse(i)?;
let (i, val) = achar::take_while_fast(i)?;
let (i, _) = crlf(i)?;
Ok((i, (key, val)))
}
#[inline]
#[allow(clippy::type_complexity)]
pub fn parse_single_crumb(i: &[u8], first: bool) -> IResult<&[u8], (&[u8], &[u8])> {
let i = if !first {
let (i, _) = (tag(&b";"[..]), take_while(AsChar::is_space)).parse(i)?;
i
} else {
i
};
let (i, key) = ck_char::take_while_fast(i)?;
let (i, val) = opt((tag(&b"="[..]), cv_char::take_while_fast)).parse(i)?;
match val {
Some((_, val)) => Ok((i, (key, val))),
None => Ok((i, (&key[..0], key))),
}
}
#[rustfmt::skip]
const SCHEME_CHAR_MAP: CharTable = make_char_table![
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
#[rustfmt::skip]
const AUTHORITY_CHAR_MAP: CharTable = make_char_table![
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
#[rustfmt::skip]
const USERINFO_CHAR_MAP: CharTable = make_char_table![
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
#[inline]
fn is_scheme_char(i: u8) -> bool {
SCHEME_CHAR_MAP[i as usize]
}
#[inline]
fn is_authority_char(i: u8) -> bool {
AUTHORITY_CHAR_MAP[i as usize]
}
#[inline]
fn is_userinfo_char(i: u8) -> bool {
USERINFO_CHAR_MAP[i as usize]
}
#[inline]
pub fn chunk_size(i: &[u8]) -> IResult<&[u8], (&[u8], usize)> {
let (i, size_hexa) = hex_digit1(i)?;
let size = std::str::from_utf8(size_hexa)
.ok()
.and_then(|chunk_size| usize::from_str_radix(chunk_size, 16).ok());
match size {
Some(size) => Ok((i, (size_hexa, size))),
None => Err(error_position(i, NomErrorKind::MapRes)),
}
}
#[inline]
pub fn parse_chunk_header(first: bool, i: &[u8]) -> IResult<&[u8], (&[u8], usize)> {
if first {
let (i, size) = chunk_size(i)?;
let (i, _) = crlf(i)?;
Ok((i, size))
} else {
let (i, _) = crlf(i)?;
let (i, size) = chunk_size(i)?;
let (i, _) = crlf(i)?;
Ok((i, size))
}
}
#[inline]
fn userinfo(i: &[u8]) -> IResult<&[u8], &[u8]> {
let (i, userinfo) = take_while_complete(is_userinfo_char).parse(i)?;
let (i, _) = char_complete('@').parse(i)?;
Ok((i, userinfo))
}
#[inline]
fn parse_asterisk_form<'a>(buffer: &[u8], i: &'a [u8]) -> IResult<&'a [u8], (Store, Store)> {
if i == b"*" {
Ok((i, (Store::Empty, Store::Static(b"*"))))
} else if i[0] == b'/' {
parse_origin_form(buffer, i)
} else {
parse_absolute_form(buffer, i, b"*")
}
}
#[inline]
fn parse_authority_form<'a>(buffer: &[u8], i: &'a [u8]) -> IResult<&'a [u8], (Store, Store)> {
Ok((&[], (Store::new_slice(buffer, i), Store::Static(b"/"))))
}
#[inline]
fn parse_origin_form<'a>(buffer: &[u8], i: &'a [u8]) -> IResult<&'a [u8], (Store, Store)> {
Ok((&[], (Store::Empty, Store::new_slice(buffer, i))))
}
#[inline]
fn parse_absolute_form<'a>(
buffer: &[u8],
i: &'a [u8],
empty_path_replacer: &'static [u8],
) -> IResult<&'a [u8], (Store, Store)> {
let (i, _scheme) = take_while_complete(is_scheme_char).parse(i)?;
let (i, _) = tag_complete(&b"://"[..]).parse(i)?;
let (i, _userinfo) = opt(userinfo).parse(i)?;
let (path, authority) = take_while_complete(is_authority_char).parse(i)?;
let authority = Store::new_slice(buffer, authority);
let path = if path.is_empty() {
Store::Static(empty_path_replacer)
} else {
Store::new_slice(buffer, path)
};
Ok((&[], (authority, path)))
}
#[inline]
pub fn parse_url(buffer: &[u8], method: &[u8], i: &[u8]) -> Option<(Store, Store)> {
if i.is_empty() {
return Some((Store::Empty, Store::Static(b"/")));
}
let url = if compare_no_case(method, b"OPTIONS") {
parse_asterisk_form(buffer, i)
} else if compare_no_case(method, b"CONNECT") {
parse_authority_form(buffer, i)
} else if i[0] == b'/' {
parse_origin_form(buffer, i)
} else {
parse_absolute_form(buffer, i, b"/")
};
match url {
Ok((_, url)) => Some(url),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::str::from_utf8_unchecked;
fn test_url(method: &str, url: &str, expect: (&str, &str)) {
println!("{method} {url} HTTP/1.1");
let result = parse_url(url.as_bytes(), method.as_bytes(), url.as_bytes());
assert!(result.is_some());
let (authority, path) = result.unwrap();
assert_eq!(
(
authority
.data_opt(url.as_bytes())
.map_or("", |data| unsafe { from_utf8_unchecked(data) }),
path.data_opt(url.as_bytes())
.map_or("", |data| unsafe { from_utf8_unchecked(data) })
),
expect
);
}
#[test]
fn test_asterisk_form() {
test_url("OPTIONS", "*", ("", "*"));
test_url("OPTIONS", "/index.html?k=v#h", ("", "/index.html?k=v#h"));
test_url(
"OPTIONS",
"http://www.example.org:8001",
("www.example.org:8001", "*"),
);
test_url(
"OPTIONS",
"http://www.example.org:8001/index.html?k=v#h",
("www.example.org:8001", "/index.html?k=v#h"),
);
}
#[test]
fn test_authority_form() {
test_url(
"CONNECT",
"www.example.org:8001",
("www.example.org:8001", "/"),
);
}
#[test]
fn test_origin_form() {
test_url("GET", "/index.html?k=v#h", ("", "/index.html?k=v#h"));
test_url("OPTIONS", "/index.html?k=v#h", ("", "/index.html?k=v#h"));
}
#[test]
fn test_absolute_form() {
test_url(
"GET",
"http://www.example.org:8001",
("www.example.org:8001", "/"),
);
test_url(
"GET",
"http://www.example.org:8001?k=v#h",
("www.example.org:8001", "?k=v#h"),
);
test_url(
"GET",
"http://www.example.org:8001/index.html?k=v#h",
("www.example.org:8001", "/index.html?k=v#h"),
);
test_url(
"GET",
"http://user:pass@www.example.org:8001/index.html?k=v#h",
("www.example.org:8001", "/index.html?k=v#h"),
);
}
}