Compare commits
No commits in common. "1c1d474870bf1702c956f2a43a124d67490cd3cd" and "88553cd3c8976dac68dec3cec5826d4583c71f35" have entirely different histories.
1c1d474870
...
88553cd3c8
2 changed files with 13 additions and 38 deletions
|
|
@ -27,7 +27,6 @@ import (
|
|||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
|
@ -38,9 +37,8 @@ import (
|
|||
|
||||
// ContentAnalyzer analyzes email content (HTML, links, images)
|
||||
type ContentAnalyzer struct {
|
||||
Timeout time.Duration
|
||||
httpClient *http.Client
|
||||
listUnsubscribeURLs []string // URLs from List-Unsubscribe header
|
||||
Timeout time.Duration
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
// NewContentAnalyzer creates a new content analyzer with configurable timeout
|
||||
|
|
@ -112,9 +110,6 @@ func (c *ContentAnalyzer) AnalyzeContent(email *EmailMessage) *ContentResults {
|
|||
|
||||
results.IsMultipart = len(email.Parts) > 1
|
||||
|
||||
// Parse List-Unsubscribe header URLs for use in link detection
|
||||
c.listUnsubscribeURLs = email.GetListUnsubscribeURLs()
|
||||
|
||||
// Get HTML and text parts
|
||||
htmlParts := email.GetHTMLParts()
|
||||
textParts := email.GetTextParts()
|
||||
|
|
@ -336,11 +331,6 @@ func (c *ContentAnalyzer) getAttr(n *html.Node, key string) string {
|
|||
|
||||
// isUnsubscribeLink checks if a link is an unsubscribe link
|
||||
func (c *ContentAnalyzer) isUnsubscribeLink(href string, node *html.Node) bool {
|
||||
// First check: does the href match a URL from the List-Unsubscribe header?
|
||||
if slices.Contains(c.listUnsubscribeURLs, href) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check href for unsubscribe keywords
|
||||
lowerHref := strings.ToLower(href)
|
||||
unsubKeywords := []string{"unsubscribe", "opt-out", "optout", "remove", "list-unsubscribe"}
|
||||
|
|
@ -449,8 +439,7 @@ func (c *ContentAnalyzer) hasDomainMisalignment(href, linkText string) bool {
|
|||
// Extract the actual destination domain/email based on scheme
|
||||
var actualDomain string
|
||||
|
||||
switch parsedURL.Scheme {
|
||||
case "mailto":
|
||||
if parsedURL.Scheme == "mailto" {
|
||||
// Extract email address from mailto: URL
|
||||
// Format can be: mailto:user@domain.com or mailto:user@domain.com?subject=...
|
||||
mailtoAddr := parsedURL.Opaque
|
||||
|
|
@ -468,8 +457,7 @@ func (c *ContentAnalyzer) hasDomainMisalignment(href, linkText string) bool {
|
|||
} else {
|
||||
return false // Invalid mailto
|
||||
}
|
||||
case "http":
|
||||
case "https":
|
||||
} else if parsedURL.Scheme == "http" || parsedURL.Scheme == "https" {
|
||||
// Check if URL has a host
|
||||
if parsedURL.Host == "" {
|
||||
return false
|
||||
|
|
@ -481,7 +469,7 @@ func (c *ContentAnalyzer) hasDomainMisalignment(href, linkText string) bool {
|
|||
actualDomain = actualDomain[:idx]
|
||||
}
|
||||
actualDomain = strings.ToLower(actualDomain)
|
||||
default:
|
||||
} else {
|
||||
// Skip checks for other URL schemes (tel, etc.)
|
||||
return false
|
||||
}
|
||||
|
|
@ -504,8 +492,10 @@ func (c *ContentAnalyzer) hasDomainMisalignment(href, linkText string) bool {
|
|||
"email us", "contact us", "send email", "get in touch", "reach out",
|
||||
"contact", "email", "write to us",
|
||||
}
|
||||
if slices.Contains(genericTexts, linkText) {
|
||||
return false
|
||||
for _, generic := range genericTexts {
|
||||
if linkText == generic {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Extract domain-like patterns from link text using regex
|
||||
|
|
@ -572,8 +562,10 @@ func (c *ContentAnalyzer) isSuspiciousURL(urlStr string, parsedURL *url.URL) boo
|
|||
"bit.ly", "tinyurl.com", "goo.gl", "ow.ly", "t.co",
|
||||
"buff.ly", "is.gd", "bl.ink", "short.io",
|
||||
}
|
||||
if slices.Contains(shorteners, strings.ToLower(parsedURL.Host)) {
|
||||
return true
|
||||
for _, shortener := range shorteners {
|
||||
if strings.ToLower(parsedURL.Host) == shortener {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Check for excessive subdomains (possible obfuscation)
|
||||
|
|
|
|||
|
|
@ -301,20 +301,3 @@ func (e *EmailMessage) GetHeaderValue(key string) string {
|
|||
func (e *EmailMessage) HasHeader(key string) bool {
|
||||
return e.Header.Get(key) != ""
|
||||
}
|
||||
|
||||
// GetListUnsubscribeURLs parses the List-Unsubscribe header and returns all URLs.
|
||||
// The header format is: <url1>, <url2>, ...
|
||||
func (e *EmailMessage) GetListUnsubscribeURLs() []string {
|
||||
value := e.Header.Get("List-Unsubscribe")
|
||||
if value == "" {
|
||||
return nil
|
||||
}
|
||||
var urls []string
|
||||
for _, part := range strings.Split(value, ",") {
|
||||
part = strings.TrimSpace(part)
|
||||
if strings.HasPrefix(part, "<") && strings.HasSuffix(part, ">") {
|
||||
urls = append(urls, part[1:len(part)-1])
|
||||
}
|
||||
}
|
||||
return urls
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue