1use bytes::Bytes;
5
6use crate::error::HeaderError;
7use crate::headers::grammar::{self, HeaderParam, is_token_char, parse_u64, skip_ws, trim};
8use crate::message::{Method, TypedHeader};
9use crate::name::HeaderName;
10
11#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct CSeq {
14 pub sequence: u32,
16 pub method: Method,
18}
19
20impl TypedHeader for CSeq {
21 const NAME: HeaderName = HeaderName::CSeq;
22
23 fn decode(value: &[u8]) -> Result<Self, HeaderError> {
24 let value = trim(value);
25 let space = value
26 .iter()
27 .position(|&b| matches!(b, b' ' | b'\t'))
28 .ok_or(HeaderError::Syntax { header: "CSeq" })?;
29 let digits = value.get(..space).unwrap_or(&[]);
30 let method_raw = trim(value.get(skip_ws(value, space)..).unwrap_or(&[]));
31
32 if method_raw.is_empty() || !method_raw.iter().all(|&b| is_token_char(b)) {
33 return Err(HeaderError::Syntax { header: "CSeq" });
34 }
35
36 let sequence = parse_u64(digits, "CSeq")?;
40 if sequence > u64::from(i32::MAX as u32) {
41 return Err(HeaderError::OutOfRange { header: "CSeq" });
42 }
43
44 Ok(Self {
45 sequence: u32::try_from(sequence)
46 .map_err(|_| HeaderError::OutOfRange { header: "CSeq" })?,
47 method: Method::parse(&Bytes::copy_from_slice(method_raw)),
48 })
49 }
50}
51
52#[derive(Debug, Clone, PartialEq, Eq, Hash)]
54pub struct CallId(pub Vec<u8>);
55
56impl TypedHeader for CallId {
57 const NAME: HeaderName = HeaderName::CallId;
58
59 fn decode(value: &[u8]) -> Result<Self, HeaderError> {
60 let value = trim(value);
61 if value.is_empty() {
62 return Err(HeaderError::Syntax { header: "Call-ID" });
63 }
64 Ok(Self(value.to_vec()))
65 }
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70pub struct MaxForwards(pub u8);
71
72impl TypedHeader for MaxForwards {
73 const NAME: HeaderName = HeaderName::MaxForwards;
74
75 fn decode(value: &[u8]) -> Result<Self, HeaderError> {
76 let n = parse_u64(trim(value), "Max-Forwards")?;
77 u8::try_from(n)
78 .map(Self)
79 .map_err(|_| HeaderError::OutOfRange {
80 header: "Max-Forwards",
81 })
82 }
83}
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
87pub struct Expires(pub u32);
88
89impl TypedHeader for Expires {
90 const NAME: HeaderName = HeaderName::Expires;
91
92 fn decode(value: &[u8]) -> Result<Self, HeaderError> {
93 let n = parse_u64(trim(value), "Expires")?;
94 u32::try_from(n)
95 .map(Self)
96 .map_err(|_| HeaderError::OutOfRange { header: "Expires" })
97 }
98}
99
100#[derive(Debug, Clone, Copy, PartialEq, Eq)]
102pub struct ContentLength(pub u64);
103
104impl TypedHeader for ContentLength {
105 const NAME: HeaderName = HeaderName::ContentLength;
106
107 fn decode(value: &[u8]) -> Result<Self, HeaderError> {
108 parse_u64(trim(value), "Content-Length").map(Self)
109 }
110}
111
112#[derive(Debug, Clone, PartialEq, Eq)]
114pub struct ContentType {
115 pub media_type: Vec<u8>,
117 pub subtype: Vec<u8>,
119 pub params: Vec<HeaderParam>,
121}
122
123impl ContentType {
124 #[must_use]
126 pub fn is(&self, media_type: &str, subtype: &str) -> bool {
127 self.media_type == media_type.as_bytes() && self.subtype == subtype.as_bytes()
128 }
129
130 #[must_use]
132 pub fn param(&self, name: &str) -> Option<&[u8]> {
133 grammar::param(&self.params, name).and_then(|p| p.value.as_deref())
134 }
135}
136
137impl TypedHeader for ContentType {
138 const NAME: HeaderName = HeaderName::ContentType;
139
140 fn decode(value: &[u8]) -> Result<Self, HeaderError> {
141 const LABEL: &str = "Content-Type";
142 let value = trim(value);
143 let (before_params, tail) = match grammar::find_param_start(value) {
144 Some(semi) => (
145 value.get(..semi).unwrap_or(&[]),
146 value.get(semi..).unwrap_or(&[]),
147 ),
148 None => (value, &[][..]),
149 };
150 let slash = before_params
151 .iter()
152 .position(|&b| b == b'/')
153 .ok_or(HeaderError::Syntax { header: LABEL })?;
154 let media_type = trim(before_params.get(..slash).unwrap_or(&[]));
155 let subtype = trim(before_params.get(slash + 1..).unwrap_or(&[]));
156
157 if media_type.is_empty()
158 || subtype.is_empty()
159 || !media_type.iter().all(|&b| is_token_char(b))
160 || !subtype.iter().all(|&b| is_token_char(b))
161 {
162 return Err(HeaderError::Syntax { header: LABEL });
163 }
164
165 Ok(Self {
166 media_type: media_type.to_ascii_lowercase(),
167 subtype: subtype.to_ascii_lowercase(),
168 params: grammar::parse_params(trim(tail), LABEL)?,
169 })
170 }
171}
172
173#[derive(Debug, Clone, PartialEq, Eq)]
179pub struct Date(pub Vec<u8>);
180
181impl TypedHeader for Date {
182 const NAME: HeaderName = HeaderName::Date;
183
184 fn decode(value: &[u8]) -> Result<Self, HeaderError> {
185 const LABEL: &str = "Date";
186 const WKDAYS: [&[u8]; 7] = [b"Mon", b"Tue", b"Wed", b"Thu", b"Fri", b"Sat", b"Sun"];
190 const MONTHS: [&[u8]; 12] = [
191 b"Jan", b"Feb", b"Mar", b"Apr", b"May", b"Jun", b"Jul", b"Aug", b"Sep", b"Oct", b"Nov",
192 b"Dec",
193 ];
194 let value = trim(value);
195
196 if value.len() != 29 {
199 return Err(HeaderError::Syntax { header: LABEL });
200 }
201 let field = |from: usize, to: usize| value.get(from..to).unwrap_or(&[]);
202 if field(3, 5) != b", "
203 || field(7, 8) != b" "
204 || field(11, 12) != b" "
205 || field(16, 17) != b" "
206 || field(19, 20) != b":"
207 || field(22, 23) != b":"
208 || field(25, 26) != b" "
209 || field(26, 29) != b"GMT"
210 {
211 return Err(HeaderError::Syntax { header: LABEL });
212 }
213 if !WKDAYS.contains(&field(0, 3))
214 || !MONTHS.contains(&field(8, 11))
215 || !field(12, 16).iter().all(u8::is_ascii_digit)
216 {
217 return Err(HeaderError::Syntax { header: LABEL });
218 }
219
220 let day = two_digits(value, 5).ok_or(HeaderError::Syntax { header: LABEL })?;
221 let hour = two_digits(value, 17).ok_or(HeaderError::Syntax { header: LABEL })?;
222 let minute = two_digits(value, 20).ok_or(HeaderError::Syntax { header: LABEL })?;
223 let second = two_digits(value, 23).ok_or(HeaderError::Syntax { header: LABEL })?;
224 if !(1..=31).contains(&day) || hour > 23 || minute > 59 || second > 59 {
227 return Err(HeaderError::OutOfRange { header: LABEL });
228 }
229
230 Ok(Self(value.to_vec()))
231 }
232}
233
234#[must_use]
236fn two_digits(value: &[u8], at: usize) -> Option<u8> {
237 let hi = value.get(at)?.checked_sub(b'0')?;
238 let lo = value.get(at + 1)?.checked_sub(b'0')?;
239 (hi <= 9 && lo <= 9).then_some(hi * 10 + lo)
240}
241
242#[derive(Debug, Clone, PartialEq, Eq)]
245pub struct TokenList(pub Vec<Vec<u8>>);
246
247impl TokenList {
248 fn decode_named(
249 value: &[u8],
250 header: &'static str,
251 may_be_empty: bool,
252 ) -> Result<Self, HeaderError> {
253 let mut tokens = Vec::new();
254 for part in grammar::split_list(value, header)? {
255 let token = trim(part);
256 if token.is_empty() {
261 if may_be_empty && grammar::split_list(value, header)?.len() == 1 {
262 return Ok(Self(Vec::new()));
263 }
264 return Err(HeaderError::Syntax { header });
265 }
266 if !token.iter().all(|&b| is_token_char(b)) {
267 return Err(HeaderError::Syntax { header });
268 }
269 tokens.push(token.to_vec());
270 }
271 Ok(Self(tokens))
272 }
273
274 #[must_use]
276 pub fn contains(&self, token: &str) -> bool {
277 self.0
278 .iter()
279 .any(|t| t.eq_ignore_ascii_case(token.as_bytes()))
280 }
281}
282
283macro_rules! token_list_header {
284 ($(#[$meta:meta])* $type:ident => $variant:ident, $label:literal, $may_be_empty:literal) => {
285 $(#[$meta])*
286 #[derive(Debug, Clone, PartialEq, Eq)]
287 pub struct $type(pub TokenList);
288
289 impl std::ops::Deref for $type {
290 type Target = TokenList;
291 fn deref(&self) -> &TokenList {
292 &self.0
293 }
294 }
295
296 impl TypedHeader for $type {
297 const NAME: HeaderName = HeaderName::$variant;
298
299 fn decode(value: &[u8]) -> Result<Self, HeaderError> {
300 TokenList::decode_named(value, $label, $may_be_empty).map(Self)
301 }
302 }
303 };
304}
305
306token_list_header!(
307 Allow => Allow, "Allow", true
309);
310token_list_header!(
311 Supported => Supported, "Supported", true
313);
314token_list_header!(
315 Require => Require, "Require", false
317);
318token_list_header!(
319 ProxyRequire => ProxyRequire, "Proxy-Require", false
321);
322token_list_header!(
323 Unsupported => Unsupported, "Unsupported", false
325);
326
327#[cfg(test)]
328#[allow(
329 clippy::unwrap_used,
330 clippy::expect_used,
331 clippy::panic,
332 clippy::indexing_slicing
333)]
334mod tests {
335 use super::*;
336
337 #[test]
338 fn cseq_parses_number_and_method() {
339 let c = CSeq::decode(b"8 INVITE").unwrap();
340 assert_eq!(c.sequence, 8);
341 assert_eq!(c.method, Method::Invite);
342 }
343
344 #[test]
347 fn cseq_accepts_leading_zeros_and_extra_whitespace() {
348 let c = CSeq::decode(b"0009 INVITE").unwrap();
349 assert_eq!(c.sequence, 9);
350 }
351
352 #[test]
354 fn cseq_rejects_overlarge_sequence_numbers() {
355 assert!(matches!(
356 CSeq::decode(b"2147483648 INVITE"),
357 Err(HeaderError::OutOfRange { header: "CSeq" })
358 ));
359 assert!(matches!(
360 CSeq::decode(b"9292394834772304023312 OPTIONS"),
361 Err(HeaderError::OutOfRange { header: "CSeq" })
362 ));
363 assert_eq!(
365 CSeq::decode(b"2147483647 INVITE").unwrap().sequence,
366 i32::MAX as u32
367 );
368 }
369
370 #[test]
371 fn cseq_rejects_a_missing_or_non_token_method() {
372 assert!(CSeq::decode(b"8").is_err());
373 assert!(CSeq::decode(b"8 IN VITE").is_err());
374 assert!(CSeq::decode(b"x INVITE").is_err());
375 }
376
377 #[test]
378 fn max_forwards_is_bounded_at_255() {
379 assert_eq!(MaxForwards::decode(b"0068").unwrap().0, 68);
380 assert_eq!(MaxForwards::decode(b"0").unwrap().0, 0);
381 assert!(matches!(
382 MaxForwards::decode(b"256"),
383 Err(HeaderError::OutOfRange { .. })
384 ));
385 }
386
387 #[test]
388 fn content_type_lowercases_and_keeps_parameters() {
389 let ct = ContentType::decode(b"Application/SDP").unwrap();
390 assert!(ct.is("application", "sdp"));
391
392 let ct = ContentType::decode(b"multipart/mixed;boundary=unique-boundary-1").unwrap();
393 assert_eq!(ct.param("boundary"), Some(&b"unique-boundary-1"[..]));
394 }
395
396 #[test]
397 fn content_type_rejects_a_missing_subtype() {
398 assert!(ContentType::decode(b"application").is_err());
399 assert!(ContentType::decode(b"application/").is_err());
400 assert!(ContentType::decode(b"/sdp").is_err());
401 }
402
403 #[test]
405 fn date_requires_gmt() {
406 assert!(Date::decode(b"Fri, 01 Jan 2010 16:00:00 GMT").is_ok());
407 assert!(Date::decode(b"Fri, 01 Jan 2010 16:00:00 EST").is_err());
408 assert!(Date::decode(b"Fri, 01 Jan 2010 16:00:00").is_err());
409 assert!(Date::decode(b"nonsense GMT").is_err());
410 }
411
412 #[test]
416 fn date_validates_every_field_not_just_the_shape() {
417 for bad in [
418 &b"aaa, aaaaaaaaaaaaaaaaaaaa GMT"[..],
419 b"Fri, 32 Jan 2010 25:99:99 GMT",
420 b"Xyz, 01 Jan 2010 16:00:00 GMT",
421 b"Fri, 00 Jan 2010 16:00:00 GMT",
422 b"Fri, 01 Foo 2010 16:00:00 GMT",
423 b"Fri, 01 Jan 2010 24:00:00 GMT",
424 b"Fri, 01 Jan 2010 16:60:00 GMT",
425 b"Fri, 01 Jan 2010 16:00:60 GMT",
426 b"Fri, 01 Jan x010 16:00:00 GMT",
427 b"Fri, 01 Jan 2010 16.00.00 GMT",
428 ] {
429 assert!(
430 Date::decode(bad).is_err(),
431 "{:?} should be rejected",
432 String::from_utf8_lossy(bad)
433 );
434 }
435 for good in [
436 &b"Mon, 01 Jan 2010 00:00:00 GMT"[..],
437 b"Sat, 13 Nov 2010 23:29:00 GMT",
438 b"Sun, 31 Dec 2699 23:59:59 GMT",
439 ] {
440 assert!(
441 Date::decode(good).is_ok(),
442 "{:?} should parse",
443 String::from_utf8_lossy(good)
444 );
445 }
446 }
447
448 #[test]
449 fn token_lists_split_and_compare_case_insensitively() {
450 let allow = Allow::decode(b"INVITE, ACK, OPTIONS, CANCEL, BYE").unwrap();
451 assert_eq!(allow.0.0.len(), 5);
452 assert!(allow.contains("invite"));
453 assert!(!allow.contains("REFER"));
454
455 assert_eq!(Supported::decode(b"").unwrap().0.0.len(), 0);
457 assert!(Supported::decode(b"100rel,,timer").is_err());
459 }
460
461 #[test]
465 fn only_allow_and_supported_may_be_empty() {
466 assert!(Allow::decode(b"").is_ok());
467 assert!(Supported::decode(b"").is_ok());
468
469 assert!(Require::decode(b"").is_err());
470 assert!(ProxyRequire::decode(b"").is_err());
471 assert!(Unsupported::decode(b"").is_err());
472
473 assert!(Require::decode(b"100rel").is_ok());
475 assert!(ProxyRequire::decode(b"sec-agree").is_ok());
476 assert!(Unsupported::decode(b"foo, bar").is_ok());
477 }
478
479 #[test]
480 fn call_id_is_opaque_but_not_empty() {
481 assert_eq!(
482 CallId::decode(b"wsinv.ndaksdj@192.0.2.1").unwrap().0,
483 b"wsinv.ndaksdj@192.0.2.1"
484 );
485 assert!(CallId::decode(b" ").is_err());
486 }
487}