1use std::{fmt, str::FromStr};
2
3use http::uri::{Authority, PathAndQuery, Scheme, Uri};
4use percent_encoding::percent_decode_str;
5use vector_lib::configurable::configurable_component;
6
7use crate::http::Auth;
8
9#[configurable_component]
13#[configurable(title = "The URI component of a request.", description = "")]
14#[derive(Default, Debug, Clone)]
15#[serde(try_from = "String", into = "String")]
16pub struct UriSerde {
17 pub uri: Uri,
18 pub auth: Option<Auth>,
19}
20
21impl UriSerde {
22 pub fn with_default_parts(&self) -> Self {
26 let mut parts = self.uri.clone().into_parts();
27 if parts.scheme.is_none() {
28 parts.scheme = Some(Scheme::HTTP);
29 }
30 if parts.authority.is_none() {
31 parts.authority = Some(Authority::from_static("127.0.0.1"));
32 }
33 if parts.path_and_query.is_none() {
34 parts.path_and_query = Some(PathAndQuery::from_static(""));
37 }
38 let uri = Uri::from_parts(parts).expect("invalid parts");
39 Self {
40 uri,
41 auth: self.auth.clone(),
42 }
43 }
44
45 pub fn append_path(&self, path: &str) -> crate::Result<Self> {
47 let uri = self.uri.to_string();
48 let self_path = uri.trim_end_matches('/');
49 let other_path = path.trim_start_matches('/');
50 let path = format!("{self_path}/{other_path}");
51 let uri = path.parse::<Uri>()?;
52 Ok(Self {
53 uri,
54 auth: self.auth.clone(),
55 })
56 }
57
58 #[allow(clippy::missing_const_for_fn)] pub fn with_auth(mut self, auth: Option<Auth>) -> Self {
60 self.auth = auth;
61 self
62 }
63}
64
65impl TryFrom<String> for UriSerde {
66 type Error = <Uri as FromStr>::Err;
67
68 fn try_from(value: String) -> Result<Self, Self::Error> {
69 value.as_str().parse()
70 }
71}
72
73impl From<UriSerde> for String {
74 fn from(uri: UriSerde) -> Self {
75 uri.to_string()
76 }
77}
78
79impl fmt::Display for UriSerde {
80 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81 match (self.uri.authority(), &self.auth) {
82 (Some(authority), Some(Auth::Basic { user, password })) => {
83 let authority = format!("{user}:{password}@{authority}");
84 let authority =
85 Authority::from_maybe_shared(authority).map_err(|_| std::fmt::Error)?;
86 let mut parts = self.uri.clone().into_parts();
87 parts.authority = Some(authority);
88 Uri::from_parts(parts).unwrap().fmt(f)
89 }
90 _ => self.uri.fmt(f),
91 }
92 }
93}
94
95impl FromStr for UriSerde {
96 type Err = <Uri as FromStr>::Err;
97
98 fn from_str(s: &str) -> Result<Self, Self::Err> {
99 s.parse::<Uri>().map(Into::into)
100 }
101}
102
103impl From<Uri> for UriSerde {
104 fn from(uri: Uri) -> Self {
105 match uri.authority() {
106 None => Self { uri, auth: None },
107 Some(authority) => {
108 let (authority, auth) = get_basic_auth(authority);
109
110 let mut parts = uri.into_parts();
111 parts.authority = Some(authority);
112 let uri = Uri::from_parts(parts).unwrap();
113
114 Self { uri, auth }
115 }
116 }
117 }
118}
119
120fn get_basic_auth(authority: &Authority) -> (Authority, Option<Auth>) {
121 let mut url = url::Url::parse(&format!("http://{authority}")).expect("invalid authority");
123
124 let user = url.username();
125 if !user.is_empty() {
126 let user = percent_decode_str(user).decode_utf8_lossy().into_owned();
127
128 let password = url.password().unwrap_or("");
129 let password = percent_decode_str(password)
130 .decode_utf8_lossy()
131 .into_owned();
132
133 url.set_username("").expect("unexpected empty authority");
136 url.set_password(None).expect("unexpected empty authority");
137
138 let authority = Uri::from_maybe_shared(String::from(url))
140 .expect("invalid url")
141 .authority()
142 .expect("unexpected empty authority")
143 .clone();
144
145 (
146 authority,
147 Some(Auth::Basic {
148 user,
149 password: password.into(),
150 }),
151 )
152 } else {
153 (authority.clone(), None)
154 }
155}
156
157pub fn protocol_endpoint(uri: Uri) -> (String, String) {
160 let mut parts = uri.into_parts();
161
162 parts.authority = parts.authority.map(|auth| {
164 let host = auth.host();
165 match auth.port() {
166 None => host.to_string(),
167 Some(port) => format!("{host}:{port}"),
168 }
169 .parse()
170 .unwrap_or_else(|_| unreachable!())
171 });
172
173 parts.path_and_query = parts.path_and_query.map(|pq| {
175 pq.path()
176 .parse::<PathAndQuery>()
177 .unwrap_or_else(|_| unreachable!())
178 });
179
180 (
181 parts.scheme.clone().unwrap_or(Scheme::HTTP).as_str().into(),
182 Uri::from_parts(parts)
183 .unwrap_or_else(|_| unreachable!())
184 .to_string(),
185 )
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191
192 fn test_parse(input: &str, expected_uri: &'static str, expected_auth: Option<(&str, &str)>) {
193 let UriSerde { uri, auth } = input.parse().unwrap();
194 assert_eq!(uri, Uri::from_static(expected_uri));
195 assert_eq!(
196 auth,
197 expected_auth.map(|(user, password)| {
198 Auth::Basic {
199 user: user.to_owned(),
200 password: password.to_owned().into(),
201 }
202 })
203 );
204 }
205
206 #[test]
207 fn parse_endpoint() {
208 test_parse(
209 "http://user:pass@example.com/test",
210 "http://example.com/test",
211 Some(("user", "pass")),
212 );
213
214 test_parse("localhost:8080", "localhost:8080", None);
215
216 test_parse("/api/test", "/api/test", None);
217
218 test_parse(
219 "http://user:pass;@example.com",
220 "http://example.com",
221 Some(("user", "pass;")),
222 );
223
224 test_parse(
225 "user:pass@example.com",
226 "example.com",
227 Some(("user", "pass")),
228 );
229
230 test_parse("user@example.com", "example.com", Some(("user", "")));
231 }
232
233 #[test]
234 fn protocol_endpoint_parses_urls() {
235 let parse = |uri: &str| protocol_endpoint(uri.parse().unwrap());
236
237 assert_eq!(
238 parse("http://example.com/"),
239 ("http".into(), "http://example.com/".into())
240 );
241 assert_eq!(
242 parse("https://user:pass@example.org:123/path?query"),
243 ("https".into(), "https://example.org:123/path".into())
244 );
245 assert_eq!(
246 parse("gopher://example.net:123/path?query#frag,emt"),
247 ("gopher".into(), "gopher://example.net:123/path".into())
248 );
249 }
250}