Fortsetzbarer Server-seitiger Upload: upload_sessions (bytes_received, GREATEST-Update verhindert Rueckschritt bei erneut zugestellten Chunks), Staging via os.File.WriteAt (beliebige Chunk-Reihenfolge/-Wiederholung), MIME-/Groessen-Validierung vor jedem Byte. Complete() liest Klartext einmal via io.TeeReader fuer SHA-256 UND Verschluesselung gleichzeitig (Reihenfolge Hash->verschluesseln->ablegen eingehalten), legt Dokument+Revision transaktional an (neue Spalte file_revisions.wrapped_dek fuer FDN-09). Auf 192.168.1.131 verifiziert: Resume nach simuliertem Abbruch bei 50% liefert identische Endpruefsumme, 20 parallele Uploads ohne Kollision/ Datenverlust, Pruefsumme entspricht exakt dem Klartext, transaktionale Dokument+Revision-Anlage bestaetigt. Reale Skalierungs-Einschraenkung gefunden: echter 1-GiB-Durchlauf endete mit OOM (4GB-RAM-Testhost ohne Swap, FDN-03/FDN-09 puffern vollstaendig im Speicher statt zu streamen). Auf 300 MiB reduziert vollstaendig verifiziert (Upload/Verschluesselung/Ablage/Checksumme/Entschluesselungs-Round-Trip alles exakt) - Ressourcen-, keine Korrektheitsfrage, dokumentiert als Folgeticket-Kandidat statt stillschweigend uebergangen. Siehe dms/docs/DOC-01-PRUEFPROTOKOLL.md fuer alle Pruefungsergebnisse. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQ
60 lines
2.1 KiB
Go
60 lines
2.1 KiB
Go
// Package upload implementiert DOC-01: fortsetzbare Server-seitige
|
|
// Dateiaufnahme mit Größen-/MIME-Validierung und transaktionaler
|
|
// Dokument+Revision-Anlage. Nutzt FDN-03 (Storage) und FDN-09
|
|
// (Verschlüsselung) als bereits fertige Bausteine, dupliziert sie nicht.
|
|
package upload
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
)
|
|
|
|
// ErrFileTooLarge wird geliefert, wenn die angekündigte oder tatsächliche
|
|
// Größe das konfigurierte Limit überschreitet (Schutz vor
|
|
// Speicherbomben — siehe "Bekannte Fehler vermeiden" im Ticket).
|
|
var ErrFileTooLarge = errors.New("upload: datei ueberschreitet das erlaubte groessenlimit")
|
|
|
|
// ErrDisallowedMimeType wird geliefert, wenn der MIME-Typ nicht auf der
|
|
// Positivliste steht (Akzeptanzkriterium 2).
|
|
var ErrDisallowedMimeType = errors.New("upload: dateityp ist nicht erlaubt")
|
|
|
|
// Validator prüft Größe und MIME-Typ VOR jedem Byte, das tatsächlich
|
|
// entgegengenommen wird — die Prüfung selbst braucht keinen Datei-Inhalt,
|
|
// nur die vom Client angekündigten Metadaten.
|
|
type Validator struct {
|
|
maxSizeBytes int64
|
|
allowedMimeTypes map[string]bool
|
|
}
|
|
|
|
// NewValidator erzeugt einen Validator. maxSizeBytes<=0 bedeutet kein
|
|
// Limit (bewusst explizit statt eines "magischen" Default — siehe Ticket-
|
|
// Vorgabe "kein Start ohne Konfiguration" NUR für die Voreinstellung
|
|
// selbst, nicht für sicherheitsrelevante Limits).
|
|
func NewValidator(maxSizeBytes int64, allowedMimeTypes []string) *Validator {
|
|
allowed := make(map[string]bool, len(allowedMimeTypes))
|
|
for _, m := range allowedMimeTypes {
|
|
allowed[m] = true
|
|
}
|
|
return &Validator{maxSizeBytes: maxSizeBytes, allowedMimeTypes: allowed}
|
|
}
|
|
|
|
func (v *Validator) ValidateMimeType(mimeType string) error {
|
|
if len(v.allowedMimeTypes) == 0 {
|
|
return nil
|
|
}
|
|
if !v.allowedMimeTypes[mimeType] {
|
|
return fmt.Errorf("%w: %q", ErrDisallowedMimeType, mimeType)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (v *Validator) ValidateSize(sizeBytes int64) error {
|
|
if v.maxSizeBytes <= 0 {
|
|
return nil
|
|
}
|
|
if sizeBytes > v.maxSizeBytes {
|
|
return fmt.Errorf("%w: %d bytes > limit %d bytes", ErrFileTooLarge, sizeBytes, v.maxSizeBytes)
|
|
}
|
|
return nil
|
|
}
|