Files
sysops 27d45f58e8 feat(PROJ-36,PROJ-37): gzip-Kompression + Attachment-Deduplication
Sprint 1: Emails werden vor AES-256-GCM optional gzip-komprimiert (compress: true).
Magic-Byte 0x01 als Prefix ermöglicht backward-kompatibles Load() für Legacy-Dateien.
Neue DB-Tabelle storage_objects trackt Kompressions-Metadaten.

Sprint 2: Attachments werden via SHA-256 dedupliziert — gleicher Anhang in N Mails
wird nur einmal gespeichert. Neue Tabellen: attachments, email_attachments.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 01:19:51 +02:00

57 lines
1.5 KiB
Go

package storage
import (
"bytes"
"compress/gzip"
"fmt"
"io"
)
// Compression magic byte — prepended to compressed content before encryption.
// RFC 2822 email headers always start with printable ASCII (>= 0x20),
// so 0x01 is safe as a marker and will never appear in legacy uncompressed files.
const (
magicGzip = byte(0x01)
)
// compressGzip compresses data with gzip and prepends the magic byte.
// Returns an error only on internal gzip failure (effectively never).
func compressGzip(data []byte) ([]byte, error) {
var buf bytes.Buffer
buf.WriteByte(magicGzip)
w, err := gzip.NewWriterLevel(&buf, gzip.BestCompression)
if err != nil {
return nil, fmt.Errorf("storage: gzip writer: %w", err)
}
if _, err := w.Write(data); err != nil {
_ = w.Close()
return nil, fmt.Errorf("storage: gzip write: %w", err)
}
if err := w.Close(); err != nil {
return nil, fmt.Errorf("storage: gzip close: %w", err)
}
return buf.Bytes(), nil
}
// maybeDecompress inspects the first byte of data and decompresses if needed.
// Legacy files (no magic byte, first byte is printable ASCII) are returned as-is.
func maybeDecompress(data []byte) ([]byte, error) {
if len(data) < 2 {
return data, nil
}
if data[0] != magicGzip {
// Legacy file: no compression — return as-is
return data, nil
}
r, err := gzip.NewReader(bytes.NewReader(data[1:]))
if err != nil {
return nil, fmt.Errorf("storage: gzip reader: %w", err)
}
defer r.Close()
out, err := io.ReadAll(r)
if err != nil {
return nil, fmt.Errorf("storage: gzip decompress: %w", err)
}
return out, nil
}