Name dinners from links with letter-only words (PR #97 review F2)
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:
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user