Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement support for OpenVMS (only LIST thus far) #339

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 69 additions & 1 deletion parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,81 @@ var listLineParsers = []parseFunc{
parseLsListLine,
parseDirListLine,
parseHostedFTPLine,
parseVMSFTPLine,
}

var dirTimeFormats = []string{
"01-02-06 03:04PM",
"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.
func parseRFC3659ListLine(line string, _ time.Time, loc *time.Location) (*Entry, error) {
return parseNextRFC3659ListLine(line, loc, &Entry{})
Expand Down Expand Up @@ -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
// 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{}
var err error

Expand Down
33 changes: 19 additions & 14 deletions parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ var listTests = []line{

// 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)},

// 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{
Expand All @@ -96,19 +100,20 @@ var listTestsFail = []unsupportedLine{
{"total 1", errUnsupportedListLine},
{"000000000x ", errUnsupportedListLine}, // see https://github.com/jlaffaye/ftp/issues/97
{"", errUnsupportedListLine},
{"DECW$SM.LOG;247", errUnsupportedListLine}, //This is the case where VMS has a continuation on the next line
}

func TestParseValidListLine(t *testing.T) {
for _, lt := range listTests {
t.Run(lt.line, func(t *testing.T) {
assert := assert.New(t)
assertions := assert.New(t)
entry, err := parseListLine(lt.line, now, time.UTC)

if assert.NoError(err) {
assert.Equal(lt.name, entry.Name)
assert.Equal(lt.entryType, entry.Type)
assert.Equal(lt.size, entry.Size)
assert.Equal(lt.time, entry.Time)
if assertions.NoError(err) {
assertions.Equal(lt.name, entry.Name)
assertions.Equal(lt.entryType, entry.Type)
assertions.Equal(lt.size, entry.Size)
assertions.Equal(lt.time, entry.Time)
}
})
}
Expand All @@ -117,13 +122,13 @@ func TestParseValidListLine(t *testing.T) {
func TestParseSymlinks(t *testing.T) {
for _, lt := range listTestsSymlink {
t.Run(lt.line, func(t *testing.T) {
assert := assert.New(t)
assertions := assert.New(t)
entry, err := parseListLine(lt.line, now, time.UTC)

if assert.NoError(err) {
assert.Equal(lt.name, entry.Name)
assert.Equal(lt.target, entry.Target)
assert.Equal(EntryTypeLink, entry.Type)
if assertions.NoError(err) {
assertions.Equal(lt.name, entry.Name)
assertions.Equal(lt.target, entry.Target)
assertions.Equal(EntryTypeLink, entry.Type)
}
})
}
Expand Down Expand Up @@ -171,7 +176,7 @@ func TestSettime(t *testing.T) {

// 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 {
var hour, min, sec int
var hour, minute, sec int

switch len(hourMinSec) {
case 0:
Expand All @@ -180,13 +185,13 @@ func newTime(year int, month time.Month, day int, hourMinSec ...int) time.Time {
sec = hourMinSec[2]
fallthrough
case 2:
min = hourMinSec[1]
minute = hourMinSec[1]
fallthrough
case 1:
hour = hourMinSec[0]
default:
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)
}