webmachine_rust/
content_negotiation.rs1use std::cmp::Ordering;
5
6use itertools::Itertools;
7
8use crate::Resource;
9use crate::context::{WebmachineContext, WebmachineRequest};
10use crate::headers::HeaderValue;
11
12#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
14pub enum MediaTypeMatch {
15 Full,
17 SubStar,
19 Star,
21 None
23}
24
25impl MediaTypeMatch {
26 pub fn is_match(&self) -> bool {
28 match self {
29 MediaTypeMatch::None => false,
30 _ => true
31 }
32 }
33}
34
35#[derive(Debug, Clone, PartialEq)]
37pub struct MediaType {
38 pub main: String,
40 pub sub: String,
42 pub weight: f32
44}
45
46impl MediaType {
47 pub fn parse_string(media_type: &str) -> MediaType {
49 let types: Vec<&str> = media_type.splitn(2, '/').collect_vec();
50 if types.is_empty() || types[0].is_empty() {
51 MediaType {
52 main: "*".to_string(),
53 sub: "*".to_string(),
54 weight: 1.0
55 }
56 } else {
57 MediaType {
58 main: types[0].to_string(),
59 sub: if types.len() == 1 || types[1].is_empty() { "*".to_string() } else { types[1].to_string() },
60 weight: 1.0
61 }
62 }
63 }
64
65 pub fn with_weight(&self, weight: &String) -> MediaType {
67 MediaType {
68 main: self.main.clone(),
69 sub: self.sub.clone(),
70 weight: weight.parse().unwrap_or(1.0)
71 }
72 }
73
74 pub fn weight(&self) -> (f32, u8) {
76 if self.main == "*" && self.sub == "*" {
77 (self.weight, 2)
78 } else if self.sub == "*" {
79 (self.weight, 1)
80 } else {
81 (self.weight, 0)
82 }
83 }
84
85 pub fn matches(&self, other: &MediaType) -> MediaTypeMatch {
87 if other.main == "*" {
88 if other.sub == "*" || self.sub == other.sub {
89 MediaTypeMatch::Star
90 } else {
91 MediaTypeMatch::None
92 }
93 } else if self.main == other.main && other.sub == "*" {
94 MediaTypeMatch::SubStar
95 } else if self.main == other.main && self.sub == other.sub {
96 MediaTypeMatch::Full
97 } else {
98 MediaTypeMatch::None
99 }
100 }
101
102 pub fn to_string(&self) -> String {
104 format!("{}/{}", self.main, self.sub)
105 }
106}
107
108impl HeaderValue {
109 pub fn as_media_type(&self) -> MediaType {
111 if self.params.contains_key("q") {
112 MediaType::parse_string(&self.value).with_weight(self.params.get("q").unwrap())
113 } else {
114 MediaType::parse_string(&self.value)
115 }
116 }
117}
118
119pub fn sort_media_types(media_types: &Vec<HeaderValue>) -> Vec<HeaderValue> {
121 media_types.into_iter().cloned().sorted_by(|a, b| {
122 let media_a = a.as_media_type().weight();
123 let media_b = b.as_media_type().weight();
124 let order = media_a.0.partial_cmp(&media_b.0).unwrap_or(Ordering::Greater);
125 if order == Ordering::Equal {
126 Ord::cmp(&media_a.1, &media_b.1)
127 } else {
128 order.reverse()
129 }
130 }).collect()
131}
132
133pub fn matching_content_type(resource: &(dyn Resource + Send + Sync), request: &WebmachineRequest) -> Option<String> {
136 if request.has_accept_header() {
137 let acceptable_media_types = sort_media_types(&request.accept());
138 resource.produces().iter()
139 .cartesian_product(acceptable_media_types.iter())
140 .map(|(produced, acceptable)| {
141 let acceptable_media_type = acceptable.as_media_type();
142 let produced_media_type = MediaType::parse_string(produced);
143 (produced_media_type.clone(), acceptable_media_type.clone(), produced_media_type.matches(&acceptable_media_type))
144 })
145 .sorted_by(|a, b| Ord::cmp(&a.2, &b.2))
146 .filter(|val| val.2 != MediaTypeMatch::None)
147 .next()
148 .map(|result| result.0.to_string())
149 } else {
150 resource.produces().first().map(|s| s.to_string())
151 }
152}
153
154pub fn acceptable_content_type(
157 resource: &(dyn Resource + Send + Sync),
158 context: &mut WebmachineContext
159) -> bool {
160 let ct = context.request.content_type().as_media_type();
161 resource.acceptable_content_types(context)
162 .iter()
163 .any(|acceptable_ct| {
164 ct.matches(&MediaType::parse_string(acceptable_ct)).is_match()
165 })
166}
167
168#[derive(Debug, Clone, PartialEq)]
170pub struct MediaLanguage {
171 pub main: String,
173 pub sub: String,
175 pub weight: f32
177}
178
179impl MediaLanguage {
180 pub fn parse_string(language: &str) -> MediaLanguage {
182 let types: Vec<&str> = language.splitn(2, '-').collect_vec();
183 if types.is_empty() || types[0].is_empty() {
184 MediaLanguage {
185 main: "*".to_string(),
186 sub: "".to_string(),
187 weight: 1.0
188 }
189 } else {
190 MediaLanguage {
191 main: types[0].to_string(),
192 sub: if types.len() == 1 || types[1].is_empty() { "".to_string() } else { types[1].to_string() },
193 weight: 1.0
194 }
195 }
196 }
197
198 pub fn with_weight(&self, weight: &str) -> MediaLanguage {
200 MediaLanguage {
201 main: self.main.clone(),
202 sub: self.sub.clone(),
203 weight: weight.parse().unwrap_or(1.0)
204 }
205 }
206
207 pub fn matches(&self, other: &MediaLanguage) -> bool {
209 if other.main == "*" || (self.main == other.main && self.sub == other.sub) {
210 true
211 } else {
212 let check = format!("{}-", self.to_string());
213 other.to_string().starts_with(&check)
214 }
215 }
216
217 pub fn to_string(&self) -> String {
219 if self.sub.is_empty() {
220 self.main.clone()
221 } else {
222 format!("{}-{}", self.main, self.sub)
223 }
224 }
225}
226
227impl HeaderValue {
228 pub fn as_media_language(&self) -> MediaLanguage {
230 if self.params.contains_key("q") {
231 MediaLanguage::parse_string(&self.value).with_weight(self.params.get("q").unwrap())
232 } else {
233 MediaLanguage::parse_string(&self.value)
234 }
235 }
236}
237
238pub fn sort_media_languages(media_languages: &Vec<HeaderValue>) -> Vec<MediaLanguage> {
240 media_languages.iter()
241 .cloned()
242 .map(|lang| lang.as_media_language())
243 .filter(|lang| lang.weight > 0.0)
244 .sorted_by(|a, b| {
245 let weight_a = a.weight;
246 let weight_b = b.weight;
247 weight_b.partial_cmp(&weight_a).unwrap_or(Ordering::Greater)
248 })
249 .collect()
250}
251
252pub fn matching_language(resource: &(dyn Resource + Send + Sync), request: &WebmachineRequest) -> Option<String> {
255 if request.has_accept_language_header() && !request.accept_language().is_empty() {
256 let acceptable_languages = sort_media_languages(&request.accept_language());
257 if resource.languages_provided().is_empty() {
258 acceptable_languages.first().map(|lang| lang.to_string())
259 } else {
260 acceptable_languages.iter()
261 .cartesian_product(resource.languages_provided().iter())
262 .map(|(acceptable_language, produced_language)| {
263 let produced_language = MediaLanguage::parse_string(produced_language);
264 (produced_language.clone(), produced_language.matches(&acceptable_language))
265 })
266 .find(|val| val.1)
267 .map(|result| result.0.to_string())
268 }
269 } else if resource.languages_provided().is_empty() {
270 Some("*".to_string())
271 } else {
272 resource.languages_provided().first().map(|s| s.to_string())
273 }
274}
275
276#[derive(Debug, Clone, PartialEq)]
278pub struct Charset {
279 pub charset: String,
281 pub weight: f32
283}
284
285impl Charset {
286 pub fn parse_string(charset: &str) -> Charset {
288 Charset {
289 charset: charset.to_string(),
290 weight: 1.0
291 }
292 }
293
294 pub fn with_weight(&self, weight: &str) -> Charset {
296 Charset {
297 charset: self.charset.clone(),
298 weight: weight.parse().unwrap_or(1.0)
299 }
300 }
301
302 pub fn matches(&self, other: &Charset) -> bool {
304 other.charset == "*" || (self.charset.to_uppercase() == other.charset.to_uppercase())
305 }
306
307 pub fn to_string(&self) -> String {
309 self.charset.clone()
310 }
311}
312
313impl HeaderValue {
314 pub fn as_charset(&self) -> Charset {
316 if self.params.contains_key("q") {
317 Charset::parse_string(&self.value).with_weight(self.params.get("q").unwrap())
318 } else {
319 Charset::parse_string(&self.value)
320 }
321 }
322}
323
324pub fn sort_media_charsets(charsets: &Vec<HeaderValue>) -> Vec<Charset> {
327 let mut charsets = charsets.clone();
328 if charsets.iter().find(|cs| cs.value == "*" || cs.value.to_uppercase() == "ISO-8859-1").is_none() {
329 charsets.push(h!("ISO-8859-1"));
330 }
331 charsets.into_iter()
332 .map(|cs| cs.as_charset())
333 .filter(|cs| cs.weight > 0.0)
334 .sorted_by(|a, b| {
335 let weight_a = a.weight;
336 let weight_b = b.weight;
337 weight_b.partial_cmp(&weight_a).unwrap_or(Ordering::Greater)
338 })
339 .collect()
340}
341
342pub fn matching_charset(resource: &(dyn Resource + Send + Sync), request: &WebmachineRequest) -> Option<String> {
345 if request.has_accept_charset_header() && !request.accept_charset().is_empty() {
346 let acceptable_charsets = sort_media_charsets(&request.accept_charset());
347 if resource.charsets_provided().is_empty() {
348 acceptable_charsets.first().map(|cs| cs.to_string())
349 } else {
350 acceptable_charsets.iter()
351 .cartesian_product(resource.charsets_provided().iter())
352 .map(|(acceptable_charset, provided_charset)| {
353 let provided_charset = Charset::parse_string(provided_charset);
354 (provided_charset.clone(), provided_charset.matches(&acceptable_charset))
355 })
356 .find(|val| val.1)
357 .map(|result| result.0.to_string())
358 }
359 } else if resource.charsets_provided().is_empty() {
360 Some("ISO-8859-1".to_string())
361 } else {
362 resource.charsets_provided().first().map(|s| s.to_string())
363 }
364}
365
366#[derive(Debug, Clone, PartialEq)]
368pub struct Encoding {
369 pub encoding: String,
371 pub weight: f32
373}
374
375impl Encoding {
376 pub fn parse_string(encoding: &str) -> Encoding {
378 Encoding {
379 encoding: encoding.to_string(),
380 weight: 1.0
381 }
382 }
383
384 pub fn with_weight(&self, weight: &str) -> Encoding {
386 Encoding {
387 encoding: self.encoding.to_string(),
388 weight: weight.parse().unwrap_or(1.0)
389 }
390 }
391
392 pub fn matches(&self, other: &Encoding) -> bool {
394 other.encoding == "*" || (self.encoding.to_lowercase() == other.encoding.to_lowercase())
395 }
396
397 pub fn to_string(&self) -> String {
399 self.encoding.clone()
400 }
401}
402
403impl HeaderValue {
404 pub fn as_encoding(&self) -> Encoding {
406 if self.params.contains_key("q") {
407 Encoding::parse_string(&self.value).with_weight(self.params.get("q").unwrap())
408 } else {
409 Encoding::parse_string(&self.value)
410 }
411 }
412}
413
414pub fn sort_encodings(encodings: &Vec<HeaderValue>) -> Vec<Encoding> {
417 let mut encodings = encodings.clone();
418 if encodings.iter().find(|e| e.value == "*" || e.value.to_lowercase() == "identity").is_none() {
419 encodings.push(h!("identity"));
420 }
421 encodings.into_iter()
422 .map(|encoding| encoding.as_encoding())
423 .filter(|encoding| encoding.weight > 0.0)
424 .sorted_by(|a, b| {
425 let weight_a = a.weight;
426 let weight_b = b.weight;
427 weight_b.partial_cmp(&weight_a).unwrap_or(Ordering::Greater)
428 })
429 .collect()
430}
431
432pub fn matching_encoding(resource: &(dyn Resource + Send + Sync), request: &WebmachineRequest) -> Option<String> {
435 let identity = Encoding::parse_string("identity");
436 if request.has_accept_encoding_header() {
437 let acceptable_encodings = sort_encodings(&request.accept_encoding());
438 if resource.encodings_provided().is_empty() {
439 if acceptable_encodings.contains(&identity) {
440 Some("identity".to_string())
441 } else {
442 None
443 }
444 } else {
445 acceptable_encodings.iter()
446 .cartesian_product(resource.encodings_provided().iter())
447 .map(|(acceptable_encoding, provided_encoding)| {
448 let provided_encoding = Encoding::parse_string(provided_encoding);
449 (provided_encoding.clone(), provided_encoding.matches(&acceptable_encoding))
450 })
451 .find(|val| val.1)
452 .map(|result| { result.0.to_string() })
453 }
454 } else if resource.encodings_provided().is_empty() {
455 Some("identity".to_string())
456 } else {
457 resource.encodings_provided().first().map(|s| s.to_string())
458 }
459}