2007年04月3日

PHP mail() and charset encoding question

php mail phpmailer

不知從何時開始, PHP 內建的 mail() 行為改變了,使用 mail() 寄發電子郵件時,似乎會固定將內文之字元編碼轉為 iso-8859-1 字元集。於是用 mail() 寄中文郵件時變亂碼、寄東歐文字郵件時變亂碼、寄日文郵件時變亂碼等等問題一一出現。如何寄非英文語系郵件幾乎成了 FAQ 級問題。


早期的 mail() 並沒有這種狀況。基本上只要在信件的標頭欄位中添加文件型態及字元集(charset)資訊,如 'Content-type: text/plain; charset="big5"' 即可用以寄發中文郵件。但不知何時開始,這種方式不再適用。在 PHP manual 中也未提到這方面的變化,僅僅說可用 PEAR::Mail 完成更複雜郵件寄送工作。另外隱晦地加上 mb_send_mail() ,表示應該使用此一函數寄發多位元組字元集文字郵件。

為了測試前述問題所在,以及完成寄送中文郵件的工作,我設計了一個替用的 mail 函數。對於了解 SMTP 協定內容的程序員而言,要自行設計一個 mail() 並不困難。下列便是我設計的 mail() 替用函數。它是一個對照組,用以測試並確認郵件內容變亂碼是 PHP 內建的 mail() 所造成的。由於設計目的之中包含了測試用途,所以程式碼中還留有觀察訊息的敘述。

MyMailer.php

MyMailer 以靜態方法 MyMailer::mail() 實現郵件寄送行為,其用法與 PHP 內建的 mail() 完全相同。它將讀取 php.ini 中的 SMTPsendmail_from 之設定值內容決定 SMTP 伺服器位址以及寄信人資訊,接著開啟 socket 連線直接與 SMTP 伺服器交談以完成郵件遞送工作。

以下為一測試範例,將同一訊息分別以 PHP的 mail() 和我設計的 MyMailer::mail() 寄送,藉此觀察能否寄送中文郵件。從測試結果來看,我們可以發現 PHP 的 mail() 所寄送之內容會成亂碼 (但也有人不會碰到這種情形)。

由於 PHP 內建的 mail() 功能頗為簡單,用於寄送簡單的 log 訊息尚可,寄送一般信件就略顯力有未逮。因此在開發應用軟體時,建議使用 PEAR::Mail 或是 PHPMailer。不僅可解決寄送中文郵件的困擾,尚可寄送夾帶附件的郵件,應多加利用。雖然兩者功能類似,但我建議依授權方式選擇。 PEAR::Mail 採 PHP/BSD license 授權發佈,而 PHPMailer 採 GPL/LGPL 授權發佈。故偏好 GPL/LGPL 授權的程序員應選擇採用 PHPMailer 。啥?用本文中的 MyMailer ? 那也成,順便提醒一下, MyMailer 也是採 GPL/LGPL 授權發佈。

內文沒問題,但標題和寄信人內容是亂碼

標題和寄信人的內容是亂碼之原因在於未指定字元編碼。此處常引起誤解,程序員會說「我已經在Content-type中指定 charset 了啊」。內文的字元編碼可透過 Content-type 指定字元集(charset) ,但標題和寄信人的內容並非內文之一部份,故其字元編碼並非由 Content-type 所指定。事實上,標題和寄信人屬於郵件 Header ,其規範內容為 RFC 2047 - MIME (Multipurpose Internet Mail Extensions) Part Three: Message Header Extensions for Non-ASCII Text。根據該規範內容, Header 中的非 ASCII 字元之內容應以 「=?charset encoding?encoding code?header content?=」之格式指定。起於=? ,止於 ?=encoding code 一般為 "q": Quoted-printable ;或 "b": Base64。

由於 PHP 僅提供 base64_encode() ,故下列範例之 encoding code 一律為 b


Posted by shirock at 樂多Roodo! │15:50 │回應(8)引用(1)PHP
樂多分類:網路/3C 共同主題:PHP基本語法 工具:編輯本文
Ads by Roodo! 

引用URL

