Skip to content

Commit 26bbb37

Browse files
committed
Add SetTime command, related options and state getters
1 parent 16857ee commit 26bbb37

File tree

1 file changed

+73
-3
lines changed

1 file changed

+73
-3
lines changed

ftp.go

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -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
204223
func 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.
638665
func (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

Comments
 (0)