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
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
114 lines
2.5 KiB
Go
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
|
|
}
|