rs_jsonrpc_server_utils/
hosts.rs1use std::collections::HashSet;
4use std::net::SocketAddr;
5use matcher::{Matcher, Pattern};
6
7const SPLIT_PROOF: &'static str = "split always returns non-empty iterator.";
8
9#[derive(Clone, Hash, PartialEq, Eq, Debug)]
11pub enum Port {
12 None,
14 Pattern(String),
16 Fixed(u16)
18}
19
20impl From<Option<u16>> for Port {
21 fn from(opt: Option<u16>) -> Self {
22 match opt {
23 Some(port) => Port::Fixed(port),
24 None => Port::None,
25 }
26 }
27}
28
29impl From<u16> for Port {
30 fn from(port: u16) -> Port {
31 Port::Fixed(port)
32 }
33}
34
35#[derive(Clone, Hash, PartialEq, Eq, Debug)]
37pub struct Host {
38 hostname: String,
39 port: Port,
40 as_string: String,
41 matcher: Matcher,
42}
43
44impl<T: AsRef<str>> From<T> for Host {
45 fn from(string: T) -> Self {
46 Host::parse(string.as_ref())
47 }
48}
49
50impl Host {
51 pub fn new<T: Into<Port>>(hostname: &str, port: T) -> Self {
53 let port = port.into();
54 let hostname = Self::pre_process(hostname);
55 let string = Self::to_string(&hostname, &port);
56 let matcher = Matcher::new(&string);
57
58 Host {
59 hostname: hostname,
60 port: port,
61 as_string: string,
62 matcher: matcher,
63 }
64 }
65
66 pub fn parse(hostname: &str) -> Self {
69 let hostname = Self::pre_process(hostname);
70 let mut hostname = hostname.split(':');
71 let host = hostname.next().expect(SPLIT_PROOF);
72 let port = match hostname.next() {
73 None => Port::None,
74 Some(port) => match port.clone().parse::<u16>().ok() {
75 Some(num) => Port::Fixed(num),
76 None => Port::Pattern(port.into()),
77 }
78 };
79
80 Host::new(host, port)
81 }
82
83 fn pre_process(host: &str) -> String {
84 let mut it = host.split("://");
86 let protocol = it.next().expect(SPLIT_PROOF);
87 let host = match it.next() {
88 Some(data) => data,
89 None => protocol,
90 };
91
92 let mut it = host.split('/');
93 it.next().expect(SPLIT_PROOF).to_lowercase()
94 }
95
96 fn to_string(hostname: &str, port: &Port) -> String {
97 format!(
98 "{}{}",
99 hostname,
100 match *port {
101 Port::Fixed(port) => format!(":{}", port),
102 Port::Pattern(ref port) => format!(":{}", port),
103 Port::None => "".into(),
104 },
105 )
106 }
107}
108
109impl Pattern for Host {
110 fn matches<T: AsRef<str>>(&self, other: T) -> bool {
111 self.matcher.matches(other)
112 }
113}
114
115impl ::std::ops::Deref for Host {
116 type Target = str;
117 fn deref(&self) -> &Self::Target {
118 &self.as_string
119 }
120}
121
122#[derive(Clone, Debug, PartialEq, Eq)]
124pub enum DomainsValidation<T> {
125 AllowOnly(Vec<T>),
127 Disabled,
129}
130
131impl<T> Into<Option<Vec<T>>> for DomainsValidation<T> {
132 fn into(self) -> Option<Vec<T>> {
133 use self::DomainsValidation::*;
134 match self {
135 AllowOnly(list) => Some(list),
136 Disabled => None,
137 }
138 }
139}
140
141impl<T> From<Option<Vec<T>>> for DomainsValidation<T> {
142 fn from(other: Option<Vec<T>>) -> Self {
143 match other {
144 Some(list) => DomainsValidation::AllowOnly(list),
145 None => DomainsValidation::Disabled,
146 }
147 }
148}
149
150pub fn is_host_valid(host: Option<&str>, allowed_hosts: &Option<Vec<Host>>) -> bool {
152 match allowed_hosts.as_ref() {
153 None => true,
154 Some(ref allowed_hosts) => match host {
155 None => false,
156 Some(ref host) => {
157 allowed_hosts.iter().any(|h| h.matches(host))
158 }
159 }
160 }
161}
162
163pub fn update(hosts: Option<Vec<Host>>, address: &SocketAddr) -> Option<Vec<Host>> {
165 hosts.map(|current_hosts| {
166 let mut new_hosts = current_hosts.into_iter().collect::<HashSet<_>>();
167 let address = address.to_string();
168 new_hosts.insert(address.clone().into());
169 new_hosts.insert(address.replace("127.0.0.1", "localhost").into());
170 new_hosts.into_iter().collect()
171 })
172}
173
174#[cfg(test)]
175mod tests {
176 use super::{Host, is_host_valid};
177
178 #[test]
179 fn should_parse_host() {
180 assert_eq!(Host::parse("http://superstring.ch"), Host::new("superstring.ch", None));
181 assert_eq!(Host::parse("http://superstring.ch:8443"), Host::new("superstring.ch", Some(8443)));
182 assert_eq!(Host::parse("chrome-extension://124.0.0.1"), Host::new("124.0.0.1", None));
183 assert_eq!(Host::parse("superstring.ch/somepath"), Host::new("superstring.ch", None));
184 assert_eq!(Host::parse("127.0.0.1:8545/somepath"), Host::new("127.0.0.1", Some(8545)));
185 }
186
187 #[test]
188 fn should_reject_when_there_is_no_header() {
189 let valid = is_host_valid(None, &Some(vec![]));
190 assert_eq!(valid, false);
191 }
192
193 #[test]
194 fn should_reject_when_validation_is_disabled() {
195 let valid = is_host_valid(Some("any"), &None);
196 assert_eq!(valid, true);
197 }
198
199 #[test]
200 fn should_reject_if_header_not_on_the_list() {
201 let valid = is_host_valid(Some("superstring.ch"), &Some(vec![]));
202 assert_eq!(valid, false);
203 }
204
205 #[test]
206 fn should_accept_if_on_the_list() {
207 let valid = is_host_valid(
208 Some("superstring.ch"),
209 &Some(vec!["superstring.ch".into()]),
210 );
211 assert_eq!(valid, true);
212 }
213
214 #[test]
215 fn should_accept_if_on_the_list_with_port() {
216 let valid = is_host_valid(
217 Some("superstring.ch:443"),
218 &Some(vec!["superstring.ch:443".into()]),
219 );
220 assert_eq!(valid, true);
221 }
222
223 #[test]
224 fn should_support_wildcards() {
225 let valid = is_host_valid(
226 Some("s.web3.site:8180"),
227 &Some(vec!["*.web3.site:*".into()]),
228 );
229 assert_eq!(valid, true);
230 }
231}