diff options
Diffstat (limited to 'utils')
| -rw-r--r-- | utils/email/messages.go | 63 | ||||
| -rw-r--r-- | utils/format/html.go | 49 |
2 files changed, 88 insertions, 24 deletions
diff --git a/utils/email/messages.go b/utils/email/messages.go index cedbec5..38d9f3a 100644 --- a/utils/email/messages.go +++ b/utils/email/messages.go @@ -1,6 +1,7 @@ package email import ( + "bytes" "fmt" "io" "lain/types" @@ -17,14 +18,14 @@ func SelectFolder(client *types.EmailClient, folderName string) (*imap.MailboxSt return mbox, nil } -func FetchMessages(client *types.EmailClient, folderName string, limit uint32) ([]*types.EmailMessage, error) { - mbox, err := SelectFolder(client, folderName) +func FetchMessages(client *types.EmailClient, folderName string, limit uint32) ([]types.EmailMessage, error) { + mbox, err := client.Select(folderName, false) if err != nil { return nil, err } if mbox.Messages == 0 { - return []*types.EmailMessage{}, nil + return []types.EmailMessage{}, nil } from := uint32(1) @@ -39,14 +40,24 @@ func FetchMessages(client *types.EmailClient, folderName string, limit uint32) ( messages := make(chan *imap.Message, 10) done := make(chan error, 1) - section := &imap.BodySectionName{} - items := []imap.FetchItem{imap.FetchEnvelope, imap.FetchFlags, imap.FetchUid, imap.FetchRFC822Size, section.FetchItem()} + section := &imap.BodySectionName{Peek: true} + headerSection := &imap.BodySectionName{Peek: true} + + items := []imap.FetchItem{ + imap.FetchEnvelope, + imap.FetchFlags, + imap.FetchUid, + imap.FetchRFC822Size, + section.FetchItem(), + headerSection.FetchItem(), + } go func() { done <- client.Fetch(seqset, items, messages) }() - var result []*types.EmailMessage + var result []types.EmailMessage + for msg := range messages { if msg == nil { continue @@ -57,11 +68,11 @@ func FetchMessages(client *types.EmailClient, folderName string, limit uint32) ( continue } - result = append(result, emailMsg) + result = append(result, *emailMsg) } if err := <-done; err != nil { - return nil, fmt.Errorf("failed to fetch messages: %w", err) + return nil, err } return result, nil @@ -72,13 +83,28 @@ func parseMessage(msg *imap.Message) (*types.EmailMessage, error) { return nil, fmt.Errorf("message envelope is nil") } - section := &imap.BodySectionName{} + section := &imap.BodySectionName{Peek: true} bodyReader := msg.GetBody(section) if bodyReader == nil { return nil, fmt.Errorf("message body is nil") } - mr, err := mail.CreateReader(bodyReader) + fullMessage, err := io.ReadAll(bodyReader) + if err != nil { + return nil, fmt.Errorf("failed to read message: %w", err) + } + + headerEnd := bytes.Index(fullMessage, []byte("\r\n\r\n")) + if headerEnd == -1 { + headerEnd = bytes.Index(fullMessage, []byte("\n\n")) + } + + var rawHeaders string + if headerEnd != -1 { + rawHeaders = string(fullMessage[:headerEnd]) + } + + mr, err := mail.CreateReader(bytes.NewReader(fullMessage)) if err != nil { return nil, fmt.Errorf("failed to create mail reader: %w", err) } @@ -100,9 +126,10 @@ func parseMessage(msg *imap.Message) (*types.EmailMessage, error) { contentType, _, _ := h.ContentType() body, _ := io.ReadAll(part.Body) - if contentType == "text/plain" { + switch contentType { + case "text/plain": bodyText = string(body) - } else if contentType == "text/html" { + case "text/html": bodyHTML = string(body) } @@ -127,7 +154,12 @@ func parseMessage(msg *imap.Message) (*types.EmailMessage, error) { var toList []string for _, addr := range msg.Envelope.To { - toList = append(toList, addr.MailboxName+"@"+addr.HostName) + email := addr.MailboxName + "@" + addr.HostName + if addr.PersonalName != "" { + toList = append(toList, addr.PersonalName+" <"+email+">") + } else { + toList = append(toList, email) + } } var ccList []string @@ -176,6 +208,7 @@ func parseMessage(msg *imap.Message) (*types.EmailMessage, error) { Date: msg.Envelope.Date, BodyText: bodyText, BodyHTML: bodyHTML, + RawHeaders: rawHeaders, Size: msg.Size, InReplyTo: msg.Envelope.InReplyTo, IsRead: isRead, @@ -196,7 +229,7 @@ func MarkAsRead(client *types.EmailClient, folderName string, uid uint32) error seqSet.AddNum(uid) item := imap.FormatFlagsOp(imap.AddFlags, true) - flags := []interface{}{imap.SeenFlag} + flags := []any{imap.SeenFlag} if err := client.UidStore(seqSet, item, flags, nil); err != nil { return fmt.Errorf("failed to mark as read: %w", err) @@ -220,7 +253,7 @@ func ToggleFlag(client *types.EmailClient, folderName string, uid uint32, isFlag item = imap.FormatFlagsOp(imap.AddFlags, true) } - flags := []interface{}{imap.FlaggedFlag} + flags := []any{imap.FlaggedFlag} if err := client.UidStore(seqSet, item, flags, nil); err != nil { return fmt.Errorf("failed to toggle flag: %w", err) diff --git a/utils/format/html.go b/utils/format/html.go index d976cb8..60e2e85 100644 --- a/utils/format/html.go +++ b/utils/format/html.go @@ -7,18 +7,10 @@ import ( ) func SanitizeHTML(htmlContent string) string { - // Remove dangerous tags htmlContent = removeDangerousTags(htmlContent) - - // Remove inline event handlers htmlContent = removeEventHandlers(htmlContent) - - // Remove javascript: protocol htmlContent = removeJavascriptProtocol(htmlContent) - - // Sanitize styles htmlContent = sanitizeStyles(htmlContent) - return htmlContent } @@ -49,7 +41,6 @@ func removeJavascriptProtocol(html string) string { } func sanitizeStyles(html string) string { - // Remove dangerous CSS properties dangerousStyles := []string{"behavior", "expression", "binding", "import", "moz-binding"} for _, style := range dangerousStyles { @@ -127,6 +118,46 @@ func StripHTML(html string) string { return strings.TrimSpace(strings.Join(cleanLines, " ")) } +func HTMLToPlainText(htmlContent string) string { + text := htmlContent + + text = regexp.MustCompile(`(?i)<style[^>]*>[\s\S]*?</style>`).ReplaceAllString(text, "") + text = regexp.MustCompile(`(?i)<script[^>]*>[\s\S]*?</script>`).ReplaceAllString(text, "") + text = regexp.MustCompile(`(?i)<head[^>]*>[\s\S]*?</head>`).ReplaceAllString(text, "") + text = regexp.MustCompile(`(?i)<title[^>]*>[\s\S]*?</title>`).ReplaceAllString(text, "") + + text = regexp.MustCompile(`(?i)<br\s*/?>`).ReplaceAllString(text, "\n") + text = regexp.MustCompile(`(?i)</p>`).ReplaceAllString(text, "\n\n") + text = regexp.MustCompile(`(?i)</div>`).ReplaceAllString(text, "\n") + text = regexp.MustCompile(`(?i)</tr>`).ReplaceAllString(text, "\n") + text = regexp.MustCompile(`(?i)</h[1-6]>`).ReplaceAllString(text, "\n\n") + text = regexp.MustCompile(`(?i)</li>`).ReplaceAllString(text, "\n") + + text = regexp.MustCompile(`<[^>]+>`).ReplaceAllString(text, "") + + text = strings.ReplaceAll(text, " ", " ") + text = strings.ReplaceAll(text, "<", "<") + text = strings.ReplaceAll(text, ">", ">") + text = strings.ReplaceAll(text, "&", "&") + text = strings.ReplaceAll(text, """, "\"") + text = strings.ReplaceAll(text, "'", "'") + text = strings.ReplaceAll(text, "'", "'") + + text = regexp.MustCompile(`\n\s*\n\s*\n+`).ReplaceAllString(text, "\n\n") + text = regexp.MustCompile(`[ \t]+`).ReplaceAllString(text, " ") + + lines := strings.Split(text, "\n") + var cleanLines []string + for _, line := range lines { + trimmed := strings.TrimSpace(line) + if trimmed != "" { + cleanLines = append(cleanLines, trimmed) + } + } + + return strings.TrimSpace(strings.Join(cleanLines, "\n")) +} + func DecodeHTML(text string) string { return html.UnescapeString(text) } |
