use std::fmt::{Debug, Display};
use sip_header::{
ContactList, HistoryInfo, ListParse, ParseError, SipAccept, SipAcceptEncoding,
SipAcceptLanguage, SipAuthValue, SipCallId, SipGeolocation, SipHeader, SipHeaderAddr,
SipHeaderAddrList, SipHeaderField, SipHeaderFieldRows, SipHeaderFields, SipHeaderLookup,
SipHeaderRows, SipJoin, SipReasonList, SipReplaces, SipSecurity, SipTargetDialog, SipVia,
SipWarning, TokenList, TypedHeader, UriInfo, WarningCode,
};
type R = Result<(), ParseError>;
fn message() -> SipHeaderFields<'static> {
SipHeaderFields::from(vec![
("VIA", "SIP/2.0/UDP 198.51.100.1;branch=z9hG4bK1"),
("f", r#""Alice" <sip:alice@example.com>;tag=a1"#),
("v", "SIP/2.0/TCP 203.0.113.5;branch=z9hG4bK2"),
("TO", "<sip:bob@example.com>"),
("i", "a84b4c76e66710@example.com"),
("Via", "SIP/2.0/UDP 198.51.100.3;branch=z9hG4bK3"),
("r", "<sip:carol@example.com>"),
("b", "<sip:alice@example.com>"),
("reason", "SIP;cause=200, Q.850;cause=16"),
("call-info", "<https://example.com/a>;purpose=icon"),
("alert-info", "<https://example.com/ring>"),
("ERROR-INFO", "<sip:not-in-service@example.com>"),
(
"history-info",
"<sip:a@example.com>;index=1, <sip:b@example.com>;index=1.1",
),
(
"p-asserted-identity",
"<sip:+15551234567@example.com>, <tel:+15551234567>",
),
("P-Preferred-Identity", "<sip:alice@example.com>"),
("route", "<sip:p1.example.com;lr>"),
("Record-Route", "<sip:p2.example.com;lr>"),
("path", "<sip:p3.example.com;lr>"),
("service-route", "<sip:p4.example.com;lr>"),
(
"diversion",
"<sip:+15557654321@example.com>;reason=unconditional",
),
(
"remote-party-id",
"<sip:+15551234567@example.com>;party=calling",
),
("m", "<sip:alice@198.51.100.1>;expires=60"),
("allow", "INVITE, ACK"),
("k", "timer, 100rel"),
("REQUIRE", "100rel"),
("proxy-require", "sec-agree"),
("unsupported", "foo"),
("u", "presence"),
("e", "gzip"),
("content-language", "fr"),
("in-reply-to", "70710@example.com"),
("replaces", "abc@203.0.113.5;to-tag=t1;from-tag=f1"),
("join", "abc@203.0.113.5;to-tag=t1;from-tag=f1"),
("target-dialog", "abc@203.0.113.5;local-tag=l;remote-tag=r"),
(
"authorization",
r#"Digest username="a", realm="example.com""#,
),
("Proxy-Authorization", "Bearer mF_9.B5f-4.1JqM"),
(
"www-authenticate",
r#"Digest realm="example.com", nonce="n""#,
),
(
"proxy-authenticate",
r#"Digest realm="example.org", nonce="m""#,
),
("warning", r#"399 example.com "say hi""#),
("security-client", "digest;d-qop=auth"),
("Security-Server", "tls;q=0.1"),
("security-verify", "digest;d-alg=md5"),
("accept", "application/sdp"),
("accept-encoding", "gzip"),
("accept-language", "fr-ca"),
("geolocation", "<cid:abc@example.com>"),
("v", "SIP/2.0/TLS 203.0.113.9;branch=z9hG4bK4"),
])
}
macro_rules! all_present {
($m:expr; $($accessor:ident),+ $(,)?) => {$(
assert!($m.$accessor()?.is_some(), stringify!($accessor));
)+};
}
#[test]
fn every_accessor_reads_a_holder() -> R {
let m = message();
all_present!(m;
sip_from, sip_to, call_id, refer_to, referred_by, reason, call_info, history_info,
p_asserted_identity, p_preferred_identity, route, record_route, path, service_route,
diversion, remote_party_id, contact, alert_info, error_info, allow, supported, require,
proxy_require, unsupported, allow_events, content_encoding, content_language, in_reply_to,
via, replaces, join, target_dialog, authorization, proxy_authorization, www_authenticate,
proxy_authenticate, warning, security_client, security_server, security_verify, accept,
accept_encoding, accept_language, geolocation,
);
let via = m
.via()?
.unwrap();
assert_eq!(
via.entries()
.iter()
.map(|e| e.branch())
.collect::<Vec<_>>(),
[
Some("z9hG4bK1"),
Some("z9hG4bK2"),
Some("z9hG4bK3"),
Some("z9hG4bK4")
]
);
assert_eq!(
m.sip_from()?
.unwrap()
.tag(),
Some("a1")
);
assert_eq!(
m.p_asserted_identity()?
.unwrap()
.len(),
2
);
assert_eq!(
m.authorization()?
.unwrap()
.len(),
1
);
Ok(())
}
#[test]
fn a_field_is_a_store_for_its_own_header() -> R {
let field = SipHeaderField::new(
"v",
vec![
"SIP/2.0/UDP 198.51.100.1;branch=z9hG4bK1",
"SIP/2.0/TCP 203.0.113.5;branch=z9hG4bK2",
],
);
assert_eq!(
field
.via()?
.unwrap()
.len(),
2
);
assert_eq!(field.sip_from()?, None);
let rows: SipHeaderFieldRows<'_> = field.rows();
assert_eq!(SipVia::from_rows(rows)?.len(), 2);
Ok(())
}
fn hostile() -> SipHeaderFields<'static> {
SipHeaderFields::from(vec![
("f", "Alice\0 <sip:alice@example.com>;tag=a\r\nb"),
("T", "\"Bob\r\n\" <sip:bob@example.com>;tag=b\"c"),
("i", "a84b\0c@example.com\r\n"),
("r", "<sip:carol@example.com>;x=\"1\r\n"),
("b", "<sip:alice@example.com>\0"),
("Reason", "SIP;cause=200;text=\"else\r\nwhere\""),
("call-info", "<https://example.com/a>;purpose=icon\0"),
("Alert-Info", "<https://example.com/b>\r\n"),
("Error-Info", "<https://example.com/c>;x=\"\0"),
(
"History-Info",
"<sip:a@example.com>;index=1\r\n, <sip:b@example.com>;index=1.1",
),
(
"P-Asserted-Identity",
"\"A\0\" <sip:+15551234567@example.com>",
),
("P-Preferred-Identity", "<sip:alice@example.com>\r\n"),
(
"Route",
"<sip:p1.example.com;lr>\r\n, <sip:p2.example.com;lr>",
),
("Record-Route", "<sip:p2.example.com;lr>\0"),
("Path", "\"<sip:p3.example.com;lr>\r\n"),
("Service-Route", "<sip:p4.example.com;lr>;x=\"\0"),
(
"Diversion",
"<sip:+15557654321@example.com>;reason=\"a\rb\"",
),
(
"Remote-Party-ID",
"<sip:+15551234567@example.com>;party=calling\n",
),
("m", "<sip:a@example.com>;expires=60\0"),
("Allow", "INVITE\r\n, ACK"),
("k", "timer\0, 100rel"),
("Require", "100rel\r\n"),
("Proxy-Require", "sec-agree\0"),
("Unsupported", "fo\"o"),
("Allow-Events", "presence\r\n"),
("e", "gzip\0"),
("Content-Language", "fr\r\n"),
("In-Reply-To", "70710@example.com\0"),
("v", "SIP/2.0/UDP 198.51.100.1;branch=z9hG4bK1\r\n"),
("VIA", "SIP/2.0/TCP 203.0.113.5\0;branch=z9hG4bK2"),
("Replaces", "abc@203.0.113.5;to-tag=t1\0;from-tag=f1"),
("Join", "abc@203.0.113.5;to-tag=t1;from-tag=f1\r\n"),
(
"Target-Dialog",
"abc@203.0.113.5\0;local-tag=l;remote-tag=r",
),
(
"Authorization",
"Digest username=\"a\0\", realm=\"example.com\"",
),
("Proxy-Authorization", "Bearer abc\r\n"),
(
"WWW-Authenticate",
"Digest realm=\"example.com\", nonce=\"x\0\"",
),
("Proxy-Authenticate", "Digest realm=\"example.com\r\n\""),
("Warning", "399 example.com \"say\0 hi\""),
("Security-Client", "digest;d-qop=auth\r\n"),
("Security-Server", "tls;q=0.1\0"),
("Security-Verify", "digest;d-alg=\"md5\0\""),
("Accept", "application/sdp\0"),
("Accept-Encoding", "gzip\r\n"),
("Accept-Language", "fr-ca\0"),
("Geolocation", "<cid:abc@example.com>\r\n"),
("X Bad\r\n", "<sip:x@example.com>"),
("Via\0", "SIP/2.0/UDP 198.51.100.9"),
])
}
fn clean_or_refused<T>(
fields: &SipHeaderFields<'static>,
header: SipHeader,
lenient: Result<Option<T>, ParseError>,
print: impl Fn(&T) -> String,
) where
T: TypedHeader + PartialEq + Debug,
{
let strict = fields.parse_header_strict::<T>(header);
assert!(strict.is_err(), "{header}: strict accepted {strict:?}");
match fields.parse_header::<T>(header) {
Ok(Some(parsed)) => {
let wire = print(&parsed.value);
assert!(!wire.contains(['\r', '\n', '\0']), "{header}: {wire:?}");
assert!(
parsed
.warnings
.iter()
.any(|w| matches!(
w.code,
WarningCode::ControlChar | WarningCode::StrayDelimiter
)),
"{header}: {:?}",
parsed.warnings
);
assert_eq!(lenient, Ok(Some(parsed.value)), "{header}");
}
Ok(None) => panic!("{header}: absent"),
Err(e) => assert_eq!(lenient, Err(e), "{header}"),
}
}
fn shown<T: Display>(value: &T) -> String {
value.to_string()
}
macro_rules! hostile_accessors {
($m:expr; $($accessor:ident: $Type:ty => $header:ident),+ $(,)?) => {$(
clean_or_refused::<$Type>(&$m, SipHeader::$header, $m.$accessor(), shown);
)+};
}
#[test]
fn hostile_rows_through_a_holder_come_out_clean_or_refused() {
let m = hostile();
hostile_accessors!(m;
sip_from: SipHeaderAddr => From,
sip_to: SipHeaderAddr => To,
call_id: SipCallId => CallId,
refer_to: SipHeaderAddr => ReferTo,
referred_by: SipHeaderAddr => ReferredBy,
reason: SipReasonList => Reason,
call_info: UriInfo => CallInfo,
alert_info: UriInfo => AlertInfo,
error_info: UriInfo => ErrorInfo,
history_info: HistoryInfo => HistoryInfo,
p_asserted_identity: SipHeaderAddrList => PAssertedIdentity,
p_preferred_identity: SipHeaderAddrList => PPreferredIdentity,
route: SipHeaderAddrList => Route,
record_route: SipHeaderAddrList => RecordRoute,
path: SipHeaderAddrList => Path,
service_route: SipHeaderAddrList => ServiceRoute,
diversion: SipHeaderAddrList => Diversion,
remote_party_id: SipHeaderAddrList => RemotePartyId,
contact: ContactList => Contact,
allow: TokenList => Allow,
supported: TokenList => Supported,
require: TokenList => Require,
proxy_require: TokenList => ProxyRequire,
unsupported: TokenList => Unsupported,
allow_events: TokenList => AllowEvents,
content_encoding: TokenList => ContentEncoding,
content_language: TokenList => ContentLanguage,
in_reply_to: TokenList => InReplyTo,
via: SipVia => Via,
replaces: SipReplaces => Replaces,
join: SipJoin => Join,
target_dialog: SipTargetDialog => TargetDialog,
warning: SipWarning => Warning,
security_client: SipSecurity => SecurityClient,
security_server: SipSecurity => SecurityServer,
security_verify: SipSecurity => SecurityVerify,
accept: SipAccept => Accept,
accept_encoding: SipAcceptEncoding => AcceptEncoding,
accept_language: SipAcceptLanguage => AcceptLanguage,
geolocation: SipGeolocation => Geolocation,
);
let auth = |values: &Vec<SipAuthValue>| {
values
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join(" | ")
};
clean_or_refused(&m, SipHeader::Authorization, m.authorization(), auth);
clean_or_refused(
&m,
SipHeader::ProxyAuthorization,
m.proxy_authorization(),
auth,
);
clean_or_refused(&m, SipHeader::WwwAuthenticate, m.www_authenticate(), auth);
clean_or_refused(
&m,
SipHeader::ProxyAuthenticate,
m.proxy_authenticate(),
auth,
);
}
#[test]
fn a_name_that_is_no_token_is_held_and_matches_only_itself() -> R {
let m = hostile();
assert_eq!(
m.via()?
.unwrap()
.len(),
2
);
assert_eq!(m.sip_header_rows_str("x bad\r\n")?, ["<sip:x@example.com>"]);
assert_eq!(
m.sip_header_rows_str("Via\0")?,
["SIP/2.0/UDP 198.51.100.9"]
);
Ok(())
}