1use std::str::FromStr;
2
3use http::Uri;
4use http::uri::{InvalidUri, PathAndQuery, Scheme};
5use percent_encoding::{AsciiSet, utf8_percent_encode};
6
7use crate::{WELL_KNOWN_PATH, WebFingerRequest, WebFingerResponse};
8
9pub(crate) const CORS_ALLOW_ORIGIN: &str = "*";
10
11const QUERY: AsciiSet = percent_encoding::CONTROLS
28 .add(b' ')
29 .add(b'!')
30 .add(b'"')
31 .add(b'#')
32 .add(b'$')
33 .add(b'%')
34 .add(b'&')
35 .add(b'\'')
36 .add(b'(')
37 .add(b')')
38 .add(b'*')
39 .add(b'+')
40 .add(b',')
41 .add(b'/')
42 .add(b':')
43 .add(b';')
44 .add(b'<')
45 .add(b'=')
46 .add(b'>')
47 .add(b'?')
48 .add(b'@')
49 .add(b'[')
50 .add(b'\\')
51 .add(b']')
52 .add(b'^')
53 .add(b'`')
54 .add(b'{')
55 .add(b'|')
56 .add(b'}');
57
58impl TryFrom<&WebFingerRequest> for PathAndQuery {
59 type Error = InvalidUri;
60
61 fn try_from(query: &WebFingerRequest) -> Result<PathAndQuery, InvalidUri> {
62 let resource = query.resource.to_string();
63 let resource = utf8_percent_encode(&resource, &QUERY).to_string();
64 let mut path = WELL_KNOWN_PATH.to_owned();
65 path.push_str("?resource=");
66 path.push_str(&resource);
67 for rel in &query.rels {
68 let rel = utf8_percent_encode(rel.as_ref(), &QUERY).to_string();
69 path.push_str("&rel=");
70 path.push_str(&rel);
71 }
72 PathAndQuery::from_str(&path)
73 }
74}
75
76impl TryFrom<&WebFingerRequest> for Uri {
77 type Error = http::Error;
78
79 fn try_from(query: &WebFingerRequest) -> Result<Uri, http::Error> {
80 let path_and_query = PathAndQuery::try_from(query)?;
81
82 const SCHEME: Scheme = Scheme::HTTPS;
86
87 Uri::builder()
88 .scheme(SCHEME)
89 .authority(query.host.clone())
90 .path_and_query(path_and_query)
91 .build()
92 }
93}
94
95impl TryFrom<&WebFingerResponse> for http::Response<()> {
96 type Error = http::Error;
97 fn try_from(_: &WebFingerResponse) -> Result<http::Response<()>, http::Error> {
98 http::Response::builder()
99 .header("Content-Type", "application/jrd+json")
100 .header("Access-Control-Allow-Origin", CORS_ALLOW_ORIGIN)
101 .body(())
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108 use crate::Rel;
109
110 #[test]
119 fn outgoing_resource_preserves_inner_percent_escapes() {
120 let request = WebFingerRequest {
121 resource: "https://example.org/profile/a%20b".parse().unwrap(),
122 host: "example.org".to_string(),
123 rels: Vec::new(),
124 };
125
126 let uri = Uri::try_from(&request).unwrap();
127
128 assert_eq!(
129 uri.to_string(),
130 "https://example.org/.well-known/webfinger?resource=https%3A%2F%2Fexample.org%2Fprofile%2Fa%2520b",
131 );
132 }
133
134 #[test]
142 fn outgoing_rel_preserves_inner_percent_escapes() {
143 let request = WebFingerRequest {
144 resource: "acct:carol@example.org".parse().unwrap(),
145 host: "example.org".to_string(),
146 rels: vec![Rel::new("https://example.org/rel/a%2Fb")],
147 };
148
149 let uri = Uri::try_from(&request).unwrap();
150
151 assert_eq!(
152 uri.to_string(),
153 "https://example.org/.well-known/webfinger?resource=acct%3Acarol%40example.org&rel=https%3A%2F%2Fexample.org%2Frel%2Fa%252Fb",
154 );
155 }
156}