2025 年第二天,來個低級錯誤連發。

寫了如下程式發送圖文信。(參考:使用 C# 寄送圖文並茂郵件)

public static void TestEmbImgMail() 
{
    var mailBody = """
    <div>
        <div>
            JPG: <img src="cid:sample.jpg" />
        </div>
        <div>
            PNG: <img src="cid:sample.png" />
        </div>
    </div>
    """;
    using var message = new MailMessage
    {
        Body = mailBody,
        IsBodyHtml = true
    };
    using var avHtmlView = AlternateView.CreateAlternateViewFromString(mailBody, null, "text/html");
    avHtmlView.LinkedResources.Add(new LinkedResource(
        new MemoryStream(ReadImgBytes("sample.jpg")), "image/jpg") { ContentId = "sample.jpg" });
    avHtmlView.LinkedResources.Add(new LinkedResource(
        new MemoryStream(ReadImgBytes("sample.png")), "image/png") { ContentId = "sample.png" });
    message.AlternateViews.Add(avHtmlView);
    SendOutlookMail("admin@example.com", "JPG/PNG Embedded Image Test", message);
}

Outlook 收信測試看起來沒啥問題:

接獲反映,雖然郵件可以順利檢視,但在某幾台 Outlook 會出現名為 ATT00001 的不明附件。且似乎與 Outlook 版本有關,只會某些人會遇到。

我找到一個方法重現類似結果,是啟用「信任中心設定/電子郵件安全性/以純文字檔讀取所有標準郵件」選項,

Outlook 純文模式下,會出現兩個附件檔:「未命名的附件 00104.dat」/「未命名的附件 00107.png」,而前者約略為 sample.jpg 的大小,意思是 sample.jpg 沒被正確解析。

好奇瞄了 sample.jpg 看有什麼異常,看到 JFIF 跟 LEADTOOLS v22.0 等陌生字樣,而我腦海中的 JPEG 規格還停在 JPEG 2000...

這個時代,遇到常識類問題,LLM 是最好的老師。

把以上擷圖胋到 ChatGPT、Gemini、Claude,提問「如何解析這個 .jpg 檔的檔頭,從中可得到哪些資訊?」,三者都能給出答案。其中我最喜歡 Gemini 的回答:

除了 JFIF OCR 誤轉成 JEIF,Gemini 針對內容逐段解析,詳細說明,完整解答了我的疑惑。我得到以下結論:

  1. 由 FF D8 可確認這是一個 JPEG 檔案無誤
  2. FF E0 後方為 APP0 (Application Seciont 0,應用程式區段 0),JFIF 字樣代表案使用 JFIF 規格
  3. FF FE 後方為 COM (Comment 註解區段),註記檔案可能由 LEADTOOLS v22.0 處理過

進一步查了 JFIF 跟 LEADTOOLS,再搞懂兩個新名詞。

  • JFIF (JPEG File Interchange Format,JPEG 檔案交換格式),是一種圖像檔案格式標準。早期的 JPEG 標準 (JIF) 只定義了圖像的壓縮方式,但沒有規範檔案的具體結構,這導致不同軟體或硬體產生的 JPEG 檔案可能無法互相相容。JFIF 在 JPEG 基礎上添加了一些必要的資訊,使得 JPEG 檔案能夠在不同的系統上正確顯示。
  • JFIF 和 JPEG 常被混用,是因為二者關係非常密切。我們可以把 JFIF 看作是 JPEG 的一種具體實現方式,或是「標準化的 JPEG」。實際上,目前網路上大部分看到的 JPEG 檔案,都是採用 JFIF 標準。
  • 而 LEADTOOLS 是常見的的影像處理函式庫,提供影像處理、壓縮、轉換、OCR 等功能,JPEG 檔案的 COM (Comment) 區段的 "LEADTOOLS v22.0" 字串,應是使用 LEADTOOLS 程式庫留下了,我的圖檔使用 Snagit 處理,而其官網也提到它有用到 LeadTools (17 October 2023: Snagit 2024.0.1 / Updates for IT Administrators)

所以,這是一個正常的 JPG 檔,回頭再看程式,這才發現錯誤所在,sample.jpg 的 MIME Type 我寫成了 "image/jpg",雖然 .jpg、.jpeg 副檔名可以互動,但 JPEG 的 MIME Type 是 image/jpeg,而 image/jpg 從來不是個有效值。修正後,彈出 ATT00001 的問題就排除了。

而上面的這段程式,其實有更好的寫法:

    using var avHtmlView = AlternateView.CreateAlternateViewFromString(mailBody, null, MediaTypeNames.Text.Html);
    avHtmlView.LinkedResources.Add(new LinkedResource(
        new MemoryStream(ReadImgBytes("sample.jpg")), MediaTypeNames.Image.Jpeg) { ContentId = "sample.jpg" });
    avHtmlView.LinkedResources.Add(new LinkedResource(
        new MemoryStream(ReadImgBytes("sample.png")), MediaTypeNames.Image.Png) { ContentId = "sample.png" });        

System.Net.Mime.MediaTypeNames.Image 有定義好標準字串,既能免除記錯拼錯的風險,又能享受強型別的關聯性,不要自己敲。(筆記)

The blog post discusses an issue with sending HTML emails containing embedded images using C#. An error occurred due to using an incorrect MIME type for a JPEG image. The correct MIME type is image/jpeg, not image/jpg. The post also highlights the benefits of using predefined constants in System.Net.Mime.MediaTypeNames to avoid such errors.


Comments

# by 名無し

會不會搞不好你直接拿程式問他就有答案了 XD

# by Python路過吾好錯過

import smtplib from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from email.mime.image import MIMEImage from email.utils import COMMASPACE def read_img_bytes(filename): """ 讀取指定檔案名的影像檔並返回其位元組資料。 參數: filename (str): 影像檔的路徑 返回: bytes: 影像檔的位元組資料 """ with open(filename, 'rb') as f: return f.read() def send_email_with_embedded_images(smtp_server, smtp_port, sender, password, recipient, subject, html_body, image_files): """ 構建並發送一封包含嵌入圖片的HTML格式郵件。 參數: smtp_server (str): SMTP伺服器地址 smtp_port (int): SMTP伺服器埠 sender (str): 寄件者的電子郵寄地址 password (str): 寄件者的電子郵件密碼 recipient (str): 收件人的電子郵寄地址 subject (str): 郵件主題 html_body (str): HTML格式的郵件正文 image_files (list): 包含要嵌入的影像檔路徑的清單 """ # 創建一個多部分郵件物件,類型為'related',以便處理內聯資源(如嵌入圖片) msg = MIMEMultipart('related') msg['From'] = sender msg['To'] = recipient msg['Subject'] = subject # 將HTML格式的郵件正文附加到郵件中 msg.attach(MIMEText(html_body, 'html')) # 遍歷每個影像檔並將其作為單獨的部分附加到郵件中 for filename in image_files: img_data = read_img_bytes(filename) # 讀取影像檔的位元組資料 mime_image = MIMEImage(img_data) # 創建MIMEImage對象 mime_image.add_header('Content-ID', f'<{filename}>') # 設置Content-ID頭以匹配HTML中的src屬性 msg.attach(mime_image) # 將MIMEImage物件附加到郵件中 # 使用SMTP協定連接到郵件伺服器,登錄並發送郵件 with smtplib.SMTP(smtp_server, smtp_port) as server: server.starttls() # 啟用TLS加密 server.login(sender, password) # 登錄到SMTP伺服器 server.sendmail(sender, recipient, msg.as_string()) # 發送郵件 if __name__ == "__main__": # 替換以下變數為您自己的SMTP伺服器資訊、郵箱位址和密碼等敏感資訊 smtp_server = 'smtp.example.com' # SMTP伺服器地址 smtp_port = 587 # SMTP伺服器埠 sender = 'your_email@example.com' # 寄件者的電子郵寄地址 password = 'your_password' # 寄件者的電子郵件密碼 recipient = 'recipient_email@example.com' # 收件人的電子郵寄地址 subject = 'Test Email with Embedded Images' # 定義HTML格式的郵件正文,其中包含兩個<img>標籤引用嵌入的圖片 html_body = """ <html> <body> <p>This is a test email with embedded images.</p> <img src="cid:image1.jpg"><br/> <img src="cid:image2.png"> </body> </html> """ # 包含要嵌入的影像檔路徑的清單 image_files = ['image1.jpg', 'image2.png'] # 調用函數發送郵件 send_email_with_embedded_images(smtp_server, smtp_port, sender, password, recipient, subject, html_body, image_files)

Post a comment