用PHP實現POP3郵件的解碼(三)

實現 MIME 解碼的類

該類實現解碼的方法是 decode($head=null,$body=null,$content_num=-1),爲了處理上的方便,要求輸入的是兩個字符數組,在我們的上篇中,所用到的POP類所收取得到的就是兩個這樣的數組,一個是郵件頭內容,一個是郵件的正文內容。限于篇幅,不對其做詳細的說明,其實現思想跟本文上篇中所介紹的POP類類似。請參考其中的注釋。

該類中用到了大量的正則表達式的操作,對此不熟悉的讀者,請參考正則表達式的有關資料。

class decode_mail

{

var $from_name;var $to_name;var $mail_time;var $from_mail;var $to_mail;

var $reply_to;var $cc_to;var $subject;

// 解碼後的郵件頭部分的信息:

var $body;

// 解碼後得到的正文數據,爲一個數組。

var $body_type;// 正文類型

var $tem_num=0;

var $get_content_num=0;

var $body_temp=array();

var $body_code_type;

var $boundary;

// 以上是一些方法中用到的一些全局性的臨時變量,由于PHP不能做到良好的封裝,所以只能放在這裏定義

var $err_str;// 錯誤信息

var $debug=0; // 調試標記

var $month_num=array("Jan"=>1,"Feb"=>2,"Mar"=>3,"Apr"=>4,"May"=>5,"Jun"=>6,"Jul"=>7,

"Aug"=>8,"Sep"=>9,"Oct"=>10,"Nov"=>11,"Dec"=>12); // 把英文月份轉換成數字表示的月份

function decode($head=null,$body=null,$content_num=-1) // 調用的主方法,$head 與 $body 是兩個數組,$content_num 表示的是當正文有多個部分的時候,只取出指定部分的內容以提高效率,默認爲-1 ,表示解碼全部內容,如果解碼成功,該 方法返回 true

{

if (!$head and !$body)

{

$this->err_str="沒有指定郵件的頭與內容!!";

return false;

}

if (gettype($head)=="array")

{

$have_decode=true;

$this->decode_head($head);

}

if (gettype($body)=="array")

{

$this->get_content_num=$content_num;

$this->body_temp=$body;

$have_decode=true;

$this->decode_body();

unset($this->body_temp);

}

if (!$have_decode)

{

$this->err_str="傳遞的參數不對,用法:new decode_mail(head,body) 兩個參數都是數組";

return false;

}

}

function decode_head($head) // 郵件頭內容 的解碼,取出郵件頭中有意義的內容

{

$i=0;

$this->from_name=$this->to_name=$this->mail_time=$this->from_mail=$this->

to_mail=$this->reply_to=$this->cc_to=$this->subject="";

$this->body_type=$Sthis->boundary=$this->body_code_type="";

while ($head[$i])

{

if (strpos($head[$i],"=?"))

$head[$i]=$this->decode_mime($head[$i]); //如果有編碼的內容,則進行解碼,解碼函數是上文所介紹的decode_mime()

$pos=strpos($head[$i],":");

$summ=substr($head[$i],0,$pos);

$content=substr($head[$i],$pos+1); //將郵件頭信息的標識與內容分開

if ($this->debug) echo $summ.":----:".$content."<BR>";

switch (strtoupper($summ))

{

case "FROM": // 發件人地址及姓名(可能沒有姓名,只有地址信息)

if ($left_tag_pos=strpos($content,"<"))

{

$mail_lenth=strrpos($content,">")-$left_tag_pos-1;

$this->from_name=substr($content,0,$left_tag_pos);

$this->from_mail=substr($content,$left_tag_pos+1,$mail_lenth);

if (trim($this->from_name)=="") $this->from_name=$this->from_mail;

else

if (ereg("[\"|\']([^\'\"]+)[\'|\"]",$this->from_name,$reg))

$this->from_name=$reg[1];

}

else

{

$this->from_name=$content;

$this->from_mail=$content;

//沒有發件人的郵件地址

}

break;

case "TO": //收件人地址及姓名(可能 沒有姓名)

if ($left_tag_pos=strpos($content,"<"))

{

$mail_lenth=strrpos($content,">")-$left_tag_pos-1;

$this->to_name=substr($content,0,$left_tag_pos);

$this->to_mail=substr($content,$left_tag_pos+1,$mail_lenth);

if (trim($this->to_name)=="") $this->to_name=$this->to_mail;

else

if (ereg("[\"|\']([^\'\"]+)[\'|\"]",$this->to_name,$reg))

$this->to_name=$reg[1];

}

else

{

$this->to_name=$content;

$this->to_mail=$content;

//沒有分開收件人的郵件地址

}

break;

case "DATE" : //發送日期,爲了處理方便,這裏返回的是一個 Unix 時間戳,可以用 date("Y-m-d",$this->mail_time)來得到一般格式的日期

$content=trim($content);

$day=strtok($content," ");

$day=substr($day,0,strlen($day)-1);

$date=strtok(" ");

$month=$this->month_num[strtok(" ")];

$year=strtok(" ");

$time=strtok(" ");

$time=split(":",$time);

$this->mail_time=mktime($time[0],$time[1],$time[2],$month,$date,$year);

break;

case "SUBJECT": //郵件主題

$this->subject=$content;

break;

case "REPLY_TO":// 回複地址(可能沒有)

if (ereg("<([^>]+)>",$content,$reg))

$this->reply_to=$reg[1];

else $this->reply_to=$content;

break;

case "CONTENT-TYPE": // 整個郵件的 Content類型, eregi("([^;]*);",$content,$reg);

$this->body_type=trim($reg[1]);

if (eregi("multipart",$content)) // 如果是multipart 類型,取得分隔符

{

while (!eregi('boundary=\"(.*)\"',$head[$i],$reg) and $head[$i])

$i++;

$this->boundary=$reg[1];

}

else //對于一般的正文類型,直接取得其編碼方法

{

while (!eregi("charset=[\"|\'](.*)[\'|\"]",$head[$i],$reg))

$i++;

$this->body_char_set=$reg[1];

while (!eregi("Content-Transfer-Encoding:(.*)",$head[$i],$reg))

$i++;

$this->body_code_type=trim($reg[1]);

}

break;

case "CC"://抄送到。。

if (ereg("<([^>]+)>",$content,$reg))

$this->cc_to=$reg[1];

else

$this->cc_to=$content;

default:

break;

} // end switch

$i++;

} // end while

if (trim($this->reply_to)=="") //如果沒有指定回複地址,則回複地址爲發送人地址

$this->reply_to=$this->from_mail;

}// end function define

function decode_body()//正文的解碼,其中用到了不少郵件頭解碼所得來的信息

{

$i=0;

if (!eregi("multipart",$this->body_type))//如果不是複合類型,可以直接解碼

{

$tem_body=implode($this->body_temp,"\r\n");

switch (strtolower($this->body_code_type))// body_code_type ,正文的編碼方式,由郵件頭信息中取得

{case "base64":

$tem_body=base64_decode($tem_body);

break;

case "quoted-printable":

$tem_body=quoted_printable_decode($tem_body);

break;

}

$this->tem_num=0;

$this->body=array();

$this->body[$this->tem_num][content_id]="";

$this->body[$this->tem_num][type]=$this->body_type;

switch (strtolower($this->body_type))

{

case "text/html":

$this->body[$this->tem_num][name]="超文本正文";

break;

case "text/plain":

$this->body[$this->tem_num][name]="文本正文";

break;

default:

$this->body[$this->tem_num][name]="未知正文";

}

$this->body[$this->tem_num][size]=strlen($tem_body);

$this->body[$this->tem_num][content]=$tem_body;

unset($tem_body);

}

else //如果是複合類型的

{

$this->body=array();

$this->tem_num=0;

$this->decode_mult($this->body_type,$this->boundary,0);//調用複合類型的解碼方法

}

}

function decode_mult($type,$boundary,$begin_row)// 該方法用遞歸的方法實現複合類型郵件正文的解碼,郵件源文件取自于 body_temp 數組,調用時給出該複合類型的類型、分隔符及在body_temp 數組中的開始指針

{

$i=$begin_row;

$lines=count($this->body_temp);

while ($i<$lines) // 這是一個部分的結束標識;

{

while (!eregi($boundary,$this->body_temp[$i]))//找到一個開始標識

$i++;

if (eregi($boundary."--",$this->body_temp[$i]))

{

return $i;

}

while (!eregi("Content-Type:([^;]*);",$this->body_temp[$i],$reg ) and $this->body_temp[$i])

$i++;

$sub_type=trim($reg[1]); // 取得這一個部分的 類型是milt or text ....

if (eregi("multipart",$sub_type))// 該子部分又是有多個部分的;

{

while (!eregi('boundary=\"([^\"]*)\"',$this->body_temp[$i],$reg) and $this->body_temp[$i])

$i++;

$sub_boundary=$reg[1];// 子部分的分隔符;

$i++;

$last_row=$this->decode_mult($sub_type,$sub_boundary,$i);

$i=$last_row;

}

else

{

$comm="";

while (trim($this->body_temp[$i])!="")

{

if (strpos($this->body_temp[$i],"=?"))

$this->body_temp[$i]=$this->decode_mime($this->body_temp[$i]);

if (eregi("Content-Transfer-Encoding:(.*)",$this->body_temp[$i],$reg))

$code_type=strtolower(trim($reg[1])); // 編碼方式

$comm.=$this->body_temp[$i]."\r\n";

$i++;

} // comm 是編碼的說明部分

if (eregi('name=[\"]([^\"]*)[\"]',$comm,$reg))

$name=$reg[1];

if (eregi("Content-Disposition:(.*);",$comm,$reg))

$disp=$reg[1];

if (eregi("charset=[\"|\'](.*)[\'|\"]",$comm,$reg))

$char_set=$reg[1];

if (eregi("Content-ID:[ ]*\<(.*)\>",$comm,$reg)) // 圖片的標識符。

$content_id=$reg[1];

$this->body[$this->tem_num][type]=$sub_type;

$this->body[$this->tem_num][content_id]=$content_id;

$this->body[$this->tem_num][char_set]=$char_set;

if ($name)

$this->body[$this->tem_num][name]=$name;

else

switch (strtolower($sub_type))

{

case "text/html":

$this->body[$this->tem_num][name]="超文本正文";

break;

case "text/plain":

$this->body[$this->tem_num][name]="文本正文";

break;

default:

$this->body[$this->tem_num][name]="未知正文";

}

// 下一行開始取回正文

if ($this->get_content_num==-1 or $this->get_content_num==$this->tem_num) // 判斷這個部分是否是需要的。-1 表示全部

{

$content="";

while (!ereg($boundary,$this->body_temp[$i]))

{

//$content[]=$this->body_temp[$i];

$content.=$this->body_temp[$i]."\r\n";

$i++;

}

//$content=implode("\r\n",$content);

switch ($code_type)

{

case "base64":

$content=base64_decode($content);

break;

case "quoted-printable":

$content=str_replace("\n","\r\n",quoted_printable_decode($content));

break;

}

$this->body[$this->tem_num][size]=strlen($content);

$this->body[$this->tem_num][content]=$content;

}

else

{

while (!ereg($boundary,$this->body_temp[$i]))

$i++;

}

$this->tem_num++;

}

// end else

} // end while;

} // end function define

function decode_mime($string) {

//decode_mime 已在上文中給出,這裏略過。

}

} // end class define

