pub fn from_url(encoded: &str) -> Result<Self, UrlParseError> {
        let parsed = Url::parse(encoded).map_err(UrlParseError::from)?;

        if parsed.scheme() != "ss" {
            return Err(UrlParseError::InvalidScheme);
        }

        let user_info = parsed.username();
        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 URL_PASSWORD_BASE64_ENGINE.decode(encoded) {
                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);
                }
            };

            decoded_body.insert_str(0, "ss://");
            // Parse it like ss://method:password@host:port
            return Self::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-encoded 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-encoded password in userinfo, err: {}", err);
                        return Err(UrlParseError::InvalidAuthInfo);
                    }
                };

                (m, p)
            }
            None => {
                // userinfo is not required to be percent encoded, but some implementation did.
                // If the base64 library have padding = added to the encoded string, then it will become %3D.

                let decoded_user_info = match percent_encoding::percent_decode_str(user_info).decode_utf8() {
                    Ok(m) => m,
                    Err(err) => {
                        error!("failed to parse percent-encoded userinfo, err: {}", err);
                        return Err(UrlParseError::InvalidAuthInfo);
                    }
                };

                // reborrow to fit AsRef<[u8]>
                let decoded_user_info: &str = &decoded_user_info;

                // Some implementation, like outline,
                // or those with Python (base64 in Python will still have '=' padding for URL safe encode)
                let account = match URL_PASSWORD_BASE64_ENGINE.decode(decoded_user_info) {
                    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())
            }
        };

        let host = match parsed.host_str() {
            Some(host) => host,
            None => return Err(UrlParseError::MissingHost),
        };

        let port = parsed.port().unwrap_or(8388);
        let addr = format!("{host}:{port}");

        let addr = match addr.parse::<ServerAddr>() {
            Ok(a) => a,
            Err(err) => {
                error!("failed to parse \"{}\" to ServerAddr, err: {:?}", addr, err);
                return Err(UrlParseError::InvalidServerAddr);
            }
        };

        let method = match method.parse::<CipherKind>() {
            Ok(m) => m,
            Err(err) => {
                error!("failed to parse \"{}\" to CipherKind, err: {:?}", method, err);
                return Err(UrlParseError::InvalidMethod);
            }
        };
        let mut svrconfig = Self::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);
                    return Err(UrlParseError::InvalidQueryString);
                }
            };

            for (key, value) in query {
                if key != "plugin" {
                    continue;
                }

                let mut vsp = value.splitn(2, ';');
                match vsp.next() {
                    None => {}
                    Some(p) => {
                        let plugin = PluginConfig {
                            plugin: p.to_owned(),
                            plugin_opts: vsp.next().map(ToOwned::to_owned),
                            plugin_args: Vec::new(), // SIP002 doesn't have arguments for plugins
                            plugin_mode: Mode::TcpOnly, // SIP002 doesn't support SIP003u
                        };
                        svrconfig.set_plugin(plugin);
                    }
                }
            }
        }

        if let Some(frag) = parsed.fragment() {
            match percent_encoding::percent_decode_str(frag).decode_utf8() {
                Ok(m) => svrconfig.set_remarks(m),
                Err(..) => svrconfig.set_remarks(frag),
            }
        }

        Ok(svrconfig)
    }
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
crates/shadowsocks/src/config.rs