Skip to main content

tuwunel_service/appservice/
request.rs

1use std::{fmt::Debug, mem};
2
3use bytes::BytesMut;
4use ruma::api::{
5	IncomingResponse, OutgoingRequest,
6	appservice::Registration,
7	auth_scheme::{AuthScheme, SendAccessToken},
8	path_builder::PathBuilder,
9};
10use tuwunel_core::{Err, Result, debug_error, err, implement, trace, utils, warn};
11
12/// Sends a request to an appservice
13///
14/// Only returns Ok(None) if there is no url specified in the appservice
15/// registration file
16#[implement(super::Service)]
17pub async fn send_request<T>(
18	&self,
19	registration: Registration,
20	request: T,
21) -> Result<Option<T::IncomingResponse>>
22where
23	T: OutgoingRequest + Debug + Send,
24	for<'a> T::Authentication: AuthScheme<Input<'a> = SendAccessToken<'a>>,
25	for<'a> T::PathBuilder: PathBuilder<Input<'a> = ()>,
26{
27	let client = &self.services.client.appservice;
28
29	let Some(dest) = registration.url else {
30		return Ok(None);
31	};
32
33	if dest == *"null" || dest.is_empty() {
34		return Ok(None);
35	}
36
37	trace!("Appservice URL \"{dest}\", Appservice ID: {}", registration.id);
38
39	let hs_token = registration.hs_token.as_str();
40	let mut http_request = request
41		.try_into_http_request::<BytesMut>(&dest, SendAccessToken::IfRequired(hs_token), ())
42		.map_err(|e| {
43			err!(BadServerResponse(
44				warn!(appservice = %registration.id, "Failed to find destination {dest}: {e:?}")
45			))
46		})?
47		.map(BytesMut::freeze);
48
49	let mut parts = http_request.uri().clone().into_parts();
50	let old_path_and_query = parts
51		.path_and_query
52		.expect("valid request uri path and query")
53		.as_str()
54		.to_owned();
55
56	let symbol = if old_path_and_query.contains('?') { "&" } else { "?" };
57
58	parts.path_and_query = Some(
59		(old_path_and_query + symbol + "access_token=" + hs_token)
60			.parse()
61			.expect("valid path and query"),
62	);
63	*http_request.uri_mut() = parts
64		.try_into()
65		.expect("our manipulation is always valid");
66
67	let reqwest_request = reqwest::Request::try_from(http_request)?;
68
69	let mut response = client
70		.execute(reqwest_request)
71		.await
72		.map_err(|e| {
73			warn!(
74				"Could not send request to appservice \"{}\" at {dest}: {e:?}",
75				registration.id
76			);
77			e
78		})?;
79
80	// reqwest::Response -> http::Response conversion
81	let status = response.status();
82	let mut http_response_builder = http::Response::builder()
83		.status(status)
84		.version(response.version());
85
86	mem::swap(
87		response.headers_mut(),
88		http_response_builder
89			.headers_mut()
90			.expect("http::response::Builder is usable"),
91	);
92
93	let body = response.bytes().await?; // TODO: handle timeout
94
95	if !status.is_success() {
96		debug_error!("Appservice response bytes: {:?}", utils::string_from_bytes(&body));
97		return Err!(BadServerResponse(warn!(
98			"Appservice \"{}\" returned unsuccessful HTTP response {status} at {dest}",
99			registration.id
100		)));
101	}
102
103	let response = T::IncomingResponse::try_from_http_response(
104		http_response_builder
105			.body(body)
106			.expect("reqwest body is valid http body"),
107	);
108
109	response.map(Some).map_err(|e| {
110		err!(BadServerResponse(warn!(
111			"Appservice \"{}\" returned invalid/malformed response bytes {dest}: {e}",
112			registration.id
113		)))
114	})
115}