actix_http/header/shared/
quality_item.rs1use std::{cmp, fmt, str};
2
3use super::Quality;
4use crate::error::ParseError;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub struct QualityItem<T> {
35 pub item: T,
37
38 pub quality: Quality,
40}
41
42impl<T> QualityItem<T> {
43 pub fn new(item: T, quality: Quality) -> Self {
47 QualityItem { item, quality }
48 }
49
50 pub fn max(item: T) -> Self {
52 Self::new(item, Quality::MAX)
53 }
54
55 pub fn min(item: T) -> Self {
57 Self::new(item, Quality::MIN)
58 }
59
60 pub fn zero(item: T) -> Self {
62 Self::new(item, Quality::ZERO)
63 }
64}
65
66impl<T: PartialEq> PartialOrd for QualityItem<T> {
67 fn partial_cmp(&self, other: &QualityItem<T>) -> Option<cmp::Ordering> {
68 self.quality.partial_cmp(&other.quality)
69 }
70}
71
72impl<T: fmt::Display> fmt::Display for QualityItem<T> {
73 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74 fmt::Display::fmt(&self.item, f)?;
75
76 match self.quality {
77 Quality::MAX => Ok(()),
79
80 Quality::ZERO => f.write_str("; q=0"),
82
83 q => write!(f, "; q={}", q),
85 }
86 }
87}
88
89impl<T: str::FromStr> str::FromStr for QualityItem<T> {
90 type Err = ParseError;
91
92 fn from_str(q_item_str: &str) -> Result<Self, Self::Err> {
93 if !q_item_str.is_ascii() {
94 return Err(ParseError::Header);
95 }
96
97 let mut raw_item = q_item_str;
99 let mut quality = Quality::MAX;
100
101 let parts = q_item_str
102 .rsplit_once(';')
103 .map(|(item, q_attr)| (item.trim(), q_attr.trim()));
104
105 if let Some((val, q_attr)) = parts {
106 if q_attr.len() < 2 {
115 return Err(ParseError::Header);
118 }
119
120 let q = &q_attr[0..2];
121
122 if q == "q=" || q == "Q=" {
123 let q_val = &q_attr[2..];
124 if q_val.len() > 5 {
125 return Err(ParseError::Header);
127 }
128
129 let q_value = q_val.parse::<f32>().map_err(|_| ParseError::Header)?;
130 let q_value = Quality::try_from(q_value).map_err(|_| ParseError::Header)?;
131
132 quality = q_value;
133 raw_item = val;
134 }
135 }
136
137 let item = raw_item.parse::<T>().map_err(|_| ParseError::Header)?;
138
139 Ok(QualityItem::new(item, quality))
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 #[allow(clippy::enum_variant_names)] #[derive(Debug, Clone, PartialEq, Eq)]
150 pub enum Encoding {
151 Chunked,
152 Brotli,
153 Gzip,
154 Deflate,
155 Compress,
156 Identity,
157 Trailers,
158 EncodingExt(String),
159 }
160
161 impl fmt::Display for Encoding {
162 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163 use Encoding::*;
164 f.write_str(match *self {
165 Chunked => "chunked",
166 Brotli => "br",
167 Gzip => "gzip",
168 Deflate => "deflate",
169 Compress => "compress",
170 Identity => "identity",
171 Trailers => "trailers",
172 EncodingExt(ref s) => s.as_ref(),
173 })
174 }
175 }
176
177 impl str::FromStr for Encoding {
178 type Err = crate::error::ParseError;
179 fn from_str(s: &str) -> Result<Encoding, crate::error::ParseError> {
180 use Encoding::*;
181 match s {
182 "chunked" => Ok(Chunked),
183 "br" => Ok(Brotli),
184 "deflate" => Ok(Deflate),
185 "gzip" => Ok(Gzip),
186 "compress" => Ok(Compress),
187 "identity" => Ok(Identity),
188 "trailers" => Ok(Trailers),
189 _ => Ok(EncodingExt(s.to_owned())),
190 }
191 }
192 }
193
194 #[test]
195 fn test_quality_item_fmt_q_1() {
196 use Encoding::*;
197 let x = QualityItem::max(Chunked);
198 assert_eq!(format!("{}", x), "chunked");
199 }
200 #[test]
201 fn test_quality_item_fmt_q_0001() {
202 use Encoding::*;
203 let x = QualityItem::new(Chunked, Quality(1));
204 assert_eq!(format!("{}", x), "chunked; q=0.001");
205 }
206 #[test]
207 fn test_quality_item_fmt_q_05() {
208 use Encoding::*;
209 let x = QualityItem {
211 item: EncodingExt("identity".to_owned()),
212 quality: Quality(500),
213 };
214 assert_eq!(format!("{}", x), "identity; q=0.5");
215 }
216
217 #[test]
218 fn test_quality_item_fmt_q_0() {
219 use Encoding::*;
220 let x = QualityItem {
222 item: EncodingExt("identity".to_owned()),
223 quality: Quality(0),
224 };
225 assert_eq!(x.to_string(), "identity; q=0");
226 }
227
228 #[test]
229 fn test_quality_item_from_str1() {
230 use Encoding::*;
231 let x: Result<QualityItem<Encoding>, _> = "chunked".parse();
232 assert_eq!(
233 x.unwrap(),
234 QualityItem {
235 item: Chunked,
236 quality: Quality(1000),
237 }
238 );
239 }
240
241 #[test]
242 fn test_quality_item_from_str2() {
243 use Encoding::*;
244 let x: Result<QualityItem<Encoding>, _> = "chunked; q=1".parse();
245 assert_eq!(
246 x.unwrap(),
247 QualityItem {
248 item: Chunked,
249 quality: Quality(1000),
250 }
251 );
252 }
253
254 #[test]
255 fn test_quality_item_from_str3() {
256 use Encoding::*;
257 let x: Result<QualityItem<Encoding>, _> = "gzip; q=0.5".parse();
258 assert_eq!(
259 x.unwrap(),
260 QualityItem {
261 item: Gzip,
262 quality: Quality(500),
263 }
264 );
265 }
266
267 #[test]
268 fn test_quality_item_from_str4() {
269 use Encoding::*;
270 let x: Result<QualityItem<Encoding>, _> = "gzip; q=0.273".parse();
271 assert_eq!(
272 x.unwrap(),
273 QualityItem {
274 item: Gzip,
275 quality: Quality(273),
276 }
277 );
278 }
279
280 #[test]
281 fn test_quality_item_from_str5() {
282 let x: Result<QualityItem<Encoding>, _> = "gzip; q=0.2739999".parse();
283 assert!(x.is_err());
284 }
285
286 #[test]
287 fn test_quality_item_from_str6() {
288 let x: Result<QualityItem<Encoding>, _> = "gzip; q=2".parse();
289 assert!(x.is_err());
290 }
291
292 #[test]
293 fn test_quality_item_ordering() {
294 let x: QualityItem<Encoding> = "gzip; q=0.5".parse().ok().unwrap();
295 let y: QualityItem<Encoding> = "gzip; q=0.273".parse().ok().unwrap();
296 let comparison_result: bool = x.gt(&y);
297 assert!(comparison_result)
298 }
299
300 #[test]
301 fn test_fuzzing_bugs() {
302 assert!("99999;".parse::<QualityItem<String>>().is_err());
303 assert!("\x0d;;;=\u{d6aa}==".parse::<QualityItem<String>>().is_err())
304 }
305}