@@ -27,6 +27,9 @@ const (
27
27
EntryTypeLink
28
28
)
29
29
30
+ // Time format used by the MDTM and MFMT commands
31
+ const timeFormat = "20060102150405"
32
+
30
33
// ServerConn represents the connection to a remote FTP server.
31
34
// A single connection only supports one in-flight data connection.
32
35
// It is not safe to be called concurrently.
@@ -40,6 +43,9 @@ type ServerConn struct {
40
43
features map [string ]string
41
44
skipEPSV bool
42
45
mlstSupported bool
46
+ mfmtSupported bool
47
+ mdtmSupported bool
48
+ mdtmCanWrite bool
43
49
usePRET bool
44
50
}
45
51
@@ -58,6 +64,7 @@ type dialOptions struct {
58
64
disableEPSV bool
59
65
disableUTF8 bool
60
66
disableMLSD bool
67
+ writingMDTM bool
61
68
location * time.Location
62
69
debugOutput io.Writer
63
70
dialFunc func (network , address string ) (net.Conn , error )
@@ -199,6 +206,18 @@ func DialWithDisabledMLSD(disabled bool) DialOption {
199
206
}}
200
207
}
201
208
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
+
202
221
// DialWithLocation returns a DialOption that configures the ServerConn with specified time.Location
203
222
// The location is used to parse the dates sent by the server which are in server's timezone
204
223
func DialWithLocation (location * time.Location ) DialOption {
@@ -313,9 +332,11 @@ func (c *ServerConn) Login(user, password string) error {
313
332
if _ , mlstSupported := c .features ["MLST" ]; mlstSupported && ! c .options .disableMLSD {
314
333
c .mlstSupported = true
315
334
}
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
319
340
320
341
// Switch to binary mode
321
342
if _ , _ , err = c .cmd (StatusCommandOK , "TYPE I" ); err != nil {
@@ -633,6 +654,12 @@ func (c *ServerConn) List(path string) (entries []*Entry, err error) {
633
654
return entries , err
634
655
}
635
656
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
+
636
663
// ChangeDir issues a CWD FTP command, which changes the current directory to
637
664
// the specified path.
638
665
func (c * ServerConn ) ChangeDir (path string ) error {
@@ -676,6 +703,49 @@ func (c *ServerConn) FileSize(path string) (int64, error) {
676
703
return strconv .ParseInt (msg , 10 , 64 )
677
704
}
678
705
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
+
679
749
// Retr issues a RETR FTP command to fetch the specified file from the remote
680
750
// FTP server.
681
751
//
0 commit comments