diff --git a/x/config/config_test.go b/x/config/config_test.go index 2308c34c..21725596 100644 --- a/x/config/config_test.go +++ b/x/config/config_test.go @@ -15,7 +15,6 @@ package config import ( - "encoding/base64" "testing" "github.com/stretchr/testify/require" @@ -55,69 +54,9 @@ func TestSanitizeConfig(t *testing.T) { require.Error(t, err) } -func TestShowsocksLagacyBase64URL(t *testing.T) { - encoded := base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString([]byte("aes-256-gcm:1234567@example.com:1234?prefix=HTTP%2F1.1%20")) - urls, err := parseConfig("ss://" + string(encoded) + "#outline-123") - require.NoError(t, err) - require.Equal(t, 1, len(urls)) - config, err := parseShadowsocksLegacyBase64URL(urls[0]) - require.Equal(t, "example.com:1234", config.serverAddress) - require.Equal(t, "HTTP/1.1 ", string(config.prefix)) - require.NoError(t, err) -} - -func TestParseShadowsocksURL(t *testing.T) { - encoded := base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString([]byte("aes-256-gcm:1234567@example.com:1234?prefix=HTTP%2F1.1%20")) - urls, err := parseConfig("ss://" + string(encoded) + "#outline-123") - require.NoError(t, err) - require.Equal(t, 1, len(urls)) - config, err := parseShadowsocksURL(urls[0]) - require.Equal(t, "example.com:1234", config.serverAddress) - require.Equal(t, "HTTP/1.1 ", string(config.prefix)) - require.NoError(t, err) - - encoded = base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString([]byte("aes-256-gcm:1234567")) - urls, err = parseConfig("ss://" + string(encoded) + "@example.com:1234?prefix=HTTP%2F1.1%20" + "#outline-123") - require.NoError(t, err) - require.Equal(t, 1, len(urls)) - config, err = parseShadowsocksURL(urls[0]) - require.Equal(t, "example.com:1234", config.serverAddress) - require.Equal(t, "HTTP/1.1 ", string(config.prefix)) - require.NoError(t, err) -} - func TestSocks5URLSanitization(t *testing.T) { configString := "socks5://myuser:mypassword@192.168.1.100:1080" sanitizedConfig, err := SanitizeConfig(configString) require.NoError(t, err) require.Equal(t, "socks5://REDACTED@192.168.1.100:1080", sanitizedConfig) } - -func TestParseShadowsocksSIP002URLUnsuccessful(t *testing.T) { - encoded := base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString([]byte("aes-256-gcm:1234567@example.com:1234?prefix=HTTP%2F1.1%20")) - urls, err := parseConfig("ss://" + string(encoded) + "#outline-123") - require.NoError(t, err) - require.Equal(t, 1, len(urls)) - _, err = parseShadowsocksSIP002URL(urls[0]) - require.Error(t, err) -} - -func TestParseShadowsocksSIP002URLUnsupportedCypher(t *testing.T) { - configString := "ss://Y2hhY2hhMjAtaWV0Zi1wb2x5MTMwnTpLeTUyN2duU3FEVFB3R0JpQ1RxUnlT@example.com:1234?prefix=HTTP%2F1.1%20" - urls, err := parseConfig(configString) - require.NoError(t, err) - require.Equal(t, 1, len(urls)) - _, err = parseShadowsocksSIP002URL(urls[0]) - require.Error(t, err) -} - -func TestParseShadowsocksSIP002URLSuccessful(t *testing.T) { - configString := "ss://Y2hhY2hhMjAtaWV0Zi1wb2x5MTMwNTpLeTUyN2duU3FEVFB3R0JpQ1RxUnlT@example.com:1234?prefix=HTTP%2F1.1%20" - urls, err := parseConfig(configString) - require.NoError(t, err) - require.Equal(t, 1, len(urls)) - config, err := parseShadowsocksSIP002URL(urls[0]) - require.NoError(t, err) - require.Equal(t, "example.com:1234", config.serverAddress) - require.Equal(t, "HTTP/1.1 ", string(config.prefix)) -} diff --git a/x/config/shadowsocks.go b/x/config/shadowsocks.go index 33d93229..c65f7ba8 100644 --- a/x/config/shadowsocks.go +++ b/x/config/shadowsocks.go @@ -144,11 +144,17 @@ func parseShadowsocksSIP002URL(url *url.URL) (*shadowsocksConfig, error) { return nil, errors.New("host not specified") } config.serverAddress = url.Host - cipherInfoBytes, err := base64.URLEncoding.WithPadding(base64.NoPadding).DecodeString(url.User.String()) - if err != nil { - return nil, fmt.Errorf("failed to decode cipher info [%v]: %w", url.User.String(), err) + userInfo := url.User.String() + // Cipher info can be optionally encoded with Base64URL. + encoding := base64.URLEncoding.WithPadding(base64.NoPadding) + decodedUserInfo, err := encoding.DecodeString(userInfo) + var cipherInfo string + if err == nil { + cipherInfo = string(decodedUserInfo) + } else { + cipherInfo = userInfo } - cipherName, secret, found := strings.Cut(string(cipherInfoBytes), ":") + cipherName, secret, found := strings.Cut(cipherInfo, ":") if !found { return nil, errors.New("invalid cipher info: no ':' separator") } diff --git a/x/config/shadowsocks_test.go b/x/config/shadowsocks_test.go index 0fad540b..e9cc50c2 100644 --- a/x/config/shadowsocks_test.go +++ b/x/config/shadowsocks_test.go @@ -15,6 +15,7 @@ package config import ( + "encoding/base64" "net/url" "testing" @@ -36,3 +37,87 @@ func Test_sanitizeShadowsocksURL_withPrefix(t *testing.T) { require.NoError(t, err) require.Equal(t, "ss://REDACTED@192.168.100.1:8888?prefix=foo", sanitized) } + +func TestParseShadowsocksURLFullyEncoded(t *testing.T) { + encoded := base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString([]byte("aes-256-gcm:1234567@example.com:1234?prefix=HTTP%2F1.1%20")) + urls, err := parseConfig("ss://" + string(encoded) + "#outline-123") + require.NoError(t, err) + require.Equal(t, 1, len(urls)) + + config, err := parseShadowsocksURL(urls[0]) + + require.NoError(t, err) + require.Equal(t, "example.com:1234", config.serverAddress) + require.Equal(t, "HTTP/1.1 ", string(config.prefix)) +} + +func TestParseShadowsocksURLUserInfoEncoded(t *testing.T) { + encoded := base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString([]byte("aes-256-gcm:1234567")) + urls, err := parseConfig("ss://" + string(encoded) + "@example.com:1234?prefix=HTTP%2F1.1%20" + "#outline-123") + require.NoError(t, err) + require.Equal(t, 1, len(urls)) + + config, err := parseShadowsocksURL(urls[0]) + + require.NoError(t, err) + require.Equal(t, "example.com:1234", config.serverAddress) + require.Equal(t, "HTTP/1.1 ", string(config.prefix)) +} + +func TestParseShadowsocksURLNoEncoding(t *testing.T) { + configString := "ss://aes-256-gcm:1234567@example.com:1234" + urls, err := parseConfig(configString) + require.NoError(t, err) + require.Equal(t, 1, len(urls)) + + config, err := parseShadowsocksURL(urls[0]) + + require.NoError(t, err) + require.Equal(t, "example.com:1234", config.serverAddress) +} + +func TestParseShadowsocksURLInvalidCipherInfoFails(t *testing.T) { + configString := "ss://aes-256-gcm1234567@example.com:1234" + urls, err := parseConfig(configString) + require.NoError(t, err) + require.Equal(t, 1, len(urls)) + + _, err = parseShadowsocksURL(urls[0]) + + require.Error(t, err) +} + +func TestParseShadowsocksURLUnsupportedCypherFails(t *testing.T) { + configString := "ss://Y2hhY2hhMjAtaWV0Zi1wb2x5MTMwnTpLeTUyN2duU3FEVFB3R0JpQ1RxUnlT@example.com:1234" + urls, err := parseConfig(configString) + require.NoError(t, err) + require.Equal(t, 1, len(urls)) + + _, err = parseShadowsocksURL(urls[0]) + + require.Error(t, err) +} + +func TestParseShadowsocksLegacyBase64URL(t *testing.T) { + encoded := base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString([]byte("aes-256-gcm:1234567@example.com:1234?prefix=HTTP%2F1.1%20")) + urls, err := parseConfig("ss://" + string(encoded) + "#outline-123") + require.NoError(t, err) + require.Equal(t, 1, len(urls)) + + config, err := parseShadowsocksLegacyBase64URL(urls[0]) + + require.NoError(t, err) + require.Equal(t, "example.com:1234", config.serverAddress) + require.Equal(t, "HTTP/1.1 ", string(config.prefix)) +} + +func TestParseShadowsocksSIP002URLUnsuccessful(t *testing.T) { + encoded := base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString([]byte("aes-256-gcm:1234567@example.com:1234?prefix=HTTP%2F1.1%20")) + urls, err := parseConfig("ss://" + string(encoded) + "#outline-123") + require.NoError(t, err) + require.Equal(t, 1, len(urls)) + + _, err = parseShadowsocksSIP002URL(urls[0]) + + require.Error(t, err) +}