libsubconverter/parser/explodes/
ssr.rs1use crate::models::{Proxy, SSR_DEFAULT_GROUP, SS_CIPHERS};
2use crate::utils::base64::url_safe_base64_decode;
3use serde_json::Value;
4use url::Url;
5
6pub fn explode_ssr(ssr: &str, node: &mut Proxy) -> bool {
9 if !ssr.starts_with("ssr://") {
11 return false;
12 }
13
14 let encoded = &ssr[6..];
16
17 let mut decoded = url_safe_base64_decode(encoded);
19 if decoded.is_empty() {
20 return false;
21 }
22
23 decoded = decoded.replace('\r', "");
25
26 let mut _strobfs = String::new();
28 let mut group = String::new();
29 let mut remarks = String::new();
30 let mut obfsparam = String::new();
31 let mut protoparam = String::new();
32
33 if let Some(query_pos) = decoded.find("/?") {
34 _strobfs = decoded[query_pos + 2..].to_string();
35 decoded = decoded[..query_pos].to_string();
36
37 let url_str = format!("http://localhost/?{}", _strobfs);
39 if let Ok(url) = Url::parse(&url_str) {
40 for (key, value) in url.query_pairs() {
41 let decoded_value = url_safe_base64_decode(&value);
42
43 match key.as_ref() {
44 "group" => group = decoded_value,
45 "remarks" => remarks = decoded_value,
46 "obfsparam" => obfsparam = decoded_value.replace(" ", ""),
47 "protoparam" => protoparam = decoded_value.replace(" ", ""),
48 _ => {}
49 }
50 }
51 }
52 }
53
54 let parts: Vec<&str> = decoded.split(':').collect();
56 if parts.len() < 6 {
57 return false;
58 }
59
60 let server = parts[0];
61 let port_str = parts[1];
62 let protocol = parts[2];
63 let method = parts[3];
64 let obfs = parts[4];
65 let password_encoded = parts[5];
66
67 let password = url_safe_base64_decode(password_encoded);
69
70 let port = match port_str.parse::<u16>() {
72 Ok(p) => p,
73 Err(_) => return false,
74 };
75
76 if port == 0 {
78 return false;
79 }
80
81 if group.is_empty() {
83 group = SSR_DEFAULT_GROUP.to_string();
84 }
85 if remarks.is_empty() {
86 remarks = format!("{} ({})", server, port);
87 }
88
89 if SS_CIPHERS.iter().any(|c| *c == method)
91 && (obfs.is_empty() || obfs == "plain")
92 && (protocol.is_empty() || protocol == "origin")
93 {
94 *node = Proxy::ss_construct(
96 &group, &remarks, server, port, &password, method, "", "", None, None, None, None, "",
97 );
98 } else {
99 *node = Proxy::ssr_construct(
101 &group,
102 &remarks,
103 server,
104 port,
105 protocol,
106 method,
107 obfs,
108 &password,
109 &obfsparam,
110 &protoparam,
111 None,
112 None,
113 None,
114 "",
115 );
116 }
117
118 true
119}
120
121pub fn explode_ssr_conf(content: &str, nodes: &mut Vec<Proxy>) -> bool {
123 let json: Value = match serde_json::from_str(content) {
125 Ok(json) => json,
126 Err(_) => return false,
127 };
128
129 if !json["configs"].is_array() {
131 return false;
132 }
133
134 let configs = json["configs"].as_array().unwrap();
136
137 for config in configs {
138 let server = config["server"].as_str().unwrap_or("");
139 let port = config["server_port"].as_u64().unwrap_or(0) as u16;
140 let protocol = config["protocol"].as_str().unwrap_or("");
141 let method = config["method"].as_str().unwrap_or("");
142 let obfs = config["obfs"].as_str().unwrap_or("");
143 let password = config["password"].as_str().unwrap_or("");
144 let obfs_param = config["obfsparam"].as_str().unwrap_or("");
145 let proto_param = config["protocolparam"].as_str().unwrap_or("");
146 let remarks = config["remarks"].as_str().unwrap_or("");
147 let group = config["group"].as_str().unwrap_or("");
148
149 let group_str = if group.is_empty() {
151 SSR_DEFAULT_GROUP.to_string()
152 } else {
153 group.to_string()
154 };
155 let remark_str = if remarks.is_empty() {
156 format!("{} ({})", server, port)
157 } else {
158 remarks.to_string()
159 };
160
161 let node = Proxy::ssr_construct(
163 &group_str,
164 &remark_str,
165 server,
166 port,
167 protocol,
168 method,
169 obfs,
170 password,
171 obfs_param,
172 proto_param,
173 None,
174 None,
175 None,
176 "",
177 );
178
179 nodes.push(node);
180 }
181
182 !nodes.is_empty()
183}
184
185#[cfg(test)]
186mod tests {
187 use crate::ProxyType;
188
189 use super::*;
190 use base64::{engine::general_purpose::STANDARD, Engine};
191
192 #[test]
193 fn test_explode_ssr_valid_link() {
194 let mut node = Proxy::default();
195
196 let ssr_link = "ssr://ZXhhbXBsZS5jb206ODM4ODphdXRoX2FlczEyOF9tZDU6YWVzLTI1Ni1jZmI6dGxzMS4yX3RpY2tldF9hdXRoOmRHVnpkQT09Lz9vYmZzcGFyYW09ZEdWemRBPT0mcHJvdG9wYXJhbT1kR1Z6ZEE9PSZyZW1hcmtzPVZHVnpkQ0JUVTFJPSZncm91cD1WR1Z6ZENCVFUxST0=";
198
199 let result = explode_ssr(ssr_link, &mut node);
201
202 assert!(result);
204 assert_eq!(node.proxy_type, ProxyType::ShadowsocksR);
205 assert_eq!(node.hostname, "example.com");
206 assert_eq!(node.port, 8388);
207 assert_eq!(node.protocol.as_deref().unwrap_or(""), "auth_aes128_md5");
208 assert_eq!(node.encrypt_method.as_deref().unwrap_or(""), "aes-256-cfb");
209 assert_eq!(node.obfs.as_deref().unwrap_or(""), "tls1.2_ticket_auth");
210 assert_eq!(node.password.as_deref().unwrap_or(""), "test");
211 assert_eq!(node.obfs_param.as_deref().unwrap_or(""), "test");
212 assert_eq!(node.protocol_param.as_deref().unwrap_or(""), "test");
213 assert_eq!(node.remark, "Test SSR");
214 assert_eq!(node.group, "Test SSR");
215 }
216
217 #[test]
218 fn test_explode_ssr_invalid_prefix() {
219 let mut node = Proxy::default();
220 let result = explode_ssr("ss://invalid", &mut node);
221 assert!(!result);
222 }
223
224 #[test]
225 fn test_explode_ssr_invalid_base64() {
226 let mut node = Proxy::default();
227 let result = explode_ssr("ssr://invalid!base64", &mut node);
228 assert!(!result);
229 }
230
231 #[test]
232 fn test_explode_ssr_missing_parts() {
233 let mut node = Proxy::default();
234 let link = format!(
236 "ssr://{}",
237 STANDARD.encode("example.com:8388:auth_aes128_md5")
238 );
239 let result = explode_ssr(&link, &mut node);
240 assert!(!result);
241 }
242
243 #[test]
244 fn test_explode_ssr_default_group() {
245 let mut node = Proxy::default();
246 let server = "example.com";
247 let port = 8388;
248 let protocol = "auth_aes128_md5";
249 let method = "aes-256-cfb";
250 let obfs = "tls1.2_ticket_auth";
251 let password = "password123";
252
253 let password_b64 = STANDARD.encode(password);
255
256 let ssr_link = format!(
258 "{}:{}:{}:{}:{}:{}",
259 server, port, protocol, method, obfs, password_b64
260 );
261
262 let ssr_link_b64 = format!("ssr://{}", STANDARD.encode(&ssr_link));
264
265 let result = explode_ssr(&ssr_link_b64, &mut node);
267
268 assert!(result);
270 assert_eq!(node.group, SSR_DEFAULT_GROUP);
271 assert_eq!(node.remark, format!("{} ({})", server, port));
272 }
273
274 #[test]
275 fn test_explode_ssr_conf_valid() {
276 let mut nodes = Vec::new();
277 let content = r#"{
278 "configs": [
279 {
280 "server": "example1.com",
281 "server_port": 8388,
282 "protocol": "auth_aes128_md5",
283 "method": "aes-256-cfb",
284 "obfs": "tls1.2_ticket_auth",
285 "password": "password1",
286 "obfsparam": "obfs.param1",
287 "protocolparam": "proto.param1",
288 "remarks": "Server 1",
289 "group": "Group 1"
290 },
291 {
292 "server": "example2.com",
293 "server_port": 8389,
294 "protocol": "auth_chain_a",
295 "method": "chacha20",
296 "obfs": "http_simple",
297 "password": "password2",
298 "obfsparam": "obfs.param2",
299 "protocolparam": "proto.param2",
300 "remarks": "Server 2",
301 "group": "Group 2"
302 }
303 ]
304 }"#;
305
306 let result = explode_ssr_conf(content, &mut nodes);
307
308 assert!(result);
309 assert_eq!(nodes.len(), 2);
310
311 assert_eq!(nodes[0].proxy_type, ProxyType::ShadowsocksR);
313 assert_eq!(nodes[0].hostname, "example1.com");
314 assert_eq!(nodes[0].port, 8388);
315 assert_eq!(
316 nodes[0].protocol.as_deref().unwrap_or(""),
317 "auth_aes128_md5"
318 );
319 assert_eq!(
320 nodes[0].encrypt_method.as_deref().unwrap_or(""),
321 "aes-256-cfb"
322 );
323 assert_eq!(nodes[0].obfs.as_deref().unwrap_or(""), "tls1.2_ticket_auth");
324 assert_eq!(nodes[0].password.as_deref().unwrap_or(""), "password1");
325 assert_eq!(nodes[0].obfs_param.as_deref().unwrap_or(""), "obfs.param1");
326 assert_eq!(
327 nodes[0].protocol_param.as_deref().unwrap_or(""),
328 "proto.param1"
329 );
330 assert_eq!(nodes[0].remark, "Server 1");
331 assert_eq!(nodes[0].group, "Group 1");
332
333 assert_eq!(nodes[1].proxy_type, ProxyType::ShadowsocksR);
335 assert_eq!(nodes[1].hostname, "example2.com");
336 assert_eq!(nodes[1].port, 8389);
337 assert_eq!(nodes[1].protocol.as_deref().unwrap_or(""), "auth_chain_a");
338 assert_eq!(nodes[1].encrypt_method.as_deref().unwrap_or(""), "chacha20");
339 assert_eq!(nodes[1].obfs.as_deref().unwrap_or(""), "http_simple");
340 assert_eq!(nodes[1].password.as_deref().unwrap_or(""), "password2");
341 assert_eq!(nodes[1].obfs_param.as_deref().unwrap_or(""), "obfs.param2");
342 assert_eq!(
343 nodes[1].protocol_param.as_deref().unwrap_or(""),
344 "proto.param2"
345 );
346 assert_eq!(nodes[1].remark, "Server 2");
347 assert_eq!(nodes[1].group, "Group 2");
348 }
349
350 #[test]
351 fn test_explode_ssr_conf_invalid_json() {
352 let mut nodes = Vec::new();
353 let content = "invalid json";
354 let result = explode_ssr_conf(content, &mut nodes);
355 assert!(!result);
356 assert_eq!(nodes.len(), 0);
357 }
358
359 #[test]
360 fn test_explode_ssr_conf_missing_configs() {
361 let mut nodes = Vec::new();
362 let content = r#"{ "not_configs": [] }"#;
363 let result = explode_ssr_conf(content, &mut nodes);
364 assert!(!result);
365 assert_eq!(nodes.len(), 0);
366 }
367
368 #[test]
369 fn test_explode_ssr_conf_empty_configs() {
370 let mut nodes = Vec::new();
371 let content = r#"{ "configs": [] }"#;
372 let result = explode_ssr_conf(content, &mut nodes);
373 assert!(!result);
374 assert_eq!(nodes.len(), 0);
375 }
376
377 #[test]
378 fn test_explode_ssr_conf_default_values() {
379 let mut nodes = Vec::new();
380 let content = r#"{
381 "configs": [
382 {
383 "server": "example.com",
384 "server_port": 8388
385 }
386 ]
387 }"#;
388
389 let result = explode_ssr_conf(content, &mut nodes);
390
391 assert!(result);
392 assert_eq!(nodes.len(), 1);
393
394 assert_eq!(nodes[0].proxy_type, ProxyType::ShadowsocksR);
395 assert_eq!(nodes[0].hostname, "example.com");
396 assert_eq!(nodes[0].port, 8388);
397 assert_eq!(nodes[0].protocol.as_deref().unwrap_or(""), "");
398 assert_eq!(nodes[0].encrypt_method.as_deref().unwrap_or(""), "");
399 assert_eq!(nodes[0].obfs.as_deref().unwrap_or(""), "");
400 assert_eq!(nodes[0].password.as_deref().unwrap_or(""), "");
401 assert_eq!(nodes[0].obfs_param.as_deref().unwrap_or(""), "");
402 assert_eq!(nodes[0].protocol_param.as_deref().unwrap_or(""), "");
403 assert_eq!(nodes[0].remark, "example.com (8388)");
404 assert_eq!(nodes[0].group, SSR_DEFAULT_GROUP);
405 }
406}