- commit
- ccc233b
- parent
- a4ff178
- author
- Eric Bower
- date
- 2026-02-16 20:15:23 -0500 EST
chore(pgs): tests to prevent infinite redirects
2 files changed,
+85,
-0
+31,
-0
1@@ -77,6 +77,32 @@ The parts are:
2 - "conditions": a whitespace-separated list of "key=value"
3 - "Sign" is a special condition
4 */
5+// isSelfReferentialRedirect checks if a redirect rule would redirect to itself.
6+// This includes exact matches and wildcard patterns that would match the same path.
7+func isSelfReferentialRedirect(from, to string) bool {
8+ // External URLs are never self-referential
9+ if isUrl(to) {
10+ return false
11+ }
12+
13+ // Exact match: /page redirects to /page
14+ if from == to {
15+ return true
16+ }
17+
18+ // Wildcard match: /* redirects to /*
19+ if from == to && strings.Contains(from, "*") {
20+ return true
21+ }
22+
23+ // Pattern with variable: /:path redirects to /:path
24+ if from == to && strings.Contains(from, ":") {
25+ return true
26+ }
27+
28+ return false
29+}
30+
31 func parseRedirectText(text string) ([]*RedirectRule, error) {
32 rules := []*RedirectRule{}
33 origLines := strings.Split(text, "\n")
34@@ -131,6 +157,11 @@ func parseRedirectText(text string) ([]*RedirectRule, error) {
35 conditions = parsePairs(lastParts[1:])
36 }
37
38+ // Validate that the redirect is not self-referential
39+ if isSelfReferentialRedirect(from, to) {
40+ return rules, fmt.Errorf("self-referential redirect: '%s' cannot redirect to itself", from)
41+ }
42+
43 rules = append(rules, &RedirectRule{
44 To: to,
45 From: from,
+54,
-0
1@@ -92,6 +92,55 @@ func TestParseRedirectText(t *testing.T) {
2 },
3 }
4
5+ selfReferentialExact := RedirectFixture{
6+ name: "self-referential-exact",
7+ input: "/page /page 301",
8+ expect: []*RedirectRule{},
9+ shouldError: true,
10+ }
11+
12+ selfReferentialWildcard := RedirectFixture{
13+ name: "self-referential-wildcard",
14+ input: "/* /* 301",
15+ expect: []*RedirectRule{},
16+ shouldError: true,
17+ }
18+
19+ selfReferentialWithVariables := RedirectFixture{
20+ name: "self-referential-with-variables",
21+ input: "/:path /:path 301",
22+ expect: []*RedirectRule{},
23+ shouldError: true,
24+ }
25+
26+ externalUrlNotSelfRef := RedirectFixture{
27+ name: "external-url-not-self-referential",
28+ input: "/* https://example.com 301",
29+ expect: []*RedirectRule{
30+ {
31+ From: "/*",
32+ To: "https://example.com",
33+ Status: 301,
34+ Query: empty,
35+ Conditions: empty,
36+ },
37+ },
38+ }
39+
40+ validPathRedirect := RedirectFixture{
41+ name: "valid-path-redirect",
42+ input: "/old-path /new-path 301",
43+ expect: []*RedirectRule{
44+ {
45+ From: "/old-path",
46+ To: "/new-path",
47+ Status: 301,
48+ Query: empty,
49+ Conditions: empty,
50+ },
51+ },
52+ }
53+
54 fixtures := []RedirectFixture{
55 spa,
56 rss,
57@@ -99,6 +148,11 @@ func TestParseRedirectText(t *testing.T) {
58 noStatus,
59 absoluteUriNoProto,
60 absoluteUriWithProto,
61+ selfReferentialExact,
62+ selfReferentialWildcard,
63+ selfReferentialWithVariables,
64+ externalUrlNotSelfRef,
65+ validPathRedirect,
66 }
67
68 for _, fixture := range fixtures {