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 // The fallback itself is registered at "/", so a miss 263 // The fallback itself is registered at "/", so a miss
264 // reports that pattern; a real route reports its own. 264 // reports that pattern; a real route reports its own.
265 if _, pattern := mux.Handler(trimmed); pattern != "" && pattern != "/" { 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 http.Redirect(w, r, u.String(), http.StatusMovedPermanently) 271 http.Redirect(w, r, u.String(), http.StatusMovedPermanently)
267 return 272 return
268 } 273 }
internal/httpd/trailingslash_test.go +18
@@ -1,6 +1,7 @@
1package httpd 1package httpd
2 2
3import ( 3import (
4 "bufio"
4 "net/http" 5 "net/http"
5 "net/http/httptest" 6 "net/http/httptest"
6 "strings" 7 "strings"
@@ -37,6 +38,23 @@ func TestTrailingSlashRedirects(t *testing.T) {
37 t.Errorf("%s: Location %q leaves the site", p, loc) 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 r := httptest.NewRequest("POST", "/cmc/", nil) 58 r := httptest.NewRequest("POST", "/cmc/", nil)
41 r.Host = "forge.test" 59 r.Host = "forge.test"
42 w := httptest.NewRecorder() 60 w := httptest.NewRecorder()
internal/web/templates/globalsearch.html +1 −1
@@ -20,7 +20,7 @@
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}} 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</div> 21</div>
22<form method="get" action="/search" class="searchform"> 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 {{if .Kind}}<input type="hidden" name="kind" value="{{.Kind}}">{{end}} 24 {{if .Kind}}<input type="hidden" name="kind" value="{{.Kind}}">{{end}}
25 <button type="submit" class="btn">Search</button> 25 <button type="submit" class="btn">Search</button>
26</form> 26</form>
internal/web/templates/register.html +1 −1
@@ -6,7 +6,7 @@
6{{else}}<p class="lede">Open registration. Your account activates once you verify your email.</p>{{end}} 6{{else}}<p class="lede">Open registration. Your account activates once you verify your email.</p>{{end}}
7{{if .Error}}<p class="error" role="alert">{{.Error}}</p>{{end}} 7{{if .Error}}<p class="error" role="alert">{{.Error}}</p>{{end}}
8<form method="post" action="/register" class="signupform"> 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{{if eq .Mode "invite"}}<div class="field"><label for="invite">Invite code</label><input type="text" id="invite" name="invite" required></div> 10{{if eq .Mode "invite"}}<div class="field"><label for="invite">Invite code</label><input type="text" id="invite" name="invite" required></div>
11{{else}}<div class="field"><label for="email">Email</label><input type="text" id="email" name="email" required></div>{{end}} 11{{else}}<div class="field"><label for="email">Email</label><input type="text" id="email" name="email" required></div>{{end}}
12<div class="field"><label for="key">SSH public key</label> 12<div class="field"><label for="key">SSH public key</label>
internal/web/templates/search.html +1 −1
@@ -3,7 +3,7 @@
3{{define "content"}} 3{{define "content"}}
4<h1>Search</h1> 4<h1>Search</h1>
5<form method="get" action="/{{.Repo.OwnerName}}/{{.Repo.Name}}/search" class="searchform"> 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 <button type="submit" class="btn">Search</button> 7 <button type="submit" class="btn">Search</button>
8</form> 8</form>
9{{if .QueryErr}}<p class="error" role="alert">{{.QueryErr}}</p> 9{{if .QueryErr}}<p class="error" role="alert">{{.QueryErr}}</p>