Withhold recipe source urls whose host the browser cannot parse
safeSourceURL accepted hosts that Go's url.Parse takes but the browser's URL parser refuses, such as "https://[bad]/curry", so the detail API could send a source.url that sourceUrl() in web/src/dinners/detail.ts rejects, failing the whole recipe screen. The server now also requires a host the browser parses as it stands: a bracketed host must be an IPv6 address without a zone; any other must be ASCII letters, digits and hyphens in non-empty dot-separated labels, with no punycode label and, when its last label is a number, a valid dotted quad; and the host must be spelled out, not percent-encoded. Any other link is withheld as before. web/src/dinners/testdata/source_url_cases.json pins the source object the handler sends for each stored link. The Go server test checks the handler against it, and the recipe screen test renders each source from it, so the two parsers cannot drift apart on those links. Task: c1a97243-3e41-42fc-a1a0-1ac684ccb463 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -294,6 +295,37 @@ func TestGetDinnerSourceURLOnlyWhenSafe(t *testing.T) {
|
||||
}
|
||||
withheld(tc.what, cleanID, "Curry House", tc.link, "kitchen.example", "-token-", "password", "fragment")
|
||||
}
|
||||
|
||||
// web/src/dinners/testdata/source_url_cases.json pins source as this
|
||||
// handler sends it for each stored link; the recipe screen's tests
|
||||
// render each one, so a link the server sends and the client refuses
|
||||
// fails one side or the other.
|
||||
fixture, err := os.ReadFile("../../web/src/dinners/testdata/source_url_cases.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var shared struct {
|
||||
Cases []struct {
|
||||
Stored string
|
||||
Source map[string]any
|
||||
}
|
||||
}
|
||||
if err := json.Unmarshal(fixture, &shared); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(shared.Cases) == 0 {
|
||||
t.Fatal("source_url_cases.json has no cases")
|
||||
}
|
||||
for _, c := range shared.Cases {
|
||||
if _, err := e.st.Writer.Exec(`UPDATE recipes SET source_url = ? WHERE id = (SELECT recipe_id FROM dinners WHERE id = ?)`, c.Stored, cleanID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if src := source(c.Stored, cleanID); !reflect.DeepEqual(src, c.Source) {
|
||||
got, _ := json.Marshal(src)
|
||||
want, _ := json.Marshal(c.Source)
|
||||
t.Errorf("source of %s = %s, want %s", c.Stored, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetDinnerSiteNameNeverFromLink: a page that names no site is stored
|
||||
|
||||
Reference in New Issue
Block a user