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