@@ -2,6 +2,7 @@ package ftpserver
22
33import (
44 "bufio"
5+ "compress/flate"
56 "errors"
67 "fmt"
78 "io"
@@ -33,6 +34,17 @@ const (
3334 TransferTypeBinary
3435)
3536
37+ // TransferMode is the enumerable that represents the transfer mode (stream, block, compressed, deflate)
38+ type TransferMode int8
39+
40+ // Transfer modes
41+ const (
42+ // TransferModeStream is the standard uncompressed transfer mode
43+ TransferModeStream TransferMode = iota
44+ // TransferModeDeflate is the compressed transfer mode using deflate algorithm
45+ TransferModeDeflate
46+ )
47+
3648// DataChannel is the enumerable that represents the data channel (active or passive)
3749type DataChannel int8
3850
@@ -103,6 +115,7 @@ type clientHandler struct {
103115 debug bool // Show debugging info on the server side
104116 transferTLS bool // Use TLS for transfer connection
105117 controlTLS bool // Use TLS for control connection
118+ transferMode TransferMode // Transfer mode (stream, deflate)
106119 isTransferOpen bool // indicate if the transfer connection is opened
107120 isTransferAborted bool // indicate if the transfer was aborted
108121 connClosed bool // indicates if the connection has been commanded to close
@@ -660,7 +673,7 @@ func (c *clientHandler) GetTranferInfo() string {
660673 return c .transfer .GetInfo ()
661674}
662675
663- func (c * clientHandler ) TransferOpen (info string ) (net. Conn , error ) {
676+ func (c * clientHandler ) TransferOpen (info string ) (io. ReadWriter , error ) {
664677 c .transferMu .Lock ()
665678 defer c .transferMu .Unlock ()
666679
@@ -696,6 +709,17 @@ func (c *clientHandler) TransferOpen(info string) (net.Conn, error) {
696709 return nil , err
697710 }
698711
712+ var transferStream io.ReadWriter = conn
713+
714+ if c .transferMode == TransferModeDeflate {
715+ transferStream , err = newDeflateTransfer (transferStream , c .server .settings .DeflateCompressionLevel )
716+ if err != nil {
717+ c .writeMessage (StatusActionNotTaken , fmt .Sprintf ("Could not switch to deflate mode: %v" , err ))
718+
719+ return nil , fmt .Errorf ("could not switch to deflate mode: %w" , err )
720+ }
721+ }
722+
699723 c .isTransferOpen = true
700724 c .transfer .SetInfo (info )
701725
@@ -708,18 +732,56 @@ func (c *clientHandler) TransferOpen(info string) (net.Conn, error) {
708732 "localAddr" , conn .LocalAddr ().String ())
709733 }
710734
711- return conn , nil
735+ return transferStream , nil
736+ }
737+
738+ // Flusher is the interface that wraps the basic Flush method.
739+ type Flusher interface {
740+ Flush () error
741+ }
742+
743+ // TransferFinalizer is the interface for transfer streams that need explicit
744+ // finalization (e.g., deflate streams need to write end-of-stream markers).
745+ // This is distinct from closing the underlying connection.
746+ // We use FinalizeTransfer() instead of Close() to distinguish from io.Closer,
747+ // since net.Conn already implements io.Closer and we don't want to accidentally
748+ // close the underlying connection.
749+ type TransferFinalizer interface {
750+ FinalizeTransfer () error
712751}
713752
714- func (c * clientHandler ) TransferClose (err error ) {
753+ func (c * clientHandler ) TransferClose (transfer io. ReadWriter , err error ) {
715754 c .transferMu .Lock ()
716755 defer c .transferMu .Unlock ()
717756
757+ // Check if this is a transfer stream that needs explicit finalization (e.g., deflate).
758+ // TransferFinalizer.FinalizeTransfer() for deflate writes the end-of-stream marker AND flushes,
759+ // so we don't need to call Flush() separately.
760+ if finalizer , ok := transfer .(TransferFinalizer ); ok {
761+ if errFinalize := finalizer .FinalizeTransfer (); errFinalize != nil {
762+ c .logger .Warn (
763+ "Error finalizing transfer stream" ,
764+ "err" , errFinalize ,
765+ )
766+ }
767+ } else if flush , ok := transfer .(Flusher ); ok {
768+ // Only flush if NOT a TransferCloser (deflate's Close already flushes)
769+ if errFlush := flush .Flush (); errFlush != nil {
770+ c .logger .Warn (
771+ "Error flushing transfer connection" ,
772+ "err" , errFlush ,
773+ )
774+ }
775+ }
776+
777+ // Finally close the underlying connection.
778+ // "Use of closed network connection" is normal in FTP - the client can close
779+ // the connection when done, and we should treat this as success.
718780 errClose := c .closeTransfer ()
719- if errClose != nil {
781+ if errClose != nil && ! isClosedConnError ( errClose ) {
720782 c .logger .Warn (
721783 "Problem closing transfer connection" ,
722- "err" , err ,
784+ "err" , errClose ,
723785 )
724786 }
725787
@@ -730,6 +792,11 @@ func (c *clientHandler) TransferClose(err error) {
730792 return
731793 }
732794
795+ // Treat "connection already closed" as success - it means transfer completed
796+ if isClosedConnError (errClose ) {
797+ errClose = nil
798+ }
799+
733800 switch {
734801 case err == nil && errClose == nil :
735802 c .writeMessage (StatusClosingDataConn , "Closing transfer connection" )
@@ -740,6 +807,19 @@ func (c *clientHandler) TransferClose(err error) {
740807 }
741808}
742809
810+ // isClosedConnError checks if the error indicates the connection is already closed.
811+ // This is normal FTP behavior - the client can close the connection when done.
812+ func isClosedConnError (err error ) bool {
813+ if err == nil {
814+ return false
815+ }
816+
817+ errStr := err .Error ()
818+
819+ return strings .Contains (errStr , "use of closed network connection" ) ||
820+ strings .Contains (errStr , "connection reset by peer" )
821+ }
822+
743823func (c * clientHandler ) checkDataConnectionRequirement (dataConnIP net.IP , channelType DataChannel ) error {
744824 var requirement DataConnectionRequirement
745825
@@ -821,3 +901,57 @@ func getMessageLines(message string) []string {
821901
822902 return lines
823903}
904+
905+ // Compile-time checks that deflateReadWriter implements required interfaces
906+ var (
907+ _ Flusher = (* deflateReadWriter )(nil )
908+ _ TransferFinalizer = (* deflateReadWriter )(nil )
909+ )
910+
911+ type deflateReadWriter struct {
912+ reader io.ReadCloser // flate.NewReader returns io.ReadCloser
913+ writer * flate.Writer
914+ }
915+
916+ func (d * deflateReadWriter ) Read (p []byte ) (int , error ) {
917+ return d .reader .Read (p )
918+ }
919+
920+ func (d * deflateReadWriter ) Write (p []byte ) (int , error ) {
921+ return d .writer .Write (p )
922+ }
923+
924+ // Flush flushes buffered data to the underlying writer.
925+ func (d * deflateReadWriter ) Flush () error {
926+ return d .writer .Flush ()
927+ }
928+
929+ // FinalizeTransfer finalizes the deflate stream by writing the BFINAL block (end-of-stream marker).
930+ // This does NOT close the underlying connection - it only finalizes the deflate stream.
931+ func (d * deflateReadWriter ) FinalizeTransfer () error {
932+ // Close the writer to write the BFINAL block
933+ if err := d .writer .Close (); err != nil {
934+ return fmt .Errorf ("error closing deflate writer: %w" , err )
935+ }
936+
937+ // Close the reader to release resources
938+ if err := d .reader .Close (); err != nil {
939+ return fmt .Errorf ("error closing deflate reader: %w" , err )
940+ }
941+
942+ return nil
943+ }
944+
945+ func newDeflateTransfer (conn io.ReadWriter , level int ) (* deflateReadWriter , error ) {
946+ writer , err := flate .NewWriter (conn , level )
947+ if err != nil {
948+ return nil , fmt .Errorf ("could not create deflate writer: %w" , err )
949+ }
950+
951+ reader := flate .NewReader (conn )
952+
953+ return & deflateReadWriter {
954+ reader : reader ,
955+ writer : writer ,
956+ }, nil
957+ }
0 commit comments