Download
130 lines 4.4 KiBGoModified 2026-10-20 raw
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
package httpd

import (
	"strings"
	"testing"
)

// TestPatternMatch checks the port of patterns(7) against the cases the
// site's config depends on, and Lua's own semantics around them.
func TestPatternMatch(t *testing.T) {
	for _, c := range []struct {
		s, pat string
		want   []string // nil: no match
	}{
		{"/code/r/src/", "^/code/(.+)/$", []string{"/code/r/src/", "r/src"}},
		{"/code/r/src", "^/code/(.+)/$", nil},
		{"/x/code/r", "^/code/(.+)$", nil},
		{"/code/r/src/", "^/code/(..-)/?$", []string{"/code/r/src/", "r/src"}},
		{"/code/r/src", "^/code/(..-)/?$", []string{"/code/r/src", "r/src"}},
		{"/code/r", "^/code/(..-)/?$", []string{"/code/r", "r"}},
		{"/code/", "^/code/(..-)/?$", nil},
		{"/CODE/r", "^/code/(..-)/?$", nil}, // case matters
		{"/x/code/r", "/code/(.+)$", []string{"/code/r", "r"}},
		{"/git/a.git/HEAD", "^/git/[^/]+%.git/HEAD$", []string{"/git/a.git/HEAD"}},
		{"/git/aXgit/HEAD", "^/git/[^/]+%.git/HEAD$", nil},
		{"/git/a/b.git/HEAD", "^/git/[^/]+%.git/HEAD$", nil},
		{"/p/pack-0fA9.idx", "pack%-%x+%.idx$", []string{"pack-0fA9.idx"}},
		{"/p/pack-0fg9.idx", "pack%-%x+%.idx$", nil},
		{"aaab", "a-b", []string{"aaab"}},
		{"ab", "^a?b$", []string{"ab"}},
		{"b", "^a?b$", []string{"b"}},
		{"a]", "^%a[]]$", []string{"a]"}}, // ] first in a set is literal
		{"a^b", "a^b", []string{"a^b"}},   // ^ is literal past the start
		{"a$b", "a$b", []string{"a$b"}},   // and $ before the end
		{"x1-y", "^%w+[%d%-]+%l$", []string{"x1-y"}},
		{"x!", "^%g%p$", []string{"x!"}},
		{"((a)(b))", "^%((%a)%((%a)%)%)$", nil},
		{"((a)(b))", "^%(%((%a)%)%((%a)%)%)$", []string{"((a)(b))", "a", "b"}},
		{strings.Repeat("a", 300), "^" + strings.Repeat("a?", 300) + "$", nil}, // too complex
	} {
		got, ok := PatternLocation(c.pat).Match(c.s)
		if ok != (c.want != nil) || strings.Join(got, "|") != strings.Join(c.want, "|") {
			t.Errorf("match %q in %q = %q, %v; want %q", c.pat, c.s, got, ok, c.want)
		}
	}
}

func TestPatternCheck(t *testing.T) {
	for pat, bad := range map[string]bool{
		"^/code/(..-)/?$": false, "[]]": false, "%%": false, "[%]]": false,
		"%": true, "[a": true, "[]": true, "[%": true, "(a": true, "a)": true,
		"()": true, "%b()": true, "%f[a]": true, "(a)%1": true,
		strings.Repeat("(a)", 33): true, `a\.b`: true, `a"b`: true,
	} {
		err := PatternLocation(pat).Check()
		if (err != nil) != bad {
			t.Errorf("Check(%q) = %v, want an error: %v", pat, err, bad)
		}
		if bad {
			if _, ok := PatternLocation(pat).Match(pat); ok {
				t.Errorf("%q: matched despite failing Check", pat)
			}
		}
	}
}

func TestGlob(t *testing.T) {
	for _, c := range []struct {
		glob, s string
		want    bool
	}{
		{"/static/*", "/static/style.css", true},
		{"/static/*", "/static/a/b", true}, // * matches / too
		{"/static/*", "/STATIC/x", true},   // case is folded
		{"/static/*", "/staticx", false},
		{"/git/*/objects/pack/*", "/git/a.git/objects/pack/p.idx", true},
		{"/git/*/objects/pack/*", "/git/a/b.git/objects/pack/p.idx", true},
		{"/git/*/objects/pack/*", "/git/a.git/objects/p.idx", false},
		{"/a", "/a", true},
		{"/a", "/a/", false},
		{"/a?", "/ab", false}, // refused: matches nothing
		{"/[a]", "/a", false},
	} {
		if _, got := GlobLocation(c.glob).Match(c.s); got != c.want {
			t.Errorf("glob %q on %q = %v, want %v", c.glob, c.s, got, c.want)
		}
	}
	for _, g := range []string{"/a?", "/[a]", `/a\*`, `/a"`} {
		if GlobLocation(g).Check() == nil {
			t.Errorf("Check(%q): no error", g)
		}
	}
}

func TestRewrite(t *testing.T) {
	caps := []string{"/code/r/src/", "r/src"}
	for target, want := range map[string]string{
		"/code/%1.html": "/code/r/src.html",
		"%0":            "/code/r/src/",
		"/%2/%1":        "/%2/r/src",
		"100%":          "100%",
		"%%1":           "%r/src",
	} {
		if got := Rewrite(target, caps); got != want {
			t.Errorf("Rewrite(%q) = %q, want %q", target, got, want)
		}
	}
	loc := PatternLocation("^/code/(..-)/?$")
	if err := CheckRewrite(loc, "/code/%1.html"); err != nil {
		t.Error(err)
	}
	if err := CheckRewrite(loc, "/code/%2.html"); err == nil {
		t.Error("%2 of a pattern with one capture: no error")
	}
	if err := CheckRewrite(PatternLocation("^/code/%(x%)(.*)"), "/%1"); err != nil {
		t.Error(err)
	}
	if err := CheckRewrite(GlobLocation("/code/*"), "/x/%1"); err == nil {
		t.Error("%1 of a glob: no error")
	}
}

func TestString(t *testing.T) {
	if got := GlobLocation("/static/*").String(); got != `"/static/*"` {
		t.Errorf("glob: %s", got)
	}
	if got := PatternLocation("^/a$").String(); got != `match "^/a$"` {
		t.Errorf("pattern: %s", got)
	}
}