diff --git a/internal/api/v1/favicon/favicon.go b/internal/api/v1/favicon/favicon.go index d44d8941..92559bc4 100644 --- a/internal/api/v1/favicon/favicon.go +++ b/internal/api/v1/favicon/favicon.go @@ -33,6 +33,13 @@ type content struct { status int } +type fetchResult struct { + icon []byte + contentType string + statusCode int + errMsg string +} + func newContent() *content { return &content{ header: make(http.Header), @@ -56,6 +63,10 @@ func (c *content) Hijack() (net.Conn, *bufio.ReadWriter, error) { return nil, nil, errors.New("not supported") } +func (res *fetchResult) OK() bool { + return res.icon != nil +} + // GetFavIcon returns the favicon of the route // // Returns: @@ -82,12 +93,13 @@ func GetFavIcon(w http.ResponseWriter, req *http.Request) { U.RespondError(w, err, http.StatusBadRequest) return } - icon, status, errMsg := getFavIconFromURL(&iconURL) - if icon == nil { - http.Error(w, errMsg, status) + fetchResult := getFavIconFromURL(&iconURL) + if !fetchResult.OK() { + http.Error(w, fetchResult.errMsg, fetchResult.statusCode) return } - U.WriteBody(w, icon) + w.Header().Set("Content-Type", fetchResult.contentType) + U.WriteBody(w, fetchResult.icon) return } @@ -97,39 +109,40 @@ func GetFavIcon(w http.ResponseWriter, req *http.Request) { U.RespondError(w, errors.New("no such route"), http.StatusNotFound) return } - var icon []byte - var status int - var errMsg string + var result *fetchResult hp := r.RawEntry().Homepage.GetOverride() if !hp.IsEmpty() && hp.Icon != nil { - switch hp.Icon.IconSource { - case homepage.IconSourceRelative: - icon, status, errMsg = findIcon(r, req, hp.Icon.Value) - default: - icon, status, errMsg = getFavIconFromURL(hp.Icon) + if hp.Icon.IconSource == homepage.IconSourceRelative { + result = findIcon(r, req, hp.Icon.Value) + } else { + result = getFavIconFromURL(hp.Icon) } } else { // try extract from "link[rel=icon]" - icon, status, errMsg = findIcon(r, req, "/") + result = findIcon(r, req, "/") } - if status != http.StatusOK { - http.Error(w, errMsg, status) + if result.statusCode == 0 { + result.statusCode = http.StatusOK + } + if !result.OK() { + http.Error(w, result.errMsg, result.statusCode) return } - U.WriteBody(w, icon) + w.Header().Set("Content-Type", result.contentType) + U.WriteBody(w, result.icon) } -func getFavIconFromURL(iconURL *homepage.IconURL) ([]byte, int, string) { +func getFavIconFromURL(iconURL *homepage.IconURL) *fetchResult { switch iconURL.IconSource { case homepage.IconSourceAbsolute: return fetchIconAbsolute(iconURL.URL()) case homepage.IconSourceRelative: - return nil, http.StatusBadRequest, "unexpected relative icon" + return &fetchResult{statusCode: http.StatusBadRequest, errMsg: "unexpected relative icon"} case homepage.IconSourceWalkXCode, homepage.IconSourceSelfhSt: return fetchKnownIcon(iconURL) } - return nil, http.StatusBadRequest, "invalid icon source" + return &fetchResult{statusCode: http.StatusBadRequest, errMsg: "invalid icon source"} } // cache key can be absolute url or route name. @@ -156,22 +169,34 @@ func InitIconCache() { }) } +func routeKey(r route.HTTPRoute) string { + return r.RawEntry().Provider + ":" + r.TargetName() +} + func ResetIconCache(route route.HTTPRoute) { iconCacheMu.Lock() defer iconCacheMu.Unlock() - delete(iconCache, route.TargetName()) + delete(iconCache, routeKey(route)) } -func loadIconCache(key string) (icon []byte, ok bool) { +func loadIconCache(key string) *fetchResult { iconCacheMu.RLock() defer iconCacheMu.RUnlock() - icon, ok = iconCache[key] - if ok { + icon, ok := iconCache[key] + if ok && icon != nil { logging.Debug(). Str("key", key). Msg("icon found in cache") + + var contentType string + if bytes.HasPrefix(icon, []byte(" link[rel=icon]").First() if ele.Length() == 0 { - return nil, http.StatusNotFound, "icon element not found" + return &fetchResult{statusCode: http.StatusNotFound, errMsg: "icon element not found"} } href := ele.AttrOr("href", "") if href == "" { - return nil, http.StatusNotFound, "icon href not found" + return &fetchResult{statusCode: http.StatusNotFound, errMsg: "icon href not found"} } // https://en.wikipedia.org/wiki/Data_URI_scheme if strings.HasPrefix(href, "data:image/") { @@ -336,9 +355,9 @@ func findIconSlow(r route.HTTPRoute, req *http.Request, uri string) (icon []byte logging.Error().Err(err). Str("route", r.TargetName()). Msg("failed to decode favicon") - return nil, http.StatusInternalServerError, "internal error" + return &fetchResult{statusCode: http.StatusInternalServerError, errMsg: "internal error"} } - return dataURI.Data, http.StatusOK, "" + return &fetchResult{icon: dataURI.Data, contentType: dataURI.ContentType()} } switch { case strings.HasPrefix(href, "http://"), strings.HasPrefix(href, "https://"): diff --git a/internal/homepage/homepage.go b/internal/homepage/homepage.go index 875aaec2..25ad3164 100644 --- a/internal/homepage/homepage.go +++ b/internal/homepage/homepage.go @@ -24,7 +24,7 @@ type ( AltURL string `json:"alt_url"` // original proxy target Provider string `json:"provider"` - IsUnset bool + IsUnset bool `json:"-"` } ) diff --git a/internal/homepage/override_config.go b/internal/homepage/override_config.go index 5196fab4..11f84595 100644 --- a/internal/homepage/override_config.go +++ b/internal/homepage/override_config.go @@ -68,12 +68,14 @@ func (c *OverrideConfig) GetOverride(item *Item) *Item { if catOverride, ok := c.CategoryName[item.Category]; ok { clone := *item clone.Category = catOverride + clone.IsUnset = false return &clone } return item } else { clone := *item clone.ItemConfig = itemOverride + clone.IsUnset = false if catOverride, ok := c.CategoryName[clone.Category]; ok { clone.Category = catOverride }