jsonrpsee_server/middleware/http/
authority.rs

1// Copyright 2019-2023 Parity Technologies (UK) Ltd.
2//
3// Permission is hereby granted, free of charge, to any
4// person obtaining a copy of this software and associated
5// documentation files (the "Software"), to deal in the
6// Software without restriction, including without
7// limitation the rights to use, copy, modify, merge,
8// publish, distribute, sublicense, and/or sell copies of
9// the Software, and to permit persons to whom the Software
10// is furnished to do so, subject to the following
11// conditions:
12//
13// The above copyright notice and this permission notice
14// shall be included in all copies or substantial portions
15// of the Software.
16//
17// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
18// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
19// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
20// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
21// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
22// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
24// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25// DEALINGS IN THE SOFTWARE.
26
27//! Utility and types related to the authority of an URI.
28
29use crate::HttpRequest;
30use http::uri::{InvalidUri, Uri};
31use jsonrpsee_core::http_helpers;
32
33/// Represent the http URI scheme that is returned by the HTTP host header
34///
35/// Further information can be found: <https://www.rfc-editor.org/rfc/rfc7230#section-2.7.1>
36#[derive(Clone, Hash, PartialEq, Eq, Debug)]
37pub struct Authority {
38	/// The host.
39	pub host: String,
40	/// The port.
41	pub port: Port,
42}
43
44/// Error that can happen when parsing an URI authority fails.
45#[derive(Debug, thiserror::Error)]
46pub enum AuthorityError {
47	/// Invalid URI.
48	#[error(transparent)]
49	InvalidUri(InvalidUri),
50	/// Invalid port.
51	#[error("Invalid port: {0}")]
52	InvalidPort(String),
53	/// The host was not found.
54	#[error("The host was not found")]
55	MissingHost,
56}
57
58/// Port pattern
59#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug)]
60pub enum Port {
61	/// No port specified (default port)
62	Default,
63	/// Port specified as a wildcard pattern (*).
64	Any,
65	/// Fixed numeric port
66	Fixed(u16),
67}
68
69impl Authority {
70	fn inner_from_str(value: &str) -> Result<Self, AuthorityError> {
71		let uri: Uri = value.parse().map_err(AuthorityError::InvalidUri)?;
72		let authority = uri.authority().ok_or(AuthorityError::MissingHost)?;
73		let host = authority.host();
74		let maybe_port = &authority.as_str()[host.len()..];
75
76		// After the host segment, the authority may contain a port such as `fooo:33`, `foo:*` or `foo`
77		let port = match maybe_port.split_once(':') {
78			Some((_, "*")) => Port::Any,
79			Some((_, p)) => {
80				let port_u16: u16 =
81					p.parse().map_err(|e: std::num::ParseIntError| AuthorityError::InvalidPort(e.to_string()))?;
82
83				// Omit default port to allow both requests with and without the default port.
84				match default_port(uri.scheme_str()) {
85					Some(p) if p == port_u16 => Port::Default,
86					_ => port_u16.into(),
87				}
88			}
89			None => Port::Default,
90		};
91
92		Ok(Self { host: host.to_owned(), port })
93	}
94
95	/// Attempts to parse the authority from a HTTP request.
96	///
97	/// The `Authority` can be sent by the client in the `Host header` or in the `URI`
98	/// such that both must be checked.
99	pub fn from_http_request<T>(request: &HttpRequest<T>) -> Option<Self> {
100		// NOTE: we use our own `Authority type` here because an invalid port number would return `None` here
101		// and that should be denied.
102		let host_header =
103			http_helpers::read_header_value(request.headers(), hyper::header::HOST).map(Authority::try_from);
104		let uri = request.uri().authority().map(|v| Authority::try_from(v.as_str()));
105
106		match (host_header, uri) {
107			(Some(Ok(a1)), Some(Ok(a2))) => {
108				if a1 == a2 {
109					Some(a1)
110				} else {
111					None
112				}
113			}
114			(Some(Ok(a)), _) => Some(a),
115			(_, Some(Ok(a))) => Some(a),
116			_ => None,
117		}
118	}
119}
120
121impl<'a> TryFrom<&'a str> for Authority {
122	type Error = AuthorityError;
123
124	fn try_from(value: &'a str) -> Result<Self, Self::Error> {
125		Self::inner_from_str(value)
126	}
127}
128
129impl TryFrom<String> for Authority {
130	type Error = AuthorityError;
131
132	fn try_from(value: String) -> Result<Self, Self::Error> {
133		Self::inner_from_str(&value)
134	}
135}
136
137impl TryFrom<std::net::SocketAddr> for Authority {
138	type Error = AuthorityError;
139
140	fn try_from(sockaddr: std::net::SocketAddr) -> Result<Self, Self::Error> {
141		Self::inner_from_str(&sockaddr.to_string())
142	}
143}
144
145impl From<u16> for Port {
146	fn from(port: u16) -> Port {
147		Port::Fixed(port)
148	}
149}
150
151fn default_port(scheme: Option<&str>) -> Option<u16> {
152	match scheme {
153		Some("http") | Some("ws") => Some(80),
154		Some("https") | Some("wss") => Some(443),
155		Some("ftp") => Some(21),
156		_ => None,
157	}
158}
159
160#[cfg(test)]
161mod tests {
162	use super::{Authority, HttpRequest, Port};
163	use hyper::header::HOST;
164
165	fn authority(host: &str, port: Port) -> Authority {
166		Authority { host: host.to_owned(), port }
167	}
168
169	type EmptyBody = http_body_util::Empty<hyper::body::Bytes>;
170
171	#[test]
172	fn should_parse_valid_authority() {
173		assert_eq!(Authority::try_from("http://parity.io").unwrap(), authority("parity.io", Port::Default));
174		assert_eq!(Authority::try_from("https://parity.io:8443").unwrap(), authority("parity.io", Port::Fixed(8443)));
175		assert_eq!(Authority::try_from("chrome-extension://124.0.0.1").unwrap(), authority("124.0.0.1", Port::Default));
176		assert_eq!(Authority::try_from("http://*.domain:*/somepath").unwrap(), authority("*.domain", Port::Any));
177		assert_eq!(Authority::try_from("parity.io").unwrap(), authority("parity.io", Port::Default));
178		assert_eq!(Authority::try_from("127.0.0.1:8845").unwrap(), authority("127.0.0.1", Port::Fixed(8845)));
179		assert_eq!(
180			Authority::try_from("http://[2001:db8:85a3:8d3:1319:8a2e:370:7348]:9933/").unwrap(),
181			authority("[2001:db8:85a3:8d3:1319:8a2e:370:7348]", Port::Fixed(9933))
182		);
183		assert_eq!(
184			Authority::try_from("http://[2001:db8:85a3:8d3:1319:8a2e:370:7348]/").unwrap(),
185			authority("[2001:db8:85a3:8d3:1319:8a2e:370:7348]", Port::Default)
186		);
187		assert_eq!(
188			Authority::try_from("https://user:password@example.com/tmp/foo").unwrap(),
189			authority("example.com", Port::Default)
190		);
191	}
192
193	#[test]
194	fn should_not_parse_invalid_authority() {
195		assert!(Authority::try_from("/foo/bar").is_err());
196		assert!(Authority::try_from("user:password").is_err());
197		assert!(Authority::try_from("parity.io/somepath").is_err());
198		assert!(Authority::try_from("127.0.0.1:8545/somepath").is_err());
199		assert!(Authority::try_from("127.0.0.1:-1337").is_err());
200	}
201
202	#[test]
203	fn authority_from_http_only_host_works() {
204		let req = HttpRequest::builder().header(HOST, "example.com").body(EmptyBody::new()).unwrap();
205		assert!(Authority::from_http_request(&req).is_some());
206	}
207
208	#[test]
209	fn authority_only_uri_works() {
210		let req = HttpRequest::builder().uri("example.com").body(EmptyBody::new()).unwrap();
211		assert!(Authority::from_http_request(&req).is_some());
212	}
213
214	#[test]
215	fn authority_host_and_uri_works() {
216		let req = HttpRequest::builder()
217			.header(HOST, "example.com:9999")
218			.uri("example.com:9999")
219			.body(EmptyBody::new())
220			.unwrap();
221		assert!(Authority::from_http_request(&req).is_some());
222	}
223
224	#[test]
225	fn authority_host_and_uri_mismatch() {
226		let req =
227			HttpRequest::builder().header(HOST, "example.com:9999").uri("example.com").body(EmptyBody::new()).unwrap();
228		assert!(Authority::from_http_request(&req).is_none());
229	}
230
231	#[test]
232	fn authority_missing_host_and_uri() {
233		let req = HttpRequest::builder().body(EmptyBody::new()).unwrap();
234		assert!(Authority::from_http_request(&req).is_none());
235	}
236}