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
<?php
/*
MyMailer class
Copyright (C) 2007 Shih Yuncheng <shirock@educities.edu.tw>
This library is licensed under GNU Lesser General Public License.
*/
class MyMailer {
private static function waitSmtpResponse ( & $sh , $code ) {
echo ( $response = fgets ( $sh ));
if ( strncmp ( $response , $code , 3 )) {
echo $response ;
return self :: smtpFalse ( $sh );
}
return true ;
}
private static function smtpFalse ( & $sh ) {
fclose ( $sh );
return false ;
}
public static function mail ( $To , $Subject , $message , $additional_headers = false ) {
$headerTable = array ( 'To' , 'Subject' );
$smtpServer = get_cfg_var ( 'SMTP' );
if ( empty ( $smtpServer ))
return false ;
if ( ! ( $mailFrom = get_cfg_var ( 'sendmail_from' ))) {
$headers = explode ( " \n " , $additional_headers );
foreach ( $headers as $header ) {
if ( preg_match ( '/^From: ([^\b]+)/' , $header , $matches ))
$mailFrom = $matches [ 1 ];
}
if ( empty ( $mailFrom ))
return self :: smtpFalse ( $fsock );
}
if ( ! ( $fsock = fsockopen ( 'tcp://' . $smtpServer , get_cfg_var ( 'smtp_port' ),
$errno , $errstr )))
{
return false ;
}
if ( ! self :: waitSmtpResponse ( $fsock , '220' ))
return false ;
fwrite ( $fsock , "Helo { $smtpServer } \r\n " );
if ( ! self :: waitSmtpResponse ( $fsock , '250' ))
return false ;
fwrite ( $fsock , "Mail from: { $mailFrom } \r\n " );
if ( ! self :: waitSmtpResponse ( $fsock , '250' ))
return false ;
$rcptSet = explode ( ',' , $To );
foreach ( $rcptSet as $rcpt ) {
fwrite ( $fsock , "Rcpt to: { $rcpt } \r\n " );
if ( ! self :: waitSmtpResponse ( $fsock , '250' ))
return false ;
}
fwrite ( $fsock , "Data \r\n " );
if ( ! self :: waitSmtpResponse ( $fsock , '354' )) //354 Enter mail
return false ;
foreach ( $headerTable as $cmd )
fwrite ( $fsock , "${cmd}: { $$cmd } \r\n " );
if ( $additional_headers )
fwrite ( $fsock , $additional_headers );
fwrite ( $fsock , " \r\n " );
fwrite ( $fsock , $message );
fwrite ( $fsock , " \r\n . \r\n " );
fwrite ( $fsock , "Quit \r\n " );
fclose ( $fsock );
}
}
?>
MyMailer 以靜態方法 MyMailer::mail()
實現郵件寄送行為,其用法與 PHP 內建的 mail()
完全相同。它將讀取 php.ini
中的 SMTP 與 sendmail_from 之設定值內容決定 SMTP 伺服器位址以及寄信人資訊,接著開啟 socket 連線直接與 SMTP 伺服器交談以完成郵件遞送工作。
以下為一測試範例,將同一訊息分別以 PHP的 mail()
和我設計的 MyMailer::mail()
寄送,藉此觀察能否寄送中文郵件。從測試結果來看,我們可以發現 PHP 的 mail()
所寄送之內容會成亂碼 (但也有人不會碰到這種情形)。
<?php
require 'MyMailer.php' ;
$mailTo = 'your@example.com' ; // 請自行替換有效的收件地址
$message = 'test 測試' ;
$subject = 'TEST 測試' ;
$headers = 'Content-type: text/plain; charset="big5"' . " \r\n " ;
$headers . = 'From: my@example.com' . " \r\n " ; // 請自行替換寄件地址
mail ( $mailTo , $subject , $message , $headers );
MyMailer :: mail ( $mailTo , $subject , $message , $headers );
?>
由於 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
。
<?php
//資料皆為utf-8編碼
$messageHeaderMap = array (
'Subject' => '中文標題測試' ,
'From' => '許功蓋 <you@example.com>'
);
$charset = 'utf-8' ;
$messageEncoding = 'base64' ;
$headerFormat = '=?' . $charset . '?' . $messageEncoding [ 0 ] . '?%s?=' ;
$headers = '' ;
foreach ( $messageHeaderMap as $k => $v ) {
$headers . = $k . ': ' . sprintf ( $headerFormat , base64_encode ( $v )) . " \r\n " ;
}
$headers . = 'Content-type: text/plain; charset=' . $charset . " \r\n " ;
$message = '中文內容測試' ;
MyMailer :: mail (
'my@example.com' ,
sprintf ( $headerFormat , base64_encode ( $messageHeaderMap [ 'Subject' ])),
$message ,
$headers
);
?>
相關文章
樂多舊網址: http://blog.roodo.com/rocksaying/archives/2950655.html
樂多舊回應