statsig_rust/evaluation/user_agent_parsing/statsig_uaparser/
ua_parser.rs1use super::tokenizer::{Token, Tokenizer};
2
3pub struct UaParser;
4
5impl UaParser {
6 pub fn parse_os<'a>(agent: &'a str) -> ParserResult<'a> {
7 let result = Tokenizer::run(agent);
8
9 if let Some(token) = &result.possible_os_token {
10 return create_res(token.tag, token.get_version());
11 }
12
13 for token in &result.tokens {
14 if token.tag == "ATV OS X" {
15 return create_res("ATV OS X", None);
16 }
17
18 if token.tag == "iPhone OS" {
19 return create_res("iOS", token.get_version());
20 }
21
22 if token.tag == "CPU OS" && result.ios_hint {
23 return create_res("iOS", token.get_version());
24 }
25
26 if token.tag == "Version" && result.ios_hint && result.macos_hint {
27 return create_res("iOS", token.get_version());
28 }
29
30 if token.tag == "CFNetwork" {
31 return create_res("iOS", None);
32 }
33
34 if token.tag == "Android" {
35 return create_res(token.tag, token.get_version());
36 }
37
38 if token.tag == "Chromecast" {
39 return create_res("Chromecast", None);
40 }
41
42 if token.tag == "Red Hat" {
43 return create_res("Red Hat", None);
44 }
45
46 if token.tag == "Kindle" {
47 return create_res("Kindle", token.get_version());
48 }
49
50 if token.tag == "Ubuntu" {
51 return create_res("Ubuntu", token.get_version());
52 }
53 }
54
55 if result.ios_hint {
56 return create_res("iOS", None);
57 }
58
59 if result.macos_hint {
60 return create_res("Mac OS X", None);
61 }
62
63 if result.windows_hint {
64 return create_res("Windows", None);
65 }
66
67 if result.linux_hint {
68 return create_res("Linux", None);
69 }
70
71 create_res("Other", None)
72 }
73
74 pub fn parse_browser<'a>(agent: &'a str) -> ParserResult<'a> {
75 let result = Tokenizer::run(agent);
76
77 if let Some(token) = &result.possible_browser_token {
78 return create_res(token.tag, token.get_version());
79 }
80
81 let mut android_token: Option<&Token> = None;
82 let mut chrome_token: Option<&Token> = None;
83 let mut version_token: Option<&Token> = None;
84
85 for token in &result.tokens {
86 if token.tag == "Firefox" {
87 if result.mobile_hint {
88 return create_res("Firefox Mobile", token.get_version());
89 }
90
91 return create_res("Firefox", token.get_version());
92 }
93
94 if token.tag == "Android" {
95 android_token = Some(token);
96 continue;
97 }
98
99 if token.tag == "Version" {
100 version_token = Some(token);
101 continue;
102 }
103
104 if token.tag == "Yahoo! Slurp" {
105 return create_res("Yahoo! Slurp", None);
106 }
107
108 if token.tag == "ChatGPT" {
109 return create_res("ChatGPT", token.get_version());
110 }
111
112 if token.tag == "Silk" {
113 if result.playstation_hint {
114 return create_res("NetFront NX", None);
115 }
116
117 return create_res("Amazon Silk", token.get_version());
118 }
119
120 if token.tag == "NetFront NX" {
121 return create_res("NetFront NX", token.get_version());
122 }
123
124 if token.tag == "YaBrowser" {
125 return create_res("Yandex Browser", token.get_version());
126 }
127
128 if token.tag == "Edge" && result.mobile_hint {
129 return create_res("Edge Mobile", token.get_version());
130 }
131
132 if token.tag == "Edge" {
133 return create_res("Edge", token.get_version());
134 }
135
136 if token.tag == "Chrome" {
137 chrome_token = Some(token);
138 continue;
139 }
140 }
141
142 if let Some(token) = chrome_token {
143 if version_token.is_some() {
144 return create_res("Chrome Mobile WebView", token.get_version());
145 }
146
147 if result.mobile_hint && token.version.is_some() && !result.huawei_hint {
148 return create_res("Chrome Mobile", token.get_version());
149 }
150
151 if token.version.is_none() {
152 if let Some(token) = android_token {
153 return create_res("Android", token.get_version());
154 }
155 }
156
157 return create_res("Chrome", token.get_version());
158 }
159
160 if let Some(token) = android_token {
161 return create_res("Android", token.get_version());
162 }
163
164 if result.safari_hint {
165 let version = version_token.and_then(|t| t.get_version());
166
167 if result.mobile_hint {
168 return create_res("Mobile Safari", version);
169 }
170
171 return create_res("Safari", version);
172 }
173
174 if result.ios_hint {
175 return create_res("Mobile Safari UI/WKWebView", None);
176 }
177
178 create_res("Other", None)
179 }
180}
181
182#[derive(Debug, Default)]
183pub struct Version<'a> {
184 pub major: Option<&'a str>,
185 pub minor: Option<&'a str>,
186 pub patch: Option<&'a str>,
187 pub patch_minor: Option<&'a str>,
188}
189
190impl<'a> Version<'a> {
191 pub fn major(major: &'a str) -> Self {
192 Self {
193 major: Some(major),
194 minor: None,
195 patch: None,
196 patch_minor: None,
197 }
198 }
199
200 pub fn get_version_string(&self) -> Option<String> {
201 let major = self.major?;
202
203 let mut version = String::new();
204
205 version.push_str(major);
206
207 if let Some(minor) = self.minor {
208 version.push('.');
209 version.push_str(minor);
210 }
211
212 if let Some(patch) = self.patch {
213 version.push('.');
214 version.push_str(patch);
215 }
216
217 if let Some(patch_minor) = self.patch_minor {
218 version.push('.');
219 version.push_str(patch_minor);
220 }
221
222 Some(version)
223 }
224}
225
226pub struct ParserResult<'a> {
227 pub name: &'a str,
228 pub version: Version<'a>,
229}
230
231fn create_res<'a>(name: &'a str, version: Option<Version<'a>>) -> ParserResult<'a> {
232 ParserResult {
233 name,
234 version: version.unwrap_or_default(),
235 }
236}