diff --git a/Dockerfile b/Dockerfile index 782c9df..f9a69e0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ ################################################################################ FROM golang:1.13.1 as builder -ENV VERSION 1.6.6 +ENV VERSION 1.7.0 ENV BUILD_DIR /build RUN mkdir -p ${BUILD_DIR} @@ -31,5 +31,5 @@ LABEL life.apets.vendor="Halverneus" \ life.apets.url="https://github.com/halverneus/static-file-server" \ life.apets.name="Static File Server" \ life.apets.description="A tiny static file server" \ - life.apets.version="v1.6.6" \ + life.apets.version="v1.7.0" \ life.apets.schema-version="1.0" diff --git a/Dockerfile.all b/Dockerfile.all index 66d026e..3d696be 100644 --- a/Dockerfile.all +++ b/Dockerfile.all @@ -1,6 +1,6 @@ FROM golang:1.13.1 as builder -ENV VERSION 1.6.6 +ENV VERSION 1.7.0 ENV BUILD_DIR /build RUN mkdir -p ${BUILD_DIR} @@ -21,5 +21,5 @@ LABEL life.apets.vendor="Halverneus" \ life.apets.url="https://github.com/halverneus/static-file-server" \ life.apets.name="Static File Server" \ life.apets.description="A tiny static file server" \ - life.apets.version="v1.6.6" \ + life.apets.version="v1.7.0" \ life.apets.schema-version="1.0" diff --git a/README.md b/README.md index 1fad068..86da89e 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,9 @@ Install from any of the following locations: Default values are shown with the associated environment variable. ```bash +# Enables resource access from any domain. +CORS=false + # Enable debugging for troubleshooting. If set to 'true' this prints extra # information during execution. IMPORTANT NOTE: The configuration summary is # printed to stdout while logs generated during execution are printed to stderr. @@ -60,6 +63,7 @@ the path to the configuration file using the command line option ('-c', '-config', '--config'). ```yaml +cors: false debug: false folder: /web host: "" diff --git a/cli/help/help.go b/cli/help/help.go index 6f8a3fa..e5bbb65 100644 --- a/cli/help/help.go +++ b/cli/help/help.go @@ -33,6 +33,10 @@ DEPENDENCIES None... not even libc! ENVIRONMENT VARIABLES + CORS + When set to 'true' it enables resource access from any domain. All + responses will include the headers 'Access-Control-Allow-Origin' and + 'Access-Control-Allow-Headers' with a wildcard value ('*'). DEBUG When set to 'true' enables additional logging, including the configuration used and an access log for each request. IMPORTANT NOTE: @@ -85,6 +89,7 @@ CONFIGURATION FILE Example config.yml with defaults: ---------------------------------------------------------------------------- + cors: false debug: false folder: /web host: "" diff --git a/cli/server/server.go b/cli/server/server.go index 31d98d1..447c90d 100644 --- a/cli/server/server.go +++ b/cli/server/server.go @@ -61,6 +61,12 @@ func handlerSelector() (handler http.HandlerFunc) { if !config.Get.ShowListing { handler = handle.IgnoreIndex(handler) } + + // If configured, apply wildcard CORS support. + if config.Get.Cors { + handler = handle.AddCorsWildcardHeaders(handler) + } + return } diff --git a/cli/server/server_test.go b/cli/server/server_test.go index c0a2bb9..c297f93 100644 --- a/cli/server/server_test.go +++ b/cli/server/server_test.go @@ -42,23 +42,28 @@ func TestHandlerSelector(t *testing.T) { listing bool debug bool refer []string + cors bool }{ - {"Basic handler w/o debug", testFolder, "", true, false, ignoreReferrer}, - {"Prefix handler w/o debug", testFolder, testPrefix, true, false, ignoreReferrer}, - {"Basic and hide listing handler w/o debug", testFolder, "", false, false, ignoreReferrer}, - {"Prefix and hide listing handler w/o debug", testFolder, testPrefix, false, false, ignoreReferrer}, - {"Basic handler w/debug", testFolder, "", true, true, ignoreReferrer}, - {"Prefix handler w/debug", testFolder, testPrefix, true, true, ignoreReferrer}, - {"Basic and hide listing handler w/debug", testFolder, "", false, true, ignoreReferrer}, - {"Prefix and hide listing handler w/debug", testFolder, testPrefix, false, true, ignoreReferrer}, - {"Basic handler w/o debug w/refer", testFolder, "", true, false, testReferrer}, - {"Prefix handler w/o debug w/refer", testFolder, testPrefix, true, false, testReferrer}, - {"Basic and hide listing handler w/o debug w/refer", testFolder, "", false, false, testReferrer}, - {"Prefix and hide listing handler w/o debug w/refer", testFolder, testPrefix, false, false, testReferrer}, - {"Basic handler w/debug w/refer", testFolder, "", true, true, testReferrer}, - {"Prefix handler w/debug w/refer", testFolder, testPrefix, true, true, testReferrer}, - {"Basic and hide listing handler w/debug w/refer", testFolder, "", false, true, testReferrer}, - {"Prefix and hide listing handler w/debug w/refer", testFolder, testPrefix, false, true, testReferrer}, + {"Basic handler w/o debug", testFolder, "", true, false, ignoreReferrer, false}, + {"Prefix handler w/o debug", testFolder, testPrefix, true, false, ignoreReferrer, false}, + {"Basic and hide listing handler w/o debug", testFolder, "", false, false, ignoreReferrer, false}, + {"Prefix and hide listing handler w/o debug", testFolder, testPrefix, false, false, ignoreReferrer, false}, + {"Basic handler w/debug", testFolder, "", true, true, ignoreReferrer, false}, + {"Prefix handler w/debug", testFolder, testPrefix, true, true, ignoreReferrer, false}, + {"Basic and hide listing handler w/debug", testFolder, "", false, true, ignoreReferrer, false}, + {"Prefix and hide listing handler w/debug", testFolder, testPrefix, false, true, ignoreReferrer, false}, + {"Basic handler w/o debug w/refer", testFolder, "", true, false, testReferrer, false}, + {"Prefix handler w/o debug w/refer", testFolder, testPrefix, true, false, testReferrer, false}, + {"Basic and hide listing handler w/o debug w/refer", testFolder, "", false, false, testReferrer, false}, + {"Prefix and hide listing handler w/o debug w/refer", testFolder, testPrefix, false, false, testReferrer, false}, + {"Basic handler w/debug w/refer w/o cors", testFolder, "", true, true, testReferrer, false}, + {"Prefix handler w/debug w/refer w/o cors", testFolder, testPrefix, true, true, testReferrer, false}, + {"Basic and hide listing handler w/debug w/refer w/o cors", testFolder, "", false, true, testReferrer, false}, + {"Prefix and hide listing handler w/debug w/refer w/o cors", testFolder, testPrefix, false, true, testReferrer, false}, + {"Basic handler w/debug w/refer w/cors", testFolder, "", true, true, testReferrer, true}, + {"Prefix handler w/debug w/refer w/cors", testFolder, testPrefix, true, true, testReferrer, true}, + {"Basic and hide listing handler w/debug w/refer w/cors", testFolder, "", false, true, testReferrer, true}, + {"Prefix and hide listing handler w/debug w/refer w/cors", testFolder, testPrefix, false, true, testReferrer, true}, } for _, tc := range testCases { @@ -68,6 +73,7 @@ func TestHandlerSelector(t *testing.T) { config.Get.ShowListing = tc.listing config.Get.URLPrefix = tc.prefix config.Get.Referrers = tc.refer + config.Get.Cors = tc.cors handlerSelector() }) diff --git a/config/config.go b/config/config.go index 1694e33..58aee00 100644 --- a/config/config.go +++ b/config/config.go @@ -14,6 +14,7 @@ import ( var ( // Get the desired configuration value. Get struct { + Cors bool `yaml:"cors"` Debug bool `yaml:"debug"` Folder string `yaml:"folder"` Host string `yaml:"host"` @@ -27,6 +28,7 @@ var ( ) const ( + corsKey = "CORS" debugKey = "DEBUG" folderKey = "FOLDER" hostKey = "HOST" @@ -48,6 +50,7 @@ var ( defaultTLSCert = "" defaultTLSKey = "" defaultURLPrefix = "" + defaultCors = false ) func init() { @@ -65,6 +68,7 @@ func setDefaults() { Get.TLSCert = defaultTLSCert Get.TLSKey = defaultTLSKey Get.URLPrefix = defaultURLPrefix + Get.Cors = defaultCors } // Load the configuration file. @@ -104,6 +108,7 @@ func Log() { // overrideWithEnvVars the default values and the configuration file values. func overrideWithEnvVars() { // Assign envvars, if set. + Get.Cors = envAsBool(corsKey, Get.Cors) Get.Debug = envAsBool(debugKey, Get.Debug) Get.Folder = envAsStr(folderKey, Get.Folder) Get.Host = envAsStr(hostKey, Get.Host) diff --git a/handle/handle.go b/handle/handle.go index babedd3..e869e3b 100644 --- a/handle/handle.go +++ b/handle/handle.go @@ -109,6 +109,16 @@ func IgnoreIndex(serve http.HandlerFunc) http.HandlerFunc { } } +// AddCorsWildcardHeaders wraps an HTTP request to notify client browsers that +// resources should be allowed to be retrieved by any other domain. +func AddCorsWildcardHeaders(serve http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Headers", "*") + serve(w, r) + } +} + // Listening function for serving the handler function. func Listening() ListenerFunc { return func(binding string, handler http.HandlerFunc) error { diff --git a/handle/handle_test.go b/handle/handle_test.go index 11dd6d7..c165300 100644 --- a/handle/handle_test.go +++ b/handle/handle_test.go @@ -458,3 +458,76 @@ func TestValidReferrer(t *testing.T) { }) } } + +func TestAddCorsWildcardHeaders(t *testing.T) { + testCases := []struct { + name string + corsEnabled bool + }{ + {"CORS disabled", false}, + {"CORS enabled", true}, + } + + corsHeaders := map[string]string{ + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Headers": "*", + } + + for _, serveFile := range serveFileFuncs { + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + var handler http.HandlerFunc + if tc.corsEnabled { + handler = AddCorsWildcardHeaders(Basic(serveFile, baseDir)) + } else { + handler = Basic(serveFile, baseDir) + } + + fullpath := "http://localhost/" + tmpFileName + 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 ok != resp.StatusCode { + t.Errorf( + "While retrieving %s expected status code of %d but got %d", + fullpath, ok, resp.StatusCode, + ) + } + if tmpFile != contents { + t.Errorf( + "While retrieving %s expected contents '%s' but got '%s'", + fullpath, tmpFile, contents, + ) + } + + if tc.corsEnabled { + for k, v := range corsHeaders { + if v != resp.Header.Get(k) { + t.Errorf( + "With CORS enabled expect header '%s' to return '%s' but got '%s'", + k, v, resp.Header.Get(k), + ) + } + } + } else { + for k := range corsHeaders { + if "" != resp.Header.Get(k) { + t.Errorf( + "With CORS disabled expected header '%s' to return '' but got '%s'", + k, resp.Header.Get(k), + ) + } + } + } + }) + } + } +}