Repository: labstack/echo (go)

Issue:
Rewrite middleware in v4.1.16 no longer supports matching beginning of path
### Issue Description
Rewrite middleware was changed in v4.1.16 here:
https://github.com/labstack/echo/commit/542835808e41723e5ecf5864b189a0ad36b8f3f6

After this change it seems it's not possible to explicitly match beginning of path anymore.

- Is there some way to get the old behavior or let the users write proper regexps? (besides rewriting the rewrite middleware)
- https://github.com/labstack/echo/blob/master/middleware/rewrite.go#L62 is adding end-of-line but not beginning-of-line. Is that intentional?

### Checklist

- [x] Dependencies installed
- [x] No typos
- [x] Searched existing issues and docs

### Expected behaviour
Before we were using this:
```
	// Rewrite requests without version to use v1
	e.Pre(echomiddleware.Rewrite(map[string]string{
		"^/abc/*": "/v1/abc/$1",
	}))
```
where ^ is regexp for beginning of string.

Some examples before and what they got mapped to:
```
/abc/test -> /v1/abc/test
/v1/abc/test  (same, no rewrite)
/v2/abc/test (same, no rewrite)
```
### Actual behaviour

The new versions re-formats the beginning of line character using regexp.QuoteMeta so it can't be used anymore. Rewrite will change ANY "version prefix" to v1 if removing "^", not just ones without it, e.g.:
```
/abc/test -> /v1/abc/test
/v2/abc/test -> /v1/abc/test
/not/like/this/abc/test -> /v1/abc/test
```
which is not what we intended and breaks backwards compatibility.

### Steps to reproduce

### Working code to debug

```go
package main

import(
	"github.com/labstack/echo/v4"
	echomiddleware "github.com/labstack/echo/v4/middleware"
)

func main() {
	e := echo.New()
	// Rewrite requests without version to use v1.
	// This worked on v4.1.13 but now nothing matches until removing the ^ here.
	// However, that makes it match in the middle of strings.
	e.Pre(echomiddleware.Rewrite(map[string]string{
		"^/abc/*": "/v1/abc/$1",
	}))

	group := e.Group("")
	group.Use(echomiddleware.Logger())
	group.Any("/v1/abc/test", func(c echo.Context) error {
		return c.String(http.StatusOK, "Route: /v1/abc/test")
	})

	e.Logger.Fatal(e.StartServer(e.Server))
}
```

### Version/commit


Pull request:
Fixes the uses of caret(^) in rewrite regex
Fixes #1573

When use `QuoteMeta`，`^` will be replaced to `\^`, and than out of action as regex element.

Patch:
diff --git a/.travis.yml b/.travis.yml
index ef826e9..a1fc876 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,7 @@
 language: go
 go:
-  - 1.14.x
-  - 1.15.x
+  - 1.12.x
+  - 1.13.x
   - tip
 env:
   - GO111MODULE=on
diff --git a/README.md b/README.md
index d9d9613..769c9bb 100644
--- a/README.md
+++ b/README.md
@@ -17,7 +17,7 @@ Therefore a Go version capable of understanding /vN suffixed imports is required
 
 - 1.9.7+
 - 1.10.3+
-- 1.14+
+- 1.11+
 
 Any of these versions will allow you to import Echo as `github.com/labstack/echo/v4` which is the recommended
 way of using Echo going forward.
