libsubconverter/parser/explodes/
ssr.rs

1use crate::models::{Proxy, SSR_DEFAULT_GROUP, SS_CIPHERS};
2use crate::utils::base64::url_safe_base64_decode;
3use serde_json::Value;
4use url::Url;
5
6/// Parse a ShadowsocksR link into a Proxy object
7/// Based on the C++ implementation in explodeSSR function
8pub fn explode_ssr(ssr: &str, node: &mut Proxy) -> bool {
9    // Check if the link starts with ssr://
10    if !ssr.starts_with("ssr://") {
11        return false;
12    }
13
14    // Extract the base64 part and decode it
15    let encoded = &ssr[6..];
16
17    // Decode base64
18    let mut decoded = url_safe_base64_decode(encoded);
19    if decoded.is_empty() {
20        return false;
21    }
22
23    // Replace \r with empty string
24    decoded = decoded.replace('\r', "");
25
26    // Extract query parameters if present
27    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        // Parse query parameters
38        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    // Parse the main part of the URL (server:port:protocol:method:obfs:password)
55    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    // Decode password (base64 encoded)
68    let password = url_safe_base64_decode(password_encoded);
69
70    // Parse port
71    let port = match port_str.parse::<u16>() {
72        Ok(p) => p,
73        Err(_) => return false,
74    };
75
76    // Skip if port is 0
77    if port == 0 {
78        return false;
79    }
80
81    // Set default group and remarks if not provided
82    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    // Check if this should be an SS or SSR proxy
90    if SS_CIPHERS.iter().any(|c| *c == method)
91        && (obfs.is_empty() || obfs == "plain")
92        && (protocol.is_empty() || protocol == "origin")
93    {
94        // Create SS proxy
95        *node = Proxy::ss_construct(
96            &group, &remarks, server, port, &password, method, "", "", None, None, None, None, "",
97        );
98    } else {
99        // Create SSR proxy
100        *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
121/// Parse a ShadowsocksR configuration file into a vector of Proxy objects
122pub fn explode_ssr_conf(content: &str, nodes: &mut Vec<Proxy>) -> bool {
123    // Try to parse as JSON
124    let json: Value = match serde_json::from_str(content) {
125        Ok(json) => json,
126        Err(_) => return false,
127    };
128
129    // Check if it's a ShadowsocksR configuration
130    if !json["configs"].is_array() {
131        return false;
132    }
133
134    // Extract configs
135    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        // Create formatted remark and group
150        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        // Create the proxy object
162        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        // This is a valid SSR link with known parameters
197        let ssr_link = "ssr://ZXhhbXBsZS5jb206ODM4ODphdXRoX2FlczEyOF9tZDU6YWVzLTI1Ni1jZmI6dGxzMS4yX3RpY2tldF9hdXRoOmRHVnpkQT09Lz9vYmZzcGFyYW09ZEdWemRBPT0mcHJvdG9wYXJhbT1kR1Z6ZEE9PSZyZW1hcmtzPVZHVnpkQ0JUVTFJPSZncm91cD1WR1Z6ZENCVFUxST0=";
198
199        // Parse the link
200        let result = explode_ssr(ssr_link, &mut node);
201
202        // Verify the result
203        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        // Only server:port:protocol
235        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        // Encode the password
254        let password_b64 = STANDARD.encode(password);
255
256        // Construct the SSR link without group
257        let ssr_link = format!(
258            "{}:{}:{}:{}:{}:{}",
259            server, port, protocol, method, obfs, password_b64
260        );
261
262        // Base64 encode the entire link
263        let ssr_link_b64 = format!("ssr://{}", STANDARD.encode(&ssr_link));
264
265        // Parse the link
266        let result = explode_ssr(&ssr_link_b64, &mut node);
267
268        // Verify the result
269        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        // Check first node
312        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        // Check second node
334        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}