rustfs_targets/sys/
user_agent.rs1use rustfs_config::VERSION;
16use std::borrow::Cow;
17use std::env;
18use std::fmt;
19use std::sync::OnceLock;
20#[cfg(not(target_os = "openbsd"))]
21use sysinfo::System;
22
23#[derive(Debug, Clone, PartialEq)]
25pub enum ServiceType {
26 Basis,
27 Core,
28 Event,
29 Logger,
30 Custom(Cow<'static, str>),
31}
32
33impl ServiceType {
34 fn as_str(&self) -> &str {
35 match self {
36 ServiceType::Basis => "basis",
37 ServiceType::Core => "core",
38 ServiceType::Event => "event",
39 ServiceType::Logger => "logger",
40 ServiceType::Custom(s) => s,
41 }
42 }
43}
44
45#[derive(Debug)]
48struct UserAgent {
49 os_platform: &'static str,
50 arch: &'static str,
51 version: &'static str,
52 service: ServiceType,
53}
54
55static OS_PLATFORM: OnceLock<String> = OnceLock::new();
56
57impl UserAgent {
58 fn new(service: ServiceType) -> Self {
60 UserAgent {
61 os_platform: Self::get_os_platform(),
62 arch: env::consts::ARCH,
63 version: VERSION,
64 service,
65 }
66 }
67
68 fn get_os_platform() -> &'static str {
73 OS_PLATFORM.get_or_init(|| {
74 if cfg!(target_os = "windows") {
75 Self::get_windows_platform()
76 } else if cfg!(target_os = "macos") {
77 Self::get_macos_platform()
78 } else if cfg!(target_os = "linux") {
79 Self::get_linux_platform()
80 } else if cfg!(target_os = "freebsd") {
81 Self::get_freebsd_platform()
82 } else if cfg!(target_os = "netbsd") {
83 Self::get_netbsd_platform()
84 } else {
85 "Unknown".to_string()
86 }
87 })
88 }
89
90 #[cfg(windows)]
92 fn get_windows_platform() -> String {
93 let version = System::os_version().unwrap_or_else(|| "NT Unknown".to_string());
94 if version.starts_with("Windows") {
95 version
96 } else {
97 format!("Windows NT {version}")
98 }
99 }
100
101 #[cfg(not(windows))]
102 fn get_windows_platform() -> String {
103 "N/A".to_string()
104 }
105
106 #[cfg(target_os = "macos")]
108 fn get_macos_platform() -> String {
109 let version_str = System::os_version().unwrap_or_else(|| "14.0.0".to_string());
110 let mut parts = version_str.split('.');
111 let major = parts.next().unwrap_or("14");
112 let minor = parts.next().unwrap_or("0");
113 let patch = parts.next().unwrap_or("0");
114
115 let cpu_info = if env::consts::ARCH == "aarch64" { "Apple" } else { "Intel" };
116
117 format!("Macintosh; {cpu_info} Mac OS X {major}_{minor}_{patch}")
118 }
119
120 #[cfg(not(target_os = "macos"))]
121 fn get_macos_platform() -> String {
122 "N/A".to_string()
123 }
124
125 #[cfg(target_os = "linux")]
127 fn get_linux_platform() -> String {
128 let os_name = System::long_os_version().unwrap_or_else(|| "Linux Unknown".to_string());
129 format!("X11; {os_name}")
130 }
131
132 #[cfg(not(target_os = "linux"))]
133 fn get_linux_platform() -> String {
134 "N/A".to_string()
135 }
136
137 #[cfg(target_os = "freebsd")]
138 fn get_freebsd_platform() -> String {
139 format!("FreeBSD; {}", env::consts::ARCH)
140 }
141
142 #[cfg(not(target_os = "freebsd"))]
143 fn get_freebsd_platform() -> String {
144 "N/A".to_string()
145 }
146
147 #[cfg(target_os = "netbsd")]
148 fn get_netbsd_platform() -> String {
149 format!("NetBSD; {}", env::consts::ARCH)
150 }
151
152 #[cfg(not(target_os = "netbsd"))]
153 fn get_netbsd_platform() -> String {
154 "N/A".to_string()
155 }
156}
157
158impl fmt::Display for UserAgent {
159 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
160 write!(f, "Mozilla/5.0 ({}; {}) RustFS/{}", self.os_platform, self.arch, self.version)?;
161 if self.service != ServiceType::Basis {
162 write!(f, " ({})", self.service.as_str())?;
163 }
164 Ok(())
165 }
166}
167
168pub fn get_user_agent(service: ServiceType) -> String {
170 UserAgent::new(service).to_string()
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176 use rustfs_config::VERSION;
177
178 #[test]
179 fn test_user_agent_format_basis() {
180 let ua = get_user_agent(ServiceType::Basis);
181 assert!(ua.starts_with("Mozilla/5.0"));
182 assert!(ua.contains(&format!("RustFS/{VERSION}")));
183 assert!(!ua.contains("(basis)"));
184 }
185
186 #[test]
187 fn test_user_agent_format_core() {
188 let ua = get_user_agent(ServiceType::Core);
189 assert!(ua.contains(&format!("RustFS/{VERSION} (core)")));
190 }
191
192 #[test]
193 fn test_user_agent_format_custom() {
194 let ua = get_user_agent(ServiceType::Custom("monitor".into()));
195 assert!(ua.contains(&format!("RustFS/{VERSION} (monitor)")));
196 }
197
198 #[test]
199 fn test_os_platform_caching() {
200 let ua1 = UserAgent::new(ServiceType::Basis);
201 let ua2 = UserAgent::new(ServiceType::Basis);
202 assert_eq!(ua1.os_platform, ua2.os_platform);
203 assert!(std::ptr::eq(ua1.os_platform.as_ptr(), ua2.os_platform.as_ptr()));
204 }
205}