|
|
|
@ -6,11 +6,15 @@ use crate::core::*;
|
|
|
|
|
use crate::util::slice::SliceCursor;
|
|
|
|
|
use crate::util::transcode;
|
|
|
|
|
|
|
|
|
|
/// Parse an HTTP Structured Field Value according to
|
|
|
|
|
/// [RFC 8941](https://www.rfc-editor.org/info/rfc8941).
|
|
|
|
|
/// Note: This only parses one "line" although the RFC says conforming
|
|
|
|
|
/// software MUST support values split over several headers.
|
|
|
|
|
pub trait ParseHeader<'a>: Sized {
|
|
|
|
|
fn parse_from_ascii(header: &'a [u8]) -> Result<Self>;
|
|
|
|
|
fn parse_from_ascii(header: &'a [u8], strict: bool) -> Result<Self>;
|
|
|
|
|
|
|
|
|
|
fn parse_from_header(header: &'a HeaderValue) -> Result<Self> {
|
|
|
|
|
Self::parse_from_ascii(header.as_bytes())
|
|
|
|
|
fn parse_from_header(header: &'a HeaderValue, strict: bool) -> Result<Self> {
|
|
|
|
|
Self::parse_from_ascii(header.as_bytes(), strict)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -44,6 +48,7 @@ pub enum BareItem<'a> {
|
|
|
|
|
Decimal(f32),
|
|
|
|
|
String(StringItem<'a>),
|
|
|
|
|
Token(TokenItem<'a>),
|
|
|
|
|
Url(UrlItem<'a>),
|
|
|
|
|
ByteSequence(ByteSequenceItem<'a>),
|
|
|
|
|
Boolean(bool),
|
|
|
|
|
}
|
|
|
|
@ -53,11 +58,13 @@ pub struct StringItem<'a>(&'a str);
|
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
|
|
|
pub struct TokenItem<'a>(&'a str);
|
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
|
|
|
pub struct UrlItem<'a>(&'a str);
|
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
|
|
|
pub struct ByteSequenceItem<'a>(&'a str);
|
|
|
|
|
|
|
|
|
|
impl<'a> ParseHeader<'a> for Dictionary<'a> {
|
|
|
|
|
fn parse_from_ascii(header: &'a [u8]) -> Result<Self> {
|
|
|
|
|
Parser::new(header)?.parse_dictionary()
|
|
|
|
|
fn parse_from_ascii(header: &'a [u8], strict: bool) -> Result<Self> {
|
|
|
|
|
Parser::new(header, strict)?.parse_dictionary()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -72,8 +79,8 @@ impl<'a> Dictionary<'a> {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a> ParseHeader<'a> for List<'a> {
|
|
|
|
|
fn parse_from_ascii(header: &'a [u8]) -> Result<Self> {
|
|
|
|
|
Parser::new(header)?.parse_list()
|
|
|
|
|
fn parse_from_ascii(header: &'a [u8], strict: bool) -> Result<Self> {
|
|
|
|
|
Parser::new(header, strict)?.parse_list()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -92,12 +99,16 @@ impl<'a> List<'a> {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a> ParseHeader<'a> for Item<'a> {
|
|
|
|
|
fn parse_from_ascii(header: &'a [u8]) -> Result<Self> {
|
|
|
|
|
Parser::new(header)?.parse_item()
|
|
|
|
|
fn parse_from_ascii(header: &'a [u8], strict: bool) -> Result<Self> {
|
|
|
|
|
Parser::new(header, strict)?.parse_item(!strict)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a> Item<'a> {
|
|
|
|
|
pub fn get_params(&self) -> &[(&'a str, BareItem<'a>)] {
|
|
|
|
|
self.params.as_slice()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn param<K>(&self, key: K) -> Option<&BareItem<'a>>
|
|
|
|
|
where
|
|
|
|
|
K: Into<&'a str>,
|
|
|
|
@ -132,6 +143,10 @@ impl<'a> Item<'a> {
|
|
|
|
|
self.bare_item.as_token()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn as_url(&self) -> Option<&'a str> {
|
|
|
|
|
self.bare_item.as_url()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn as_byte_sequence(&self) -> Option<Bytes> {
|
|
|
|
|
self.bare_item.as_byte_sequence()
|
|
|
|
|
}
|
|
|
|
@ -170,6 +185,13 @@ impl<'a> BareItem<'a> {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn as_url(&self) -> Option<&'a str> {
|
|
|
|
|
match self {
|
|
|
|
|
BareItem::Url(u) => Some(u.0),
|
|
|
|
|
_ => None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn as_byte_sequence(&self) -> Option<Bytes> {
|
|
|
|
|
match self {
|
|
|
|
|
BareItem::ByteSequence(bs) => Some(bs.into()),
|
|
|
|
@ -186,6 +208,13 @@ impl<'a> BareItem<'a> {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a> Member<'a> {
|
|
|
|
|
pub fn get_params(&self) -> &[(&'a str, BareItem<'a>)] {
|
|
|
|
|
match self {
|
|
|
|
|
Member::Item(i) => i.get_params(),
|
|
|
|
|
Member::InnerList(l) => l.get_params(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn param<K>(&self, key: K) -> Option<&BareItem<'a>>
|
|
|
|
|
where
|
|
|
|
|
K: Into<&'a str>,
|
|
|
|
@ -269,6 +298,10 @@ impl<'a> InnerList<'a> {
|
|
|
|
|
self.items.len()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn get_params(&self) -> &[(&'a str, BareItem<'a>)] {
|
|
|
|
|
self.params.as_slice()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn param<K>(&self, key: K) -> Option<&BareItem<'a>>
|
|
|
|
|
where
|
|
|
|
|
K: Into<&'a str>,
|
|
|
|
@ -290,13 +323,15 @@ impl<'a> InnerList<'a> {
|
|
|
|
|
|
|
|
|
|
struct Parser<'a> {
|
|
|
|
|
cursor: SliceCursor<'a, u8>,
|
|
|
|
|
strict: bool,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a> Parser<'a> {
|
|
|
|
|
fn new(data: &'a [u8]) -> Result<Parser> {
|
|
|
|
|
if data.is_ascii() {
|
|
|
|
|
fn new(data: &'a [u8], strict: bool) -> Result<Parser> {
|
|
|
|
|
if data.is_ascii() || (std::str::from_utf8(data).is_ok() && !strict) {
|
|
|
|
|
Ok(Parser {
|
|
|
|
|
cursor: SliceCursor::new(data),
|
|
|
|
|
strict,
|
|
|
|
|
})
|
|
|
|
|
} else {
|
|
|
|
|
Err(Error::BadHeader(String::from(
|
|
|
|
@ -335,7 +370,7 @@ impl<'a> Parser<'a> {
|
|
|
|
|
if self.cursor.peek().copied() == Some(b'(') {
|
|
|
|
|
self.parse_inner_list().map(Member::InnerList)
|
|
|
|
|
} else {
|
|
|
|
|
self.parse_item().map(Member::Item)
|
|
|
|
|
self.parse_item(false).map(Member::Item)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -377,7 +412,7 @@ impl<'a> Parser<'a> {
|
|
|
|
|
if self.cursor.peek().copied() == Some(b'(') {
|
|
|
|
|
Member::InnerList(self.parse_inner_list()?)
|
|
|
|
|
} else {
|
|
|
|
|
Member::Item(self.parse_item()?)
|
|
|
|
|
Member::Item(self.parse_item(false)?)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// parameters
|
|
|
|
@ -403,7 +438,7 @@ impl<'a> Parser<'a> {
|
|
|
|
|
if self.skip_if(|c| c == b')') {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
items.push(self.parse_item()?);
|
|
|
|
|
items.push(self.parse_item(false)?);
|
|
|
|
|
// > Parsers MUST support Inner Lists containing at least 256 members.
|
|
|
|
|
if items.len() == 256 {
|
|
|
|
|
break;
|
|
|
|
@ -424,8 +459,8 @@ impl<'a> Parser<'a> {
|
|
|
|
|
/// ```notrust
|
|
|
|
|
/// sf-item = bare-item parameters
|
|
|
|
|
/// ```
|
|
|
|
|
fn parse_item(&mut self) -> Result<Item<'a>> {
|
|
|
|
|
let bare_item = self.parse_bare_item()?;
|
|
|
|
|
fn parse_item(&mut self, allow_url: bool) -> Result<Item<'a>> {
|
|
|
|
|
let bare_item = self.parse_bare_item(allow_url)?;
|
|
|
|
|
let params = self.parse_parameters()?;
|
|
|
|
|
Ok(Item { bare_item, params })
|
|
|
|
|
}
|
|
|
|
@ -457,7 +492,7 @@ impl<'a> Parser<'a> {
|
|
|
|
|
fn parse_parameter(&mut self) -> Result<(&'a str, BareItem<'a>)> {
|
|
|
|
|
let key = self.parse_key()?;
|
|
|
|
|
let value = if self.skip_if(|c| c == b'=') {
|
|
|
|
|
self.parse_bare_item()?
|
|
|
|
|
self.parse_bare_item(false)?
|
|
|
|
|
} else {
|
|
|
|
|
BareItem::Boolean(true)
|
|
|
|
|
};
|
|
|
|
@ -484,7 +519,7 @@ impl<'a> Parser<'a> {
|
|
|
|
|
/// bare-item = sf-integer / sf-decimal / sf-string
|
|
|
|
|
/// / sf-token / sf-binary / sf-boolean
|
|
|
|
|
/// ```
|
|
|
|
|
fn parse_bare_item(&mut self) -> Result<BareItem<'a>> {
|
|
|
|
|
fn parse_bare_item(&mut self, allow_url: bool) -> Result<BareItem<'a>> {
|
|
|
|
|
match self
|
|
|
|
|
.cursor
|
|
|
|
|
.peek()
|
|
|
|
@ -493,10 +528,11 @@ impl<'a> Parser<'a> {
|
|
|
|
|
{
|
|
|
|
|
c if is_numeric_start(c) => self.parse_numeric(),
|
|
|
|
|
b'"' => self.parse_string(),
|
|
|
|
|
b'<' if allow_url => self.parse_url(),
|
|
|
|
|
c if is_token_start(c) => self.parse_token(),
|
|
|
|
|
b':' => self.parse_byte_sequence(),
|
|
|
|
|
b'?' => self.parse_boolean(),
|
|
|
|
|
_ => Err(self.make_error("Unexpected character")),
|
|
|
|
|
c => Err(self.make_error(format!("Unexpected character {:?}", c as char))),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -550,6 +586,21 @@ impl<'a> Parser<'a> {
|
|
|
|
|
Ok(BareItem::String(StringItem(slice)))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn parse_url(&mut self) -> Result<BareItem<'a>> {
|
|
|
|
|
if self.strict {
|
|
|
|
|
return Err(
|
|
|
|
|
self.make_error("URLs enclosed in <angle brackets> are forbidden in strict mode")
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.assert_next(|c| c == b'<')?;
|
|
|
|
|
self.chop();
|
|
|
|
|
self.skip_while(|c| c != b'>');
|
|
|
|
|
let slice = self.chop();
|
|
|
|
|
self.assert_next(|c| c == b'>')?;
|
|
|
|
|
Ok(BareItem::Url(UrlItem(slice)))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Parse a Token item (section 3.3.4).
|
|
|
|
|
///
|
|
|
|
|
/// ```notrust
|
|
|
|
@ -736,15 +787,15 @@ mod tests {
|
|
|
|
|
use crate::util::transcode::base64_decode;
|
|
|
|
|
|
|
|
|
|
fn mklist(header: &'static str) -> Result<List<'static>> {
|
|
|
|
|
List::parse_from_ascii(header.as_bytes())
|
|
|
|
|
List::parse_from_ascii(header.as_bytes(), true)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn mkdict(header: &'static str) -> Result<Dictionary<'static>> {
|
|
|
|
|
Dictionary::parse_from_ascii(header.as_bytes())
|
|
|
|
|
Dictionary::parse_from_ascii(header.as_bytes(), true)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn mkitem(header: &'static str) -> Result<Item<'static>> {
|
|
|
|
|
Item::parse_from_ascii(header.as_bytes())
|
|
|
|
|
Item::parse_from_ascii(header.as_bytes(), true)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|