Skip to main content

tuwunel_core/error/
response.rs

1use bytes::BytesMut;
2use http::StatusCode;
3use http_body_util::Full;
4use ruma::{
5	ServerName,
6	api::{
7		OutgoingResponse,
8		client::uiaa::UiaaResponse,
9		error::{Error as RumaError, ErrorBody, ErrorKind, StandardErrorBody},
10	},
11};
12
13use super::Error;
14use crate::error;
15
16impl axum::response::IntoResponse for Error {
17	fn into_response(self) -> axum::response::Response {
18		let response: UiaaResponse = self.into();
19		response
20			.try_into_http_response::<BytesMut>()
21			.inspect_err(|e| error!("error response error: {e}"))
22			.map_or_else(
23				|_| StatusCode::INTERNAL_SERVER_ERROR.into_response(),
24				|r| {
25					r.map(BytesMut::freeze)
26						.map(Full::new)
27						.into_response()
28				},
29			)
30	}
31}
32
33impl From<Error> for UiaaResponse {
34	#[inline]
35	fn from(error: Error) -> Self {
36		if let Error::Uiaa(uiaainfo) = error {
37			return Self::AuthResponse(uiaainfo);
38		}
39
40		let status = match &error {
41			| Error::Federation(origin, remote) if !is_relayable(ruma_error_kind(remote)) =>
42				return withheld_remote_error(origin),
43
44			// A remote's 401 reads to a client as its own session failing.
45			| Error::Federation(..) if error.status_code() == StatusCode::UNAUTHORIZED =>
46				StatusCode::BAD_REQUEST,
47
48			| _ => error.status_code(),
49		};
50
51		_ = status
52			.is_server_error()
53			.then(|| error!(?error, "request failed"));
54
55		matrix_response(status, error.kind(), error.sanitized_message())
56	}
57}
58
59/// Whether a remote server's error may be repeated to a local client.
60///
61/// A remote answers only for the resource it was asked about, so a kind
62/// describing the caller's own session or this server's state is withheld: the
63/// client has no way to tell the two apart and would act on it as ours.
64fn is_relayable(kind: &ErrorKind) -> bool {
65	use ErrorKind::*;
66
67	matches!(
68		kind,
69		Forbidden
70			| NotFound
71			| UnsupportedRoomVersion
72			| IncompatibleRoomVersion(..)
73			| InviteBlocked
74			| UnableToAuthorizeJoin
75			| UnableToGrantJoin
76	)
77}
78
79fn withheld_remote_error(origin: &ServerName) -> UiaaResponse {
80	let message = format!("Request to {origin} failed.");
81
82	matrix_response(StatusCode::BAD_GATEWAY, ErrorKind::Unknown, message)
83}
84
85fn matrix_response(status: StatusCode, kind: ErrorKind, message: String) -> UiaaResponse {
86	let body = ErrorBody::Standard(StandardErrorBody { kind, message });
87
88	UiaaResponse::MatrixError(RumaError::new(status, body))
89}
90
91pub(super) fn status_code(kind: &ErrorKind, hint: StatusCode) -> StatusCode {
92	if hint == StatusCode::BAD_REQUEST {
93		bad_request_code(kind)
94	} else {
95		hint
96	}
97}
98
99pub(super) fn bad_request_code(kind: &ErrorKind) -> StatusCode {
100	use ErrorKind::*;
101
102	match kind {
103		// 504
104		| NotYetUploaded | ConnectionTimeout => StatusCode::GATEWAY_TIMEOUT,
105
106		// 502
107		| BadStatus(..) | ConnectionFailed => StatusCode::BAD_GATEWAY,
108
109		// 429
110		| LimitExceeded { .. } => StatusCode::TOO_MANY_REQUESTS,
111
112		// 413
113		| TooLarge => StatusCode::PAYLOAD_TOO_LARGE,
114
115		// 409
116		| CannotOverwriteMedia => StatusCode::CONFLICT,
117
118		// 404
119		| NotFound | NotImplemented | FeatureDisabled | Unrecognized => StatusCode::NOT_FOUND,
120
121		// 403
122		| GuestAccessForbidden
123		| ThreepidAuthFailed
124		| UserDeactivated
125		| UserSuspended
126		| ThreepidDenied
127		| InviteBlocked
128		| CannotLeaveServerNoticeRoom
129		| WrongRoomKeysVersion { .. }
130		| Forbidden => StatusCode::FORBIDDEN,
131
132		// 401
133		| UnknownToken { .. } | MissingToken | Unauthorized | UserLocked =>
134			StatusCode::UNAUTHORIZED,
135
136		// 400
137		| _ => StatusCode::BAD_REQUEST,
138	}
139}
140
141pub(super) fn ruma_error_message(error: &RumaError) -> String {
142	if let ErrorBody::Standard(StandardErrorBody { message, .. }) = &error.body {
143		return message.clone();
144	}
145
146	format!("{error}")
147}
148
149pub(super) fn ruma_error_kind(e: &RumaError) -> &ErrorKind {
150	e.error_kind().unwrap_or(&ErrorKind::Unknown)
151}
152
153pub(super) fn io_error_code(kind: std::io::ErrorKind) -> StatusCode {
154	use std::io::ErrorKind;
155
156	match kind {
157		| ErrorKind::InvalidInput => StatusCode::BAD_REQUEST,
158		| ErrorKind::PermissionDenied => StatusCode::FORBIDDEN,
159		| ErrorKind::NotFound => StatusCode::NOT_FOUND,
160		| ErrorKind::TimedOut => StatusCode::GATEWAY_TIMEOUT,
161		| ErrorKind::FileTooLarge => StatusCode::PAYLOAD_TOO_LARGE,
162		| ErrorKind::StorageFull => StatusCode::INSUFFICIENT_STORAGE,
163		| ErrorKind::Interrupted => StatusCode::SERVICE_UNAVAILABLE,
164		| _ => StatusCode::INTERNAL_SERVER_ERROR,
165	}
166}