Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions command/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,12 @@ var app = &cli.App{
Name: "credentials-file",
Usage: "use the specified credentials file instead of the default credentials file",
},
&cli.StringFlag{
Name: "proxy",
Aliases: []string{"x"},
Usage: "proxy URL (e.g., http://proxy:8080, socks5://proxy:1080)",
EnvVars: []string{"S5CMD_PROXY"},
},
},
Before: func(c *cli.Context) error {
retryCount := c.Int("retry-count")
Expand Down Expand Up @@ -188,6 +194,7 @@ func NewStorageOpts(c *cli.Context) storage.Options {
UseListObjectsV1: c.Bool("use-list-objects-v1"),
Profile: c.String("profile"),
CredentialFile: c.String("credentials-file"),
Proxy: c.String("proxy"),
LogLevel: log.LevelFromString(c.String("log")),
NoSuchUploadRetryCount: c.Int("no-such-upload-retry-count"),
}
Expand Down
338 changes: 338 additions & 0 deletions e2e/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,140 @@ func TestAppProxy(t *testing.T) {
}
}

func TestAppProxyFlag(t *testing.T) {
testcases := []struct {
name string
proxyURL string
flag string
}{
{
name: "http proxy via flag",
proxyURL: "http://proxy:8080",
flag: "--proxy",
},
{
name: "https proxy via flag",
proxyURL: "https://proxy:8443",
flag: "--proxy",
},
{
name: "socks5 proxy via flag",
proxyURL: "socks5://proxy:1080",
flag: "--proxy",
},
{
name: "http proxy via short flag",
proxyURL: "http://proxy:8080",
flag: "-x",
},
{
name: "proxy with no-verify-ssl flag",
proxyURL: "http://proxy:8080",
flag: "--proxy",
},
}
for _, tc := range testcases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
const expectedReqs = 1

proxy := httpProxy{}
pxyURL := setupProxy(t, &proxy)

// set endpoint scheme to 'http'
if os.Getenv(s5cmdTestEndpointEnv) != "" {
origEndpoint := os.Getenv(s5cmdTestEndpointEnv)
endpoint, err := url.Parse(origEndpoint)
if err != nil {
t.Fatal(err)
}
endpoint.Scheme = "http"
os.Setenv(s5cmdTestEndpointEnv, endpoint.String())

defer func() {
os.Setenv(s5cmdTestEndpointEnv, origEndpoint)
}()
}

// Use the actual proxy URL from the test setup instead of the test case
// since we need a real proxy server for the test
_, s5cmd := setup(t, withProxy())

var cmd icmd.Cmd
if strings.Contains(tc.name, "no-verify-ssl") {
cmd = s5cmd(tc.flag, pxyURL, "--no-verify-ssl", "ls")
} else {
cmd = s5cmd(tc.flag, pxyURL, "ls")
}

result := icmd.RunCmd(cmd)

result.Assert(t, icmd.Success)
assert.Assert(t, proxy.isSuccessful(expectedReqs))
})
}
}

func TestAppProxyEnvironmentVariable(t *testing.T) {
testcases := []struct {
name string
proxyURL string
envVar string
}{
{
name: "http proxy via S5CMD_PROXY env var",
proxyURL: "http://proxy:8080",
envVar: "S5CMD_PROXY",
},
{
name: "https proxy via S5CMD_PROXY env var",
proxyURL: "https://proxy:8443",
envVar: "S5CMD_PROXY",
},
{
name: "socks5 proxy via S5CMD_PROXY env var",
proxyURL: "socks5://proxy:1080",
envVar: "S5CMD_PROXY",
},
}
for _, tc := range testcases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
const expectedReqs = 1

proxy := httpProxy{}
pxyURL := setupProxy(t, &proxy)

// set endpoint scheme to 'http'
if os.Getenv(s5cmdTestEndpointEnv) != "" {
origEndpoint := os.Getenv(s5cmdTestEndpointEnv)
endpoint, err := url.Parse(origEndpoint)
if err != nil {
t.Fatal(err)
}
endpoint.Scheme = "http"
os.Setenv(s5cmdTestEndpointEnv, endpoint.String())

defer func() {
os.Setenv(s5cmdTestEndpointEnv, origEndpoint)
}()
}

// Set the environment variable
os.Setenv(tc.envVar, pxyURL)
defer os.Unsetenv(tc.envVar)

_, s5cmd := setup(t, withProxy())

cmd := s5cmd("ls")
result := icmd.RunCmd(cmd)

result.Assert(t, icmd.Success)
assert.Assert(t, proxy.isSuccessful(expectedReqs))
})
}
}

func TestAppUnknownCommand(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -312,3 +446,207 @@ func TestAppEndpointShouldHaveScheme(t *testing.T) {
})
}
}

