Fixes vim mode line.
[banana.git] / banana / mimepart.inc.php
index 9270438..d654ea5 100644 (file)
@@ -19,6 +19,7 @@ class BananaMimePart
     private $boundary     = null;
     private $filename     = null;
     private $format       = null;
+    private $signature    = array();
 
     private $body         = null;
     private $multipart    = null;
@@ -81,12 +82,13 @@ class BananaMimePart
         return true;
     }
 
-    protected function makeMultiPart($body, $content_type, $encoding, $boundary)
+    protected function makeMultiPart($body, $content_type, $encoding, $boundary, $sign_protocole)
     {
         $this->body         = $body;
         $this->content_type = $content_type;
         $this->encoding     = $encoding;
         $this->boundary     = $boundary;
+        $this->signature['protocole'] = $sign_protocole;
         $this->parse();
     }
 
@@ -105,7 +107,7 @@ class BananaMimePart
             $this->body         = null;
             $this->format       = null;
             $this->id           = null;
-        } 
+        }
     }
 
     public function addAttachment(array $file, $content_type = null, $disposition = 'attachment')
@@ -122,7 +124,7 @@ class BananaMimePart
         return false;
     }
 
-    protected function getHeader($title, $filter = null)
+    public function getHeader($title, $filter = null)
     {
         if (!isset($this->headers[$title])) {
             return null;
@@ -134,7 +136,7 @@ class BananaMimePart
             return trim($matches[1]);
         }
         return null;
-    } 
+    }
 
     protected function fromRaw($data)
     {
@@ -154,10 +156,12 @@ class BananaMimePart
             return;
         }
         $content       = join("\n", $lines);
+        unset($lines);
         $test          = trim($content);
         if (empty($test)) {
             return;
         }
+        unset($test);
 
         $content_type = strtolower($this->getHeader('content-type', '/^\s*([^ ;]+?)(;|$)/'));
         if (empty($content_type)) {
@@ -170,20 +174,25 @@ class BananaMimePart
             $disposition  = $this->getHeader('content-disposition', '/(inline|attachment)/i');
             $boundary     = $this->getHeader('content-type', '/boundary="?([^ "]+?)"?\s*(;|$)/i');
             $charset      = strtolower($this->getHeader('content-type', '/charset="?([^ "]+?)"?\s*(;|$)/i'));
-            $filename     = $this->getHeader('content-disposition', '/filename="?([^ "]+?)"?\s*(;|$)/i');
+            $filename     = $this->getHeader('content-disposition', '/filename="?([^"]+?)"?\s*(;|$)/i');
             $format       = strtolower($this->getHeader('content-type', '/format="?([^ "]+?)"?\s*(;|$)/i'));
             $id           = $this->getHeader('content-id', '/<(.*?)>/');
+            $sign_protocole = strtolower($this->getHeader('content-type', '/protocol="?([^ "]+?)"?\s*(;|$)/i'));
             if (empty($filename)) {
                 $filename = $this->getHeader('content-type', '/name="?([^"]+)"?/');
             }
-        } 
+        }
         list($type, $subtype) = explode('/', $content_type);
+        if ($disposition == 'attachment') {
+            $this->makeDataPart($content, $content_type, $encoding, $filename, $disposition, $id);
+            return;
+        }
         switch ($type) {
           case 'text': case 'message':
             $this->makeTextPart($content, $content_type, $encoding, $charset, $format);
             break;
           case 'multipart':
-            $this->makeMultiPart($content, $content_type, $encoding, $boundary);
+            $this->makeMultiPart($content, $content_type, $encoding, $boundary, $sign_protocole);
             break;
           default:
             $this->makeDataPart($content, $content_type, $encoding, $filename, $disposition, $id);
@@ -198,7 +207,14 @@ class BananaMimePart
             $parts = $this->findUUEncoded();
             if (count($parts)) {
                 $this->convertToMultiPart();
-                $this->multipart    = array_merge(array($textpart), $parts);
+                $this->multipart = array_merge($this->multipart, $parts);
+                // Restore "message" headers to the previous level"
+                $this->headers = array();
+                foreach (Banana::$msgshow_headers as $hdr) {
+                    if (isset($this->multipart[0]->headers[$hdr])) {
+                        $this->headers[$hdr] = $this->multipart[0]->headers[$hdr];
+                    }
+                }
             }
         }
     }
@@ -210,13 +226,24 @@ class BananaMimePart
             $this->multipart = array();
         }
         $boundary =& $this->boundary;
