Strip trailing slashes from favicon URLsUnverified
1358397 parent: d4ef62e modified
internal/feed/discover.go +10 -5 | @@ -6,6 +6,7 @@ import ( | ||
| 6 | 6 | "net/http" |
| 7 | 7 | "net/url" |
| 8 | 8 | "regexp" |
| 9 | + "strings" | |
| 9 | 10 | "time" |
| 10 | 11 | ) |
| 11 | 12 | |
| @@ -22,7 +23,7 @@ var ( | ||
| 22 | 23 | relIconRe = regexp.MustCompile(`rel="[^"]*icon[^"]*"`) |
| 23 | 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 | 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 | 46 | }, nil |
| 46 | 47 | } |
| 47 | 48 | |
| 49 | +func cleanFavicon(s string) string { | |
| 50 | + return strings.TrimRight(s, "/") | |
| 51 | +} | |
| 52 | + | |
| 48 | 53 | func ResolveFavicon(ctx context.Context, feedURL, siteURL, parsedFavicon string) string { |
| 49 | 54 | if parsedFavicon != "" { |
| 50 | - return parsedFavicon | |
| 55 | + return cleanFavicon(parsedFavicon) | |
| 51 | 56 | } |
| 52 | 57 | target := siteURL |
| 53 | 58 | if target == "" { |
| @@ -57,7 +62,7 @@ func ResolveFavicon(ctx context.Context, feedURL, siteURL, parsedFavicon string) | ||
| 57 | 62 | if err != nil { |
| 58 | 63 | return "" |
| 59 | 64 | } |
| 60 | - return result.Favicon | |
| 65 | + return cleanFavicon(result.Favicon) | |
| 61 | 66 | } |
| 62 | 67 | |
| 63 | 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 | 92 | continue |
| 88 | 93 | } |
| 89 | 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 | 115 | resp.Body.Close() |
| 111 | 116 | if resp.StatusCode == http.StatusOK { |
| 112 | - return resolved.String() | |
| 117 | + return cleanFavicon(resolved.String()) | |
| 113 | 118 | } |
| 114 | 119 | } |
| 115 | 120 | 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 | }, nil | 46 | }, 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 parsedFavicon | 55 | + return cleanFavicon(parsedFavicon) |
| 51 | } | 56 | } |
| 52 | target := siteURL | 57 | 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.Favicon | 65 | + 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 | continue | 92 | 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) { | ||
| 151 | 151 | Title: jf.Title, |
| 152 | 152 | SiteURL: jf.HomePageURL, |
| 153 | 153 | Description: jf.Description, |
| 154 | - FaviconURL: jf.Favicon, | |
| 154 | + FaviconURL: cleanFavicon(jf.Favicon), | |
| 155 | 155 | Type: "json", |
| 156 | 156 | }, |
| 157 | 157 | } |
| @@ -222,7 +222,7 @@ func convertRSS(rss *rssFeed, feedURL string) *ParseResult { | ||
| 222 | 222 | Title: rss.Channel.Title, |
| 223 | 223 | SiteURL: rss.Channel.Link, |
| 224 | 224 | Description: rss.Channel.Description, |
| 225 | - FaviconURL: rss.Channel.Image.URL, | |
| 225 | + FaviconURL: cleanFavicon(rss.Channel.Image.URL), | |
| 226 | 226 | Type: "rss", |
| 227 | 227 | }, |
| 228 | 228 | } |
| @@ -292,7 +292,7 @@ func convertAtom(atom *atomFeed, feedURL string) *ParseResult { | ||
| 292 | 292 | Title: atom.Title, |
| 293 | 293 | SiteURL: pickAtomLink(atom.Link), |
| 294 | 294 | Description: atom.Subtitle, |
| 295 | - FaviconURL: favicon, | |
| 295 | + FaviconURL: cleanFavicon(favicon), | |
| 296 | 296 | Type: "atom", |
| 297 | 297 | }, |
| 298 | 298 | } |
| @@ -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 | } |