diff --git a/pkg/analyzer/content.go b/pkg/analyzer/content.go index 3e29a7a..06f8ddf 100644 --- a/pkg/analyzer/content.go +++ b/pkg/analyzer/content.go @@ -501,11 +501,6 @@ func (c *ContentAnalyzer) hasDomainMisalignment(href, linkText string) bool { return false } - // Replace email addresses with just their domain part to avoid false positives - // e.g. "john.doe@example.com" → "example.com" so local-part dots don't look like domains - emailAddrRegex := regexp.MustCompile(`(?i)[a-z0-9._%+\-]+@([a-z0-9.\-]+\.[a-z]{2,})`) - linkText = emailAddrRegex.ReplaceAllString(linkText, "$1") - // Common generic link texts that shouldn't trigger warnings genericTexts := []string{ "click here", "read more", "learn more", "download", "subscribe", diff --git a/pkg/analyzer/headers.go b/pkg/analyzer/headers.go index 9e5853e..448de57 100644 --- a/pkg/analyzer/headers.go +++ b/pkg/analyzer/headers.go @@ -589,53 +589,9 @@ func (h *HeaderAnalyzer) findHeaderIssues(email *EmailMessage) []model.HeaderIss }) } - // Check for fake reply/forward: Subject has Re:/Fwd: prefix but no thread headers - subject := email.GetHeaderValue("Subject") - if h.hasReplyPrefix(subject) && !email.HasHeader("References") && !email.HasHeader("In-Reply-To") { - issues = append(issues, model.HeaderIssue{ - Header: "Subject", - Severity: model.HeaderIssueSeverityHigh, - Message: "Subject indicates a reply or forward but no References or In-Reply-To header is present", - Advice: utils.PtrTo("Remove the Re:/Fwd: prefix from the subject, or add References/In-Reply-To headers if this is a genuine reply"), - }) - } - return issues } -// hasReplyPrefix reports whether a subject line starts with a reply or forward prefix. -func (h *HeaderAnalyzer) hasReplyPrefix(subject string) bool { - // Normalize: collapse leading whitespace and make comparison case-insensitive - s := strings.ToLower(strings.TrimSpace(subject)) - - prefixes := []string{ - "re:", // English / universal - "fwd:", // English forward - "fw:", // English forward (short) - "aw:", // German Antwort - "wg:", // German Weitergeleitet - "sv:", // Scandinavian Svar - "vs:", // Finnish Vastaus / Norwegian - "ref:", // Some clients - "rép:", // French Réponse - "tr:", // French Transfert - "odp:", // Polish Odpowiedź - "ynt:", // Turkish Yanıt - "res:", // Portuguese/Spanish Resposta/Respuesta - "enc:", // Spanish Enviado/Reenviado - "vl:", // Dutch Verwijzing - "antw:", // Dutch Antwoord - "rv:", // Norwegian/Swedish - } - - for _, p := range prefixes { - if strings.HasPrefix(s, p) { - return true - } - } - return false -} - // parseReceivedChain extracts the chain of Received headers from an email func (h *HeaderAnalyzer) parseReceivedChain(email *EmailMessage) []model.ReceivedHop { if email == nil || email.Header == nil { diff --git a/pkg/analyzer/headers_test.go b/pkg/analyzer/headers_test.go index 8426c58..7b453fa 100644 --- a/pkg/analyzer/headers_test.go +++ b/pkg/analyzer/headers_test.go @@ -974,146 +974,6 @@ func TestCheckHeader_DateValidation(t *testing.T) { } } -func TestHasReplyPrefix(t *testing.T) { - tests := []struct { - subject string - expected bool - }{ - // Positive cases - {"Re: Hello", true}, - {"RE: Hello", true}, - {"re: Hello", true}, - {"Fwd: Hello", true}, - {"FWD: Hello", true}, - {"fw: Hello", true}, - {"FW: Hello", true}, - {"Aw: Hallo", true}, - {"WG: Weitergeleitet", true}, - {"Sv: Hej", true}, - {"Vs: Vastaus", true}, - {"Ref: something", true}, - {"Rép: Bonjour", true}, - {"TR: Transféré", true}, - {"Odp: Odpowiedź", true}, - {"Ynt: Yanıt", true}, - {"Res: Resposta", true}, - {"Enc: Reenviado", true}, - {"Vl: Verwijzing", true}, - {"Antw: Antwoord", true}, - {"Rv: Svar", true}, - // Negative cases - {"Hello", false}, - {"", false}, - {"react: something", false}, - {"reference: check this", false}, - {"Resources available", false}, - {"Friendly reminder", false}, - } - - analyzer := NewHeaderAnalyzer() - - for _, tt := range tests { - t.Run(tt.subject, func(t *testing.T) { - result := analyzer.hasReplyPrefix(tt.subject) - if result != tt.expected { - t.Errorf("hasReplyPrefix(%q) = %v, want %v", tt.subject, result, tt.expected) - } - }) - } -} - -func TestFindHeaderIssues_FakeReply(t *testing.T) { - tests := []struct { - name string - headers map[string]string - expectIssueType string // non-empty means we expect an issue containing this substring - }{ - { - name: "Re: subject without thread headers", - headers: map[string]string{ - "From": "sender@example.com", - "Date": "Mon, 01 Jan 2024 12:00:00 +0000", - "Message-ID": "", - "Subject": "Re: Your invoice", - }, - expectIssueType: "References or In-Reply-To", - }, - { - name: "Fwd: subject without thread headers", - headers: map[string]string{ - "From": "sender@example.com", - "Date": "Mon, 01 Jan 2024 12:00:00 +0000", - "Message-ID": "", - "Subject": "Fwd: Important update", - }, - expectIssueType: "References or In-Reply-To", - }, - { - name: "Re: subject with References header - no issue", - headers: map[string]string{ - "From": "sender@example.com", - "Date": "Mon, 01 Jan 2024 12:00:00 +0000", - "Message-ID": "", - "Subject": "Re: Your invoice", - "References": "", - }, - expectIssueType: "", - }, - { - name: "Re: subject with In-Reply-To only - no issue", - headers: map[string]string{ - "From": "sender@example.com", - "Date": "Mon, 01 Jan 2024 12:00:00 +0000", - "Message-ID": "", - "Subject": "Re: Your invoice", - "In-Reply-To": "", - }, - expectIssueType: "", - }, - { - name: "Normal subject without thread headers - no issue", - headers: map[string]string{ - "From": "sender@example.com", - "Date": "Mon, 01 Jan 2024 12:00:00 +0000", - "Message-ID": "", - "Subject": "Your invoice", - }, - expectIssueType: "", - }, - } - - analyzer := NewHeaderAnalyzer() - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - email := &EmailMessage{ - Header: createHeaderWithFields(tt.headers), - } - - issues := analyzer.findHeaderIssues(email) - - found := false - for _, issue := range issues { - if strings.Contains(issue.Message, tt.expectIssueType) { - found = true - break - } - } - - if tt.expectIssueType != "" && !found { - t.Errorf("expected issue containing %q, but none found (issues: %v)", tt.expectIssueType, issues) - } - if tt.expectIssueType == "" { - for _, issue := range issues { - if strings.Contains(issue.Message, "References or In-Reply-To") { - t.Errorf("unexpected fake-reply issue found: %s", issue.Message) - } - } - } - }) - } -} - // Helper functions for testing func strPtr(s string) *string { return &s diff --git a/web/src/lib/components/BimiRecordDisplay.svelte b/web/src/lib/components/BimiRecordDisplay.svelte index 8d21b1f..889e24f 100644 --- a/web/src/lib/components/BimiRecordDisplay.svelte +++ b/web/src/lib/components/BimiRecordDisplay.svelte @@ -72,26 +72,6 @@ {bimiRecord.error} {/if} - {#if !bimiRecord.valid} -
-
- - Explicitly decline BIMI participation -
-

- If you do not intend to publish a brand logo, you can add a declination - record to signal that this domain deliberately opts out of BIMI. This - prevents mail clients from falling back to a parent-domain record: -

- {bimiRecord.selector}._bimi.{bimiRecord.domain}. IN TXT "v=BIMI1; l=; a=" -

- Declination record format as defined in § 4.3.1 of - draft-brand-indicators-for-message-identification. -

-
- {/if} {/if}