updated layout for webmail and added http dns letsencrypt

This commit is contained in:
2026-08-15 12:35:44 +01:00
parent 310700407e
commit f283c90f11
49 changed files with 3359 additions and 431 deletions
+73 -1
View File
@@ -9,7 +9,10 @@ import (
"net/mail"
"os"
"path/filepath"
"strings"
"time"
"mailgoserver/internal/mailview"
)
// extractHeaderValue reads a single header out of raw without parsing the body — used
@@ -25,6 +28,29 @@ func extractHeaderValue(raw []byte, name string) string {
return msg.Header.Get(name)
}
const previewSnippetLen = 150
// previewSnippet extracts up to previewSnippetLen characters of the plain-text body
// for the folder list's preview line — cached in plain text alongside cached_from/
// cached_subject (see schema.go's comment on cached_preview for why that's consistent
// with the existing cached_* columns, not a new exposure). HTML-only mail (no
// text/plain part) gets no preview rather than a crude tag-stripped approximation —
// an accepted scope limit, not a bug: most real mail includes a text/plain
// alternative regardless of whether the sender expects it to be shown.
func previewSnippet(raw []byte) string {
parsed, err := mailview.Parse(raw)
if err != nil {
return ""
}
text := strings.Join(strings.Fields(parsed.TextBody), " ")
// Rune-safe truncation — a plain byte slice could split a multi-byte UTF-8
// character in half and produce invalid text.
if runes := []rune(text); len(runes) > previewSnippetLen {
text = string(runes[:previewSnippetLen])
}
return text
}
// ErrQuotaExceeded is returned by StoreMessage when storing raw would push the
// mailbox over its quota. No row, file, or used_bytes change occurs in that case.
var ErrQuotaExceeded = errors.New("mailstore: mailbox quota exceeded")
@@ -66,7 +92,7 @@ func (s *Store) StoreMessage(mailboxID int64, folder string, raw []byte, message
return 0, err
}
uid, err = s.DB.InsertMessage(mailboxID, folder, messageIDHeader, "", now, int64(len(raw)), storagePath, nonce, from, extractHeaderValue(raw, "To"), subject)
uid, err = s.DB.InsertMessage(mailboxID, folder, messageIDHeader, "", now, int64(len(raw)), storagePath, nonce, from, extractHeaderValue(raw, "To"), subject, previewSnippet(raw))
if err != nil {
os.Remove(storagePath)
return 0, err
@@ -77,6 +103,52 @@ func (s *Store) StoreMessage(mailboxID int64, folder string, raw []byte, message
return uid, nil
}
// RebuildMessageCache re-derives cached_from/cached_to/cached_subject/cached_preview
// for every message already stored in mailboxID, from each message's own decrypted
// content — these fields are otherwise only ever computed once, at delivery time
// (see StoreMessage), so mail stored before a caching fix or addition landed (e.g.
// caching the From: header's display name instead of the bare envelope address, or
// the cached_preview column itself) keeps showing the old/blank value forever unless
// something re-derives it. Returns how many rows actually changed; a message that
// fails to decrypt/parse is skipped (counted in the error map, not fatal to the rest).
func (s *Store) RebuildMessageCache(mailboxID int64) (updated int, skipped map[int64]error) {
skipped = map[int64]error{}
msgs, err := s.DB.ListMessagesForMailbox(mailboxID)
if err != nil {
skipped[0] = err
return 0, skipped
}
for _, m := range msgs {
raw, err := s.FetchMessage(mailboxID, m.ID)
if err != nil {
skipped[m.ID] = err
continue
}
from := extractHeaderValue(raw, "From")
if from == "" {
from = m.CachedFrom
}
to := extractHeaderValue(raw, "To")
if to == "" {
to = m.CachedTo
}
subject := extractHeaderValue(raw, "Subject")
if subject == "" {
subject = m.CachedSubject
}
preview := previewSnippet(raw)
if from == m.CachedFrom && to == m.CachedTo && subject == m.CachedSubject && preview == m.CachedPreview {
continue // already correct — don't churn a write for nothing
}
if err := s.DB.UpdateMessageCachedFields(m.ID, from, to, subject, preview); err != nil {
skipped[m.ID] = err
continue
}
updated++
}
return updated, skipped
}
// FetchMessage decrypts a stored message on demand. Plaintext is never written to disk
// or cached — only returned to the caller.
func (s *Store) FetchMessage(mailboxID, uid int64) ([]byte, error) {