From 60012218fd9ca9bbb8c0d78b1deba0c05943f34f Mon Sep 17 00:00:00 2001 From: CrazyMax Date: Tue, 5 May 2020 13:04:49 +0200 Subject: [PATCH 1/6] Make "OPTS UTF8 ON" optional (#172) --- README.md | 5 +++++ ftp.go | 63 ++++++++++++++++++++++++++----------------------------- 2 files changed, 35 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 8488deb..898c3e2 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,11 @@ if err != nil { log.Fatal(err) } +err = c.SetUTF8() +if err != nil { + log.Fatal(err) +} + // Do something with the FTP conn if err := c.Quit(); err != nil { diff --git a/ftp.go b/ftp.go index 341e58d..859fc36 100644 --- a/ftp.go +++ b/ftp.go @@ -279,9 +279,6 @@ func (c *ServerConn) Login(user, password string) error { return err } - // Switch to UTF-8 - err = c.setUTF8() - // If using implicit TLS, make data connections also use TLS if c.options.tlsConfig != nil { c.cmd(StatusCommandOK, "PBSZ 0") @@ -334,36 +331,6 @@ func (c *ServerConn) feat() error { return nil } -// setUTF8 issues an "OPTS UTF8 ON" command. -func (c *ServerConn) setUTF8() error { - if _, ok := c.features["UTF8"]; !ok { - return nil - } - - code, message, err := c.cmd(-1, "OPTS UTF8 ON") - if err != nil { - return err - } - - // Workaround for FTP servers, that does not support this option. - if code == StatusBadArguments { - return nil - } - - // The ftpd "filezilla-server" has FEAT support for UTF8, but always returns - // "202 UTF8 mode is always enabled. No need to send this command." when - // trying to use it. That's OK - if code == StatusCommandNotImplemented { - return nil - } - - if code != StatusCommandOK { - return errors.New(message) - } - - return nil -} - // epsv issues an "EPSV" command to get a port number for a data connection. func (c *ServerConn) epsv() (port int, err error) { _, line, err := c.cmd(StatusExtendedPassiveMode, "EPSV") @@ -564,6 +531,36 @@ func (c *ServerConn) List(path string) (entries []*Entry, err error) { return } +// SetUTF8 issues an "OPTS UTF8 ON" command. +func (c *ServerConn) SetUTF8() error { + if _, ok := c.features["UTF8"]; !ok { + return nil + } + + code, message, err := c.cmd(-1, "OPTS UTF8 ON") + if err != nil { + return err + } + + // Workaround for FTP servers, that does not support this option. + if code == StatusBadArguments { + return nil + } + + // The ftpd "filezilla-server" has FEAT support for UTF8, but always returns + // "202 UTF8 mode is always enabled. No need to send this command." when + // trying to use it. That's OK + if code == StatusCommandNotImplemented { + return nil + } + + if code != StatusCommandOK { + return errors.New(message) + } + + return nil +} + // ChangeDir issues a CWD FTP command, which changes the current directory to // the specified path. func (c *ServerConn) ChangeDir(path string) error { From c21b2b322ecb9c56bbd9a54e7f315cb004b18bf8 Mon Sep 17 00:00:00 2001 From: CrazyMax Date: Tue, 5 May 2020 13:08:53 +0200 Subject: [PATCH 2/6] Update tests --- README.md | 2 +- client_test.go | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 898c3e2..4181a8c 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ if err != nil { err = c.SetUTF8() if err != nil { - log.Fatal(err) + log.Error(err) } // Do something with the FTP conn diff --git a/client_test.go b/client_test.go index ea0fa81..3e209c8 100644 --- a/client_test.go +++ b/client_test.go @@ -31,6 +31,11 @@ func testConn(t *testing.T, disableEPSV bool) { t.Fatal(err) } + err = c.SetUTF8() + if err != nil { + t.Error(err) + } + err = c.NoOp() if err != nil { t.Error(err) From 0a6572881e5d85fdbb489f9dfb6c457036d3fe45 Mon Sep 17 00:00:00 2001 From: CrazyMax Date: Tue, 5 May 2020 13:22:37 +0200 Subject: [PATCH 3/6] Udpate ftpMock --- conn_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/conn_test.go b/conn_test.go index 47baeea..b8b8172 100644 --- a/conn_test.go +++ b/conn_test.go @@ -75,7 +75,7 @@ func (mock *ftpMock) listen(t *testing.T) { // At least one command must have a multiline response switch cmdParts[0] { case "FEAT": - mock.proto.Writer.PrintfLine("211-Features:\r\n FEAT\r\n PASV\r\n EPSV\r\n SIZE\r\n211 End") + mock.proto.Writer.PrintfLine("211-Features:\r\n FEAT\r\n PASV\r\n EPSV\r\n UTF8\r\n SIZE\r\n211 End") case "USER": if cmdParts[1] == "anonymous" { mock.proto.Writer.PrintfLine("331 Please send your password") @@ -196,6 +196,8 @@ func (mock *ftpMock) listen(t *testing.T) { mock.proto.Writer.PrintfLine("350 Restarting at %s. Send STORE or RETRIEVE to initiate transfer", cmdParts[1]) case "NOOP": mock.proto.Writer.PrintfLine("200 NOOP ok.") + case "OPTS UTF8 ON": + mock.proto.Writer.PrintfLine("200 OK, UTF-8 enabled") case "REIN": mock.proto.Writer.PrintfLine("220 Logged out") case "QUIT": From 83f3ade61d72b87403b99853e47799ba06d42857 Mon Sep 17 00:00:00 2001 From: CrazyMax Date: Tue, 5 May 2020 13:34:35 +0200 Subject: [PATCH 4/6] Fix ftpMock --- conn_test.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/conn_test.go b/conn_test.go index b8b8172..11be24d 100644 --- a/conn_test.go +++ b/conn_test.go @@ -196,8 +196,15 @@ func (mock *ftpMock) listen(t *testing.T) { mock.proto.Writer.PrintfLine("350 Restarting at %s. Send STORE or RETRIEVE to initiate transfer", cmdParts[1]) case "NOOP": mock.proto.Writer.PrintfLine("200 NOOP ok.") - case "OPTS UTF8 ON": - mock.proto.Writer.PrintfLine("200 OK, UTF-8 enabled") + case "OPTS": + if len(cmdParts) != 3 { + mock.proto.Writer.PrintfLine("500 wrong number of arguments") + break + } + if (strings.Join(cmdParts[1:], " ")) == "UTF8 ON" { + mock.proto.Writer.PrintfLine("200 OK, UTF-8 enabled") + break + } case "REIN": mock.proto.Writer.PrintfLine("220 Logged out") case "QUIT": From 01c291065f3bb03a2133009a327d29135a85c27e Mon Sep 17 00:00:00 2001 From: CrazyMax Date: Tue, 5 May 2020 21:04:03 +0200 Subject: [PATCH 5/6] Move func --- ftp.go | 60 +++++++++++++++++++++++++++++----------------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/ftp.go b/ftp.go index 859fc36..54853ad 100644 --- a/ftp.go +++ b/ftp.go @@ -331,6 +331,36 @@ func (c *ServerConn) feat() error { return nil } +// SetUTF8 issues an "OPTS UTF8 ON" command. +func (c *ServerConn) SetUTF8() error { + if _, ok := c.features["UTF8"]; !ok { + return nil + } + + code, message, err := c.cmd(-1, "OPTS UTF8 ON") + if err != nil { + return err + } + + // Workaround for FTP servers, that does not support this option. + if code == StatusBadArguments { + return nil + } + + // The ftpd "filezilla-server" has FEAT support for UTF8, but always returns + // "202 UTF8 mode is always enabled. No need to send this command." when + // trying to use it. That's OK + if code == StatusCommandNotImplemented { + return nil + } + + if code != StatusCommandOK { + return errors.New(message) + } + + return nil +} + // epsv issues an "EPSV" command to get a port number for a data connection. func (c *ServerConn) epsv() (port int, err error) { _, line, err := c.cmd(StatusExtendedPassiveMode, "EPSV") @@ -531,36 +561,6 @@ func (c *ServerConn) List(path string) (entries []*Entry, err error) { return } -// SetUTF8 issues an "OPTS UTF8 ON" command. -func (c *ServerConn) SetUTF8() error { - if _, ok := c.features["UTF8"]; !ok { - return nil - } - - code, message, err := c.cmd(-1, "OPTS UTF8 ON") - if err != nil { - return err - } - - // Workaround for FTP servers, that does not support this option. - if code == StatusBadArguments { - return nil - } - - // The ftpd "filezilla-server" has FEAT support for UTF8, but always returns - // "202 UTF8 mode is always enabled. No need to send this command." when - // trying to use it. That's OK - if code == StatusCommandNotImplemented { - return nil - } - - if code != StatusCommandOK { - return errors.New(message) - } - - return nil -} - // ChangeDir issues a CWD FTP command, which changes the current directory to // the specified path. func (c *ServerConn) ChangeDir(path string) error { From 696d865fa3c47238512bd9fa9c4f6d5c462ab9ac Mon Sep 17 00:00:00 2001 From: CrazyMax Date: Tue, 28 Jul 2020 21:13:33 +0200 Subject: [PATCH 6/6] Opt-out --- README.md | 5 ----- client_test.go | 5 ----- conn_test.go | 2 +- ftp.go | 17 +++++++++++++++-- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 4181a8c..8488deb 100644 --- a/README.md +++ b/README.md @@ -26,11 +26,6 @@ if err != nil { log.Fatal(err) } -err = c.SetUTF8() -if err != nil { - log.Error(err) -} - // Do something with the FTP conn if err := c.Quit(); err != nil { diff --git a/client_test.go b/client_test.go index 3e209c8..ea0fa81 100644 --- a/client_test.go +++ b/client_test.go @@ -31,11 +31,6 @@ func testConn(t *testing.T, disableEPSV bool) { t.Fatal(err) } - err = c.SetUTF8() - if err != nil { - t.Error(err) - } - err = c.NoOp() if err != nil { t.Error(err) diff --git a/conn_test.go b/conn_test.go index 11be24d..4b9d7a5 100644 --- a/conn_test.go +++ b/conn_test.go @@ -328,7 +328,7 @@ func openConn(t *testing.T, addr string, options ...DialOption) (*ftpMock, *Serv // Helper to close a client connected to a mock server func closeConn(t *testing.T, mock *ftpMock, c *ServerConn, commands []string) { - expected := []string{"FEAT", "USER", "PASS", "TYPE"} + expected := []string{"FEAT", "USER", "PASS", "TYPE", "OPTS"} expected = append(expected, commands...) expected = append(expected, "QUIT") diff --git a/ftp.go b/ftp.go index 54853ad..adcdf41 100644 --- a/ftp.go +++ b/ftp.go @@ -53,6 +53,7 @@ type dialOptions struct { explicitTLS bool conn net.Conn disableEPSV bool + disableUTF8 bool location *time.Location debugOutput io.Writer dialFunc func(network, address string) (net.Conn, error) @@ -176,6 +177,13 @@ func DialWithDisabledEPSV(disabled bool) DialOption { }} } +// DialWithDisabledUTF8 returns a DialOption that configures the ServerConn with UTF8 option disabled +func DialWithDisabledUTF8(disabled bool) DialOption { + return DialOption{func(do *dialOptions) { + do.disableUTF8 = disabled + }} +} + // DialWithLocation returns a DialOption that configures the ServerConn with specified time.Location // The location is used to parse the dates sent by the server which are in server's timezone func DialWithLocation(location *time.Location) DialOption { @@ -279,6 +287,11 @@ func (c *ServerConn) Login(user, password string) error { return err } + // Switch to UTF-8 + if !c.options.disableUTF8 { + err = c.setUTF8() + } + // If using implicit TLS, make data connections also use TLS if c.options.tlsConfig != nil { c.cmd(StatusCommandOK, "PBSZ 0") @@ -331,8 +344,8 @@ func (c *ServerConn) feat() error { return nil } -// SetUTF8 issues an "OPTS UTF8 ON" command. -func (c *ServerConn) SetUTF8() error { +// setUTF8 issues an "OPTS UTF8 ON" command. +func (c *ServerConn) setUTF8() error { if _, ok := c.features["UTF8"]; !ok { return nil }