use std::ops::Range;
use bytes::Bytes;
use crate::error::HeaderError;
use crate::headers::grammar::{
self, HeaderParam, find_param_start, is_token_char, quoted_string_end, skip_ws, trim,
};
use crate::message::TypedHeader;
use crate::name::HeaderName;
use crate::uri::Uri;
#[derive(Debug, Clone)]
pub struct Address {
pub display_name: Option<Vec<u8>>,
pub uri: Uri,
pub params: Vec<HeaderParam>,
}
impl Address {
pub fn parse(value: &[u8], header: &'static str) -> Result<Self, HeaderError> {
parse_spanned(value, header).map(|parsed| parsed.address)
}
pub fn parse_list(value: &[u8], header: &'static str) -> Result<Vec<Self>, HeaderError> {
grammar::split_list(value, header)?
.into_iter()
.map(|part| Self::parse(part, header))
.collect()
}
#[must_use]
pub fn param(&self, name: &str) -> Option<&[u8]> {
grammar::param(&self.params, name).and_then(|p| p.value.as_deref())
}
#[must_use]
pub fn tag(&self) -> Option<&[u8]> {
self.param("tag")
}
}
#[derive(Debug)]
struct ParsedAddress {
address: Address,
presentation: Range<usize>,
uri: Range<usize>,
}
fn parse_spanned(value: &[u8], header: &'static str) -> Result<ParsedAddress, HeaderError> {
let outer = trimmed_range(value);
let value = value.get(outer.clone()).unwrap_or(&[]);
let mut i = skip_ws(value, 0);
let presentation_start = i;
let mut display_name = None;
if value.get(i) == Some(&b'"') {
let end =
quoted_string_end(value, i).ok_or(HeaderError::UnterminatedQuotedString { header })?;
let raw = value.get(i + 1..end.saturating_sub(1)).unwrap_or(&[]);
display_name = Some(unescape(raw));
i = skip_ws(value, end);
if value.get(i) != Some(&b'<') {
return Err(HeaderError::Syntax { header });
}
} else if let Some(angle) = find_angle(value, i) {
let raw = trim(value.get(i..angle).unwrap_or(&[]));
if !raw.is_empty() {
if !raw
.iter()
.all(|&b| is_token_char(b) || matches!(b, b' ' | b'\t'))
{
return Err(HeaderError::Syntax { header });
}
display_name = Some(raw.to_vec());
}
i = angle;
}
let (uri_span, presentation_span, params_tail) = if value.get(i) == Some(&b'<') {
let close = find_closing_angle(value, i).ok_or(HeaderError::Syntax { header })?;
(
i + 1..close,
presentation_start..close.saturating_add(1),
value.get(close + 1..).unwrap_or(&[]),
)
} else {
let rest = value.get(i..).unwrap_or(&[]);
let param_start = find_param_start(rest);
let candidate = match param_start {
Some(semi) => i..i.checked_add(semi).ok_or(HeaderError::Syntax { header })?,
None => i..value.len(),
};
let candidate_bytes = value.get(candidate.clone()).unwrap_or(&[]);
let trimmed = trimmed_range(candidate_bytes);
let uri_span = candidate
.start
.checked_add(trimmed.start)
.zip(candidate.start.checked_add(trimmed.end))
.map(|(start, end)| start..end)
.ok_or(HeaderError::Syntax { header })?;
let params_tail = if let Some(semi) = param_start {
rest.get(semi..).unwrap_or(&[])
} else {
&[][..]
};
(uri_span.clone(), uri_span, params_tail)
};
let uri_bytes = value.get(uri_span.clone()).unwrap_or(&[]);
if uri_bytes.is_empty() {
return Err(HeaderError::Syntax { header });
}
if value.get(i) != Some(&b'<') && uri_bytes.contains(&b'?') {
return Err(HeaderError::Syntax { header });
}
let parsed_uri = Uri::parse(Bytes::copy_from_slice(uri_bytes))
.map_err(|source| HeaderError::Uri { header, source })?;
let params = grammar::parse_params(trim(params_tail), header)?;
let uri_span = add_offset(uri_span, outer.start, header)?;
let presentation_span = add_offset(presentation_span, outer.start, header)?;
Ok(ParsedAddress {
address: Address {
display_name,
uri: parsed_uri,
params,
},
presentation: presentation_span,
uri: uri_span,
})
}
#[derive(Debug, Clone)]
pub(crate) struct AddressValueSpan {
pub(crate) part: Range<usize>,
pub(crate) item: Range<usize>,
pub(crate) presentation: Range<usize>,
pub(crate) uri: Range<usize>,
}
pub(crate) fn value_spans(
value: &[u8],
header: &'static str,
is_list: bool,
) -> Result<Vec<AddressValueSpan>, HeaderError> {
let parts = if is_list {
grammar::split_list_spans(value, header)?
} else {
std::iter::once(0..value.len()).collect()
};
parts
.into_iter()
.map(|part| {
let bytes = value.get(part.clone()).unwrap_or(&[]);
let parsed = parse_spanned(bytes, header)?;
let item = trimmed_range(bytes);
Ok(AddressValueSpan {
part: part.clone(),
item: add_offset(item, part.start, header)?,
presentation: add_offset(parsed.presentation, part.start, header)?,
uri: add_offset(parsed.uri, part.start, header)?,
})
})
.collect()
}
fn trimmed_range(value: &[u8]) -> Range<usize> {
let mut start = 0usize;
while matches!(value.get(start), Some(b' ' | b'\t')) {
start += 1;
}
let mut end = value.len();
while end > start && matches!(value.get(end - 1), Some(b' ' | b'\t')) {
end -= 1;
}
start..end
}
fn add_offset(
range: Range<usize>,
offset: usize,
header: &'static str,
) -> Result<Range<usize>, HeaderError> {
offset
.checked_add(range.start)
.zip(offset.checked_add(range.end))
.map(|(start, end)| start..end)
.ok_or(HeaderError::Syntax { header })
}
#[must_use]
fn find_angle(value: &[u8], from: usize) -> Option<usize> {
let mut i = from;
while i < value.len() {
match value.get(i) {
Some(b'"') => i = quoted_string_end(value, i)?,
Some(b'<') => return Some(i),
Some(_) => i += 1,
None => break,
}
}
None
}
#[must_use]
fn find_closing_angle(value: &[u8], open: usize) -> Option<usize> {
value
.get(open + 1..)?
.iter()
.position(|&b| b == b'>')
.map(|p| p + open + 1)
}
#[must_use]
fn unescape(raw: &[u8]) -> Vec<u8> {
let mut out = Vec::with_capacity(raw.len());
let mut i = 0usize;
while let Some(&b) = raw.get(i) {
if b == b'\\'
&& let Some(&next) = raw.get(i + 1)
{
out.push(next);
i += 2;
continue;
}
out.push(b);
i += 1;
}
out
}
macro_rules! address_type {
($(#[$meta:meta])* $type:ident) => {
$(#[$meta])*
#[derive(Debug, Clone)]
pub struct $type(pub Address);
impl std::ops::Deref for $type {
type Target = Address;
fn deref(&self) -> &Address {
&self.0
}
}
};
}
macro_rules! single_address_header {
($(#[$meta:meta])* $type:ident => $variant:ident, $label:literal) => {
address_type!($(#[$meta])* $type);
impl TypedHeader for $type {
const NAME: HeaderName = HeaderName::$variant;
fn decode(value: &[u8]) -> Result<Self, HeaderError> {
Address::parse(value, $label).map(Self)
}
}
};
}
macro_rules! address_list_header {
($(#[$meta:meta])* $type:ident => $variant:ident, $label:literal) => {
address_type!($(#[$meta])* $type);
impl $type {
pub fn parse_list(value: &[u8]) -> Result<Vec<Self>, HeaderError> {
Address::parse_list(value, $label).map(|list| list.into_iter().map(Self).collect())
}
}
impl TypedHeader for $type {
const NAME: HeaderName = HeaderName::$variant;
fn decode(value: &[u8]) -> Result<Self, HeaderError> {
let parts = grammar::split_list(value, $label)?;
let first = parts.first().copied().unwrap_or(&[]);
Address::parse(first, $label).map(Self)
}
fn decode_list(value: &[u8]) -> Result<Vec<Self>, HeaderError> {
Self::parse_list(value)
}
}
};
}
single_address_header!(
From => From, "From"
);
single_address_header!(
To => To, "To"
);
address_list_header!(
Contact => Contact, "Contact"
);
address_list_header!(
Route => Route, "Route"
);
address_list_header!(
RecordRoute => RecordRoute, "Record-Route"
);
address_list_header!(
Path => Path, "Path"
);
address_list_header!(
ServiceRoute => ServiceRoute, "Service-Route"
);
#[derive(Debug, Clone)]
pub enum ContactValue {
Wildcard,
Address(Address),
}
impl TypedHeader for ContactValue {
const NAME: HeaderName = HeaderName::Contact;
fn decode(value: &[u8]) -> Result<Self, HeaderError> {
if trim(value) == b"*" {
return Ok(Self::Wildcard);
}
Contact::decode(value).map(|c| Self::Address(c.0))
}
fn decode_list(value: &[u8]) -> Result<Vec<Self>, HeaderError> {
if trim(value) == b"*" {
return Ok(vec![Self::Wildcard]);
}
Address::parse_list(value, "Contact")
.map(|list| list.into_iter().map(Self::Address).collect())
}
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::indexing_slicing
)]
mod tests {
use super::*;
fn addr(value: &[u8]) -> Address {
Address::parse(value, "To").unwrap_or_else(|e| panic!("{value:?} should parse: {e}"))
}
#[test]
fn parses_a_bare_addr_spec() {
let a = addr(b"sip:j.user@example.com");
assert!(a.display_name.is_none());
assert_eq!(
a.uri.to_bytes(),
Bytes::from_static(b"sip:j.user@example.com")
);
assert!(a.params.is_empty());
}
#[test]
fn parses_a_quoted_display_name_with_escapes() {
let a = addr(br#""J Rosenberg \\\"" <sip:jdrosen@example.com>;tag=98asjd8"#);
assert_eq!(a.display_name.as_deref(), Some(&br#"J Rosenberg \""#[..]));
assert_eq!(a.tag(), Some(&b"98asjd8"[..]));
}
#[test]
fn parses_an_unquoted_token_display_name() {
let a = addr(b"J Rosenberg <sip:jdrosen@example.com>");
assert_eq!(a.display_name.as_deref(), Some(&b"J Rosenberg"[..]));
}
#[test]
fn parses_a_display_name_abutting_the_angle_bracket() {
let a = addr(br#""caller"<sip:caller@example.com>;tag=323"#);
assert_eq!(a.display_name.as_deref(), Some(&b"caller"[..]));
assert_eq!(a.tag(), Some(&b"323"[..]));
}
#[test]
fn semicolons_bind_to_the_header_without_brackets_and_to_the_uri_within_them() {
let bare = addr(b"sip:a@b.com;tag=1");
assert_eq!(bare.uri.to_bytes(), Bytes::from_static(b"sip:a@b.com"));
assert_eq!(bare.tag(), Some(&b"1"[..]));
let bracketed = addr(b"<sip:a@b.com;tag=1>");
assert_eq!(
bracketed.uri.to_bytes(),
Bytes::from_static(b"sip:a@b.com;tag=1")
);
assert!(bracketed.tag().is_none());
}
#[test]
fn a_quoted_parameter_value_may_contain_an_angle_bracket() {
let a = addr(br#"sip:a@b.com;x="<y>""#);
assert_eq!(a.uri.to_bytes(), Bytes::from_static(b"sip:a@b.com"));
assert_eq!(a.param("x"), Some(&b"<y>"[..]));
let a = addr(br#"sip:a@b.com;note="hi <there>""#);
assert_eq!(a.uri.to_bytes(), Bytes::from_static(b"sip:a@b.com"));
assert_eq!(a.param("note"), Some(&b"hi <there>"[..]));
let a = addr(br#"sip:a@b.com;x="a\"<b""#);
assert_eq!(a.param("x"), Some(&br#"a"<b"#[..]));
}
#[test]
fn rejects_an_unquoted_comma_in_a_display_name() {
let err = Address::parse(b"Bell, Alexander <sip:a.g.bell@example.com>", "From");
assert!(matches!(err, Err(HeaderError::Syntax { header: "From" })));
}
#[test]
fn rejects_an_unterminated_quoted_display_name() {
let err = Address::parse(br#""Mr. J. User <sip:j.user@example.com>"#, "To");
assert!(matches!(
err,
Err(HeaderError::UnterminatedQuotedString { header: "To" })
));
}
#[test]
fn rejects_spaces_within_the_addr_spec() {
let err = Address::parse(br#""Watson, Thomas" < sip:t.watson@example.org >"#, "To");
assert!(matches!(err, Err(HeaderError::Uri { .. })));
}
#[test]
fn rejects_an_unbracketed_question_mark_for_every_uri_scheme() {
for bare in [
b"sip:user@example.com?Route=%3Csip:sip.example.com%3E".as_slice(),
b"tel:+12015550123?x=y",
b"mailto:alice@example.com?subject=hello",
] {
assert!(matches!(
Address::parse(bare, "Contact"),
Err(HeaderError::Syntax { header: "Contact" })
));
}
for bracketed in [
b"<sip:user@example.com?Route=%3Csip:sip.example.com%3E>".as_slice(),
b"<mailto:alice@example.com?subject=hello>",
] {
assert!(Address::parse(bracketed, "Contact").is_ok());
}
}
#[test]
fn decodes_the_first_address_of_a_comma_separated_row() {
let c = Contact::decode(b"<sip:a@b.com>, <sip:c@d.com>").unwrap();
assert_eq!(c.uri.to_bytes(), Bytes::from_static(b"sip:a@b.com"));
let r = Route::decode(b"<sip:p1.example.com;lr>,<sip:p2.example.com;lr>").unwrap();
assert_eq!(
r.uri.to_bytes(),
Bytes::from_static(b"sip:p1.example.com;lr")
);
}
#[test]
fn parses_every_address_of_a_comma_separated_row() {
let list = Address::parse_list(
br#""Bell, Alexander" <sip:a@b.com>;q=0.7, <sip:c@d.com>"#,
"Contact",
)
.unwrap();
assert_eq!(list.len(), 2);
assert_eq!(
list[0].display_name.as_deref(),
Some(&b"Bell, Alexander"[..])
);
assert_eq!(list[0].param("q"), Some(&b"0.7"[..]));
assert_eq!(list[1].uri.to_bytes(), Bytes::from_static(b"sip:c@d.com"));
assert!(Address::parse_list(b"<sip:a@b.com>, not a uri", "Contact").is_err());
}
#[test]
fn wildcard_and_addresses_both_come_out_of_a_contact_list() {
let all = ContactValue::decode_list(b"*").unwrap();
assert!(matches!(all.as_slice(), [ContactValue::Wildcard]));
let all = ContactValue::decode_list(b"<sip:a@b.com>, <sip:c@d.com>").unwrap();
assert_eq!(all.len(), 2);
assert!(all.iter().all(|v| matches!(v, ContactValue::Address(_))));
}
#[test]
fn parses_the_wildcard_contact() {
assert!(matches!(
ContactValue::decode(b"*"),
Ok(ContactValue::Wildcard)
));
assert!(matches!(
ContactValue::decode(b"<sip:a@b>"),
Ok(ContactValue::Address(_))
));
}
}