Repository: labstack/echo
Pull request #1623 (2020-08-18 02:26:13)

cors allow regex pattern
enable cors to use regex pattern for allowed origins

implementation is similar to another popular cors middleware: https://github.com/astaxie/beego/blob/master/plugins/cors/cors.go#L196-L201

codecov[bot]: # [Codecov](https://codecov.io/gh/labstack/echo/pull/1623?src=pr&el=h1) Report
> Merging [#1623](https://codecov.io/gh/labstack/echo/pull/1623?src=pr&el=desc) into [master](https://codecov.io/gh/labstack/echo/commit/8dd25c39ced4fafbe63e361c281f2275d7d9fa41&el=desc) will **increase** coverage by `0.12%`.
> The diff coverage is `88.88%`.

[![Impacted file tree graph](https://codecov.io/gh/labstack/echo/pull/1623/graphs/tree.svg?width=650&height=150&src=pr&token=vqnT76l79i)](https://codecov.io/gh/labstack/echo/pull/1623?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master    #1623      +/-   ##
==========================================
+ Coverage   85.08%   85.20%   +0.12%     
==========================================
  Files          28       28              
  Lines        2185     2203      +18     
==========================================
+ Hits         1859     1877      +18     
  Misses        212      212              
  Partials      114      114              
```


| [Impacted Files](https://codecov.io/gh/labstack/echo/pull/1623?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [middleware/cors.go](https://codecov.io/gh/labstack/echo/pull/1623/diff?src=pr&el=tree#diff-bWlkZGxld2FyZS9jb3JzLmdv) | `79.45% <88.88%> (+3.08%)` | :arrow_up: |
| [middleware/util.go](https://codecov.io/gh/labstack/echo/pull/1623/diff?src=pr&el=tree#diff-bWlkZGxld2FyZS91dGlsLmdv) | `90.62% <0.00%> (+6.25%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/labstack/echo/pull/1623?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/labstack/echo/pull/1623?src=pr&el=footer). Last update [8dd25c3...bbe8e46](https://codecov.io/gh/labstack/echo/pull/1623?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


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/cors.go b/middleware/cors.go
index 5dfe31f..c263f73 100644
--- a/middleware/cors.go
+++ b/middleware/cors.go
@@ -2,6 +2,7 @@ package middleware
 
 import (
 	"net/http"
+	"regexp"
 	"strconv"
 	"strings"
 
@@ -76,6 +77,15 @@ func CORSWithConfig(config CORSConfig) echo.MiddlewareFunc {
 		config.AllowMethods = DefaultCORSConfig.AllowMethods
 	}
 
+	allowOriginPatterns := []string{}
+	for _, origin := range config.AllowOrigins {
+		pattern := regexp.QuoteMeta(origin)
+		pattern = strings.Replace(pattern, "\\*", ".*", -1)
+		pattern = strings.Replace(pattern, "\\?", ".", -1)
+		pattern = "^" + pattern + "$"
+		allowOriginPatterns = append(allowOriginPatterns, pattern)
+	}
+
 	allowMethods := strings.Join(config.AllowMethods, ",")
 	allowHeaders := strings.Join(config.AllowHeaders, ",")
 	exposeHeaders := strings.Join(config.ExposeHeaders, ",")
@@ -108,6 +118,26 @@ func CORSWithConfig(config CORSConfig) echo.MiddlewareFunc {
 				}
 			}
 
+			// Check allowed origin patterns
+			for _, re := range allowOriginPatterns {
+				if allowOrigin == "" {
+					didx := strings.Index(origin, "://")
+					if didx == -1 {
+						continue
+					}
+					domAuth := origin[didx+3:]
+					// to avoid regex cost by invalid long domain
+					if len(domAuth) > 253 {
+						break
+					}
+
+					if match, _ := regexp.MatchString(re, origin); match {
+						allowOrigin = origin
+						break
+					}
+				}
+			}
+
 			// Simple request
 			if req.Method != http.MethodOptions {
 				res.Header().Add(echo.HeaderVary, echo.HeaderOrigin)
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
 }


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/cors_test.go b/middleware/cors_test.go
index 456ec7b..ca92232 100644
--- a/middleware/cors_test.go
+++ b/middleware/cors_test.go
@@ -83,3 +83,141 @@ func TestCORS(t *testing.T) {
 	h(c)
 	assert.Equal(t, "http://bbb.example.com", rec.Header().Get(echo.HeaderAccessControlAllowOrigin))
 }
+
+func Test_allowOriginScheme(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,
+		},
+	}
+
+	e := echo.New()
+	for _, tt := range tests {
+		req := httptest.NewRequest(http.MethodOptions, "/", nil)
+		rec := httptest.NewRecorder()
+		c := e.NewContext(req, rec)
+		req.Header.Set(echo.HeaderOrigin, tt.domain)
+		cors := CORSWithConfig(CORSConfig{
+			AllowOrigins: []string{tt.pattern},
+		})
+		h := cors(echo.NotFoundHandler)
+		h(c)
+
+		if tt.expected {
+			assert.Equal(t, tt.domain, rec.Header().Get(echo.HeaderAccessControlAllowOrigin))
+		} else {
+			assert.Equal(t, "", rec.Header().Get(echo.HeaderAccessControlAllowOrigin))
+		}
+	}
+}
+
+func Test_allowOriginSubdomain(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://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,
+		},
+		{
+			domain:   "https://prod-preview--aaa.bbb.com",
+			pattern:  "https://*--aaa.bbb.com",
+			expected: true,
+		},
+		{
+			domain:   "http://ccc.bbb.example.com",
+			pattern:  "http://*.example.com",
+			expected: true,
+		},
+		{
+			domain:   "http://ccc.bbb.example.com",
+			pattern:  "http://foo.[a-z]*.example.com",
+			expected: false,
+		},
+	}
+
+	e := echo.New()
+	for _, tt := range tests {
+		req := httptest.NewRequest(http.MethodOptions, "/", nil)
+		rec := httptest.NewRecorder()
+		c := e.NewContext(req, rec)
+		req.Header.Set(echo.HeaderOrigin, tt.domain)
+		cors := CORSWithConfig(CORSConfig{
+			AllowOrigins: []string{tt.pattern},
+		})
+		h := cors(echo.NotFoundHandler)
+		h(c)
+
+		if tt.expected {
+			assert.Equal(t, tt.domain, rec.Header().Get(echo.HeaderAccessControlAllowOrigin))
+		} else {
+			assert.Equal(t, "", rec.Header().Get(echo.HeaderAccessControlAllowOrigin))
+		}
+	}
+}
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) {