mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-10-31 02:27:19 -04:00 
			
		
		
		
	httpserver: CaseSensitivePath applied to paths in site keys (#2034)
* different cases in path make different keys * Respect CaseSensitivePath variable when matching paths
This commit is contained in:
		
							parent
							
								
									f1eaae9b0d
								
							
						
					
					
						commit
						a8dfa9f0b7
					
				| @ -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) { | ||||
|  | ||||
| @ -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) { | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user