rouille_ng/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_ng::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                if 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
71            if req_elem_right == Some("*") || header_elem_right == Some("*") {
72                if req_elem_left == header_elem_left
73                    || req_elem_left == Some("*")
74                    || header_elem_left == Some("*")
75                {
76                    result = (Some(index), prio);
77                    continue;
78                }
79            }
80        }
81    }
82
83    result.0
84}
85
86/// Parses the value of a header that has values with priorities. Suitable for
87/// `Accept-*`, `TE`, etc.
88///
89/// # Example
90///
91/// ```
92/// use rouille_ng::input::parse_priority_header;
93///
94/// let mut iter = parse_priority_header("text/plain, image/png; q=1.5");
95///
96/// assert_eq!(iter.next().unwrap(), ("text/plain", 1.0));
97/// assert_eq!(iter.next().unwrap(), ("image/png", 1.5));
98/// assert_eq!(iter.next(), None);
99/// ```
100#[inline]
101pub fn parse_priority_header(input: &str) -> PriorityHeaderIter {
102    PriorityHeaderIter {
103        iter: input.split(','),
104    }
105}
106
107/// Iterator to the elements of a priority header.
108///
109/// Created with [`parse_priority_header`](fn.parse_priority_header.html).
110pub struct PriorityHeaderIter<'a> {
111    iter: Split<'a, char>,
112}
113
114impl<'a> Iterator for PriorityHeaderIter<'a> {
115    type Item = (&'a str, f32);
116
117    fn next(&mut self) -> Option<Self::Item> {
118        loop {
119            let elem = match self.iter.next() {
120                Some(n) => n,
121                None => return None,
122            };
123
124            let mut params = elem.split(';');
125
126            let t = match params.next() {
127                Some(t) => t.trim(),
128                None => continue,
129            };
130
131            let mut value = 1.0f32;
132
133            for p in params {
134                let trimmed_p = p.trim_start();
135                if trimmed_p.starts_with("q=") {
136                    if let Ok(val) = FromStr::from_str(&trimmed_p[2..].trim()) {
137                        value = val;
138                        break;
139                    }
140                }
141            }
142
143            return Some((t, value));
144        }
145    }
146
147    #[inline]
148    fn size_hint(&self) -> (usize, Option<usize>) {
149        let (_, len) = self.iter.size_hint();
150        (0, len)
151    }
152}
153
154#[cfg(test)]
155mod tests {
156    use super::parse_priority_header;
157    use super::priority_header_preferred;
158
159    #[test]
160    fn parse_basic() {
161        let mut iter = parse_priority_header("text/plain; q=1.5, */*");
162        assert_eq!(iter.next().unwrap(), ("text/plain", 1.5));
163        assert_eq!(iter.next().unwrap(), ("*/*", 1.0));
164        assert_eq!(iter.next(), None);
165    }
166
167    #[test]
168    fn parse_white_spaces() {
169        let mut iter = parse_priority_header("   text/plain   ;  q=   1.5  ,    */*   ");
170        assert_eq!(iter.next().unwrap(), ("text/plain", 1.5));
171        assert_eq!(iter.next().unwrap(), ("*/*", 1.0));
172        assert_eq!(iter.next(), None);
173    }
174
175    #[test]
176    fn preferred_basic() {
177        let header = "text/plain; q=1.2, image/png; q=2.0";
178        let handled = ["image/gif", "image/png", "text/plain"];
179        assert_eq!(
180            priority_header_preferred(header, handled.iter().cloned()),
181            Some(1)
182        );
183    }
184
185    #[test]
186    fn preferred_multimatch_first() {
187        let header = "text/plain";
188        let handled = ["text/plain", "text/plain"];
189        assert_eq!(
190            priority_header_preferred(header, handled.iter().cloned()),
191            Some(0)
192        );
193    }
194
195    #[test]
196    fn preferred_wildcard_header() {
197        let header = "text/plain; q=1.2, */*";
198        let handled = ["image/gif"];
199        assert_eq!(
200            priority_header_preferred(header, handled.iter().cloned()),
201            Some(0)
202        );
203    }
204
205    #[test]
206    fn preferred_wildcard_header_left() {
207        let header = "text/*; q=2.0, */*";
208        let handled = ["image/gif", "text/html"];
209        assert_eq!(
210            priority_header_preferred(header, handled.iter().cloned()),
211            Some(1)
212        );
213    }
214
215    #[test]
216    fn preferred_empty() {
217        let header = "*/*";
218        let handled = [];
219        assert_eq!(
220            priority_header_preferred(header, handled.iter().cloned()),
221            None
222        );
223    }
224}