diff --git a/echo.go b/echo.go
index 18c1101..c6881c9 100644
--- a/echo.go
+++ b/echo.go
@@ -48,7 +48,6 @@ import (
 	"net"
 	"net/http"
 	"net/url"
-	"os"
 	"path"
 	"path/filepath"
 	"reflect"
@@ -231,7 +230,7 @@ const (
 
 const (
 	// Version of Echo
-	Version = "4.1.17"
+	Version = "4.1.16"
 	website = "https://echo.labstack.com"
 	// http://patorjk.com/software/taag/#p=display&f=Small%20Slant&t=Echo
 	banner = `
@@ -480,20 +479,7 @@ func (common) static(prefix, root string, get func(string, HandlerFunc, ...Middl
 		if err != nil {
 			return err
 		}
-
 		name := filepath.Join(root, path.Clean("/"+p)) // "/"+ for security
-		fi, err := os.Stat(name)
-		if err != nil {
-			// The access path does not exist
-			return NotFoundHandler(c)
-		}
-
-		// If the request is for a directory and does not end with "/"
-		p = c.Request().URL.Path // path must not be empty.
-		if fi.IsDir() && p[len(p)-1] != '/' {
-			// Redirect to ends with "/"
-			return c.Redirect(http.StatusMovedPermanently, p+"/")
-		}
 		return c.File(name)
 	}
 	if prefix == "/" {
diff --git a/go.mod b/go.mod
index 74c6a9a..ed220b7 100644
--- a/go.mod
+++ b/go.mod
@@ -1,15 +1,14 @@
 module github.com/labstack/echo/v4
 
-go 1.15
+go 1.14
 
 require (
 	github.com/dgrijalva/jwt-go v3.2.0+incompatible
 	github.com/labstack/gommon v0.3.0
-	github.com/mattn/go-colorable v0.1.7 // indirect
+	github.com/mattn/go-colorable v0.1.6 // indirect
 	github.com/stretchr/testify v1.4.0
-	github.com/valyala/fasttemplate v1.2.1
-	golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a
-	golang.org/x/net v0.0.0-20200822124328-c89045814202
-	golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6 // indirect
+	github.com/valyala/fasttemplate v1.1.0
+	golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d
+	golang.org/x/net v0.0.0-20200226121028-0de0cce0169b
 	golang.org/x/text v0.3.3 // indirect
 )
diff --git a/go.sum b/go.sum
index 58c80c8..11ef341 100644
--- a/go.sum
+++ b/go.sum
@@ -1,13 +1,14 @@
 github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dgrijalva/jwt-go v1.0.2 h1:KPldsxuKGsS2FPWsNeg9ZO18aCrGKujPoWXn2yo+KQM=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0=
 github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
 github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
 github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
-github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw=
-github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=
+github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
 github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg=
 github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
@@ -22,15 +23,14 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw
 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=
-github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4=
-github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
+github.com/valyala/fasttemplate v1.1.0 h1:RZqt0yGBsps8NGvLSGW804QQqCUYYLsaOjTVHy1Ocw4=
+github.com/valyala/fasttemplate v1.1.0/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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM=
-golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d h1:1ZiEyfaQIg3Qh0EoqpwAakHVhecoE5wlSg5GjnafJGw=
+golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA=
-golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 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/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -39,15 +39,13 @@ golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8=
 golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6 h1:DvY3Zkh7KabQE/kfzMvYvKirSiguP9Q/veMtkYyf0o8=
-golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/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=
+golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/middleware/compress.go b/middleware/compress.go
index dd97d98..89da16e 100644
--- a/middleware/compress.go
+++ b/middleware/compress.go
@@ -119,10 +119,3 @@ func (w *gzipResponseWriter) Flush() {
 func (w *gzipResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
 	return w.ResponseWriter.(http.Hijacker).Hijack()
 }
-
-func (w *gzipResponseWriter) Push(target string, opts *http.PushOptions) error {
-	if p, ok := w.ResponseWriter.(http.Pusher); ok {
-		return p.Push(target, opts)
-	}
-	return http.ErrNotSupported
-}
diff --git a/middleware/proxy.go b/middleware/proxy.go
index a9b91f6..1956e91 100644
--- a/middleware/proxy.go
+++ b/middleware/proxy.go
@@ -45,9 +45,6 @@ type (
 		// Examples: If custom TLS certificates are required.
 		Transport http.RoundTripper
 
-		// ModifyResponse defines function to modify response from ProxyTarget.
-		ModifyResponse func(*http.Response) error
-
 		rewriteRegex map[*regexp.Regexp]string
 	}
 
diff --git a/middleware/proxy_1_11.go b/middleware/proxy_1_11.go
index a439278..12b7568 100644
--- a/middleware/proxy_1_11.go
+++ b/middleware/proxy_1_11.go
@@ -20,6 +20,5 @@ func proxyHTTP(tgt *ProxyTarget, c echo.Context, config ProxyConfig) http.Handle
 		c.Set("_error", echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("remote %s unreachable, could not forward: %v", desc, err)))
 	}
 	proxy.Transport = config.Transport
-	proxy.ModifyResponse = config.ModifyResponse
 	return proxy
 }
diff --git a/middleware/rewrite.go b/middleware/rewrite.go
index d1387af..476023f 100644
--- a/middleware/rewrite.go
+++ b/middleware/rewrite.go
@@ -59,6 +59,9 @@ func RewriteWithConfig(config RewriteConfig) echo.MiddlewareFunc {
 	for k, v := range config.Rules {
 		k = regexp.QuoteMeta(k)
 		k = strings.Replace(k, `\*`, "(.*)", -1)
+		if strings.HasPrefix(k, `\^`) {
+			k = strings.Replace(k, `\^`, "^", -1)
+		}
 		k = k + "$"
 		config.rulesRegex[regexp.MustCompile(k)] = v
 	}


Test patch:
diff --git a/echo_test.go b/echo_test.go
index e1706ef..b9c1778 100644
--- a/echo_test.go
+++ b/echo_test.go
@@ -76,17 +76,9 @@ func TestEchoStatic(t *testing.T) {
 
 	// Directory
 	e.Static("/images", "_fixture/images")
-	c, _ = request(http.MethodGet, "/images/", e)
+	c, _ = request(http.MethodGet, "/images", e)
 	assert.Equal(http.StatusNotFound, c)
 
-	// Directory Redirect
-	e.Static("/", "_fixture")
-	req := httptest.NewRequest(http.MethodGet, "/folder", nil)
-	rec := httptest.NewRecorder()
-	e.ServeHTTP(rec, req)
-	assert.Equal(http.StatusMovedPermanently, rec.Code)
-	assert.Equal("/folder/", rec.HeaderMap["Location"][0])
-
 	// Directory with index.html
 	e.Static("/", "_fixture")
 	c, r := request(http.MethodGet, "/", e)
@@ -94,10 +86,9 @@ func TestEchoStatic(t *testing.T) {
 	assert.Equal(true, strings.HasPrefix(r, "<!doctype html>"))
 
 	// Sub-directory with index.html
-	c, r = request(http.MethodGet, "/folder/", e)
+	c, r = request(http.MethodGet, "/folder", e)
 	assert.Equal(http.StatusOK, c)
 	assert.Equal(true, strings.HasPrefix(r, "<!doctype html>"))
-
 }
 
 func TestEchoFile(t *testing.T) {
diff --git a/middleware/proxy_test.go b/middleware/proxy_test.go
index b19bf4f..5ef11bc 100644
--- a/middleware/proxy_test.go
+++ b/middleware/proxy_test.go
@@ -1,9 +1,7 @@
 package middleware
 
 import (
-	"bytes"
 	"fmt"
-	"io/ioutil"
 	"net"
 	"net/http"
 	"net/http/httptest"
@@ -106,26 +104,11 @@ func TestProxy(t *testing.T) {
 	e.ServeHTTP(rec, req)
 	assert.Equal(t, "/user/jack/order/1", req.URL.Path)
 	assert.Equal(t, http.StatusOK, rec.Code)
-	req.URL.Path = "/users/jill/orders/T%2FcO4lW%2Ft%2FVp%2F"
+  req.URL.Path = "/users/jill/orders/T%2FcO4lW%2Ft%2FVp%2F"
 	e.ServeHTTP(rec, req)
 	assert.Equal(t, "/user/jill/order/T%2FcO4lW%2Ft%2FVp%2F", req.URL.Path)
 	assert.Equal(t, http.StatusOK, rec.Code)
 
-	// ModifyResponse
-	e = echo.New()
-	e.Use(ProxyWithConfig(ProxyConfig{
-		Balancer: rrb,
-		ModifyResponse: func(res *http.Response) error {
-			res.Body = ioutil.NopCloser(bytes.NewBuffer([]byte("modified")))
-			res.Header.Set("X-Modified", "1")
-			return nil
-		},
-	}))
-	rec = httptest.NewRecorder()
-	e.ServeHTTP(rec, req)
-	assert.Equal(t, "modified", rec.Body.String())
-	assert.Equal(t, "1", rec.Header().Get("X-Modified"))
-
 	// ProxyTarget is set in context
 	contextObserver := func(next echo.HandlerFunc) echo.HandlerFunc {
 		return func(c echo.Context) (err error) {
diff --git a/middleware/rewrite_test.go b/middleware/rewrite_test.go
index eb5a46d..848f002 100644
--- a/middleware/rewrite_test.go
+++ b/middleware/rewrite_test.go
@@ -94,3 +94,30 @@ func TestRewriteWithConfigPreMiddleware_Issue1143(t *testing.T) {
 		assert.Equal(t, "hosts", string(bodyBytes))
 	}
 }
+
+// Issue #1573
+func TestEchoRewriteWithCaret(t *testing.T) {
+	e := echo.New()
+
+	e.Pre(RewriteWithConfig(RewriteConfig{
+		Rules: map[string]string{
+			"^/abc/*": "/v1/abc/$1",
+		},
+	}))
+
+	rec := httptest.NewRecorder()
+
+	var req *http.Request
+
+	req = httptest.NewRequest(http.MethodGet, "/abc/test", nil)
+	e.ServeHTTP(rec, req)
+	assert.Equal(t, "/v1/abc/test", req.URL.Path)
+
+	req = httptest.NewRequest(http.MethodGet, "/v1/abc/test", nil)
+	e.ServeHTTP(rec, req)
+	assert.Equal(t, "/v1/abc/test", req.URL.Path)
+
+	req = httptest.NewRequest(http.MethodGet, "/v2/abc/test", nil)
+	e.ServeHTTP(rec, req)
+	assert.Equal(t, "/v2/abc/test", req.URL.Path)
+}