27d45f58e8
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>
57 lines
1.5 KiB
Go
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
|
|
}
|