rouille/input/
priority_header.rs

1// Copyright (c) 2016 The Rouille developers
2// Licensed under the Apache License, Version 2.0
3// <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT
5// license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
6// at your option. All files in the project carrying such
7// notice may not be copied, modified, or distributed except
8// according to those terms.
9
10use std::f32;
11use std::str::FromStr;
12use std::str::Split;
13
14/// Returns the preferred value amongst a priority header.
15///
16/// This function takes the value of a priority header and a list of elements that can be handled
17/// by the server, and returns the index within that list of the element with the highest priority.
18///
19/// If multiple elements in `handled` match and have the same priority, the first one is returned.
20///
21/// # Example
22///
23/// ```
24/// use rouille::input::priority_header_preferred;
25///
26/// let header = "text/plain; q=1.2, image/png; q=2.0";
27/// let handled = ["image/gif", "image/png", "text/plain"];
28/// assert_eq!(priority_header_preferred(header, handled.iter().cloned()), Some(1));
29/// ```
30pub fn priority_header_preferred<'a, I>(input: &'a str, elements: I) -> Option<usize>
31where
32    I: Iterator<Item = &'a str>,
33{
34    let mut result = (None, f32::NEG_INFINITY);
35
36    for (index, req_elem) in elements.enumerate() {
37        for (header_elem, prio) in parse_priority_header(input) {
38            if prio <= result.1 {
39                continue;
40            }
41
42            if req_elem == header_elem {
43                result = (Some(index), prio);
44                continue;
45            }
46
47            let (req_elem_left, req_elem_right) = {
48                let mut parts = req_elem.split('/');
49                let left = parts.next();
50                let right = parts.next();
51                (left, right)
52            };
53
54            let (header_elem_left, header_elem_right) = {
55                let mut parts = header_elem.split('/');
56                let left = parts.next();
57                let right = parts.next();
58                (left, right)
59            };
60
61            if (req_elem_left == Some("*") || header_elem_left == Some("*"))
62                && (req_elem_right == header_elem_right
63                    || req_elem_right == Some("*")
64                    || header_elem_right == Some("*"))
65            {
66                result = (Some(index), prio);
67                continue;
68            }
69
70            if (req_elem_right == Some("*") || header_elem_right == Some("*"))
71                && (req_elem_left == header_elem_left
72                    || req_elem_left == Some("*")
73                    || header_elem_left == Some("*"))
74            {
75                result = (Some(index), prio);
76                continue;
77            }
78        }
79    }
80
81    result.0
82}
83
84/// Parses the value of a header that has values with priorities. Suitable for
85/// `Accept-*`, `TE`, etc.
86///
87/// # Example
88///
89/// ```
90/// use rouille::input::parse_priority_header;
91///
92/// let mut iter = parse_priority_header("text/plain, image/png; q=1.5");
93///
94/// assert_eq!(iter.next().unwrap(), ("text/plain", 1.0));
95/// assert_eq!(iter.next().unwrap(), ("image/png", 1.5));
96/// assert_eq!(iter.next(), None);
97/// ```
98#[inline]
99pub fn parse_priority_header(input: &str) -> PriorityHeaderIter {
100    PriorityHeaderIter {
101        iter: input.split(','),
102    }
103}
104
105/// Iterator to the elements of a priority header.
106///
107/// Created with [`parse_priority_header`](fn.parse_priority_header.html).
108pub struct PriorityHeaderIter<'a> {
109    iter: Split<'a, char>,
110}
111
112impl<'a> Iterator for PriorityHeaderIter<'a> {
113    type Item = (&'a str, f32);
114
115    fn next(&mut self) -> Option<Self::Item> {
116        loop {
117            let elem = match self.iter.next() {
118                Some(n) => n,
119                None => return None,
120            };
121
122            let mut params = elem.split(';');
123
124            let t = match params.next() {
125                Some(t) => t.trim(),
126                None => continue,
127            };
128
129            let mut value = 1.0f32;
130
131            for p in params {
132                let trimmed_p = p.trim_start();
133                if let Some(stripped) = trimmed_p.strip_prefix("q=") {
134                    if let Ok(val) = FromStr::from_str(stripped.trim()) {
135                        value = val;
136                        break;
137                    }
138                }
139            }
140
141            return Some((t, value));
142        }
143    }
144
145    #[inline]
146    fn size_hint(&self) -> (usize, Option<usize>) {
147        let (_, len) = self.iter.size_hint();
148        (0, len)
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    use super::parse_priority_header;
155    use super::priority_header_preferred;
156
157    #[test]
158    fn parse_basic() {
159        let mut iter = parse_priority_header("text/plain; q=1.5, */*");
160        assert_eq!(iter.next().unwrap(), ("text/plain", 1.5));
161        assert_eq!(iter.next().unwrap(), ("*/*", 1.0));
162        assert_eq!(iter.next(), None);
163    }
164
165    #[test]
166    fn parse_white_spaces() {
167        let mut iter = parse_priority_header("   text/plain   ;  q=   1.5  ,    */*   ");
168        assert_eq!(iter.next().unwrap(), ("text/plain", 1.5));
169        assert_eq!(iter.next().unwrap(), ("*/*", 1.0));
170        assert_eq!(iter.next(), None);
171    }
172
173    #[test]
174    fn preferred_basic() {
175        let header = "text/plain; q=1.2, image/png; q=2.0";
176        let handled = ["image/gif", "image/png", "text/plain"];
177        assert_eq!(
178            priority_header_preferred(header, handled.iter().cloned()),
179            Some(1)
180        );
181    }
182
183    #[test]
184    fn preferred_multimatch_first() {
185        let header = "text/plain";
186        let handled = ["text/plain", "text/plain"];
187        assert_eq!(
188            priority_header_preferred(header, handled.iter().cloned()),
189            Some(0)
190        );
191    }
192
193    #[test]
194    fn preferred_wildcard_header() {
195        let header = "text/plain; q=1.2, */*";
196        let handled = ["image/gif"];
197        assert_eq!(
198            priority_header_preferred(header, handled.iter().cloned()),
199            Some(0)
200        );
201    }
202
203    #[test]
204    fn preferred_wildcard_header_left() {
205        let header = "text/*; q=2.0, */*";
206        let handled = ["image/gif", "text/html"];
207        assert_eq!(
208            priority_header_preferred(header, handled.iter().cloned()),
209            Some(1)
210        );
211    }
212
213    #[test]
214    fn preferred_empty() {
215        let header = "*/*";
216        let handled = [];
217        assert_eq!(
218            priority_header_preferred(header, handled.iter().cloned()),
219            None
220        );
221    }
222}