fiber/middleware/favicon/favicon.go

150 lines
3.3 KiB
Go

package favicon
import (
"io"
"io/fs"
"os"
"strconv"
"github.com/gofiber/fiber/v3"
)
// Config defines the config for middleware.
type Config struct {
// FileSystem is an optional alternate filesystem to search for the favicon in.
// An example of this could be an embedded or network filesystem
//
// Optional. Default: nil
FileSystem fs.FS `json:"-"`
// Next defines a function to skip this middleware when returned true.
//
// Optional. Default: nil
Next func(c fiber.Ctx) bool
// File holds the path to an actual favicon that will be cached
//
// Optional. Default: ""
File string `json:"file"`
// URL for favicon handler
//
// Optional. Default: "/favicon.ico"
URL string `json:"url"`
// CacheControl defines how the Cache-Control header in the response should be set
//
// Optional. Default: "public, max-age=31536000"
CacheControl string `json:"cache_control"`
// Raw data of the favicon file
//
// Optional. Default: nil
Data []byte `json:"-"`
}
// ConfigDefault is the default config
var ConfigDefault = Config{
Next: nil,
File: "",
URL: fPath,
CacheControl: "public, max-age=31536000",
}
const (
fPath = "/favicon.ico"
hType = "image/x-icon"
hAllow = "GET, HEAD, OPTIONS"
hZero = "0"
)
// New creates a new middleware handler
func New(config ...Config) fiber.Handler {
// Set default config
cfg := ConfigDefault
// Override config if provided
if len(config) > 0 {
cfg = config[0]
// Set default values
if cfg.Next == nil {
cfg.Next = ConfigDefault.Next
}
if cfg.URL == "" {
cfg.URL = ConfigDefault.URL
}
if cfg.File == "" {
cfg.File = ConfigDefault.File
}
if cfg.CacheControl == "" {
cfg.CacheControl = ConfigDefault.CacheControl
}
}
// Load iconData if provided
var (
err error
iconData []byte
iconLenHeader string
iconLen int
)
if cfg.Data != nil {
// use the provided favicon data
iconData = cfg.Data
iconLenHeader = strconv.Itoa(len(cfg.Data))
iconLen = len(cfg.Data)
} else if cfg.File != "" {
// read from configured filesystem if present
if cfg.FileSystem != nil {
f, err := cfg.FileSystem.Open(cfg.File)
if err != nil {
panic(err)
}
if iconData, err = io.ReadAll(f); err != nil {
panic(err)
}
} else if iconData, err = os.ReadFile(cfg.File); err != nil {
panic(err)
}
iconLenHeader = strconv.Itoa(len(iconData))
iconLen = len(iconData)
}
// Return new handler
return func(c fiber.Ctx) error {
// Don't execute middleware if Next returns true
if cfg.Next != nil && cfg.Next(c) {
return c.Next()
}
// Only respond to favicon requests
if c.Path() != cfg.URL {
return c.Next()
}
// Only allow GET, HEAD and OPTIONS requests
if c.Method() != fiber.MethodGet && c.Method() != fiber.MethodHead {
if c.Method() != fiber.MethodOptions {
c.Status(fiber.StatusMethodNotAllowed)
} else {
c.Status(fiber.StatusOK)
}
c.Set(fiber.HeaderAllow, hAllow)
c.Set(fiber.HeaderContentLength, hZero)
return nil
}
// Serve cached favicon
if iconLen > 0 {
c.Set(fiber.HeaderContentLength, iconLenHeader)
c.Set(fiber.HeaderContentType, hType)
c.Set(fiber.HeaderCacheControl, cfg.CacheControl)
return c.Status(fiber.StatusOK).Send(iconData)
}
return c.SendStatus(fiber.StatusNoContent)
}
}