-        $parts = preg_split("/\n--" . preg_quote($boundary, '/') . "(--|\n)/", $this->body, -1, PREG_SPLIT_NO_EMPTY);
+        $parts = preg_split("/(^|\n)--" . preg_quote($boundary, '/') . "(--|\n)/", $this->body, -1, PREG_SPLIT_NO_EMPTY);
+        $signed = $this->isType('multipart', 'signed');
+        $signature = null;
+        $signed_message = null;
         foreach ($parts as &$part) {
             $newpart = new BananaMimePart($part);
             if (!is_null($newpart->content_type)) {
+                if ($signed && $newpart->content_type == $this->signature['protocole']) {
+                    $signature = $newpart->body;
+                } elseif ($signed) {
+                    $signed_message = $part;
+                }
                 $this->multipart[] = $newpart;
             }
         }
+        if ($signed) {
+            $this->checkPGPSignature($signature, $signed_message);
+        }
         $this->body = null;
     }
 
@@ -224,6 +251,9 @@ class BananaMimePart
     {
         if ($is_filename) {
             $type = mime_content_type($data);
+            if ($type == 'text/plain') { // XXX Workaround a bug of php 5.2.0+etch10 (fallback for mime_content_type is text/plain)
+                $type = preg_replace('/;.*/', '', trim(shell_exec('file -bi ' . escapeshellarg($data))));
+            }
         } else {
             $arg = escapeshellarg($data);
             $type = preg_replace('/;.*/', '', trim(shell_exec("echo $arg | file -bi -")));
@@ -234,7 +264,7 @@ class BananaMimePart
     private function findUUEncoded()
     {
         $this->decodeContent();
-        $parts = array(); 
+        $parts = array();
         if (preg_match_all("/\n(begin \d+ ([^\r\n]+)\r?(?:\n(?!end)[^\n]*)*\nend)/",
             $this->body, $matches, PREG_SET_ORDER)) {
             foreach ($matches as &$match) {
@@ -243,11 +273,12 @@ class BananaMimePart
                 if ($mime != 'application/x-empty') {
                     $this->body = trim(str_replace($match[0], '', $this->body));
                     $newpart = new BananaMimePart;
+                    self::decodeHeader($match[2]);
                     $newpart->makeDataPart($data, $mime, '8bit', $match[2], 'attachment');
                     $parts[] = $newpart;
                 }
-            }   
-        } 
+            }
+        }
         return $parts;
     }
 
@@ -258,7 +289,7 @@ class BananaMimePart
         return str_replace('_', ' ', $s);
     }
 
