1
0
Fork 0

Update routing syntax

Co-authored-by: Tom Moulard <tom.moulard@traefik.io>
This commit is contained in:
Antoine 2022-11-28 15:48:05 +01:00 committed by GitHub
parent b93141992e
commit 4d86668af3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 2484 additions and 2085 deletions

View file

@ -7,14 +7,13 @@ import (
"net/http/httptest"
"testing"
"github.com/gorilla/mux"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/traefik/traefik/v2/pkg/middlewares/requestdecorator"
"github.com/traefik/traefik/v2/pkg/testhelpers"
)
func Test_addRoute(t *testing.T) {
func TestMuxer(t *testing.T) {
testCases := []struct {
desc string
rule string
@ -33,607 +32,177 @@ func Test_addRoute(t *testing.T) {
expectedError: true,
},
{
desc: "Host empty",
rule: "Host(``)",
desc: "Rule without quote",
rule: "Host(example.com)",
expectedError: true,
},
{
desc: "PathPrefix empty",
rule: "PathPrefix(``)",
expectedError: true,
},
{
desc: "PathPrefix",
rule: "PathPrefix(`/foo`)",
expected: map[string]int{
"http://localhost/foo": http.StatusOK,
},
},
{
desc: "wrong PathPrefix",
rule: "PathPrefix(`/bar`)",
expected: map[string]int{
"http://localhost/foo": http.StatusNotFound,
},
},
{
desc: "Host",
rule: "Host(`localhost`)",
expected: map[string]int{
"http://localhost/foo": http.StatusOK,
},
},
{
desc: "Non-ASCII Host",
rule: "Host(`locàlhost`)",
expectedError: true,
},
{
desc: "Non-ASCII HostRegexp",
rule: "HostRegexp(`locàlhost`)",
expectedError: true,
},
{
desc: "HostHeader equivalent to Host",
rule: "HostHeader(`localhost`)",
expected: map[string]int{
"http://localhost/foo": http.StatusOK,
"http://bar/foo": http.StatusNotFound,
},
},
{
desc: "Host with trailing period in rule",
rule: "Host(`localhost.`)",
expected: map[string]int{
"http://localhost/foo": http.StatusOK,
},
},
{
desc: "Host with trailing period in domain",
rule: "Host(`localhost`)",
expected: map[string]int{
"http://localhost./foo": http.StatusOK,
},
},
{
desc: "Host with trailing period in domain and rule",
rule: "Host(`localhost.`)",
expected: map[string]int{
"http://localhost./foo": http.StatusOK,
},
},
{
desc: "wrong Host",
rule: "Host(`nope`)",
expected: map[string]int{
"http://localhost/foo": http.StatusNotFound,
},
},
{
desc: "Host and PathPrefix",
rule: "Host(`localhost`) && PathPrefix(`/foo`)",
rule: "Host(`localhost`) && PathPrefix(`/css`)",
expected: map[string]int{
"http://localhost/foo": http.StatusOK,
},
},
{
desc: "Host and PathPrefix wrong PathPrefix",
rule: "Host(`localhost`) && PathPrefix(`/bar`)",
expected: map[string]int{
"http://localhost/foo": http.StatusNotFound,
},
},
{
desc: "Host and PathPrefix wrong Host",
rule: "Host(`nope`) && PathPrefix(`/foo`)",
expected: map[string]int{
"http://localhost/foo": http.StatusNotFound,
},
},
{
desc: "Host and PathPrefix Host OR, first host",
rule: "Host(`nope`,`localhost`) && PathPrefix(`/foo`)",
expected: map[string]int{
"http://localhost/foo": http.StatusOK,
},
},
{
desc: "Host and PathPrefix Host OR, second host",
rule: "Host(`nope`,`localhost`) && PathPrefix(`/foo`)",
expected: map[string]int{
"http://nope/foo": http.StatusOK,
},
},
{
desc: "Host and PathPrefix Host OR, first host and wrong PathPrefix",
rule: "Host(`nope,localhost`) && PathPrefix(`/bar`)",
expected: map[string]int{
"http://localhost/foo": http.StatusNotFound,
},
},
{
desc: "HostRegexp with capturing group",
rule: "HostRegexp(`{subdomain:(foo\\.)?bar\\.com}`)",
expected: map[string]int{
"http://foo.bar.com": http.StatusOK,
"http://bar.com": http.StatusOK,
"http://fooubar.com": http.StatusNotFound,
"http://barucom": http.StatusNotFound,
"http://barcom": http.StatusNotFound,
},
},
{
desc: "HostRegexp with non capturing group",
rule: "HostRegexp(`{subdomain:(?:foo\\.)?bar\\.com}`)",
expected: map[string]int{
"http://foo.bar.com": http.StatusOK,
"http://bar.com": http.StatusOK,
"http://fooubar.com": http.StatusNotFound,
"http://barucom": http.StatusNotFound,
"http://barcom": http.StatusNotFound,
},
},
{
desc: "Methods with GET",
rule: "Method(`GET`)",
expected: map[string]int{
"http://localhost/foo": http.StatusOK,
},
},
{
desc: "Methods with GET and POST",
rule: "Method(`GET`,`POST`)",
expected: map[string]int{
"http://localhost/foo": http.StatusOK,
},
},
{
desc: "Methods with POST",
rule: "Method(`POST`)",
expected: map[string]int{
"http://localhost/foo": http.StatusMethodNotAllowed,
},
},
{
desc: "Header with matching header",
rule: "Headers(`Content-Type`,`application/json`)",
headers: map[string]string{
"Content-Type": "application/json",
},
expected: map[string]int{
"http://localhost/foo": http.StatusOK,
},
},
{
desc: "Header without matching header",
rule: "Headers(`Content-Type`,`application/foo`)",
headers: map[string]string{
"Content-Type": "application/json",
},
expected: map[string]int{
"http://localhost/foo": http.StatusNotFound,
},
},
{
desc: "HeaderRegExp with matching header",
rule: "HeadersRegexp(`Content-Type`, `application/(text|json)`)",
headers: map[string]string{
"Content-Type": "application/json",
},
expected: map[string]int{
"http://localhost/foo": http.StatusOK,
},
},
{
desc: "HeaderRegExp without matching header",
rule: "HeadersRegexp(`Content-Type`, `application/(text|json)`)",
headers: map[string]string{
"Content-Type": "application/foo",
},
expected: map[string]int{
"http://localhost/foo": http.StatusNotFound,
},
},
{
desc: "HeaderRegExp with matching second header",
rule: "HeadersRegexp(`Content-Type`, `application/(text|json)`)",
headers: map[string]string{
"Content-Type": "application/text",
},
expected: map[string]int{
"http://localhost/foo": http.StatusOK,
},
},
{
desc: "Query with multiple params",
rule: "Query(`foo=bar`, `bar=baz`)",
expected: map[string]int{
"http://localhost/foo?foo=bar&bar=baz": http.StatusOK,
"http://localhost/foo?bar=baz": http.StatusNotFound,
},
},
{
desc: "Query with multiple equals",
rule: "Query(`foo=b=ar`)",
expected: map[string]int{
"http://localhost/foo?foo=b=ar": http.StatusOK,
"http://localhost/foo?foo=bar": http.StatusNotFound,
},
},
{
desc: "Rule with simple path",
rule: `Path("/a")`,
expected: map[string]int{
"http://plop/a": http.StatusOK,
},
},
{
desc: `Rule with a simple host`,
rule: `Host("plop")`,
expected: map[string]int{
"http://plop": http.StatusOK,
},
},
{
desc: "Rule with Path AND Host",
rule: `Path("/a") && Host("plop")`,
expected: map[string]int{
"http://plop/a": http.StatusOK,
"http://plopi/a": http.StatusNotFound,
"https://localhost/css": http.StatusOK,
"https://localhost/js": http.StatusNotFound,
},
},
{
desc: "Rule with Host OR Host",
rule: `Host("tchouk") || Host("pouet")`,
rule: "Host(`example.com`) || Host(`example.org`)",
expected: map[string]int{
"http://tchouk/toto": http.StatusOK,
"http://pouet/a": http.StatusOK,
"http://plopi/a": http.StatusNotFound,
"https://example.com/css": http.StatusOK,
"https://example.org/js": http.StatusOK,
"https://example.eu/html": http.StatusNotFound,
},
},
{
desc: "Rule with host OR (host AND path)",
rule: `Host("tchouk") || (Host("pouet") && Path("/powpow"))`,
rule: `Host("example.com") || (Host("example.org") && Path("/css"))`,
expected: map[string]int{
"http://tchouk/toto": http.StatusOK,
"http://tchouk/powpow": http.StatusOK,
"http://pouet/powpow": http.StatusOK,
"http://pouet/toto": http.StatusNotFound,
"http://plopi/a": http.StatusNotFound,
"https://example.com/css": http.StatusOK,
"https://example.com/js": http.StatusOK,
"https://example.org/css": http.StatusOK,
"https://example.org/js": http.StatusNotFound,
"https://example.eu/css": http.StatusNotFound,
},
},
{
desc: "Rule with host OR host AND path",
rule: `Host("tchouk") || Host("pouet") && Path("/powpow")`,
rule: `Host("example.com") || Host("example.org") && Path("/css")`,
expected: map[string]int{
"http://tchouk/toto": http.StatusOK,
"http://tchouk/powpow": http.StatusOK,
"http://pouet/powpow": http.StatusOK,
"http://pouet/toto": http.StatusNotFound,
"http://plopi/a": http.StatusNotFound,
"https://example.com/css": http.StatusOK,
"https://example.com/js": http.StatusOK,
"https://example.org/css": http.StatusOK,
"https://example.org/js": http.StatusNotFound,
"https://example.eu/css": http.StatusNotFound,
},
},
{
desc: "Rule with (host OR host) AND path",
rule: `(Host("tchouk") || Host("pouet")) && Path("/powpow")`,
rule: `(Host("example.com") || Host("example.org")) && Path("/css")`,
expected: map[string]int{
"http://tchouk/toto": http.StatusNotFound,
"http://tchouk/powpow": http.StatusOK,
"http://pouet/powpow": http.StatusOK,
"http://pouet/toto": http.StatusNotFound,
"http://plopi/a": http.StatusNotFound,
},
},
{
desc: "Rule with multiple host AND path",
rule: `(Host("tchouk","pouet")) && Path("/powpow")`,
expected: map[string]int{
"http://tchouk/toto": http.StatusNotFound,
"http://tchouk/powpow": http.StatusOK,
"http://pouet/powpow": http.StatusOK,
"http://pouet/toto": http.StatusNotFound,
"http://plopi/a": http.StatusNotFound,
},
},
{
desc: "Rule with multiple host AND multiple path",
rule: `Host("tchouk","pouet") && Path("/powpow", "/titi")`,
expected: map[string]int{
"http://tchouk/toto": http.StatusNotFound,
"http://tchouk/powpow": http.StatusOK,
"http://pouet/powpow": http.StatusOK,
"http://tchouk/titi": http.StatusOK,
"http://pouet/titi": http.StatusOK,
"http://pouet/toto": http.StatusNotFound,
"http://plopi/a": http.StatusNotFound,
"https://example.com/css": http.StatusOK,
"https://example.com/js": http.StatusNotFound,
"https://example.org/css": http.StatusOK,
"https://example.org/js": http.StatusNotFound,
"https://example.eu/css": http.StatusNotFound,
},
},
{
desc: "Rule with (host AND path) OR (host AND path)",
rule: `(Host("tchouk") && Path("/titi")) || ((Host("pouet")) && Path("/powpow"))`,
rule: `(Host("example.com") && Path("/js")) || ((Host("example.org")) && Path("/css"))`,
expected: map[string]int{
"http://tchouk/titi": http.StatusOK,
"http://tchouk/powpow": http.StatusNotFound,
"http://pouet/powpow": http.StatusOK,
"http://pouet/toto": http.StatusNotFound,
"http://plopi/a": http.StatusNotFound,
"https://example.com/css": http.StatusNotFound,
"https://example.com/js": http.StatusOK,
"https://example.org/css": http.StatusOK,
"https://example.org/js": http.StatusNotFound,
"https://example.eu/css": http.StatusNotFound,
},
},
{
desc: "Rule without quote",
rule: `Host(tchouk)`,
expectedError: true,
},
{
desc: "Rule case UPPER",
rule: `(HOST("tchouk") && PATHPREFIX("/titi"))`,
rule: `PATHPREFIX("/css")`,
expected: map[string]int{
"http://tchouk/titi": http.StatusOK,
"http://tchouk/powpow": http.StatusNotFound,
"https://example.com/css": http.StatusOK,
"https://example.com/js": http.StatusNotFound,
},
},
{
desc: "Rule case lower",
rule: `(host("tchouk") && pathprefix("/titi"))`,
rule: `pathprefix("/css")`,
expected: map[string]int{
"http://tchouk/titi": http.StatusOK,
"http://tchouk/powpow": http.StatusNotFound,
"https://example.com/css": http.StatusOK,
"https://example.com/js": http.StatusNotFound,
},
},
{
desc: "Rule case CamelCase",
rule: `(Host("tchouk") && PathPrefix("/titi"))`,
rule: `PathPrefix("/css")`,
expected: map[string]int{
"http://tchouk/titi": http.StatusOK,
"http://tchouk/powpow": http.StatusNotFound,
"https://example.com/css": http.StatusOK,
"https://example.com/js": http.StatusNotFound,
},
},
{
desc: "Rule case Title",
rule: `(Host("tchouk") && Pathprefix("/titi"))`,
rule: `Pathprefix("/css")`,
expected: map[string]int{
"http://tchouk/titi": http.StatusOK,
"http://tchouk/powpow": http.StatusNotFound,
"https://example.com/css": http.StatusOK,
"https://example.com/js": http.StatusNotFound,
},
},
{
desc: "Rule Path with error",
rule: `Path("titi")`,
expectedError: true,
},
{
desc: "Rule PathPrefix with error",
rule: `PathPrefix("titi")`,
expectedError: true,
},
{
desc: "Rule HostRegexp with error",
rule: `HostRegexp("{test")`,
expectedError: true,
},
{
desc: "Rule Headers with error",
rule: `Headers("titi")`,
expectedError: true,
},
{
desc: "Rule HeadersRegexp with error",
rule: `HeadersRegexp("titi")`,
expectedError: true,
},
{
desc: "Rule Query",
rule: `Query("titi")`,
expectedError: true,
},
{
desc: "Rule Query with bad syntax",
rule: `Query("titi={test")`,
expectedError: true,
},
{
desc: "Rule with Path without args",
rule: `Host("tchouk") && Path()`,
expectedError: true,
},
{
desc: "Rule with an empty path",
rule: `Host("tchouk") && Path("")`,
expectedError: true,
},
{
desc: "Rule with an empty path",
rule: `Host("tchouk") && Path("", "/titi")`,
expectedError: true,
},
{
desc: "Rule with not",
rule: `!Host("tchouk")`,
rule: `!Host("example.com")`,
expected: map[string]int{
"http://tchouk/titi": http.StatusNotFound,
"http://test/powpow": http.StatusOK,
},
},
{
desc: "Rule with not on Path",
rule: `!Path("/titi")`,
expected: map[string]int{
"http://tchouk/titi": http.StatusNotFound,
"http://tchouk/powpow": http.StatusOK,
"https://example.org": http.StatusOK,
"https://example.com": http.StatusNotFound,
},
},
{
desc: "Rule with not on multiple route with or",
rule: `!(Host("tchouk") || Host("toto"))`,
rule: `!(Host("example.com") || Host("example.org"))`,
expected: map[string]int{
"http://tchouk/titi": http.StatusNotFound,
"http://toto/powpow": http.StatusNotFound,
"http://test/powpow": http.StatusOK,
"https://example.eu/js": http.StatusOK,
"https://example.com/css": http.StatusNotFound,
"https://example.org/js": http.StatusNotFound,
},
},
{
desc: "Rule with not on multiple route with and",
rule: `!(Host("tchouk") && Path("/titi"))`,
rule: `!(Host("example.com") && Path("/css"))`,
expected: map[string]int{
"http://tchouk/titi": http.StatusNotFound,
"http://tchouk/toto": http.StatusOK,
"http://test/titi": http.StatusOK,
"https://example.com/js": http.StatusOK,
"https://example.eu/css": http.StatusOK,
"https://example.com/css": http.StatusNotFound,
},
},
{
desc: "Rule with not on multiple route with and another not",
rule: `!(Host("tchouk") && !Path("/titi"))`,
rule: `!(Host("example.com") && !Path("/css"))`,
expected: map[string]int{
"http://tchouk/titi": http.StatusOK,
"http://toto/titi": http.StatusOK,
"http://tchouk/toto": http.StatusNotFound,
"https://example.com/css": http.StatusOK,
"https://example.org/css": http.StatusOK,
"https://example.com/js": http.StatusNotFound,
},
},
{
desc: "Rule with not on two rule",
rule: `!Host("tchouk") || !Path("/titi")`,
rule: `!Host("example.com") || !Path("/css")`,
expected: map[string]int{
"http://tchouk/titi": http.StatusNotFound,
"http://tchouk/toto": http.StatusOK,
"http://test/titi": http.StatusOK,
"https://example.com/js": http.StatusOK,
"https://example.org/css": http.StatusOK,
"https://example.com/css": http.StatusNotFound,
},
},
{
desc: "Rule case with double not",
rule: `!(!(Host("tchouk") && Pathprefix("/titi")))`,
rule: `!(!(Host("example.com") && Pathprefix("/css")))`,
expected: map[string]int{
"http://tchouk/titi": http.StatusOK,
"http://tchouk/powpow": http.StatusNotFound,
"http://test/titi": http.StatusNotFound,
"https://example.com/css": http.StatusOK,
"https://example.com/js": http.StatusNotFound,
"https://example.org/css": http.StatusNotFound,
},
},
{
desc: "Rule case with not domain",
rule: `!Host("tchouk") && Pathprefix("/titi")`,
rule: `!Host("example.com") && Pathprefix("/css")`,
expected: map[string]int{
"http://tchouk/titi": http.StatusNotFound,
"http://tchouk/powpow": http.StatusNotFound,
"http://toto/powpow": http.StatusNotFound,
"http://toto/titi": http.StatusOK,
"https://example.org/css": http.StatusOK,
"https://example.org/js": http.StatusNotFound,
"https://example.com/css": http.StatusNotFound,
"https://example.com/js": http.StatusNotFound,
},
},
{
desc: "Rule with multiple host AND multiple path AND not",
rule: `!(Host("tchouk","pouet") && Path("/powpow", "/titi"))`,
rule: `!(Host("example.com") && Path("/js"))`,
expected: map[string]int{
"http://tchouk/toto": http.StatusOK,
"http://tchouk/powpow": http.StatusNotFound,
"http://pouet/powpow": http.StatusNotFound,
"http://tchouk/titi": http.StatusNotFound,
"http://pouet/titi": http.StatusNotFound,
"http://pouet/toto": http.StatusOK,
"http://plopi/a": http.StatusOK,
},
},
{
desc: "ClientIP empty",
rule: "ClientIP(``)",
expectedError: true,
},
{
desc: "Invalid ClientIP",
rule: "ClientIP(`invalid`)",
expectedError: true,
},
{
desc: "Non matching ClientIP",
rule: "ClientIP(`10.10.1.1`)",
remoteAddr: "10.0.0.0",
expected: map[string]int{
"http://tchouk/toto": http.StatusNotFound,
},
},
{
desc: "Non matching IPv6",
rule: "ClientIP(`10::10`)",
remoteAddr: "::1",
expected: map[string]int{
"http://tchouk/toto": http.StatusNotFound,
},
},
{
desc: "Matching IP",
rule: "ClientIP(`10.0.0.0`)",
remoteAddr: "10.0.0.0:8456",
expected: map[string]int{
"http://tchouk/toto": http.StatusOK,
},
},
{
desc: "Matching IPv6",
rule: "ClientIP(`10::10`)",
remoteAddr: "10::10",
expected: map[string]int{
"http://tchouk/toto": http.StatusOK,
},
},
{
desc: "Matching IP among several IP",
rule: "ClientIP(`10.0.0.1`, `10.0.0.0`)",
remoteAddr: "10.0.0.0",
expected: map[string]int{
"http://tchouk/toto": http.StatusOK,
},
},
{
desc: "Non Matching IP with CIDR",
rule: "ClientIP(`11.0.0.0/24`)",
remoteAddr: "10.0.0.0",
expected: map[string]int{
"http://tchouk/toto": http.StatusNotFound,
},
},
{
desc: "Non Matching IPv6 with CIDR",
rule: "ClientIP(`11::/16`)",
remoteAddr: "10::",
expected: map[string]int{
"http://tchouk/toto": http.StatusNotFound,
},
},
{
desc: "Matching IP with CIDR",
rule: "ClientIP(`10.0.0.0/16`)",
remoteAddr: "10.0.0.0",
expected: map[string]int{
"http://tchouk/toto": http.StatusOK,
},
},
{
desc: "Matching IPv6 with CIDR",
rule: "ClientIP(`10::/16`)",
remoteAddr: "10::10",
expected: map[string]int{
"http://tchouk/toto": http.StatusOK,
},
},
{
desc: "Matching IP among several CIDR",
rule: "ClientIP(`11.0.0.0/16`, `10.0.0.0/16`)",
remoteAddr: "10.0.0.0",
expected: map[string]int{
"http://tchouk/toto": http.StatusOK,
},
},
{
desc: "Matching IP among non matching CIDR and matching IP",
rule: "ClientIP(`11.0.0.0/16`, `10.0.0.0`)",
remoteAddr: "10.0.0.0",
expected: map[string]int{
"http://tchouk/toto": http.StatusOK,
},
},
{
desc: "Matching IP among matching CIDR and non matching IP",
rule: "ClientIP(`11.0.0.0`, `10.0.0.0/16`)",
remoteAddr: "10.0.0.0",
expected: map[string]int{
"http://tchouk/toto": http.StatusOK,
"https://example.com/js": http.StatusNotFound,
"https://example.com/html": http.StatusOK,
"https://example.org/js": http.StatusOK,
"https://example.com/css": http.StatusOK,
"https://example.org/css": http.StatusOK,
"https://example.org/html": http.StatusOK,
"https://example.eu/images": http.StatusOK,
},
},
}
@ -644,36 +213,37 @@ func Test_addRoute(t *testing.T) {
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)
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
err = muxer.AddRoute(test.rule, 0, handler)
if test.expectedError {
require.Error(t, err)
} else {
require.NoError(t, err)
// RequestDecorator is necessary for the host rule
reqHost := requestdecorator.New(nil)
results := make(map[string]int)
for calledURL := range test.expected {
w := httptest.NewRecorder()
req := testhelpers.MustNewRequest(http.MethodGet, calledURL, nil)
// Useful for the ClientIP matcher
req.RemoteAddr = test.remoteAddr
for key, value := range test.headers {
req.Header.Set(key, value)
}
reqHost.ServeHTTP(w, req, muxer.ServeHTTP)
results[calledURL] = w.Code
}
assert.Equal(t, test.expected, results)
return
}
require.NoError(t, err)
// RequestDecorator is necessary for the host rule
reqHost := requestdecorator.New(nil)
results := make(map[string]int)
for calledURL := range test.expected {
req := testhelpers.MustNewRequest(http.MethodGet, calledURL, http.NoBody)
// Useful for the ClientIP matcher
req.RemoteAddr = test.remoteAddr
for key, value := range test.headers {
req.Header.Set(key, value)
}
w := httptest.NewRecorder()
reqHost.ServeHTTP(w, req, muxer.ServeHTTP)
results[calledURL] = w.Code
}
assert.Equal(t, test.expected, results)
})
}
}
@ -813,7 +383,7 @@ func Test_addRoutePriority(t *testing.T) {
muxer.SortRoutes()
w := httptest.NewRecorder()
req := testhelpers.MustNewRequest(http.MethodGet, test.path, nil)
req := testhelpers.MustNewRequest(http.MethodGet, test.path, http.NoBody)
muxer.ServeHTTP(w, req)
@ -822,86 +392,6 @@ func Test_addRoutePriority(t *testing.T) {
}
}
func TestHostRegexp(t *testing.T) {
testCases := []struct {
desc string
hostExp string
urls map[string]bool
}{
{
desc: "capturing group",
hostExp: "{subdomain:(foo\\.)?bar\\.com}",
urls: map[string]bool{
"http://foo.bar.com": true,
"http://bar.com": true,
"http://fooubar.com": false,
"http://barucom": false,
"http://barcom": false,
},
},
{
desc: "non capturing group",
hostExp: "{subdomain:(?:foo\\.)?bar\\.com}",
urls: map[string]bool{
"http://foo.bar.com": true,
"http://bar.com": true,
"http://fooubar.com": false,
"http://barucom": false,
"http://barcom": false,
},
},
{
desc: "regex insensitive",
hostExp: "{dummy:[A-Za-z-]+\\.bar\\.com}",
urls: map[string]bool{
"http://FOO.bar.com": true,
"http://foo.bar.com": true,
"http://fooubar.com": false,
"http://barucom": false,
"http://barcom": false,
},
},
{
desc: "insensitive host",
hostExp: "{dummy:[a-z-]+\\.bar\\.com}",
urls: map[string]bool{
"http://FOO.bar.com": true,
"http://foo.bar.com": true,
"http://fooubar.com": false,
"http://barucom": false,
"http://barcom": false,
},
},
{
desc: "insensitive host simple",
hostExp: "foo.bar.com",
urls: map[string]bool{
"http://FOO.bar.com": true,
"http://foo.bar.com": true,
"http://fooubar.com": false,
"http://barucom": false,
"http://barcom": false,
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
rt := &mux.Route{}
err := hostRegexp(rt, test.hostExp)
require.NoError(t, err)
for testURL, match := range test.urls {
req := testhelpers.MustNewRequest(http.MethodGet, testURL, nil)
assert.Equal(t, match, rt.Match(req, &mux.RouteMatch{}), testURL)
}
})
}
}
func TestParseDomains(t *testing.T) {
testCases := []struct {
description string
@ -914,21 +404,6 @@ func TestParseDomains(t *testing.T) {
expression: "Foobar(`foo.bar`,`test.bar`)",
errorExpected: true,
},
{
description: "Several host rules",
expression: "Host(`foo.bar`,`test.bar`)",
domain: []string{"foo.bar", "test.bar"},
},
{
description: "Several host rules upper",
expression: "HOST(`foo.bar`,`test.bar`)",
domain: []string{"foo.bar", "test.bar"},
},
{
description: "Several host rules lower",
expression: "host(`foo.bar`,`test.bar`)",
domain: []string{"foo.bar", "test.bar"},
},
{
description: "No host rule",
expression: "Path(`/test`)",
@ -938,6 +413,11 @@ func TestParseDomains(t *testing.T) {
expression: "Host(`foo.bar`) && Path(`/test`)",
domain: []string{"foo.bar"},
},
{
description: "Host rule to trim and another rule",
expression: "Host(`Foo.Bar`) || Host(`bar.buz`) && Path(`/test`)",
domain: []string{"foo.bar", "bar.buz"},
},
{
description: "Host rule to trim and another rule",
expression: "Host(`Foo.Bar`) && Path(`/test`)",
@ -967,7 +447,9 @@ func TestParseDomains(t *testing.T) {
}
}
func TestAbsoluteFormURL(t *testing.T) {
// TestEmptyHost is a non regression test for
// https://github.com/traefik/traefik/pull/9131
func TestEmptyHost(t *testing.T) {
testCases := []struct {
desc string
request string
@ -975,41 +457,41 @@ func TestAbsoluteFormURL(t *testing.T) {
expected int
}{
{
desc: "!HostRegexp with absolute-form URL with empty host with non-matching host header",
request: "GET http://@/ HTTP/1.1\r\nHost: test.localhost\r\n\r\n",
rule: "!HostRegexp(`test.localhost`)",
expected: http.StatusNotFound,
},
{
desc: "!Host with absolute-form URL with empty host with non-matching host header",
request: "GET http://@/ HTTP/1.1\r\nHost: test.localhost\r\n\r\n",
rule: "!Host(`test.localhost`)",
expected: http.StatusNotFound,
},
{
desc: "!HostRegexp with absolute-form URL with matching host header",
request: "GET http://test.localhost/ HTTP/1.1\r\nHost: toto.localhost\r\n\r\n",
rule: "!HostRegexp(`test.localhost`)",
expected: http.StatusNotFound,
},
{
desc: "!Host with absolute-form URL with matching host header",
request: "GET http://test.localhost/ HTTP/1.1\r\nHost: toto.localhost\r\n\r\n",
rule: "!Host(`test.localhost`)",
expected: http.StatusNotFound,
},
{
desc: "!HostRegexp with absolute-form URL with non-matching host header",
request: "GET http://test.localhost/ HTTP/1.1\r\nHost: toto.localhost\r\n\r\n",
rule: "!HostRegexp(`toto.localhost`)",
desc: "HostRegexp with absolute-form URL with empty host with non-matching host header",
request: "GET http://@/ HTTP/1.1\r\nHost: example.com\r\n\r\n",
rule: "HostRegexp(`example.com`)",
expected: http.StatusOK,
},
{
desc: "!Host with absolute-form URL with non-matching host header",
request: "GET http://test.localhost/ HTTP/1.1\r\nHost: toto.localhost\r\n\r\n",
rule: "!Host(`toto.localhost`)",
desc: "Host with absolute-form URL with empty host with non-matching host header",
request: "GET http://@/ HTTP/1.1\r\nHost: example.com\r\n\r\n",
rule: "Host(`example.com`)",
expected: http.StatusOK,
},
{
desc: "HostRegexp with absolute-form URL with matching host header",
request: "GET http://example.com/ HTTP/1.1\r\nHost: example.org\r\n\r\n",
rule: "HostRegexp(`example.com`)",
expected: http.StatusOK,
},
{
desc: "Host with absolute-form URL with matching host header",
request: "GET http://example.com/ HTTP/1.1\r\nHost: example.org\r\n\r\n",
rule: "Host(`example.com`)",
expected: http.StatusOK,
},
{
desc: "HostRegexp with absolute-form URL with non-matching host header",
request: "GET http://example.com/ HTTP/1.1\r\nHost: example.org\r\n\r\n",
rule: "HostRegexp(`example.org`)",
expected: http.StatusNotFound,
},
{
desc: "Host with absolute-form URL with non-matching host header",
request: "GET http://example.com/ HTTP/1.1\r\nHost: example.org\r\n\r\n",
rule: "Host(`example.org`)",
expected: http.StatusNotFound,
},
}
for _, test := range testCases {