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>
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user