画像が添付されたメールをPHPスクリプトで処理するメモ。
CakePHP(1.2.1.8004)のタスク機能にメール解析処理を実装してみます。

MIMEデータのデコードに、Zend Framework(1.7.5)のZend_Mime_Decodeを使います。

Index

  1. すること、しないこと
  2. サーバー環境
  3. 準備
  4. メール受信スクリプト
  5. Postfix関連の設定
  6. リンク

すること、しないこと

  • 画像が添付されたメールを解析し、画像をファイルに保存します。
  • エンコード方式がBase64以外の添付ファイルは扱いません。
  • 添付ファイルを保存するだけで、画像のフォーマットなどのチェックはしません。

サーバー環境

CentOS4で試しました。
SMTPサーバーはPostfixです。

PHPのバージョンは5。
PHPのSAPIはコマンドラインインターフェース(cli)です。

CakePHP コンソール :: CakePHPによる開発 :: マニュアル :: 1.2 Collection :: The Cookbookによると、

コンソールを使用する場合は、PHP のコマンドライン(CLI)版が利用可能な状態になっている必要があります。

とのことです。

準備

cake/console/cakeに記述されているphpを絶対パスに変更しました。

cake/console/cake 抜粋 (phpが/usr/local/bin/phpにある場合)
exec /usr/local/bin/php -q ${LIB}cake.php -working "${APP}" "$@"

メール受信スクリプト

実験的なものです。
いつもPEAR::Mail_mimeDecodeを使うのですが、Zend_Mime_Decodeを使ってみました。
(Zend_Mime_Decodeを正しく使用していないかも。)

app/vendors/shells/photo_mail.php
PHP:
  1. <?php
  2. class PhotoMailShell extends Shell
  3. {
  4.   var $tasks = array('SavePhotoMail');
  5.  
  6.   function main()
  7.   {
  8.     include_once 'Zend/Mime/Decode.php';
  9.  
  10.     $this->SavePhotoMail->execute();
  11.     exit(0);
  12.   }
  13. }
  14. ?>

SavePhotoMailタスクでは、どんなデータが取得できたかを見るためにログを出力しています。

