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')
$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']) {
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);
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;
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'));
- $filename = $this->getHeader('content-disposition', '/filename="?([^"]+?)"?\s*(;|$)/i');
- $format = strtolower($this->getHeader('content-type', '/format="?([^"]+?)"?\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'));
$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);
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);
$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];
+ }
+ }
}
}
}
$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 -")));
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;
}
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;
+ $headers[$hdr] = ltrim($val);
} else {
unset($hdr);
}
$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;
}
}
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;
}
$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;
}
$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);
}
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 '<img class="multipart" src="'
'artid' => Banana::$artid,
'part' => $part)))
. '" alt="' . 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;
'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;
+ } 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 '<hr />' . banana_formatPlainText($this);
+ if ($type == 'message') { // we have a raw source of data (no specific pre-formatting)
+ return '<hr />' . utf8_encode(banana_formatPlainText($this));
}
return banana_formatPlainText($this);
}
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')) {
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) {
}
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: