Repository: labstack/echo
Pull request #1330 (2019-05-07 15:45:46)

findChildByKind executed on nil node crashes an app


Patch:
diff --git a/context.go b/context.go
index 98cf50b..75f59fd 100644
--- a/context.go
+++ b/context.go
@@ -234,7 +234,7 @@ func (c *context) IsTLS() bool {
 
 func (c *context) IsWebSocket() bool {
 	upgrade := c.request.Header.Get(HeaderUpgrade)
-	return strings.ToLower(upgrade) == "websocket"
+	return upgrade == "websocket" || upgrade == "Websocket"
 }
 
 func (c *context) Scheme() string {
diff --git a/echo.go b/echo.go
index 39c30d2..409dfad 100644
--- a/echo.go
+++ b/echo.go
@@ -57,21 +57,18 @@ import (
 
 	"github.com/labstack/gommon/color"
 	"github.com/labstack/gommon/log"
-	"golang.org/x/crypto/acme"
 	"golang.org/x/crypto/acme/autocert"
 )
 
 type (
 	// Echo is the top-level framework instance.
 	Echo struct {
-		common
 		StdLogger        *stdLog.Logger
 		colorer          *color.Color
 		premiddleware    []MiddlewareFunc
 		middleware       []MiddlewareFunc
 		maxParam         *int
 		router           *Router
-		routers          map[string]*Router
 		notFoundHandler  HandlerFunc
 		pool             sync.Pool
 		Server           *http.Server
@@ -126,8 +123,10 @@ type (
 	// Map defines a generic map of type `map[string]interface{}`.
 	Map map[string]interface{}
 
-	// Common struct for Echo & Group.
-	common struct{}
+	// i is the interface for Echo and Group.
+	i interface {
+		GET(string, HandlerFunc, ...MiddlewareFunc) *Route
+	}
 )
 
 // HTTP methods
@@ -224,7 +223,7 @@ const (
 
 const (
 	// Version of Echo
-	Version = "4.1.5"
+	Version = "4.0.0"
 	website = "https://echo.labstack.com"
 	// http://patorjk.com/software/taag/#p=display&f=Small%20Slant&t=Echo
 	banner = `
@@ -308,7 +307,6 @@ func New() (e *Echo) {
 		return e.NewContext(nil, nil)
 	}
 	e.router = NewRouter(e)
-	e.routers = map[string]*Router{}
 	return
 }
 
@@ -324,16 +322,11 @@ func (e *Echo) NewContext(r *http.Request, w http.ResponseWriter) Context {
 	}
 }
 
-// Router returns the default router.
+// Router returns router.
 func (e *Echo) Router() *Router {
 	return e.router
 }
 
-// Routers returns the map of host => router.
-func (e *Echo) Routers() map[string]*Router {
-	return e.routers
-}
-
 // DefaultHTTPErrorHandler is the default HTTP error handler. It sends a JSON response
 // with status code.
 func (e *Echo) DefaultHTTPErrorHandler(err error, c Context) {
@@ -460,10 +453,10 @@ func (e *Echo) Static(prefix, root string) *Route {
 	if root == "" {
 		root = "." // For security we want to restrict to CWD.
 	}
-	return e.static(prefix, root, e.GET)
+	return static(e, prefix, root)
 }
 
-func (common) static(prefix, root string, get func(string, HandlerFunc, ...MiddlewareFunc) *Route) *Route {
+func static(i i, prefix, root string) *Route {
 	h := func(c Context) error {
 		p, err := url.PathUnescape(c.Param("*"))
 		if err != nil {
@@ -472,28 +465,26 @@ func (common) static(prefix, root string, get func(string, HandlerFunc, ...Middl
 		name := filepath.Join(root, path.Clean("/"+p)) // "/"+ for security
 		return c.File(name)
 	}
+	i.GET(prefix, h)
 	if prefix == "/" {
-		return get(prefix+"*", h)
+		return i.GET(prefix+"*", h)
 	}
-	return get(prefix+"/*", h)
-}
 
-func (common) file(path, file string, get func(string, HandlerFunc, ...MiddlewareFunc) *Route,
-	m ...MiddlewareFunc) *Route {
-	return get(path, func(c Context) error {
-		return c.File(file)
-	}, m...)
+	return i.GET(prefix+"/*", h)
 }
 
 // File registers a new route with path to serve a static file with optional route-level middleware.
 func (e *Echo) File(path, file string, m ...MiddlewareFunc) *Route {
-	return e.file(path, file, e.GET, m...)
+	return e.GET(path, func(c Context) error {
+		return c.File(file)
+	}, m...)
 }
 
-func (e *Echo) add(host, method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) *Route {
+// Add registers a new route for an HTTP method and path with matching handler
+// in the router with optional route-level middleware.
+func (e *Echo) Add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) *Route {
 	name := handlerName(handler)
-	router := e.findRouter(host)
-	router.Add(method, path, func(c Context) error {
+	e.router.Add(method, path, func(c Context) error {
 		h := handler
 		// Chain middleware
 		for i := len(middleware) - 1; i >= 0; i-- {
@@ -510,20 +501,6 @@ func (e *Echo) add(host, method, path string, handler HandlerFunc, middleware ..
 	return r
 }
 
-// Add registers a new route for an HTTP method and path with matching handler
-// in the router with optional route-level middleware.
-func (e *Echo) Add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) *Route {
-	return e.add("", method, path, handler, middleware...)
-}
-
-// Host creates a new router group for the provided host and optional host-level middleware.
-func (e *Echo) Host(name string, m ...MiddlewareFunc) (g *Group) {
-	e.routers[name] = NewRouter(e)
-	g = &Group{host: name, echo: e}
-	g.Use(m...)
-	return
-}
-
 // Group creates a new router group with prefix and optional group-level middleware.
 func (e *Echo) Group(prefix string, m ...MiddlewareFunc) (g *Group) {
 	g = &Group{prefix: prefix, echo: e}
@@ -596,17 +573,23 @@ func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	h := NotFoundHandler
 
 	if e.premiddleware == nil {
-		e.findRouter(r.Host).Find(r.Method, getPath(r), c)
+		e.router.Find(r.Method, getPath(r), c)
 		h = c.Handler()
-		h = applyMiddleware(h, e.middleware...)
+		for i := len(e.middleware) - 1; i >= 0; i-- {
+			h = e.middleware[i](h)
+		}
 	} else {
 		h = func(c Context) error {
-			e.findRouter(r.Host).Find(r.Method, getPath(r), c)
+			e.router.Find(r.Method, getPath(r), c)
 			h := c.Handler()
-			h = applyMiddleware(h, e.middleware...)
+			for i := len(e.middleware) - 1; i >= 0; i-- {
+				h = e.middleware[i](h)
+			}
 			return h(c)
 		}
-		h = applyMiddleware(h, e.premiddleware...)
+		for i := len(e.premiddleware) - 1; i >= 0; i-- {
+			h = e.premiddleware[i](h)
+		}
 	}
 
 	// Execute chain
@@ -664,7 +647,6 @@ func (e *Echo) StartAutoTLS(address string) error {
 	s := e.TLSServer
 	s.TLSConfig = new(tls.Config)
 	s.TLSConfig.GetCertificate = e.AutoTLSManager.GetCertificate
-	s.TLSConfig.NextProtos = append(s.TLSConfig.NextProtos, acme.ALPNProto)
 	return e.startTLS(address)
 }
 
@@ -783,15 +765,6 @@ func getPath(r *http.Request) string {
 	return path
 }
 
-func (e *Echo) findRouter(host string) *Router {
-	if len(e.routers) > 0 {
-		if r, ok := e.routers[host]; ok {
-			return r
-		}
-	}
-	return e.router
-}
-
 func handlerName(h HandlerFunc) string {
 	t := reflect.ValueOf(h).Type()
 	if t.Kind() == reflect.Func {
@@ -831,10 +804,3 @@ func newListener(address string) (*tcpKeepAliveListener, error) {
 	}
 	return &tcpKeepAliveListener{l.(*net.TCPListener)}, nil
 }
-
-func applyMiddleware(h HandlerFunc, middleware ...MiddlewareFunc) HandlerFunc {
-	for i := len(middleware) - 1; i >= 0; i-- {
-		h = middleware[i](h)
-	}
-	return h
-}
diff --git a/go.mod b/go.mod
index 9a2e531..2853cba 100644
--- a/go.mod
+++ b/go.mod
@@ -1,13 +1,13 @@
 module github.com/labstack/echo/v4
 
-go 1.12
-
 require (
 	github.com/dgrijalva/jwt-go v3.2.0+incompatible
 	github.com/labstack/gommon v0.2.8
-	github.com/mattn/go-colorable v0.1.1 // indirect
-	github.com/mattn/go-isatty v0.0.7 // indirect
+	github.com/mattn/go-colorable v0.0.9 // indirect
+	github.com/mattn/go-isatty v0.0.4 // indirect
 	github.com/stretchr/testify v1.3.0
-	github.com/valyala/fasttemplate v1.0.1
-	golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734
+	github.com/valyala/bytebufferpool v1.0.0 // indirect
+	github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4
+	golang.org/x/crypto v0.0.0-20190130090550-b01c7a725664
+	golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc // indirect
 )
diff --git a/go.sum b/go.sum
index 4a46384..e698193 100644
--- a/go.sum
+++ b/go.sum
@@ -4,11 +4,10 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumC
 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 github.com/labstack/gommon v0.2.8 h1:JvRqmeZcfrHC5u6uVleB4NxxNbzx6gpbJiQknDbKQu0=
 github.com/labstack/gommon v0.2.8/go.mod h1:/tj9csK2iPSBvn+3NLM9e52usepMtrd5ilFYA+wQNJ4=
-github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg=
-github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
-github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
-github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc=
-github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
+github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
+github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
+github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -16,16 +15,9 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
 github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
-github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8=
-github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 h1:p/H982KKEjUnLJkM3tt/LemDnOc1GiZL5FCVlORJ5zo=
-golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
-golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4 h1:gKMu1Bf6QINDnvyZuTaACm9ofY+PRh+5vFz4oxBZeF8=
+github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4/go.mod h1:50wTf68f99/Zt14pr046Tgt3Lp2vLyFZKzbFXTOabXw=
+golang.org/x/crypto v0.0.0-20190130090550-b01c7a725664 h1:YbZJ76lQ1BqNhVe7dKTSB67wDrc2VPRR75IyGyyPDX8=
+golang.org/x/crypto v0.0.0-20190130090550-b01c7a725664/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc h1:WiYx1rIFmx8c0mXAFtv5D/mHyKe1+jmuP7PViuwqwuQ=
+golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
diff --git a/group.go b/group.go
index 5d95825..3e3732b 100644
--- a/group.go
+++ b/group.go
@@ -2,6 +2,7 @@ package echo
 
 import (
 	"net/http"
+	"path"
 )
 
 type (
@@ -9,8 +10,6 @@ type (
 	// routes that share a common middleware or functionality that should be separate
 	// from the parent echo instance while still inheriting from it.
 	Group struct {
-		common
-		host       string
 		prefix     string
 		middleware []MiddlewareFunc
 		echo       *Echo
@@ -20,13 +19,13 @@ type (
 // Use implements `Echo#Use()` for sub-routes within the Group.
 func (g *Group) Use(middleware ...MiddlewareFunc) {
 	g.middleware = append(g.middleware, middleware...)
-	if len(g.middleware) == 0 {
-		return
-	}
 	// Allow all requests to reach the group as they might get dropped if router
 	// doesn't find a match, making none of the group middleware process.
-	g.Any("", NotFoundHandler)
-	g.Any("/*", NotFoundHandler)
+	for _, p := range []string{"", "/*"} {
+		g.echo.Any(path.Clean(g.prefix+p), func(c Context) error {
+			return NotFoundHandler(c)
+		}, g.middleware...)
+	}
 }
 
 // CONNECT implements `Echo#CONNECT()` for sub-routes within the Group.
@@ -93,23 +92,21 @@ func (g *Group) Match(methods []string, path string, handler HandlerFunc, middle
 }
 
 // Group creates a new sub-group with prefix and optional sub-group-level middleware.
-func (g *Group) Group(prefix string, middleware ...MiddlewareFunc) (sg *Group) {
+func (g *Group) Group(prefix string, middleware ...MiddlewareFunc) *Group {
 	m := make([]MiddlewareFunc, 0, len(g.middleware)+len(middleware))
 	m = append(m, g.middleware...)
 	m = append(m, middleware...)
-	sg = g.echo.Group(g.prefix+prefix, m...)
-	sg.host = g.host
-	return
+	return g.echo.Group(g.prefix+prefix, m...)
 }
 
 // Static implements `Echo#Static()` for sub-routes within the Group.
 func (g *Group) Static(prefix, root string) {
-	g.static(prefix, root, g.GET)
+	static(g, prefix, root)
 }
 
 // File implements `Echo#File()` for sub-routes within the Group.
 func (g *Group) File(path, file string) {
-	g.file(g.prefix+path, file, g.GET)
+	g.echo.File(g.prefix+path, file)
 }
 
 // Add implements `Echo#Add()` for sub-routes within the Group.
@@ -120,5 +117,5 @@ func (g *Group) Add(method, path string, handler HandlerFunc, middleware ...Midd
 	m := make([]MiddlewareFunc, 0, len(g.middleware)+len(middleware))
 	m = append(m, g.middleware...)
 	m = append(m, middleware...)
-	return g.echo.add(g.host, method, g.prefix+path, handler, m...)
+	return g.echo.Add(method, g.prefix+path, handler, m...)
 }
diff --git a/middleware/compress.go b/middleware/compress.go
index 89da16e..1905206 100644
--- a/middleware/compress.go
+++ b/middleware/compress.go
@@ -111,9 +111,6 @@ func (w *gzipResponseWriter) Write(b []byte) (int, error) {
 
 func (w *gzipResponseWriter) Flush() {
 	w.Writer.(*gzip.Writer).Flush()
-	if flusher, ok := w.ResponseWriter.(http.Flusher); ok {
-		flusher.Flush()
-	}
 }
 
 func (w *gzipResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
diff --git a/middleware/cors.go b/middleware/cors.go
index 5dfe31f..c61c712 100644
--- a/middleware/cors.go
+++ b/middleware/cors.go
@@ -102,10 +102,6 @@ func CORSWithConfig(config CORSConfig) echo.MiddlewareFunc {
 					allowOrigin = o
 					break
 				}
-				if matchSubdomain(origin, o) {
-					allowOrigin = origin
-					break
-				}
 			}
 
 			// Simple request
diff --git a/middleware/logger.go b/middleware/logger.go
index 6fd59ef..b2e4834 100644
--- a/middleware/logger.go
+++ b/middleware/logger.go
@@ -2,7 +2,6 @@ package middleware
 
 import (
 	"bytes"
-	"encoding/json"
 	"io"
 	"os"
 	"strconv"
@@ -21,7 +20,7 @@ type (
 		// Skipper defines a function to skip middleware.
 		Skipper Skipper
 
-		// Tags to construct the logger format.
+		// Tags to constructed the logger format.
 		//
 		// - time_unix
 		// - time_unix_nano
@@ -176,10 +175,7 @@ func LoggerWithConfig(config LoggerConfig) echo.MiddlewareFunc {
 					return buf.WriteString(s)
 				case "error":
 					if err != nil {
-						// Error may contain invalid JSON e.g. `"`
-						b, _ := json.Marshal(err.Error())
-						b = b[1 : len(b)-1]
-						return buf.Write(b)
+						return buf.WriteString(err.Error())
 					}
 				case "latency":
 					l := stop.Sub(start)
diff --git a/middleware/proxy.go b/middleware/proxy.go
index 532346d..9d67f44 100644
--- a/middleware/proxy.go
+++ b/middleware/proxy.go
@@ -200,7 +200,7 @@ func Proxy(balancer ProxyBalancer) echo.MiddlewareFunc {
 func ProxyWithConfig(config ProxyConfig) echo.MiddlewareFunc {
 	// Defaults
 	if config.Skipper == nil {
-		config.Skipper = DefaultProxyConfig.Skipper
+		config.Skipper = DefaultLoggerConfig.Skipper
 	}
 	if config.Balancer == nil {
 		panic("echo: proxy middleware requires balancer")
diff --git a/middleware/secure.go b/middleware/secure.go
index 77a1487..eded54f 100644
--- a/middleware/secure.go
+++ b/middleware/secure.go
@@ -60,12 +60,6 @@ type (
 		// have occurred instead of blocking the resource.
 		// Optional. Default value false.
 		CSPReportOnly bool `yaml:"csp_report_only"`
-
-		// HSTSPreloadEnabled will add the preload tag in the `Strict Transport Security`
-		// header, which enables the domain to be included in the HSTS preload list
-		// maintained by Chrome (and used by Firefox and Safari): https://hstspreload.org/
-		// Optional.  Default value false.
-		HSTSPreloadEnabled bool `yaml:"hsts_preload_enabled"`
 	}
 )
 
@@ -76,7 +70,6 @@ var (
 		XSSProtection:      "1; mode=block",
 		ContentTypeNosniff: "nosniff",
 		XFrameOptions:      "SAMEORIGIN",
-		HSTSPreloadEnabled: false,
 	}
 )
 
@@ -119,9 +112,6 @@ func SecureWithConfig(config SecureConfig) echo.MiddlewareFunc {
 				if !config.HSTSExcludeSubdomains {
 					subdomains = "; includeSubdomains"
 				}
-				if config.HSTSPreloadEnabled {
-					subdomains = fmt.Sprintf("%s; preload", subdomains)
-				}
 				res.Header().Set(echo.HeaderStrictTransportSecurity, fmt.Sprintf("max-age=%d%s", config.HSTSMaxAge, subdomains))
 			}
 			if config.ContentSecurityPolicy != "" {
diff --git a/middleware/util.go b/middleware/util.go
deleted file mode 100644
index ab951a0..0000000
--- a/middleware/util.go
+++ /dev/null
@@ -1,54 +0,0 @@
-package middleware
-
-import (
-	"strings"
-)
-
-func matchScheme(domain, pattern string) bool {
-	didx := strings.Index(domain, ":")
-	pidx := strings.Index(pattern, ":")
-	return didx != -1 && pidx != -1 && domain[:didx] == pattern[:pidx]
-}
-
-// matchSubdomain compares authority with wildcard
-func matchSubdomain(domain, pattern string) bool {
-	if !matchScheme(domain, pattern) {
-		return false
-	}
-	didx := strings.Index(domain, "://")
-	pidx := strings.Index(pattern, "://")
-	if didx == -1 || pidx == -1 {
-		return false
-	}
-	domAuth := domain[didx+3:]
-	// to avoid long loop by invalid long domain
-	if len(domAuth) > 253 {
-		return false
-	}
-	patAuth := pattern[pidx+3:]
-
-	domComp := strings.Split(domAuth, ".")
-	patComp := strings.Split(patAuth, ".")
-	for i := len(domComp)/2 - 1; i >= 0; i-- {
-		opp := len(domComp) - 1 - i
-		domComp[i], domComp[opp] = domComp[opp], domComp[i]
-	}
-	for i := len(patComp)/2 - 1; i >= 0; i-- {
-		opp := len(patComp) - 1 - i
-		patComp[i], patComp[opp] = patComp[opp], patComp[i]
-	}
-
-	for i, v := range domComp {
-		if len(patComp) <= i {
-			return false
-		}
-		p := patComp[i]
-		if p == "*" {
-			return true
-		}
-		if p != v {
-			return false
-		}
-	}
-	return false
-}
diff --git a/router.go b/router.go
index 85e316a..aae99a3 100644
--- a/router.go
+++ b/router.go
@@ -57,7 +57,7 @@ func NewRouter(e *Echo) *Router {
 func (r *Router) Add(method, path string, h HandlerFunc) {
 	// Validate path
 	if path == "" {
-		path = "/"
+		panic("echo: path cannot be empty")
 	}
 	if path[0] != '/' {
 		path = "/" + path
@@ -79,13 +79,14 @@ func (r *Router) Add(method, path string, h HandlerFunc) {
 
 			if i == l {
 				r.insert(method, path[:i], h, pkind, ppath, pnames)
-			} else {
-				r.insert(method, path[:i], nil, pkind, "", nil)
+				return
 			}
+			r.insert(method, path[:i], nil, pkind, "", nil)
 		} else if path[i] == '*' {
 			r.insert(method, path[:i], nil, skind, "", nil)
 			pnames = append(pnames, "*")
 			r.insert(method, path[:i+1], h, akind, ppath, pnames)
+			return
 		}
 	}
 
@@ -217,6 +218,9 @@ func (n *node) findChildWithLabel(l byte) *node {
 }
 
 func (n *node) findChildByKind(t kind) *node {
+	if n == nil {
+		return nil
+	}
 	for _, c := range n.children {
 		if c.kind == t {
 			return c


Test patch:
diff --git a/middleware/compress_test.go b/middleware/compress_test.go
index ac5b6c3..dc7d78c 100644
--- a/middleware/compress_test.go
+++ b/middleware/compress_test.go
@@ -3,7 +3,6 @@ package middleware
 import (
 	"bytes"
 	"compress/gzip"
-	"io"
 	"io/ioutil"
 	"net/http"
 	"net/http/httptest"
@@ -45,49 +44,6 @@ func TestGzip(t *testing.T) {
 		buf.ReadFrom(r)
 		assert.Equal("test", buf.String())
 	}
-
-	chunkBuf := make([]byte, 5)
-
-	// Gzip chunked
-	req = httptest.NewRequest(http.MethodGet, "/", nil)
-	req.Header.Set(echo.HeaderAcceptEncoding, gzipScheme)
-	rec = httptest.NewRecorder()
-
-	c = e.NewContext(req, rec)
-	Gzip()(func(c echo.Context) error {
-		c.Response().Header().Set("Content-Type", "text/event-stream")
-		c.Response().Header().Set("Transfer-Encoding", "chunked")
-
-		// Write and flush the first part of the data
-		c.Response().Write([]byte("test\n"))
-		c.Response().Flush()
-
-		// Read the first part of the data
-		assert.True(rec.Flushed)
-		assert.Equal(gzipScheme, rec.Header().Get(echo.HeaderContentEncoding))
-		r.Reset(rec.Body)
-
-		_, err = io.ReadFull(r, chunkBuf)
-		assert.NoError(err)
-		assert.Equal("test\n", string(chunkBuf))
-
-		// Write and flush the second part of the data
-		c.Response().Write([]byte("test\n"))
-		c.Response().Flush()
-
-		_, err = io.ReadFull(r, chunkBuf)
-		assert.NoError(err)
-		assert.Equal("test\n", string(chunkBuf))
-
-		// Write the final part of the data and return
-		c.Response().Write([]byte("test"))
-		return nil
-	})(c)
-
-	buf := new(bytes.Buffer)
-	defer r.Close()
-	buf.ReadFrom(r)
-	assert.Equal("test", buf.String())
 }
 
 func TestGzipNoContent(t *testing.T) {
diff --git a/middleware/cors_test.go b/middleware/cors_test.go
index acfdf47..7a71e73 100644
--- a/middleware/cors_test.go
+++ b/middleware/cors_test.go
@@ -66,20 +66,4 @@ func TestCORS(t *testing.T) {
 	assert.NotEmpty(t, rec.Header().Get(echo.HeaderAccessControlAllowMethods))
 	assert.Equal(t, "true", rec.Header().Get(echo.HeaderAccessControlAllowCredentials))
 	assert.Equal(t, "3600", rec.Header().Get(echo.HeaderAccessControlMaxAge))
-
-	// Preflight request with `AllowOrigins` which allow all subdomains with *
-	req = httptest.NewRequest(http.MethodOptions, "/", nil)
-	rec = httptest.NewRecorder()
-	c = e.NewContext(req, rec)
-	req.Header.Set(echo.HeaderOrigin, "http://aaa.example.com")
-	cors = CORSWithConfig(CORSConfig{
-		AllowOrigins:     []string{"http://*.example.com"},
-	})
-	h = cors(echo.NotFoundHandler)
-	h(c)
-	assert.Equal(t, "http://aaa.example.com", rec.Header().Get(echo.HeaderAccessControlAllowOrigin))
-
-	req.Header.Set(echo.HeaderOrigin, "http://bbb.example.com")
-	h(c)
-	assert.Equal(t, "http://bbb.example.com", rec.Header().Get(echo.HeaderAccessControlAllowOrigin))
 }
diff --git a/middleware/secure_test.go b/middleware/secure_test.go
index 96245fe..47121fb 100644
--- a/middleware/secure_test.go
+++ b/middleware/secure_test.go
@@ -62,25 +62,4 @@ func TestSecure(t *testing.T) {
 	assert.Equal(t, "max-age=3600; includeSubdomains", rec.Header().Get(echo.HeaderStrictTransportSecurity))
 	assert.Equal(t, "default-src 'self'", rec.Header().Get(echo.HeaderContentSecurityPolicyReportOnly))
 	assert.Equal(t, "", rec.Header().Get(echo.HeaderContentSecurityPolicy))
-
-	// Custom, with preload option enabled
-	req.Header.Set(echo.HeaderXForwardedProto, "https")
-	rec = httptest.NewRecorder()
-	c = e.NewContext(req, rec)
-	SecureWithConfig(SecureConfig{
-		HSTSMaxAge:         3600,
-		HSTSPreloadEnabled: true,
-	})(h)(c)
-	assert.Equal(t, "max-age=3600; includeSubdomains; preload", rec.Header().Get(echo.HeaderStrictTransportSecurity))
-
-	// Custom, with preload option enabled and subdomains excluded
-	req.Header.Set(echo.HeaderXForwardedProto, "https")
-	rec = httptest.NewRecorder()
-	c = e.NewContext(req, rec)
-	SecureWithConfig(SecureConfig{
-		HSTSMaxAge:            3600,
-		HSTSPreloadEnabled:    true,
-		HSTSExcludeSubdomains: true,
-	})(h)(c)
-	assert.Equal(t, "max-age=3600; preload", rec.Header().Get(echo.HeaderStrictTransportSecurity))
 }
diff --git a/middleware/util_test.go b/middleware/util_test.go
deleted file mode 100644
index df1d262..0000000
--- a/middleware/util_test.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package middleware
-
-import (
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-)
-
-func Test_matchScheme(t *testing.T) {
-	tests := []struct {
-		domain, pattern string
-		expected        bool
-	}{
-		{
-			domain:   "http://example.com",
-			pattern:  "http://example.com",
-			expected: true,
-		},
-		{
-			domain:   "https://example.com",
-			pattern:  "https://example.com",
-			expected: true,
-		},
-		{
-			domain:   "http://example.com",
-			pattern:  "https://example.com",
-			expected: false,
-		},
-		{
-			domain:   "https://example.com",
-			pattern:  "http://example.com",
-			expected: false,
-		},
-	}
-
-	for _, v := range tests {
-		assert.Equal(t, v.expected, matchScheme(v.domain, v.pattern))
-	}
-}
-
-func Test_matchSubdomain(t *testing.T) {
-	tests := []struct {
-		domain, pattern string
-		expected        bool
-	}{
-		{
-			domain:   "http://aaa.example.com",
-			pattern:  "http://*.example.com",
-			expected: true,
-		},
-		{
-			domain:   "http://bbb.aaa.example.com",
-			pattern:  "http://*.example.com",
-			expected: true,
-		},
-		{
-			domain:   "http://bbb.aaa.example.com",
-			pattern:  "http://*.aaa.example.com",
-			expected: true,
-		},
-		{
-			domain:   "http://aaa.example.com:8080",
-			pattern:  "http://*.example.com:8080",
-			expected: true,
-		},
-
-		{
-			domain:   "http://fuga.hoge.com",
-			pattern:  "http://*.example.com",
-			expected: false,
-		},
-		{
-			domain:   "http://ccc.bbb.example.com",
-			pattern:  "http://*.aaa.example.com",
-			expected: false,
-		},
-		{
-			domain: `http://1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890\
-      .1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890\
-      .1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890\
-      .1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.example.com`,
-			pattern:  "http://*.example.com",
-			expected: false,
-		},
-		{
-			domain:   "http://ccc.bbb.example.com",
-			pattern:  "http://example.com",
-			expected: false,
-		},
-	}
-
-	for _, v := range tests {
-		assert.Equal(t, v.expected, matchSubdomain(v.domain, v.pattern))
-	}
-}
diff --git a/router_test.go b/router_test.go
index a5d53c0..f084fb9 100644
--- a/router_test.go
+++ b/router_test.go
@@ -756,6 +756,12 @@ func TestRouterPriorityNotFound(t *testing.T) {
 		c.Set("b", 2)
 		return nil
 	})
+	r.Add(http.MethodGet, "/:lang/", func(c Context) error {
+		return nil
+	})
+	r.Add(http.MethodGet, "/:lang/dupa", func(c Context) error {
+		return nil
+	})
 
 	// Find
 	r.Find(http.MethodGet, "/a/foo", c)