Add MLST command in the form of a Get method (#269)
* Add MLST command in the form of a Get method The `LIST` and `MLSD` commands are inefficient when the objective is to retrieve one single `Entry` for a known path, because the only way to get such an entry is to list the parent directory using a data connection. The `MLST` fixes this by allowing one single `Entry` to be returned using the control connection. The name `Get` was chosen because it is often used in conjunction with `List` as a mean to get one single entry. Signed-off-by: Thomas Hallgren <thomas@datawire.io> * Changes in response to code review: - Rename `Get` to `GetEntry` (because it returns an `*Entry`) - Add test-case for multiline response on the control connection - Fix issues with parsing the multiline response. Signed-off-by: Thomas Hallgren <thomas@datawire.io> * Add sample output from MLST to GetEntry comment. Signed-off-by: Thomas Hallgren <thomas@datawire.io> * Changes in response to code review: - Remove unused `time.Time` argument - Add struct labels to make `govet` happy Signed-off-by: Thomas Hallgren <thomas@datawire.io> * Remove time arg when calling parseNextRFC3659ListLine Signed-off-by: Thomas Hallgren <thomas@datawire.io> Signed-off-by: Thomas Hallgren <thomas@datawire.io>
This commit is contained in:
52
ftp.go
52
ftp.go
@@ -678,6 +678,58 @@ func (c *ServerConn) List(path string) (entries []*Entry, err error) {
|
||||
return entries, errs.ErrorOrNil()
|
||||
}
|
||||
|
||||
// GetEntry issues a MLST FTP command which retrieves one single Entry using the
|
||||
// control connection. The returnedEntry will describe the current directory
|
||||
// when no path is given.
|
||||
func (c *ServerConn) GetEntry(path string) (entry *Entry, err error) {
|
||||
if !c.mlstSupported {
|
||||
return nil, &textproto.Error{Code: StatusNotImplemented, Msg: StatusText(StatusNotImplemented)}
|
||||
}
|
||||
space := " "
|
||||
if path == "" {
|
||||
space = ""
|
||||
}
|
||||
_, msg, err := c.cmd(StatusRequestedFileActionOK, "%s%s%s", "MLST", space, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// The expected reply will look something like:
|
||||
//
|
||||
// 250-File details
|
||||
// Type=file;Size=1024;Modify=20220813133357; path
|
||||
// 250 End
|
||||
//
|
||||
// Multiple lines are allowed though, so it can also be in the form:
|
||||
//
|
||||
// 250-File details
|
||||
// Type=file;Size=1024; path
|
||||
// Modify=20220813133357; path
|
||||
// 250 End
|
||||
lines := strings.Split(msg, "\n")
|
||||
lc := len(lines)
|
||||
|
||||
// lines must be a multi-line message with a length of 3 or more, and we
|
||||
// don't care about the first and last line
|
||||
if lc < 3 {
|
||||
return nil, errors.New("invalid response")
|
||||
}
|
||||
|
||||
e := &Entry{}
|
||||
for _, l := range lines[1 : lc-1] {
|
||||
// According to RFC 3659, the entry lines must start with a space when passed over the
|
||||
// control connection. Some servers don't seem to add that space though. Both forms are
|
||||
// accepted here.
|
||||
if len(l) > 0 && l[0] == ' ' {
|
||||
l = l[1:]
|
||||
}
|
||||
if e, err = parseNextRFC3659ListLine(l, c.options.location, e); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return e, nil
|
||||
}
|
||||
|
||||
// IsTimePreciseInList returns true if client and server support the MLSD
|
||||
// command so List can return time with 1-second precision for all files.
|
||||
func (c *ServerConn) IsTimePreciseInList() bool {
|
||||
|
||||
Reference in New Issue
Block a user