Skip to main content

sip_header/
accept_encoding.rs

1//! SIP Accept-Encoding header parser (RFC 3261 ยง20.2).
2
3use std::fmt;
4
5/// A single Accept-Encoding entry: `encoding *(SEMI accept-param)`.
6#[derive(Debug, Clone, PartialEq, Eq)]
7#[non_exhaustive]
8pub struct SipAcceptEncodingEntry {
9    encoding: String,
10    params: Vec<(String, String)>,
11}
12
13impl SipAcceptEncodingEntry {
14    /// The content-coding value (e.g. `"gzip"`, `"identity"`, `"*"`).
15    pub fn encoding(&self) -> &str {
16        &self.encoding
17    }
18
19    /// All parameters as `(key, value)` pairs.
20    pub fn params(&self) -> &[(String, String)] {
21        &self.params
22    }
23
24    /// Look up a parameter by key (case-insensitive).
25    pub fn param(&self, key: &str) -> Option<&str> {
26        self.params
27            .iter()
28            .find(|(k, _)| k.eq_ignore_ascii_case(key))
29            .map(|(_, v)| v.as_str())
30    }
31
32    /// The `q` quality value, if present.
33    pub fn q(&self) -> Option<&str> {
34        self.param("q")
35    }
36}
37
38impl fmt::Display for SipAcceptEncodingEntry {
39    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40        write!(f, "{}", self.encoding)?;
41        for (key, value) in &self.params {
42            write!(f, ";{key}={value}")?;
43        }
44        Ok(())
45    }
46}
47
48/// Errors from parsing an Accept-Encoding header value.
49#[derive(Debug, Clone, PartialEq, Eq)]
50#[non_exhaustive]
51pub enum SipAcceptEncodingError {
52    /// The input string was empty or whitespace-only.
53    Empty,
54    /// An entry could not be parsed.
55    InvalidFormat(String),
56}
57
58impl fmt::Display for SipAcceptEncodingError {
59    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60        match self {
61            Self::Empty => write!(f, "empty Accept-Encoding header value"),
62            Self::InvalidFormat(raw) => write!(f, "invalid Accept-Encoding entry: {raw}"),
63        }
64    }
65}
66
67impl std::error::Error for SipAcceptEncodingError {}
68
69fn parse_entry(raw: &str) -> Result<SipAcceptEncodingEntry, SipAcceptEncodingError> {
70    let raw = raw.trim();
71    if raw.is_empty() {
72        return Err(SipAcceptEncodingError::InvalidFormat(raw.to_string()));
73    }
74
75    let (encoding_part, params_part) = match raw.split_once(';') {
76        Some((e, p)) => (e.trim(), Some(p)),
77        None => (raw, None),
78    };
79
80    if encoding_part.is_empty() {
81        return Err(SipAcceptEncodingError::InvalidFormat(raw.to_string()));
82    }
83
84    let encoding = encoding_part.to_ascii_lowercase();
85    let mut params = Vec::new();
86
87    if let Some(params_str) = params_part {
88        for segment in params_str.split(';') {
89            let segment = segment.trim();
90            if segment.is_empty() {
91                continue;
92            }
93            if let Some((key, value)) = segment.split_once('=') {
94                params.push((
95                    key.trim()
96                        .to_ascii_lowercase(),
97                    value
98                        .trim()
99                        .to_string(),
100                ));
101            } else {
102                params.push((segment.to_ascii_lowercase(), String::new()));
103            }
104        }
105    }
106
107    Ok(SipAcceptEncodingEntry { encoding, params })
108}
109
110/// Parsed SIP Accept-Encoding header value.
111#[derive(Debug, Clone, PartialEq, Eq)]
112#[non_exhaustive]
113pub struct SipAcceptEncoding(Vec<SipAcceptEncodingEntry>);
114
115impl SipAcceptEncoding {
116    /// Parse a comma-separated Accept-Encoding header value.
117    pub fn parse(raw: &str) -> Result<Self, SipAcceptEncodingError> {
118        let raw = raw.trim();
119        if raw.is_empty() {
120            return Err(SipAcceptEncodingError::Empty);
121        }
122        let entries: Vec<_> = crate::split_comma_entries(raw)
123            .into_iter()
124            .map(parse_entry)
125            .collect::<Result<_, _>>()?;
126        if entries.is_empty() {
127            return Err(SipAcceptEncodingError::Empty);
128        }
129        Ok(Self(entries))
130    }
131
132    /// The parsed entries as a slice.
133    pub fn entries(&self) -> &[SipAcceptEncodingEntry] {
134        &self.0
135    }
136
137    /// Consume self and return entries as a `Vec`.
138    pub fn into_entries(self) -> Vec<SipAcceptEncodingEntry> {
139        self.0
140    }
141
142    /// Number of entries.
143    pub fn len(&self) -> usize {
144        self.0
145            .len()
146    }
147
148    /// Returns `true` if there are no entries.
149    pub fn is_empty(&self) -> bool {
150        self.0
151            .is_empty()
152    }
153}
154
155impl fmt::Display for SipAcceptEncoding {
156    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157        crate::fmt_joined(f, &self.0, ", ")
158    }
159}
160
161impl_from_str_via_parse!(SipAcceptEncoding, SipAcceptEncodingError);
162
163impl<'a> IntoIterator for &'a SipAcceptEncoding {
164    type Item = &'a SipAcceptEncodingEntry;
165    type IntoIter = std::slice::Iter<'a, SipAcceptEncodingEntry>;
166
167    fn into_iter(self) -> Self::IntoIter {
168        self.0
169            .iter()
170    }
171}
172
173impl IntoIterator for SipAcceptEncoding {
174    type Item = SipAcceptEncodingEntry;
175    type IntoIter = std::vec::IntoIter<SipAcceptEncodingEntry>;
176
177    fn into_iter(self) -> Self::IntoIter {
178        self.0
179            .into_iter()
180    }
181}
182
183#[cfg(test)]
184mod tests {
185    use super::*;
186
187    #[test]
188    fn single_encoding() {
189        let ae = SipAcceptEncoding::parse("gzip").unwrap();
190        assert_eq!(ae.len(), 1);
191        assert_eq!(ae.entries()[0].encoding(), "gzip");
192    }
193
194    #[test]
195    fn multiple_encodings_with_q() {
196        let ae = SipAcceptEncoding::parse("gzip;q=1.0, identity;q=0.5").unwrap();
197        assert_eq!(ae.len(), 2);
198        assert_eq!(ae.entries()[0].q(), Some("1.0"));
199        assert_eq!(ae.entries()[1].encoding(), "identity");
200    }
201
202    #[test]
203    fn wildcard() {
204        let ae = SipAcceptEncoding::parse("*").unwrap();
205        assert_eq!(ae.entries()[0].encoding(), "*");
206    }
207
208    #[test]
209    fn empty_input() {
210        assert!(matches!(
211            SipAcceptEncoding::parse(""),
212            Err(SipAcceptEncodingError::Empty)
213        ));
214    }
215
216    #[test]
217    fn from_str() {
218        let ae: SipAcceptEncoding = "gzip"
219            .parse()
220            .unwrap();
221        assert_eq!(ae.len(), 1);
222    }
223
224    #[test]
225    fn display_roundtrip() {
226        let raw = "gzip;q=0.8";
227        let ae = SipAcceptEncoding::parse(raw).unwrap();
228        assert_eq!(ae.to_string(), raw);
229    }
230}