matrix_sdk/client/builder/
homeserver_config.rs1use ruma::{
16 OwnedServerName, ServerName,
17 api::client::discovery::{discover_homeserver, get_supported_versions},
18};
19use tracing::debug;
20use url::Url;
21
22use crate::{
23 ClientBuildError, HttpError, config::RequestConfig, http_client::HttpClient,
24 sanitize_server_name,
25};
26
27#[derive(Clone, Debug)]
29pub(super) enum HomeserverConfig {
30 HomeserverUrl(String),
32
33 ServerName { server: OwnedServerName, protocol: UrlScheme },
35
36 ServerNameOrHomeserverUrl(String),
39}
40
41#[derive(Clone, Copy, Debug)]
43pub(super) enum UrlScheme {
44 Http,
45 Https,
46}
47
48pub(super) struct HomeserverDiscoveryResult {
50 pub server: Option<Url>,
51 pub homeserver: Url,
52 pub supported_versions: Option<get_supported_versions::Response>,
53 pub well_known: Option<discover_homeserver::Response>,
54}
55
56impl HomeserverConfig {
57 pub async fn discover(
64 &self,
65 http_client: &HttpClient,
66 well_known_lookup_disabled: bool,
67 ) -> Result<HomeserverDiscoveryResult, ClientBuildError> {
68 Ok(match self {
69 Self::HomeserverUrl(url) => {
70 let homeserver = Url::parse(url)?;
71
72 HomeserverDiscoveryResult {
73 server: None, homeserver,
75 supported_versions: None,
76 well_known: None,
77 }
78 }
79
80 Self::ServerName { server, protocol } => {
81 if well_known_lookup_disabled {
85 return Err(ClientBuildError::WellKnownLookupDisabled);
86 }
87
88 let (server, well_known) =
89 discover_homeserver(server, protocol, http_client).await?;
90
91 HomeserverDiscoveryResult {
92 server: Some(server),
93 homeserver: Url::parse(&well_known.homeserver.base_url)?,
94 supported_versions: None,
95 well_known: Some(well_known),
96 }
97 }
98
99 Self::ServerNameOrHomeserverUrl(server_name_or_url) => {
100 let (server, homeserver, supported_versions, well_known) =
101 discover_homeserver_from_server_name_or_url(
102 server_name_or_url.to_owned(),
103 http_client,
104 well_known_lookup_disabled,
105 )
106 .await?;
107
108 HomeserverDiscoveryResult { server, homeserver, supported_versions, well_known }
109 }
110 })
111 }
112}
113
114async fn discover_homeserver_from_server_name_or_url(
121 mut server_name_or_url: String,
122 http_client: &HttpClient,
123 well_known_lookup_disabled: bool,
124) -> Result<
125 (
126 Option<Url>,
127 Url,
128 Option<get_supported_versions::Response>,
129 Option<discover_homeserver::Response>,
130 ),
131 ClientBuildError,
132> {
133 let mut discovery_error: Option<ClientBuildError> = None;
134
135 let sanitize_result = sanitize_server_name(&server_name_or_url);
137
138 if let Ok(server_name) = sanitize_result.as_ref() {
139 let protocol = if server_name_or_url.starts_with("http://") {
140 UrlScheme::Http
141 } else {
142 UrlScheme::Https
143 };
144
145 let server_name_as_url = match protocol {
146 UrlScheme::Http => format!("http://{server_name}"),
147 UrlScheme::Https => format!("https://{server_name}"),
148 };
149
150 if well_known_lookup_disabled {
151 debug!("Well-known discovery is disabled, checking for a homeserver URL directly.");
152 server_name_or_url = server_name_as_url;
153 } else {
154 match discover_homeserver(server_name, &protocol, http_client).await {
155 Ok((server, well_known)) => {
156 return Ok((
157 Some(server),
158 Url::parse(&well_known.homeserver.base_url)?,
159 None,
160 Some(well_known),
161 ));
162 }
163 Err(e) => {
164 debug!(error = %e, "Well-known discovery failed.");
165 discovery_error = Some(e);
166
167 server_name_or_url = server_name_as_url;
169 }
170 }
171 }
172 }
173
174 if let Ok(homeserver_url) = Url::parse(&server_name_or_url) {
177 match get_supported_versions(&homeserver_url, http_client).await {
179 Ok(response) => {
180 return Ok((None, homeserver_url, Some(response), None));
181 }
182 Err(e) => {
183 debug!(error = %e, "Checking supported versions failed.");
184 }
185 }
186 }
187
188 Err(discovery_error.unwrap_or(ClientBuildError::InvalidServerName))
189}
190
191async fn discover_homeserver(
194 server_name: &ServerName,
195 protocol: &UrlScheme,
196 http_client: &HttpClient,
197) -> Result<(Url, discover_homeserver::Response), ClientBuildError> {
198 debug!("Trying to discover the homeserver");
199
200 let server = Url::parse(&match protocol {
201 UrlScheme::Http => format!("http://{server_name}"),
202 UrlScheme::Https => format!("https://{server_name}"),
203 })?;
204
205 let well_known = http_client
206 .send(
207 discover_homeserver::Request::new(),
208 Some(RequestConfig::short_retry()),
209 server.to_string(),
210 None,
211 (),
212 Default::default(),
213 )
214 .await
215 .map_err(|e| match e {
216 HttpError::Api(err) => ClientBuildError::AutoDiscovery(err),
217 err => ClientBuildError::Http(err),
218 })?;
219
220 debug!(homeserver_url = well_known.homeserver.base_url, "Discovered the homeserver");
221
222 Ok((server, well_known))
223}
224
225pub(super) async fn get_supported_versions(
226 homeserver_url: &Url,
227 http_client: &HttpClient,
228) -> Result<get_supported_versions::Response, HttpError> {
229 http_client
230 .send(
231 get_supported_versions::Request::new(),
232 Some(RequestConfig::short_retry()),
233 homeserver_url.to_string(),
234 None,
235 (),
236 Default::default(),
237 )
238 .await
239}
240
241#[cfg(all(test, not(target_family = "wasm")))]
242mod tests {
243 use assert_matches::assert_matches;
244 use matrix_sdk_test::async_test;
245 use ruma::OwnedServerName;
246 use serde_json::json;
247 use wiremock::{
248 Mock, MockServer, ResponseTemplate,
249 matchers::{method, path},
250 };
251
252 use super::*;
253 use crate::http_client::HttpSettings;
254
255 #[async_test]
256 async fn test_url() {
257 let http_client =
258 HttpClient::new(HttpSettings::default().make_client().unwrap(), Default::default());
259
260 let result = HomeserverConfig::HomeserverUrl("https://matrix-client.matrix.org".to_owned())
261 .discover(&http_client, false)
262 .await
263 .unwrap();
264
265 assert_eq!(result.server, None);
266 assert_eq!(result.homeserver, Url::parse("https://matrix-client.matrix.org").unwrap());
267 assert!(result.supported_versions.is_none());
268 }
269
270 #[async_test]
271 async fn test_server_name() {
272 let http_client =
273 HttpClient::new(HttpSettings::default().make_client().unwrap(), Default::default());
274
275 let server = MockServer::start().await;
276 let homeserver = MockServer::start().await;
277
278 Mock::given(method("GET"))
279 .and(path("/.well-known/matrix/client"))
280 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
281 "m.homeserver": {
282 "base_url": homeserver.uri(),
283 },
284 })))
285 .mount(&server)
286 .await;
287
288 let result = HomeserverConfig::ServerName {
289 server: OwnedServerName::try_from(server.address().to_string()).unwrap(),
290 protocol: UrlScheme::Http,
291 }
292 .discover(&http_client, false)
293 .await
294 .unwrap();
295
296 assert_eq!(result.server, Some(Url::parse(&server.uri()).unwrap()));
297 assert_eq!(result.homeserver, Url::parse(&homeserver.uri()).unwrap());
298 assert!(result.supported_versions.is_none());
299 }
300
301 #[async_test]
302 async fn test_server_name_or_url_with_name() {
303 let http_client =
304 HttpClient::new(HttpSettings::default().make_client().unwrap(), Default::default());
305
306 let server = MockServer::start().await;
307 let homeserver = MockServer::start().await;
308
309 Mock::given(method("GET"))
310 .and(path("/.well-known/matrix/client"))
311 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
312 "m.homeserver": {
313 "base_url": homeserver.uri(),
314 },
315 })))
316 .mount(&server)
317 .await;
318
319 let result = HomeserverConfig::ServerNameOrHomeserverUrl(server.uri().to_string())
320 .discover(&http_client, false)
321 .await
322 .unwrap();
323
324 assert_eq!(result.server, Some(Url::parse(&server.uri()).unwrap()));
325 assert_eq!(result.homeserver, Url::parse(&homeserver.uri()).unwrap());
326 assert!(result.supported_versions.is_none());
327 }
328
329 #[async_test]
330 async fn test_server_name_or_url_with_url() {
331 let http_client =
332 HttpClient::new(HttpSettings::default().make_client().unwrap(), Default::default());
333
334 let homeserver = MockServer::start().await;
335
336 Mock::given(method("GET"))
337 .and(path("/_matrix/client/versions"))
338 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
339 "versions": [],
340 })))
341 .mount(&homeserver)
342 .await;
343
344 let result = HomeserverConfig::ServerNameOrHomeserverUrl(homeserver.uri().to_string())
345 .discover(&http_client, false)
346 .await
347 .unwrap();
348
349 assert!(result.server.is_none());
350 assert_eq!(result.homeserver, Url::parse(&homeserver.uri()).unwrap());
351 assert!(result.supported_versions.is_some());
352 }
353
354 async fn mock_well_known_never_called(server: &MockServer, homeserver: &MockServer) {
357 Mock::given(method("GET"))
358 .and(path("/.well-known/matrix/client"))
359 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
360 "m.homeserver": {
361 "base_url": homeserver.uri(),
362 },
363 })))
364 .named("well-known mock")
365 .expect(0)
366 .mount(server)
367 .await;
368 }
369
370 #[async_test]
371 async fn test_url_with_well_known_lookup_disabled() {
372 let http_client =
373 HttpClient::new(HttpSettings::default().make_client().unwrap(), Default::default());
374
375 let result = HomeserverConfig::HomeserverUrl("https://matrix-client.matrix.org".to_owned())
377 .discover(&http_client, true)
378 .await
379 .unwrap();
380
381 assert_eq!(result.server, None);
382 assert_eq!(result.homeserver, Url::parse("https://matrix-client.matrix.org").unwrap());
383 assert!(result.supported_versions.is_none());
384 }
385
386 #[async_test]
387 async fn test_server_name_with_well_known_lookup_disabled() {
388 let http_client =
389 HttpClient::new(HttpSettings::default().make_client().unwrap(), Default::default());
390
391 let server = MockServer::start().await;
392 let homeserver = MockServer::start().await;
393
394 mock_well_known_never_called(&server, &homeserver).await;
395
396 let error = HomeserverConfig::ServerName {
399 server: OwnedServerName::try_from(server.address().to_string()).unwrap(),
400 protocol: UrlScheme::Http,
401 }
402 .discover(&http_client, true)
403 .await
404 .err()
405 .unwrap();
406
407 assert_matches!(error, ClientBuildError::WellKnownLookupDisabled);
408 }
409
410 #[async_test]
411 async fn test_server_name_or_url_with_name_and_well_known_lookup_disabled() {
412 let http_client =
413 HttpClient::new(HttpSettings::default().make_client().unwrap(), Default::default());
414
415 let server = MockServer::start().await;
416 let homeserver = MockServer::start().await;
417
418 mock_well_known_never_called(&server, &homeserver).await;
419
420 let error = HomeserverConfig::ServerNameOrHomeserverUrl(server.uri().to_string())
424 .discover(&http_client, true)
425 .await
426 .err()
427 .unwrap();
428
429 assert_matches!(error, ClientBuildError::InvalidServerName);
430 }
431
432 #[async_test]
433 async fn test_server_name_or_url_with_url_and_well_known_lookup_disabled() {
434 let http_client =
435 HttpClient::new(HttpSettings::default().make_client().unwrap(), Default::default());
436
437 let homeserver = MockServer::start().await;
438
439 mock_well_known_never_called(&homeserver, &homeserver).await;
440
441 Mock::given(method("GET"))
442 .and(path("/_matrix/client/versions"))
443 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
444 "versions": [],
445 })))
446 .mount(&homeserver)
447 .await;
448
449 let result = HomeserverConfig::ServerNameOrHomeserverUrl(homeserver.uri().to_string())
452 .discover(&http_client, true)
453 .await
454 .unwrap();
455
456 assert!(result.server.is_none());
457 assert_eq!(result.homeserver, Url::parse(&homeserver.uri()).unwrap());
458 assert!(result.supported_versions.is_some());
459 assert!(result.well_known.is_none());
460 }
461}