use std::str::FromStr;
use http::Uri;
use http::uri::{InvalidUri, PathAndQuery, Scheme};
use percent_encoding::{AsciiSet, utf8_percent_encode};
use crate::{WELL_KNOWN_PATH, WebFingerRequest, WebFingerResponse};
pub(crate) const CORS_ALLOW_ORIGIN: &str = "*";
const QUERY: AsciiSet = percent_encoding::CONTROLS
.add(b' ')
.add(b'!')
.add(b'"')
.add(b'#')
.add(b'$')
.add(b'%')
.add(b'&')
.add(b'\'')
.add(b'(')
.add(b')')
.add(b'*')
.add(b'+')
.add(b',')
.add(b'/')
.add(b':')
.add(b';')
.add(b'<')
.add(b'=')
.add(b'>')
.add(b'?')
.add(b'@')
.add(b'[')
.add(b'\\')
.add(b']')
.add(b'^')
.add(b'`')
.add(b'{')
.add(b'|')
.add(b'}');
impl TryFrom<&WebFingerRequest> for PathAndQuery {
type Error = InvalidUri;
fn try_from(query: &WebFingerRequest) -> Result<PathAndQuery, InvalidUri> {
let resource = query.resource.to_string();
let resource = utf8_percent_encode(&resource, &QUERY).to_string();
let mut path = WELL_KNOWN_PATH.to_owned();
path.push_str("?resource=");
path.push_str(&resource);
for rel in &query.rels {
let rel = utf8_percent_encode(rel.as_ref(), &QUERY).to_string();
path.push_str("&rel=");
path.push_str(&rel);
}
PathAndQuery::from_str(&path)
}
}
impl TryFrom<&WebFingerRequest> for Uri {
type Error = http::Error;
fn try_from(query: &WebFingerRequest) -> Result<Uri, http::Error> {
let path_and_query = PathAndQuery::try_from(query)?;
const SCHEME: Scheme = Scheme::HTTPS;
Uri::builder()
.scheme(SCHEME)
.authority(query.host.clone())
.path_and_query(path_and_query)
.build()
}
}
impl TryFrom<&WebFingerResponse> for http::Response<()> {
type Error = http::Error;
fn try_from(_: &WebFingerResponse) -> Result<http::Response<()>, http::Error> {
http::Response::builder()
.header("Content-Type", "application/jrd+json")
.header("Access-Control-Allow-Origin", CORS_ALLOW_ORIGIN)
.body(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Rel;
#[test]
fn outgoing_resource_preserves_inner_percent_escapes() {
let request = WebFingerRequest {
resource: "https://example.org/profile/a%20b".parse().unwrap(),
host: "example.org".to_string(),
rels: Vec::new(),
};
let uri = Uri::try_from(&request).unwrap();
assert_eq!(
uri.to_string(),
"https://example.org/.well-known/webfinger?resource=https%3A%2F%2Fexample.org%2Fprofile%2Fa%2520b",
);
}
#[test]
fn outgoing_rel_preserves_inner_percent_escapes() {
let request = WebFingerRequest {
resource: "acct:carol@example.org".parse().unwrap(),
host: "example.org".to_string(),
rels: vec![Rel::new("https://example.org/rel/a%2Fb")],
};
let uri = Uri::try_from(&request).unwrap();
assert_eq!(
uri.to_string(),
"https://example.org/.well-known/webfinger?resource=acct%3Acarol%40example.org&rel=https%3A%2F%2Fexample.org%2Frel%2Fa%252Fb",
);
}
}