Name dinners from links with letter-only words (PR #97 review F2)
CI / ci (push) Successful in 2m25s
CI / ci (pull_request) Successful in 2m24s

Keep only words of at most 20 letters from the link's last path segment,
so punctuated or long token text such as /stew-t=abcdef never reaches
the dinner name, and stop at combined credential words like accesstoken.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-06 20:48:03 -04:00
co-authored by Claude Opus 5.5
parent 903a332375
commit 4e0c31b7d6
2 changed files with 22 additions and 7 deletions
+18 -7
View File
@@ -500,15 +500,21 @@ func checkRecipeURL(raw string) (*url.URL, error) {
// can carry a personal token in its path, as in /stew-access_token-abc123, // can carry a personal token in its path, as in /stew-access_token-abc123,
// and the name is shown in every dinner response. // and the name is shown in every dinner response.
var credentialWords = map[string]bool{ var credentialWords = map[string]bool{
"access": true, "api": true, "apikey": true, "auth": true, "credential": true, "access": true, "accesstoken": true, "api": true, "apikey": true, "auth": true,
"credentials": true, "passcode": true, "password": true, "passwd": true, "authtoken": true, "bearer": true, "credential": true, "credentials": true,
"secret": true, "session": true, "sig": true, "signature": true, "token": true, "jwt": true, "passcode": true, "password": true, "passwd": true, "secret": true,
"session": true, "sig": true, "signature": true, "token": true,
} }
// maxSlugWordChars bounds a word nameFromURL keeps: a longer run of letters
// is more likely a token than a dish.
const maxSlugWordChars = 20
// nameFromURL names a dinner from the last path segment of u: separators // nameFromURL names a dinner from the last path segment of u: separators
// become spaces, a file extension and words holding a digit are dropped, the // become spaces, a file extension is dropped, only words of at most
// name ends at the first of credentialWords and the first letter is // maxSlugWordChars letters are kept, the name ends at the first of
// capitalized, so https://example.com/recipes/12345/chicken-tikka-masala/ is // credentialWords and the first letter is capitalized, so
// https://example.com/recipes/12345/chicken-tikka-masala/ is
// "Chicken tikka masala". ok is false when no usable name is left. // "Chicken tikka masala". ok is false when no usable name is left.
func nameFromURL(u *url.URL) (name string, ok bool) { func nameFromURL(u *url.URL) (name string, ok bool) {
segments := strings.FieldsFunc(u.Path, func(r rune) bool { return r == '/' }) segments := strings.FieldsFunc(u.Path, func(r rune) bool { return r == '/' })
@@ -525,7 +531,7 @@ func nameFromURL(u *url.URL) (name string, ok bool) {
if credentialWords[w] { if credentialWords[w] {
break break
} }
if strings.IndexFunc(w, unicode.IsDigit) < 0 { if utf8.RuneCountInString(w) <= maxSlugWordChars && strings.IndexFunc(w, notLetter) < 0 {
words = append(words, w) words = append(words, w)
} }
} }
@@ -539,6 +545,11 @@ func nameFromURL(u *url.URL) (name string, ok bool) {
return name, true return name, true
} }
// notLetter reports whether r is neither a letter nor a combining mark.
func notLetter(r rune) bool {
return !unicode.IsLetter(r) && !unicode.IsMark(r)
}
// checkUnique returns a *DuplicateError when name is harness.Similar to an // checkUnique returns a *DuplicateError when name is harness.Similar to an
// unarchived dinner other than self, or recipeURL is one's recipe link: the // unarchived dinner other than self, or recipeURL is one's recipe link: the
// source_url of the recipe it is grounded in, else the recipe_url still // source_url of the recipe it is grounded in, else the recipe_url still
+4
View File
@@ -26,6 +26,10 @@ func TestNameFromURL(t *testing.T) {
{"https://example.com/stew-x7k2p9q4", "Stew"}, {"https://example.com/stew-x7k2p9q4", "Stew"},
{"https://example.com/recipes/token-abcdefghijk", ""}, {"https://example.com/recipes/token-abcdefghijk", ""},
{"https://example.com/key-lime-pie", "Key lime pie"}, {"https://example.com/key-lime-pie", "Key lime pie"},
{"https://example.com/stew-t=abcdef", "Stew"},
{"https://example.com/stew-accesstoken-abcdefghijk", "Stew"},
{"https://example.com/stew-abcdefghijklmnopqrstuvwxyz", "Stew"},
{"https://example.com/cre%CC%80me-bru%CC%82le%CC%81e", "Cre\u0300me bru\u0302le\u0301e"},
} }
for _, tt := range tests { for _, tt := range tests {
u, err := url.Parse(tt.link) u, err := url.Parse(tt.link)