Skip to content

Commit

Permalink
cors allow regex pattern
Browse files Browse the repository at this point in the history
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
  • Loading branch information
KlotzAndrew committed Aug 19, 2020
1 parent 8dd25c3 commit 9a28fb8
Show file tree
Hide file tree
Showing 2 changed files with 168 additions and 0 deletions.
30 changes: 30 additions & 0 deletions middleware/cors.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package middleware

import (
"net/http"
"regexp"
"strconv"
"strings"

Expand Down Expand Up @@ -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, ",")
Expand Down Expand Up @@ -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)
Expand Down
138 changes: 138 additions & 0 deletions middleware/cors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
}
}

0 comments on commit 9a28fb8

Please sign in to comment.