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