Implement support for OpenVMS (only LIST thus far)
This commit is contained in:
parent
2c2aa379fd
commit
410d55ca70
70
parse.go
70
parse.go
@ -19,6 +19,7 @@ var listLineParsers = []parseFunc{
|
|||||||
parseLsListLine,
|
parseLsListLine,
|
||||||
parseDirListLine,
|
parseDirListLine,
|
||||||
parseHostedFTPLine,
|
parseHostedFTPLine,
|
||||||
|
parseVMSFTPLine,
|
||||||
}
|
}
|
||||||
|
|
||||||
var dirTimeFormats = []string{
|
var dirTimeFormats = []string{
|
||||||
@ -26,6 +27,73 @@ var dirTimeFormats = []string{
|
|||||||
"2006-01-02 15:04",
|
"2006-01-02 15:04",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Empty string that saves the last string for VMS
|
||||||
|
var previousString = ""
|
||||||
|
|
||||||
|
func parseVMSFTPLine(s string, _ time.Time, location *time.Location) (*Entry, error) {
|
||||||
|
//If the string is empty, there are continuations on the next line(s)
|
||||||
|
if s == "" {
|
||||||
|
return nil, errUnsupportedListLine
|
||||||
|
}
|
||||||
|
|
||||||
|
scanner := newScanner(s)
|
||||||
|
filename := scanner.NextFields(1)[0]
|
||||||
|
|
||||||
|
// If the line does not contain a semicolon and there is nothing in previousString it is not a VMS FTP filename line
|
||||||
|
if !strings.Contains(filename, ";") && previousString == "" {
|
||||||
|
return nil, errUnsupportedListLine
|
||||||
|
}
|
||||||
|
|
||||||
|
remainingFields := scanner.NextFields(5)
|
||||||
|
|
||||||
|
// If there are no more fields then the current line has a continuation on the next line
|
||||||
|
if len(remainingFields) == 0 {
|
||||||
|
previousString = filename
|
||||||
|
return nil, errUnsupportedListLine
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is a previousString, then the current line is a continuation of the previous line
|
||||||
|
// Insert the current filename in remainingFields and set filename to previousString
|
||||||
|
if previousString != "" {
|
||||||
|
remainingFields = append([]string{filename}, remainingFields...)
|
||||||
|
filename = previousString
|
||||||
|
// Reset previousString
|
||||||
|
previousString = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(remainingFields) < 5 {
|
||||||
|
return nil, errUnsupportedListLine
|
||||||
|
}
|
||||||
|
|
||||||
|
entry := &Entry{}
|
||||||
|
// Files are formatted like this:
|
||||||
|
// FILENAME.EXT;1 123/125 12-DEC-2017 14:10:37 [GROUP,OWNER] (RWED,RWED,RE,)
|
||||||
|
// Directories are formatted like this:
|
||||||
|
// DIRECTORY.DIR;1 123/125 12-DEC-2017 14:10:37 [GROUP,OWNER] (RWED,RWED,RE,)
|
||||||
|
|
||||||
|
// Remove the version
|
||||||
|
parsedNameUnix := strings.Split(filename, ";")[0]
|
||||||
|
|
||||||
|
if strings.Contains(filename, ".DIR;") {
|
||||||
|
// Strip .DIR from parsedNameUnix
|
||||||
|
entry.Name = strings.Replace(parsedNameUnix, ".DIR", "", 1)
|
||||||
|
entry.Type = EntryTypeFolder
|
||||||
|
entry.Size = 0
|
||||||
|
} else {
|
||||||
|
entry.Name = parsedNameUnix
|
||||||
|
entry.Type = EntryTypeFile
|
||||||
|
// First number is the blocks used
|
||||||
|
parsedSize := strings.Split(remainingFields[0], "/")[0]
|
||||||
|
|
||||||
|
_ = entry.setSize(parsedSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the date
|
||||||
|
entry.Time, _ = time.ParseInLocation("_2-Jan-2006 15:04:05", remainingFields[1]+" "+remainingFields[2], location)
|
||||||
|
|
||||||
|
return entry, nil
|
||||||
|
}
|
||||||
|
|
||||||
// parseRFC3659ListLine parses the style of directory line defined in RFC 3659.
|
// parseRFC3659ListLine parses the style of directory line defined in RFC 3659.
|
||||||
func parseRFC3659ListLine(line string, _ time.Time, loc *time.Location) (*Entry, error) {
|
func parseRFC3659ListLine(line string, _ time.Time, loc *time.Location) (*Entry, error) {
|
||||||
return parseNextRFC3659ListLine(line, loc, &Entry{})
|
return parseNextRFC3659ListLine(line, loc, &Entry{})
|
||||||
@ -164,7 +232,7 @@ func parseLsListLine(line string, now time.Time, loc *time.Location) (*Entry, er
|
|||||||
|
|
||||||
// parseDirListLine parses a directory line in a format based on the output of
|
// parseDirListLine parses a directory line in a format based on the output of
|
||||||
// the MS-DOS DIR command.
|
// the MS-DOS DIR command.
|
||||||
func parseDirListLine(line string, now time.Time, loc *time.Location) (*Entry, error) {
|
func parseDirListLine(line string, _ time.Time, loc *time.Location) (*Entry, error) {
|
||||||
e := &Entry{}
|
e := &Entry{}
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
@ -79,6 +79,10 @@ var listTests = []line{
|
|||||||
|
|
||||||
// Line with ACL persmissions
|
// Line with ACL persmissions
|
||||||
{"-rwxrw-r--+ 1 521 101 2080 May 21 10:53 data.csv", "data.csv", 2080, EntryTypeFile, newTime(thisYear, time.May, 21, 10, 53)},
|
{"-rwxrw-r--+ 1 521 101 2080 May 21 10:53 data.csv", "data.csv", 2080, EntryTypeFile, newTime(thisYear, time.May, 21, 10, 53)},
|
||||||
|
|
||||||
|
// OPENVMS style
|
||||||
|
{"DATA.DIR;1 1/576 4-JAN-2022 14:14:36 [SCANCO,MICROCT] (RWE,RWE,RE,)", "DATA", 0, EntryTypeFolder, newTime(2022, time.January, 4, 14, 14, 36)},
|
||||||
|
{"DECW$SM.LOG;247 0/576 17-MAY-2023 15:20:28 [SCANCO,MICROCT] (RWED,RWED,RE,)", "DECW$SM.LOG", 0, EntryTypeFile, newTime(2023, time.May, 17, 15, 20, 28)},
|
||||||
}
|
}
|
||||||
|
|
||||||
var listTestsSymlink = []symlinkLine{
|
var listTestsSymlink = []symlinkLine{
|
||||||
@ -96,19 +100,20 @@ var listTestsFail = []unsupportedLine{
|
|||||||
{"total 1", errUnsupportedListLine},
|
{"total 1", errUnsupportedListLine},
|
||||||
{"000000000x ", errUnsupportedListLine}, // see https://github.com/jlaffaye/ftp/issues/97
|
{"000000000x ", errUnsupportedListLine}, // see https://github.com/jlaffaye/ftp/issues/97
|
||||||
{"", errUnsupportedListLine},
|
{"", errUnsupportedListLine},
|
||||||
|
{"DECW$SM.LOG;247", errUnsupportedListLine}, //This is the case where VMS has a continuation on the next line
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseValidListLine(t *testing.T) {
|
func TestParseValidListLine(t *testing.T) {
|
||||||
for _, lt := range listTests {
|
for _, lt := range listTests {
|
||||||
t.Run(lt.line, func(t *testing.T) {
|
t.Run(lt.line, func(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assertions := assert.New(t)
|
||||||
entry, err := parseListLine(lt.line, now, time.UTC)
|
entry, err := parseListLine(lt.line, now, time.UTC)
|
||||||
|
|
||||||
if assert.NoError(err) {
|
if assertions.NoError(err) {
|
||||||
assert.Equal(lt.name, entry.Name)
|
assertions.Equal(lt.name, entry.Name)
|
||||||
assert.Equal(lt.entryType, entry.Type)
|
assertions.Equal(lt.entryType, entry.Type)
|
||||||
assert.Equal(lt.size, entry.Size)
|
assertions.Equal(lt.size, entry.Size)
|
||||||
assert.Equal(lt.time, entry.Time)
|
assertions.Equal(lt.time, entry.Time)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -117,13 +122,13 @@ func TestParseValidListLine(t *testing.T) {
|
|||||||
func TestParseSymlinks(t *testing.T) {
|
func TestParseSymlinks(t *testing.T) {
|
||||||
for _, lt := range listTestsSymlink {
|
for _, lt := range listTestsSymlink {
|
||||||
t.Run(lt.line, func(t *testing.T) {
|
t.Run(lt.line, func(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assertions := assert.New(t)
|
||||||
entry, err := parseListLine(lt.line, now, time.UTC)
|
entry, err := parseListLine(lt.line, now, time.UTC)
|
||||||
|
|
||||||
if assert.NoError(err) {
|
if assertions.NoError(err) {
|
||||||
assert.Equal(lt.name, entry.Name)
|
assertions.Equal(lt.name, entry.Name)
|
||||||
assert.Equal(lt.target, entry.Target)
|
assertions.Equal(lt.target, entry.Target)
|
||||||
assert.Equal(EntryTypeLink, entry.Type)
|
assertions.Equal(EntryTypeLink, entry.Type)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -171,7 +176,7 @@ func TestSettime(t *testing.T) {
|
|||||||
|
|
||||||
// newTime builds a UTC time from the given year, month, day, hour and minute
|
// newTime builds a UTC time from the given year, month, day, hour and minute
|
||||||
func newTime(year int, month time.Month, day int, hourMinSec ...int) time.Time {
|
func newTime(year int, month time.Month, day int, hourMinSec ...int) time.Time {
|
||||||
var hour, min, sec int
|
var hour, minute, sec int
|
||||||
|
|
||||||
switch len(hourMinSec) {
|
switch len(hourMinSec) {
|
||||||
case 0:
|
case 0:
|
||||||
@ -180,7 +185,7 @@ func newTime(year int, month time.Month, day int, hourMinSec ...int) time.Time {
|
|||||||
sec = hourMinSec[2]
|
sec = hourMinSec[2]
|
||||||
fallthrough
|
fallthrough
|
||||||
case 2:
|
case 2:
|
||||||
min = hourMinSec[1]
|
minute = hourMinSec[1]
|
||||||
fallthrough
|
fallthrough
|
||||||
case 1:
|
case 1:
|
||||||
hour = hourMinSec[0]
|
hour = hourMinSec[0]
|
||||||
@ -188,5 +193,5 @@ func newTime(year int, month time.Month, day int, hourMinSec ...int) time.Time {
|
|||||||
panic("too many arguments")
|
panic("too many arguments")
|
||||||
}
|
}
|
||||||
|
|
||||||
return time.Date(year, month, day, hour, min, sec, 0, time.UTC)
|
return time.Date(year, month, day, hour, minute, sec, 0, time.UTC)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user