diff --git a/README.md b/README.md index 05a4a35..3a38825 100644 --- a/README.md +++ b/README.md @@ -28,8 +28,17 @@ rules: [] # CORS configuration cors: - - enabled: false - allowed_hosts: [] + enabled: true + credentials: true + allowed_headers: + - Depth + allowed_hosts: + - http://localhost:8080 + allowed_methods: + - GET + exposed_headers: + - Content-Length + - Content-Range users: - username: admin @@ -54,6 +63,13 @@ There are more ways to customize how you run WebDAV through flags and environmen An example of how to use this with `systemd` is on [webdav.service.example](/webdav.service.example). +### CORS + +The `allowed_*` properties are optional, the default value for each of them will be `*`. `exposed_headers` is optional as well, but is not set if not defined. Setting `credentials` to `true` will allow you to: + +1. Use `withCredentials = true` in javascript. +2. Use the `username:password@host` syntax. + ## License MIT © [Henrique Dias](https://hacdias.com) diff --git a/cmd/config.go b/cmd/config.go index b9196ef..19d06f0 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -123,30 +123,44 @@ func parseUsers(raw []interface{}, c *webdav.Config) { } } -func parseCors(raw []interface{}, c *webdav.Config) { - hosts := []string{} +func parseCors(cfg map[string]interface{}, c *webdav.Config) { + cors := webdav.CorsCfg{ + Enabled: cfg["enabled"].(bool), + Credentials: cfg["credentials"].(bool), + } - for _, v := range raw { + cors.AllowedHeaders = corsProperty("allowed_headers", cfg) + cors.AllowedHosts = corsProperty("allowed_hosts", cfg) + cors.AllowedMethods = corsProperty("allowed_methods", cfg) + cors.ExposedHeaders = corsProperty("exposed_headers", cfg) - if cfg, ok := v.(map[interface{}]interface{}); ok { + c.Cors = cors +} - cors := webdav.CorsCfg{ - Enabled: cfg["enabled"].(bool), - AllowedHosts: []string{}, - } +func corsProperty(property string, cfg map[string]interface{}) []string { + var def []string - if allowedHosts, ok := cfg["allowed_hosts"]; ok { - hosts = append(hosts, strings.Split(allowedHosts.(string), ",")...) - } + if property == "exposed_headers" { + def = []string{} + } else { + def = []string{"*"} + } - if len(hosts) == 0 { - hosts = append(hosts, "*") - } + if allowed, ok := cfg[property].([]interface{}); ok { + items := make([]string, len(allowed)) - cors.AllowedHosts = hosts - c.Cors = cors + for idx, a := range allowed { + items[idx] = a.(string) + } + + if len(items) == 0 { + return def + } else { + return items } } + + return def } func readConfig(flags *pflag.FlagSet) *webdav.Config { @@ -162,8 +176,8 @@ func readConfig(flags *pflag.FlagSet) *webdav.Config { }, Auth: getOptB(flags, "auth"), Cors: webdav.CorsCfg{ - Enabled: false, - AllowedHosts: []string{}, + Enabled: false, + Credentials: false, }, Users: map[string]*webdav.User{}, } @@ -179,7 +193,7 @@ func readConfig(flags *pflag.FlagSet) *webdav.Config { } rawCors := v.Get("cors") - if cors, ok := rawCors.([]interface{}); ok { + if cors, ok := rawCors.(map[string]interface{}); ok { parseCors(cors, cfg) } diff --git a/webdav/webdav.go b/webdav/webdav.go index 757af5c..c69192e 100755 --- a/webdav/webdav.go +++ b/webdav/webdav.go @@ -4,12 +4,17 @@ import ( "context" "log" "net/http" + "strings" ) // CorsCfg is the CORS config. type CorsCfg struct { - Enabled bool - AllowedHosts []string + Enabled bool + Credentials bool + AllowedHeaders []string + AllowedHosts []string + AllowedMethods []string + ExposedHeaders []string } // Config is the configuration of a WebDAV instance. @@ -25,19 +30,34 @@ func (c *Config) ServeHTTP(w http.ResponseWriter, r *http.Request) { u := c.User requestOrigin := r.Header.Get("Origin") - // add cors headers before any operation so even on 401 unauthorized cors will working only when Origin header is present so request came from browser + // Add CORS headers before any operation so even on a 401 unauthorized status, CORS will work. if c.Cors.Enabled && requestOrigin != "" { - headers := w.Header() - if len(c.Cors.AllowedHosts) == 1 && c.Cors.AllowedHosts[0] == "*" { - headers.Set("Access-Control-Allow-Methods", "*") - headers.Set("Access-Control-Allow-Headers", "*") + allowedHeaders := strings.Join(c.Cors.AllowedHeaders, ", ") + allowedMethods := strings.Join(c.Cors.AllowedMethods, ", ") + exposedHeaders := strings.Join(c.Cors.ExposedHeaders, ", ") + + allowAllHosts := len(c.Cors.AllowedHosts) == 1 && c.Cors.AllowedHosts[0] == "*" + allowedHost := isAllowedHost(c.Cors.AllowedHosts, requestOrigin) + + if allowAllHosts { headers.Set("Access-Control-Allow-Origin", "*") - } else if isAllowedHost(c.Cors.AllowedHosts, requestOrigin) { + } else if allowedHost { headers.Set("Access-Control-Allow-Origin", requestOrigin) - headers.Set("Access-Control-Allow-Headers", "*") - headers.Set("Access-Control-Allow-Methods", "*") + } + + if allowAllHosts || allowedHost { + headers.Set("Access-Control-Allow-Headers", allowedHeaders) + headers.Set("Access-Control-Allow-Methods", allowedMethods) + + if c.Cors.Credentials { + headers.Set("Access-Control-Allow-Credentials", "true") + } + + if len(c.Cors.ExposedHeaders) > 0 { + headers.Set("Access-Control-Expose-Headers", exposedHeaders) + } } } @@ -45,6 +65,7 @@ func (c *Config) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } + // Authentication if c.Auth { w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)