290 lines
7.1 KiB
Go
290 lines
7.1 KiB
Go
package internal
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"io"
|
|
"net/mail"
|
|
"os"
|
|
"strings"
|
|
"unicode/utf8"
|
|
|
|
"golang.org/x/text/encoding/charmap"
|
|
"golang.org/x/text/transform"
|
|
)
|
|
|
|
type EmailAttachment struct {
|
|
Filename string `json:"filename"`
|
|
ContentType string `json:"contentType"`
|
|
Data []byte `json:"data"`
|
|
}
|
|
|
|
type EmailData struct {
|
|
From string `json:"from"`
|
|
To []string `json:"to"`
|
|
Cc []string `json:"cc"`
|
|
Bcc []string `json:"bcc"`
|
|
Subject string `json:"subject"`
|
|
Body string `json:"body"`
|
|
Attachments []EmailAttachment `json:"attachments"`
|
|
IsPec bool `json:"isPec"`
|
|
HasInnerEmail bool `json:"hasInnerEmail"`
|
|
}
|
|
|
|
func ReadEmlFile(filePath string) (*EmailData, error) {
|
|
file, err := os.Open(filePath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to open file: %w", err)
|
|
}
|
|
defer file.Close()
|
|
|
|
email, err := Parse(file)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse email: %w", err)
|
|
}
|
|
|
|
// Format addresses
|
|
formatAddress := func(addr []*mail.Address) []string {
|
|
var result []string
|
|
for _, a := range addr {
|
|
result = append(result, convertToUTF8(a.String()))
|
|
}
|
|
return result
|
|
}
|
|
|
|
// Determine body (prefer HTML)
|
|
body := email.HTMLBody
|
|
if body == "" {
|
|
body = email.TextBody
|
|
}
|
|
|
|
// Process attachments list and PEC detection
|
|
var attachments []EmailAttachment
|
|
var hasDatiCert, hasSmime, hasInnerEmail bool
|
|
|
|
// Process embedded files (inline images) -> add to body AND add as attachments
|
|
for _, ef := range email.EmbeddedFiles {
|
|
data, err := io.ReadAll(ef.Data)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
// Convert to base64
|
|
b64 := base64.StdEncoding.EncodeToString(data)
|
|
mimeType := ef.ContentType
|
|
if parts := strings.Split(mimeType, ";"); len(parts) > 0 {
|
|
mimeType = strings.TrimSpace(parts[0])
|
|
}
|
|
if mimeType == "" {
|
|
mimeType = "application/octet-stream"
|
|
}
|
|
|
|
// Create data URI
|
|
dataURI := fmt.Sprintf("data:%s;base64,%s", mimeType, b64)
|
|
|
|
// Replace cid:reference with data URI in HTML body
|
|
// ef.CID is already trimmed of <>
|
|
target := "cid:" + ef.CID
|
|
body = strings.ReplaceAll(body, target, dataURI)
|
|
|
|
// ALSO ADD AS ATTACHMENTS for the viewer
|
|
filename := ef.CID
|
|
if filename == "" {
|
|
filename = "embedded_image"
|
|
}
|
|
// If no extension, try to infer from mimetype
|
|
if !strings.Contains(filename, ".") {
|
|
ext := "dat"
|
|
switch mimeType {
|
|
case "image/jpeg":
|
|
ext = "jpg"
|
|
case "image/png":
|
|
ext = "png"
|
|
case "image/gif":
|
|
ext = "gif"
|
|
case "application/pdf":
|
|
ext = "pdf"
|
|
default:
|
|
if parts := strings.Split(mimeType, "/"); len(parts) > 1 {
|
|
ext = parts[1]
|
|
}
|
|
}
|
|
filename = fmt.Sprintf("%s.%s", filename, ext)
|
|
}
|
|
|
|
attachments = append(attachments, EmailAttachment{
|
|
Filename: filename,
|
|
ContentType: mimeType,
|
|
Data: data,
|
|
})
|
|
}
|
|
|
|
// Process standard attachments
|
|
for _, att := range email.Attachments {
|
|
data, err := io.ReadAll(att.Data)
|
|
if err != nil {
|
|
continue // Handle error or skip? Skipping for now.
|
|
}
|
|
|
|
// PEC Detection Logic
|
|
filenameLower := strings.ToLower(att.Filename)
|
|
if filenameLower == "daticert.xml" {
|
|
hasDatiCert = true
|
|
}
|
|
if filenameLower == "smime.p7s" {
|
|
hasSmime = true
|
|
}
|
|
if strings.HasSuffix(filenameLower, ".eml") {
|
|
hasInnerEmail = true
|
|
}
|
|
|
|
attachments = append(attachments, EmailAttachment{
|
|
Filename: att.Filename,
|
|
ContentType: att.ContentType,
|
|
Data: data,
|
|
})
|
|
}
|
|
|
|
isPec := hasDatiCert && hasSmime
|
|
|
|
// Format From
|
|
var from string
|
|
if len(email.From) > 0 {
|
|
from = email.From[0].String()
|
|
}
|
|
|
|
return &EmailData{
|
|
From: convertToUTF8(from),
|
|
To: formatAddress(email.To),
|
|
Cc: formatAddress(email.Cc),
|
|
Bcc: formatAddress(email.Bcc),
|
|
Subject: convertToUTF8(email.Subject),
|
|
Body: convertToUTF8(body),
|
|
Attachments: attachments,
|
|
IsPec: isPec,
|
|
HasInnerEmail: hasInnerEmail,
|
|
}, nil
|
|
}
|
|
|
|
func convertToUTF8(s string) string {
|
|
if utf8.ValidString(s) {
|
|
return s
|
|
}
|
|
|
|
// If invalid UTF-8, assume Windows-1252 (superset of ISO-8859-1)
|
|
decoder := charmap.Windows1252.NewDecoder()
|
|
decoded, _, err := transform.String(decoder, s)
|
|
if err != nil {
|
|
return s // Return as-is if decoding fails
|
|
}
|
|
return decoded
|
|
}
|
|
|
|
func ReadPecInnerEml(filePath string) (*EmailData, error) {
|
|
file, err := os.Open(filePath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to open file: %w", err)
|
|
}
|
|
defer file.Close()
|
|
|
|
// 1. Parse outer "Envelope"
|
|
outerEmail, err := Parse(file)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse outer email: %w", err)
|
|
}
|
|
|
|
// 2. Look for the real content inside postacert.eml
|
|
var innerEmailData []byte
|
|
foundPec := false
|
|
|
|
for _, att := range outerEmail.Attachments {
|
|
// Standard PEC puts the real message in postacert.eml
|
|
// Using case-insensitive check and substring as per example
|
|
if strings.Contains(strings.ToLower(att.Filename), "postacert.eml") {
|
|
data, err := io.ReadAll(att.Data)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read inner email content: %w", err)
|
|
}
|
|
innerEmailData = data
|
|
foundPec = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !foundPec {
|
|
return nil, fmt.Errorf("not a signed PEC or 'postacert.eml' attachment is missing")
|
|
}
|
|
|
|
// 3. Parse the inner EML content
|
|
innerEmail, err := Parse(bytes.NewReader(innerEmailData))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse inner email structure: %w", err)
|
|
}
|
|
|
|
// Helper to format addresses (reused logic pattern from eml_reader.go)
|
|
formatAddress := func(addr []*mail.Address) []string {
|
|
var result []string
|
|
for _, a := range addr {
|
|
// convertToUTF8 is defined in eml_reader.go (same package)
|
|
result = append(result, convertToUTF8(a.String()))
|
|
}
|
|
return result
|
|
}
|
|
|
|
// Determine body (prefer HTML)
|
|
body := innerEmail.HTMLBody
|
|
if body == "" {
|
|
body = innerEmail.TextBody
|
|
}
|
|
|
|
// Process attachments of the inner email
|
|
var attachments []EmailAttachment
|
|
var hasDatiCert, hasSmime, hasInnerPecEmail bool
|
|
|
|
for _, att := range innerEmail.Attachments {
|
|
data, err := io.ReadAll(att.Data)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
// Check internal flags for the inner email (recursive PEC check?)
|
|
filenameLower := strings.ToLower(att.Filename)
|
|
if filenameLower == "daticert.xml" {
|
|
hasDatiCert = true
|
|
}
|
|
if filenameLower == "smime.p7s" {
|
|
hasSmime = true
|
|
}
|
|
if strings.HasSuffix(filenameLower, ".eml") {
|
|
hasInnerPecEmail = true
|
|
}
|
|
|
|
attachments = append(attachments, EmailAttachment{
|
|
Filename: att.Filename,
|
|
ContentType: att.ContentType,
|
|
Data: data,
|
|
})
|
|
}
|
|
|
|
isPec := hasDatiCert && hasSmime
|
|
|
|
// Format From
|
|
var from string
|
|
if len(innerEmail.From) > 0 {
|
|
from = innerEmail.From[0].String()
|
|
}
|
|
|
|
return &EmailData{
|
|
From: convertToUTF8(from),
|
|
To: formatAddress(innerEmail.To),
|
|
Cc: formatAddress(innerEmail.Cc),
|
|
Bcc: formatAddress(innerEmail.Bcc),
|
|
Subject: convertToUTF8(innerEmail.Subject),
|
|
Body: convertToUTF8(body),
|
|
Attachments: attachments,
|
|
IsPec: isPec,
|
|
HasInnerEmail: hasInnerPecEmail,
|
|
}, nil
|
|
}
|