relay_filter/
localhost.rs1use crate::{FilterConfig, FilterStatKey, Filterable};
4
5const LOCAL_IPS: &[&str] = &["127.0.0.1", "::1"];
6const LOCAL_DOMAINS: &[&str] = &["127.0.0.1", "localhost"];
7
8const FORWARDED_HOST_HEADER: &str = "X-Forwarded-Host";
9
10const HOST_HEADER: &str = "Host";
11
12fn matches<F: Filterable>(item: &F) -> bool {
14 if let Some(ip_addr) = item.ip_addr() {
15 if LOCAL_IPS.contains(&ip_addr) {
16 return true;
17 }
18 }
19
20 if let Some(url) = item.url() {
21 if url.scheme() == "file" {
22 return true;
23 }
24
25 if let Some(host) = url.host_str() {
26 for &local_domain in LOCAL_DOMAINS {
27 if host_matches_or_is_subdomain_of(host, local_domain) {
28 return true;
29 }
30 }
31 }
32 }
33
34 for header_name in [HOST_HEADER, FORWARDED_HOST_HEADER] {
35 if let Some(header) = item.header(header_name) {
36 if let Some(domain_part) = header.split(":").next() {
37 if LOCAL_DOMAINS.contains(&domain_part) {
39 return true;
40 }
41 }
42 }
43 }
44
45 false
46}
47
48fn host_matches_or_is_subdomain_of(host: &str, domain: &str) -> bool {
49 host.strip_suffix(domain)
50 .is_some_and(|s| s.is_empty() || s.ends_with('.'))
51}
52
53pub fn should_filter<F: Filterable>(item: &F, config: &FilterConfig) -> Result<(), FilterStatKey> {
55 if !config.is_enabled {
56 return Ok(());
57 }
58 if matches(item) {
59 return Err(FilterStatKey::Localhost);
60 }
61 Ok(())
62}
63
64#[cfg(test)]
65mod tests {
66 use relay_event_schema::protocol::{
67 Event, HeaderName, HeaderValue, Headers, IpAddr, PairList, Request, User,
68 };
69 use relay_protocol::Annotated;
70
71 use super::*;
72
73 fn get_event_with_ip_addr(val: &str) -> Event {
74 Event {
75 user: Annotated::from(User {
76 ip_address: Annotated::from(IpAddr(val.to_string())),
77 ..User::default()
78 }),
79 ..Event::default()
80 }
81 }
82
83 fn get_event_with_domain(val: &str) -> Event {
84 Event {
85 request: Annotated::from(Request {
86 url: Annotated::from(format!("http://{val}:8080/")),
87 ..Request::default()
88 }),
89 ..Event::default()
90 }
91 }
92
93 fn get_event_with_url(val: &str) -> Event {
94 Event {
95 request: Annotated::from(Request {
96 url: Annotated::from(val.to_string()),
97 ..Request::default()
98 }),
99 ..Event::default()
100 }
101 }
102
103 fn get_event_with_header(key: &str, value: &str) -> Event {
104 Event {
105 request: Annotated::from(Request {
106 headers: Annotated::from(Headers(PairList(vec![Annotated::new((
107 Annotated::new(HeaderName::new(key)),
108 Annotated::new(HeaderValue::new(value)),
109 ))]))),
110 ..Default::default()
111 }),
112 ..Event::default()
113 }
114 }
115
116 #[test]
117 fn test_dont_filter_when_disabled() {
118 for event in &[
119 get_event_with_ip_addr("127.0.0.1"),
120 get_event_with_domain("localhost"),
121 ] {
122 let filter_result = should_filter(event, &FilterConfig { is_enabled: false });
123 assert_eq!(
124 filter_result,
125 Ok(()),
126 "Event filtered although filter should have been disabled."
127 );
128 }
129 }
130
131 #[test]
132 fn test_filter_local_ip() {
133 for ip_addr in &["127.0.0.1", "::1"] {
134 let event = get_event_with_ip_addr(ip_addr);
135 let filter_result = should_filter(&event, &FilterConfig { is_enabled: true });
136 assert_ne!(
137 filter_result,
138 Ok(()),
139 "Failed to filter address '{ip_addr}'"
140 );
141 }
142 }
143
144 #[test]
145 fn test_dont_filter_non_local_ip() {
146 for ip_addr in &["133.12.12.1", "2001:db8:0:0:0:ff00:42:8329"] {
147 let event = get_event_with_ip_addr(ip_addr);
148 let filter_result = should_filter(&event, &FilterConfig { is_enabled: true });
149 assert_eq!(
150 filter_result,
151 Ok(()),
152 "Filtered valid ip address '{ip_addr}'"
153 );
154 }
155 }
156
157 #[test]
158 fn test_dont_filter_missing_ip_or_domains() {
159 let event = Event::default();
160 let filter_result = should_filter(&event, &FilterConfig { is_enabled: true });
161 assert_eq!(
162 filter_result,
163 Ok(()),
164 "Filtered event with no ip address and no domain."
165 );
166 }
167
168 #[test]
169 fn test_filter_local_domains() {
170 for domain in &[
171 "127.0.0.1",
172 "localhost",
173 "foo.localhost",
174 "foo.bar.baz.localhost",
175 ] {
176 let event = get_event_with_domain(domain);
177 let filter_result = should_filter(&event, &FilterConfig { is_enabled: true });
178 assert_ne!(filter_result, Ok(()), "Failed to filter domain '{domain}'");
179 }
180 }
181
182 #[test]
183 fn test_dont_filter_non_local_domains() {
184 for domain in &[
185 "my.dom.com",
186 "123.123.123.44",
187 "localhost.com",
188 "foolocalhost",
189 "localhostbar",
190 "alocalhostgoesintoabar",
191 ] {
192 let event = get_event_with_domain(domain);
193 let filter_result = should_filter(&event, &FilterConfig { is_enabled: true });
194 assert_eq!(
195 filter_result,
196 Ok(()),
197 "Filtered perfectly valid domain '{domain}'"
198 );
199 }
200 }
201
202 #[test]
203 fn test_filter_file_urls() {
204 let url = "file:///Users/Maisey/work/squirrelchasers/src/leaderboard.html";
205 let event = get_event_with_url(url);
206 let filter_result = should_filter(&event, &FilterConfig { is_enabled: true });
207 assert_ne!(
208 filter_result,
209 Ok(()),
210 "Failed to filter event with url '{url}'"
211 );
212 }
213
214 #[test]
215 fn test_dont_filter_non_file_urls() {
216 let url = "http://www.squirrelchasers.com/leaderboard";
217 let event = get_event_with_url(url);
218 let filter_result = should_filter(&event, &FilterConfig { is_enabled: true });
219 assert_eq!(filter_result, Ok(()), "Filtered valid url '{url}'");
220 }
221
222 #[test]
223 fn test_filter_forwarded_host_header() {
224 for host_value in ["localhost:3000", "127.0.0.1:3000", "localhost"] {
225 let event = get_event_with_header(FORWARDED_HOST_HEADER, host_value);
226 let filter_result = should_filter(&event, &FilterConfig { is_enabled: true });
227 assert_eq!(filter_result, Err(FilterStatKey::Localhost))
228 }
229 }
230
231 #[test]
232 fn test_filter_request_host_header() {
233 for host_value in ["localhost:3000", "127.0.0.1:3000", "localhost"] {
234 let event = get_event_with_header(HOST_HEADER, host_value);
235 let filter_result = should_filter(&event, &FilterConfig { is_enabled: true });
236 assert_eq!(filter_result, Err(FilterStatKey::Localhost))
237 }
238 }
239
240 #[test]
241 fn test_filter_request_subdomain_host_header() {
242 for domain in ["localhost.sentry.io", "localhost.sentry.io:3000"] {
243 let event = get_event_with_header(HOST_HEADER, domain);
244 let filter_result = should_filter(&event, &FilterConfig { is_enabled: true });
245 assert_eq!(filter_result, Ok(()))
246 }
247 }
248
249 #[test]
250 fn test_filter_request_subdomain_forwarded_host_header() {
251 for domain in ["localhost.sentry.io", "localhost.sentry.io:3000"] {
252 let event = get_event_with_header(FORWARDED_HOST_HEADER, domain);
253 let filter_result = should_filter(&event, &FilterConfig { is_enabled: true });
254 assert_eq!(filter_result, Ok(()))
255 }
256 }
257}