From 574c3807f3e4fd88f969faf05ce5003858c70e9d Mon Sep 17 00:00:00 2001 From: Keivan-sf Date: Sun, 27 Jul 2025 20:35:12 +0330 Subject: [PATCH] Parse shadowsocks --- src/config_models/mod.rs | 16 +++++++ src/parser/mod.rs | 8 +++- src/parser/shadow_socks/data.rs | 73 +++++++++++++++++++++++++++++++ src/parser/shadow_socks/mod.rs | 15 +++++++ src/parser/shadow_socks/models.rs | 6 +++ src/parser/trojan/data.rs | 1 + src/parser/vless/data.rs | 1 + src/parser/vmess/data.rs | 4 +- 8 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 src/parser/shadow_socks/data.rs create mode 100644 src/parser/shadow_socks/mod.rs create mode 100644 src/parser/shadow_socks/models.rs diff --git a/src/config_models/mod.rs b/src/config_models/mod.rs index 4488584..5c94b68 100644 --- a/src/config_models/mod.rs +++ b/src/config_models/mod.rs @@ -24,6 +24,15 @@ pub struct TrojanServerObject { pub level: Option, } +#[derive(Serialize, Deserialize)] +pub struct ShadowSocksServerObject { + pub address: Option, + pub port: Option, + pub password: Option, + pub level: Option, + pub method: Option, +} + #[derive(Serialize, Deserialize)] pub struct VlessOutboundSettings { pub vnext: Vec, @@ -39,12 +48,18 @@ pub struct TrojanOutboundSettings { pub servers: Vec, } +#[derive(Serialize, Deserialize)] +pub struct ShadowSocksOutboundSettings { + pub servers: Vec, +} + #[derive(Serialize, Deserialize)] #[serde(untagged)] pub enum OutboundSettings { Vless(VlessOutboundSettings), Vmess(VmessOutboundSettings), Trojan(TrojanOutboundSettings), + ShadowSocks(ShadowSocksOutboundSettings), } #[derive(Serialize, Deserialize)] @@ -227,4 +242,5 @@ pub struct RawData { pub uuid: Option, pub address: Option, pub port: Option, + pub server_method: Option, } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 1f83c60..c5ce531 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -5,10 +5,11 @@ use crate::config_models::{ }; use crate::utils::{inbound_generator, parse_raw_json}; +mod shadow_socks; +mod trojan; mod uri_identifier; mod vless; mod vmess; -mod trojan; pub fn get_name(uri: &str) -> String { let (_, data, _) = get_uri_data(uri); @@ -168,6 +169,11 @@ fn get_uri_data(uri: &str) -> (String, RawData, OutboundSettings) { let s = trojan::create_outbound_settings(&d); (String::from("trojan"), d, s) } + Some(uri_identifier::Protocols::Shadowsocks) => { + let d = shadow_socks::data::get_data(uri); + let s = shadow_socks::create_outbound_settings(&d); + (String::from("shadowsocks"), d, s) + } Some(_) => { panic!("The protocol was recognized but is not supported yet"); } diff --git a/src/parser/shadow_socks/data.rs b/src/parser/shadow_socks/data.rs new file mode 100644 index 0000000..7f1c474 --- /dev/null +++ b/src/parser/shadow_socks/data.rs @@ -0,0 +1,73 @@ +use http::Uri; + +use crate::{ + config_models::RawData, + parser::shadow_socks::models, + utils::{url_decode, url_decode_str}, +}; +use base64::{engine::general_purpose, Engine}; + +pub fn get_data(uri: &str) -> RawData { + let data = uri.split_once("ss://").unwrap().1; + let (raw_data, name) = data.split_once("#").unwrap_or((data, "")); + let (raw_uri, _) = raw_data.split_once("?").unwrap_or((raw_data, "")); + let parsed_address = parse_ss_address(raw_uri); + return RawData { + remarks: url_decode(Some(String::from(name))).unwrap_or(String::from("")), + server_method: url_decode(Some(parsed_address.method)), + address: Some(parsed_address.address), + port: Some(parsed_address.port), + uuid: url_decode(Some(parsed_address.password)), + r#type: Some(String::from("tcp")), + header_type: Some(String::from("none")), + security: None, + fp: None, + sni: None, + pbk: None, + sid: None, + key: None, + spx: None, + flow: None, + path: None, + host: None, + seed: None, + mode: None, + slpn: None, + alpn: None, + extra: None, + authority: None, + encryption: None, + service_name: None, + quic_security: None, + allowInsecure: None, + vnext_security: None, + }; +} + +fn parse_ss_address(raw_data: &str) -> models::ShadowSocksAddress { + let (userinfo, raw_address): (String, &str) = match raw_data.split_once("@") { + None => { + panic!("Wrong shadowsocks format, no `@` found in the address"); + } + Some(data) => (String::from(data.0), data.1), + }; + let address_wo_slash = raw_address.strip_suffix("/").unwrap_or(raw_address); + + let parsed = address_wo_slash.parse::().unwrap(); + + let a = general_purpose::STANDARD + .decode(url_decode_str(&userinfo).unwrap_or(userinfo)) + .expect("User info is not base64"); + + let (method, password) = std::str::from_utf8(&a) + .expect("Base64 did not yield a valid utf-8 string") + .split_once(":") + .expect("No `:` found in the decoded base64"); + + return models::ShadowSocksAddress { + method: String::from(method), + password: String::from(password), + address: parsed.host().unwrap().to_string(), + port: parsed.port().unwrap().as_u16(), + }; +} diff --git a/src/parser/shadow_socks/mod.rs b/src/parser/shadow_socks/mod.rs new file mode 100644 index 0000000..4254bea --- /dev/null +++ b/src/parser/shadow_socks/mod.rs @@ -0,0 +1,15 @@ +pub mod data; +mod models; +use crate::config_models::*; + +pub fn create_outbound_settings(data: &RawData) -> OutboundSettings { + return OutboundSettings::ShadowSocks(ShadowSocksOutboundSettings { + servers: vec![ShadowSocksServerObject { + address: data.address.clone(), + port: data.port, + password: data.uuid.clone(), + level: Some(0), + method: data.server_method.clone(), + }], + }); +} diff --git a/src/parser/shadow_socks/models.rs b/src/parser/shadow_socks/models.rs new file mode 100644 index 0000000..2ce57cd --- /dev/null +++ b/src/parser/shadow_socks/models.rs @@ -0,0 +1,6 @@ +pub struct ShadowSocksAddress { + pub password: String, + pub method: String, + pub address: String, + pub port: u16, +} diff --git a/src/parser/trojan/data.rs b/src/parser/trojan/data.rs index 25ec883..e6729e8 100644 --- a/src/parser/trojan/data.rs +++ b/src/parser/trojan/data.rs @@ -40,6 +40,7 @@ pub fn get_data(uri: &str) -> RawData { spx: url_decode(get_parameter_value(&query, "spx")), extra: url_decode(get_parameter_value(&query, "extra")), allowInsecure: get_parameter_value(&query, "allowInsecure"), + server_method: None, }; } diff --git a/src/parser/vless/data.rs b/src/parser/vless/data.rs index e437e6b..944f47a 100644 --- a/src/parser/vless/data.rs +++ b/src/parser/vless/data.rs @@ -40,6 +40,7 @@ pub fn get_data(uri: &str) -> RawData { spx: url_decode(get_parameter_value(&query, "spx")), extra: url_decode(get_parameter_value(&query, "extra")), allowInsecure: get_parameter_value(&query, "allowInsecure"), + server_method: None, }; } diff --git a/src/parser/vmess/data.rs b/src/parser/vmess/data.rs index 985d8b9..abead67 100644 --- a/src/parser/vmess/data.rs +++ b/src/parser/vmess/data.rs @@ -1,5 +1,5 @@ use crate::config_models::RawData; -use crate::parser::vmess::models::{self, VmessAddress}; +use crate::parser::vmess::models::VmessAddress; use crate::utils::{get_parameter_value, url_decode, url_decode_str}; use base64::{engine::general_purpose, Engine}; use http::Uri; @@ -57,6 +57,7 @@ fn get_raw_data_from_base64(decoded_base64: &Vec) -> RawData { extra: url_decode(get_str_field(&json, "extra")), // this probably does not exist in vmess uri allowInsecure: None, + server_method: None, }; } @@ -102,6 +103,7 @@ fn get_raw_data_from_uri(uri: &str) -> RawData { spx: url_decode(get_parameter_value(&query, "spx")), extra: url_decode(get_parameter_value(&query, "extra")), allowInsecure: get_parameter_value(&query, "allowInsecure"), + server_method: None, }; }