From eb4df55ed69c5e4e88d0ef5daf2b27b07f657009 Mon Sep 17 00:00:00 2001 From: zonyitoo Date: Thu, 12 May 2022 19:56:28 +0800 Subject: [PATCH] SIP002 extended format URL. Allowing non-encoded userinfo https://github.com/shadowsocks/shadowsocks-org/issues/27#issuecomment-1124853747 --- Cargo.lock | 19 ++++--- crates/shadowsocks/Cargo.toml | 1 + crates/shadowsocks/src/config.rs | 94 ++++++++++++++++++++++++++------ 3 files changed, 87 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a70ceb1822fc..105e23830475 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -342,9 +342,9 @@ dependencies = [ [[package]] name = "clap" -version = "3.1.17" +version = "3.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47582c09be7c8b32c0ab3a6181825ababb713fde6fff20fc573a3870dd45c6a0" +checksum = "d2dbdf4bdacb33466e854ce889eee8dfd5729abf7ccd7664d0a2d60cd384440b" dependencies = [ "atty", "bitflags", @@ -569,9 +569,9 @@ dependencies = [ [[package]] name = "ed25519" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d5c4b5e5959dc2c2b89918d8e2cc40fcdd623cef026ed09d2f0ee05199dc8e4" +checksum = "d916019f70ae3a1faa1195685e290287f39207d38e6dfee727197cffcc002214" dependencies = [ "signature", ] @@ -2040,6 +2040,7 @@ dependencies = [ "lru_time_cache", "notify", "once_cell", + "percent-encoding", "pin-project", "rand", "sendfd", @@ -2239,12 +2240,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.4.5" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca642ba17f8b2995138b1d7711829c92e98c0a25ea019de790f4f09279c4e296" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" dependencies = [ "libc", - "windows-sys", + "winapi", ] [[package]] @@ -2276,9 +2277,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.92" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ff7c592601f11445996a06f8ad0c27f094a58857c2f89e97974ab9235b92c52" +checksum = "04066589568b72ec65f42d65a1a52436e954b168773148893c020269563decf2" dependencies = [ "proc-macro2", "quote", diff --git a/crates/shadowsocks/Cargo.toml b/crates/shadowsocks/Cargo.toml index fb9aa3f93a75..f14b17ed5fbe 100644 --- a/crates/shadowsocks/Cargo.toml +++ b/crates/shadowsocks/Cargo.toml @@ -66,6 +66,7 @@ lru_time_cache = { version = "0.11", optional = true } serde = { version = "1.0", features = ["derive"] } serde_urlencoded = "0.7" serde_json = "1.0" +percent-encoding = "2.1" futures = "0.3" async-trait = "0.1" diff --git a/crates/shadowsocks/src/config.rs b/crates/shadowsocks/src/config.rs index 25c9cc413f84..bab446d6cc97 100644 --- a/crates/shadowsocks/src/config.rs +++ b/crates/shadowsocks/src/config.rs @@ -389,6 +389,11 @@ impl ServerConfig { } /// Parse from [SIP002](https://github.com/shadowsocks/shadowsocks-org/issues/27) URL + /// + /// Extended formats: + /// + /// 1. QRCode URL supported by shadowsocks-android, https://github.com/shadowsocks/shadowsocks-android/issues/51 + /// 2. Plain userinfo:password format supported by go2-shadowsocks2 pub fn from_url(encoded: &str) -> Result { let parsed = Url::parse(encoded).map_err(UrlParseError::from)?; @@ -397,16 +402,71 @@ impl ServerConfig { } let user_info = parsed.username(); - let account = match decode_config(user_info, URL_SAFE_NO_PAD) { - Ok(account) => match String::from_utf8(account) { - Ok(ac) => ac, - Err(..) => { - return Err(UrlParseError::InvalidAuthInfo); + if user_info.is_empty() { + // This maybe a QRCode URL, which is ss://BASE64-URL-ENCODE(pass:encrypt@hostname:port) + + let encoded = match parsed.host_str() { + Some(e) => e, + None => return Err(UrlParseError::MissingHost), + }; + + let mut decoded_body = match decode_config(encoded, URL_SAFE_NO_PAD) { + Ok(b) => match String::from_utf8(b) { + Ok(b) => b, + Err(..) => return Err(UrlParseError::InvalidServerAddr), + }, + Err(err) => { + error!("failed to parse legacy ss://ENCODED with Base64, err: {}", err); + return Err(UrlParseError::InvalidServerAddr); } - }, - Err(err) => { - error!("Failed to parse UserInfo with Base64, err: {}", err); - return Err(UrlParseError::InvalidUserInfo); + }; + + decoded_body.insert_str(0, "ss://"); + // Parse it like ss://method:password@host:port + return ServerConfig::from_url(&decoded_body); + } + + let (method, pwd) = match parsed.password() { + Some(password) => { + // Plain method:password without base64 encoded + + let m = match percent_encoding::percent_decode_str(user_info).decode_utf8() { + Ok(m) => m, + Err(err) => { + error!("failed to parse percent-encoding method in userinfo, err: {}", err); + return Err(UrlParseError::InvalidAuthInfo); + } + }; + + let p = match percent_encoding::percent_decode_str(password).decode_utf8() { + Ok(m) => m, + Err(err) => { + error!("failed to parse percent-encoding password in userinfo, err: {}", err); + return Err(UrlParseError::InvalidAuthInfo); + } + }; + + (m, p) + } + None => { + let account = match decode_config(user_info, URL_SAFE_NO_PAD) { + Ok(account) => match String::from_utf8(account) { + Ok(ac) => ac, + Err(..) => return Err(UrlParseError::InvalidAuthInfo), + }, + Err(err) => { + error!("failed to parse UserInfo with Base64, err: {}", err); + return Err(UrlParseError::InvalidUserInfo); + } + }; + + let mut sp2 = account.splitn(2, ':'); + let (m, p) = match (sp2.next(), sp2.next()) { + (Some(m), Some(p)) => (m, p), + _ => return Err(UrlParseError::InvalidUserInfo), + }; + + (m.to_owned().into(), p.to_owned().into()) } }; @@ -418,28 +478,22 @@ impl ServerConfig { let port = parsed.port().unwrap_or(8388); let addr = format!("{}:{}", host, port); - let mut sp2 = account.splitn(2, ':'); - let (method, pwd) = match (sp2.next(), sp2.next()) { - (Some(m), Some(p)) => (m, p), - _ => return Err(UrlParseError::InvalidUserInfo), - }; - let addr = match addr.parse::() { Ok(a) => a, Err(err) => { - error!("Failed to parse \"{}\" to ServerAddr, err: {:?}", addr, err); + error!("failed to parse \"{}\" to ServerAddr, err: {:?}", addr, err); return Err(UrlParseError::InvalidServerAddr); } }; let method = method.parse().expect("method"); - let mut svrconfig = ServerConfig::new(addr, pwd.to_owned(), method); + let mut svrconfig = ServerConfig::new(addr, pwd, method); if let Some(q) = parsed.query() { let query = match serde_urlencoded::from_bytes::>(q.as_bytes()) { Ok(q) => q, Err(err) => { - error!("Failed to parse QueryString, err: {}", err); + error!("failed to parse QueryString, err: {}", err); return Err(UrlParseError::InvalidQueryString); } }; @@ -464,6 +518,10 @@ impl ServerConfig { } } + if let Some(frag) = parsed.fragment() { + svrconfig.set_remarks(frag); + } + Ok(svrconfig) }