1use crate::identity::{Browser, Identity, Os};
2use crate::error::IdentityError;
3
4fn full_version(parts: &[u16]) -> String {
6 parts.iter().map(|v| v.to_string()).collect::<Vec<_>>().join(".")
7}
8
9pub fn build_user_agent(identity: &Identity) -> Result<String, IdentityError> {
11 let chrome_full = full_version(&identity.browser_version);
12
13 match (&identity.os, &identity.browser) {
14 (Os::Windows, Browser::Chrome) => {
16 let nt_version = windows_nt_version(&identity.os_version);
17 Ok(format!(
18 "Mozilla/5.0 (Windows NT {}; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/{} Safari/537.36",
19 nt_version, chrome_full
20 ))
21 }
22
23 (Os::Macintosh, Browser::Chrome) => {
25 let osx_underscored = identity.os_version.replace('.', "_");
26 Ok(format!(
27 "Mozilla/5.0 (Macintosh; Intel Mac OS X {}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/{} Safari/537.36",
28 osx_underscored, chrome_full
29 ))
30 }
31
32 (Os::Android, Browser::Chrome) => {
34 let device = identity.device_model.as_deref().unwrap_or("Pixel 7");
35 Ok(format!(
36 "Mozilla/5.0 (Linux; Android {}; {}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/{} Mobile Safari/537.36",
37 identity.os_version, device, chrome_full
38 ))
39 }
40
41 (Os::Android, Browser::Edge) => {
43 let device = identity.device_model.as_deref().unwrap_or("Pixel 7");
44 Ok(format!(
45 "Mozilla/5.0 (Linux; Android {}; {}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/{} Mobile EdgA/{} Safari/537.36",
46 identity.os_version, device, chrome_full, chrome_full
47 ))
48 }
49
50 (Os::Ios, Browser::Safari) => {
52 let os_underscored = identity.os_version.replace('.', "_");
53 let nav_platform = identity.platform.navigator_platform.as_str();
54 Ok(format!(
55 "Mozilla/5.0 ({}; CPU iPhone OS {} like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/{} Mobile/15E148 Safari/604.1",
56 nav_platform, os_underscored, full_version(&identity.browser_version)
57 ))
58 }
59
60 (Os::Ios, Browser::Chrome) => {
62 let os_underscored = identity.os_version.replace('.', "_");
63 let nav_platform = identity.platform.navigator_platform.as_str();
64 Ok(format!(
65 "Mozilla/5.0 ({}; CPU iPhone OS {} like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/{} Mobile/15E148 Safari/604.1",
66 nav_platform, os_underscored, chrome_full
67 ))
68 }
69
70 (Os::Ios, Browser::Edge) => {
72 let os_underscored = identity.os_version.replace('.', "_");
73 let nav_platform = identity.platform.navigator_platform.as_str();
74 Ok(format!(
75 "Mozilla/5.0 ({}; CPU iPhone OS {} like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/{} EdgiOS/{} Mobile/15E148 Safari/604.1",
76 nav_platform, os_underscored, full_version(&identity.browser_version), chrome_full
77 ))
78 }
79
80 (Os::Macintosh, Browser::Safari) => {
82 let osx_underscored = identity.os_version.replace('.', "_");
83 Ok(format!(
84 "Mozilla/5.0 (Macintosh; Intel Mac OS X {}) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/{} Safari/605.1.15",
85 osx_underscored, full_version(&identity.browser_version)
86 ))
87 }
88
89 (Os::Linux, Browser::Chrome) => {
91 Ok(format!(
92 "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/{} Safari/537.36",
93 chrome_full
94 ))
95 }
96
97 (Os::Windows, Browser::Edge) => {
99 let nt_version = windows_nt_version(&identity.os_version);
100 Ok(format!(
101 "Mozilla/5.0 (Windows NT {}; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/{} Safari/537.36 Edg/{}",
102 nt_version, chrome_full, chrome_full
103 ))
104 }
105
106 (Os::Macintosh, Browser::Edge) => {
108 let osx_underscored = identity.os_version.replace('.', "_");
109 Ok(format!(
110 "Mozilla/5.0 (Macintosh; Intel Mac OS X {}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/{} Safari/537.36 Edg/{}",
111 osx_underscored, chrome_full, chrome_full
112 ))
113 }
114
115 (os, browser) => Err(IdentityError::UaError(format!(
116 "unsupported OS/browser combination: {:?}/{:?}",
117 os, browser
118 ))),
119 }
120}
121
122fn windows_nt_version(os_version: &str) -> &str {
124 match os_version {
125 "11" => "10.0",
126 "10" => "10.0",
127 "8.1" => "6.3",
128 "8" => "6.2",
129 "7" => "6.1",
130 _ => "10.0",
131 }
132}