在這裏要特別說明一點的是html正文裏所用圖片的解碼。發送html格式的正文時,都會碰到圖片如何傳送的問題。圖片在 html 文檔裏是一個<img src="" >的標簽,關鍵是這個源文件從何來的。很多郵件的處理方法是用一個絕對的 url 標識,就是在郵件的html正文裏用<img src= http://www.ccidnet.com/image/22.gif >之類的標簽,這樣,在閱讀郵件時,郵件閱讀器(通常是用內嵌的浏覽器)會自動從網上下載圖片,但是如果郵件收下來之後,與 Internet 的連接斷了,圖片也就不能正常顯示。

所以更好的方法是把圖片放在郵件中一起發送出去。在 MIME 編碼裏,描述圖片與正文的關系,除了上面所提到的multipart/relatedMIME頭信息之外,還用到了一個 Content-ID: 的屬性來使圖片與 html 正文之間建立關系。html 文檔中的圖片在編碼時,其MIME頭中加入一個 Content-ID:122223443556dsdf@ntsever 之類的屬性,122223443556dsdf@ntsever是一個唯一的標識,在 html 文檔裏,<img>標簽被修改成<img src="cid: 122223443556dsdf@ntsever">,在解碼的時候,實際上,還需要把 html 正文中的這些<img src>標簽進行修改,使之指向解碼後的圖片的具體路徑。但是考慮到具體的解碼程序中對圖片會有不同的處理,所以在這個解碼的類中,沒有對 hmtl 正文中的<img>標簽進行修改。所以在實際使用這個類時,對于有圖片的 html 正文,還需要一定的處理。正文中的圖片,可以用臨時文件來保存,也可以用數據庫來保存。

