2021-08-11 23:09:43 +08:00
|
|
|
package sftp
|
|
|
|
|
|
|
|
import (
|
|
|
|
"os"
|
|
|
|
"regexp"
|
2024-04-05 04:19:37 +08:00
|
|
|
"runtime"
|
2021-08-11 23:09:43 +08:00
|
|
|
"strings"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
typeDirectory = "d"
|
|
|
|
typeFile = "[^d]"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestRunLsWithExamplesDirectory(t *testing.T) {
|
|
|
|
path := "examples"
|
|
|
|
item, _ := os.Stat(path)
|
2021-08-17 19:40:30 +08:00
|
|
|
result := runLs(nil, item)
|
2021-08-11 23:09:43 +08:00
|
|
|
runLsTestHelper(t, result, typeDirectory, path)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRunLsWithLicensesFile(t *testing.T) {
|
|
|
|
path := "LICENSE"
|
|
|
|
item, _ := os.Stat(path)
|
2021-08-17 19:40:30 +08:00
|
|
|
result := runLs(nil, item)
|
|
|
|
runLsTestHelper(t, result, typeFile, path)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRunLsWithExamplesDirectoryWithOSLookup(t *testing.T) {
|
|
|
|
path := "examples"
|
|
|
|
item, _ := os.Stat(path)
|
|
|
|
result := runLs(osIDLookup{}, item)
|
|
|
|
runLsTestHelper(t, result, typeDirectory, path)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRunLsWithLicensesFileWithOSLookup(t *testing.T) {
|
|
|
|
path := "LICENSE"
|
|
|
|
item, _ := os.Stat(path)
|
|
|
|
result := runLs(osIDLookup{}, item)
|
2021-08-11 23:09:43 +08:00
|
|
|
runLsTestHelper(t, result, typeFile, path)
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2023-03-28 01:05:24 +08:00
|
|
|
The format of the `longname' field is unspecified by this protocol.
|
|
|
|
It MUST be suitable for use in the output of a directory listing
|
|
|
|
command (in fact, the recommended operation for a directory listing
|
|
|
|
command is to simply display this data). However, clients SHOULD NOT
|
|
|
|
attempt to parse the longname field for file attributes; they SHOULD
|
|
|
|
use the attrs field instead.
|
|
|
|
|
|
|
|
The recommended format for the longname field is as follows:
|
|
|
|
|
|
|
|
-rwxr-xr-x 1 mjos staff 348911 Mar 25 14:29 t-filexfer
|
|
|
|
1234567890 123 12345678 12345678 12345678 123456789012
|
|
|
|
|
|
|
|
Here, the first line is sample output, and the second field indicates
|
|
|
|
widths of the various fields. Fields are separated by spaces. The
|
|
|
|
first field lists file permissions for user, group, and others; the
|
|
|
|
second field is link count; the third field is the name of the user
|
|
|
|
who owns the file; the fourth field is the name of the group that
|
|
|
|
owns the file; the fifth field is the size of the file in bytes; the
|
|
|
|
sixth field (which actually may contain spaces, but is fixed to 12
|
|
|
|
characters) is the file modification time, and the seventh field is
|
|
|
|
the file name. Each field is specified to be a minimum of certain
|
|
|
|
number of character positions (indicated by the second line above),
|
|
|
|
but may also be longer if the data does not fit in the specified
|
|
|
|
length.
|
|
|
|
|
|
|
|
The SSH_FXP_ATTRS response has the following format:
|
|
|
|
|
|
|
|
uint32 id
|
|
|
|
ATTRS attrs
|
|
|
|
|
|
|
|
where `id' is the request identifier, and `attrs' is the returned
|
|
|
|
file attributes as described in Section “File Attributes”.
|
|
|
|
|
|
|
|
N.B.: FileZilla does parse this ls formatting, and so not rendering it
|
|
|
|
on any particular GOOS/GOARCH can cause compatibility issues with this client.
|
2021-08-11 23:09:43 +08:00
|
|
|
*/
|
|
|
|
func runLsTestHelper(t *testing.T, result, expectedType, path string) {
|
|
|
|
// using regular expressions to make tests work on all systems
|
|
|
|
// a virtual file system (like afero) would be needed to mock valid filesystem checks
|
|
|
|
// expected layout is:
|
|
|
|
// drwxr-xr-x 8 501 20 272 Aug 9 19:46 examples
|
|
|
|
|
|
|
|
t.Log(result)
|
|
|
|
|
|
|
|
sparce := strings.Split(result, " ")
|
|
|
|
|
|
|
|
var fields []string
|
|
|
|
for _, field := range sparce {
|
|
|
|
if field == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
fields = append(fields, field)
|
|
|
|
}
|
|
|
|
|
|
|
|
perms, linkCnt, user, group, size := fields[0], fields[1], fields[2], fields[3], fields[4]
|
|
|
|
dateTime := strings.Join(fields[5:8], " ")
|
|
|
|
filename := fields[8]
|
|
|
|
|
2024-04-05 04:19:37 +08:00
|
|
|
if runtime.GOOS == "zos" {
|
|
|
|
// User and Group are always only uppercase characters on z/OS
|
|
|
|
user = strings.ToLower(user)
|
|
|
|
group = strings.ToLower(group)
|
|
|
|
}
|
|
|
|
|
2021-08-11 23:09:43 +08:00
|
|
|
// permissions (len 10, "drwxr-xr-x")
|
|
|
|
const (
|
|
|
|
rwxs = "[-r][-w][-xsS]"
|
|
|
|
rwxt = "[-r][-w][-xtT]"
|
|
|
|
)
|
|
|
|
if ok, err := regexp.MatchString("^"+expectedType+rwxs+rwxs+rwxt+"$", perms); !ok {
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal("unexpected error:", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
t.Errorf("runLs(%q): permission field mismatch, expected dir, got: %#v, err: %#v", path, perms, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// link count (len 3, number)
|
|
|
|
const (
|
|
|
|
number = "(?:[0-9]+)"
|
|
|
|
)
|
|
|
|
if ok, err := regexp.MatchString("^"+number+"$", linkCnt); !ok {
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal("unexpected error:", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
t.Errorf("runLs(%q): link count field mismatch, got: %#v, err: %#v", path, linkCnt, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// username / uid (len 8, number or string)
|
|
|
|
const (
|
|
|
|
name = "(?:[a-z_][a-z0-9_]*)"
|
|
|
|
)
|
|
|
|
if ok, err := regexp.MatchString("^(?:"+number+"|"+name+")+$", user); !ok {
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal("unexpected error:", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
t.Errorf("runLs(%q): username / uid mismatch, expected user, got: %#v, err: %#v", path, user, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// groupname / gid (len 8, number or string)
|
|
|
|
if ok, err := regexp.MatchString("^(?:"+number+"|"+name+")+$", group); !ok {
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal("unexpected error:", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
t.Errorf("runLs(%q): groupname / gid mismatch, expected group, got: %#v, err: %#v", path, group, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// filesize (len 8)
|
|
|
|
if ok, err := regexp.MatchString("^"+number+"$", size); !ok {
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal("unexpected error:", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
t.Errorf("runLs(%q): filesize field mismatch, expected size in bytes, got: %#v, err: %#v", path, size, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// mod time (len 12, e.g. Aug 9 19:46)
|
|
|
|
_, err := time.Parse("Jan 2 15:04", dateTime)
|
|
|
|
if err != nil {
|
|
|
|
_, err = time.Parse("Jan 2 2006", dateTime)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("runLs.dateTime = %#v should match `Jan 2 15:04` or `Jan 2 2006`: %+v", dateTime, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// filename
|
|
|
|
if path != filename {
|
|
|
|
t.Errorf("runLs.filename = %#v, expected: %#v", filename, path)
|
|
|
|
}
|
|
|
|
}
|