repos / pico

pico services - prose.sh, pastes.sh, imgs.sh, feeds.sh, pgs.sh
git clone https://github.com/picosh/pico.git

commit
b910e2a
parent
5d921ed
author
Eric Bower
date
2024-04-03 14:23:25 +0000 UTC
feat(pgs): 301 redirect urls that are directories without trailing slash (#116)

3 files changed,  +107, -28
M pgs/api.go
+1, -1
1@@ -179,7 +179,7 @@ func (h *AssetHandler) handle(w http.ResponseWriter, r *http.Request) {
2 	status := http.StatusOK
3 	attempts := []string{}
4 	for _, fp := range routes {
5-		if hasProtocol(fp.Filepath) {
6+		if hasProtocol(fp.Filepath) || fp.Status == http.StatusMovedPermanently {
7 			h.Logger.Info(
8 				"redirecting request",
9 				"bucket", h.Bucket.Name,
M pgs/cal_route.go
+39, -18
 1@@ -19,6 +19,9 @@ type HttpReply struct {
 2 }
 3 
 4 func expandRoute(projectName, fp string, status int) []*HttpReply {
 5+	if fp == "" {
 6+		fp = "/"
 7+	}
 8 	mimeType := storage.GetMimeType(fp)
 9 	fname := filepath.Base(fp)
10 	fdir := filepath.Dir(fp)
11@@ -33,9 +36,22 @@ func expandRoute(projectName, fp string, status int) []*HttpReply {
12 		return routes
13 	}
14 
15-	if fname != "" && fname != "/" {
16-		// we need to accommodate routes that are just directories
17-		// and point the user to the index.html of each root dir.
18+	// we know it's a directory so send the index.html for it
19+	if strings.HasSuffix(fp, "/") {
20+		dirRoute := shared.GetAssetFileName(&utils.FileEntry{
21+			Filepath: filepath.Join(projectName, fp, "index.html"),
22+		})
23+
24+		routes = append(
25+			routes,
26+			&HttpReply{Filepath: dirRoute, Status: status},
27+		)
28+	} else {
29+		if fname == "." {
30+			return routes
31+		}
32+
33+		// pretty urls where we just append .html to base of fp
34 		nameRoute := shared.GetAssetFileName(&utils.FileEntry{
35 			Filepath: filepath.Join(
36 				projectName,
37@@ -50,15 +66,6 @@ func expandRoute(projectName, fp string, status int) []*HttpReply {
38 		)
39 	}
40 
41-	dirRoute := shared.GetAssetFileName(&utils.FileEntry{
42-		Filepath: filepath.Join(projectName, fp, "index.html"),
43-	})
44-
45-	routes = append(
46-		routes,
47-		&HttpReply{Filepath: dirRoute, Status: status},
48-	)
49-
50 	return routes
51 }
52 
53@@ -68,13 +75,7 @@ func hasProtocol(url string) bool {
54 }
55 
56 func calcRoutes(projectName, fp string, userRedirects []*RedirectRule) []*HttpReply {
57-	notFound := &HttpReply{
58-		Filepath: filepath.Join(projectName, "404.html"),
59-		Status:   http.StatusNotFound,
60-	}
61-
62 	rts := expandRoute(projectName, fp, http.StatusOK)
63-
64 	fext := filepath.Ext(fp)
65 	// add route as-is without expansion if there is a file ext
66 	if fp != "" && fext != "" {
67@@ -130,6 +131,26 @@ func calcRoutes(projectName, fp string, userRedirects []*RedirectRule) []*HttpRe
68 		}
69 	}
70 
71+	// filename without extension mean we might have a directory
72+	// so add a trailing slash with a 301
73+	if !strings.HasSuffix(fp, "/") && fp != "" && fext == "" {
74+		redirectRoute := shared.GetAssetFileName(&utils.FileEntry{
75+			Filepath: fp + "/",
76+		})
77+		rts = append(
78+			rts,
79+			&HttpReply{Filepath: redirectRoute, Status: http.StatusMovedPermanently},
80+		)
81+		// 301 is always actived so anything after this branch will never
82+		// be executed ... so return early
83+		// return rts
84+	}
85+
86+	notFound := &HttpReply{
87+		Filepath: filepath.Join(projectName, "404.html"),
88+		Status:   http.StatusNotFound,
89+	}
90+
91 	rts = append(rts,
92 		notFound,
93 	)
M pgs/calc_route_test.go
+67, -9
  1@@ -57,16 +57,24 @@ func TestCalcRoutes(t *testing.T) {
  2 		},
  3 		{
  4 			Name:   "subdirectory-bare",
  5-			Actual: calcRoutes("test", "/nice", []*RedirectRule{}),
  6+			Actual: calcRoutes("test", "/nice/", []*RedirectRule{}),
  7 			Expected: []*HttpReply{
  8-				{Filepath: "test/nice.html", Status: 200},
  9 				{Filepath: "test/nice/index.html", Status: 200},
 10 				{Filepath: "test/404.html", Status: 404},
 11 			},
 12 		},
 13+		{
 14+			Name:   "trailing-slash",
 15+			Actual: calcRoutes("test", "/folder", []*RedirectRule{}),
 16+			Expected: []*HttpReply{
 17+				{Filepath: "test/folder.html", Status: 200},
 18+				{Filepath: "/folder/", Status: 301},
 19+				{Filepath: "test/404.html", Status: 404},
 20+			},
 21+		},
 22 		{
 23 			Name: "spa",
 24-			Actual: calcRoutes("test", "/nice", []*RedirectRule{
 25+			Actual: calcRoutes("test", "/nice.html", []*RedirectRule{
 26 				{
 27 					From:   "/*",
 28 					To:     "/index.html",
 29@@ -75,7 +83,6 @@ func TestCalcRoutes(t *testing.T) {
 30 			}),
 31 			Expected: []*HttpReply{
 32 				{Filepath: "test/nice.html", Status: 200},
 33-				{Filepath: "test/nice/index.html", Status: 200},
 34 				{Filepath: "test/index.html", Status: 200},
 35 				{Filepath: "test/404.html", Status: 404},
 36 			},
 37@@ -103,8 +110,28 @@ func TestCalcRoutes(t *testing.T) {
 38 			),
 39 			Expected: []*HttpReply{
 40 				{Filepath: "test/wow.html", Status: 200},
 41-				{Filepath: "test/wow/index.html", Status: 200},
 42 				{Filepath: "test/index.html", Status: 301},
 43+				{Filepath: "/wow/", Status: 301},
 44+				{Filepath: "test/404.html", Status: 404},
 45+			},
 46+		},
 47+		{
 48+			Name: "redirectToPico",
 49+			Actual: calcRoutes(
 50+				"test",
 51+				"/tester1",
 52+				[]*RedirectRule{
 53+					{
 54+						From:   "/tester1",
 55+						To:     "https://pico.sh",
 56+						Status: 301,
 57+					},
 58+				},
 59+			),
 60+			Expected: []*HttpReply{
 61+				{Filepath: "test/tester1.html", Status: 200},
 62+				{Filepath: "https://pico.sh", Status: 301},
 63+				{Filepath: "/tester1/", Status: 301},
 64 				{Filepath: "test/404.html", Status: 404},
 65 			},
 66 		},
 67@@ -112,17 +139,28 @@ func TestCalcRoutes(t *testing.T) {
 68 			Name: "root",
 69 			Actual: calcRoutes(
 70 				"test",
 71-				"/wow",
 72+				"",
 73+				[]*RedirectRule{},
 74+			),
 75+			Expected: []*HttpReply{
 76+				{Filepath: "test/index.html", Status: 200},
 77+				{Filepath: "test/404.html", Status: 404},
 78+			},
 79+		},
 80+		{
 81+			Name: "redirectToRoot",
 82+			Actual: calcRoutes(
 83+				"test",
 84+				"/wow/",
 85 				[]*RedirectRule{
 86 					{
 87-						From:   "/wow",
 88+						From:   "/wow/",
 89 						To:     "/",
 90 						Status: 301,
 91 					},
 92 				},
 93 			),
 94 			Expected: []*HttpReply{
 95-				{Filepath: "test/wow.html", Status: 200},
 96 				{Filepath: "test/wow/index.html", Status: 200},
 97 				{Filepath: "test/index.html", Status: 301},
 98 				{Filepath: "test/404.html", Status: 404},
 99@@ -144,11 +182,31 @@ func TestCalcRoutes(t *testing.T) {
100 			),
101 			Expected: []*HttpReply{
102 				{Filepath: "test/index.html", Status: 301},
103+				{Filepath: "/wow/", Status: 301},
104 				{Filepath: "test/404.html", Status: 404},
105 			},
106 		},
107 		{
108 			Name: "redirectFullUrl",
109+			Actual: calcRoutes(
110+				"test",
111+				"/wow.html",
112+				[]*RedirectRule{
113+					{
114+						From:   "/wow",
115+						To:     "https://pico.sh",
116+						Status: 301,
117+					},
118+				},
119+			),
120+			Expected: []*HttpReply{
121+				{Filepath: "test/wow.html", Status: 200},
122+				{Filepath: "https://pico.sh", Status: 301},
123+				{Filepath: "test/404.html", Status: 404},
124+			},
125+		},
126+		{
127+			Name: "redirectFullUrlDirectory",
128 			Actual: calcRoutes(
129 				"test",
130 				"/wow",
131@@ -162,8 +220,8 @@ func TestCalcRoutes(t *testing.T) {
132 			),
133 			Expected: []*HttpReply{
134 				{Filepath: "test/wow.html", Status: 200},
135-				{Filepath: "test/wow/index.html", Status: 200},
136 				{Filepath: "https://pico.sh", Status: 301},
137+				{Filepath: "/wow/", Status: 301},
138 				{Filepath: "test/404.html", Status: 404},
139 			},
140 		},