func TestAppProxyAuthentication(t *testing.T) {
testcases := []struct {
name string
proxyURL string
flag string
}{
{
name: "http proxy with auth via flag",
proxyURL: "http://user:pass@proxy:8080",
flag: "--proxy",
},
{
name: "https proxy with auth via flag",
proxyURL: "https://admin:secret@proxy:8443",
flag: "--proxy",
},
{
name: "socks5 proxy with auth via flag",
proxyURL: "socks5://proxyuser:proxypass@proxy:1080",
flag: "--proxy",
},
{
name: "http proxy with auth via short flag",
proxyURL: "http://user:pass@proxy:8080",
flag: "-x",
},
{
name: "proxy with auth and no-verify-ssl flag",
proxyURL: "http://user:pass@proxy:8080",
flag: "--proxy",
},
{
name: "proxy with special chars in password",
proxyURL: "http://user:pass@word@proxy:8080",
flag: "--proxy",
},
{
name: "proxy with empty password",
proxyURL: "http://user@proxy:8080",
flag: "--proxy",
},
{
name: "proxy with empty username",
proxyURL: "http://:pass@proxy:8080",
flag: "--proxy",
},
}
for _, tc := range testcases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
const expectedReqs = 1

proxy := httpProxy{}
pxyURL := setupProxy(t, &proxy)

// set endpoint scheme to 'http'
if os.Getenv(s5cmdTestEndpointEnv) != "" {
origEndpoint := os.Getenv(s5cmdTestEndpointEnv)
endpoint, err := url.Parse(origEndpoint)
if err != nil {
t.Fatal(err)
}
endpoint.Scheme = "http"
os.Setenv(s5cmdTestEndpointEnv, endpoint.String())

defer func() {
os.Setenv(s5cmdTestEndpointEnv, origEndpoint)
}()
}

// Use the actual proxy URL from the test setup instead of the test case
// since we need a real proxy server for the test
_, s5cmd := setup(t, withProxy())

var cmd icmd.Cmd
if strings.Contains(tc.name, "no-verify-ssl") {
cmd = s5cmd(tc.flag, pxyURL, "--no-verify-ssl", "ls")
} else {
cmd = s5cmd(tc.flag, pxyURL, "ls")
}

result := icmd.RunCmd(cmd)

result.Assert(t, icmd.Success)
assert.Assert(t, proxy.isSuccessful(expectedReqs))
})
}
}

func TestAppProxyAuthenticationEnvironmentVariable(t *testing.T) {
testcases := []struct {
name string
proxyURL string
envVar string
}{
{
name: "http proxy with auth via S5CMD_PROXY env var",
proxyURL: "http://user:pass@proxy:8080",
envVar: "S5CMD_PROXY",
},
{
name: "https proxy with auth via S5CMD_PROXY env var",
proxyURL: "https://admin:secret@proxy:8443",
envVar: "S5CMD_PROXY",
},
{
name: "socks5 proxy with auth via S5CMD_PROXY env var",
proxyURL: "socks5://proxyuser:proxypass@proxy:1080",
envVar: "S5CMD_PROXY",
},
{
name: "proxy with special chars in auth via env var",
proxyURL: "http://user:pass@word@proxy:8080",
envVar: "S5CMD_PROXY",
},
}
for _, tt := range testcases {
tt := tt
t.Run(tt.name, func(t *testing.T) {
const expectedReqs = 1

proxy := httpProxy{}
pxyURL := setupProxy(t, &proxy)

// set endpoint scheme to 'http'
if os.Getenv(s5cmdTestEndpointEnv) != "" {
origEndpoint := os.Getenv(s5cmdTestEndpointEnv)
endpoint, err := url.Parse(origEndpoint)
if err != nil {
t.Fatal(err)
}
endpoint.Scheme = "http"
os.Setenv(s5cmdTestEndpointEnv, endpoint.String())

defer func() {
os.Setenv(s5cmdTestEndpointEnv, origEndpoint)
}()
}

// Set the environment variable
os.Setenv(tt.envVar, pxyURL)
defer os.Unsetenv(tt.envVar)

_, s5cmd := setup(t, withProxy())

cmd := s5cmd("ls")
result := icmd.RunCmd(cmd)

result.Assert(t, icmd.Success)
assert.Assert(t, proxy.isSuccessful(expectedReqs))
})
}
}

func TestAppProxyAuthenticationErrors(t *testing.T) {
testcases := []struct {
name string
proxyURL string
flag string
expectError bool
errorMsg string
}{
{
name: "proxy URL with invalid scheme",
proxyURL: "://user:pass@proxy:8080",
flag: "--proxy",
expectError: true,
errorMsg: "missing protocol scheme",
},
{
name: "proxy URL with missing host",
proxyURL: "http://user:pass@",
flag: "--proxy",
expectError: true,
errorMsg: "invalid proxy URL",
},
{
name: "proxy URL with invalid port",
proxyURL: "http://user:pass@proxy:invalid",
flag: "--proxy",
expectError: true,
errorMsg: "invalid proxy URL",
},
}
for _, tc := range testcases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
_, s5cmd := setup(t)

cmd := s5cmd(tc.flag, tc.proxyURL, "ls")
result := icmd.RunCmd(cmd)

if tc.expectError {
result.Assert(t, icmd.Expected{ExitCode: 1})
// Check that the error message contains the expected text
assert.Assert(t, strings.Contains(result.Stderr(), tc.errorMsg),
"Expected error message '%s' not found in stderr: %s", tc.errorMsg, result.Stderr())
} else {
result.Assert(t, icmd.Success)
}
})
}
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ require (
go.etcd.io/bbolt v1.3.6 // indirect
golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/net v0.25.0
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/tools v0.21.0 // indirect
Expand Down
3 changes: 2 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
Expand Down Expand Up @@ -125,8 +126,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190829051458-42f498d34c4d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
Expand Down
Loading
Loading