v6/src/activitypub/util/accept.rs

126 lines
3.1 KiB
Rust

use axum::http::header::ACCEPT;
use axum::http::HeaderMap;
use mime::{Mime, TEXT_HTML};
use std::cmp::Ordering;
pub fn accepts_html(headers: &HeaderMap) -> bool {
headers.get(ACCEPT).map_or(false, |val| {
let s = val.to_str().unwrap();
parse_accept(s).accepts(&TEXT_HTML)
})
}
pub fn best_match<'a>(headers: &HeaderMap, options: &'a [&'a Mime]) -> Option<&'a Mime> {
headers
.get(ACCEPT)
.map(|val| {
let accept = parse_accept(val.to_str().unwrap());
for &opt in options.iter() {
if accept.accepts(opt) {
return Some(opt);
}
}
None
})
.flatten()
}
fn parse_accept(s: &str) -> Accept {
let mut entries = s
.split(",")
.flat_map(|s| s.trim().parse::<Mime>().ok())
.collect::<Vec<_>>();
entries.sort_by(|a, b| {
if b.is_wildcard() || b.is_subtype_wildcard() {
Ordering::Less
} else {
b.quality()
.unwrap_or(1f32)
.partial_cmp(&a.quality().unwrap_or(1f32))
.unwrap()
}
});
Accept { entries }
}
#[derive(Debug, PartialEq)]
struct Accept {
entries: Vec<Mime>,
}
impl Accept {
fn accepts(&self, mime: &Mime) -> bool {
self.entries.iter().any(|e| {
if e == mime {
true
} else if e.type_() == mime.type_() && e.is_subtype_wildcard() {
true
} else {
false
}
})
}
}
trait MimeExt {
fn quality(&self) -> Option<f32>;
fn is_wildcard(&self) -> bool;
fn is_subtype_wildcard(&self) -> bool;
}
impl MimeExt for Mime {
fn quality(&self) -> Option<f32> {
self.params()
.find(|p| p.0 == "q")
.map(|(_, val)| val.as_str().parse().unwrap())
}
fn is_wildcard(&self) -> bool {
*self == mime::STAR_STAR
}
fn is_subtype_wildcard(&self) -> bool {
self.subtype() == "*"
}
}
#[cfg(test)]
mod tests {
use axum::http::HeaderMap;
use super::best_match;
#[test]
fn test_sort_accept() {
let accept =
super::parse_accept("text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c");
assert_eq!(
accept.entries,
vec![
"text/html",
"text/x-c",
"text/x-dvi; q=0.8",
"text/plain; q=0.5"
]
);
}
#[test]
fn test_accept() {
let accept =
super::parse_accept("text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c");
assert!(accept.accepts(&"text/html".parse().unwrap()));
assert!(!accept.accepts(&"application/json".parse().unwrap()));
}
#[test]
fn test_best_match() {
let mut headers = HeaderMap::new();
headers.insert("Accept", "text/plain; q=0.5, text/html".parse().unwrap());
assert_eq!(
best_match(&headers, &[&mime::TEXT_HTML, &mime::APPLICATION_JAVASCRIPT]),
Some(&mime::TEXT_HTML)
);
}
}