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		matrix_response(status, error.kind(), error.message())
52	}
53}
54
55/// Whether a remote server's error may be repeated to a local client.
56///
57/// A remote answers only for the resource it was asked about, so a kind
58/// describing the caller's own session or this server's state is withheld: the
59/// client has no way to tell the two apart and would act on it as ours.
60fn is_relayable(kind: &ErrorKind) -> bool {
61	use ErrorKind::*;
62
63	matches!(
64		kind,
65		Forbidden
66			| NotFound
67			| UnsupportedRoomVersion
68			| IncompatibleRoomVersion(..)
69			| InviteBlocked
70			| UnableToAuthorizeJoin
71			| UnableToGrantJoin
72	)
73}
74
75fn withheld_remote_error(origin: &ServerName) -> UiaaResponse {
76	let message = format!("Request to {origin} failed.");
77
78	matrix_response(StatusCode::BAD_GATEWAY, ErrorKind::Unknown, message)
79}
80
81fn matrix_response(status: StatusCode, kind: ErrorKind, message: String) -> UiaaResponse {
82	let body = ErrorBody::Standard(StandardErrorBody { kind, message });
83
84	UiaaResponse::MatrixError(RumaError::new(status, body))
85}
86
87pub(super) fn status_code(kind: &ErrorKind, hint: StatusCode) -> StatusCode {
88	if hint == StatusCode::BAD_REQUEST {
89		bad_request_code(kind)
90	} else {
91		hint
92	}
93}
94
95pub(super) fn bad_request_code(kind: &ErrorKind) -> StatusCode {
96	use ErrorKind::*;
97
98	match kind {
99		// 504
100		| NotYetUploaded | ConnectionTimeout => StatusCode::GATEWAY_TIMEOUT,
101
102		// 502
103		| BadStatus(..) | ConnectionFailed => StatusCode::BAD_GATEWAY,
104
105		// 429
106		| LimitExceeded { .. } => StatusCode::TOO_MANY_REQUESTS,
107
108		// 413
109		| TooLarge => StatusCode::PAYLOAD_TOO_LARGE,
110
111		// 409
112		| CannotOverwriteMedia => StatusCode::CONFLICT,
113
114		// 404
115		| NotFound | NotImplemented | FeatureDisabled | Unrecognized => StatusCode::NOT_FOUND,
116
117		// 403
118		| GuestAccessForbidden
119		| ThreepidAuthFailed
120		| UserDeactivated
121		| ThreepidDenied
122		| InviteBlocked
123		| WrongRoomKeysVersion { .. }
124		| Forbidden => StatusCode::FORBIDDEN,
125
126		// 401
127		| UnknownToken { .. } | MissingToken | Unauthorized => StatusCode::UNAUTHORIZED,
128
129		// 400
130		| _ => StatusCode::BAD_REQUEST,
131	}
132}
133
134pub(super) fn ruma_error_message(error: &RumaError) -> String {
135	if let ErrorBody::Standard(StandardErrorBody { message, .. }) = &error.body {
136		return message.clone();
137	}
138
139	format!("{error}")
140}
141
142pub(super) fn ruma_error_kind(e: &RumaError) -> &ErrorKind {
143	e.error_kind().unwrap_or(&ErrorKind::Unknown)
144}
145
146pub(super) fn io_error_code(kind: std::io::ErrorKind) -> StatusCode {
147	use std::io::ErrorKind;
148
149	match kind {
150		| ErrorKind::InvalidInput => StatusCode::BAD_REQUEST,
151		| ErrorKind::PermissionDenied => StatusCode::FORBIDDEN,
152		| ErrorKind::NotFound => StatusCode::NOT_FOUND,
153		| ErrorKind::TimedOut => StatusCode::GATEWAY_TIMEOUT,
154		| ErrorKind::FileTooLarge => StatusCode::PAYLOAD_TOO_LARGE,
155		| ErrorKind::StorageFull => StatusCode::INSUFFICIENT_STORAGE,
156		| ErrorKind::Interrupted => StatusCode::SERVICE_UNAVAILABLE,
157		| _ => StatusCode::INTERNAL_SERVER_ERROR,
158	}
159}