@@ -27,6 +27,9 @@ const (
2727 EntryTypeLink
2828)
2929
30+ // Time format used by the MDTM and MFMT commands
31+ const timeFormat = "20060102150405"
32+
3033// ServerConn represents the connection to a remote FTP server.
3134// A single connection only supports one in-flight data connection.
3235// It is not safe to be called concurrently.
@@ -40,6 +43,9 @@ type ServerConn struct {
4043 features map [string ]string
4144 skipEPSV bool
4245 mlstSupported bool
46+ mfmtSupported bool
47+ mdtmSupported bool
48+ mdtmCanWrite bool
4349 usePRET bool
4450}
4551
@@ -58,6 +64,7 @@ type dialOptions struct {
5864 disableEPSV bool
5965 disableUTF8 bool
6066 disableMLSD bool
67+ writingMDTM bool
6168 location * time.Location
6269 debugOutput io.Writer
6370 dialFunc func (network , address string ) (net.Conn , error )
@@ -199,6 +206,18 @@ func DialWithDisabledMLSD(disabled bool) DialOption {
199206 }}
200207}
201208
209+ // DialWithWritingMDTM returns a DialOption making ServerConn use MDTM to set file time
210+ //
211+ // This option addresses a quirk in the VsFtpd server which doesn't support
212+ // the MFMT command for setting file time like other servers but by default
213+ // uses the MDTM command with non-standard arguments for that.
214+ // See "mdtm_write" in https://security.appspot.com/vsftpd/vsftpd_conf.html
215+ func DialWithWritingMDTM (enabled bool ) DialOption {
216+ return DialOption {func (do * dialOptions ) {
217+ do .writingMDTM = enabled
218+ }}
219+ }
220+
202221// DialWithLocation returns a DialOption that configures the ServerConn with specified time.Location
203222// The location is used to parse the dates sent by the server which are in server's timezone
204223func DialWithLocation (location * time.Location ) DialOption {
@@ -313,9 +332,11 @@ func (c *ServerConn) Login(user, password string) error {
313332 if _ , mlstSupported := c .features ["MLST" ]; mlstSupported && ! c .options .disableMLSD {
314333 c .mlstSupported = true
315334 }
316- if _ , usePRET := c .features ["PRET" ]; usePRET {
317- c .usePRET = true
318- }
335+ _ , c .usePRET = c .features ["PRET" ]
336+
337+ _ , c .mfmtSupported = c .features ["MFMT" ]
338+ _ , c .mdtmSupported = c .features ["MDTM" ]
339+ c .mdtmCanWrite = c .mdtmSupported && c .options .writingMDTM
319340
320341 // Switch to binary mode
321342 if _ , _ , err = c .cmd (StatusCommandOK , "TYPE I" ); err != nil {
@@ -633,6 +654,12 @@ func (c *ServerConn) List(path string) (entries []*Entry, err error) {
633654 return entries , err
634655}
635656
657+ // IsTimePreciseInList returns true if client and server support the MLSD
658+ // command so List can return time with 1-second precision for all files.
659+ func (c * ServerConn ) IsTimePreciseInList () bool {
660+ return c .mlstSupported
661+ }
662+
636663// ChangeDir issues a CWD FTP command, which changes the current directory to
637664// the specified path.
638665func (c * ServerConn ) ChangeDir (path string ) error {
@@ -676,6 +703,49 @@ func (c *ServerConn) FileSize(path string) (int64, error) {
676703 return strconv .ParseInt (msg , 10 , 64 )
677704}
678705
706+ // GetTime issues the MDTM FTP command to obtain the file modification time.
707+ // It returns a UTC time.
708+ func (c * ServerConn ) GetTime (path string ) (time.Time , error ) {
709+ var t time.Time
710+ if ! c .mdtmSupported {
711+ return t , errors .New ("GetTime is not supported" )
712+ }
713+ _ , msg , err := c .cmd (StatusFile , "MDTM %s" , path )
714+ if err != nil {
715+ return t , err
716+ }
717+ return time .ParseInLocation (timeFormat , msg , time .UTC )
718+ }
719+
720+ // IsGetTimeSupported allows library callers to check in advance that they
721+ // can use GetTime to get file time.
722+ func (c * ServerConn ) IsGetTimeSupported () bool {
723+ return c .mdtmSupported
724+ }
725+
726+ // SetTime issues the MFMT FTP command to set the file modification time.
727+ // Also it can use a non-standard form of the MDTM command supported by
728+ // the VsFtpd server instead of MFMT for the same purpose.
729+ // See "mdtm_write" in https://security.appspot.com/vsftpd/vsftpd_conf.html
730+ func (c * ServerConn ) SetTime (path string , t time.Time ) (err error ) {
731+ utime := t .In (time .UTC ).Format (timeFormat )
732+ switch {
733+ case c .mfmtSupported :
734+ _ , _ , err = c .cmd (StatusFile , "MFMT %s %s" , utime , path )
735+ case c .mdtmCanWrite :
736+ _ , _ , err = c .cmd (StatusFile , "MDTM %s %s" , utime , path )
737+ default :
738+ err = errors .New ("SetTime is not supported" )
739+ }
740+ return
741+ }
742+
743+ // IsSetTimeSupported allows library callers to check in advance that they
744+ // can use SetTime to set file time.
745+ func (c * ServerConn ) IsSetTimeSupported () bool {
746+ return c .mfmtSupported || c .mdtmCanWrite
747+ }
748+
679749// Retr issues a RETR FTP command to fetch the specified file from the remote
680750// FTP server.
681751//
0 commit comments