1use std::collections::BTreeSet;
4
5use relay_ua::UserAgent;
6
7use crate::{FilterStatKey, Filterable, LegacyBrowser, LegacyBrowsersFilterConfig};
8
9fn matches(user_agent: &str, browsers: &BTreeSet<LegacyBrowser>) -> bool {
11 let user_agent = relay_ua::parse_user_agent(user_agent);
12
13 let family = match user_agent.family.as_ref() {
15 "IE Mobile" => "IE",
16 other => other,
17 };
18
19 if browsers.contains(&LegacyBrowser::Default) {
20 return default_filter(family, &user_agent);
21 }
22
23 for browser_type in browsers {
24 let should_filter = match browser_type {
25 LegacyBrowser::IePre9 => filter_browser(family, &user_agent, "IE", |x| x <= 8),
26 LegacyBrowser::Ie9 => filter_browser(family, &user_agent, "IE", |x| x == 9),
27 LegacyBrowser::Ie10 => filter_browser(family, &user_agent, "IE", |x| x == 10),
28 LegacyBrowser::Ie11 => filter_browser(family, &user_agent, "IE", |x| x == 11),
29 LegacyBrowser::OperaMiniPre8 => {
30 filter_browser(family, &user_agent, "Opera Mini", |x| x < 8)
31 }
32 LegacyBrowser::OperaPre15 => filter_browser(family, &user_agent, "Opera", |x| x < 15),
33 LegacyBrowser::AndroidPre4 => filter_browser(family, &user_agent, "Android", |x| x < 4),
34 LegacyBrowser::SafariPre6 => filter_browser(family, &user_agent, "Safari", |x| x < 6),
35 LegacyBrowser::EdgePre79 => filter_browser(family, &user_agent, "Edge", |x| x < 79),
36 LegacyBrowser::Ie => filter_browser(family, &user_agent, "IE", |x| x < 12),
37 LegacyBrowser::OperaMini => {
38 filter_browser(family, &user_agent, "Opera Mini", |x| x < 35)
39 }
40 LegacyBrowser::Opera => filter_browser(family, &user_agent, "Opera", |x| x < 51),
41 LegacyBrowser::Android => filter_browser(family, &user_agent, "Android", |x| x < 4),
42 LegacyBrowser::Safari => filter_browser(family, &user_agent, "Safari", |x| x < 12),
43 LegacyBrowser::Edge => filter_browser(family, &user_agent, "Edge", |x| x < 79),
44 LegacyBrowser::Chrome => filter_browser(family, &user_agent, "Chrome", |x| x < 64),
45 LegacyBrowser::Firefox => filter_browser(family, &user_agent, "Firefox", |x| x < 67),
46 LegacyBrowser::Unknown(_) => {
47 false
49 }
50 LegacyBrowser::Default => unreachable!(),
51 };
52 if should_filter {
53 return true;
54 }
55 }
56 false
57}
58
59pub fn should_filter<F: Filterable>(
61 item: &F,
62 config: &LegacyBrowsersFilterConfig,
63) -> Result<(), FilterStatKey> {
64 if !config.is_enabled || config.browsers.is_empty() {
65 return Ok(()); }
67
68 let browsers = &config.browsers;
69 if item.user_agent().is_some_and(|ua| matches(ua, browsers)) {
70 Err(FilterStatKey::LegacyBrowsers)
71 } else {
72 Ok(())
73 }
74}
75
76fn get_browser_major_version(user_agent: &UserAgent) -> Option<i32> {
77 if let Some(browser_major_version_str) = &user_agent.major {
78 if let Ok(browser_major_version) = browser_major_version_str.parse::<i32>() {
79 return Some(browser_major_version);
80 }
81 }
82
83 None
84}
85
86fn min_version(family: &str) -> Option<i32> {
87 match family {
88 "Chrome" => Some(0),
89 "IE" => Some(10),
90 "Firefox" => Some(0),
91 "Safari" => Some(6),
92 "Edge" => Some(0),
93 "Opera" => Some(15),
94 "Android" => Some(4),
95 "Opera Mini" => Some(8),
96 _ => None,
97 }
98}
99
100fn default_filter(mapped_family: &str, user_agent: &UserAgent) -> bool {
101 if let Some(browser_major_version) = get_browser_major_version(user_agent) {
102 if let Some(min_version) = min_version(mapped_family) {
103 if min_version > browser_major_version {
104 return true;
105 }
106 }
107 }
108 false
109}
110
111fn filter_browser<F>(
112 mapped_family: &str,
113 user_agent: &UserAgent,
114 family: &str,
115 should_filter: F,
116) -> bool
117where
118 F: FnOnce(i32) -> bool,
119{
120 if mapped_family == family {
121 if let Some(browser_major_version) = get_browser_major_version(user_agent) {
122 if should_filter(browser_major_version) {
123 return true;
124 }
125 }
126 }
127 false
128}
129
130#[cfg(test)]
131mod tests {
132 const IE8_UA: &str = "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)";
133 const IE_MOBILE9_UA: &str = "Mozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; Trident/5.0; IEMobile/9.0; NOKIA; Lumia 710)";
134 const IE9_UA: &str = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 7.1; Trident/5.0)";
135 const IE10_UA: &str = "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)";
136 const IE11_UA: &str = "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko";
137 const OPERA_MINI_PRE8_UA: &str =
138 "Opera/9.80 (J2ME/MIDP; Opera Mini/7.0.32796/59.323; U; fr) Presto/2.12.423 Version/12.16";
139 const OPERA_MINI_8_UA: &str =
140 "Opera/9.80 (J2ME/MIDP; Opera Mini/8.0.35158/36.2534; U; en) Presto/2.12.423 Version/12.16";
141 const OPERA_PRE15_UA: &str = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.12 Safari/537.36 OPR/14.0.1116.4";
142 const OPERA_15_UA: &str = "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.45 Safari/537.36 OPR/15.0.1147.61 (Edition Next)";
143 const ANDROID_PRE4_UA: &str = "Mozilla/5.0 (Linux; U; Android 3.2; nl-nl; GT-P6800 Build/HTJ85B) AppleWebKit/534.13 (KHTML, like Gecko) Version/4.0 Safari/534.13";
144 const ANDROID_4_UA: &str = "Mozilla/5.0 (Linux; U; Android 4.1.1; en-gb; Build/KLP) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30";
145 const SAFARI_PRE6_UA: &str = "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 1063; tr-DE) AppleWebKit/533.16 (KHTML like Gecko) Version/5.0 Safari/533.16";
146 const SAFARI_6_UA: &str = "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.17.4; en-GB) AppleWebKit/605.1.5 (KHTML, like Gecko) Version/6.0 Safari/605.1.5";
147 const EDGE_ANDROID_118_UA: &str = "Mozilla/5.0 (Linux; Android 10; Pixel 3 XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.5993.80 Mobile Safari/537.36 EdgA/118.0.2088.58";
148 const EDGE_79_UA: &str = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3919.0 Safari/537.36 Edg/79.0.294.1";
149 const EDGE_18_UA: &str = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.19582";
150 const EDGE_12_UA: &str = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.246";
151 const OPERA_UA: &str = "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.45 Safari/537.36 OPR/49.0.1147.61";
152 const OPERA_MINI_UA: &str = "Opera/20.80 (J2ME/MIDP; Opera Mini/16.0.35158/36.2534; U; en) Presto/2.12.423 Version/12.16";
153 const CHROME_UA: &str = "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.2228.0 Safari/537.36";
154 const FIREFOX_UA: &str = "Mozilla/5.0 (Windows NT 6.1; rv:60.0) Gecko/20100101 Firefox/60.0";
155 const IE_UA: &str = "Mozilla/5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Gecko";
156 const EDGE_UA: &str = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.19582";
157 const SAFARI_UA: &str = "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.17.4; en-GB) AppleWebKit/605.1.5 (KHTML, like Gecko) Version/6.0 Safari/605.1.5";
158 const ANDROID_UA: &str = "Mozilla/5.0 (Linux; U; Android 3.2; nl-nl; GT-P6800 Build/HTJ85B) AppleWebKit/534.13 (KHTML, like Gecko) Version/4.0 Safari/534.13";
159
160 use super::*;
161 use crate::testutils;
162
163 fn get_legacy_browsers_config(
164 is_enabled: bool,
165 legacy_browsers: &[LegacyBrowser],
166 ) -> LegacyBrowsersFilterConfig {
167 LegacyBrowsersFilterConfig {
168 is_enabled,
169 browsers: {
170 let mut browsers = BTreeSet::<LegacyBrowser>::new();
171 for elm in legacy_browsers {
172 browsers.insert(elm.clone());
173 }
174 browsers
175 },
176 }
177 }
178
179 #[test]
180 fn test_dont_filter_when_disabled() {
181 let evt = testutils::get_event_with_user_agent(IE8_UA);
182 let filter_result = should_filter(
183 &evt,
184 &get_legacy_browsers_config(false, &[LegacyBrowser::Default]),
185 );
186 assert_eq!(
187 filter_result,
188 Ok(()),
189 "Event filtered although filter should have been disabled"
190 )
191 }
192
193 #[test]
194 fn test_filter_default_browsers() {
195 for old_user_agent in &[
196 IE9_UA,
197 IE_MOBILE9_UA,
198 SAFARI_PRE6_UA,
199 OPERA_PRE15_UA,
200 ANDROID_PRE4_UA,
201 OPERA_MINI_PRE8_UA,
202 ] {
203 let evt = testutils::get_event_with_user_agent(old_user_agent);
204 let filter_result = should_filter(
205 &evt,
206 &get_legacy_browsers_config(true, &[LegacyBrowser::Default]),
207 );
208 assert_ne!(
209 filter_result,
210 Ok(()),
211 "Default filter should have filtered User Agent\n{old_user_agent}"
212 )
213 }
214 }
215
216 #[test]
217 fn test_dont_filter_default_above_minimum_versions() {
218 for old_user_agent in &[
219 IE10_UA,
220 SAFARI_6_UA,
221 OPERA_15_UA,
222 ANDROID_4_UA,
223 OPERA_MINI_8_UA,
224 ] {
225 let evt = testutils::get_event_with_user_agent(old_user_agent);
226 let filter_result = should_filter(
227 &evt,
228 &get_legacy_browsers_config(true, &[LegacyBrowser::Default]),
229 );
230 assert_eq!(
231 filter_result,
232 Ok(()),
233 "Default filter shouldn't have filtered User Agent\n{old_user_agent}"
234 )
235 }
236 }
237
238 #[test]
239 fn test_filter_configured_browsers() {
240 let test_configs = [
241 (
242 IE10_UA,
243 &[LegacyBrowser::AndroidPre4, LegacyBrowser::Ie10][..],
244 ),
245 (IE11_UA, &[LegacyBrowser::Ie11][..]),
246 (IE10_UA, &[LegacyBrowser::Ie10][..]),
247 (IE9_UA, &[LegacyBrowser::Ie9][..]),
248 (IE_MOBILE9_UA, &[LegacyBrowser::Ie9][..]),
249 (
250 IE9_UA,
251 &[LegacyBrowser::AndroidPre4, LegacyBrowser::Ie9][..],
252 ),
253 (IE8_UA, &[LegacyBrowser::IePre9][..]),
254 (
255 IE8_UA,
256 &[LegacyBrowser::OperaPre15, LegacyBrowser::IePre9][..],
257 ),
258 (OPERA_PRE15_UA, &[LegacyBrowser::OperaPre15][..]),
259 (
260 OPERA_PRE15_UA,
261 &[LegacyBrowser::Ie10, LegacyBrowser::OperaPre15][..],
262 ),
263 (OPERA_MINI_PRE8_UA, &[LegacyBrowser::OperaMiniPre8][..]),
264 (
265 OPERA_MINI_PRE8_UA,
266 &[LegacyBrowser::Ie10, LegacyBrowser::OperaMiniPre8][..],
267 ),
268 (ANDROID_PRE4_UA, &[LegacyBrowser::AndroidPre4][..]),
269 (
270 ANDROID_PRE4_UA,
271 &[LegacyBrowser::Ie10, LegacyBrowser::AndroidPre4][..],
272 ),
273 (SAFARI_PRE6_UA, &[LegacyBrowser::SafariPre6][..]),
274 (
275 SAFARI_PRE6_UA,
276 &[LegacyBrowser::OperaPre15, LegacyBrowser::SafariPre6][..],
277 ),
278 (EDGE_12_UA, &[LegacyBrowser::EdgePre79][..]),
279 (
280 EDGE_18_UA,
281 &[LegacyBrowser::OperaPre15, LegacyBrowser::EdgePre79][..],
282 ),
283 (OPERA_UA, &[LegacyBrowser::Opera][..]),
284 (OPERA_MINI_UA, &[LegacyBrowser::OperaMini][..]),
285 (CHROME_UA, &[LegacyBrowser::Chrome][..]),
286 (FIREFOX_UA, &[LegacyBrowser::Firefox][..]),
287 (IE_UA, &[LegacyBrowser::Ie][..]),
288 (EDGE_UA, &[LegacyBrowser::Edge][..]),
289 (SAFARI_UA, &[LegacyBrowser::Safari][..]),
290 (ANDROID_UA, &[LegacyBrowser::Android][..]),
291 ];
292
293 for (user_agent, active_filters) in &test_configs {
294 let evt = testutils::get_event_with_user_agent(user_agent);
295 let filter_result =
296 should_filter(&evt, &get_legacy_browsers_config(true, active_filters));
297 assert_ne!(
298 filter_result,
299 Ok(()),
300 "Filters {active_filters:?} should have filtered User Agent\n{user_agent} for "
301 )
302 }
303 }
304
305 #[test]
306 fn test_dont_filter_unconfigured_browsers() {
307 let test_configs = [
308 (IE11_UA, LegacyBrowser::Ie10),
309 (IE10_UA, LegacyBrowser::Ie9),
310 (IE10_UA, LegacyBrowser::Ie11),
311 (IE9_UA, LegacyBrowser::IePre9),
312 (OPERA_15_UA, LegacyBrowser::OperaPre15),
313 (OPERA_MINI_8_UA, LegacyBrowser::OperaMiniPre8),
314 (ANDROID_4_UA, LegacyBrowser::AndroidPre4),
315 (SAFARI_6_UA, LegacyBrowser::SafariPre6),
316 (EDGE_12_UA, LegacyBrowser::Ie10),
317 (EDGE_18_UA, LegacyBrowser::Ie10),
318 (EDGE_79_UA, LegacyBrowser::EdgePre79),
319 (EDGE_ANDROID_118_UA, LegacyBrowser::EdgePre79),
320 ];
321
322 for (user_agent, active_filter) in &test_configs {
323 let evt = testutils::get_event_with_user_agent(user_agent);
324 let filter_result = should_filter(
325 &evt,
326 &get_legacy_browsers_config(true, &[active_filter.clone()]),
327 );
328 assert_eq!(
329 filter_result,
330 Ok(()),
331 "Filter {active_filter:?} shouldn't have filtered User Agent\n{user_agent} for "
332 )
333 }
334 }
335
336 mod sentry_compatibility {
340 use super::*;
341
342 const ANDROID2_S_UA: &str = "Mozilla/5.0 (Linux; U; Android 2.3.5; en-us; HTC Vision Build/GRI40) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1";
344 const ANDROID4_S_UA: &str = "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19";
345 const IE5_S_UA: &str =
346 "Mozilla/4.0 (compatible; MSIE 5.50; Windows NT; SiteKiosk 4.9; SiteCoach 1.0)";
347 const IE8_S_UA: &str = "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Win64; x64; Trident/4.0; .NET CLR 2.0.50727; SLCC2; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; MDDC; Tablet PC 2.0)";
348 const IE9_S_UA: &str = "Mozilla/5.0 (Windows; U; MSIE 9.0; WIndows NT 9.0; en-US))";
349 const IE_MOBILE9_S_UA: &str = "Mozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; Trident/5.0; IEMobile/9.0; NOKIA; Lumia 710)";
350 const IE10_S_UA: &str = "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 7.0; InfoPath.3; .NET CLR 3.1.40767; Trident/6.0; en-IN)";
351 const IE_MOBILE10_S_UA: &str = "Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; NOKIA; Lumia 520)";
352 const OPERA11_S_UA: &str = "Opera/9.80 (Windows NT 5.1; U; it) Presto/2.7.62 Version/11.00";
353 const OPERA_12_S_UA: &str =
354 "Opera/9.80 (X11; Linux i686; Ubuntu/14.10) Presto/2.12.388 Version/12.16";
355 const OPERA_15_S_UA: &str = "Mozilla/5.0 (X11; Linux x86_64; Debian) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.52 Safari/537.36 OPR/15.0.1147.100";
356 const CHROME_S_UA: &str = "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36";
357 const EDGE_S_UA: &str = "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.10136";
358 const SAFARI5_S_UA: &str = "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-HK) AppleWebKit/533.18.1 (KHTML, like Gecko) Version/5.0.2 Safari/533.18.5";
359 const SAFARI7_S_UA: &str = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/7046A194A";
360 const OPERA_MINI8_S_UA: &str = "Opera/9.80 (J2ME/MIDP; Opera Mini/8.0.35158/36.2534; U; en) Presto/2.12.423 Version/12.16";
361 const OPERA_MINI7_S_UA: &str = "Opera/9.80 (J2ME/MIDP; Opera Mini/7.0.32796/59.323; U; fr) Presto/2.12.423 Version/12.16";
362
363 #[test]
364 fn test_filter_sentry_user_agents() {
365 let test_configs = [
366 (ANDROID2_S_UA, LegacyBrowser::Default),
367 (IE9_S_UA, LegacyBrowser::Default),
368 (IE_MOBILE9_S_UA, LegacyBrowser::Default),
369 (IE5_S_UA, LegacyBrowser::Default),
370 (OPERA11_S_UA, LegacyBrowser::Default),
371 (OPERA_12_S_UA, LegacyBrowser::Default),
372 (OPERA_MINI7_S_UA, LegacyBrowser::Default),
373 (OPERA_12_S_UA, LegacyBrowser::OperaPre15),
374 (OPERA_MINI7_S_UA, LegacyBrowser::OperaMiniPre8),
375 (OPERA_MINI7_S_UA, LegacyBrowser::Default),
376 (OPERA_MINI7_S_UA, LegacyBrowser::Default),
377 (IE8_S_UA, LegacyBrowser::IePre9),
378 (IE8_S_UA, LegacyBrowser::Default),
379 (IE9_S_UA, LegacyBrowser::Ie9),
380 (IE_MOBILE9_S_UA, LegacyBrowser::Ie9),
381 (IE10_S_UA, LegacyBrowser::Ie10),
382 (IE_MOBILE10_S_UA, LegacyBrowser::Ie10),
383 (SAFARI5_S_UA, LegacyBrowser::SafariPre6),
384 (SAFARI5_S_UA, LegacyBrowser::Default),
385 (ANDROID2_S_UA, LegacyBrowser::AndroidPre4),
386 ];
387
388 for (user_agent, active_filter) in &test_configs {
389 let evt = testutils::get_event_with_user_agent(user_agent);
390 let filter_result = should_filter(
391 &evt,
392 &get_legacy_browsers_config(true, &[active_filter.clone()]),
393 );
394 assert_ne!(
395 filter_result,
396 Ok(()),
397 "Filter <{active_filter:?}> should have filtered User Agent\n{user_agent} for "
398 )
399 }
400 }
401
402 #[test]
403 fn test_dont_filter_sentry_allowed_user_agents() {
404 let test_configs = [
405 (ANDROID4_S_UA, LegacyBrowser::Default),
406 (IE10_S_UA, LegacyBrowser::Default),
407 (IE_MOBILE10_S_UA, LegacyBrowser::Default),
408 (CHROME_S_UA, LegacyBrowser::Default),
409 (EDGE_S_UA, LegacyBrowser::Default),
410 (OPERA_15_S_UA, LegacyBrowser::Default),
411 (OPERA_MINI8_S_UA, LegacyBrowser::Default),
412 (IE10_S_UA, LegacyBrowser::Default),
413 (IE_MOBILE10_S_UA, LegacyBrowser::Default),
414 (SAFARI7_S_UA, LegacyBrowser::Default),
415 ];
416
417 for (user_agent, active_filter) in &test_configs {
418 let evt = testutils::get_event_with_user_agent(user_agent);
419 let filter_result = should_filter(
420 &evt,
421 &get_legacy_browsers_config(true, &[active_filter.clone()]),
422 );
423 assert_eq!(
424 filter_result,
425 Ok(()),
426 "Filter {active_filter:?} shouldn't have filtered User Agent\n{user_agent} for "
427 )
428 }
429 }
430 }
431}