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

            
29
use crate::HttpRequest;
30
use http::uri::{InvalidUri, Uri};
31
use 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)]
37
pub 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)]
46
pub enum AuthorityError {
47
	/// Invalid URI.
48
	#[error("{0}")]
49
	InvalidUri(InvalidUri),
50
	/// Invalid port.
51
	#[error("{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)]
60
pub 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

            
69
impl 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

            
121
impl<'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

            
129
impl 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

            
137
impl 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

            
145
impl From<u16> for Port {
146
	fn from(port: u16) -> Port {
147
		Port::Fixed(port)
148
	}
149
}
150

            
151
fn 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)]
161
mod 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
}