libsubconverter/parser/explodes/
snell.rs

1use crate::{
2    models::{Proxy, SNELL_DEFAULT_GROUP},
3    utils::url_decode,
4};
5use std::collections::HashMap;
6use url::Url;
7
8/// Parse a Snell link into a Proxy object
9pub fn explode_snell(snell: &str, node: &mut Proxy) -> bool {
10    // Check if the link starts with snell://
11    if !snell.starts_with("snell://") {
12        return false;
13    }
14
15    // Try to parse as URL
16    let url = match Url::parse(snell) {
17        Ok(url) => url,
18        Err(_) => return false,
19    };
20
21    // Extract host and port
22    let host = match url.host_str() {
23        Some(host) => host,
24        None => return false,
25    };
26    let port = url.port().unwrap_or(8388);
27    if port == 0 {
28        return false; // Skip if port is 0
29    }
30
31    // Extract password (username in URL)
32    let password = url.username();
33    if password.is_empty() {
34        return false;
35    }
36
37    // Extract parameters from the query string
38    let mut params = HashMap::new();
39    for (key, value) in url.query_pairs() {
40        params.insert(key.to_string(), url_decode(&value));
41    }
42
43    // Extract obfs
44    let obfs = params.get("obfs").map(|s| s.as_str()).unwrap_or("none");
45
46    // Extract host
47    let host_param = params.get("host").map(|s| s.as_str()).unwrap_or("");
48
49    // Extract version
50    let version = params
51        .get("version")
52        .map(|s| s.parse::<u16>().unwrap_or(1))
53        .unwrap_or(1);
54
55    // Extract UDP, TFO, and allow_insecure flags
56    let udp = params.get("udp").map(|s| s == "true" || s == "1");
57    let tfo = params.get("tfo").map(|s| s == "true" || s == "1");
58    let allow_insecure = params
59        .get("skip-cert-verify")
60        .map(|s| s == "true" || s == "1");
61
62    // Extract remark from the fragment
63    let remark = url_decode(url.fragment().unwrap_or(""));
64    let formatted_remark = if remark.is_empty() {
65        format!("{} ({})", host, port)
66    } else {
67        remark.to_string()
68    };
69
70    // Create the proxy object
71    *node = Proxy::snell_construct(
72        SNELL_DEFAULT_GROUP.to_string(),
73        formatted_remark,
74        host.to_string(),
75        port,
76        password.to_string(),
77        obfs.to_string(),
78        host_param.to_string(),
79        version,
80        udp,
81        tfo,
82        allow_insecure,
83        None,
84    );
85
86    true
87}
88
89/// Parse a Snell configuration in Surge format
90/// Format: snell = server, port, psk=password, obfs=obfs, obfs-host=host, version=version
91pub fn explode_snell_surge(surge: &str, node: &mut Proxy) -> bool {
92    if !surge.starts_with("snell") {
93        return false;
94    }
95
96    // Split the line by commas and remove spaces
97    let parts: Vec<&str> = surge.split(',').map(|s| s.trim()).collect();
98    if parts.len() < 3 {
99        return false;
100    }
101
102    // Extract server and port
103    let server_part = parts[0];
104    let server = if server_part.contains('=') {
105        server_part
106            .split('=')
107            .nth(1)
108            .unwrap_or("")
109            .trim()
110            .to_string()
111    } else {
112        server_part.replace("snell", "").trim().to_string()
113    };
114    if server.is_empty() {
115        return false;
116    }
117
118    let port_str = parts[1];
119    let port = match port_str.parse::<u16>() {
120        Ok(p) => p,
121        Err(_) => return false,
122    };
123    if port == 0 {
124        return false;
125    }
126
127    // Default values
128    let mut password = String::new();
129    let mut obfs = String::new();
130    let mut obfs_host = String::new();
131    let mut version = 1u16;
132    let mut udp = None;
133    let mut tfo = None;
134    let mut allow_insecure = None;
135
136    // Parse additional parameters
137    for i in 2..parts.len() {
138        let param_parts: Vec<&str> = parts[i].split('=').collect();
139        if param_parts.len() != 2 {
140            continue;
141        }
142        let key = param_parts[0].trim();
143        let value = param_parts[1].trim();
144
145        match key {
146            "psk" => password = value.to_string(),
147            "obfs" => obfs = value.to_string(),
148            "obfs-host" => obfs_host = value.to_string(),
149            "version" => version = value.parse::<u16>().unwrap_or(1),
150            "udp" | "udp-relay" => udp = Some(value == "true" || value == "1"),
151            "tfo" => tfo = Some(value == "true" || value == "1"),
152            "skip-cert-verify" => allow_insecure = Some(value == "true" || value == "1"),
153            _ => {}
154        }
155    }
156
157    if password.is_empty() {
158        return false;
159    }
160
161    // Create remark
162    let remark = format!("{} ({})", server, port);
163
164    // Create the proxy object
165    *node = Proxy::snell_construct(
166        SNELL_DEFAULT_GROUP.to_string(),
167        remark,
168        server,
169        port,
170        password,
171        obfs,
172        obfs_host,
173        version,
174        udp,
175        tfo,
176        allow_insecure,
177        None,
178    );
179
180    true
181}