現在我們已經介紹了POP3 收取郵件並進行 MIME 解碼的原理。下面給出一個使用這兩個類的一段小程序:

<?

include("pop3.inc.php");

include("mime.inc.php");

$host="pop.china.com";

$user="boss_ch";

$pass="mypassword";

$rec=new pop3($host,110,2);

$decoder=new decode_mail();

if (!$rec->open()) die($rec->err_str);

if (!$rec->login($user,$pass)) die($rec->err_str);

if (!$rec->stat()) die($rec->err_str);

echo "共有".$rec->messages."封信件,共".$rec->size."字節大小<br>";

if ($rec->messages>0)

{

if (!$rec->listmail()) die($rec->err_str);

echo "以下是信件內容:<br>";

for ($i=1;$i<=count($rec->mail_list);$i++)

{

echo "信件".$rec->mail_list[$i][num].",大小:".$rec->mail_list[$i][size]."<BR>";

$rec->getmail($rec->mail_list[$i][num]);

$decoder->decode($rec->head,$rec->body);

echo "<h3>郵件頭的內容:</h3><br>";

echo $decoder->from_name."(".$decoder->from_mail.") 于".date("Y-m-d H:i:s",$decoder->mail_time)." 發給".$decoder->to_name."(".$decoder->to_mail.")";

echo "\n<br>抄送:";

if ($decoder->cc_to) echo $decoder->cc_to;else echo "無";

echo "\n<br>主題:".$decoder->subject;

echo "\n<br>回複到:".$decoder->reply_to;

echo "<h3>郵件正文:</h3><BR>";

echo "正文類型:".$decoder->body_type;

echo "<br>正文各內容:";

for ($j=0;$j<count($decoder->body);$j++)

{

echo "\n<br>類型:".$decoder->body[$j][type];

echo "\n<br>名稱:".$decoder->body[$j][name];

echo "\n<br>大小:".$decoder->body[$j][size];

echo "\n<br>content_id:".$decoder->body[$j][content_id];

echo "\n<br>正文字符集".$decoder->body[$j][char_set];

echo "<pre>";

echo "正文內容:".$decoder->body[$j][content];

echo "</pre>";

}

$rec->dele($i);

}

}

