114 lines
2.2 KiB
Go
114 lines
2.2 KiB
Go
package listunsubscribe
|
|
|
|
import (
|
|
"net/mail"
|
|
"net/url"
|
|
"strings"
|
|
)
|
|
|
|
// Mailto holds a one-click mailto unsubscribe target.
|
|
type Mailto struct {
|
|
Address string
|
|
Subject string
|
|
Body string
|
|
}
|
|
|
|
// Parsed from List-Unsubscribe (RFC 2369).
|
|
type Parsed struct {
|
|
Mailto *Mailto
|
|
HTTP string
|
|
}
|
|
|
|
func splitHeaderParts(raw string) []string {
|
|
var parts []string
|
|
var cur strings.Builder
|
|
depth := 0
|
|
for _, r := range raw {
|
|
switch r {
|
|
case '<':
|
|
depth++
|
|
cur.WriteRune(r)
|
|
case '>':
|
|
if depth > 0 {
|
|
depth--
|
|
}
|
|
cur.WriteRune(r)
|
|
case ',':
|
|
if depth == 0 {
|
|
if p := strings.TrimSpace(cur.String()); p != "" {
|
|
parts = append(parts, p)
|
|
}
|
|
cur.Reset()
|
|
continue
|
|
}
|
|
cur.WriteRune(r)
|
|
default:
|
|
cur.WriteRune(r)
|
|
}
|
|
}
|
|
if p := strings.TrimSpace(cur.String()); p != "" {
|
|
parts = append(parts, p)
|
|
}
|
|
return parts
|
|
}
|
|
|
|
func unwrapAngle(s string) string {
|
|
s = strings.TrimSpace(s)
|
|
if strings.HasPrefix(s, "<") && strings.HasSuffix(s, ">") {
|
|
return strings.TrimSpace(s[1 : len(s)-1])
|
|
}
|
|
return s
|
|
}
|
|
|
|
// ParseMailtoURL parses mailto:user@host?subject=...&body=...
|
|
func ParseMailtoURL(raw string) (*Mailto, bool) {
|
|
raw = unwrapAngle(strings.TrimSpace(raw))
|
|
if raw == "" {
|
|
return nil, false
|
|
}
|
|
if !strings.HasPrefix(strings.ToLower(raw), "mailto:") {
|
|
return nil, false
|
|
}
|
|
u, err := url.Parse(raw)
|
|
if err != nil {
|
|
return nil, false
|
|
}
|
|
addr := strings.TrimSpace(u.Opaque)
|
|
if addr == "" {
|
|
addr = strings.TrimSpace(strings.TrimPrefix(u.Path, "/"))
|
|
}
|
|
if addr == "" {
|
|
return nil, false
|
|
}
|
|
if _, err := mail.ParseAddress(addr); err != nil {
|
|
// bare addr@host
|
|
if !strings.Contains(addr, "@") {
|
|
return nil, false
|
|
}
|
|
}
|
|
return &Mailto{
|
|
Address: addr,
|
|
Subject: u.Query().Get("subject"),
|
|
Body: u.Query().Get("body"),
|
|
}, true
|
|
}
|
|
|
|
// Parse extracts mailto and https targets from a List-Unsubscribe header value.
|
|
func Parse(listUnsubscribe string) Parsed {
|
|
out := Parsed{}
|
|
for _, part := range splitHeaderParts(listUnsubscribe) {
|
|
part = unwrapAngle(part)
|
|
if m, ok := ParseMailtoURL(part); ok && out.Mailto == nil {
|
|
out.Mailto = m
|
|
continue
|
|
}
|
|
lower := strings.ToLower(part)
|
|
if strings.HasPrefix(lower, "http://") || strings.HasPrefix(lower, "https://") {
|
|
if out.HTTP == "" {
|
|
out.HTTP = part
|
|
}
|
|
}
|
|
}
|
|
return out
|
|
}
|