Merge branch 'master' into telemetry

# Conflicts:
#	caddy/caddymain/run.go
#	caddyhttp/httpserver/plugin.go
#	caddytls/client.go
This commit is contained in:
Matthew Holt
2018-04-20 00:03:57 -06:00
228 changed files with 10317 additions and 5964 deletions
+2 -2
View File
@@ -100,8 +100,8 @@ func enableAutoHTTPS(configs []*SiteConfig, loadCertificates bool) error {
}
cfg.TLS.Enabled = true
cfg.Addr.Scheme = "https"
if loadCertificates && caddytls.HostQualifies(cfg.Addr.Host) {
_, err := cfg.TLS.CacheManagedCertificate(cfg.Addr.Host)
if loadCertificates && caddytls.HostQualifies(cfg.TLS.Hostname) {
_, err := cfg.TLS.CacheManagedCertificate(cfg.TLS.Hostname)
if err != nil {
return err
}
+12
View File
@@ -44,6 +44,7 @@ type Logger struct {
V4ipMask net.IPMask
V6ipMask net.IPMask
IPMaskExists bool
Exceptions []string
}
// NewTestLogger creates logger suitable for testing purposes
@@ -84,6 +85,17 @@ func (l Logger) MaskIP(ip string) string {
}
// ShouldLog returns true if the path is not exempted from
// being logged (i.e. it is not found in l.Exceptions).
func (l Logger) ShouldLog(path string) bool {
for _, exc := range l.Exceptions {
if Path(path).Matches(exc) {
return false
}
}
return true
}
// Attach binds logger Start and Close functions to
// controller's OnStartup and OnShutdown hooks.
func (l *Logger) Attach(controller *caddy.Controller) {
+76 -7
View File
@@ -15,6 +15,7 @@
package httpserver
import (
"crypto/tls"
"flag"
"fmt"
"log"
@@ -123,15 +124,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 {
@@ -146,7 +149,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) ||
@@ -218,6 +221,13 @@ func (h *httpContext) MakeServers() ([]caddy.Server, error) {
}
}
// Iterate each site configuration and make sure that:
// 1) TLS is disabled for explicitly-HTTP sites (necessary
// when an HTTP address shares a block containing tls)
// 2) if QUIC is enabled, TLS ClientAuth is not, because
// currently, QUIC does not support ClientAuth (TODO:
// revisit this when our QUIC implementation supports it)
// 3) if TLS ClientAuth is used, StrictHostMatching is on
var atLeastOneSiteLooksLikeProduction bool
for _, cfg := range h.siteConfigs {
// see if all the addresses (both sites and
@@ -254,6 +264,17 @@ func (h *httpContext) MakeServers() ([]caddy.Server, error) {
// instead of 443 because it doesn't know about TLS.
cfg.Addr.Port = HTTPSPort
}
if cfg.TLS.ClientAuth != tls.NoClientCert {
if QUIC {
return nil, fmt.Errorf("cannot enable TLS client authentication with QUIC, because QUIC does not yet support it")
}
// this must be enabled so that a client cannot connect
// using SNI for another site on this listener that
// does NOT require ClientAuth, and then send HTTP
// requests with the Host header of this site which DOES
// require client auth, thus bypassing it...
cfg.StrictHostMatching = true
}
}
// we must map (group) each config to a bind address
@@ -287,12 +308,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
}
@@ -396,6 +427,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) {
@@ -523,6 +591,7 @@ var directives = []string{
"startup", // TODO: Deprecate this directive
"shutdown", // TODO: Deprecate this directive
"on",
"supervisor", // github.com/lucaslorentz/caddy-supervisor
"request_id",
"realip", // github.com/captncraig/caddy-realip
"git", // github.com/abiosoft/caddy-git
@@ -538,13 +607,13 @@ var directives = []string{
"ext",
"gzip",
"header",
"geoip", // github.com/kodnaplakal/caddy-geoip
"errors",
"authz", // github.com/casbin/caddy-authz
"filter", // github.com/echocat/caddy-filter
"minify", // github.com/hacdias/caddy-minify
"ipfilter", // github.com/pyed/ipfilter
"ratelimit", // github.com/xuqingfeng/caddy-rate-limit
"search", // github.com/pedronasser/caddy-search
"expires", // github.com/epicagency/caddy-expires
"forwardproxy", // github.com/caddyserver/forwardproxy
"basicauth",
+84 -1
View File
@@ -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) {
+97 -17
View File
@@ -29,6 +29,7 @@ import (
"time"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddytls"
)
// requestReplacer is a strings.Replacer which is used to
@@ -140,6 +141,14 @@ func canLogRequest(r *http.Request) bool {
return false
}
// unescapeBraces finds escaped braces in s and returns
// a string with those braces unescaped.
func unescapeBraces(s string) string {
s = strings.Replace(s, "\\{", "{", -1)
s = strings.Replace(s, "\\}", "}", -1)
return s
}
// Replace performs a replacement of values on s and returns
// the string with the replaced values.
func (r *replacer) Replace(s string) string {
@@ -149,32 +158,59 @@ func (r *replacer) Replace(s string) string {
}
result := ""
Placeholders: // process each placeholder in sequence
for {
idxStart := strings.Index(s, "{")
if idxStart == -1 {
// no placeholder anymore
break
}
idxEnd := strings.Index(s[idxStart:], "}")
if idxEnd == -1 {
// unpaired placeholder
break
}
idxEnd += idxStart
var idxStart, idxEnd int
// get a replacement
placeholder := s[idxStart : idxEnd+1]
idxOffset := 0
for { // find first unescaped opening brace
searchSpace := s[idxOffset:]
idxStart = strings.Index(searchSpace, "{")
if idxStart == -1 {
// no more placeholders
break Placeholders
}
if idxStart == 0 || searchSpace[idxStart-1] != '\\' {
// preceding character is not an escape
idxStart += idxOffset
break
}
// the brace we found was escaped
// search the rest of the string next
idxOffset += idxStart + 1
}
idxOffset = 0
for { // find first unescaped closing brace
searchSpace := s[idxStart+idxOffset:]
idxEnd = strings.Index(searchSpace, "}")
if idxEnd == -1 {
// unpaired placeholder
break Placeholders
}
if idxEnd == 0 || searchSpace[idxEnd-1] != '\\' {
// preceding character is not an escape
idxEnd += idxOffset + idxStart
break
}
// the brace we found was escaped
// search the rest of the string next
idxOffset += idxEnd + 1
}
// get a replacement for the unescaped placeholder
placeholder := unescapeBraces(s[idxStart : idxEnd+1])
replacement := r.getSubstitution(placeholder)
// append prefix + replacement
result += s[:idxStart] + replacement
// append unescaped prefix + replacement
result += strings.TrimPrefix(unescapeBraces(s[:idxStart]), "\\") + replacement
// strip out scanned parts
s = s[idxEnd+1:]
}
// append unscanned parts
return result + s
return result + unescapeBraces(s)
}
func roundDuration(d time.Duration) time.Duration {
@@ -224,6 +260,16 @@ func (r *replacer) getSubstitution(key string) string {
}
}
}
// search response headers then
if r.responseRecorder != nil && key[1] == '<' {
want := key[2 : len(key)-1]
for key, values := range r.responseRecorder.Header() {
// Header placeholders (case-insensitive)
if strings.EqualFold(key, want) {
return strings.Join(values, ",")
}
}
}
// next check for cookies
if key[1] == '~' {
name := key[2 : len(key)-1]
@@ -365,12 +411,46 @@ func (r *replacer) getSubstitution(key string) string {
}
elapsedDuration := time.Since(r.responseRecorder.start)
return strconv.FormatInt(convertToMilliseconds(elapsedDuration), 10)
case "{tls_protocol}":
if r.request.TLS != nil {
for k, v := range caddytls.SupportedProtocols {
if v == r.request.TLS.Version {
return k
}
}
return "tls" // this should never happen, but guard in case
}
return r.emptyValue // because not using a secure channel
case "{tls_cipher}":
if r.request.TLS != nil {
for k, v := range caddytls.SupportedCiphersMap {
if v == r.request.TLS.CipherSuite {
return k
}
}
return "UNKNOWN" // this should never happen, but guard in case
}
return r.emptyValue
default:
// {labelN}
if strings.HasPrefix(key, "{label") {
nStr := key[6 : len(key)-1] // get the integer N in "{labelN}"
n, err := strconv.Atoi(nStr)
if err != nil || n < 1 {
return r.emptyValue
}
labels := strings.Split(r.request.Host, ".")
if n > len(labels) {
return r.emptyValue
}
return labels[n-1]
}
}
return r.emptyValue
}
//convertToMilliseconds returns the number of milliseconds in the given duration
// convertToMilliseconds returns the number of milliseconds in the given duration
func convertToMilliseconds(d time.Duration) int64 {
return d.Nanoseconds() / 1e6
}
+113 -3
View File
@@ -53,7 +53,7 @@ func TestReplace(t *testing.T) {
recordRequest := NewResponseRecorder(w)
reader := strings.NewReader(`{"username": "dennis"}`)
request, err := http.NewRequest("POST", "http://localhost/?foo=bar", reader)
request, err := http.NewRequest("POST", "http://localhost.local/?foo=bar", reader)
if err != nil {
t.Fatalf("Failed to make request: %v", err)
}
@@ -67,6 +67,9 @@ func TestReplace(t *testing.T) {
request.Header.Set("CustomAdd", "caddy")
request.Header.Set("Cookie", "foo=bar; taste=delicious")
// add some respons headers
recordRequest.Header().Set("Custom", "CustomResponseHeader")
hostname, err := os.Hostname()
if err != nil {
t.Fatalf("Failed to determine hostname: %v", err)
@@ -84,7 +87,7 @@ func TestReplace(t *testing.T) {
expect string
}{
{"This hostname is {hostname}", "This hostname is " + hostname},
{"This host is {host}.", "This host is localhost."},
{"This host is {host}.", "This host is localhost.local."},
{"This request method is {method}.", "This request method is POST."},
{"The response status is {status}.", "The response status is 200."},
{"{when}", "02/Jan/2006:15:04:05 +0000"},
@@ -92,10 +95,13 @@ func TestReplace(t *testing.T) {
{"{when_unix}", "1136214252"},
{"The Custom header is {>Custom}.", "The Custom header is foobarbaz."},
{"The CustomAdd header is {>CustomAdd}.", "The CustomAdd header is caddy."},
{"The request is {request}.", "The request is POST /?foo=bar HTTP/1.1\\r\\nHost: localhost\\r\\n" +
{"The Custom response header is {<Custom}.", "The Custom response header is CustomResponseHeader."},
{"Bad {>Custom placeholder", "Bad {>Custom placeholder"},
{"The request is {request}.", "The request is POST /?foo=bar HTTP/1.1\\r\\nHost: localhost.local\\r\\n" +
"Cookie: foo=bar; taste=delicious\\r\\nCustom: foobarbaz\\r\\nCustomadd: caddy\\r\\n" +
"Shorterval: 1\\r\\n\\r\\n."},
{"The cUsToM header is {>cUsToM}...", "The cUsToM header is foobarbaz..."},
{"The cUsToM response header is {<CuSTom}.", "The cUsToM response header is CustomResponseHeader."},
{"The Non-Existent header is {>Non-Existent}.", "The Non-Existent header is -."},
{"Bad {host placeholder...", "Bad {host placeholder..."},
{"Bad {>Custom placeholder", "Bad {>Custom placeholder"},
@@ -106,6 +112,9 @@ func TestReplace(t *testing.T) {
{"Query string is {query}", "Query string is foo=bar"},
{"Query string value for foo is {?foo}", "Query string value for foo is bar"},
{"Missing query string argument is {?missing}", "Missing query string argument is "},
{"{label1} {label2} {label3} {label4}", "localhost local - -"},
{"Label with missing number is {label} or {labelQQ}", "Label with missing number is - or -"},
{"\\{ 'hostname': '{hostname}' \\}", "{ 'hostname': '" + hostname + "' }"},
}
for _, c := range testCases {
@@ -138,6 +147,107 @@ func TestReplace(t *testing.T) {
}
}
func BenchmarkReplace(b *testing.B) {
w := httptest.NewRecorder()
recordRequest := NewResponseRecorder(w)
reader := strings.NewReader(`{"username": "dennis"}`)
request, err := http.NewRequest("POST", "http://localhost/?foo=bar", reader)
if err != nil {
b.Fatalf("Failed to make request: %v", err)
}
ctx := context.WithValue(request.Context(), OriginalURLCtxKey, *request.URL)
request = request.WithContext(ctx)
request.Header.Set("Custom", "foobarbaz")
request.Header.Set("ShorterVal", "1")
repl := NewReplacer(request, recordRequest, "-")
// add some headers after creating replacer
request.Header.Set("CustomAdd", "caddy")
request.Header.Set("Cookie", "foo=bar; taste=delicious")
// add some respons headers
recordRequest.Header().Set("Custom", "CustomResponseHeader")
now = func() time.Time {
return time.Date(2006, 1, 2, 15, 4, 5, 02, time.FixedZone("hardcoded", -7))
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
repl.Replace("This hostname is {hostname}")
}
}
func BenchmarkReplaceEscaped(b *testing.B) {
w := httptest.NewRecorder()
recordRequest := NewResponseRecorder(w)
reader := strings.NewReader(`{"username": "dennis"}`)
request, err := http.NewRequest("POST", "http://localhost/?foo=bar", reader)
if err != nil {
b.Fatalf("Failed to make request: %v", err)
}
ctx := context.WithValue(request.Context(), OriginalURLCtxKey, *request.URL)
request = request.WithContext(ctx)
request.Header.Set("Custom", "foobarbaz")
request.Header.Set("ShorterVal", "1")
repl := NewReplacer(request, recordRequest, "-")
// add some headers after creating replacer
request.Header.Set("CustomAdd", "caddy")
request.Header.Set("Cookie", "foo=bar; taste=delicious")
// add some respons headers
recordRequest.Header().Set("Custom", "CustomResponseHeader")
now = func() time.Time {
return time.Date(2006, 1, 2, 15, 4, 5, 02, time.FixedZone("hardcoded", -7))
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
repl.Replace("\\{ 'hostname': '{hostname}' \\}")
}
}
func TestResponseRecorderNil(t *testing.T) {
reader := strings.NewReader(`{"username": "dennis"}`)
request, err := http.NewRequest("POST", "http://localhost/?foo=bar", reader)
if err != nil {
t.Fatalf("Failed to make request: %v", err)
}
request.Header.Set("Custom", "foobarbaz")
repl := NewReplacer(request, nil, "-")
// add some headers after creating replacer
request.Header.Set("CustomAdd", "caddy")
request.Header.Set("Cookie", "foo=bar; taste=delicious")
old := now
now = func() time.Time {
return time.Date(2006, 1, 2, 15, 4, 5, 02, time.FixedZone("hardcoded", -7))
}
defer func() {
now = old
}()
testCases := []struct {
template string
expect string
}{
{"The Custom response header is {<Custom}.", "The Custom response header is -."},
}
for _, c := range testCases {
if expected, actual := c.expect, repl.Replace(c.template); expected != actual {
t.Errorf("for template '%s', expected '%s', got '%s'", c.template, expected, actual)
}
}
}
func TestSet(t *testing.T) {
w := httptest.NewRecorder()
recordRequest := NewResponseRecorder(w)
+28 -5
View File
@@ -320,6 +320,9 @@ func (s *Server) Serve(ln net.Listener) error {
}
err := s.Server.Serve(ln)
if err == http.ErrServerClosed {
err = nil // not an error worth reporting since closing a server is intentional
}
if s.quicServer != nil {
s.quicServer.Close()
}
@@ -421,19 +424,39 @@ func (s *Server) serveHTTP(w http.ResponseWriter, r *http.Request) (int, error)
r.URL = trimPathPrefix(r.URL, pathPrefix)
}
// enforce strict host matching, which ensures that the SNI
// value (if any), matches the Host header; essential for
// sites that rely on TLS ClientAuth sharing a port with
// sites that do not - if mismatched, close the connection
if vhost.StrictHostMatching && r.TLS != nil &&
strings.ToLower(r.TLS.ServerName) != strings.ToLower(hostname) {
r.Close = true
log.Printf("[ERROR] %s - strict host matching: SNI (%s) and HTTP Host (%s) values differ",
vhost.Addr, r.TLS.ServerName, hostname)
return http.StatusForbidden, nil
}
return vhost.middlewareChain.ServeHTTP(w, r)
}
func trimPathPrefix(u *url.URL, prefix string) *url.URL {
// We need to use URL.EscapedPath() when trimming the pathPrefix as
// URL.Path is ambiguous about / or %2f - see docs. See #1927
trimmed := strings.TrimPrefix(u.EscapedPath(), prefix)
if !strings.HasPrefix(trimmed, "/") {
trimmed = "/" + trimmed
trimmedPath := strings.TrimPrefix(u.EscapedPath(), prefix)
if !strings.HasPrefix(trimmedPath, "/") {
trimmedPath = "/" + trimmedPath
}
trimmedURL, err := url.Parse(trimmed)
// After trimming path reconstruct uri string with Query before parsing
trimmedURI := trimmedPath
if u.RawQuery != "" || u.ForceQuery == true {
trimmedURI = trimmedPath + "?" + u.RawQuery
}
if u.Fragment != "" {
trimmedURI = trimmedURI + "#" + u.Fragment
}
trimmedURL, err := url.Parse(trimmedURI)
if err != nil {
log.Printf("[ERROR] Unable to parse trimmed URL %s: %v", trimmed, err)
log.Printf("[ERROR] Unable to parse trimmed URL %s: %v", trimmedURI, err)
return u
}
return trimmedURL
+37 -17
View File
@@ -129,88 +129,108 @@ func TestMakeHTTPServerWithTimeouts(t *testing.T) {
func TestTrimPathPrefix(t *testing.T) {
for i, pt := range []struct {
path string
url string
prefix string
expected string
shouldFail bool
}{
{
path: "/my/path",
url: "/my/path",
prefix: "/my",
expected: "/path",
shouldFail: false,
},
{
path: "/my/%2f/path",
url: "/my/%2f/path",
prefix: "/my",
expected: "/%2f/path",
shouldFail: false,
},
{
path: "/my/path",
url: "/my/path",
prefix: "/my/",
expected: "/path",
shouldFail: false,
},
{
path: "/my///path",
url: "/my///path",
prefix: "/my",
expected: "/path",
shouldFail: true,
},
{
path: "/my///path",
url: "/my///path",
prefix: "/my",
expected: "///path",
shouldFail: false,
},
{
path: "/my/path///slash",
url: "/my/path///slash",
prefix: "/my",
expected: "/path///slash",
shouldFail: false,
},
{
path: "/my/%2f/path/%2f",
url: "/my/%2f/path/%2f",
prefix: "/my",
expected: "/%2f/path/%2f",
shouldFail: false,
}, {
path: "/my/%20/path",
url: "/my/%20/path",
prefix: "/my",
expected: "/%20/path",
shouldFail: false,
}, {
path: "/path",
url: "/path",
prefix: "",
expected: "/path",
shouldFail: false,
}, {
path: "/path/my/",
url: "/path/my/",
prefix: "/my",
expected: "/path/my/",
shouldFail: false,
}, {
path: "",
url: "",
prefix: "/my",
expected: "/",
shouldFail: false,
}, {
path: "/apath",
url: "/apath",
prefix: "",
expected: "/apath",
shouldFail: false,
}, {
url: "/my/path/page.php?akey=value",
prefix: "/my",
expected: "/path/page.php?akey=value",
shouldFail: false,
}, {
url: "/my/path/page?key=value#fragment",
prefix: "/my",
expected: "/path/page?key=value#fragment",
shouldFail: false,
}, {
url: "/my/path/page#fragment",
prefix: "/my",
expected: "/path/page#fragment",
shouldFail: false,
}, {
url: "/my/apath?",
prefix: "/my",
expected: "/apath?",
shouldFail: false,
},
} {
u, _ := url.Parse(pt.path)
if got, want := trimPathPrefix(u, pt.prefix), pt.expected; got.EscapedPath() != want {
u, _ := url.Parse(pt.url)
if got, want := trimPathPrefix(u, pt.prefix), pt.expected; got.String() != want {
if !pt.shouldFail {
t.Errorf("Test %d: Expected='%s', but was '%s' ", i, want, got.EscapedPath())
t.Errorf("Test %d: Expected='%s', but was '%s' ", i, want, got.String())
}
} else if pt.shouldFail {
t.Errorf("SHOULDFAIL Test %d: Expected='%s', and was '%s' but should fail", i, want, got.EscapedPath())
t.Errorf("SHOULDFAIL Test %d: Expected='%s', and was '%s' but should fail", i, want, got.String())
}
}
}
+10
View File
@@ -36,6 +36,16 @@ type SiteConfig struct {
// TLS configuration
TLS *caddytls.Config
// If true, the Host header in the HTTP request must
// match the SNI value in the TLS handshake (if any).
// This should be enabled whenever a site relies on
// TLS client authentication, for example; or any time
// you want to enforce that THIS site's TLS config
// is used and not the TLS config of any other site
// on the same listener. TODO: Check how relevant this
// is with TLS 1.3.
StrictHostMatching bool
// Uncompiled middleware stack
middleware []Middleware
+1 -1
View File
@@ -277,7 +277,7 @@ func TestHostname(t *testing.T) {
// // Test 3 - ipv6 without port and brackets
// {"2001:4860:4860::8888", "google-public-dns-a.google.com."},
// Test 4 - no hostname available
{"1.1.1.1", "1.1.1.1"},
{"0.0.0.0", "0.0.0.0"},
}
for i, test := range tests {