From 785c6d5ab611a424006653805a3baca682f7f5b1 Mon Sep 17 00:00:00 2001 From: Lesmiscore Date: Thu, 16 Mar 2023 03:09:29 +0900 Subject: [PATCH] ftp: fix 426 errors on downloads with vsftpd Sometimes vsftpd returns a 426 error when closing the stream even when all the data has been transferred successfully. This is some TLS protocol mismatch. Rclone has code to deal with this already, but the error returned from Close was wrapped in a multierror so the detection didn't work. This properly extract `textproto.Error` from the errors returned by `github.com/jlaffaye/ftp` in all the cases. See: https://forum.rclone.org/t/vsftpd-vs-rclone-part-2/36774 --- backend/ftp/ftp.go | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/backend/ftp/ftp.go b/backend/ftp/ftp.go index 8dd520efb..2491e10d2 100644 --- a/backend/ftp/ftp.go +++ b/backend/ftp/ftp.go @@ -315,10 +315,17 @@ func (dl *debugLog) Write(p []byte) (n int, err error) { return len(p), nil } +// Return a *textproto.Error if err contains one or nil otherwise +func textprotoError(err error) (errX *textproto.Error) { + if errors.As(err, &errX) { + return errX + } + return nil +} + // returns true if this FTP error should be retried func isRetriableFtpError(err error) bool { - switch errX := err.(type) { - case *textproto.Error: + if errX := textprotoError(err); errX != nil { switch errX.Code { case ftp.StatusNotAvailable, ftp.StatusTransfertAborted: return true @@ -471,8 +478,7 @@ func (f *Fs) putFtpConnection(pc **ftp.ServerConn, err error) { *pc = nil if err != nil { // If not a regular FTP error code then check the connection - var tpErr *textproto.Error - if !errors.As(err, &tpErr) { + if tpErr := textprotoError(err); tpErr != nil { nopErr := c.NoOp() if nopErr != nil { fs.Debugf(f, "Connection failed, closing: %v", nopErr) @@ -621,8 +627,7 @@ func (f *Fs) Shutdown(ctx context.Context) error { // translateErrorFile turns FTP errors into rclone errors if possible for a file func translateErrorFile(err error) error { - switch errX := err.(type) { - case *textproto.Error: + if errX := textprotoError(err); errX != nil { switch errX.Code { case ftp.StatusFileUnavailable, ftp.StatusFileActionIgnored: err = fs.ErrorObjectNotFound @@ -633,8 +638,7 @@ func translateErrorFile(err error) error { // translateErrorDir turns FTP errors into rclone errors if possible for a directory func translateErrorDir(err error) error { - switch errX := err.(type) { - case *textproto.Error: + if errX := textprotoError(err); errX != nil { switch errX.Code { case ftp.StatusFileUnavailable, ftp.StatusFileActionIgnored: err = fs.ErrorDirNotFound @@ -925,8 +929,7 @@ func (f *Fs) mkdir(ctx context.Context, abspath string) error { } err = c.MakeDir(f.dirFromStandardPath(abspath)) f.putFtpConnection(&c, err) - switch errX := err.(type) { - case *textproto.Error: + if errX := textprotoError(err); errX != nil { switch errX.Code { case ftp.StatusFileUnavailable: // dir already exists: see issue #2181 err = nil @@ -1167,8 +1170,7 @@ func (f *ftpReadCloser) Close() error { // mask the error if it was caused by a premature close // NB StatusAboutToSend is to work around a bug in pureftpd // See: https://github.com/rclone/rclone/issues/3445#issuecomment-521654257 - switch errX := err.(type) { - case *textproto.Error: + if errX := textprotoError(err); errX != nil { switch errX.Code { case ftp.StatusTransfertAborted, ftp.StatusFileUnavailable, ftp.StatusAboutToSend: err = nil @@ -1246,13 +1248,10 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op } err = c.Stor(o.fs.opt.Enc.FromStandardPath(path), in) // Ignore error 250 here - send by some servers - if err != nil { - switch errX := err.(type) { - case *textproto.Error: - switch errX.Code { - case ftp.StatusRequestedFileActionOK: - err = nil - } + if errX := textprotoError(err); errX != nil { + switch errX.Code { + case ftp.StatusRequestedFileActionOK: + err = nil } } if err != nil {