f69c22039b
- nginx: client_max_body_size 10m (limite par défaut 1 Mo bloquait les images) - icons.go: import _ golang.org/x/image/webp et image/gif pour décoder WEBP/GIF - index.html: retire SVG de l'accept (serveur le rejette) et corrige le hint - popups.js: try/catch autour de uploadIcon → message d'erreur visible dans le hint pendant 4s si l'upload échoue ; reset du file input pour re-sélectionner le même fichier ; rafraîchit l'img de la tuile avec cache-busting après succès Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
90 lines
2.0 KiB
Go
90 lines
2.0 KiB
Go
package handlers
|
|
|
|
import (
|
|
"bytes"
|
|
"image"
|
|
_ "image/gif"
|
|
_ "image/jpeg"
|
|
"image/png"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/disintegration/imaging"
|
|
"github.com/user/nanometrics/server/db"
|
|
_ "golang.org/x/image/webp"
|
|
)
|
|
|
|
const maxIconSize = 128
|
|
|
|
func IconUploadHandler(database *db.DB) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method not allowed", 405)
|
|
return
|
|
}
|
|
parts := strings.Split(strings.Trim(r.URL.Path, "/"), "/")
|
|
if len(parts) < 4 {
|
|
http.Error(w, "invalid path", 400)
|
|
return
|
|
}
|
|
agentID := parts[2]
|
|
|
|
r.ParseMultipartForm(2 << 20)
|
|
file, header, err := r.FormFile("icon")
|
|
if err != nil {
|
|
http.Error(w, "fichier manquant", 400)
|
|
return
|
|
}
|
|
defer file.Close()
|
|
|
|
mime := header.Header.Get("Content-Type")
|
|
if mime == "" {
|
|
mime = "image/png"
|
|
}
|
|
|
|
// SVG refusé (risque XSS)
|
|
if strings.Contains(mime, "svg") {
|
|
http.Error(w, "SVG non supporté — utilisez PNG, JPG ou WEBP", 400)
|
|
return
|
|
}
|
|
|
|
// Limite de taille
|
|
limited := io.LimitReader(file, 2<<20)
|
|
img, _, err := image.Decode(limited)
|
|
if err != nil {
|
|
http.Error(w, "image invalide", 400)
|
|
return
|
|
}
|
|
resized := imaging.Fit(img, maxIconSize, maxIconSize, imaging.Lanczos)
|
|
var buf bytes.Buffer
|
|
if err := png.Encode(&buf, resized); err != nil {
|
|
http.Error(w, err.Error(), 500)
|
|
return
|
|
}
|
|
if err := database.SaveIcon(agentID, buf.Bytes(), "image/png"); err != nil {
|
|
http.Error(w, err.Error(), 500)
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
}
|
|
|
|
func IconGetHandler(database *db.DB) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
parts := strings.Split(strings.Trim(r.URL.Path, "/"), "/")
|
|
if len(parts) < 4 {
|
|
http.Error(w, "invalid path", 400)
|
|
return
|
|
}
|
|
agentID := parts[2]
|
|
data, mime, err := database.GetIcon(agentID)
|
|
if err != nil {
|
|
http.Error(w, "not found", 404)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", mime)
|
|
w.Write(data)
|
|
}
|
|
}
|