diff --git a/caddyhttp/httpserver/plugin.go b/caddyhttp/httpserver/plugin.go index c8b4891a..ad2d8738 100644 --- a/caddyhttp/httpserver/plugin.go +++ b/caddyhttp/httpserver/plugin.go @@ -122,15 +122,17 @@ func (h *httpContext) InspectServerBlocks(sourceFile string, serverBlocks []cadd // For each address in each server block, make a new config for _, sb := range serverBlocks { for _, key := range sb.Keys { - key = strings.ToLower(key) - if _, dup := h.keysToSiteConfigs[key]; dup { - return serverBlocks, fmt.Errorf("duplicate site key: %s", key) - } addr, err := standardizeAddress(key) if err != nil { return serverBlocks, err } + addr = addr.Normalize() + key = addr.Key() + if _, dup := h.keysToSiteConfigs[key]; dup { + return serverBlocks, fmt.Errorf("duplicate site key: %s", key) + } + // Fill in address components from command line so that middleware // have access to the correct information during setup if addr.Host == "" && Host != DefaultHost { @@ -145,7 +147,7 @@ func (h *httpContext) InspectServerBlocks(sourceFile string, serverBlocks []cadd if addrCopy.Port == "" && Port == DefaultPort { addrCopy.Port = Port } - addrStr := strings.ToLower(addrCopy.String()) + addrStr := addrCopy.String() if otherSiteKey, dup := siteAddrs[addrStr]; dup { err := fmt.Errorf("duplicate site address: %s", addrStr) if (addrCopy.Host == Host && Host != DefaultHost) || @@ -249,12 +251,22 @@ func (h *httpContext) MakeServers() ([]caddy.Server, error) { return servers, nil } +// normalizedKey returns "normalized" key representation: +// scheme and host names are lowered, everything else stays the same +func normalizedKey(key string) string { + addr, err := standardizeAddress(key) + if err != nil { + return key + } + return addr.Normalize().Key() +} + // GetConfig gets the SiteConfig that corresponds to c. // If none exist (should only happen in tests), then a // new, empty one will be created. func GetConfig(c *caddy.Controller) *SiteConfig { ctx := c.Context().(*httpContext) - key := strings.ToLower(c.Key) + key := normalizedKey(c.Key) if cfg, ok := ctx.keysToSiteConfigs[key]; ok { return cfg } @@ -358,6 +370,43 @@ func (a Address) VHost() string { return a.Original } +// Normalize normalizes URL: turn scheme and host names into lower case +func (a Address) Normalize() Address { + path := a.Path + if !CaseSensitivePath { + path = strings.ToLower(path) + } + return Address{ + Original: a.Original, + Scheme: strings.ToLower(a.Scheme), + Host: strings.ToLower(a.Host), + Port: a.Port, + Path: path, + } +} + +// Key is similar to String, just replaces scheme and host values with modified values. +// Unlike String it doesn't add anything default (scheme, port, etc) +func (a Address) Key() string { + res := "" + if a.Scheme != "" { + res += a.Scheme + "://" + } + if a.Host != "" { + res += a.Host + } + if a.Port != "" { + if strings.HasPrefix(a.Original[len(res):], ":"+a.Port) { + // insert port only if the original has its own explicit port + res += ":" + a.Port + } + } + if a.Path != "" { + res += a.Path + } + return res +} + // standardizeAddress parses an address string into a structured format with separate // scheme, host, port, and path portions, as well as the original input string. func standardizeAddress(str string) (Address, error) { diff --git a/caddyhttp/httpserver/plugin_test.go b/caddyhttp/httpserver/plugin_test.go index f7b9cfc0..bf922dfc 100644 --- a/caddyhttp/httpserver/plugin_test.go +++ b/caddyhttp/httpserver/plugin_test.go @@ -18,6 +18,10 @@ import ( "strings" "testing" + "sort" + + "fmt" + "github.com/mholt/caddy" "github.com/mholt/caddy/caddyfile" ) @@ -147,7 +151,20 @@ func TestInspectServerBlocksWithCustomDefaultPort(t *testing.T) { if err != nil { t.Fatalf("Didn't expect an error, but got: %v", err) } - addr := ctx.keysToSiteConfigs["localhost"].Addr + localhostKey := "localhost" + item, ok := ctx.keysToSiteConfigs[localhostKey] + if !ok { + availableKeys := make(sort.StringSlice, len(ctx.keysToSiteConfigs)) + i := 0 + for key := range ctx.keysToSiteConfigs { + availableKeys[i] = fmt.Sprintf("'%s'", key) + i++ + } + availableKeys.Sort() + t.Errorf("`%s` not found within registered keys, only these are available: %s", localhostKey, strings.Join(availableKeys, ", ")) + return + } + addr := item.Addr if addr.Port != Port { t.Errorf("Expected the port on the address to be set, but got: %#v", addr) } @@ -184,6 +201,64 @@ func TestInspectServerBlocksCaseInsensitiveKey(t *testing.T) { } } +func TestKeyNormalization(t *testing.T) { + originalCaseSensitivePath := CaseSensitivePath + defer func() { + CaseSensitivePath = originalCaseSensitivePath + }() + CaseSensitivePath = true + + caseSensitiveData := []struct { + orig string + res string + }{ + { + orig: "HTTP://A/ABCDEF", + res: "http://a/ABCDEF", + }, + { + orig: "A/ABCDEF", + res: "a/ABCDEF", + }, + { + orig: "A:2015/Port", + res: "a:2015/Port", + }, + } + for _, item := range caseSensitiveData { + v := normalizedKey(item.orig) + if v != item.res { + t.Errorf("Normalization of `%s` with CaseSensitivePath option set to true must be equal to `%s`, got `%s` instead", item.orig, item.res, v) + } + } + + CaseSensitivePath = false + caseInsensitiveData := []struct { + orig string + res string + }{ + { + orig: "HTTP://A/ABCDEF", + res: "http://a/abcdef", + }, + { + orig: "A/ABCDEF", + res: "a/abcdef", + }, + { + orig: "A:2015/Port", + res: "a:2015/port", + }, + } + for _, item := range caseInsensitiveData { + v := normalizedKey(item.orig) + if v != item.res { + t.Errorf("Normalization of `%s` with CaseSensitivePath option set to false must be equal to `%s`, got `%s` instead", item.orig, item.res, v) + } + } + +} + func TestGetConfig(t *testing.T) { // case insensitivity for key con := caddy.NewTestController("http", "") @@ -201,6 +276,14 @@ func TestGetConfig(t *testing.T) { if cfg == cfg3 { t.Errorf("Expected different configs using when key is different; got %p and %p", cfg, cfg3) } + + con.Key = "foo/foobar" + cfg4 := GetConfig(con) + con.Key = "foo/Foobar" + cfg5 := GetConfig(con) + if cfg4 == cfg5 { + t.Errorf("Expected different cases in path to differentiate keys in general") + } } func TestDirectivesList(t *testing.T) {