From 550917bf6ee471cd01c265ef5e0d93a8c1a08161 Mon Sep 17 00:00:00 2001 From: Yann Stepienik Date: Tue, 16 May 2023 23:16:53 +0100 Subject: [PATCH] [release] version 0.5.0-unstable20 --- package.json | 2 +- src/icons.go | 217 +++++++++++++++++++++++---------------------------- 2 files changed, 98 insertions(+), 121 deletions(-) diff --git a/package.json b/package.json index 87e19fa..fbb4845 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cosmos-server", - "version": "0.5.0-unstable20", + "version": "0.5.0-unstable21", "description": "", "main": "test-server.js", "bugs": { diff --git a/src/icons.go b/src/icons.go index 357e600..04eda0d 100644 --- a/src/icons.go +++ b/src/icons.go @@ -7,9 +7,11 @@ import ( "strings" "os" "io/ioutil" - "regexp" - "path" "encoding/json" + "strconv" + "path" + + "go.deanishe.net/favicon" "github.com/azukaar/cosmos-server/src/utils" ) @@ -22,70 +24,6 @@ type CachedImage struct { var cache = make(map[string]CachedImage) -func ExtractFaviconMetaTag(filename string) string { - // Read the contents of the file - htmlBytes, err := ioutil.ReadFile(filename) - if err != nil { - return "/favicon.ico" - } - html := string(htmlBytes) - - // Regular expression pattern to match the favicon metatag - pattern := `]*rel="icon"[^>]*(?:sizes="([^"]+)")?[^>]*href="([^"]+)"[^>]*>|]*name="msapplication-TileImage"[^>]*content="([^"]+)"[^>]*>` - - // Compile the regular expression pattern - regex := regexp.MustCompile(pattern) - - // Find all matches in the HTML string - matches := regex.FindAllStringSubmatch(html, -1) - - var faviconURL string - - // Iterate over the matches to find the appropriate favicon - for _, match := range matches { - sizes := match[1] - href := match[2] - msAppTileImage := match[3] - - // Check if the meta tag specifies msapplication-TileImage - if msAppTileImage != "" { - faviconURL = msAppTileImage - break - } - - // Check if the sizes attribute contains 96x96 - if strings.Contains(sizes, "96x96") { - faviconURL = href - break - } - - // Check if the sizes attribute contains 64x64 - if strings.Contains(sizes, "64x64") { - faviconURL = href - continue - } - - // Check if the sizes attribute contains 32x32 - if strings.Contains(sizes, "32x32") { - faviconURL = href - continue - } - - // If no sizes specified, set faviconURL to the first match without sizes - if faviconURL == "" && sizes == "" { - faviconURL = href - } - } - - // If a favicon URL is found, return it - if faviconURL != "" { - return faviconURL - } - - // Return an error if no favicon URL is found - return "/favicon.ico" -} - func sendImage(w http.ResponseWriter, image CachedImage) { // Copy the response to the output w.Header().Set("Content-Type", image.ContentType) @@ -111,6 +49,7 @@ func sendFallback(w http.ResponseWriter) { w.Write(fallback) } +var IconCacheLock = make(chan bool, 1) func GetFavicon(w http.ResponseWriter, req *http.Request) { if utils.LoggedInOnly(w, req) != nil { @@ -120,6 +59,9 @@ func GetFavicon(w http.ResponseWriter, req *http.Request) { // get url from query string escsiteurl := req.URL.Query().Get("q") + IconCacheLock <- true + defer func() { <-IconCacheLock }() + // URL decode siteurl, err := url.QueryUnescape(escsiteurl) if err != nil { @@ -147,70 +89,105 @@ func GetFavicon(w http.ResponseWriter, req *http.Request) { return } - urlBody, err := ioutil.ReadAll(respNew.Body) + newsiteurl := respNew.Request.URL.String() + + icons, err := favicon.Find(newsiteurl) + + utils.Debug("Found Favicon: " + strconv.Itoa(len(icons))) if err != nil { utils.Error("FaviconFetch", err) sendFallback(w) return } + + if len(icons) == 0 { + utils.Error("FaviconFetch", err) + sendFallback(w) + return + } + + // if !strings.Contains(icons[iconIndex].URL, "favicon.ico") { + // icons = append(icons, favicon.Icon{URL: "favicon.ico", Width: 0}) + // } - // get favicon meta tag from the response - faviconURL := ExtractFaviconMetaTag((string)(urlBody)) + // if !strings.Contains(icons[iconIndex].URL, "/favicon.ico") { + // icons = append(icons, favicon.Icon{URL: "/favicon.ico", Width: 0}) + // } - // if faviconURL is relative get hostname of the URL and append icon - if !strings.HasPrefix(faviconURL, "http") { - u, err := url.Parse(siteurl) - if err != nil { - utils.Error("FaviconFetch", err) + for _, icon := range icons { + utils.Debug("Favicon Width: " + icon.URL + " " + strconv.Itoa(icon.Width)) + if icon.Width <= 256 { + + iconURL := icon.URL + u, err := url.Parse(siteurl) + if err != nil { + utils.Error("FaviconFetch failed to parse ", err) + continue + } + + if !strings.HasPrefix(iconURL, "http") { + if strings.HasPrefix(iconURL, ".") { + // Relative URL starting with "." + // Resolve the relative URL based on the base URL + baseURL := u.Scheme + "://" + u.Host + iconURL = baseURL + iconURL[1:] + } else if strings.HasPrefix(iconURL, "/") { + // Relative URL starting with "/" + // Append the relative URL to the base URL + iconURL = u.Scheme + "://" + u.Host + iconURL + } else { + // Relative URL without starting dot or slash + // Construct the absolute URL based on the current page's URL path + baseURL := u.Scheme + "://" + u.Host + baseURLPath := path.Dir(u.Path) + iconURL = baseURL + baseURLPath + "/" + iconURL + } + } + + utils.Log("Favicon Trying to fetch " + iconURL) + // Fetch the favicon + resp, err := http.Get(iconURL) + if err != nil { + utils.Error("FaviconFetch", err) + continue + } + + // check if 200 and if image + if resp.StatusCode != 200 { + utils.Error("FaviconFetch - " + iconURL + " - not 200 ", nil) + continue + } else if !strings.Contains(resp.Header.Get("Content-Type"), "image") { + utils.Error("FaviconFetch - " + iconURL + " - not image ", nil) + continue + } else { + utils.Log("Favicon found " + iconURL) + + // Cache the response + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + utils.Error("FaviconFetch", err) + sendFallback(w) + return + } + + finalImage := CachedImage{ + ContentType: resp.Header.Get("Content-Type"), + ETag: resp.Header.Get("ETag"), + Body: body, + } + + cache[siteurl] = finalImage + + sendImage(w, cache[siteurl]) + return + } + + utils.Log("Favicon final fallback ") sendFallback(w) return } - - if strings.HasPrefix(faviconURL, ".") { - // Relative URL starting with "." - // Resolve the relative URL based on the base URL - baseURL := u.Scheme + "://" + u.Host - faviconURL = baseURL + faviconURL[1:] - } else if strings.HasPrefix(faviconURL, "/") { - // Relative URL starting with "/" - // Append the relative URL to the base URL - faviconURL = u.Scheme + "://" + u.Host + faviconURL - } else { - // Relative URL without starting dot or slash - // Construct the absolute URL based on the current page's URL path - baseURL := u.Scheme + "://" + u.Host - baseURLPath := path.Dir(u.Path) - faviconURL = baseURL + baseURLPath + "/" + faviconURL - } - } - - utils.Log("Favicon: " + faviconURL) - - // Fetch the favicon - resp, err := http.Get(faviconURL) - if err != nil { - utils.Error("FaviconFetch", err) - sendFallback(w) - return - } - - // Cache the response - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - utils.Error("FaviconFetch", err) - sendFallback(w) - return - } - - finalImage := CachedImage{ - ContentType: resp.Header.Get("Content-Type"), - ETag: resp.Header.Get("ETag"), - Body: body, - } - - cache[siteurl] = finalImage - - sendImage(w, cache[siteurl]) + } + } else { utils.Error("Favicon: Method not allowed" + req.Method, nil) utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001")