mirror of
https://github.com/selfhst/icons.git
synced 2026-09-20 04:30:57 -04:00
v4.0.3 (Expanded white detection)
This commit is contained in:
+121
-112
@@ -1,113 +1,122 @@
|
|||||||
# v4.0.2
|
# v4.0.3
|
||||||
|
|
||||||
## What's Changed
|
## What's Changed
|
||||||
|
|
||||||
* Fixed custom icon issue where names converted to lowercase for cache keys were also being used to look up the file path, causing requests for icons with uppercase characters to fail ([#751](https://github.com/selfhst/icons/issues/751))
|
* Expanded detection of white (`hex`, `rbg`, `rgba`, and `white`) in SVG files for colorization ([#830](https://github.com/selfhst/icons/discussions/830))
|
||||||
* Added server name and sample endpoints to the root path ([#752](https://github.com/selfhst/icons/issues/752))
|
* Fixed caching bug with fallback formats
|
||||||
|
* Updated Go version to [v1.26.3](https://go.dev/doc/devel/release#go1.26.minor)
|
||||||
# v4.0.1
|
|
||||||
|
# v4.0.2
|
||||||
## What's Changed
|
|
||||||
|
## What's Changed
|
||||||
* Replaced hard-coded cache age values with the `CACHE_TTL` variable added in v4.0.0 ([#748](https://github.com/selfhst/icons/issues/748))
|
|
||||||
* Updated background routine to periodically purge stale assets based on `CACHE_TTL` time
|
* Fixed custom icon issue where names converted to lowercase for cache keys were also being used to look up the file path, causing requests for icons with uppercase characters to fail ([#751](https://github.com/selfhst/icons/issues/751))
|
||||||
|
* Added server name and sample endpoints to the root path ([#752](https://github.com/selfhst/icons/issues/752))
|
||||||
# v4.0.0
|
|
||||||
|
# v4.0.1
|
||||||
This release initially set out to address a request to support icon extensions in URLs, but quickly grew into a number of under the hood optimization/security fixes and a new ```hybrid``` mode that allows users with local collections to set remote icons as a fallback.
|
|
||||||
|
## What's Changed
|
||||||
## Breaking Changes
|
|
||||||
|
* Replaced hard-coded cache age values with the `CACHE_TTL` variable added in v4.0.0 ([#748](https://github.com/selfhst/icons/issues/748))
|
||||||
The ```STANDARD_ICON_FORMAT``` variable has been deprecated and users now have the option to specify their desired icon extension in the URL. If an extension is not specified, the server will default to WebP.
|
* Added the `Cache-Control` header to custom icon requests
|
||||||
|
* Updated background routine to periodically purge stale assets based on `CACHE_TTL` time
|
||||||
Users should review the updated methods for referencing icons in the [project's wiki](https://github.com/selfhst/icons/wiki#building-links) when upgrading.
|
|
||||||
|
# v4.0.0
|
||||||
## What's Changed
|
|
||||||
|
This release initially set out to address a request to support icon extensions in URLs, but quickly grew into a number of under the hood optimization/security fixes and a new `hybrid` mode that allows users with local collections to set remote icons as a fallback.
|
||||||
* [Feature] Added support for icon extensions in the URL (```example.svg```, ```example.png```, etc.) ([#718](https://github.com/selfhst/icons/issues/718), [#737](https://github.com/selfhst/icons/issues/737))
|
|
||||||
* [Feature] Added support for custom color URL parameters (```?color=2d2d2d```) as an alternative to paths (```icon/2d2d2d```) ([#737](https://github.com/selfhst/icons/issues/737))
|
## Breaking Changes
|
||||||
* [Feature] Added a new ```hybrid``` source option that prioritizes local collections and falls back to remote when missing
|
|
||||||
* [Feature] Added an optional ```LOG_LEVEL``` variable to control log verbosity
|
The `STANDARD_ICON_FORMAT` variable has been deprecated and users now have the option to specify their desired icon extension in the URL. If an extension is not specified, the server will default to WebP.
|
||||||
* [Feature] Added an optional ```CORS_ALLOWED_ORIGINS``` variable (defaults to all or ```*```)
|
|
||||||
* [Feature] Added optional variables for configurable cache times (```CACHE_TTL```) and size (```CACHE_SIZE```)
|
Users should review the updated methods for referencing icons in the [project's wiki](https://github.com/selfhst/icons/wiki#building-links) when upgrading.
|
||||||
* [Feature] Added an optional ```REMOTE_TIMEOUT``` variable to prevent the server from hanging when requests take too long (default: ```10``` seconds)
|
|
||||||
* Configured WebP as the global default when no extension is specified or an unsupported extension is requested
|
## What's Changed
|
||||||
* Added Docker health checks via the `-healthcheck` flag to accommodate scratch's lack of tooling (curl/wget)
|
|
||||||
* Added startup validations and logging to warn users of misconfigured variables
|
* [Feature] Added support for icon extensions in the URL (`example.svg`, `example.png`, etc.) ([#718](https://github.com/selfhst/icons/issues/718), [#737](https://github.com/selfhst/icons/issues/737))
|
||||||
* Added graceful shutdowns to allow in-flight requests to complete before exiting
|
* [Feature] Added support for custom color URL parameters (`?color=2d2d2d`) as an alternative to paths (`icon/2d2d2d`) ([#737](https://github.com/selfhst/icons/issues/737))
|
||||||
* Added validations to properly identify ```#fff``` and ```#ffffff``` when colorizing icons
|
* [Feature] Added a new `hybrid` source option that prioritizes local collections and falls back to remote when missing
|
||||||
* Added request completion time to log messages
|
* [Feature] Added an optional `LOG_LEVEL` variable to control log verbosity
|
||||||
* Added case normalization checks to prevent the server from caching the same icon multiple times
|
* [Feature] Added an optional `CORS_ALLOWED_ORIGINS` variable (defaults to all or `*`)
|
||||||
* Added GZIP compression for SVG requests with proper proxy headers
|
* [Feature] Added optional variables for configurable cache times (`CACHE_TTL`) and size (`CACHE_SIZE`)
|
||||||
* Added a security check for icons names with ```/```, ```\```, or ```..``` (attempted path traversal)
|
* [Feature] Added an optional `REMOTE_TIMEOUT` variable to prevent the server from hanging when requests take too long (default: `10` seconds)
|
||||||
* Added an ```X-Cache``` response header to signal cache status without having to view container logs
|
* Configured WebP as the global default when no extension is specified or an unsupported extension is requested
|
||||||
* Added a process to regularly clean stale cache entries
|
* Added Docker health checks via the `-healthcheck` flag to accommodate scratch's lack of tooling (curl/wget)
|
||||||
* Removed redundant file existence checks for local icons
|
* Added startup validations and logging to warn users of misconfigured variables
|
||||||
* Removed unnecessary directory scans when a custom icon is not found (initially included for debugging purposes)
|
* Added graceful shutdowns to allow in-flight requests to complete before exiting
|
||||||
|
* Added validations to properly identify `#fff` and `#ffffff` when colorizing icons
|
||||||
# v3.2.0
|
* Added request completion time to log messages
|
||||||
|
* Added case normalization checks to prevent the server from caching the same icon multiple times
|
||||||
## What's Changed
|
* Added GZIP compression for SVG requests with proper proxy headers
|
||||||
|
* Added a security check for icons names with `/`, `\`, or `..` (attempted path traversal)
|
||||||
* [Feature] Added ```PRIMARY_COLOR``` variable to easily apply a single custom color to all icons (see Wiki for additional details)
|
* Added an `X-Cache` response header to signal cache status without having to view container logs
|
||||||
* [Feature] Added ```REMOTE_URL``` to allow users to serve icons from their own remote sources (see Wiki for additional details) ([#690](https://github.com/selfhst/icons/issues/690))
|
* Added a process to regularly clean stale cache entries
|
||||||
* Reduced remote icon load time by removing redundant existence checks
|
* Removed redundant file existence checks for local icons
|
||||||
* Updated Go version to [v1.26](https://go.dev/doc/go1.26)
|
* Removed unnecessary directory scans when a custom icon is not found (initially included for debugging purposes)
|
||||||
|
|
||||||
# v3.1.1
|
# v3.2.0
|
||||||
|
|
||||||
## What's Changed
|
## What's Changed
|
||||||
|
|
||||||
* Fixed crash caused by concurrent writes under high load ([#684](https://github.com/selfhst/icons/issues/684))
|
* [Feature] Added `PRIMARY_COLOR` variable to easily apply a single custom color to all icons (see Wiki for additional details)
|
||||||
* Suppress favicon error log message when viewing icons directly from a browser
|
* [Feature] Added `REMOTE_URL` to allow users to serve icons from their own remote sources (see Wiki for additional details) ([#690](https://github.com/selfhst/icons/issues/690))
|
||||||
|
* Reduced remote icon load time by removing redundant existence checks
|
||||||
# v3.1.0
|
* Updated Go version to [v1.26](https://go.dev/doc/go1.26)
|
||||||
|
|
||||||
## What's Changed
|
# v3.1.1
|
||||||
|
|
||||||
* Updated logic to also replace gradient fills with custom colors when applicable
|
## What's Changed
|
||||||
|
|
||||||
# v3.0.0
|
* Fixed crash caused by concurrent writes under high load ([#684](https://github.com/selfhst/icons/issues/684))
|
||||||
|
* Suppress favicon error log message when viewing icons directly from a browser
|
||||||
## Breaking Changes
|
|
||||||
|
# v3.1.0
|
||||||
The legacy custom color URL ```domain.com/icon.svg?color=000000``` is no longer supported. See the [supported methods for building URLs](https://github.com/selfhst/icons/wiki#building-links) in the project's wiki if this change impacts you.
|
|
||||||
|
## What's Changed
|
||||||
## What's Changed
|
|
||||||
|
* Updated logic to also replace gradient fills with custom colors when applicable
|
||||||
* [Feature] Added support for the new AVIF and ICO formats
|
|
||||||
* Updated Go version to [v1.25](https://go.dev/doc/go1.25)
|
# v3.0.0
|
||||||
* Removed unmaintained [gorilla/mux](https://github.com/gorilla/mux) external dependency in favor of [net/http enhancements introduced in Go 1.22](https://go.dev/blog/routing-enhancements)
|
|
||||||
|
## Breaking Changes
|
||||||
# v2.2.0
|
|
||||||
|
The legacy custom color URL `domain.com/icon.svg?color=000000` is no longer supported. See the [supported methods for building URLs](https://github.com/selfhst/icons/wiki#building-links) in the project's wiki if this change impacts you.
|
||||||
## What's Changed
|
|
||||||
|
## What's Changed
|
||||||
* Remote source now points to main branch to ensure latest icons are fetched
|
|
||||||
|
* [Feature] Added support for the new AVIF and ICO formats
|
||||||
# v2.1.0
|
* Updated Go version to [v1.25](https://go.dev/doc/go1.25)
|
||||||
|
* Removed unmaintained [gorilla/mux](https://github.com/gorilla/mux) external dependency in favor of [net/http enhancements introduced in Go 1.22](https://go.dev/blog/routing-enhancements)
|
||||||
## What's Changed
|
|
||||||
|
# v2.2.0
|
||||||
* [Feature] Added optional volume for custom (non-selfh.st) icons ([#495](https://github.com/selfhst/icons/issues/495))
|
|
||||||
|
## What's Changed
|
||||||
# v2.0.0
|
|
||||||
|
* Remote source now points to main branch to ensure latest icons are fetched
|
||||||
## Breaking Changes
|
|
||||||
|
# v2.1.0
|
||||||
This release contains breaking changes as the image was rewritten using Go to decrease size and accommodate new features:
|
|
||||||
|
## What's Changed
|
||||||
* Updated docker-compose file with new variables and volume mounts
|
|
||||||
* Docker Hub image has been deprecated and will no longer receive updates
|
* [Feature] Added optional volume for custom (non-selfh.st) icons ([#495](https://github.com/selfhst/icons/issues/495))
|
||||||
|
|
||||||
## What's Changed
|
# v2.0.0
|
||||||
|
|
||||||
* Reduced image size (125MB --> 8MB) after replacing Node.js with Go
|
## Breaking Changes
|
||||||
* Support for local icon files
|
|
||||||
* Volume mounts for users hosting icon files locally
|
This release contains breaking changes as the image was rewritten using Go to decrease size and accommodate new features:
|
||||||
* Implemented caching to reduce external requests for commonly used assets
|
|
||||||
* Detailed logging
|
* Updated docker-compose file with new variables and volume mounts
|
||||||
|
* Docker Hub image has been deprecated and will no longer receive updates
|
||||||
# v1.0.0
|
|
||||||
|
## What's Changed
|
||||||
|
|
||||||
|
* Reduced image size (125MB --> 8MB) after replacing Node.js with Go
|
||||||
|
* Support for local icon files
|
||||||
|
* Volume mounts for users hosting icon files locally
|
||||||
|
* Implemented caching to reduce external requests for commonly used assets
|
||||||
|
* Detailed logging
|
||||||
|
|
||||||
|
# v1.0.0
|
||||||
|
|
||||||
* Initial release
|
* Initial release
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
FROM golang:1.26-alpine AS builder
|
FROM golang:1.26.3-alpine AS builder
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
4.0.2
|
4.0.3
|
||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
module github.com/selfhst/icons
|
module github.com/selfhst/icons
|
||||||
|
|
||||||
go 1.26
|
go 1.26.3
|
||||||
+99
-78
@@ -1,10 +1,10 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"compress/gzip"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"hash/fnv"
|
"hash/fnv"
|
||||||
"compress/gzip"
|
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -39,8 +39,9 @@ type Config struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type CacheItem struct {
|
type CacheItem struct {
|
||||||
Content string
|
Content string
|
||||||
Timestamp time.Time
|
ContentType string
|
||||||
|
Timestamp time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
type Cache struct {
|
type Cache struct {
|
||||||
@@ -58,13 +59,13 @@ func NewCache(ttl time.Duration, maxSize int) *Cache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cache) Get(key string) (string, bool) {
|
func (c *Cache) Get(key string) (CacheItem, bool) {
|
||||||
c.mutex.RLock()
|
c.mutex.RLock()
|
||||||
item, exists := c.items[key]
|
item, exists := c.items[key]
|
||||||
c.mutex.RUnlock()
|
c.mutex.RUnlock()
|
||||||
|
|
||||||
if !exists {
|
if !exists {
|
||||||
return "", false
|
return CacheItem{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
if time.Since(item.Timestamp) > c.ttl {
|
if time.Since(item.Timestamp) > c.ttl {
|
||||||
@@ -73,10 +74,10 @@ func (c *Cache) Get(key string) (string, bool) {
|
|||||||
delete(c.items, key)
|
delete(c.items, key)
|
||||||
}
|
}
|
||||||
c.mutex.Unlock()
|
c.mutex.Unlock()
|
||||||
return "", false
|
return CacheItem{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
return item.Content, true
|
return item, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cache) cleanup() {
|
func (c *Cache) cleanup() {
|
||||||
@@ -89,7 +90,7 @@ func (c *Cache) cleanup() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cache) Set(key, value string) {
|
func (c *Cache) Set(key, content, contentType string) {
|
||||||
c.mutex.Lock()
|
c.mutex.Lock()
|
||||||
defer c.mutex.Unlock()
|
defer c.mutex.Unlock()
|
||||||
|
|
||||||
@@ -109,8 +110,9 @@ func (c *Cache) Set(key, value string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
c.items[key] = CacheItem{
|
c.items[key] = CacheItem{
|
||||||
Content: value,
|
Content: content,
|
||||||
Timestamp: time.Now(),
|
ContentType: contentType,
|
||||||
|
Timestamp: time.Now(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,14 +122,14 @@ var (
|
|||||||
httpClient *http.Client
|
httpClient *http.Client
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// whiteVal matches any way an SVG can express white as a color value:
|
||||||
|
// 3/4/6/7/8-digit hex, the "white" keyword, or rgb()/rgba() of 255,255,255.
|
||||||
|
const whiteVal = `#fff(?:[0-9a-fA-F]|fff[0-9a-fA-F]{0,2})?|white|rgba?\(\s*255\s*[,\s]\s*255\s*[,\s]\s*255\s*(?:[,/]\s*[0-9.]+%?\s*)?\)`
|
||||||
|
|
||||||
var (
|
var (
|
||||||
hexColorRe = regexp.MustCompile(`^[0-9A-Fa-f]{6}$`)
|
hexColorRe = regexp.MustCompile(`^[0-9A-Fa-f]{6}$`)
|
||||||
reFillStyle = regexp.MustCompile(`style="[^"]*fill:\s*#fff(?:fff)?[^"]*"`)
|
reColorProp = regexp.MustCompile(`(?i)(fill|stop-color):(\s*)(` + whiteVal + `)([\s;}/),"']|$)`)
|
||||||
reFillInner = regexp.MustCompile(`fill:\s*#fff(?:fff)?`)
|
reColorAttr = regexp.MustCompile(`(?i)(fill|stop-color)=(["'])(` + whiteVal + `)(["'])`)
|
||||||
reFillAttr = regexp.MustCompile(`fill="#fff(?:fff)?"`)
|
|
||||||
reStopStyle = regexp.MustCompile(`style="[^"]*stop-color:\s*#fff(?:fff)?[^"]*"`)
|
|
||||||
reStopInner = regexp.MustCompile(`stop-color:\s*#fff(?:fff)?`)
|
|
||||||
reStopAttr = regexp.MustCompile(`stop-color="#fff(?:fff)?"`)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func logf(level int, format string, args ...any) {
|
func logf(level int, format string, args ...any) {
|
||||||
@@ -149,6 +151,18 @@ func computeETag(content string) string {
|
|||||||
return fmt.Sprintf(`"%x"`, h.Sum64())
|
return fmt.Sprintf(`"%x"`, h.Sum64())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseIntEnv(name string, def int) int {
|
||||||
|
v := os.Getenv(name)
|
||||||
|
if v == "" {
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
if n, err := strconv.Atoi(v); err == nil && n > 0 {
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
log.Printf("[WARN] Invalid %s value \"%s\", using default (%d)", name, v, def)
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
|
||||||
func loadConfig() *Config {
|
func loadConfig() *Config {
|
||||||
port := os.Getenv("PORT")
|
port := os.Getenv("PORT")
|
||||||
if port == "" {
|
if port == "" {
|
||||||
@@ -167,32 +181,9 @@ func loadConfig() *Config {
|
|||||||
|
|
||||||
primaryColor := strings.TrimPrefix(os.Getenv("PRIMARY_COLOR"), "#")
|
primaryColor := strings.TrimPrefix(os.Getenv("PRIMARY_COLOR"), "#")
|
||||||
|
|
||||||
cacheTTL := time.Hour
|
cacheTTL := time.Duration(parseIntEnv("CACHE_TTL", 3600)) * time.Second
|
||||||
if v := os.Getenv("CACHE_TTL"); v != "" {
|
cacheSize := parseIntEnv("CACHE_SIZE", 500)
|
||||||
if n, err := strconv.Atoi(v); err == nil && n > 0 {
|
remoteTimeout := time.Duration(parseIntEnv("REMOTE_TIMEOUT", 10)) * time.Second
|
||||||
cacheTTL = time.Duration(n) * time.Second
|
|
||||||
} else {
|
|
||||||
log.Printf("[WARN] Invalid CACHE_TTL value \"%s\", using default (3600)", v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cacheSize := 500
|
|
||||||
if v := os.Getenv("CACHE_SIZE"); v != "" {
|
|
||||||
if n, err := strconv.Atoi(v); err == nil && n > 0 {
|
|
||||||
cacheSize = n
|
|
||||||
} else {
|
|
||||||
log.Printf("[WARN] Invalid CACHE_SIZE value \"%s\", using default (500)", v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
remoteTimeout := 10 * time.Second
|
|
||||||
if v := os.Getenv("REMOTE_TIMEOUT"); v != "" {
|
|
||||||
if n, err := strconv.Atoi(v); err == nil && n > 0 {
|
|
||||||
remoteTimeout = time.Duration(n) * time.Second
|
|
||||||
} else {
|
|
||||||
log.Printf("[WARN] Invalid REMOTE_TIMEOUT value \"%s\", using default (10)", v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
corsAllowedOrigins := os.Getenv("CORS_ALLOWED_ORIGINS")
|
corsAllowedOrigins := os.Getenv("CORS_ALLOWED_ORIGINS")
|
||||||
if corsAllowedOrigins == "" {
|
if corsAllowedOrigins == "" {
|
||||||
@@ -319,20 +310,56 @@ func fetchRemoteFile(url string) (string, error) {
|
|||||||
return string(data), nil
|
return string(data), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func hexRGB(code string) (int64, int64, int64) {
|
||||||
|
r, _ := strconv.ParseInt(code[0:2], 16, 0)
|
||||||
|
g, _ := strconv.ParseInt(code[2:4], 16, 0)
|
||||||
|
b, _ := strconv.ParseInt(code[4:6], 16, 0)
|
||||||
|
return r, g, b
|
||||||
|
}
|
||||||
|
|
||||||
|
// newWhite returns the replacement for a matched white value, preserving any
|
||||||
|
// alpha channel from the original (8-digit hex, #rgba shorthand, or rgba()).
|
||||||
|
func newWhite(value, hexColor, code string) string {
|
||||||
|
v := strings.ToLower(value)
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(v, "#"):
|
||||||
|
h := v[1:]
|
||||||
|
switch len(h) {
|
||||||
|
case 4: // #rgba shorthand: expand the single alpha nibble
|
||||||
|
return hexColor + strings.Repeat(string(h[3]), 2)
|
||||||
|
case 7: // odd 7-digit form: expand trailing nibble
|
||||||
|
return hexColor + strings.Repeat(string(h[6]), 2)
|
||||||
|
case 8: // #rrggbbaa: keep the alpha byte
|
||||||
|
return hexColor + h[6:8]
|
||||||
|
default: // 3 or 6 digits: fully opaque
|
||||||
|
return hexColor
|
||||||
|
}
|
||||||
|
case strings.HasPrefix(v, "rgba("):
|
||||||
|
inner := value[strings.Index(value, "(")+1 : strings.LastIndex(value, ")")]
|
||||||
|
parts := strings.FieldsFunc(inner, func(r rune) bool {
|
||||||
|
return r == ',' || r == '/' || r == ' ' || r == '\t'
|
||||||
|
})
|
||||||
|
if len(parts) >= 4 {
|
||||||
|
r, g, b := hexRGB(code)
|
||||||
|
return fmt.Sprintf("rgba(%d, %d, %d, %s)", r, g, b, parts[len(parts)-1])
|
||||||
|
}
|
||||||
|
return hexColor
|
||||||
|
default: // "white" keyword or rgb() with no alpha
|
||||||
|
return hexColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func applySVGColor(svgContent, colorCode string) string {
|
func applySVGColor(svgContent, colorCode string) string {
|
||||||
color := "#" + colorCode
|
hexColor := "#" + colorCode
|
||||||
|
|
||||||
// Replace fill:#fff
|
svgContent = reColorProp.ReplaceAllStringFunc(svgContent, func(m string) string {
|
||||||
svgContent = reFillStyle.ReplaceAllStringFunc(svgContent, func(match string) string {
|
g := reColorProp.FindStringSubmatch(m)
|
||||||
return reFillInner.ReplaceAllString(match, "fill:"+color)
|
return g[1] + ":" + g[2] + newWhite(g[3], hexColor, colorCode) + g[4]
|
||||||
})
|
})
|
||||||
svgContent = reFillAttr.ReplaceAllString(svgContent, `fill="`+color+`"`)
|
svgContent = reColorAttr.ReplaceAllStringFunc(svgContent, func(m string) string {
|
||||||
|
g := reColorAttr.FindStringSubmatch(m)
|
||||||
// Replace stop-color:#fff in gradients
|
return g[1] + "=" + g[2] + newWhite(g[3], hexColor, colorCode) + g[4]
|
||||||
svgContent = reStopStyle.ReplaceAllStringFunc(svgContent, func(match string) string {
|
|
||||||
return reStopInner.ReplaceAllString(match, "stop-color:"+color)
|
|
||||||
})
|
})
|
||||||
svgContent = reStopAttr.ReplaceAllString(svgContent, `stop-color="`+color+`"`)
|
|
||||||
|
|
||||||
return svgContent
|
return svgContent
|
||||||
}
|
}
|
||||||
@@ -351,6 +378,19 @@ func serveContent(w http.ResponseWriter, r *http.Request, contentType, content s
|
|||||||
io.WriteString(w, content)
|
io.WriteString(w, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func writeIconResponse(w http.ResponseWriter, r *http.Request, contentType, content, cacheStatus string) {
|
||||||
|
etag := computeETag(content)
|
||||||
|
if r.Header.Get("If-None-Match") == etag {
|
||||||
|
w.WriteHeader(http.StatusNotModified)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", contentType)
|
||||||
|
w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%d", int(config.CacheTTL.Seconds())))
|
||||||
|
w.Header().Set("ETag", etag)
|
||||||
|
w.Header().Set("X-Cache", cacheStatus)
|
||||||
|
serveContent(w, r, contentType, content)
|
||||||
|
}
|
||||||
|
|
||||||
func getContentType(format string) string {
|
func getContentType(format string) string {
|
||||||
switch format {
|
switch format {
|
||||||
case "png":
|
case "png":
|
||||||
@@ -436,16 +476,7 @@ func handleIcon(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
if cached, found := cache.Get(cacheKey); found {
|
if cached, found := cache.Get(cacheKey); found {
|
||||||
logf(logLevelDebug, "[CACHE] Serving cached icon: \"%s\"%s (%s) %v", baseName, colorSuffix, formatToServe, formatDuration(time.Since(start)))
|
logf(logLevelDebug, "[CACHE] Serving cached icon: \"%s\"%s (%s) %v", baseName, colorSuffix, formatToServe, formatDuration(time.Since(start)))
|
||||||
etag := computeETag(cached)
|
writeIconResponse(w, r, cached.ContentType, cached.Content, "HIT")
|
||||||
if r.Header.Get("If-None-Match") == etag {
|
|
||||||
w.WriteHeader(http.StatusNotModified)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.Header().Set("Content-Type", contentType)
|
|
||||||
w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%d", int(config.CacheTTL.Seconds())))
|
|
||||||
w.Header().Set("ETag", etag)
|
|
||||||
w.Header().Set("X-Cache", "HIT")
|
|
||||||
serveContent(w, r, contentType, cached)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -518,8 +549,7 @@ func handleIcon(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cacheKey = getCacheKey(baseName+"."+formatToServe, colorCode)
|
cache.Set(cacheKey, iconContent, contentType)
|
||||||
cache.Set(cacheKey, iconContent)
|
|
||||||
|
|
||||||
level := "SUCCESS"
|
level := "SUCCESS"
|
||||||
detail := colorSuffix
|
detail := colorSuffix
|
||||||
@@ -529,16 +559,7 @@ func handleIcon(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
logf(logLevelInfo, "[%s] Serving icon: \"%s\"%s (%s, source: %s) %v", level, baseName, detail, formatToServe, servedFrom, formatDuration(time.Since(start)))
|
logf(logLevelInfo, "[%s] Serving icon: \"%s\"%s (%s, source: %s) %v", level, baseName, detail, formatToServe, servedFrom, formatDuration(time.Since(start)))
|
||||||
|
|
||||||
etag := computeETag(iconContent)
|
writeIconResponse(w, r, contentType, iconContent, "MISS")
|
||||||
if r.Header.Get("If-None-Match") == etag {
|
|
||||||
w.WriteHeader(http.StatusNotModified)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.Header().Set("Content-Type", contentType)
|
|
||||||
w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%d", int(config.CacheTTL.Seconds())))
|
|
||||||
w.Header().Set("ETag", etag)
|
|
||||||
w.Header().Set("X-Cache", "MISS")
|
|
||||||
serveContent(w, r, contentType, iconContent)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleCustomIcon(w http.ResponseWriter, r *http.Request) {
|
func handleCustomIcon(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -597,11 +618,11 @@ func handleCustomIcon(w http.ResponseWriter, r *http.Request) {
|
|||||||
cacheKey := "custom:" + filename + ":" + etag
|
cacheKey := "custom:" + filename + ":" + etag
|
||||||
if cached, found := cache.Get(cacheKey); found {
|
if cached, found := cache.Get(cacheKey); found {
|
||||||
logf(logLevelDebug, "[CACHE] Serving cached custom icon: \"%s\" %v", filename, formatDuration(time.Since(start)))
|
logf(logLevelDebug, "[CACHE] Serving cached custom icon: \"%s\" %v", filename, formatDuration(time.Since(start)))
|
||||||
w.Header().Set("Content-Type", contentType)
|
w.Header().Set("Content-Type", cached.ContentType)
|
||||||
w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%d", int(config.CacheTTL.Seconds())))
|
w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%d", int(config.CacheTTL.Seconds())))
|
||||||
w.Header().Set("ETag", etag)
|
w.Header().Set("ETag", etag)
|
||||||
w.Header().Set("X-Cache", "HIT")
|
w.Header().Set("X-Cache", "HIT")
|
||||||
serveContent(w, r, contentType, cached)
|
serveContent(w, r, cached.ContentType, cached.Content)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -612,7 +633,7 @@ func handleCustomIcon(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cache.Set(cacheKey, string(data))
|
cache.Set(cacheKey, string(data), contentType)
|
||||||
|
|
||||||
logf(logLevelInfo, "[SUCCESS] Serving custom icon: \"%s\" (%s) %v", filename, contentType, formatDuration(time.Since(start)))
|
logf(logLevelInfo, "[SUCCESS] Serving custom icon: \"%s\" (%s) %v", filename, contentType, formatDuration(time.Since(start)))
|
||||||
|
|
||||||
@@ -723,4 +744,4 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.Println("Server stopped. Bye for now!")
|
log.Println("Server stopped. Bye for now!")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user