9f039ebf758a8051b24c1e4d696874e6217d8610
2 /********************************************************************************
3 * banana/mimepart.inc.php : class for MIME parts
4 * ------------------------
6 * This file is part of the banana distribution
7 * Copyright: See COPYING files that comes with this distribution
8 ********************************************************************************/
12 public $headers = null
; /* Should be protected */
15 private $content_type = null
;
16 private $charset = null
;
17 private $encoding = null
;
18 private $disposition = null
;
19 private $boundary = null
;
20 private $filename = null
;
21 private $format = null
;
24 private $multipart = null
;
26 protected function __construct($data = null
)
28 if (!is_null($data)) {
29 $this->fromRaw($data);
33 protected function makeTextPart($body, $content_type, $encoding, $charset = null
, $format = 'fixed')
36 $this->charset
= $charset;
37 $this->encoding
= $encoding;
38 $this->content_type
= $content_type;
39 $this->format
= strtolower($format);
43 protected function makeDataPart($body, $content_type, $encoding, $filename, $disposition, $id = null
)
46 $this->content_type
= $content_type;
47 $this->encoding
= $encoding;
48 $this->filename
= $filename;
49 $this->disposition
= $disposition;
51 if (is_null($content_type) ||
$content_type == 'application/octet-stream') {
52 $this->decodeContent();
53 $this->content_type
= BananaMimePart
::getMimeType($body, false
);
57 protected function makeFilePart($file, $content_type =null
, $disposition = 'attachment')
59 $body = file_get_contents($file['tmp_name']);
60 if ($body === false ||
strlen($body) != $file['size']) {
63 if (is_null($content_type) ||
$content_type == 'application/octet-stream') {
64 $content_type = BananaMimePart
::getMimeType($file['tmp_name']);
66 if (substr($content_type, 0, 5) == 'text/') {
70 $body = chunk_split(base64_encode($body));
72 $this->filename
= $file['name'];
73 $this->content_type
= $content_type;
74 $this->disposition
= $disposition;
76 $this->encoding
= $encoding;
80 protected function makeMultiPart($body, $content_type, $encoding, $boundary)
83 $this->content_type
= $content_type;
84 $this->encoding
= $encoding;
85 $this->boundary
= $boundary;
89 protected function convertToMultiPart()
91 if (!$this->isType('multipart', 'mixed')) {
93 $this->content_type
= 'multipart/mixed';
94 $this->encoding
= '8bit';
95 $this->multipart
= array($newpart);
96 $this->headers
= null
;
97 $this->charset
= null
;
98 $this->disposition
= null
;
99 $this->filename
= null
;
100 $this->boundary
= null
;
102 $this->format
= null
;
107 public function addAttachment(array $file, $content_type = null
, $disposition = 'attachment')
109 $newpart = new BananaMimePart
;
110 if (!is_uploaded_file($file['tmp_name'])) {
113 if ($newpart->makeFilePart($file, $content_type, $disposition)) {
114 $this->convertToMultiPart();
115 $this->multipart
[] = $newpart;
121 protected function getHeader($title, $filter = null
)
123 if (!isset($this->headers
[$title])) {
126 $header =& $this->headers
[$title];
127 if (is_null($filter)) {
128 return trim($header);
129 } elseif (preg_match($filter, $header, $matches)) {
130 return trim($matches[1]);
135 protected function fromRaw($data)
137 if (is_array($data)) {
138 if (array_key_exists('From', $data)) {
139 $this->headers
= array_map(array($this, 'decodeHeader'), array_change_key_case($data));
145 $lines = explode("\n", $data);
147 $headers = BananaMimePart
::parseHeaders($lines);
148 $this->headers
=& $headers;
149 if (empty($headers) ||
empty($lines)) {
152 $content = join("\n", $lines);
153 $test = trim($content);
158 $content_type = strtolower($this->getHeader('content-type', '/^\s*([^ ;]+?)(;|$)/'));
159 if (empty($content_type)) {
162 $content_type = 'text/plain';
163 $format = strtolower($this->getHeader('x-rfc2646', '/format="?([^"]+?)"?\s*(;|$)/i'));
165 $encoding = strtolower($this->getHeader('content-transfer-encoding'));
166 $disposition = $this->getHeader('content-disposition', '/(inline|attachment)/i');
167 $boundary = $this->getHeader('content-type', '/boundary="?([^"]+?)"?\s*(;|$)/i');
168 $charset = strtolower($this->getHeader('content-type', '/charset="?([^"]+?)"?\s*(;|$)/i'));
169 $filename = $this->getHeader('content-disposition', '/filename="?([^"]+?)"?\s*(;|$)/i');
170 $format = strtolower($this->getHeader('content-type', '/format="?([^"]+?)"?\s*(;|$)/i'));
171 $id = $this->getHeader('content-id', '/<(.*?)>/');
172 if (empty($filename)) {
173 $filename = $this->getHeader('content-type', '/name="?([^"]+)"?/');
176 list($type, $subtype) = explode('/', $content_type);
178 case 'text': case 'message':
179 $this->makeTextPart($content, $content_type, $encoding, $charset, $format);
182 $this->makeMultiPart($content, $content_type, $encoding, $boundary);
185 $this->makeDataPart($content, $content_type, $encoding, $filename, $disposition, $id);
189 private function parse()
191 if ($this->isType('multipart')) {
192 $this->splitMultipart();
194 $parts = $this->findUUEncoded();
196 $this->convertToMultiPart();
197 $this->multipart
= array_merge(array($textpart), $parts);
202 private function splitMultipart()
204 $this->decodeContent();
205 if (is_null($this->multipart
)) {
206 $this->multipart
= array();
208 $boundary =& $this->boundary
;
209 $parts = preg_split("/\n--" . preg_quote($boundary, '/') . "(--|\n)/", $this->body
, -1, PREG_SPLIT_NO_EMPTY
);
210 foreach ($parts as &$part) {
211 $newpart = new BananaMimePart($part);
212 if (!is_null($newpart->content_type
)) {
213 $this->multipart
[] = $newpart;
219 public static function getMimeType($data, $is_filename = true
)
222 $type = mime_content_type($arg);
224 $arg = escapeshellarg($data);
225 $type = preg_replace('/;.*/', '', trim(shell_exec("echo $arg | file -bi -")));
227 return empty($type) ?
'application/octet-stream' : $type;
230 private function findUUEncoded()
232 $this->decodeContent();
234 if (preg_match_all("/\n(begin \d+ ([^\r\n]+)\r?(?:\n(?!end)[^\n]*)*\nend)/",
235 $this->body
, $matches, PREG_SET_ORDER
)) {
236 foreach ($matches as &$match) {
237 $data = convert_uudecode($match[1]);
238 $mime = BananaMimePart
::getMimeType($data, false
);
239 if ($mime != 'application/x-empty') {
240 $this->body
= trim(str_replace($match[0], '', $this->body
));
241 $newpart = new BananaMimePart
;
242 $newpart->makeDataPart($data, $mime, '8bit', $match[2], 'attachment');
250 static private function _decodeHeader($charset, $c, $str)
252 $s = ($c == 'Q' ||
$c == 'q') ?
quoted_printable_decode($str) : base64_decode($str);
253 $s = @iconv
($charset, 'UTF-8', $s);
254 return str_replace('_', ' ', $s);
257 static public function decodeHeader(&$val, $key)
259 if (preg_match('/[\x80-\xff]/', $val)) {
260 if (!is_utf8($val)) {
261 $val = utf8_encode($val);
264 $val = preg_replace('/(=\?.*?\?[bq]\?.*?\?=) (=\?.*?\?[bq]\?.*?\?=)/i', '\1\2', $val);
265 $val = preg_replace('/=\?(.*?)\?([bq])\?(.*?)\?=/ie', 'BananaMimePart::_decodeHeader("\1", "\2", "\3")', $val);
269 static public function &parseHeaders(array &$lines)
272 while (count($lines)) {
273 $line = array_shift($lines);
274 if (preg_match('/^[\t\r ]+/', $line) && isset($hdr)) {
275 $headers[$hdr] .= ' ' . trim($line);
276 } elseif (!empty($line)) {
277 if (preg_match("/:[ \t\r]*/", $line)) {
278 list($hdr, $val) = split(":[ \t\r]*", $line, 2);
279 $hdr = strtolower($hdr);
280 if (in_array($hdr, Banana
::$msgparse_headers)) {
281 $headers[$hdr] = $val;
290 array_walk($headers, array('BananaMimePart', 'decodeHeader'));
294 static public function encodeHeader($value, $trim = 0)
297 if (strlen($value) > $trim) {
298 $value = substr($value, 0, $trim);
301 if (preg_match('/[\x80-\xff]/', $value)) {
302 return '=?UTF-8?B?' . base64_encode($value) . '?=';
307 private function decodeContent()
309 $encodings = Array('quoted-printable' => 'quoted_printable_decode',
310 'base64' => 'base64_decode',
311 'x-uuencode' => 'convert_uudecode');
312 foreach ($encodings as $encoding => $callback) {
313 if ($this->encoding
== $encoding) {
314 $this->body
= $callback($this->body
);
315 $this->encoding
= '8bit';
319 if (!$this->isType('text')) {
323 if (!is_null($this->charset
)) {
324 $body = iconv($this->charset
, 'UTF-8//IGNORE', $this->body
);
330 $this->body
= utf8_encode($this->body
);
332 $this->charset
= 'utf-8';
335 public function send($force_inline = false
)
337 $this->decodeContent();
339 $dispostion = $this->disposition
;
340 $this->disposition
= 'inline';
342 $headers = $this->getHeaders();
343 foreach ($headers as $key => $value) {
344 header("$key: $value");
347 $this->disposition
= $disposition;
353 private function setBoundary()
355 if ($this->isType('multipart') && is_null($this->boundary
)) {
356 $this->boundary
= '--banana-bound-' . time() . rand(0, 255) . '-';
360 public function getHeaders()
363 $this->setBoundary();
364 $headers['Content-Type'] = $this->content_type
. ";"
365 . ($this->filename ?
" name=\"{$this->filename}\";" : '')
366 . ($this->charset ?
" charset=\"{$this->charset}\";" : '')
367 . ($this->boundary ?
" boundary=\"{$this->boundary}\";" : "");
368 if ($this->encoding
) {
369 $headers['Content-Transfer-Encoding'] = $this->encoding
;
371 if ($this->disposition
) {
372 $headers['Content-Disposition'] = $this->disposition
373 . ($this->filename ?
"; filename=\"{$this->filename}\"" : '');
375 return array_map(array($this, 'encodeHeader'), $headers);
378 public function hasBody()
380 if (is_null($this->content
) && !$this->isType('multipart')) {
386 public function get($with_headers = false
)
390 foreach ($this->getHeaders() as $key => $value) {
391 $content .= "$key: $value\n";
395 if ($this->isType('multipart')) {
396 $this->setBoundary();
397 foreach ($this->multipart
as &$part) {
398 $content .= "\n--{$this->boundary}\n" . $part->get(true
);
400 $content .= "\n--{$this->boundary}--";
402 $content .= $this->body
;
407 public function getText()
409 $this->decodeContent();
413 public function toHtml()
415 list($type, $subtype) = $this->getType();
416 if ($type == 'image') {
417 $part = $this->id ?
$this->id
: $this->filename
;
419 . banana_htmlentities(Banana
::$page->makeUrl(array('group' => Banana
::$group,
420 'artid' => Banana
::$artid,
422 . '" alt="' . banana_htmlentities($this->filename
) . '" />';
423 } elseif (!in_array($type, Banana
::$msgshow_mimeparts)
424 && !in_array($this->content_type
, Banana
::$msgshow_mimeparts)) {
425 $part = $this->id ?
$this->id
: $this->filename
;
427 $part = $this->content_type
;
429 return '[' . Banana
::$page->makeImgLink(array('group' => Banana
::$group,
430 'artid' => Banana
::$artid,
432 'text' => $this->filename ?
$this->filename
: $this->content_type
,
433 'img' => 'save')) . ']';
435 if ($type == 'multipart' && ($subtype == 'mixed' ||
$subtype == 'report')) {
437 foreach ($this->multipart
as &$part) {
438 $text .= $part->toHtml();
443 case 'html': return banana_formatHtml($this);
444 case 'enriched': case 'richtext': return banana_formatRichText($this);
446 if ($type == 'message') {
447 return '<hr />' . banana_formatPlainText($this);
449 return banana_formatPlainText($this);
455 public function quote()
457 list($type, $subtype) = $this->getType();
458 if (in_array($type, Banana
::$msgedit_mimeparts) ||
in_array($this->content_type
, Banana
::$msgedit_mimeparts)) {
459 if ($type == 'multipart' && ($subtype == 'mixed' ||
$subtype == 'report')) {
461 foreach ($this->multipart
as &$part) {
462 $qt = $part->quote();
465 $text .= "\n" . banana_quote("", 1) . "\n";
472 case 'html': return banana_quoteHtml($this);
473 case 'enriched': case 'richtext': return banana_quoteRichText($this);
474 default: return banana_quotePlainText($this);
479 protected function getType()
481 return explode('/', $this->content_type
);
484 protected function isType($type, $subtype = null
)
486 list($mytype, $mysub) = $this->getType();
487 return ($mytype == $type) && (is_null($subtype) ||
$mysub == $subtype);
490 public function isFlowed()
492 return $this->format
== 'flowed';
495 public function getFilename()
497 return $this->filename
;
500 protected function getParts($type, $subtype = null
)
503 if ($this->isType($type, $subtype)) {
505 } elseif ($this->isType('multipart')) {
506 foreach ($this->multipart
as &$part) {
507 $parts = array_merge($parts, $part->getParts($type, $subtype));
513 public function getFile($filename)
515 if ($this->filename
== $filename) {
517 } elseif ($this->isType('multipart')) {
518 foreach ($this->multipart
as &$part) {
519 $file = $part->getFile($filename);
520 if (!is_null($file)) {
528 public function getAttachments()
530 if (!is_null($this->filename
)) {
532 } elseif ($this->isType('multipart')) {
534 foreach ($this->multipart
as &$part) {
535 $parts = array_merge($parts, $part->getAttachments());
542 public function getPartById($id)
544 if ($this->id
== $id) {
546 } elseif ($this->isType('multipart')) {
547 foreach ($this->multipart
as &$part) {
548 $res = $part->getPartById($id);
549 if (!is_null($res)) {
558 // vim:set et sw=4 sts=4 ts=4 enc=utf-8: