八月 06

因為 php 上面提供的 mailparse 模組實在是爛到一個不行,看不下去只有自己寫,這就是網路。有興趣的人請自行取用~~註明出處即可。

/* 作者:丁昶文
    電郵:charlie@ics.com.tw
*/

class        mail_parser    {
    function    mail_parser()    {
        $this->tree        =    array() ;
        $this->header    =    array() ;
        $this->body        =    array() ;
        $this->counter    =    0 ;
    }
    function    explode($message,$pid=0) {
        if    ($pid == 0)    {
            $segment        =    preg_split("/\r*\n\r*\n/",$message,2) ;
            $this->header[0]=    iconv_mime_decode_headers(trim($segment[0]),2,SCRIPT_CHARSET) ;
            $this->body[0]    =    trim($segment[1]) ;
        }
        if    (preg_match("/boundary=\"([^\"]+)\"/i",$message,$m))    {
            $boundary    =    $m[1] ;
            $tail_off    =    array_shift(explode("--{$boundary}--",$message)) ;
            $parts        =    explode("--{$boundary}",$tail_off) ;
            array_shift($parts) ;
            if    (!isset($this->tree[$pid]))    $this->tree[$pid]    =    array() ;

            foreach    ($parts as $this_one => $part)    {
                $segment                            =    preg_split("/\r*\n\r*\n/",$part,2) ;
                $this->counter                        +=    1 ;
                $oid                                =    count($this->tree[$pid]) + 1 ;
                $this->tree[$pid][$oid]                =    $this->counter ;
                $this->tree["<-{$this->counter}"]    =    $pid ;
                $this->header[$this->counter]        =    iconv_mime_decode_headers(trim($segment[0]),2,SYSTEM_CHARSET) ;
                $this->body[$this->counter]            =    trim($segment[1]) ;

                $this->explode($part,$this->counter) ;
            }
        }
    }
}
 

用法就是:
$mp = new mail_parser() ;
$mp->explode(MAIL_CONTENT_FROM_EML_FILE) ;
然後它就會幫你把整封信炸開,之後,你可以用二種方式去捉資料。

  1. for ($i = 0 ; $i <= $mp->counter ; $i++) {
        $header = $mp->header[$i] ;
        $body = $mp->body[$i] ;
    }
  2. 也可以用樹狀的方式去捉出信包中各區塊的關係,重點就是從 $mp->tree[0] 開始,然後逐層去撈。找到 index 後,一樣從 $mp->header 和 $mp->body 去捉檔頭和內容。

這個類別基本上只作信包拆解的部份,而且是不管什麼死人骨頭全部拆光光。純粹是為了處理 mailparse 在 body 解析上的無厘頭表現而已。至於打包部份,目前我沒需求,就也沒作囉。不過就是反其道而行就對了。
email 的關鍵就是 boundary 和 mime type 處理好,然後拆拆裝裝而已。

附註:

  1. 類別中用了 SCRIPT_CHARSET 和 SYSTEM_CHARSET 這個定義,是用來將標頭內容轉成指定的字集,別忘了 define (SCRIPT_CHARSET,'***') ; 和 define (SYSTEM_CHARSET,'***') ; 先。
  2. 樹狀結構的存取就自行處理囉。
  3. php 指令 iconv_mime_decode_headers 也有點不盡理想。主要在 header 長度過長被切斷成數行的情況時,重組的結果會有不可預期的空白出現。另外則是部份 mail agent 的設計失當,在 header 中使用特定字集而未加以編碼,造成還原上的錯誤。這部份,可以考慮自行改寫 header 的反解功能來取代它。