Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 51 additions & 3 deletions server/server.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package server

import (
"mime"
"net/http"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"

"reverse-watch/api"
Expand Down Expand Up @@ -60,9 +64,13 @@ func New(cfg config.Config, factory repository.Factory) (*Server, error) {

r.Use(rwmiddleware.FactoryMiddleware(factory))

r.Get("/", func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "static/index.html")
})
// Static files are read into memory and written in a single response
// body. We avoid http.ServeFile / http.FileServer because the sendfile
// fast path on some local setups truncates large responses at the
// first TCP segment. Files served from here are tiny (HTML + a handful
// of logos/icons), so the read-once cost is negligible.
r.Get("/", serveStaticFile("static/index.html", "text/html; charset=utf-8"))
r.Get("/static/*", staticDirHandler("static"))

r.Mount("/api", api.Router())

Expand All @@ -74,3 +82,43 @@ func New(cfg config.Config, factory repository.Factory) (*Server, error) {
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s.r.ServeHTTP(w, r)
}

// serveStaticFile returns a handler that reads the file fresh on every
// request and writes its bytes with the given content-type.
func serveStaticFile(path, contentType string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
b, err := os.ReadFile(path)
if err != nil {
http.Error(w, "not found", http.StatusNotFound)
return
}
w.Header().Set("Content-Type", contentType)
w.Header().Set("Content-Length", strconv.Itoa(len(b)))
_, _ = w.Write(b)
}
}

// staticDirHandler serves files from baseDir under a chi wildcard
// /<prefix>/*. Path traversal is rejected.
func staticDirHandler(baseDir string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
rest := strings.TrimPrefix(r.URL.Path, "/static/")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hardcoded URL prefix instead of chi wildcard param

Low Severity

staticDirHandler extracts the relative path via strings.TrimPrefix(r.URL.Path, "/static/"), hardcoding the /static/ URL prefix rather than using chi.URLParam(r, "*"). The rest of the codebase consistently uses chi.URLParam for route parameter extraction. This couples the handler's internals to the route registration string and will silently break if the route is ever moved to a sub-router (where chi rewrites r.URL.Path).

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a701758. Configure here.

if rest == "" || strings.Contains(rest, "..") {
http.NotFound(w, r)
return
}
full := filepath.Join(baseDir, filepath.Clean("/"+rest))
b, err := os.ReadFile(full)
if err != nil {
http.NotFound(w, r)
return
}
ctype := mime.TypeByExtension(filepath.Ext(full))
if ctype == "" {
ctype = http.DetectContentType(b)
}
w.Header().Set("Content-Type", ctype)
w.Header().Set("Content-Length", strconv.Itoa(len(b)))
_, _ = w.Write(b)
}
}
Loading