1026 lines
30 KiB
Go
1026 lines
30 KiB
Go
package http
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/traefik/traefik/v2/pkg/middlewares/requestdecorator"
|
|
)
|
|
|
|
func TestClientIPMatcher(t *testing.T) {
|
|
testCases := []struct {
|
|
desc string
|
|
rule string
|
|
expected map[string]int
|
|
expectedError bool
|
|
}{
|
|
{
|
|
desc: "invalid ClientIP matcher",
|
|
rule: "ClientIP(`1`)",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "invalid ClientIP matcher (no parameter)",
|
|
rule: "ClientIP()",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "invalid ClientIP matcher (empty parameter)",
|
|
rule: "ClientIP(``)",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "invalid ClientIP matcher (too many parameters)",
|
|
rule: "ClientIP(`127.0.0.1`, `192.168.1.0/24`)",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "valid ClientIP matcher",
|
|
rule: "ClientIP(`127.0.0.1`)",
|
|
expected: map[string]int{
|
|
"127.0.0.1": http.StatusOK,
|
|
"192.168.1.1": http.StatusNotFound,
|
|
},
|
|
},
|
|
{
|
|
desc: "valid ClientIP matcher but invalid remote address",
|
|
rule: "ClientIP(`127.0.0.1`)",
|
|
expected: map[string]int{
|
|
"1": http.StatusNotFound,
|
|
},
|
|
},
|
|
{
|
|
desc: "valid ClientIP matcher using CIDR",
|
|
rule: "ClientIP(`192.168.1.0/24`)",
|
|
expected: map[string]int{
|
|
"192.168.1.1": http.StatusOK,
|
|
"192.168.1.100": http.StatusOK,
|
|
"192.168.2.1": http.StatusNotFound,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range testCases {
|
|
test := test
|
|
t.Run(test.desc, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
|
muxer, err := NewMuxer()
|
|
require.NoError(t, err)
|
|
|
|
err = muxer.AddRoute(test.rule, 0, handler)
|
|
if test.expectedError {
|
|
require.Error(t, err)
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
|
|
results := make(map[string]int)
|
|
for remoteAddr := range test.expected {
|
|
w := httptest.NewRecorder()
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "https://example.com", http.NoBody)
|
|
req.RemoteAddr = remoteAddr
|
|
|
|
muxer.ServeHTTP(w, req)
|
|
results[remoteAddr] = w.Code
|
|
}
|
|
assert.Equal(t, test.expected, results)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMethodMatcher(t *testing.T) {
|
|
testCases := []struct {
|
|
desc string
|
|
rule string
|
|
expected map[string]int
|
|
expectedError bool
|
|
}{
|
|
{
|
|
desc: "invalid Method matcher (no parameter)",
|
|
rule: "Method()",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "invalid Method matcher (empty parameter)",
|
|
rule: "Method(``)",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "invalid Method matcher (too many parameters)",
|
|
rule: "Method(`GET`, `POST`)",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "valid Method matcher",
|
|
rule: "Method(`GET`)",
|
|
expected: map[string]int{
|
|
http.MethodGet: http.StatusOK,
|
|
http.MethodPost: http.StatusNotFound,
|
|
strings.ToLower(http.MethodGet): http.StatusNotFound,
|
|
},
|
|
},
|
|
{
|
|
desc: "valid Method matcher (lower case)",
|
|
rule: "Method(`get`)",
|
|
expected: map[string]int{
|
|
http.MethodGet: http.StatusOK,
|
|
http.MethodPost: http.StatusNotFound,
|
|
strings.ToLower(http.MethodGet): http.StatusNotFound,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range testCases {
|
|
test := test
|
|
t.Run(test.desc, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
|
muxer, err := NewMuxer()
|
|
require.NoError(t, err)
|
|
|
|
err = muxer.AddRoute(test.rule, 0, handler)
|
|
if test.expectedError {
|
|
require.Error(t, err)
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
|
|
results := make(map[string]int)
|
|
for method := range test.expected {
|
|
w := httptest.NewRecorder()
|
|
|
|
req := httptest.NewRequest(method, "https://example.com", http.NoBody)
|
|
|
|
muxer.ServeHTTP(w, req)
|
|
results[method] = w.Code
|
|
}
|
|
assert.Equal(t, test.expected, results)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHostMatcher(t *testing.T) {
|
|
testCases := []struct {
|
|
desc string
|
|
rule string
|
|
expected map[string]int
|
|
expectedError bool
|
|
}{
|
|
{
|
|
desc: "invalid Host matcher (no parameter)",
|
|
rule: "Host()",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "invalid Host matcher (empty parameter)",
|
|
rule: "Host(``)",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "invalid Host matcher (non-ASCII)",
|
|
rule: "Host(`🦭.com`)",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "invalid Host matcher (too many parameters)",
|
|
rule: "Host(`example.com`, `example.org`)",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "valid Host matcher",
|
|
rule: "Host(`example.com`)",
|
|
expected: map[string]int{
|
|
"https://example.com": http.StatusOK,
|
|
"https://example.com:8080": http.StatusOK,
|
|
"https://example.com/path": http.StatusOK,
|
|
"https://EXAMPLE.COM/path": http.StatusOK,
|
|
"https://example.org": http.StatusNotFound,
|
|
"https://example.org/path": http.StatusNotFound,
|
|
},
|
|
},
|
|
{
|
|
desc: "valid Host matcher - matcher ending with a dot",
|
|
rule: "Host(`example.com.`)",
|
|
expected: map[string]int{
|
|
"https://example.com": http.StatusOK,
|
|
"https://example.com/path": http.StatusOK,
|
|
"https://example.org": http.StatusNotFound,
|
|
"https://example.org/path": http.StatusNotFound,
|
|
"https://example.com.": http.StatusOK,
|
|
"https://example.com./path": http.StatusOK,
|
|
"https://example.org.": http.StatusNotFound,
|
|
"https://example.org./path": http.StatusNotFound,
|
|
},
|
|
},
|
|
{
|
|
desc: "valid Host matcher - URL ending with a dot",
|
|
rule: "Host(`example.com`)",
|
|
expected: map[string]int{
|
|
"https://example.com.": http.StatusOK,
|
|
"https://example.com./path": http.StatusOK,
|
|
"https://example.org.": http.StatusNotFound,
|
|
"https://example.org./path": http.StatusNotFound,
|
|
},
|
|
},
|
|
{
|
|
desc: "valid Host matcher - matcher with UPPER case",
|
|
rule: "Host(`EXAMPLE.COM`)",
|
|
expected: map[string]int{
|
|
"https://example.com": http.StatusOK,
|
|
"https://example.com/path": http.StatusOK,
|
|
"https://example.org": http.StatusNotFound,
|
|
"https://example.org/path": http.StatusNotFound,
|
|
},
|
|
},
|
|
{
|
|
desc: "valid Host matcher - puny-coded emoji",
|
|
rule: "Host(`xn--9t9h.com`)",
|
|
expected: map[string]int{
|
|
"https://xn--9t9h.com": http.StatusOK,
|
|
"https://xn--9t9h.com/path": http.StatusOK,
|
|
"https://example.com": http.StatusNotFound,
|
|
"https://example.com/path": http.StatusNotFound,
|
|
// The request's sender must use puny-code.
|
|
"https://🦭.com": http.StatusNotFound,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range testCases {
|
|
test := test
|
|
t.Run(test.desc, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
|
muxer, err := NewMuxer()
|
|
require.NoError(t, err)
|
|
|
|
err = muxer.AddRoute(test.rule, 0, handler)
|
|
if test.expectedError {
|
|
require.Error(t, err)
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
|
|
// RequestDecorator is necessary for the Host matcher
|
|
reqHost := requestdecorator.New(nil)
|
|
|
|
results := make(map[string]int)
|
|
for calledURL := range test.expected {
|
|
w := httptest.NewRecorder()
|
|
|
|
req := httptest.NewRequest(http.MethodGet, calledURL, http.NoBody)
|
|
|
|
reqHost.ServeHTTP(w, req, muxer.ServeHTTP)
|
|
results[calledURL] = w.Code
|
|
}
|
|
assert.Equal(t, test.expected, results)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHostRegexpMatcher(t *testing.T) {
|
|
testCases := []struct {
|
|
desc string
|
|
rule string
|
|
expected map[string]int
|
|
expectedError bool
|
|
}{
|
|
{
|
|
desc: "invalid HostRegexp matcher (no parameter)",
|
|
rule: "HostRegexp()",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "invalid HostRegexp matcher (empty parameter)",
|
|
rule: "HostRegexp(``)",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "invalid HostRegexp matcher (non-ASCII)",
|
|
rule: "HostRegexp(`🦭.com`)",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "invalid HostRegexp matcher (invalid regexp)",
|
|
rule: "HostRegexp(`(example.com`)",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "invalid HostRegexp matcher (too many parameters)",
|
|
rule: "HostRegexp(`example.com`, `example.org`)",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "valid HostRegexp matcher",
|
|
rule: "HostRegexp(`^[a-zA-Z-]+\\.com$`)",
|
|
expected: map[string]int{
|
|
"https://example.com": http.StatusOK,
|
|
"https://example.com:8080": http.StatusOK,
|
|
"https://example.com/path": http.StatusOK,
|
|
"https://example.org": http.StatusNotFound,
|
|
"https://example.org/path": http.StatusNotFound,
|
|
},
|
|
},
|
|
{
|
|
desc: "valid HostRegexp matcher with case sensitive regexp",
|
|
rule: "HostRegexp(`^[A-Z]+\\.com$`)",
|
|
expected: map[string]int{
|
|
"https://example.com": http.StatusNotFound,
|
|
"https://EXAMPLE.com": http.StatusNotFound,
|
|
"https://example.com/path": http.StatusNotFound,
|
|
"https://example.org": http.StatusNotFound,
|
|
"https://example.org/path": http.StatusNotFound,
|
|
},
|
|
},
|
|
{
|
|
desc: "valid HostRegexp matcher with Traefik v2 syntax",
|
|
rule: "HostRegexp(`{domain:[a-zA-Z-]+\\.com}`)",
|
|
expected: map[string]int{
|
|
"https://example.com": http.StatusNotFound,
|
|
"https://example.com/path": http.StatusNotFound,
|
|
"https://example.org": http.StatusNotFound,
|
|
"https://example.org/path": http.StatusNotFound,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range testCases {
|
|
test := test
|
|
t.Run(test.desc, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
|
muxer, err := NewMuxer()
|
|
require.NoError(t, err)
|
|
|
|
err = muxer.AddRoute(test.rule, 0, handler)
|
|
if test.expectedError {
|
|
require.Error(t, err)
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
|
|
// RequestDecorator is necessary for the HostRegexp matcher
|
|
reqHost := requestdecorator.New(nil)
|
|
|
|
results := make(map[string]int)
|
|
for calledURL := range test.expected {
|
|
w := httptest.NewRecorder()
|
|
|
|
req := httptest.NewRequest(http.MethodGet, calledURL, http.NoBody)
|
|
|
|
reqHost.ServeHTTP(w, req, muxer.ServeHTTP)
|
|
results[calledURL] = w.Code
|
|
}
|
|
assert.Equal(t, test.expected, results)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPathMatcher(t *testing.T) {
|
|
testCases := []struct {
|
|
desc string
|
|
rule string
|
|
expected map[string]int
|
|
expectedError bool
|
|
}{
|
|
{
|
|
desc: "invalid Path matcher (no parameter)",
|
|
rule: "Path()",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "invalid Path matcher (empty parameter)",
|
|
rule: "Path(``)",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "invalid Path matcher (no leading /)",
|
|
rule: "Path(`css`)",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "invalid Path matcher (too many parameters)",
|
|
rule: "Path(`/css`, `/js`)",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "valid Path matcher",
|
|
rule: "Path(`/css`)",
|
|
expected: map[string]int{
|
|
"https://example.com": http.StatusNotFound,
|
|
"https://example.com/html": http.StatusNotFound,
|
|
"https://example.org/css": http.StatusOK,
|
|
"https://example.com/css": http.StatusOK,
|
|
"https://example.com/css/": http.StatusNotFound,
|
|
"https://example.com/css/main.css": http.StatusNotFound,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range testCases {
|
|
test := test
|
|
t.Run(test.desc, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
|
muxer, err := NewMuxer()
|
|
require.NoError(t, err)
|
|
|
|
err = muxer.AddRoute(test.rule, 0, handler)
|
|
if test.expectedError {
|
|
require.Error(t, err)
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
|
|
results := make(map[string]int)
|
|
for calledURL := range test.expected {
|
|
w := httptest.NewRecorder()
|
|
|
|
req := httptest.NewRequest(http.MethodGet, calledURL, http.NoBody)
|
|
|
|
muxer.ServeHTTP(w, req)
|
|
results[calledURL] = w.Code
|
|
}
|
|
assert.Equal(t, test.expected, results)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPathRegexpMatcher(t *testing.T) {
|
|
testCases := []struct {
|
|
desc string
|
|
rule string
|
|
expected map[string]int
|
|
expectedError bool
|
|
}{
|
|
{
|
|
desc: "invalid PathRegexp matcher (no parameter)",
|
|
rule: "PathRegexp()",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "invalid PathRegexp matcher (empty parameter)",
|
|
rule: "PathRegexp(``)",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "invalid PathRegexp matcher (invalid regexp)",
|
|
rule: "PathRegexp(`/(css`)",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "invalid PathRegexp matcher (too many parameters)",
|
|
rule: "PathRegexp(`/css`, `/js`)",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "valid PathRegexp matcher",
|
|
rule: "PathRegexp(`^/(css|js)`)",
|
|
expected: map[string]int{
|
|
"https://example.com": http.StatusNotFound,
|
|
"https://example.com/html": http.StatusNotFound,
|
|
"https://example.org/css": http.StatusOK,
|
|
"https://example.com/CSS": http.StatusNotFound,
|
|
"https://example.com/css": http.StatusOK,
|
|
"https://example.com/css/": http.StatusOK,
|
|
"https://example.com/css/main.css": http.StatusOK,
|
|
"https://example.com/js": http.StatusOK,
|
|
"https://example.com/js/": http.StatusOK,
|
|
"https://example.com/js/main.js": http.StatusOK,
|
|
},
|
|
},
|
|
{
|
|
desc: "valid PathRegexp matcher with Traefik v2 syntax",
|
|
rule: `PathRegexp("/{path:(css|js)}")`,
|
|
expected: map[string]int{
|
|
"https://example.com": http.StatusNotFound,
|
|
"https://example.com/html": http.StatusNotFound,
|
|
"https://example.org/css": http.StatusNotFound,
|
|
"https://example.com/{path:css}": http.StatusOK,
|
|
"https://example.com/{path:css}/": http.StatusOK,
|
|
"https://example.com/%7Bpath:css%7D": http.StatusOK,
|
|
"https://example.com/%7Bpath:css%7D/": http.StatusOK,
|
|
"https://example.com/{path:js}": http.StatusOK,
|
|
"https://example.com/{path:js}/": http.StatusOK,
|
|
"https://example.com/%7Bpath:js%7D": http.StatusOK,
|
|
"https://example.com/%7Bpath:js%7D/": http.StatusOK,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range testCases {
|
|
test := test
|
|
t.Run(test.desc, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
|
muxer, err := NewMuxer()
|
|
require.NoError(t, err)
|
|
|
|
err = muxer.AddRoute(test.rule, 0, handler)
|
|
if test.expectedError {
|
|
require.Error(t, err)
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
|
|
results := make(map[string]int)
|
|
for calledURL := range test.expected {
|
|
w := httptest.NewRecorder()
|
|
|
|
req := httptest.NewRequest(http.MethodGet, calledURL, http.NoBody)
|
|
|
|
muxer.ServeHTTP(w, req)
|
|
results[calledURL] = w.Code
|
|
}
|
|
assert.Equal(t, test.expected, results)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPathPrefixMatcher(t *testing.T) {
|
|
testCases := []struct {
|
|
desc string
|
|
rule string
|
|
expected map[string]int
|
|
expectedError bool
|
|
}{
|
|
{
|
|
desc: "invalid PathPrefix matcher (no parameter)",
|
|
rule: "PathPrefix()",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "invalid PathPrefix matcher (empty parameter)",
|
|
rule: "PathPrefix(``)",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "invalid PathPrefix matcher (no leading /)",
|
|
rule: "PathPrefix(`css`)",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "invalid PathPrefix matcher (too many parameters)",
|
|
rule: "PathPrefix(`/css`, `/js`)",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "valid PathPrefix matcher",
|
|
rule: `PathPrefix("/css")`,
|
|
expected: map[string]int{
|
|
"https://example.com": http.StatusNotFound,
|
|
"https://example.com/html": http.StatusNotFound,
|
|
"https://example.org/css": http.StatusOK,
|
|
"https://example.com/css": http.StatusOK,
|
|
"https://example.com/css/": http.StatusOK,
|
|
"https://example.com/css/main.css": http.StatusOK,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range testCases {
|
|
test := test
|
|
t.Run(test.desc, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
|
muxer, err := NewMuxer()
|
|
require.NoError(t, err)
|
|
|
|
err = muxer.AddRoute(test.rule, 0, handler)
|
|
if test.expectedError {
|
|
require.Error(t, err)
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
|
|
results := make(map[string]int)
|
|
for calledURL := range test.expected {
|
|
w := httptest.NewRecorder()
|
|
|
|
req := httptest.NewRequest(http.MethodGet, calledURL, http.NoBody)
|
|
|
|
muxer.ServeHTTP(w, req)
|
|
results[calledURL] = w.Code
|
|
}
|
|
assert.Equal(t, test.expected, results)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHeaderMatcher(t *testing.T) {
|
|
testCases := []struct {
|
|
desc string
|
|
rule string
|
|
expected map[*http.Header]int
|
|
expectedError bool
|
|
}{
|
|
{
|
|
desc: "invalid Header matcher (no parameter)",
|
|
rule: "Header()",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "invalid Header matcher (missing value parameter)",
|
|
rule: "Header(`X-Forwarded-Host`)",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "invalid Header matcher (missing value parameter)",
|
|
rule: "Header(`X-Forwarded-Host`, ``)",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "invalid Header matcher (missing key parameter)",
|
|
rule: "Header(``, `example.com`)",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "invalid Header matcher (too many parameters)",
|
|
rule: "Header(`X-Forwarded-Host`, `example.com`, `example.org`)",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "valid Header matcher",
|
|
rule: "Header(`X-Forwarded-Proto`, `https`)",
|
|
expected: map[*http.Header]int{
|
|
{"X-Forwarded-Proto": []string{"https"}}: http.StatusOK,
|
|
{"x-forwarded-proto": []string{"https"}}: http.StatusNotFound,
|
|
{"X-Forwarded-Proto": []string{"http", "https"}}: http.StatusOK,
|
|
{"X-Forwarded-Proto": []string{"https", "http"}}: http.StatusOK,
|
|
{"X-Forwarded-Host": []string{"example.com"}}: http.StatusNotFound,
|
|
},
|
|
},
|
|
{
|
|
desc: "valid Header matcher (non-canonical form)",
|
|
rule: "Header(`x-forwarded-proto`, `https`)",
|
|
expected: map[*http.Header]int{
|
|
{"X-Forwarded-Proto": []string{"https"}}: http.StatusOK,
|
|
{"x-forwarded-proto": []string{"https"}}: http.StatusNotFound,
|
|
{"X-Forwarded-Proto": []string{"http", "https"}}: http.StatusOK,
|
|
{"X-Forwarded-Proto": []string{"https", "http"}}: http.StatusOK,
|
|
{"X-Forwarded-Host": []string{"example.com"}}: http.StatusNotFound,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range testCases {
|
|
test := test
|
|
|
|
t.Run(test.desc, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
|
muxer, err := NewMuxer()
|
|
require.NoError(t, err)
|
|
|
|
err = muxer.AddRoute(test.rule, 0, handler)
|
|
if test.expectedError {
|
|
require.Error(t, err)
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
|
|
for headers := range test.expected {
|
|
w := httptest.NewRecorder()
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "https://example.com", http.NoBody)
|
|
req.Header = *headers
|
|
|
|
muxer.ServeHTTP(w, req)
|
|
assert.Equal(t, test.expected[headers], w.Code, headers)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHeaderRegexpMatcher(t *testing.T) {
|
|
testCases := []struct {
|
|
desc string
|
|
rule string
|
|
expected map[*http.Header]int
|
|
expectedError bool
|
|
}{
|
|
{
|
|
desc: "invalid HeaderRegexp matcher (no parameter)",
|
|
rule: "HeaderRegexp()",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "invalid HeaderRegexp matcher (missing value parameter)",
|
|
rule: "HeaderRegexp(`X-Forwarded-Host`)",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "invalid HeaderRegexp matcher (missing value parameter)",
|
|
rule: "HeaderRegexp(`X-Forwarded-Host`, ``)",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "invalid HeaderRegexp matcher (missing key parameter)",
|
|
rule: "HeaderRegexp(``, `example.com`)",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "invalid HeaderRegexp matcher (invalid regexp)",
|
|
rule: "HeaderRegexp(`X-Forwarded-Host`,`(example.com`)",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "invalid HeaderRegexp matcher (too many parameters)",
|
|
rule: "HeaderRegexp(`X-Forwarded-Host`, `example.com`, `example.org`)",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "valid HeaderRegexp matcher",
|
|
rule: "HeaderRegexp(`X-Forwarded-Proto`, `^https?$`)",
|
|
expected: map[*http.Header]int{
|
|
{"X-Forwarded-Proto": []string{"http"}}: http.StatusOK,
|
|
{"x-forwarded-proto": []string{"http"}}: http.StatusNotFound,
|
|
{"X-Forwarded-Proto": []string{"https"}}: http.StatusOK,
|
|
{"X-Forwarded-Proto": []string{"HTTPS"}}: http.StatusNotFound,
|
|
{"X-Forwarded-Proto": []string{"ws", "https"}}: http.StatusOK,
|
|
{"X-Forwarded-Host": []string{"example.com"}}: http.StatusNotFound,
|
|
},
|
|
},
|
|
{
|
|
desc: "valid HeaderRegexp matcher (non-canonical form)",
|
|
rule: "HeaderRegexp(`x-forwarded-proto`, `^https?$`)",
|
|
expected: map[*http.Header]int{
|
|
{"X-Forwarded-Proto": []string{"http"}}: http.StatusOK,
|
|
{"x-forwarded-proto": []string{"http"}}: http.StatusNotFound,
|
|
{"X-Forwarded-Proto": []string{"https"}}: http.StatusOK,
|
|
{"X-Forwarded-Proto": []string{"HTTPS"}}: http.StatusNotFound,
|
|
{"X-Forwarded-Proto": []string{"ws", "https"}}: http.StatusOK,
|
|
{"X-Forwarded-Host": []string{"example.com"}}: http.StatusNotFound,
|
|
},
|
|
},
|
|
{
|
|
desc: "valid HeaderRegexp matcher with Traefik v2 syntax",
|
|
rule: "HeaderRegexp(`X-Forwarded-Proto`, `http{secure:s?}`)",
|
|
expected: map[*http.Header]int{
|
|
{"X-Forwarded-Proto": []string{"http"}}: http.StatusNotFound,
|
|
{"X-Forwarded-Proto": []string{"https"}}: http.StatusNotFound,
|
|
{"X-Forwarded-Proto": []string{"http{secure:}"}}: http.StatusOK,
|
|
{"X-Forwarded-Proto": []string{"HTTP{secure:}"}}: http.StatusNotFound,
|
|
{"X-Forwarded-Proto": []string{"http{secure:s}"}}: http.StatusOK,
|
|
{"X-Forwarded-Proto": []string{"http{secure:S}"}}: http.StatusNotFound,
|
|
{"X-Forwarded-Proto": []string{"HTTPS"}}: http.StatusNotFound,
|
|
{"X-Forwarded-Proto": []string{"ws", "http{secure:s}"}}: http.StatusOK,
|
|
{"X-Forwarded-Host": []string{"example.com"}}: http.StatusNotFound,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range testCases {
|
|
test := test
|
|
t.Run(test.desc, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
|
muxer, err := NewMuxer()
|
|
require.NoError(t, err)
|
|
|
|
err = muxer.AddRoute(test.rule, 0, handler)
|
|
if test.expectedError {
|
|
require.Error(t, err)
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
|
|
for headers := range test.expected {
|
|
w := httptest.NewRecorder()
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "https://example.com", http.NoBody)
|
|
req.Header = *headers
|
|
|
|
muxer.ServeHTTP(w, req)
|
|
assert.Equal(t, test.expected[headers], w.Code, *headers)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestQueryMatcher(t *testing.T) {
|
|
testCases := []struct {
|
|
desc string
|
|
rule string
|
|
expected map[string]int
|
|
expectedError bool
|
|
}{
|
|
{
|
|
desc: "invalid Query matcher (no parameter)",
|
|
rule: "Query()",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "invalid Query matcher (empty key, one parameter)",
|
|
rule: "Query(``)",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "invalid Query matcher (empty key)",
|
|
rule: "Query(``, `traefik`)",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "invalid Query matcher (empty value)",
|
|
rule: "Query(`q`, ``)",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "invalid Query matcher (too many parameters)",
|
|
rule: "Query(`q`, `traefik`, `proxy`)",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "valid Query matcher",
|
|
rule: "Query(`q`, `traefik`)",
|
|
expected: map[string]int{
|
|
"https://example.com": http.StatusNotFound,
|
|
"https://example.com?q=traefik": http.StatusOK,
|
|
"https://example.com?rel=ddg&q=traefik": http.StatusOK,
|
|
"https://example.com?q=traefik&q=proxy": http.StatusOK,
|
|
"https://example.com?q=awesome&q=traefik": http.StatusOK,
|
|
"https://example.com?q=nginx": http.StatusNotFound,
|
|
"https://example.com?rel=ddg": http.StatusNotFound,
|
|
"https://example.com?q=TRAEFIK": http.StatusNotFound,
|
|
"https://example.com?Q=traefik": http.StatusNotFound,
|
|
"https://example.com?rel=traefik": http.StatusNotFound,
|
|
},
|
|
},
|
|
{
|
|
desc: "valid Query matcher with empty value",
|
|
rule: "Query(`mobile`)",
|
|
expected: map[string]int{
|
|
"https://example.com": http.StatusNotFound,
|
|
"https://example.com?mobile": http.StatusOK,
|
|
"https://example.com?mobile=true": http.StatusNotFound,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range testCases {
|
|
test := test
|
|
t.Run(test.desc, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
|
muxer, err := NewMuxer()
|
|
require.NoError(t, err)
|
|
|
|
err = muxer.AddRoute(test.rule, 0, handler)
|
|
if test.expectedError {
|
|
require.Error(t, err)
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
|
|
results := make(map[string]int)
|
|
for calledURL := range test.expected {
|
|
w := httptest.NewRecorder()
|
|
|
|
req := httptest.NewRequest(http.MethodGet, calledURL, http.NoBody)
|
|
|
|
muxer.ServeHTTP(w, req)
|
|
results[calledURL] = w.Code
|
|
}
|
|
assert.Equal(t, test.expected, results)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestQueryRegexpMatcher(t *testing.T) {
|
|
testCases := []struct {
|
|
desc string
|
|
rule string
|
|
expected map[string]int
|
|
expectedError bool
|
|
}{
|
|
{
|
|
desc: "invalid QueryRegexp matcher (no parameter)",
|
|
rule: "QueryRegexp()",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "invalid QueryRegexp matcher (empty parameter)",
|
|
rule: "QueryRegexp(``)",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "invalid QueryRegexp matcher (invalid regexp)",
|
|
rule: "QueryRegexp(`q`, `(traefik`)",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "invalid QueryRegexp matcher (too many parameters)",
|
|
rule: "QueryRegexp(`q`, `traefik`, `proxy`)",
|
|
expectedError: true,
|
|
},
|
|
{
|
|
desc: "valid QueryRegexp matcher",
|
|
rule: "QueryRegexp(`q`, `^(traefik|nginx)$`)",
|
|
expected: map[string]int{
|
|
"https://example.com": http.StatusNotFound,
|
|
"https://example.com?q=traefik": http.StatusOK,
|
|
"https://example.com?rel=ddg&q=traefik": http.StatusOK,
|
|
"https://example.com?q=traefik&q=proxy": http.StatusOK,
|
|
"https://example.com?q=awesome&q=traefik": http.StatusOK,
|
|
"https://example.com?q=TRAEFIK": http.StatusNotFound,
|
|
"https://example.com?Q=traefik": http.StatusNotFound,
|
|
"https://example.com?rel=traefik": http.StatusNotFound,
|
|
"https://example.com?q=nginx": http.StatusOK,
|
|
"https://example.com?rel=ddg&q=nginx": http.StatusOK,
|
|
"https://example.com?q=nginx&q=proxy": http.StatusOK,
|
|
"https://example.com?q=awesome&q=nginx": http.StatusOK,
|
|
"https://example.com?q=NGINX": http.StatusNotFound,
|
|
"https://example.com?Q=nginx": http.StatusNotFound,
|
|
"https://example.com?rel=nginx": http.StatusNotFound,
|
|
"https://example.com?q=haproxy": http.StatusNotFound,
|
|
"https://example.com?rel=ddg": http.StatusNotFound,
|
|
},
|
|
},
|
|
{
|
|
desc: "valid QueryRegexp matcher",
|
|
rule: "QueryRegexp(`q`, `^.*$`)",
|
|
expected: map[string]int{
|
|
"https://example.com": http.StatusNotFound,
|
|
"https://example.com?q=traefik": http.StatusOK,
|
|
"https://example.com?rel=ddg&q=traefik": http.StatusOK,
|
|
"https://example.com?q=traefik&q=proxy": http.StatusOK,
|
|
"https://example.com?q=awesome&q=traefik": http.StatusOK,
|
|
"https://example.com?q=TRAEFIK": http.StatusOK,
|
|
"https://example.com?Q=traefik": http.StatusNotFound,
|
|
"https://example.com?rel=traefik": http.StatusNotFound,
|
|
"https://example.com?q=nginx": http.StatusOK,
|
|
"https://example.com?rel=ddg&q=nginx": http.StatusOK,
|
|
"https://example.com?q=nginx&q=proxy": http.StatusOK,
|
|
"https://example.com?q=awesome&q=nginx": http.StatusOK,
|
|
"https://example.com?q=NGINX": http.StatusOK,
|
|
"https://example.com?Q=nginx": http.StatusNotFound,
|
|
"https://example.com?rel=nginx": http.StatusNotFound,
|
|
"https://example.com?q=haproxy": http.StatusOK,
|
|
"https://example.com?rel=ddg": http.StatusNotFound,
|
|
},
|
|
},
|
|
{
|
|
desc: "valid QueryRegexp matcher with Traefik v2 syntax",
|
|
rule: "QueryRegexp(`q`, `{value:(traefik|nginx)}`)",
|
|
expected: map[string]int{
|
|
"https://example.com?q=traefik": http.StatusNotFound,
|
|
"https://example.com?q={value:traefik}": http.StatusOK,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range testCases {
|
|
test := test
|
|
t.Run(test.desc, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
|
muxer, err := NewMuxer()
|
|
require.NoError(t, err)
|
|
|
|
err = muxer.AddRoute(test.rule, 0, handler)
|
|
if test.expectedError {
|
|
require.Error(t, err)
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
|
|
results := make(map[string]int)
|
|
for calledURL := range test.expected {
|
|
w := httptest.NewRecorder()
|
|
|
|
req := httptest.NewRequest(http.MethodGet, calledURL, http.NoBody)
|
|
|
|
muxer.ServeHTTP(w, req)
|
|
results[calledURL] = w.Code
|
|
}
|
|
assert.Equal(t, test.expected, results)
|
|
})
|
|
}
|
|
}
|