github.com/pkg/sftp@v1.13.6/ls_formatting_test.go (about) 1 package sftp 2 3 import ( 4 "os" 5 "regexp" 6 "strings" 7 "testing" 8 "time" 9 ) 10 11 const ( 12 typeDirectory = "d" 13 typeFile = "[^d]" 14 ) 15 16 func TestRunLsWithExamplesDirectory(t *testing.T) { 17 path := "examples" 18 item, _ := os.Stat(path) 19 result := runLs(nil, item) 20 runLsTestHelper(t, result, typeDirectory, path) 21 } 22 23 func TestRunLsWithLicensesFile(t *testing.T) { 24 path := "LICENSE" 25 item, _ := os.Stat(path) 26 result := runLs(nil, item) 27 runLsTestHelper(t, result, typeFile, path) 28 } 29 30 func TestRunLsWithExamplesDirectoryWithOSLookup(t *testing.T) { 31 path := "examples" 32 item, _ := os.Stat(path) 33 result := runLs(osIDLookup{}, item) 34 runLsTestHelper(t, result, typeDirectory, path) 35 } 36 37 func TestRunLsWithLicensesFileWithOSLookup(t *testing.T) { 38 path := "LICENSE" 39 item, _ := os.Stat(path) 40 result := runLs(osIDLookup{}, item) 41 runLsTestHelper(t, result, typeFile, path) 42 } 43 44 /* 45 The format of the `longname' field is unspecified by this protocol. 46 It MUST be suitable for use in the output of a directory listing 47 command (in fact, the recommended operation for a directory listing 48 command is to simply display this data). However, clients SHOULD NOT 49 attempt to parse the longname field for file attributes; they SHOULD 50 use the attrs field instead. 51 52 The recommended format for the longname field is as follows: 53 54 -rwxr-xr-x 1 mjos staff 348911 Mar 25 14:29 t-filexfer 55 1234567890 123 12345678 12345678 12345678 123456789012 56 57 Here, the first line is sample output, and the second field indicates 58 widths of the various fields. Fields are separated by spaces. The 59 first field lists file permissions for user, group, and others; the 60 second field is link count; the third field is the name of the user 61 who owns the file; the fourth field is the name of the group that 62 owns the file; the fifth field is the size of the file in bytes; the 63 sixth field (which actually may contain spaces, but is fixed to 12 64 characters) is the file modification time, and the seventh field is 65 the file name. Each field is specified to be a minimum of certain 66 number of character positions (indicated by the second line above), 67 but may also be longer if the data does not fit in the specified 68 length. 69 70 The SSH_FXP_ATTRS response has the following format: 71 72 uint32 id 73 ATTRS attrs 74 75 where `id' is the request identifier, and `attrs' is the returned 76 file attributes as described in Section “File Attributes”. 77 78 N.B.: FileZilla does parse this ls formatting, and so not rendering it 79 on any particular GOOS/GOARCH can cause compatibility issues with this client. 80 */ 81 func runLsTestHelper(t *testing.T, result, expectedType, path string) { 82 // using regular expressions to make tests work on all systems 83 // a virtual file system (like afero) would be needed to mock valid filesystem checks 84 // expected layout is: 85 // drwxr-xr-x 8 501 20 272 Aug 9 19:46 examples 86 87 t.Log(result) 88 89 sparce := strings.Split(result, " ") 90 91 var fields []string 92 for _, field := range sparce { 93 if field == "" { 94 continue 95 } 96 97 fields = append(fields, field) 98 } 99 100 perms, linkCnt, user, group, size := fields[0], fields[1], fields[2], fields[3], fields[4] 101 dateTime := strings.Join(fields[5:8], " ") 102 filename := fields[8] 103 104 // permissions (len 10, "drwxr-xr-x") 105 const ( 106 rwxs = "[-r][-w][-xsS]" 107 rwxt = "[-r][-w][-xtT]" 108 ) 109 if ok, err := regexp.MatchString("^"+expectedType+rwxs+rwxs+rwxt+"$", perms); !ok { 110 if err != nil { 111 t.Fatal("unexpected error:", err) 112 } 113 114 t.Errorf("runLs(%q): permission field mismatch, expected dir, got: %#v, err: %#v", path, perms, err) 115 } 116 117 // link count (len 3, number) 118 const ( 119 number = "(?:[0-9]+)" 120 ) 121 if ok, err := regexp.MatchString("^"+number+"$", linkCnt); !ok { 122 if err != nil { 123 t.Fatal("unexpected error:", err) 124 } 125 126 t.Errorf("runLs(%q): link count field mismatch, got: %#v, err: %#v", path, linkCnt, err) 127 } 128 129 // username / uid (len 8, number or string) 130 const ( 131 name = "(?:[a-z_][a-z0-9_]*)" 132 ) 133 if ok, err := regexp.MatchString("^(?:"+number+"|"+name+")+$", user); !ok { 134 if err != nil { 135 t.Fatal("unexpected error:", err) 136 } 137 138 t.Errorf("runLs(%q): username / uid mismatch, expected user, got: %#v, err: %#v", path, user, err) 139 } 140 141 // groupname / gid (len 8, number or string) 142 if ok, err := regexp.MatchString("^(?:"+number+"|"+name+")+$", group); !ok { 143 if err != nil { 144 t.Fatal("unexpected error:", err) 145 } 146 147 t.Errorf("runLs(%q): groupname / gid mismatch, expected group, got: %#v, err: %#v", path, group, err) 148 } 149 150 // filesize (len 8) 151 if ok, err := regexp.MatchString("^"+number+"$", size); !ok { 152 if err != nil { 153 t.Fatal("unexpected error:", err) 154 } 155 156 t.Errorf("runLs(%q): filesize field mismatch, expected size in bytes, got: %#v, err: %#v", path, size, err) 157 } 158 159 // mod time (len 12, e.g. Aug 9 19:46) 160 _, err := time.Parse("Jan 2 15:04", dateTime) 161 if err != nil { 162 _, err = time.Parse("Jan 2 2006", dateTime) 163 if err != nil { 164 t.Errorf("runLs.dateTime = %#v should match `Jan 2 15:04` or `Jan 2 2006`: %+v", dateTime, err) 165 } 166 } 167 168 // filename 169 if path != filename { 170 t.Errorf("runLs.filename = %#v, expected: %#v", filename, path) 171 } 172 }