1use crate::Result;
4use std::path::Path;
5use tracing::info;
6
7pub fn validate_server_config(config: &crate::common::ServerConfig) -> Result<()> {
9 if config.name.is_empty() {
10 return Err(crate::McpToolsError::Config(
11 "Server name cannot be empty".to_string(),
12 ));
13 }
14
15 if config.port == 0 {
16 return Err(crate::McpToolsError::Config(
17 "Server port must be greater than 0".to_string(),
18 ));
19 }
20
21 Ok(())
22}
23
24pub fn validate_client_config(config: &crate::common::ClientConfig) -> Result<()> {
26 if config.name.is_empty() {
27 return Err(crate::McpToolsError::Config(
28 "Client name cannot be empty".to_string(),
29 ));
30 }
31
32 if config.server_url.is_empty() {
33 return Err(crate::McpToolsError::Config(
34 "Server URL cannot be empty".to_string(),
35 ));
36 }
37
38 Ok(())
39}
40
41pub fn format_file_size(size: u64) -> String {
43 const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"];
44 let mut size = size as f64;
45 let mut unit_index = 0;
46
47 while size >= 1024.0 && unit_index < UNITS.len() - 1 {
48 size /= 1024.0;
49 unit_index += 1;
50 }
51
52 if unit_index == 0 {
53 format!("{} {}", size as u64, UNITS[unit_index])
54 } else {
55 format!("{:.1} {}", size, UNITS[unit_index])
56 }
57}
58
59pub fn is_safe_path<P: AsRef<Path>>(path: P) -> bool {
61 let path = path.as_ref();
62
63 if path.to_string_lossy().contains("..") {
65 return false;
66 }
67
68 if let Ok(canonical) = path.canonicalize() {
70 let path_str = canonical.to_string_lossy().to_lowercase();
71
72 if path_str.starts_with("/etc")
74 || path_str.starts_with("/sys")
75 || path_str.starts_with("/proc")
76 || path_str.starts_with("/dev")
77 || path_str.starts_with("/boot")
78 || path_str.starts_with("/root")
79 {
80 return false;
81 }
82
83 if path_str.starts_with("c:\\windows")
85 || path_str.starts_with("c:\\system32")
86 || path_str.starts_with("c:\\program files")
87 {
88 return false;
89 }
90 }
91
92 true
93}
94
95pub fn generate_session_id() -> String {
97 format!(
98 "session-{}-{}",
99 std::time::SystemTime::now()
100 .duration_since(std::time::UNIX_EPOCH)
101 .unwrap()
102 .as_millis(),
103 uuid::Uuid::new_v4().to_string()[..8].to_string()
104 )
105}
106
107pub fn log_server_startup(config: &crate::common::ServerConfig) {
109 info!("🚀 Starting MCP Server");
110 info!(" Name: {}", config.name);
111 info!(" Description: {}", config.description);
112 info!(" Version: {}", config.version);
113 info!(" Address: {}:{}", config.host, config.port);
114 info!(" Max Connections: {}", config.max_connections);
115 info!(" Request Timeout: {}s", config.request_timeout_secs);
116}
117
118pub fn log_client_startup(config: &crate::common::ClientConfig) {
120 info!("🔌 Starting MCP Client");
121 info!(" Name: {}", config.name);
122 info!(" Version: {}", config.version);
123 info!(" Server URL: {}", config.server_url);
124 info!(" Connect Timeout: {}s", config.connect_timeout_secs);
125 info!(" Request Timeout: {}s", config.request_timeout_secs);
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131 use crate::common::{ClientConfig, ServerConfig};
132
133 #[test]
134 fn test_validate_server_config() {
135 let mut config = ServerConfig::default();
136 assert!(validate_server_config(&config).is_ok());
137
138 config.name = "".to_string();
139 assert!(validate_server_config(&config).is_err());
140
141 config.name = "Test".to_string();
142 config.port = 0;
143 assert!(validate_server_config(&config).is_err());
144 }
145
146 #[test]
147 fn test_validate_client_config() {
148 let mut config = ClientConfig::default();
149 assert!(validate_client_config(&config).is_ok());
150
151 config.name = "".to_string();
152 assert!(validate_client_config(&config).is_err());
153
154 config.name = "Test".to_string();
155 config.server_url = "".to_string();
156 assert!(validate_client_config(&config).is_err());
157 }
158
159 #[test]
160 fn test_format_file_size() {
161 assert_eq!(format_file_size(0), "0 B");
162 assert_eq!(format_file_size(1023), "1023 B");
163 assert_eq!(format_file_size(1024), "1.0 KB");
164 assert_eq!(format_file_size(1536), "1.5 KB");
165 assert_eq!(format_file_size(1048576), "1.0 MB");
166 }
167
168 #[test]
169 fn test_is_safe_path() {
170 assert!(is_safe_path("./test.txt"));
171 assert!(is_safe_path("test/file.txt"));
172 assert!(!is_safe_path("../../../etc/passwd"));
173 assert!(!is_safe_path("test/../../../etc/passwd"));
174 }
175
176 #[test]
177 fn test_generate_session_id() {
178 let id1 = generate_session_id();
179 let id2 = generate_session_id();
180
181 assert_ne!(id1, id2);
182 assert!(id1.starts_with("session-"));
183 assert!(id2.starts_with("session-"));
184 }
185}