$rec->close();

?>

如有想要取得完整源代碼的朋友,請與本人聯系: boss_ch@netease.com

<全文完>

用PHP實現POP3郵件的收取
現在讓我們來用PHP實現一個通過POP3協議收取信件的類吧,這個類中所用到的一些sock操作的函數,不另做特殊說明,請參考php的有關資料。通過這個實例,相信你也會和我一樣,感覺到PHP中對于sock操作的靈活、方便和功能...查看完整版>>用PHP實現POP3郵件的收取
 
C#中對POP3郵件解碼
Base64和下面將要介紹的Quoted-Printable都屬于MIME(多部分( multi-part)、多媒體電子郵件和 WWW 超文本的一種編碼標准,用于傳送諸如圖形、聲音和傳真等非文本數據)。MIME定義在RFC1341中。 Base64是現今在互聯...查看完整版>>C#中對POP3郵件解碼
 
C#中對POP3郵件解碼
Base64和下面將要介紹的Quoted-Printable都屬于MIME(多部分( multi-part)、多媒體電子郵件和 WWW 超文本的一種編碼標准,用于傳送諸如圖形、聲音和傳真等非文本數據)。MIME定義在RFC1341中。 Base64是現今在互聯...查看完整版>>C#中對POP3郵件解碼
 
