nandi/gleanpublic⑂ Fork 0
⑂ 1358397
Commits
⬇ Clone ▾
git clone https://git.rickub.com/nandi/glean.git
git clone ssh://git@rickub.com/nandi/glean.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Strip trailing slashes from favicon URLsUnverified

Julien Robert committed 2026-04-22T13:40:47+02:00 Browse files
1358397 parent: d4ef62e
modified internal/feed/discover.go +10 -5
@@ -6,6 +6,7 @@ import (
66 "net/http"
77 "net/url"
88 "regexp"
9+ "strings"
910 "time"
1011 )
1112
@@ -22,7 +23,7 @@ var (
2223 relIconRe = regexp.MustCompile(`rel="[^"]*icon[^"]*"`)
2324 baseHrefRe = regexp.MustCompile(`<base[^>]+href="([^"]*)"`)
2425
25- faviconPaths = []string{"/favicon.ico", "/favicon.png", "/apple-touch-icon.png"}
26+ faviconPaths = []string{"/favicon.ico", "/favicon.png", "/apple-touch-icon.png"}
2627 discoverClient = &http.Client{Timeout: 15 * time.Second}
2728 )
2829
@@ -45,9 +46,13 @@ func Discover(ctx context.Context, siteURL string) (*DiscoveryResult, error) {
4546 }, nil
4647 }
4748
49+func cleanFavicon(s string) string {
50+ return strings.TrimRight(s, "/")
51+}
52+
4853 func ResolveFavicon(ctx context.Context, feedURL, siteURL, parsedFavicon string) string {
4954 if parsedFavicon != "" {
50- return parsedFavicon
55+ return cleanFavicon(parsedFavicon)
5156 }
5257 target := siteURL
5358 if target == "" {
@@ -57,7 +62,7 @@ func ResolveFavicon(ctx context.Context, feedURL, siteURL, parsedFavicon string)
5762 if err != nil {
5863 return ""
5964 }
60- return result.Favicon
65+ return cleanFavicon(result.Favicon)
6166 }
6267
6368 func findFeedURLs(base *url.URL, links []string) []string {
@@ -87,7 +92,7 @@ func findFavicon(ctx context.Context, base *url.URL, links []string) string {
8792 continue
8893 }
8994 if u, err := base.Parse(href); err == nil {
90- return u.String()
95+ return cleanFavicon(u.String())
9196 }
9297 }
9398
@@ -109,7 +114,7 @@ func findFavicon(ctx context.Context, base *url.URL, links []string) string {
109114 }
110115 resp.Body.Close()
111116 if resp.StatusCode == http.StatusOK {
112- return resolved.String()
117+ return cleanFavicon(resolved.String())
113118 }
114119 }
115120 return ""
@@ -6,6 +6,7 @@ import (
6 "net/http"6 "net/http"
7 "net/url"7 "net/url"
8 "regexp"8 "regexp"
9+ "strings"
9 "time"10 "time"
10 )11 )
11 12
@@ -22,7 +23,7 @@ var (
22 relIconRe = regexp.MustCompile(`rel="[^"]*icon[^"]*"`)23 relIconRe = regexp.MustCompile(`rel="[^"]*icon[^"]*"`)
23 baseHrefRe = regexp.MustCompile(`<base[^>]+href="([^"]*)"`)24 baseHrefRe = regexp.MustCompile(`<base[^>]+href="([^"]*)"`)
24 25
25- faviconPaths = []string{"/favicon.ico", "/favicon.png", "/apple-touch-icon.png"}26+ faviconPaths = []string{"/favicon.ico", "/favicon.png", "/apple-touch-icon.png"}
26 discoverClient = &http.Client{Timeout: 15 * time.Second}27 discoverClient = &http.Client{Timeout: 15 * time.Second}
27 )28 )
28 29
@@ -45,9 +46,13 @@ func Discover(ctx context.Context, siteURL string) (*DiscoveryResult, error) {
45 }, nil46 }, nil
46 }47 }
47 48
49+func cleanFavicon(s string) string {
50+ return strings.TrimRight(s, "/")
51+}
52+
48 func ResolveFavicon(ctx context.Context, feedURL, siteURL, parsedFavicon string) string {53 func ResolveFavicon(ctx context.Context, feedURL, siteURL, parsedFavicon string) string {
49 if parsedFavicon != "" {54 if parsedFavicon != "" {
50- return parsedFavicon55+ return cleanFavicon(parsedFavicon)
51 }56 }
52 target := siteURL57 target := siteURL
53 if target == "" {58 if target == "" {
@@ -57,7 +62,7 @@ func ResolveFavicon(ctx context.Context, feedURL, siteURL, parsedFavicon string)
57 if err != nil {62 if err != nil {
58 return ""63 return ""
59 }64 }
60- return result.Favicon65+ return cleanFavicon(result.Favicon)
61 }66 }
62 67
63 func findFeedURLs(base *url.URL, links []string) []string {68 func findFeedURLs(base *url.URL, links []string) []string {
@@ -87,7 +92,7 @@ func findFavicon(ctx context.Context, base *url.URL, links []string) string {
87 continue92 continue
88 }93 }
89 if u, err := base.Parse(href); err == nil {94 if u, err := base.Parse(href); err == nil {
90- return u.String()95+ return cleanFavicon(u.String())
91 }96 }
92 }97 }
93 98
@@ -109,7 +114,7 @@ func findFavicon(ctx context.Context, base *url.URL, links []string) string {
109 }114 }
110 resp.Body.Close()115 resp.Body.Close()
111 if resp.StatusCode == http.StatusOK {116 if resp.StatusCode == http.StatusOK {
112- return resolved.String()117+ return cleanFavicon(resolved.String())
113 }118 }
114 }119 }
115 return ""120 return ""
modified internal/feed/parser.go +3 -3
@@ -151,7 +151,7 @@ func parseJSONFeed(data []byte, feedURL string) (*ParseResult, error) {
151151 Title: jf.Title,
152152 SiteURL: jf.HomePageURL,
153153 Description: jf.Description,
154- FaviconURL: jf.Favicon,
154+ FaviconURL: cleanFavicon(jf.Favicon),
155155 Type: "json",
156156 },
157157 }
@@ -222,7 +222,7 @@ func convertRSS(rss *rssFeed, feedURL string) *ParseResult {
222222 Title: rss.Channel.Title,
223223 SiteURL: rss.Channel.Link,
224224 Description: rss.Channel.Description,
225- FaviconURL: rss.Channel.Image.URL,
225+ FaviconURL: cleanFavicon(rss.Channel.Image.URL),
226226 Type: "rss",
227227 },
228228 }
@@ -292,7 +292,7 @@ func convertAtom(atom *atomFeed, feedURL string) *ParseResult {
292292 Title: atom.Title,
293293 SiteURL: pickAtomLink(atom.Link),
294294 Description: atom.Subtitle,
295- FaviconURL: favicon,
295+ FaviconURL: cleanFavicon(favicon),
296296 Type: "atom",
297297 },
298298 }
@@ -151,7 +151,7 @@ func parseJSONFeed(data []byte, feedURL string) (*ParseResult, error) {
151 Title: jf.Title,151 Title: jf.Title,
152 SiteURL: jf.HomePageURL,152 SiteURL: jf.HomePageURL,
153 Description: jf.Description,153 Description: jf.Description,
154- FaviconURL: jf.Favicon,154+ FaviconURL: cleanFavicon(jf.Favicon),
155 Type: "json",155 Type: "json",
156 },156 },
157 }157 }
@@ -222,7 +222,7 @@ func convertRSS(rss *rssFeed, feedURL string) *ParseResult {
222 Title: rss.Channel.Title,222 Title: rss.Channel.Title,
223 SiteURL: rss.Channel.Link,223 SiteURL: rss.Channel.Link,
224 Description: rss.Channel.Description,224 Description: rss.Channel.Description,
225- FaviconURL: rss.Channel.Image.URL,225+ FaviconURL: cleanFavicon(rss.Channel.Image.URL),
226 Type: "rss",226 Type: "rss",
227 },227 },
228 }228 }
@@ -292,7 +292,7 @@ func convertAtom(atom *atomFeed, feedURL string) *ParseResult {
292 Title: atom.Title,292 Title: atom.Title,
293 SiteURL: pickAtomLink(atom.Link),293 SiteURL: pickAtomLink(atom.Link),
294 Description: atom.Subtitle,294 Description: atom.Subtitle,
295- FaviconURL: favicon,295+ FaviconURL: cleanFavicon(favicon),
296 Type: "atom",296 Type: "atom",
297 },297 },
298 }298 }