repos / pico

pico services mono repo
git clone https://github.com/picosh/pico.git

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
M pkg/apps/pgs/redirect.go
+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,
M pkg/apps/pgs/redirect_test.go
+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 {