fix: resolve and the 404 report refuse NUL and invalid UTF-8 addresses (WR-12)

A decoded path such as /%00 or /%FF reached Postgres as text and answered
500 with an error log line per request. parseTarget refuses a path or
query that is not valid UTF-8 or holds NUL (422), NotFoundPath does the
same for the logged address, and a referer is cleaned of both.
This commit is contained in:
2026-10-27 15:31:56 +01:00
parent d20f14fe67
commit 98fbeb6791
3 changed files with 48 additions and 3 deletions

View File

@@ -76,7 +76,11 @@ func NotFoundPath(raw string) (string, error) {
if p == "" {
p = "/"
}
return joinQuery(p, u.RawQuery), nil
out := joinQuery(p, u.RawQuery)
if !safeText(out) {
return "", ErrNotFoundURL
}
return out, nil
}
// capWarned keeps when the row-cap warning was last logged.
@@ -94,7 +98,7 @@ func (s *Service) ReportNotFound(ctx context.Context, rawURL, referer string) er
if err != nil {
return err
}
referer = strings.TrimSpace(referer)
referer = strings.TrimSpace(strings.ToValidUTF8(strings.ReplaceAll(referer, "\x00", ""), ""))
if len(referer) > MaxRefererBytes {
referer = referer[:MaxRefererBytes]
for len(referer) > 0 && !utf8ValidTail(referer) {

View File

@@ -6,6 +6,7 @@ import (
"net/url"
"sort"
"strings"
"unicode/utf8"
"git.golem15.com/golem15/sm-seo-plugin/models"
"git.golem15.com/golem15/summercms/modules/cabana"
@@ -215,7 +216,28 @@ func parseTarget(raw string) (string, url.Values, error) {
if path == "" {
path = "/"
}
return path, u.Query(), nil
query := u.Query()
if !safeText(path) || !safeText(u.RawQuery) {
return "", nil, ErrBadURL
}
for k, vs := range query {
if !safeText(k) {
return "", nil, ErrBadURL
}
for _, v := range vs {
if !safeText(v) {
return "", nil, ErrBadURL
}
}
}
return path, query, nil
}
// safeText reports whether s can be stored and compared as Postgres text:
// valid UTF-8 without NUL bytes. A decoded path such as /%00 or /%FF is
// not (WR-12).
func safeText(s string) bool {
return utf8.ValidString(s) && !strings.ContainsRune(s, 0)
}
// contentLocale returns the record's description in locale, else its first