Zend_Mail_Part で メール解析 する。
Zend_Mailを使った送信のサンプルはたくさんあったのだけど、逆のデコードについては情報がありませんでした。
デコードと言えば、PEARのMail_mimeDecodeが有名なので、みなさんそちらに走ってるのかなぁ。
あえて逆らって、Zend Frameworkで方法を探ってみた。
結論から書くと Zend_Mail_Message(Zend_Mail_Part) でできるのだけど、マルチバイト処理周りは無いので、そこは作ってやる必要がありました。
ついでに、添付ファイルのbase64のデコードとか追加して、MyMailPart というおれおれメールデコーダーを作ってみた。
動作検証はあまりしてない。
<?php $BASE_DIR = dirname(__FILE__); set_include_path($BASE_DIR.'/ZendFramework-1.5.2/library' . PATH_SEPARATOR . get_include_path()); require 'Zend/Mail/Message.php'; /** * Zend_Mail_Partを使ったメール解析クラス * 日本語も処理できるようにしたつもり。 * */ class MyMailPart { /** * @var Zend_Mail_Part */ protected $_zend_part; /** * コンテンツに適用するエンコード(このコードに変換して返す) * * @var string */ protected $_to_charset; /** * ヘッダー * * @var array */ protected $_headers; /** * コンテンツ本体 * * @var string */ protected $_content; /** * constructor * * ex) $mmp = new MyMailPart('/hoge/mail.log'); * ex) $fh = fopen('php://stdin', 'r'); $mmp = new MyMailPart($fh); * * @param string|resource|Zend_Mail_Part $filename_or_handle_or_zend_part ファイル名|ファイルハンドル * @param string $to_charset エンコーディング */ public function __construct($filename_or_handle_or_zend_part, $to_charset = 'UTF-8') { if ($filename_or_handle_or_zend_part instanceof Zend_Mail_Part) { $zend_part = $filename_or_handle_or_zend_part; } else { $zend_part = new Zend_Mail_Message(array( 'file' => $filename_or_handle_or_zend_part )); } $this->_zend_part = $zend_part; $this->_to_charset = $to_charset; } /** * ヘッダーの content-type を得る。 * * @return string|null ex)'text/plain' */ function getContentType() { $h = $this->getHeader('content-type'); if ($h === null) { return null; } $ct = Zend_Mime_Decode::splitContentType($h); return $ct['type']; } /** * 添付ファイル名を得る。 * * @return string|null ex)'dsc12345.jpg' */ function getAttachFilename() { preg_match('#filename="(.*)"#i', $this->getHeader('content-disposition'), $matches); if (!isset($matches[1])) { return null; } return $matches[1]; } /** * 指定のヘッダを得る。 * * @param string $name ヘッダの項目名 ex)'from' * @return string|null ex)'hoge@example.com' */ function getHeader($name) { if (empty($this->_headers)) { $this->getHeaders(); } if (!isset($this->_headers[$name])) { return null; } return $this->_headers[$name]; } /** * 全てのヘッダを得る。 * * @return array */ function getHeaders() { if (empty($this->_headers)) { $old_ie = mb_internal_encoding(); mb_internal_encoding($this->_to_charset); $h = $this->_zend_part->getHeaders(); foreach ($h as &$item) { if (!is_array($item)) { $item = mb_decode_mimeheader($item); } } mb_internal_encoding($old_ie); $this->_headers = $h; } return $this->_headers; } /** * コンテンツを得る。 * * @return string */ function getContent() { if (empty($this->_content)) { // content-type の charset よりエンコード変換 $c = $this->_zend_part->getContent(); $ct = Zend_Mime_Decode::splitContentType($this->_zend_part->getHeader('content-type')); $from_charset = 'ASCII'; switch (strtoupper($ct['charset'])) { case 'UTF-8': $from_charset = 'UTF-8'; break; case 'ISO-2022-JP': $from_charset = 'ISO-2022-JP'; break; case 'EUC-JP': $from_charset = 'EUC-JP'; break; case 'SHIFT_JIS': $from_charset = 'SJIS'; break; }; if ($from_charset !== $this->_to_charset) { $c = mb_convert_encoding($c, $this->_to_charset, $from_charset); } // content-transfer-encoding: base64 if ($this->getHeader('content-transfer-encoding') === 'base64') { $c = base64_decode($c); } $this->_content = $c; } return $this->_content; } /** * マルチパートを得る。 * * @return array|null MyMailPart の 配列、もしくは null */ function getParts() { if (!$this->_zend_part->isMultipart()) { return null; } $ret = array(); foreach ($this->_zend_part as $part) { $ret[] = new self($part, $this->_to_charset); } return $ret; } /** * ダンプ * * @return array */ public function dump() { $ret = array(); $ret['content-type'] = $this->getContentType(); $ret['headers'] = $this->getHeaders(); $ret['content'] = $this->getContent(); $parts = $this->getParts(); if ($parts !== null) { $ps = array(); foreach ($parts as $val) { $ps[] = $val->dump(); } $ret['parts'] = $ps; } return $ret; } } ?>
サンプルをいくつか。
どんな感じでデータが入ってくるのかは、dump()してみてください。
(バイナリの添付ファイルがあると化け化けになりますが)
$mmp = new MyMailPart($BASE_DIR.'/mail.txt'); print_r($mmp->dump()); ?>
マルチパートを考慮して、順番にcontent-typeを見て必要なものだけ抽出してみたり。
<?php function dump_content($part) { $ret = array('type' => 'unknown'); $c = $part->getContent(); switch ($part->getContentType()) { case 'text/plain': $ret = array( 'type' => 'text', 'content' => $c, ); break; case 'text/html': $ret = array( 'type' => 'text', 'content' => strip_tags($c), ); break; case 'application/octet-stream': $filename = $part->getAttachFilename(); if ($filename !== null) { $ret = array( 'type' => 'attach', 'filename' => $filename, ); } break; case 'image/png': case 'image/gif': case 'image/jpeg': $filename = $part->getAttachFilename(); if ($filename !== null) { $ret = array( 'type' => 'image', 'filename' => $filename, ); } break; } return $ret; } function dump($part) { $ret = array(); $ret['content-type'] = $part->getContentType(); $ret['content'] = dump_content($part); $parts = $part->getParts(); if ($parts !== null) { $ps = array(); foreach ($parts as $val) { $ps[] = dump($val); } $ret['parts'] = $ps; } return $ret; } print_r(dump($mmp)); ?>
最初の text/plain だけ抽出してみたり。
<?php function get_first_text($part, &$ret) { if (!isset($ret['subject']) && $part->getHeader('subject') !== null) { $ret['headers'] = $part->getHeaders(); $ret['subject'] = $part->getHeader('subject'); } if ($part->getContentType() === 'text/plain') { $ret['content'] = $part->getContent(); return; } $parts = $part->getParts(); if ($parts !== null) { $ps = array(); foreach ($parts as $val) { get_first_text($val, $ret); if (isset($ret['content'])) return; } } } $ret = array(); get_first_text($mmp, $ret); print_r($ret); ?>