http://cgi.blog.roodo.com/trackback/2950655
引用列表:
繼續探索 mail() 對信件內容編碼之原因。原因為 mail() 已被 mb_send_mail() 覆寫。
PHP mail() and charset encoding question, part2 - mbstring【石頭閒語】 at 2007年04月13日 09:49
回應文章
我使用了你的方式但是出現
Parse error: syntax error, unexpected T_STATIC, expecting T_OLD_FUNCTION or T_FUNCTION or T_VAR or '}' in /home/public_html/mail_test/MyMailer.php on line 8

因為PHPMailer之前我用的都沒問題,最近卻變成亂碼,我實在找不到解決方式,它的主旨跟標題是正常的 內容確是亂碼
不知大大有辦法幫忙解決否?
Posted by 紫誘惑 at 2007年04月8日 05:15
PHP 版本不同,我忘了在哪個版本以前, static 關鍵字必須放在可見度宣告關鍵字的後面。所以你把 static 關鍵字放在 public/private 關鍵字之後即可。

PHPMailer 內定使用 PHP 內建的 mail() 。然而就像本文提到 PHP 內建的 mail() 改變了行為內容,所以信件內容的編碼不適合中文信件。

故你應該要像本文採用的方式一般,要求 PHPMailer 使用 SMTP 方式寄送信件,例如加上 $mailer->IsSMTP();$mailer->Mailer = "smtp";
在 PHPMailer 的 FAQ 中也是建議用 SMTP 方式 (See also: What mailer gives me the best performance?
Posted by 遊手好閒的石頭成 at 2007年04月8日 17:17
我還是用mail()來寄,我用UTF-8編碼網頁
內容部分ok,只是title會變成亂碼,只要加一句就正常了
$title = "=?UTF-8?B?".base64_encode($title)."?=";

這樣寄出,主旨就不會亂碼了
Posted by 小賴 at 2007年04月12日 16:41
在主旨和標題都還是big5碼沒有任何錯誤,但是內文就無法正常顯示,我後來發現我應該不是使用SMTP,因為我那個在PHP.INI內並沒有設定任何資料,而是由主機商那邊預設值,可是經過PHPMailer 送信後,信件還是亂碼,我已經找了一星期資料,徹底放棄了,雖然我還是很想解決,網路上找的相關亂碼問題好像都是針對某些論壇外掛,幾忽找不到非論壇發信的討論mail(),有也是一些舊資料已經不適用了,大大不知可否說明的詳細些
我試了好多方式都失敗,最後,終於無奈放棄了

我想問題是出在
$mail->Body = "$d";

不知道版大可以幫我看看問題到底在那裡嗎?
Posted by 紫誘惑 at 2007年04月12日 18:57
我在上面的回應已經說了,你應改用 SMTP 方式寄信。
你的程式並未要求 PHPMailer 採用 SMTP 方式
Posted by 遊手好閒的石頭成 at 2007年04月12日 21:32
痾!!感謝大大!!我終於懂了!!

謝謝!!
補充一點,我是放在虛擬主機上面,而之前一直出錯原因是SMTP沒架設成功,一直把 HOST這個東西設成自己網域

原來還有分 win32跟LINUX不同 >"<
搞好久!!真是感謝大大回答!!真好!!
Posted by 紫誘惑 at 2007年04月13日 01:51
我也是使用虛擬主機,也遇到亂碼的問題,剛剛看到一篇文章,只用了簡單兩行(用mb_send_mail代替mail),就解決了我的問題,提供大家參考。

mb_internal_encoding("UTF-8");
$headers = 'From: nectar020@yahoo.com.cn\r\nReply-To: me@budian.cn\r\nX-Mailer: PHP/' . phpversion();
mb_send_mail("$To", "$Subject", "$Content", $headers);


取自:http://www.wretch.cc/blog/Linpy&article_id=10919198
Posted by Derek at 2008年01月16日 15:41
to Derek:
關於使用 mb_send_mail() 可解決信件亂碼的原因,請參考本文 part2

mail() 會寄出亂碼信件的原因,其實也是 mb_send_mail()。成也蕭何,敗也蕭何。
Posted by 遊手好閒的石頭成 at 2008年01月16日 17:39