Merge pull request #17 from Keivan-sf/9-shadowsocks-parser

9 shadowsocks parser
This commit is contained in:
Keivan
2025-07-27 20:36:26 +03:30
committed by GitHub
9 changed files with 132 additions and 4 deletions

View File

@@ -24,6 +24,15 @@ pub struct TrojanServerObject {
pub level: Option<u8>,
}
#[derive(Serialize, Deserialize)]
pub struct ShadowSocksServerObject {
pub address: Option<String>,
pub port: Option<u16>,
pub password: Option<String>,
pub level: Option<u8>,
pub method: Option<String>,
}
#[derive(Serialize, Deserialize)]
pub struct VlessOutboundSettings {
pub vnext: Vec<VnextServerObject>,
@@ -39,12 +48,18 @@ pub struct TrojanOutboundSettings {
pub servers: Vec<TrojanServerObject>,
}
#[derive(Serialize, Deserialize)]
pub struct ShadowSocksOutboundSettings {
pub servers: Vec<ShadowSocksServerObject>,
}
#[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<String>,
pub address: Option<String>,
pub port: Option<u16>,
pub server_method: Option<String>,
}

View File

@@ -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");
}

View File

@@ -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::<Uri>().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(),
};
}

View File

@@ -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(),
}],
});
}

View File

@@ -0,0 +1,6 @@
pub struct ShadowSocksAddress {
pub password: String,
pub method: String,
pub address: String,
pub port: u16,
}

View File

@@ -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,
};
}

View File

@@ -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,
};
}

View File

@@ -1,6 +1,6 @@
use crate::config_models::RawData;
use crate::parser::vmess::models::{self, VmessAddress};
use crate::utils::{get_parameter_value, url_decode};
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;
use serde_json::Value;
@@ -8,7 +8,9 @@ use serde_json::Value;
pub fn get_data(uri: &str) -> RawData {
let data = uri.split_once("vmess://").unwrap().1;
return match general_purpose::STANDARD.decode(data) {
return match general_purpose::STANDARD
.decode(url_decode_str(data).unwrap_or(String::from(data)))
{
Ok(decoded) => get_raw_data_from_base64(&decoded),
Err(_) => get_raw_data_from_uri(data),
};
@@ -55,6 +57,7 @@ fn get_raw_data_from_base64(decoded_base64: &Vec<u8>) -> RawData {
extra: url_decode(get_str_field(&json, "extra")),
// this probably does not exist in vmess uri
allowInsecure: None,
server_method: None,
};
}
@@ -100,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,
};
}

View File

@@ -1,5 +1,11 @@
pub mod inbound_generator;
pub fn url_decode_str(value: &str) -> Option<String> {
return urlencoding::decode(value)
.ok()
.map(|decoded| decoded.into_owned());
}
pub fn url_decode(value: Option<String>) -> Option<String> {
return value.and_then(|s| {
urlencoding::decode(&s)