package internal import ( "bytes" "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 and detect PEC var attachments []EmailAttachment var hasDatiCert, hasSmime, hasInnerEmail bool 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 }