diff --git a/caddyhttp/httpserver/context.go b/caddyhttp/httpserver/context.go index f725fdd0e..fcbae17d6 100644 --- a/caddyhttp/httpserver/context.go +++ b/caddyhttp/httpserver/context.go @@ -288,3 +288,33 @@ func (c Context) Map(values ...interface{}) (map[string]interface{}, error) { } return dict, nil } + +// Files reads and returns a slice of names from the given directory +// relative to the root of Context c. +func (c Context) Files(name string) ([]string, error) { + dir, err := c.Root.Open(path.Clean(name)) + if err != nil { + return nil, err + } + defer dir.Close() + + stat, err := dir.Stat() + if err != nil { + return nil, err + } + if !stat.IsDir() { + return nil, fmt.Errorf("%v is not a directory", name) + } + + dirInfo, err := dir.Readdir(0) + if err != nil { + return nil, err + } + + names := make([]string, len(dirInfo)) + for i, fileInfo := range dirInfo { + names[i] = fileInfo.Name() + } + + return names, nil +} diff --git a/caddyhttp/httpserver/context_test.go b/caddyhttp/httpserver/context_test.go index 56c09c41c..cb568d77d 100644 --- a/caddyhttp/httpserver/context_test.go +++ b/caddyhttp/httpserver/context_test.go @@ -8,6 +8,7 @@ import ( "net/url" "os" "path/filepath" + "reflect" "strings" "testing" "time" @@ -684,3 +685,97 @@ func TestTemplates(t *testing.T) { } } } + +func TestFiles(t *testing.T) { + tests := []struct { + fileNames []string + inputBase string + shouldErr bool + verifyErr func(error) bool + }{ + // Test 1 - directory and files exist + { + fileNames: []string{"file1", "file2"}, + shouldErr: false, + }, + // Test 2 - directory exists, no files + { + fileNames: []string{}, + shouldErr: false, + }, + // Test 3 - file or directory does not exist + { + fileNames: nil, + inputBase: "doesNotExist", + shouldErr: true, + verifyErr: os.IsNotExist, + }, + // Test 4 - directory and files exist, but path to a file + { + fileNames: []string{"file1", "file2"}, + inputBase: "file1", + shouldErr: true, + verifyErr: func(err error) bool { + return strings.HasSuffix(err.Error(), "is not a directory") + }, + }, + // Test 5 - try to leave Context Root + { + fileNames: nil, + inputBase: filepath.Join("..", "..", "..", "..", "..", "etc"), + shouldErr: true, + verifyErr: os.IsNotExist, + }, + } + + for i, test := range tests { + context := getContextOrFail(t) + testPrefix := getTestPrefix(i + 1) + var dirPath string + var err error + + // Create directory / files from test case. + if test.fileNames != nil { + dirPath, err = ioutil.TempDir(fmt.Sprintf("%s", context.Root), "caddy_test") + if err != nil { + t.Fatalf(testPrefix+"Expected no error creating directory, got: '%s'", err.Error()) + } + + for _, name := range test.fileNames { + absFilePath := filepath.Join(dirPath, name) + if err = ioutil.WriteFile(absFilePath, []byte(""), os.ModePerm); err != nil { + t.Fatalf(testPrefix+"Expected no error creating file, got: '%s'", err.Error()) + } + } + } + + // Perform test case. + input := filepath.ToSlash(filepath.Join(filepath.Base(dirPath), test.inputBase)) + actual, err := context.Files(input) + if err != nil { + if !test.shouldErr { + t.Errorf(testPrefix+"Expected no error, got: '%s'", err.Error()) + } else if !test.verifyErr(err) { + t.Errorf(testPrefix+"Could not verify error content, got: '%s'", err.Error()) + } + } else if test.shouldErr { + t.Errorf(testPrefix + "Expected error but had none") + } else { + numFiles := len(test.fileNames) + // reflect.DeepEqual does not consider two empty slices to be equal + if numFiles == 0 && len(actual) != 0 { + t.Errorf(testPrefix+"Expected files %v, got: %v", + test.fileNames, actual) + } else if numFiles > 0 && !reflect.DeepEqual(test.fileNames, actual) { + t.Errorf(testPrefix+"Expected files %v, got: %v", + test.fileNames, actual) + } + } + + if dirPath != "" { + if err := os.RemoveAll(dirPath); err != nil && !os.IsNotExist(err) { + t.Fatalf(testPrefix+"Expected no error removing directory, got: '%s'", err.Error()) + } + } + } +}