Sonar: the off-site redirect and three autofocus attributes !441
merged
merged by cmc on 2026-09-20 05:26 UTC
· krz/gitbay:sonar-238 into main
5 files changed, +26 −3
Layout: unified · split
internal/httpd/routes.go
+5
| @@ -263,6 +263,11 @@ func (s *Server) unmatched(mux *http.ServeMux) http.HandlerFunc { |
| 263 | 263 | // The fallback itself is registered at "/", so a miss |
| 264 | 264 | // reports that pattern; a real route reports its own. |
| 265 | 265 | if _, pattern := mux.Handler(trimmed); pattern != "" && pattern != "/" { |
| 266 | // net/http fills Scheme and Host from an |
| 267 | // absolute-form request line, which RFC 7230 |
| 268 | // requires a server to accept; carrying them |
| 269 | // into Location sends the reader off-site. |
| 270 | u.Scheme, u.Host, u.User = "", "", nil |
| 266 | 271 | http.Redirect(w, r, u.String(), http.StatusMovedPermanently) |
| 267 | 272 | return |
| 268 | 273 | } |
internal/httpd/trailingslash_test.go
+18
| @@ -1,6 +1,7 @@ |
| 1 | 1 | package httpd |
| 2 | 2 | |
| 3 | 3 | import ( |
| 4 | "bufio" |
| 4 | 5 | "net/http" |
| 5 | 6 | "net/http/httptest" |
| 6 | 7 | "strings" |
| @@ -37,6 +38,23 @@ func TestTrailingSlashRedirects(t *testing.T) { |
| 37 | 38 | t.Errorf("%s: Location %q leaves the site", p, loc) |
| 38 | 39 | } |
| 39 | 40 | } |
| 41 | // An absolute-form request line, which RFC 7230 requires a server to |
| 42 | // accept, fills URL.Scheme and URL.Host. Neither may reach Location. |
| 43 | // ReadRequest also takes Host from the URL; it is pinned back to the |
| 44 | // site so this stays a test of the forge handler rather than of the |
| 45 | // pages router. |
| 46 | raw := "GET http://evil.example/cmc/ HTTP/1.1\r\nHost: forge.test\r\n\r\n" |
| 47 | abs, err := http.ReadRequest(bufio.NewReader(strings.NewReader(raw))) |
| 48 | if err != nil { |
| 49 | t.Fatal(err) |
| 50 | } |
| 51 | abs.Host = "forge.test" |
| 52 | aw := httptest.NewRecorder() |
| 53 | h.ServeHTTP(aw, abs) |
| 54 | if loc := aw.Header().Get("Location"); loc != "/cmc" { |
| 55 | t.Errorf("absolute-form request: Location %q, want %q", loc, "/cmc") |
| 56 | } |
| 57 | |
| 40 | 58 | r := httptest.NewRequest("POST", "/cmc/", nil) |
| 41 | 59 | r.Host = "forge.test" |
| 42 | 60 | w := httptest.NewRecorder() |
internal/web/templates/globalsearch.html
+1 −1
| @@ -20,7 +20,7 @@ |
| 20 | 20 | {{if and .Query (not .QueryErr)}}<p class="meta">{{len .Results}} {{if eq (len .Results) 1}}result{{else}}results{{end}} for <q>{{.Query}}</q>{{if .Kind}} in {{.Kind}}{{end}}</p>{{end}} |
| 21 | 21 | </div> |
| 22 | 22 | <form method="get" action="/search" class="searchform"> |
| 23 | | <input type="search" name="q" aria-label="Search" value="{{.Query}}" placeholder="repository names and topics, issue and merge request text" autofocus> |
| 23 | <input type="search" name="q" aria-label="Search" value="{{.Query}}" placeholder="repository names and topics, issue and merge request text"> |
| 24 | 24 | {{if .Kind}}<input type="hidden" name="kind" value="{{.Kind}}">{{end}} |
| 25 | 25 | <button type="submit" class="btn">Search</button> |
| 26 | 26 | </form> |
internal/web/templates/register.html
+1 −1
| @@ -6,7 +6,7 @@ |
| 6 | 6 | {{else}}<p class="lede">Open registration. Your account activates once you verify your email.</p>{{end}} |
| 7 | 7 | {{if .Error}}<p class="error" role="alert">{{.Error}}</p>{{end}} |
| 8 | 8 | <form method="post" action="/register" class="signupform"> |
| 9 | | <div class="field"><label for="username">Username</label><input type="text" id="username" name="username" value="{{.Username}}" required autofocus></div> |
| 9 | <div class="field"><label for="username">Username</label><input type="text" id="username" name="username" value="{{.Username}}" required></div> |
| 10 | 10 | {{if eq .Mode "invite"}}<div class="field"><label for="invite">Invite code</label><input type="text" id="invite" name="invite" required></div> |
| 11 | 11 | {{else}}<div class="field"><label for="email">Email</label><input type="text" id="email" name="email" required></div>{{end}} |
| 12 | 12 | <div class="field"><label for="key">SSH public key</label> |
internal/web/templates/search.html
+1 −1
| @@ -3,7 +3,7 @@ |
| 3 | 3 | {{define "content"}} |
| 4 | 4 | <h1>Search</h1> |
| 5 | 5 | <form method="get" action="/{{.Repo.OwnerName}}/{{.Repo.Name}}/search" class="searchform"> |
| 6 | | <input type="search" name="q" aria-label="Search file contents" value="{{.Query}}" placeholder="search file contents on {{.Ref}}" autofocus> |
| 6 | <input type="search" name="q" aria-label="Search file contents" value="{{.Query}}" placeholder="search file contents on {{.Ref}}"> |
| 7 | 7 | <button type="submit" class="btn">Search</button> |
| 8 | 8 | </form> |
| 9 | 9 | {{if .QueryErr}}<p class="error" role="alert">{{.QueryErr}}</p> |