app/vendors/shells/tasks/save_photo_mail.php
PHP:
  1. <?php
  2. class SavePhotoMailTask extends Shell
  3. {
  4.   var $_saveDir;
  5.  
  6.   function initialize()
  7.   {
  8.     Shell::initialize();
  9.  
  10.     $this->_saveDir = TMP . 'image_files';
  11.   }
  12.  
  13.   function execute()
  14.   {
  15.     mb_internal_encoding('UTF-8');
  16.     mb_detect_order("ASCII,JIS,UTF-8,EUC-JP,SJIS");
  17.  
  18.     $this->_makeSaveDir();
  19.  
  20.     $message = '';
  21.  
  22.     if (php_sapi_name()=='cli') {
  23.       while(!feof(STDIN)) {
  24.         $message .= fread(STDIN, 4096);
  25.       }
  26.     }
  27.  
  28.     if (!$message) {
  29.       $this->log('メッセージを取得できませんでした');
  30.       return;
  31.     }
  32.    
  33.     Zend_Mime_Decode::splitMessage($message,$headers,$body);
  34.  
  35.     //$this->log($message);
  36.     $this->log($headers);
  37.     //$this->log($body);
  38.  
  39.     //$this->log($subject);
  40.  
  41.     $content_type = Zend_Mime_Decode::splitHeaderField($headers['content-type']);
  42.     $this->log($content_type);
  43.  
  44.     if (isset($content_type['boundary'])) {
  45.       $struct = Zend_Mime_Decode::splitMessageStruct($message,$content_type['boundary']);
  46.       if ($struct) {
  47.         foreach ($struct as $s_index=>$content) {
  48.           if (!isset($content['header']['content-type']) || !isset($content['body'])) {
  49.             continue;
  50.           }
  51.  
  52.           $this->log('----------------------------------------------------------------');
  53.           $this->log($content['header']);
  54.  
  55.           $is_attachment = false;
  56.           if (isset($content['header']['content-disposition'])) {
  57.             $cd = Zend_Mime_Decode::splitHeaderField($content['header']['content-disposition']);
  58.             if (isset($cd[0]) && $cd[0] == 'attachment') {
  59.               $is_attachment = true;
  60.             }
  61.           }
  62.            
  63.           $t = Zend_Mime_Decode::splitContentType($content['header']['content-type']);
  64.           if (!isset($t['type'])) {
  65.             $this->log('不明なcontent-type - ');
  66.             $this->log($content['header']);
  67.             continue;
  68.           }
  69.  
  70.           // テキスト本文
  71.           if ($t['type'] == 'text/plain') {
  72.             if (isset($t['charset']) && $t['charset'] == 'ISO-2022-JP') {
  73.               $this->log($t['charset']);
  74.               $body = mb_convert_encoding($content['body'],mb_internal_encoding(),'ISO-2022-JP');
  75.             }
  76.             else {
  77.               $body = mb_convert_encoding($content['body'],mb_internal_encoding(),mb_detect_order());
  78.             }
  79.             $this->log($body);
  80.           }
  81.           // 添付画像ファイル
  82.           else if ($is_attachment && strpos($t['type'],'image') !== false) {
  83.             $enc = 'base64';
  84.             if ($content['header']['content-transfer-encoding']) {
  85.               $e = Zend_Mime_Decode::splitHeaderField($content['header']['content-transfer-encoding']);
  86.               if (isset($e[0])) {
  87.                 $enc = $e[0];
  88.               }
  89.             }
  90.             if ($enc == 'base64') {
  91.               if (is_dir($this->_saveDir) && is_writable($this->_saveDir)) {
  92.                 $save_path = $this->_makeSavePath();
  93.                 $data = base64_decode($content['body']);
  94.                 file_put_contents($save_path,$data);// ファイルを保存する。
  95.                 @chmod($save_path,0644);
  96.                 $this->log('保存しました。' . $save_path);
  97.  
  98.                 // TODO: 画像ファイルをチェックする
  99.               }
  100.               else {
  101.                 $this->log('保存ディレクトリが不正: ' . $this->_saveDir);
  102.               }
  103.             }
  104.             else {
  105.               $this->log('保存失敗.現在対応していないエンコード方式 - ');
  106.               $this->log($content['header']);
  107.             }
  108.           }
  109.           else if ($t['type'] == 'multipart/alternative') {
  110.             $this->log('現在対応していないcontent-type(htmlメール?) - ');
  111.             $this->log($content['header']);
  112.           }
  113.           else if ($t['type'] == 'application/octet-stream') {
  114.             $this->log('現在対応していないcontent-type - ');
  115.             $this->log($content['header']);
  116.           }
  117.           else {
  118.             $this->log('現在対応していないcontent-type - ');
  119.             $this->log($content['header']);
  120.           }
  121.         }
  122.       }
  123.     }
  124.     // テキスト本文(添付なし)
  125.     else if ($content_type[0] == 'text/plain') {
  126.       if (isset($content_type['charset']) && $content_type['charset'] == 'ISO-2022-JP') {
  127.         $body = mb_convert_encoding($body,mb_internal_encoding(),'ISO-2022-JP');
  128.       }
  129.       else {
  130.       }
  131.       $this->log($body);
  132.     }
  133.     else {
  134.       $this->log('不明なタイプ - ');
  135.       $this->log($headers);
  136.       $this->log($content_type);
  137.       $this->log('---------------');
  138.     }
  139.   }
  140.  
  141.   function _makeSaveDir()
  142.   {
  143.     if (is_dir($this->_saveDir)) {
  144.       return true;
  145.     }
  146.     if (is_file($this->_saveDir)) {
  147.       return false;
  148.     }
  149.     if (@mkdir($this->_saveDir) !== false) {
  150.       @chmod($this->_saveDir,0777);
  151.       return true;
  152.     }
  153.     return false;
  154.   }
  155.  
  156.   function _makeSavePath()
  157.   {
  158.     if (!is_dir($this->_saveDir)) {
  159.       return false;
  160.     }
  161.     $path = tempnam($this->_saveDir,'tmp');
  162.     @chmod($path,0644);
  163.     return $path;
  164.   }
  165. }

Posftfix関連の設定

testusername@localhost.localdomain(※)というメールアドレス宛に画像を添付したメールを送信するとします。

testusername@localhost.localdomain宛のメールをCakePHPのタスク機能で処理するための記述を、/etc/aliasesに追加します。

/etc/aliases 例
testusername: "| /PATH/cake/console/cake -app /PATH/app photo_mail"

上の例では、cakeコマンドを絶対パスで指定しています。
また、「-app /PATH/app」のように、appディレクトリを絶対パスで指定しています。

/etc/aliasesを編集後、newaliasesコマンドを実行します。

以上で、メールに添付された画像をファイルに保存できました。

※testusername@localhost.localdomainは例で、存在しない仮のメールアドレスです。

リンク

関連投稿

Tags: , ,

コメントをどうぞ