用PHP實現POP3郵件的解碼(三)
實現 MIME 解碼的類  該類實現解碼的方法是 decode($head=null,$body=null,$content_num=-1),爲了處理上的方便,要求輸入的是兩個字符數組,在我們的上篇中,所用到的POP類所收取得到的就是兩個這樣的數組,一個是...查看完整版>>用PHP實現POP3郵件的解碼(三)
 
用PHP實現POP3郵件的解碼(二)
MIME 編碼方式簡介  Subject: =?gb2312?B?xOO6w6Oh?=  這裏是郵件的主題,可是因爲編碼了,我們看不出是什麽內容,其原來的文本是:“你好!”我們先看看 MIME 編碼的兩種方法。  對郵件進行編碼最初的原因是因...查看完整版>>用PHP實現POP3郵件的解碼(二)
 
用PHP實現POP3郵件的解碼(一)
初步認識郵件的源文件  本文簡要說明了通過POP3協議收取郵件、MIME郵件的解碼的原理;針對收取和MIME解碼,提供了兩個實用的PHP類,並提供了使用的樣例。分爲郵件收取、MIME解碼兩個部分。我們已經向您介紹過了郵件...查看完整版>>用PHP實現POP3郵件的解碼(一)
 
自制Pop3郵件接收系統(一):利用MatchEvaluator實現HZ的解碼
要作自制的Pop3郵件接收系統,就要自己處理郵件的編碼大家可能會收到以下的“亂碼”,~{4s<R:C#,NRJG~}qiushuiwuhen~{#,;6S-@45=~}csdn.實際上是簡體中文的HZ編碼,在郵件中經常會出現,因爲郵件協議體系通常是七...查看完整版>>自制Pop3郵件接收系統(一):利用MatchEvaluator實現HZ的解碼
 
自制Pop3郵件接收系統(一):利用MatchEvaluator實現HZ的解碼
要作自制的Pop3郵件接收系統,就要自己處理郵件的編碼大家可能會收到以下的“亂碼”,~{4s<R:C#,NRJG~}qiushuiwuhen~{#,;6S-@45=~}csdn.實際上是簡體中文的HZ編碼,在郵件中經常會出現,因爲郵件協議體系通常是七...查看完整版>>自制Pop3郵件接收系統(一):利用MatchEvaluator實現HZ的解碼
 
用PHP實現POP3郵件的收取(一)
POP協議簡介  本文簡要說明了通過POP3協議收取郵件、MIME郵件的解碼的原理;針對收取和MIME解碼,提供了兩個實用的PHP類,並提供了使用的樣例。分爲郵件收取、MIME解碼兩個部分。這裏我們先向您介紹郵件的收取,解...查看完整版>>用PHP實現POP3郵件的收取(一)
 
 
回到王朝網路首頁