package files import ( "net/http" "github.com/rs/zerolog/log" "aether/internal/thumbnails" "aether/pkg/response" ) // maxThumbUpload begrenzt, was als Vorschaubild hereinkommen darf. Ein // Vorschaubild ist ein paar Kilobyte gross; zwei Megabyte sind grosszuegig und // halten trotzdem jeden Unfug vom Dekodierer fern. const maxThumbUpload = 2 << 20 // SetThumbnail nimmt ein Vorschaubild entgegen, das nur der Browser erzeugen // kann. // // POST /api/v1/files/{id}/thumbnail // // **Warum es das ueberhaupt gibt.** Vorschaubilder entstehen sonst // serverseitig aus den hochgeladenen Bytes. Bei einer Zeichnung steht dort // JSON – daraus laesst sich ohne Browser kein Bild rechnen. Wer die Zeichnung // gerade bearbeitet, hat aber genau den Browser, der sie zeichnen kann. // // **Und warum man den Bytes trotzdem nicht glauben darf.** Was hier ankommt, // kommt von einem Klienten. Es wird deshalb nicht abgelegt, sondern durch // denselben Weg geschickt wie jedes andere Vorschaubild: dekodieren, // verkleinern, neu kodieren. Danach ist es mit Sicherheit ein Bild und hat die // uebliche Groesse – und nichts anderes kann im thumbs-Bereich landen. func (h *Handler) SetThumbnail(w http.ResponseWriter, r *http.Request) { id, err := parseID(r, "id") if err != nil { response.Error(w, http.StatusBadRequest, "invalid file id") return } // Schreibrecht: Wer die Datei nicht aendern darf, bestimmt auch nicht, wie // sie in der Liste aussieht. owner, ok := h.resolveFile(w, r, id, true) if !ok { return } f, err := h.store.GetFile(r.Context(), id, owner.OwnerID) if err != nil { response.Error(w, http.StatusNotFound, "file not found") return } typ := r.Header.Get("Content-Type") if !thumbnails.SupportedMIME(typ) { response.Error(w, http.StatusUnsupportedMediaType, "thumbnail must be a PNG, JPEG, GIF or WebP image") return } // MaxBytesReader statt einer eigenen Zaehlung: Er bricht die Verbindung // ab, statt erst alles zu lesen und dann abzulehnen. rc := http.MaxBytesReader(w, r.Body, maxThumbUpload) defer rc.Close() if err := thumbnails.Generate(r.Context(), h.storage, f.ID.String(), typ, rc); err != nil { log.Ctx(r.Context()).Warn().Err(err).Str("file_id", f.ID.String()). Msg("Vorschaubild vom Klienten liess sich nicht verarbeiten") response.Error(w, http.StatusBadRequest, "could not read that image") return } if err := h.store.UpdateThumbnail(r.Context(), f.ID, true); err != nil { response.Error(w, http.StatusInternalServerError, "database error") return } response.NoContent(w) }