From 2a6c452c0dc88d7a400d5de30c5e270e72634c28 Mon Sep 17 00:00:00 2001 From: Keivan-sf Date: Sun, 27 Jul 2025 18:19:35 +0330 Subject: [PATCH] Parse trojan URIs --- src/config_models/mod.rs | 18 +++++++++-- src/parser/mod.rs | 6 ++++ src/parser/trojan/data.rs | 62 +++++++++++++++++++++++++++++++++++++ src/parser/trojan/mod.rs | 14 +++++++++ src/parser/trojan/models.rs | 5 +++ src/parser/vless/mod.rs | 2 +- src/parser/vmess/mod.rs | 2 +- 7 files changed, 105 insertions(+), 4 deletions(-) create mode 100644 src/parser/trojan/data.rs create mode 100644 src/parser/trojan/mod.rs create mode 100644 src/parser/trojan/models.rs diff --git a/src/config_models/mod.rs b/src/config_models/mod.rs index bea63b6..6966064 100644 --- a/src/config_models/mod.rs +++ b/src/config_models/mod.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] -pub struct VlessUser { +pub struct VnextUser { pub id: Option, pub encryption: Option, pub flow: Option, @@ -13,7 +13,15 @@ pub struct VlessUser { pub struct VlessServerObject { pub address: Option, pub port: Option, - pub users: Option>, + pub users: Option>, +} + +#[derive(Serialize, Deserialize)] +pub struct TrojanServerObject { + pub address: Option, + pub port: Option, + pub password: Option, + pub level: Option, } #[derive(Serialize, Deserialize)] @@ -26,11 +34,17 @@ pub struct VmessOutboundSettings { pub vnext: Vec, } +#[derive(Serialize, Deserialize)] +pub struct TrojanOutboundSettings { + pub servers: Vec, +} + #[derive(Serialize, Deserialize)] #[serde(untagged)] pub enum OutboundSettings { Vless(VlessOutboundSettings), Vmess(VmessOutboundSettings), + Trojan(TrojanOutboundSettings), } #[derive(Serialize, Deserialize)] diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 057206f..1f83c60 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -8,6 +8,7 @@ use crate::utils::{inbound_generator, parse_raw_json}; mod uri_identifier; mod vless; mod vmess; +mod trojan; pub fn get_name(uri: &str) -> String { let (_, data, _) = get_uri_data(uri); @@ -162,6 +163,11 @@ fn get_uri_data(uri: &str) -> (String, RawData, OutboundSettings) { let s = vmess::create_outbound_settings(&d); (String::from("vmess"), d, s) } + Some(uri_identifier::Protocols::Trojan) => { + let d = trojan::data::get_data(uri); + let s = trojan::create_outbound_settings(&d); + (String::from("trojan"), d, s) + } Some(_) => { panic!("The protocol was recognized but is not supported yet"); } diff --git a/src/parser/trojan/data.rs b/src/parser/trojan/data.rs new file mode 100644 index 0000000..1d3200d --- /dev/null +++ b/src/parser/trojan/data.rs @@ -0,0 +1,62 @@ +use crate::config_models::RawData; +use crate::parser::trojan::models; +use crate::utils::{get_parameter_value, url_decode}; +use http::Uri; + +pub fn get_data(uri: &str) -> RawData { + let data = uri.split_once("trojan://").unwrap().1; + let query_and_name = uri.split_once("?").unwrap().1; + let (raw_query, name) = query_and_name + .split_once("#") + .unwrap_or((query_and_name, "")); + let parsed_address = parse_trojan_address(data.split_once("?").unwrap().0); + let query: Vec<(&str, &str)> = querystring::querify(raw_query); + + return RawData { + remarks: String::from(name), + uuid: Some(parsed_address.uuid), + port: Some(parsed_address.port), + address: Some(parsed_address.address), + alpn: url_decode(get_parameter_value(&query, "alpn")), + path: url_decode(get_parameter_value(&query, "path")), + authority: url_decode(get_parameter_value(&query, "authority")), + pbk: url_decode(get_parameter_value(&query, "pbk")), + security: get_parameter_value(&query, "security"), + sid: url_decode(get_parameter_value(&query, "sid")), + flow: get_parameter_value(&query, "flow"), + sni: get_parameter_value(&query, "sni"), + fp: url_decode(get_parameter_value(&query, "fp")), + r#type: get_parameter_value(&query, "type"), + encryption: get_parameter_value(&query, "encryption"), + header_type: get_parameter_value(&query, "headerType"), + host: url_decode(get_parameter_value(&query, "host")), + seed: url_decode(get_parameter_value(&query, "seed")), + quic_security: get_parameter_value(&query, "quicSecurity"), + key: get_parameter_value(&query, "key"), + mode: url_decode(get_parameter_value(&query, "mode")), + service_name: url_decode(get_parameter_value(&query, "serviceName")), + vnext_security: None, + slpn: get_parameter_value(&query, "slpn"), + spx: url_decode(get_parameter_value(&query, "spx")), + extra: url_decode(get_parameter_value(&query, "extra")), + allowInsecure: get_parameter_value(&query, "allowInsecure"), + }; +} + +fn parse_trojan_address(raw_data: &str) -> models::TrojanAddress { + let (uuid, raw_address): (String, &str) = match raw_data.split_once("@") { + None => { + panic!("Wrong trojan 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(); + + return models::TrojanAddress{ + uuid: url_decode(Some(uuid)).unwrap(), + address: parsed.host().unwrap().to_string(), + port: parsed.port().unwrap().as_u16(), + }; +} diff --git a/src/parser/trojan/mod.rs b/src/parser/trojan/mod.rs new file mode 100644 index 0000000..3361dc0 --- /dev/null +++ b/src/parser/trojan/mod.rs @@ -0,0 +1,14 @@ +pub mod data; +mod models; +use crate::config_models::*; + +pub fn create_outbound_settings(data: &RawData) -> OutboundSettings { + return OutboundSettings::Trojan(TrojanOutboundSettings { + servers: vec![TrojanServerObject { + address: data.address.clone(), + port: data.port, + password: data.uuid.clone(), + level: Some(0), + }], + }); +} diff --git a/src/parser/trojan/models.rs b/src/parser/trojan/models.rs new file mode 100644 index 0000000..0d2b989 --- /dev/null +++ b/src/parser/trojan/models.rs @@ -0,0 +1,5 @@ +pub struct TrojanAddress { + pub uuid: String, + pub address: String, + pub port: u16, +} diff --git a/src/parser/vless/mod.rs b/src/parser/vless/mod.rs index 0ec40a1..b1ce171 100644 --- a/src/parser/vless/mod.rs +++ b/src/parser/vless/mod.rs @@ -7,7 +7,7 @@ pub fn create_outbound_settings(data: &RawData) -> OutboundSettings { vnext: vec![VlessServerObject { port: data.port, address: data.address.clone(), - users: Some(vec![VlessUser { + users: Some(vec![VnextUser { id: data.uuid.clone(), flow: data.flow.clone(), encryption: Some(data.encryption.clone().unwrap_or(String::from("none"))), diff --git a/src/parser/vmess/mod.rs b/src/parser/vmess/mod.rs index 36b7497..0c37880 100644 --- a/src/parser/vmess/mod.rs +++ b/src/parser/vmess/mod.rs @@ -7,7 +7,7 @@ pub fn create_outbound_settings(data: &RawData) -> OutboundSettings { vnext: vec![VlessServerObject { port: data.port, address: data.address.clone(), - users: Some(vec![VlessUser { + users: Some(vec![VnextUser { id: data.uuid.clone(), flow: data.flow.clone(), encryption: Some(data.encryption.clone().unwrap_or(String::from("none"))),