Files
nexarch/dms/internal/mailimport/parse.go
patrickandClaude Sonnet 5 8ff4dcdb48
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
INT-04: e-mail-regeln & anhangverarbeitung
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-20 20:06:33 +02:00

114 lines
2.5 KiB
Go

package mailimport
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"io"
"mime"
"net/http"
"path"
"strings"
"time"
"unicode"
"github.com/emersion/go-message"
"github.com/emersion/go-message/mail"
)
type attachment struct {
Filename string
MimeType string
Data []byte
}
type parsedMessage struct {
Subject string
Sender string
Date *time.Time
MessageIDHash string
Attachments []attachment
}
func parseMessage(raw []byte) (*parsedMessage, error) {
mr, err := mail.CreateReader(bytes.NewReader(raw))
if err != nil && !message.IsUnknownCharset(err) && !message.IsUnknownEncoding(err) {
return nil, fmt.Errorf("mailimport: nachricht parsen: %w", err)
}
if mr == nil {
return nil, errors.New("mailimport: nachricht parsen: kein leser")
}
defer func() { _ = mr.Close() }()
out := &parsedMessage{}
out.Subject, _ = mr.Header.Subject()
if from, err := mr.Header.AddressList("From"); err == nil && len(from) > 0 {
out.Sender = strings.ToLower(from[0].Address)
}
if d, err := mr.Header.Date(); err == nil && !d.IsZero() {
out.Date = &d
}
if id := strings.TrimSpace(mr.Header.Get("Message-Id")); id != "" {
sum := sha256.Sum256([]byte(id))
out.MessageIDHash = hex.EncodeToString(sum[:])
}
for {
p, err := mr.NextPart()
if errors.Is(err, io.EOF) {
break
}
if err != nil {
if message.IsUnknownCharset(err) || message.IsUnknownEncoding(err) {
continue
}
return nil, fmt.Errorf("mailimport: anhang lesen: %w", err)
}
ah, ok := p.Header.(*mail.AttachmentHeader)
if !ok {
continue
}
data, err := io.ReadAll(p.Body)
if err != nil {
return nil, fmt.Errorf("mailimport: anhang lesen: %w", err)
}
name, _ := ah.Filename()
mt, _, _ := ah.ContentType()
out.Attachments = append(out.Attachments, attachment{Filename: name, MimeType: mt, Data: data})
}
return out, nil
}
func resolveMime(declared string, data []byte) string {
mt, _, err := mime.ParseMediaType(declared)
if err != nil || mt == "" || mt == "application/octet-stream" {
head := data
if len(head) > 512 {
head = head[:512]
}
return http.DetectContentType(head)
}
return strings.ToLower(mt)
}
func safeFilename(name, fallback string) string {
name = strings.ReplaceAll(name, "\\", "/")
name = path.Base(name)
var b strings.Builder
for _, r := range name {
if unicode.IsControl(r) || r == '/' {
continue
}
b.WriteRune(r)
}
out := strings.TrimSpace(strings.Trim(b.String(), "."))
if out == "" || out == "/" {
return fallback
}
if len(out) > 200 {
out = out[:200]
}
return out
}