fixing folders and image rendering in client

This commit is contained in:
2026-08-15 18:38:11 +01:00
parent 6f0c305367
commit 15678c1b6e
16 changed files with 929 additions and 26 deletions
+55 -6
View File
@@ -6,6 +6,7 @@ package db
import (
"database/sql"
"fmt"
"strings"
_ "modernc.org/sqlite"
)
@@ -211,7 +212,15 @@ CREATE TABLE IF NOT EXISTS esrv_mailboxes (
mfa_exempt INTEGER NOT NULL DEFAULT 0,
-- Off by default: collapse a run of same-subject messages in a folder view into one
-- expandable row. Per-mailbox, not global, since this is purely a display preference.
group_messages INTEGER NOT NULL DEFAULT 0
group_messages INTEGER NOT NULL DEFAULT 0,
-- Remote (http/https) images in an HTML email body are a classic tracking-pixel /
-- read-receipt leak, so they're never auto-loaded — this controls when they show at
-- all: 'ask' (default) strips them and offers a per-message "Show images" reveal;
-- 'trusted' auto-shows only for senders on this mailbox's own
-- esrv_mailbox_trusted_image_senders list; 'always' never blocks (not recommended,
-- offered anyway since it's the mailbox owner's own call). See
-- webmail_mail.go's loadMessageForView / stripRemoteImages.
remote_images_mode TEXT NOT NULL DEFAULT 'ask'
);
-- Self-service webmail portal sessions — deliberately a parallel schema to
@@ -297,6 +306,18 @@ CREATE TABLE IF NOT EXISTS esrv_mailbox_filter_rules (
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- Senders a mailbox owner has explicitly said to always show remote images from
-- (esrv_mailboxes.remote_images_mode = 'trusted') — added either from the account
-- settings page or via the "always allow images from this sender" checkbox offered
-- alongside the per-message "Show images" reveal.
CREATE TABLE IF NOT EXISTS esrv_mailbox_trusted_image_senders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
mailbox_id INTEGER NOT NULL REFERENCES esrv_mailboxes(id),
email TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
UNIQUE(mailbox_id, email)
);
-- One row per stored message. cached_from/cached_subject are deliberately plaintext
-- (a narrow, confirmed exception to "encrypted at rest") so IMAP LIST/basic SEARCH
-- don't need to decrypt every message in a folder; body and every other header stay
@@ -485,11 +506,9 @@ func migrateAddedColumns(db *sql.DB) {
`ALTER TABLE esrv_domains ADD COLUMN mfa_exempt INTEGER NOT NULL DEFAULT 0`,
`ALTER TABLE esrv_mailboxes ADD COLUMN mfa_exempt INTEGER NOT NULL DEFAULT 0`,
`ALTER TABLE esrv_mailbox_messages ADD COLUMN cached_to TEXT NOT NULL DEFAULT ''`,
// conditions_json/match_type are retrofittable via ALTER TABLE, but the action
// CHECK constraint (adding 'mark_as_spam') is not — SQLite doesn't support
// altering a CHECK on an existing table. A dev DB created before this change
// would need recreating to accept a mark_as_spam rule; a fresh install gets it
// for free from the CREATE TABLE above.
// The action CHECK constraint (adding 'mark_as_spam') isn't retrofittable via
// ALTER TABLE — see migrateFilterRulesMarkAsSpamCheck below, called at the end
// of this function, which rebuilds the table for DBs that predate it.
`ALTER TABLE esrv_mailbox_filter_rules ADD COLUMN conditions_json TEXT NOT NULL DEFAULT ''`,
`ALTER TABLE esrv_mailbox_filter_rules ADD COLUMN match_type TEXT NOT NULL DEFAULT 'all'`,
// key_pem replaces the old passphrase-wrapped key_ciphertext/key_nonce/key_salt
@@ -505,6 +524,7 @@ func migrateAddedColumns(db *sql.DB) {
`ALTER TABLE esrv_mailbox_folders ADD COLUMN restore_parent_id INTEGER REFERENCES esrv_mailbox_folders(id)`,
`ALTER TABLE esrv_mailbox_folders ADD COLUMN restore_parent_root TEXT NOT NULL DEFAULT ''`,
`ALTER TABLE esrv_mailbox_messages ADD COLUMN restore_folder TEXT NOT NULL DEFAULT ''`,
`ALTER TABLE esrv_mailboxes ADD COLUMN remote_images_mode TEXT NOT NULL DEFAULT 'ask'`,
}
// The three old columns above were NOT NULL with no default, so simply adding
// key_pem left them behind still blocking every new insert (which only ever sets
@@ -523,6 +543,35 @@ func migrateAddedColumns(db *sql.DB) {
// account skip its username change entirely once it re-hits /first-login next.
db.Exec(`UPDATE esrv_admin_users SET must_change_username = 1 WHERE username = ? AND must_change_password = 1`, DefaultAdminUsername)
migrateSpamRenamedToJunk(db)
migrateFilterRulesMarkAsSpamCheck(db)
}
// migrateFilterRulesMarkAsSpamCheck rebuilds esrv_mailbox_filter_rules for any DB
// created before 'mark_as_spam' was added to the action CHECK constraint (webmail's
// "Mark as Junk" auto-blacklist rule, see webmail_mail.go's ensureJunkRuleForSender) —
// SQLite can't ALTER a CHECK constraint on an existing table, so the only way to widen
// it is to recreate the table under the current schema and copy the rows across.
// Detects the stale constraint by inspecting sqlite_master rather than tracking a
// schema-version number, so it stays a no-op forever once a DB is caught up.
func migrateFilterRulesMarkAsSpamCheck(db *sql.DB) {
var tableSQL string
if err := db.QueryRow(`SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'esrv_mailbox_filter_rules'`).Scan(&tableSQL); err != nil {
return
}
if strings.Contains(tableSQL, "mark_as_spam") {
return
}
if _, err := db.Exec(`ALTER TABLE esrv_mailbox_filter_rules RENAME TO esrv_mailbox_filter_rules_old`); err != nil {
return
}
if _, err := db.Exec(schema); err != nil {
return
}
db.Exec(`INSERT INTO esrv_mailbox_filter_rules
(id, mailbox_id, priority, condition_field, condition_op, condition_value, action, action_value, is_active, conditions_json, match_type, created_at)
SELECT id, mailbox_id, priority, condition_field, condition_op, condition_value, action, action_value, is_active, conditions_json, match_type, created_at
FROM esrv_mailbox_filter_rules_old`)
db.Exec(`DROP TABLE esrv_mailbox_filter_rules_old`)
}
// migrateSpamRenamedToJunk renames the standard "Spam" folder to "Junk" for mailboxes