libsubconverter/parser/explodes/
netch.rs

1use crate::models::{
2    Proxy, HTTP_DEFAULT_GROUP, SOCKS_DEFAULT_GROUP, SSR_DEFAULT_GROUP, SS_DEFAULT_GROUP,
3    TROJAN_DEFAULT_GROUP, V2RAY_DEFAULT_GROUP,
4};
5use crate::utils::base64::{base64_encode, url_safe_base64_decode};
6use serde_json::Value;
7
8/// Parse a Netch JSON configuration into a Proxy object
9/// Matches C++ explodeNetch implementation
10pub fn explode_netch(netch: &str, node: &mut Proxy) -> bool {
11    // Handle the Netch protocol scheme and decode base64
12    if !netch.starts_with("Netch://") {
13        return false;
14    }
15
16    let decoded = url_safe_base64_decode(&netch[8..]);
17
18    // Parse the JSON content
19    let json: Value = match serde_json::from_str(&decoded) {
20        Ok(j) => j,
21        Err(_) => return false,
22    };
23
24    // Check if it's a valid JSON object
25    if !json.is_object() {
26        return false;
27    }
28
29    // Check if required fields exist
30    let type_str = json.get("Type").and_then(Value::as_str).unwrap_or("");
31    if type_str.is_empty() {
32        return false;
33    }
34
35    let remark = json.get("Remark").and_then(Value::as_str).unwrap_or("");
36    let server = json.get("Hostname").and_then(Value::as_str).unwrap_or("");
37    let port_str = json.get("Port").and_then(Value::as_str).unwrap_or("0");
38
39    if port_str == "0" {
40        return false;
41    }
42
43    let port = port_str.parse::<u16>().unwrap_or(0);
44    if port == 0 {
45        return false;
46    }
47
48    // Extract optional common fields
49    let group = json.get("Group").and_then(Value::as_str).unwrap_or("");
50    let udp = json.get("EnableUDP").and_then(Value::as_bool);
51    let tfo = json.get("EnableTFO").and_then(Value::as_bool);
52    let scv = json.get("AllowInsecure").and_then(Value::as_bool);
53
54    // Set default remark if empty
55    let remark = if remark.is_empty() {
56        format!("{}:{}", server, port)
57    } else {
58        remark.to_string()
59    };
60
61    // Process based on proxy type
62    match type_str {
63        "Shadowsocks" | "SS" => {
64            let method = json
65                .get("EncryptMethod")
66                .and_then(Value::as_str)
67                .or_else(|| json.get("Method").and_then(Value::as_str))
68                .unwrap_or("");
69            let password = json.get("Password").and_then(Value::as_str).unwrap_or("");
70
71            if method.is_empty() || password.is_empty() {
72                return false;
73            }
74
75            let plugin = json.get("Plugin").and_then(Value::as_str).unwrap_or("");
76            let plugin_opts = json
77                .get("PluginOption")
78                .and_then(Value::as_str)
79                .unwrap_or("");
80
81            let group = if group.is_empty() {
82                SS_DEFAULT_GROUP
83            } else {
84                group
85            };
86
87            *node = Proxy::ss_construct(
88                group,
89                &remark,
90                server,
91                port,
92                password,
93                method,
94                plugin,
95                plugin_opts,
96                udp,
97                tfo,
98                scv,
99                None,
100                "",
101            );
102
103            true
104        }
105        "ShadowsocksR" | "SSR" => {
106            let method = json
107                .get("EncryptMethod")
108                .and_then(Value::as_str)
109                .or_else(|| json.get("Method").and_then(Value::as_str))
110                .unwrap_or("");
111            let password = json.get("Password").and_then(Value::as_str).unwrap_or("");
112            let protocol = json.get("Protocol").and_then(Value::as_str).unwrap_or("");
113            let obfs = json.get("OBFS").and_then(Value::as_str).unwrap_or("");
114            let protocol_param = json
115                .get("ProtocolParam")
116                .and_then(Value::as_str)
117                .unwrap_or("");
118            let obfs_param = json.get("OBFSParam").and_then(Value::as_str).unwrap_or("");
119
120            if method.is_empty() || password.is_empty() || protocol.is_empty() || obfs.is_empty() {
121                return false;
122            }
123
124            let group = if group.is_empty() {
125                SSR_DEFAULT_GROUP
126            } else {
127                group
128            };
129
130            *node = Proxy::ssr_construct(
131                group,
132                &remark,
133                server,
134                port,
135                protocol,
136                method,
137                obfs,
138                password,
139                obfs_param,
140                protocol_param,
141                udp,
142                tfo,
143                scv,
144                "",
145            );
146
147            true
148        }
149        "SOCKS5" | "Socks5" => {
150            let username = json.get("Username").and_then(Value::as_str).unwrap_or("");
151            let password = json.get("Password").and_then(Value::as_str).unwrap_or("");
152
153            let group = if group.is_empty() {
154                SOCKS_DEFAULT_GROUP
155            } else {
156                group
157            };
158
159            *node = Proxy::socks_construct(
160                group, &remark, server, port, username, password, udp, tfo, scv, "",
161            );
162
163            true
164        }
165        "HTTP" | "HTTPS" => {
166            let username = json.get("Username").and_then(Value::as_str).unwrap_or("");
167            let password = json.get("Password").and_then(Value::as_str).unwrap_or("");
168            let is_https = type_str == "HTTPS";
169
170            let group = if group.is_empty() {
171                HTTP_DEFAULT_GROUP
172            } else {
173                group
174            };
175
176            *node = Proxy::http_construct(
177                group, &remark, server, port, username, password, is_https, tfo, scv, None, "",
178            );
179
180            true
181        }
182        "Trojan" => {
183            let password = json.get("Password").and_then(Value::as_str).unwrap_or("");
184            if password.is_empty() {
185                return false;
186            }
187
188            let sni = json
189                .get("Host")
190                .and_then(Value::as_str)
191                .or_else(|| json.get("ServerName").and_then(Value::as_str))
192                .map(|s| s.to_string());
193
194            let group = if group.is_empty() {
195                TROJAN_DEFAULT_GROUP
196            } else {
197                group
198            };
199
200            *node = Proxy::trojan_construct(
201                group.to_string(),
202                remark,
203                server.to_string(),
204                port,
205                password.to_string(),
206                None,
207                sni.clone(),
208                None,
209                sni,
210                true,
211                udp,
212                tfo,
213                scv,
214                None,
215                None,
216            );
217
218            true
219        }
220        "VMess" => {
221            let uuid = json
222                .get("UserID")
223                .and_then(Value::as_str)
224                .or_else(|| json.get("Id").and_then(Value::as_str))
225                .unwrap_or("");
226            if uuid.is_empty() {
227                return false;
228            }
229
230            let alter_id = json
231                .get("AlterID")
232                .and_then(Value::as_u64)
233                .or_else(|| json.get("AlterId").and_then(Value::as_u64))
234                .unwrap_or(0) as u16;
235            let network = json
236                .get("TransferProtocol")
237                .and_then(Value::as_str)
238                .or_else(|| json.get("Network").and_then(Value::as_str))
239                .unwrap_or("tcp");
240            let security = json
241                .get("EncryptMethod")
242                .and_then(Value::as_str)
243                .or_else(|| json.get("Security").and_then(Value::as_str))
244                .unwrap_or("auto");
245            let tls = json
246                .get("TLSSecure")
247                .and_then(Value::as_bool)
248                .or_else(|| json.get("TLS").and_then(Value::as_bool))
249                .unwrap_or(false);
250
251            let host = json.get("Host").and_then(Value::as_str).unwrap_or("");
252            let path = json.get("Path").and_then(Value::as_str).unwrap_or("/");
253            let sni = json.get("ServerName").and_then(Value::as_str).unwrap_or("");
254            let fake_type = json.get("FakeType").and_then(Value::as_str).unwrap_or("");
255            let edge = json.get("Edge").and_then(Value::as_str).unwrap_or("");
256
257            let group = if group.is_empty() {
258                V2RAY_DEFAULT_GROUP
259            } else {
260                group
261            };
262
263            *node = Proxy::vmess_construct(
264                group,
265                &remark,
266                server,
267                port,
268                fake_type,
269                uuid,
270                alter_id,
271                network,
272                security,
273                path,
274                host,
275                edge,
276                if tls { "tls" } else { "" },
277                sni,
278                udp,
279                tfo,
280                scv,
281                None,
282                "",
283            );
284
285            true
286        }
287        _ => false,
288    }
289}
290
291/// Parse a Netch configuration file into a vector of Proxy objects
292/// Matches C++ explodeNetchConf implementation
293pub fn explode_netch_conf(content: &str, nodes: &mut Vec<Proxy>) -> bool {
294    // Parse JSON
295    let json: Value = match serde_json::from_str(content) {
296        Ok(j) => j,
297        Err(_) => return false,
298    };
299
300    // Check if it's a valid JSON object with Server field
301    if !json.is_object() || !json.get("Server").is_some() {
302        return false;
303    }
304
305    let servers = match json.get("Server") {
306        Some(Value::Array(arr)) => arr,
307        _ => return false,
308    };
309
310    let mut success = false;
311
312    // Process each server
313    for server in servers {
314        let server_str = match serde_json::to_string(server) {
315            Ok(s) => s,
316            Err(_) => continue,
317        };
318
319        // Create Netch URL
320        let netch_url = format!("Netch://{}", base64_encode(&server_str));
321
322        let mut node = Proxy::default();
323        if explode_netch(&netch_url, &mut node) {
324            nodes.push(node);
325            success = true;
326        }
327    }
328
329    success
330}