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  }