mirror of
https://github.com/skidoodle/mediaproxy
synced 2025-10-14 09:45:09 +02:00
108 lines
2.7 KiB
Go
108 lines
2.7 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"image/gif"
|
|
"log/slog"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/h2non/bimg"
|
|
)
|
|
|
|
type contextKey string
|
|
|
|
const loggerKey contextKey = "logger"
|
|
|
|
type responseWriter struct {
|
|
http.ResponseWriter
|
|
statusCode int
|
|
}
|
|
|
|
func (rw *responseWriter) WriteHeader(code int) {
|
|
rw.statusCode = code
|
|
rw.ResponseWriter.WriteHeader(code)
|
|
}
|
|
|
|
// loggingMiddleware injects a contextual logger into the request and logs request metrics.
|
|
func loggingMiddleware(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
start := time.Now()
|
|
requestID := strconv.FormatInt(time.Now().UnixNano(), 36)
|
|
logger := slog.With(
|
|
"request_id", requestID,
|
|
"method", r.Method,
|
|
"path", r.URL.Path,
|
|
"remote_addr", r.RemoteAddr,
|
|
)
|
|
ctx := context.WithValue(r.Context(), loggerKey, logger)
|
|
rw := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK}
|
|
next.ServeHTTP(rw, r.WithContext(ctx))
|
|
|
|
statusText := fmt.Sprintf("%d %s", rw.statusCode, http.StatusText(rw.statusCode))
|
|
logger.Info("Handled request",
|
|
"status", statusText,
|
|
"duration", time.Since(start).String(),
|
|
"user_agent", r.UserAgent(),
|
|
)
|
|
})
|
|
}
|
|
|
|
// isAllowedType performs a categorical check for 'image/*', 'video/*', and 'audio/*'.
|
|
func isAllowedType(mimeType string, allowGeneric bool) bool {
|
|
mimeType = strings.Split(mimeType, ";")[0]
|
|
category := strings.Split(mimeType, "/")[0]
|
|
|
|
switch category {
|
|
case "image", "video", "audio":
|
|
return true
|
|
}
|
|
|
|
// Allow generic binary streams on the initial HEAD request, forcing the
|
|
// more reliable content sniffing to make the final decision.
|
|
if allowGeneric && mimeType == "application/octet-stream" {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// isAllowedDomain checks if a host is in the configured whitelist.
|
|
func isAllowedDomain(host string, allowedDomains []string) bool {
|
|
if len(allowedDomains) == 0 {
|
|
return true
|
|
}
|
|
for _, domain := range allowedDomains {
|
|
if host == domain || strings.HasSuffix(host, "."+domain) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// isGif checks if the provided byte slice represents an animated GIF.
|
|
func isGif(data []byte) (bool, error) {
|
|
r := bytes.NewReader(data)
|
|
g, err := gif.DecodeAll(r)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return len(g.Image) > 1, nil
|
|
}
|
|
|
|
// optimizeMedia converts a static image to WebP and applies a quality setting.
|
|
func optimizeMedia(data []byte, quality int) ([]byte, error) {
|
|
webp, err := bimg.NewImage(data).Convert(bimg.WEBP)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to convert image to webp: %w", err)
|
|
}
|
|
processed, err := bimg.NewImage(webp).Process(bimg.Options{Quality: quality})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to process image quality: %w", err)
|
|
}
|
|
return processed, nil
|
|
}
|