Skip to content

Commit eb4df55

Browse files
committed
SIP002 extended format URL. Allowing non-encoded userinfo
shadowsocks/shadowsocks-org#27 (comment)
1 parent a2bcd69 commit eb4df55

File tree

3 files changed

+87
-27
lines changed

3 files changed

+87
-27
lines changed

Cargo.lock

+10-9
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/shadowsocks/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ lru_time_cache = { version = "0.11", optional = true }
6666
serde = { version = "1.0", features = ["derive"] }
6767
serde_urlencoded = "0.7"
6868
serde_json = "1.0"
69+
percent-encoding = "2.1"
6970

7071
futures = "0.3"
7172
async-trait = "0.1"

crates/shadowsocks/src/config.rs

+76-18
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,11 @@ impl ServerConfig {
389389
}
390390

391391
/// Parse from [SIP002](https://github.com/shadowsocks/shadowsocks-org/issues/27) URL
392+
///
393+
/// Extended formats:
394+
///
395+
/// 1. QRCode URL supported by shadowsocks-android, https://github.com/shadowsocks/shadowsocks-android/issues/51
396+
/// 2. Plain userinfo:password format supported by go2-shadowsocks2
392397
pub fn from_url(encoded: &str) -> Result<ServerConfig, UrlParseError> {
393398
let parsed = Url::parse(encoded).map_err(UrlParseError::from)?;
394399

@@ -397,16 +402,71 @@ impl ServerConfig {
397402
}
398403

399404
let user_info = parsed.username();
400-
let account = match decode_config(user_info, URL_SAFE_NO_PAD) {
401-
Ok(account) => match String::from_utf8(account) {
402-
Ok(ac) => ac,
403-
Err(..) => {
404-
return Err(UrlParseError::InvalidAuthInfo);
405+
if user_info.is_empty() {
406+
// This maybe a QRCode URL, which is ss://BASE64-URL-ENCODE(pass:encrypt@hostname:port)
407+
408+
let encoded = match parsed.host_str() {
409+
Some(e) => e,
410+
None => return Err(UrlParseError::MissingHost),
411+
};
412+
413+
let mut decoded_body = match decode_config(encoded, URL_SAFE_NO_PAD) {
414+
Ok(b) => match String::from_utf8(b) {
415+
Ok(b) => b,
416+
Err(..) => return Err(UrlParseError::InvalidServerAddr),
417+
},
418+
Err(err) => {
419+
error!("failed to parse legacy ss://ENCODED with Base64, err: {}", err);
420+
return Err(UrlParseError::InvalidServerAddr);
405421
}
406-
},
407-
Err(err) => {
408-
error!("Failed to parse UserInfo with Base64, err: {}", err);
409-
return Err(UrlParseError::InvalidUserInfo);
422+
};
423+
424+
decoded_body.insert_str(0, "ss://");
425+
// Parse it like ss://method:password@host:port
426+
return ServerConfig::from_url(&decoded_body);
427+
}
428+
429+
let (method, pwd) = match parsed.password() {
430+
Some(password) => {
431+
// Plain method:password without base64 encoded
432+
433+
let m = match percent_encoding::percent_decode_str(user_info).decode_utf8() {
434+
Ok(m) => m,
435+
Err(err) => {
436+
error!("failed to parse percent-encoding method in userinfo, err: {}", err);
437+
return Err(UrlParseError::InvalidAuthInfo);
438+
}
439+
};
440+
441+
let p = match percent_encoding::percent_decode_str(password).decode_utf8() {
442+
Ok(m) => m,
443+
Err(err) => {
444+
error!("failed to parse percent-encoding password in userinfo, err: {}", err);
445+
return Err(UrlParseError::InvalidAuthInfo);
446+
}
447+
};
448+
449+
(m, p)
450+
}
451+
None => {
452+
let account = match decode_config(user_info, URL_SAFE_NO_PAD) {
453+
Ok(account) => match String::from_utf8(account) {
454+
Ok(ac) => ac,
455+
Err(..) => return Err(UrlParseError::InvalidAuthInfo),
456+
},
457+
Err(err) => {
458+
error!("failed to parse UserInfo with Base64, err: {}", err);
459+
return Err(UrlParseError::InvalidUserInfo);
460+
}
461+
};
462+
463+
let mut sp2 = account.splitn(2, ':');
464+
let (m, p) = match (sp2.next(), sp2.next()) {
465+
(Some(m), Some(p)) => (m, p),
466+
_ => return Err(UrlParseError::InvalidUserInfo),
467+
};
468+
469+
(m.to_owned().into(), p.to_owned().into())
410470
}
411471
};
412472

@@ -418,28 +478,22 @@ impl ServerConfig {
418478
let port = parsed.port().unwrap_or(8388);
419479
let addr = format!("{}:{}", host, port);
420480

421-
let mut sp2 = account.splitn(2, ':');
422-
let (method, pwd) = match (sp2.next(), sp2.next()) {
423-
(Some(m), Some(p)) => (m, p),
424-
_ => return Err(UrlParseError::InvalidUserInfo),
425-
};
426-
427481
let addr = match addr.parse::<ServerAddr>() {
428482
Ok(a) => a,
429483
Err(err) => {
430-
error!("Failed to parse \"{}\" to ServerAddr, err: {:?}", addr, err);
484+
error!("failed to parse \"{}\" to ServerAddr, err: {:?}", addr, err);
431485
return Err(UrlParseError::InvalidServerAddr);
432486
}
433487
};
434488

435489
let method = method.parse().expect("method");
436-
let mut svrconfig = ServerConfig::new(addr, pwd.to_owned(), method);
490+
let mut svrconfig = ServerConfig::new(addr, pwd, method);
437491

438492
if let Some(q) = parsed.query() {
439493
let query = match serde_urlencoded::from_bytes::<Vec<(String, String)>>(q.as_bytes()) {
440494
Ok(q) => q,
441495
Err(err) => {
442-
error!("Failed to parse QueryString, err: {}", err);
496+
error!("failed to parse QueryString, err: {}", err);
443497
return Err(UrlParseError::InvalidQueryString);
444498
}
445499
};
@@ -464,6 +518,10 @@ impl ServerConfig {
464518
}
465519
}
466520

521+
if let Some(frag) = parsed.fragment() {
522+
svrconfig.set_remarks(frag);
523+
}
524+
467525
Ok(svrconfig)
468526
}
469527

0 commit comments

Comments
 (0)