diff --git a/README.md b/README.md index 6b1210f..abd3757 100644 --- a/README.md +++ b/README.md @@ -34,8 +34,11 @@ HOST= # If assigned, must be a valid port number. PORT=8080 -# Automatically serve the index file for a given directory (default). If set to -# 'false', URLs ending with a '/' will return 'NOT FOUND'. +# When set to 'true' the index.html file in the folder will be served. And +# the file list will not be served. +ALLOW_INDEX=true + +# Automatically serve the index of file list for a given directory (default). SHOW_LISTING=true # Folder with the content to serve. diff --git a/cli/help/help.go b/cli/help/help.go index c71cb34..9dee86e 100644 --- a/cli/help/help.go +++ b/cli/help/help.go @@ -61,6 +61,12 @@ ENVIRONMENT VARIABLES Examples: REFERRERS='http://localhost,https://some.site,http://other.site:8080' REFERRERS=',http://localhost,https://some.site,http://other.site:8080' + ALLOW_INDEX + When set to 'true' the index.html file in the folder(not include the + sub folders) will be served. And the file list will not be served. + For example, if the client requests 'http://127.0.0.1/' the 'index.html' + file in the root of the directory being served is returned. Default value + is 'true'. SHOW_LISTING Automatically serve the index file for the directory if requested. For example, if the client requests 'http://127.0.0.1/' the 'index.html' @@ -161,12 +167,22 @@ USAGE export FOLDER=/var/www export PORT=80 + export ALLOW_INDEX=true # Default behavior export SHOW_LISTING=true # Default behavior static-file-server Retrieve 'index.html' with: wget http://my.machine/ export FOLDER=/var/www export PORT=80 + export ALLOW_INDEX=true # Default behavior + export SHOW_LISTING=false + static-file-server + Retrieve 'index.html' with: wget http://my.machine/ + Returns 'NOT FOUND': wget http://my.machine/dir/ + + export FOLDER=/var/www + export PORT=80 + export ALLOW_INDEX=false export SHOW_LISTING=false static-file-server Returns 'NOT FOUND': wget http://my.machine/ diff --git a/cli/server/server.go b/cli/server/server.go index ea754ad..a1b008e 100644 --- a/cli/server/server.go +++ b/cli/server/server.go @@ -59,9 +59,12 @@ func handlerSelector() (handler http.HandlerFunc) { // Determine whether index files should hidden. if !config.Get.ShowListing { - handler = handle.IgnoreIndex(handler) + if config.Get.AllowIndex { + handler = handle.PreventListings(handler, config.Get.Folder, config.Get.URLPrefix) + } else { + handler = handle.IgnoreIndex(handler) + } } - // If configured, apply wildcard CORS support. if config.Get.Cors { handler = handle.AddCorsWildcardHeaders(handler) diff --git a/config/config.go b/config/config.go index 535a1dd..9b3ec59 100644 --- a/config/config.go +++ b/config/config.go @@ -21,6 +21,7 @@ var ( Folder string `yaml:"folder"` Host string `yaml:"host"` Port uint16 `yaml:"port"` + AllowIndex bool `yaml:"allow-index"` ShowListing bool `yaml:"show-listing"` TLSCert string `yaml:"tls-cert"` TLSKey string `yaml:"tls-key"` @@ -39,6 +40,7 @@ const ( hostKey = "HOST" portKey = "PORT" referrersKey = "REFERRERS" + allowIndexKey = "ALLOW_INDEX" showListingKey = "SHOW_LISTING" tlsCertKey = "TLS_CERT" tlsKeyKey = "TLS_KEY" @@ -53,6 +55,7 @@ var ( defaultHost = "" defaultPort = uint16(8080) defaultReferrers = []string{} + defaultAllowIndex = true defaultShowListing = true defaultTLSCert = "" defaultTLSKey = "" @@ -73,6 +76,7 @@ func setDefaults() { Get.Host = defaultHost Get.Port = defaultPort Get.Referrers = defaultReferrers + Get.AllowIndex = defaultAllowIndex Get.ShowListing = defaultShowListing Get.TLSCert = defaultTLSCert Get.TLSKey = defaultTLSKey @@ -124,6 +128,7 @@ func overrideWithEnvVars() { Get.Folder = envAsStr(folderKey, Get.Folder) Get.Host = envAsStr(hostKey, Get.Host) Get.Port = envAsUint16(portKey, Get.Port) + Get.AllowIndex = envAsBool(allowIndexKey, Get.AllowIndex) Get.ShowListing = envAsBool(showListingKey, Get.ShowListing) Get.TLSCert = envAsStr(tlsCertKey, Get.TLSCert) Get.TLSKey = envAsStr(tlsKeyKey, Get.TLSKey) diff --git a/config/config_test.go b/config/config_test.go index 05958f5..a4c541b 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -82,6 +82,7 @@ func TestOverrideWithEnvvars(t *testing.T) { testFolder := "/my/directory" testHost := "apets.life" testPort := uint16(666) + testAllowIndex := false testShowListing := false testTLSCert := "my.pem" testTLSKey := "my.key" @@ -92,6 +93,7 @@ func TestOverrideWithEnvvars(t *testing.T) { os.Setenv(folderKey, testFolder) os.Setenv(hostKey, testHost) os.Setenv(portKey, strconv.Itoa(int(testPort))) + os.Setenv(allowIndexKey, fmt.Sprintf("%t", testAllowIndex)) os.Setenv(showListingKey, fmt.Sprintf("%t", testShowListing)) os.Setenv(tlsCertKey, testTLSCert) os.Setenv(tlsKeyKey, testTLSKey) diff --git a/handle/handle.go b/handle/handle.go index 67e1a13..6e85835 100644 --- a/handle/handle.go +++ b/handle/handle.go @@ -6,6 +6,8 @@ import ( "fmt" "log" "net/http" + "os" + "path" "strings" ) @@ -130,6 +132,23 @@ func Prefix(serveFile FileServerFunc, folder, urlPrefix string) http.HandlerFunc } } +// PreventListings returns a function that prevents listing of directories but +// still allows index.html to be served. +func PreventListings(serve http.HandlerFunc, folder string, urlPrefix string) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if strings.HasSuffix(r.URL.Path, "/") { + // If the directory does not contain an index.html file, then + // return 'NOT FOUND' to prevent listing of the directory. + stat, err := os.Stat(path.Join(folder, strings.TrimPrefix(r.URL.Path, urlPrefix), "index.html")) + if err != nil || (err == nil && !stat.Mode().IsRegular()) { + http.NotFound(w, r) + return + } + } + serve(w, r) + } +} + // IgnoreIndex wraps an HTTP request. In the event of a folder root request, // this function will automatically return 'NOT FOUND' as opposed to default // behavior where the index file for that directory is retrieved. diff --git a/handle/handle_test.go b/handle/handle_test.go index 01108fc..ef6158f 100644 --- a/handle/handle_test.go +++ b/handle/handle_test.go @@ -27,6 +27,8 @@ var ( tmpSubDeepIndexName = "sub/deep/index.html" tmpSubDeepFileName = "sub/deep/file.txt" tmpSubDeepBadName = "sub/deep/bad.txt" + tmpNoIndexDir = "noindex/" + tmpNoIndexName = "noindex/noindex.txt" tmpIndex = "Space: the final frontier" tmpFile = "These are the voyages of the starship Enterprise." @@ -48,6 +50,7 @@ var ( baseDir + tmpSubFileName: tmpSubFile, baseDir + tmpSubDeepIndexName: tmpSubDeepIndex, baseDir + tmpSubDeepFileName: tmpSubDeepFile, + baseDir + tmpNoIndexName: tmpSubDeepFile, } serveFileFuncs = []FileServerFunc{ @@ -337,6 +340,56 @@ func TestIgnoreIndex(t *testing.T) { } } +func TestPreventListings(t *testing.T) { + testCases := []struct { + name string + path string + code int + contents string + }{ + {"Good base dir", "", ok, tmpIndex}, + {"Good base index", tmpIndexName, redirect, nothing}, + {"Good base file", tmpFileName, ok, tmpFile}, + {"Bad base file", tmpBadName, missing, notFound}, + {"Good subdir dir", subDir, ok, tmpSubIndex}, + {"Good subdir index", tmpSubIndexName, redirect, nothing}, + {"Good subdir file", tmpSubFileName, ok, tmpSubFile}, + {"Dir without index", tmpNoIndexDir, missing, notFound}, + } + + for _, serveFile := range serveFileFuncs { + handler := PreventListings(Basic(serveFile, baseDir), baseDir, "") + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + fullpath := "http://localhost/" + tc.path + req := httptest.NewRequest("GET", fullpath, nil) + w := httptest.NewRecorder() + + handler(w, req) + + resp := w.Result() + body, err := ioutil.ReadAll(resp.Body) + if nil != err { + t.Errorf("While reading body got %v", err) + } + contents := string(body) + if tc.code != resp.StatusCode { + t.Errorf( + "While retrieving %s expected status code of %d but got %d", + fullpath, tc.code, resp.StatusCode, + ) + } + if tc.contents != contents { + t.Errorf( + "While retrieving %s expected contents '%s' but got '%s'", + fullpath, tc.contents, contents, + ) + } + }) + } + } +} + func TestAddAccessKey(t *testing.T) { // Prepare testing data. accessKey := "my-access-key"