-    static public function decodeHeader(&$val, $key)
+    static public function decodeHeader(&$val, $key = null)
     {
         if (preg_match('/[\x80-\xff]/', $val)) {
             if (!is_utf8($val)) {
@@ -281,7 +312,7 @@ class BananaMimePart
                 if (strpos($line, ':') !== false) {
                     list($hdr, $val) = explode(":", $line, 2);
                     $hdr = strtolower($hdr);
-                    if (in_array($hdr, Banana::$msgparse_headers)) {  
+                    if (in_array($hdr, Banana::$msgparse_headers)) {
                         $headers[$hdr] = ltrim($val);
                     } else {
                         unset($hdr);
@@ -323,7 +354,7 @@ class BananaMimePart
         }
 
         if (!is_null($this->charset)) {
-            $body = iconv($this->charset, 'UTF-8//IGNORE', $this->body);
+            $body = @iconv($this->charset, 'UTF-8//IGNORE', $this->body);
             if (empty($body)) {
                 return;
             }
@@ -391,15 +422,15 @@ class BananaMimePart
         $content = "";
         if ($with_headers) {
             foreach ($this->getHeaders() as $key => $value) {
-                $line = "$key: $value"; 
+                $line = "$key: $value";
                 $line = explode("\n", wordwrap($line, Banana::$msgshow_wrap));
                 for ($i = 1 ; $i < count($line) ; $i++) {
                     $line[$i] = "\t" . $line[$i];
                 }
                 $content .= implode("\n", $line) . "\n";
-            } 
+            }
             $content .= "\n";
-        } 
+        }
         if ($this->isType('multipart')) {
             $this->setBoundary();
             foreach ($this->multipart as &$part) {
@@ -416,12 +447,20 @@ class BananaMimePart
 
     public function getText()
     {
+        $signed =& $this->getSignedPart();
+        if ($signed !== $this) {
+            return $signed->getText();
+        }
         $this->decodeContent();
         return $this->body;
     }
 
     public function toHtml()
     {
+        $signed =& $this->getSignedPart();
+        if ($signed !== $this) {
+            return $signed->toHtml();
+        }
         @list($type, $subtype) = $this->getType();
         if ($type == 'image') {
             $part = $this->id ? $this->id : $this->filename;
@@ -430,6 +469,15 @@ class BananaMimePart
                                                                     'artid' => Banana::$artid,
                                                                     'part'  => $part)))
                  . '" alt="' . banana_htmlentities($this->filename) . '" />';
+        } else if ($type == 'multipart' && $subtype == 'alternative') {
+            $types =& Banana::$msgshow_mimeparts;
+            foreach ($types as $type) {
+                @list($type, $subtype) = explode('/', $type);
+                $part = $this->getParts($type, $subtype);
+                if (count($part) > 0) {
+                    return $part[0]->toHtml();
+                }
+            }
         } elseif ((!in_array($type, Banana::$msgshow_mimeparts)
                   && !in_array($this->content_type, Banana::$msgshow_mimeparts))
                   || $this->disposition == 'attachment') {
@@ -437,11 +485,11 @@ class BananaMimePart
             if (!$part) {
                 $part = $this->content_type;
             }
-            return '[' . Banana::$page->makeImgLink(array('group' => Banana::$group,
+            return '<span>[' . Banana::$page->makeImgLink(array('group' => Banana::$group,
                                                  'artid' => Banana::$artid,
                                                  'part'  => $part,
                                                  'text'  => $this->filename ? $this->filename : $this->content_type,
-                                                 'img'   => 'save')) . ']';
+                                                 'img'   => 'save')) . ']</span>';
         } elseif ($type == 'multipart' && ($subtype == 'mixed' || $subtype == 'report')) {
             $text = '';
             foreach ($this->multipart as &$part) {
@@ -464,6 +512,10 @@ class BananaMimePart
 
     public function quote()
     {
+        $signed =& $this->getSignedPart();
+        if ($signed !== $this) {
+            return $signed->quote();
+        }
         list($type, $subtype) = $this->getType();
         if (in_array($type, Banana::$msgedit_mimeparts) || in_array($this->content_type, Banana::$msgedit_mimeparts)) {
             if ($type == 'multipart' && ($subtype == 'mixed' || $subtype == 'report')) {
@@ -557,7 +609,9 @@ class BananaMimePart
         if (in_array('source', $types)) {
             $source = @$names['source'] ? $names['source'] : 'source';
         }
-        if (!$this->isType('multipart', 'alternative') && !$this->isType('multipart', 'related')) {
+        if ($this->isType('multipart', 'signed')) {
+            $parts = array($this->getSignedPart());
+        } else if (!$this->isType('multipart', 'alternative') && !$this->isType('multipart', 'related')) {
             if ($source) {
                 $parts = array($this);
             } else {
@@ -600,7 +654,73 @@ class BananaMimePart
         }
         return null;
     }
+
+    protected function &getSignedPart()
+    {
+        if ($this->isType('multipart', 'signed')) {
+            foreach ($this->multipart as &$part) {
+                if ($part->content_type != $this->signature['protocole']) {
+                    return $part;
+                }
+            }
+        }
+        return $this;
+    }
+
+    private function checkPGPSignature($signature, $message = null)
+    {
+        if (!Banana::$msgshow_pgpcheck) {
+            return true;
+        }
+        $signname = tempnam(Banana::$spool_root, 'banana_pgp_');
+        $gpg = 'LC_ALL="en_US" ' . Banana::$msgshow_pgppath . ' ' . Banana::$msgshow_pgpoptions . ' --verify '
+                .  $signname . '.asc ';
+        file_put_contents($signname. '.asc', $signature);
+        $gpg_check = array();
+        if (!is_null($message)) {
+            file_put_contents($signname, str_replace(array("\r\n", "\n"), array("\n", "\r\n"), $message));
+            exec($gpg . $signname . ' 2>&1', $gpg_check, $result);
+            unlink($signname);
+        } else {
+            exec($gpg . '2&>1', $gpg_check, $result);
+        }
+        unlink("$signname.asc");
+        if (preg_match('/Signature made (.+) using (.+) key ID (.+)/', array_shift($gpg_check), $matches)) {
+            $this->signature['date'] = strtotime($matches[1]);
+            $this->signature['key'] = array('format' => $matches[2],
+                                            'id'     => $matches[3]);
+        } else {
+            return false;
+        }
+        $signature = array_shift($gpg_check);
+        if (preg_match('/Good signature from "(.+)"/', $signature, $matches)) {
+            $this->signature['verify'] = true;
+            $this->signature['identity'] = array($matches[1]);
+            $this->signature['certified'] = true;
+        } elseif (preg_match('/BAD signature from "(.+)"/', $signature, $matches)) {
+            $this->signature['verify'] = false;
+            $this->signature['identity'] = array($matches[1]);
+            $this->signature['certified'] = false;
+        } else {
+            return false;
+        }
+        foreach ($gpg_check as $aka) {
+            if (preg_match('/aka "(.+)"/', $aka, $matches)) {
+                $this->signature['identity'][] = $matches[1];
+            }
+            if (preg_match('/This key is not certified with a trusted signature!/', $aka)) {
+                $this->signature['certified'] = false;
+                $this->signature['certification_error'] = _b_("identité non confirmée");
+            }
+        }
+        return true;
+    }
+
+    public function getSignature()
+    {
+        return $this->signature;
+    }
 }
 
-// vim:set et sw=4 sts=4 ts=4 enc=utf-8:
+// vim:set et sw=4 sts=4 ts=4 fenc=utf-8:
 ?>