Skip to content

Commit

Permalink
SIP002 extended format URL. Allowing non-encoded userinfo
Browse files Browse the repository at this point in the history
  • Loading branch information
zonyitoo committed May 12, 2022
1 parent a2bcd69 commit eb4df55
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 27 deletions.
19 changes: 10 additions & 9 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/shadowsocks/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
94 changes: 76 additions & 18 deletions crates/shadowsocks/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ServerConfig, UrlParseError> {
let parsed = Url::parse(encoded).map_err(UrlParseError::from)?;

Expand All @@ -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())
}
};

Expand All @@ -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::<ServerAddr>() {
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::<Vec<(String, String)>>(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);
}
};
Expand All @@ -464,6 +518,10 @@ impl ServerConfig {
}
}

if let Some(frag) = parsed.fragment() {
svrconfig.set_remarks(frag);
}

Ok(svrconfig)
}

Expand Down

0 comments on commit eb4df55

Please sign in to comment.