X-Git-Url: http://git.polytechnique.org/?a=blobdiff_plain;f=banana%2Fmimepart.inc.php;h=b072242a71f1599e7530e10df3a0bb10fa11c0aa;hb=0e2d7b8ec9a5c194f6eeed5f13e7acb69dea9089;hp=26cb31af6b2e4c636428b05ca2fb55c17acdaa2e;hpb=e1debf92f73fab82a01e0e90a4f537b28f22841f;p=banana.git diff --git a/banana/mimepart.inc.php b/banana/mimepart.inc.php index 26cb31a..b072242 100644 --- a/banana/mimepart.inc.php +++ b/banana/mimepart.inc.php @@ -19,15 +19,20 @@ class BananaMimePart private $boundary = null; private $filename = null; private $format = null; + private $signature = array(); private $body = null; private $multipart = null; protected function __construct($data = null) { - if (!is_null($data)) { + if ($data instanceof BananaMimePart) { + foreach ($this as $key=>$value) { + $this->$key = $data->$key; + } + } elseif (!is_null($data)) { $this->fromRaw($data); - } + } } protected function makeTextPart($body, $content_type, $encoding, $charset = null, $format = 'fixed') @@ -50,11 +55,11 @@ class BananaMimePart $this->id = $id; if (is_null($content_type) || $content_type == 'application/octet-stream') { $this->decodeContent(); - $this->content_type = BananaMimePart::getMimeType($body, false); - } + $this->content_type = BananaMimePart::getMimeType($this->body, false); + } } - protected function makeFilePart($file, $content_type =null, $disposition = 'attachment') + protected function makeFilePart(array $file, $content_type = null, $disposition = 'attachment') { $body = file_get_contents($file['tmp_name']); if ($body === false || strlen($body) != $file['size']) { @@ -77,19 +82,20 @@ 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(); } protected function convertToMultiPart() { if (!$this->isType('multipart', 'mixed')) { - $newpart = $this; + $newpart = new BananaMimePart($this); $this->content_type = 'multipart/mixed'; $this->encoding = '8bit'; $this->multipart = array($newpart); @@ -101,15 +107,15 @@ class BananaMimePart $this->body = null; $this->format = null; $this->id = null; - } + } } public function addAttachment(array $file, $content_type = null, $disposition = 'attachment') { - $newpart = new BananaMimePart; if (!is_uploaded_file($file['tmp_name'])) { return false; } + $newpart = new BananaMimePart; if ($newpart->makeFilePart($file, $content_type, $disposition)) { $this->convertToMultiPart(); $this->multipart[] = $newpart; @@ -118,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; @@ -130,7 +136,7 @@ class BananaMimePart return trim($matches[1]); } return null; - } + } protected function fromRaw($data) { @@ -150,36 +156,43 @@ 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)) { $encoding = '8bit'; $charset = 'CP1252'; $content_type = 'text/plain'; - $format = strtolower($this->getHeader('x-rfc2646', '/format="?([^"]+?)"?\s*(;|$)/i')); + $format = strtolower($this->getHeader('x-rfc2646', '/format="?([^ w@"]+?)"?\s*(;|$)/i')); } else { $encoding = strtolower($this->getHeader('content-transfer-encoding')); $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')); + $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'); - $format = strtolower($this->getHeader('content-type', '/format="?([^"]+?)"?\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); @@ -194,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]; + } + } } } } @@ -206,20 +226,34 @@ 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; } public static function getMimeType($data, $is_filename = true) { if ($is_filename) { - $type = mime_content_type($arg); + $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 -"))); @@ -230,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) { @@ -239,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; } @@ -254,31 +289,31 @@ 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)) { $val = utf8_encode($val); } - } else { + } elseif (strpos($val, '=') !== false) { $val = preg_replace('/(=\?.*?\?[bq]\?.*?\?=) (=\?.*?\?[bq]\?.*?\?=)/i', '\1\2', $val); $val = preg_replace('/=\?(.*?)\?([bq])\?(.*?)\?=/ie', 'BananaMimePart::_decodeHeader("\1", "\2", "\3")', $val); - } + } } static public function &parseHeaders(array &$lines) { $headers = array(); - while (count($lines)) { + while ($lines) { $line = array_shift($lines); - if (preg_match('/^[\t\r ]+/', $line) && isset($hdr)) { + if (isset($hdr) && $line && ctype_space($line{0})) { $headers[$hdr] .= ' ' . trim($line); } elseif (!empty($line)) { - if (preg_match("/:[ \t\r]*/", $line)) { - list($hdr, $val) = split(":[ \t\r]*", $line, 2); + if (strpos($line, ':') !== false) { + list($hdr, $val) = explode(":", $line, 2); $hdr = strtolower($hdr); - if (in_array($hdr, Banana::$msgparse_headers)) { - $headers[$hdr] = $val; + if (in_array($hdr, Banana::$msgparse_headers)) { + $headers[$hdr] = ltrim($val); } else { unset($hdr); } @@ -298,9 +333,7 @@ class BananaMimePart $value = substr($value, 0, $trim); } } - if (preg_match('/[\x80-\xff]/', $value)) { - return '=?UTF-8?B?' . base64_encode($value) . '?='; - } + $value = preg_replace('/([\x80-\xff]+)/e', '"=?UTF-8?B?" . base64_encode("\1") . "?="', $value); return $value; } @@ -321,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; } @@ -364,7 +397,8 @@ class BananaMimePart $headers['Content-Type'] = $this->content_type . ";" . ($this->filename ? " name=\"{$this->filename}\";" : '') . ($this->charset ? " charset=\"{$this->charset}\";" : '') - . ($this->boundary ? " boundary=\"{$this->boundary}\";" : ""); + . ($this->boundary ? " boundary=\"{$this->boundary}\";" : "") + . ($this->format ? " format={$this->format}" : ""); if ($this->encoding) { $headers['Content-Transfer-Encoding'] = $this->encoding; } @@ -388,21 +422,23 @@ 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) { $content .= "\n--{$this->boundary}\n" . $part->get(true); } $content .= "\n--{$this->boundary}--"; + } elseif ($this->isType('text', 'plain')) { + $content .= banana_flow($this->body); } else { $content .= banana_wordwrap($this->body); } @@ -411,45 +447,62 @@ class BananaMimePart public function getText() { + $signed =& $this->getSignedPart(); + if ($signed !== $this) { + return $signed->getText(); + } $this->decodeContent(); return $this->body; } public function toHtml() { - list($type, $subtype) = $this->getType(); + $signed =& $this->getSignedPart(); + if ($signed !== $this) { + return $signed->toHtml(); + } + @list($type, $subtype) = $this->getType(); if ($type == 'image') { $part = $this->id ? $this->id : $this->filename; - return '' . banana_htmlentities($this->filename) . ''; - } elseif (!in_array($type, Banana::$msgshow_mimeparts) - && !in_array($this->content_type, Banana::$msgshow_mimeparts)) { + } 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') { $part = $this->id ? $this->id : $this->filename; if (!$part) { $part = $this->content_type; } - return '[' . Banana::$page->makeImgLink(array('group' => Banana::$group, + return '[' . Banana::$page->makeImgLink(array('group' => Banana::$group, 'artid' => Banana::$artid, 'part' => $part, 'text' => $this->filename ? $this->filename : $this->content_type, - 'img' => 'save')) . ']'; - } else { - if ($type == 'multipart' && ($subtype == 'mixed' || $subtype == 'report')) { - $text = ''; - foreach ($this->multipart as &$part) { - $text .= $part->toHtml(); - } - return $text; + 'img' => 'save')) . ']'; + } elseif ($type == 'multipart' && ($subtype == 'mixed' || $subtype == 'report')) { + $text = ''; + foreach ($this->multipart as &$part) { + $text .= $part->toHtml(); } + return $text; + } else { switch ($subtype) { case 'html': return banana_formatHtml($this); case 'enriched': case 'richtext': return banana_formatRichText($this); default: - if ($type == 'message') { - return '
' . banana_formatPlainText($this); + if ($type == 'message') { // we have a raw source of data (no specific pre-formatting) + return '
' . utf8_encode(banana_formatPlainText($this)); } return banana_formatPlainText($this); } @@ -459,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')) { @@ -544,6 +601,45 @@ class BananaMimePart return array(); } + public function getAlternatives() + { + $types =& Banana::$msgshow_mimeparts; + $names =& Banana::$mimeparts; + $source = null; + if (in_array('source', $types)) { + $source = @$names['source'] ? $names['source'] : 'source'; + } + 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 { + return array(); + } + } else { + $parts =& $this->multipart; + } + $alt = array(); + foreach ($parts as &$part) { + list($type, $subtype) = $part->getType(); + $ct = $type . '/' . $subtype; + if (in_array($ct, $types) || in_array($type, $types)) { + if (isset($names[$ct])) { + $alt[$ct] = $names[$ct]; + } elseif (isset($names[$type])) { + $alt[$ct] = $names[$type]; + } else { + $alt[$ct] = $ct; + } + } + } + if ($source) { + $alt['source'] = $source; + } + return $alt; + } + public function getPartById($id) { if ($this->id == $id) { @@ -558,6 +654,72 @@ 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: