Read target mailbox for audit pane
This commit is contained in:
parent
2bdf622257
commit
ffa28f6182
3 changed files with 114 additions and 5 deletions
|
|
@ -11,6 +11,7 @@ type TargetMailbox interface {
|
||||||
Delim() string // Hierarchie-Trenner des Ziels
|
Delim() string // Hierarchie-Trenner des Ziels
|
||||||
EnsureFolder(name string) error // anlegen + subscriben, falls fehlt
|
EnsureFolder(name string) error // anlegen + subscriben, falls fehlt
|
||||||
Append(folder string, m RawMessage) error // mit m.Flags und m.InternalDate
|
Append(folder string, m RawMessage) error // mit m.Flags und m.InternalDate
|
||||||
|
Fetch(folder string) ([]RawMessage, error) // Ziel-Audit: geschriebene Mail wiederfinden
|
||||||
Close() error
|
Close() error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -78,6 +79,13 @@ func (t *imapTarget) Append(folder string, m RawMessage) error {
|
||||||
return t.c.appendMessage(folder, m)
|
return t.c.appendMessage(folder, m)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *imapTarget) Fetch(folder string) ([]RawMessage, error) {
|
||||||
|
if err := t.c.selectMailbox(folder); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return t.c.fetchAll()
|
||||||
|
}
|
||||||
|
|
||||||
func (t *imapTarget) Close() error {
|
func (t *imapTarget) Close() error {
|
||||||
return t.c.Close()
|
return t.c.Close()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -186,27 +186,37 @@ func renderReadPane(w io.Writer, title, subject, from, date, body string) {
|
||||||
func renderTargetPane(account, folder, targetAccount string, index int, raw []byte) string {
|
func renderTargetPane(account, folder, targetAccount string, index int, raw []byte) string {
|
||||||
var b strings.Builder
|
var b strings.Builder
|
||||||
b.WriteString(renderTargetAccountSelect(account, folder, targetAccount, index))
|
b.WriteString(renderTargetAccountSelect(account, folder, targetAccount, index))
|
||||||
|
if match, ok, err := findTargetIMAPCopy(account, targetAccount, raw); ok {
|
||||||
|
renderTargetMatch(&b, match)
|
||||||
|
return b.String()
|
||||||
|
} else if err != nil {
|
||||||
|
fmt.Fprintf(&b, `<div class="audit-note bad">Zielpostfach-Pruefung fehlgeschlagen: %s</div>`, html.EscapeString(err.Error()))
|
||||||
|
}
|
||||||
match, ok := findForwardedCopy(account, targetAccount, raw)
|
match, ok := findForwardedCopy(account, targetAccount, raw)
|
||||||
if !ok {
|
if !ok {
|
||||||
id := messageIDHeader(raw)
|
id := messageIDHeader(raw)
|
||||||
if id == "" {
|
if id == "" {
|
||||||
id = "(ohne Message-ID)"
|
id = "(ohne Message-ID)"
|
||||||
}
|
}
|
||||||
fmt.Fprintf(&b, `<div class="ef-empty">Keine lokale Zielkopie gefunden.<br><br>Message-ID: %s</div>`, html.EscapeString(id))
|
fmt.Fprintf(&b, `<div class="ef-empty">Keine Zielkopie gefunden.<br><br>Message-ID: %s</div>`, html.EscapeString(id))
|
||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
|
renderTargetMatch(&b, match)
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderTargetMatch(b *strings.Builder, match forwardedMatch) {
|
||||||
msg, err := mail.ReadMessage(bytes.NewReader(match.Raw))
|
msg, err := mail.ReadMessage(bytes.NewReader(match.Raw))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
renderReadContent(&b, "(unlesbar)", match.Location(), "", string(match.Raw))
|
renderReadContent(b, "(unlesbar)", match.Location(), "", string(match.Raw))
|
||||||
return b.String()
|
return
|
||||||
}
|
}
|
||||||
renderReadContent(&b,
|
renderReadContent(b,
|
||||||
decodeHeader(msg.Header.Get("Subject")),
|
decodeHeader(msg.Header.Get("Subject")),
|
||||||
decodeHeader(msg.Header.Get("From")),
|
decodeHeader(msg.Header.Get("From")),
|
||||||
decodeHeader(msg.Header.Get("Date"))+" | "+match.Location(),
|
decodeHeader(msg.Header.Get("Date"))+" | "+match.Location(),
|
||||||
messageBody(match.Raw),
|
messageBody(match.Raw),
|
||||||
)
|
)
|
||||||
return b.String()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderInitialTargetPane() string {
|
func renderInitialTargetPane() string {
|
||||||
|
|
@ -279,6 +289,63 @@ func (m forwardedMatch) Location() string {
|
||||||
return fmt.Sprintf("%s / %s #%d", m.Account, m.Folder, m.Index)
|
return fmt.Sprintf("%s / %s #%d", m.Account, m.Folder, m.Index)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func findTargetIMAPCopy(account, targetAccount string, raw []byte) (forwardedMatch, bool, error) {
|
||||||
|
id := messageIDHeader(raw)
|
||||||
|
if id == "" {
|
||||||
|
return forwardedMatch{}, false, nil
|
||||||
|
}
|
||||||
|
targetUser := strings.TrimSpace(targetAccount)
|
||||||
|
if targetUser == "" {
|
||||||
|
source, ok := accountByMboxDir(account)
|
||||||
|
if !ok {
|
||||||
|
return forwardedMatch{}, false, nil
|
||||||
|
}
|
||||||
|
targetUser = strings.TrimSpace(source.DstUser)
|
||||||
|
}
|
||||||
|
for _, a := range accountsForTargetUser(targetUser) {
|
||||||
|
dst, err := OpenIMAPTarget(a)
|
||||||
|
if err != nil {
|
||||||
|
return forwardedMatch{}, false, err
|
||||||
|
}
|
||||||
|
match, ok, scanErr := scanTargetForMessage(dst, targetUser, id)
|
||||||
|
closeErr := dst.Close()
|
||||||
|
if scanErr != nil {
|
||||||
|
return forwardedMatch{}, false, scanErr
|
||||||
|
}
|
||||||
|
if closeErr != nil {
|
||||||
|
return forwardedMatch{}, false, closeErr
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
return match, true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return forwardedMatch{}, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func scanTargetForMessage(dst TargetMailbox, targetUser, messageID string) (forwardedMatch, bool, error) {
|
||||||
|
folders, err := dst.Folders()
|
||||||
|
if err != nil {
|
||||||
|
return forwardedMatch{}, false, err
|
||||||
|
}
|
||||||
|
for _, folder := range folders {
|
||||||
|
msgs, err := dst.Fetch(folder.Name)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for i, msg := range msgs {
|
||||||
|
if msg.MessageID == messageID || messageIDHeader(msg.Body) == messageID {
|
||||||
|
return forwardedMatch{
|
||||||
|
Account: "Ziel: " + targetUser,
|
||||||
|
Folder: folder.Name,
|
||||||
|
Index: i,
|
||||||
|
Raw: msg.Body,
|
||||||
|
}, true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return forwardedMatch{}, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
func findForwardedCopy(account, targetAccount string, raw []byte) (forwardedMatch, bool) {
|
func findForwardedCopy(account, targetAccount string, raw []byte) (forwardedMatch, bool) {
|
||||||
id := messageIDHeader(raw)
|
id := messageIDHeader(raw)
|
||||||
if id == "" {
|
if id == "" {
|
||||||
|
|
@ -440,6 +507,39 @@ func accountLabels() map[string]viewerAccount {
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func accountByMboxDir(mboxDir string) (Account, bool) {
|
||||||
|
if DB == nil {
|
||||||
|
return Account{}, false
|
||||||
|
}
|
||||||
|
accounts, err := ListAccounts()
|
||||||
|
if err != nil {
|
||||||
|
return Account{}, false
|
||||||
|
}
|
||||||
|
for _, a := range accounts {
|
||||||
|
if accountMboxDir(a) == mboxDir {
|
||||||
|
return a, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Account{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func accountsForTargetUser(targetUser string) []Account {
|
||||||
|
if DB == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
accounts, err := ListAccounts()
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var out []Account
|
||||||
|
for _, a := range accounts {
|
||||||
|
if strings.EqualFold(strings.TrimSpace(a.DstUser), targetUser) {
|
||||||
|
out = append(out, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
func targetAccountOptions() []targetAccountOption {
|
func targetAccountOptions() []targetAccountOption {
|
||||||
if DB == nil {
|
if DB == nil {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
1
frontend-js/dist/style.css
vendored
1
frontend-js/dist/style.css
vendored
|
|
@ -144,6 +144,7 @@ a.tree-node { display: block; color: inherit; text-decoration: none; }
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
.ef-empty { color: var(--ol-muted); padding: 24px; }
|
.ef-empty { color: var(--ol-muted); padding: 24px; }
|
||||||
|
.audit-note { padding: 10px 16px; border-bottom: 1px solid var(--ol-border); font-size: 12px; }
|
||||||
|
|
||||||
/* Buttons / Formulare (flach, 2013er) */
|
/* Buttons / Formulare (flach, 2013er) */
|
||||||
.btn {
|
.btn {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue