- Exclude MIME functionality from rcube_imap class into rcube_mime class
1 files added
9 files modified
| | |
| | | for ($count = $IMAP->messagecount(), $i=1; $i <= $count; $i++) |
| | | { |
| | | $headers = $IMAP->get_headers($i, null, false); |
| | | $from = current($IMAP->decode_address_list($headers->from, 1, false)); |
| | | |
| | | $from = current(rcube_mime::decode_address_list($headers->from, 1, false)); |
| | | |
| | | fwrite($out, sprintf("From %s %s UID %d\n", $from['mailto'], $headers->date, $headers->uid)); |
| | | fwrite($out, $IMAP->conn->fetchPartHeader($mbox, $i)); |
| | | fwrite($out, $IMAP->conn->handlePartBody($mbox, $i)); |
| | | fwrite($out, "\n\n\n"); |
| | | |
| | | |
| | | progress_update($i, $count); |
| | | } |
| | | vputs("\ncomplete.\n"); |
| | | |
| | | |
| | | if ($filename) |
| | | fclose($out); |
| | | } |
| | |
| | | if ($IMAP->connect($host, $args['user'], $args['pass'], $imap_port, $imap_ssl)) |
| | | { |
| | | vputs("IMAP login successful.\n"); |
| | | |
| | | |
| | | $filename = null; |
| | | $mailboxes = $args['mbox'] == '*' ? $IMAP->list_mailboxes(null) : array($args['mbox']); |
| | | |
| | |
| | | $filename = preg_replace('/\.[a-z0-9]{3,4}$/i', '', $args['file']) . asciiwords($mbox) . '.mbox'; |
| | | else if ($args['mbox'] == '*') |
| | | $filename = asciiwords($mbox) . '.mbox'; |
| | | |
| | | |
| | | if ($args['mbox'] == '*' && in_array(strtolower($mbox), array('junk','spam','trash'))) |
| | | continue; |
| | | |
| | |
| | | |
| | | |
| | | /** |
| | | * Forces selection of a mailbox |
| | | * |
| | | * @param string $mailbox Mailbox/Folder name |
| | | * @access public |
| | | */ |
| | | function select_mailbox($mailbox=null) |
| | | { |
| | | if (!strlen($mailbox)) { |
| | | $mailbox = $this->mailbox; |
| | | } |
| | | |
| | | $selected = $this->conn->select($mailbox); |
| | | |
| | | if ($selected && $this->mailbox != $mailbox) { |
| | | // clear messagecount cache for this mailbox |
| | | $this->_clear_messagecount($mailbox); |
| | | $this->mailbox = $mailbox; |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Set internal list page |
| | | * |
| | | * @param number $page Page number to list |
| | |
| | | |
| | | |
| | | /** |
| | | * Save a set of message ids for future message listing methods |
| | | * Save a search result for future message listing methods |
| | | * |
| | | * @param string IMAP Search query |
| | | * @param rcube_result_index|rcube_result_thread Result set |
| | | * @param string Charset of search string |
| | | * @param string Sorting field |
| | | * @param string True if set is sorted (SORT was used for searching) |
| | | * @param array $set Search set, result from rcube_imap::get_search_set(): |
| | | * 0 - searching criteria, string |
| | | * 1 - search result, rcube_result_index|rcube_result_thread |
| | | * 2 - searching character set, string |
| | | * 3 - sorting field, string |
| | | * 4 - true if sorted, bool |
| | | */ |
| | | function set_search_set($str=null, $msgs=null, $charset=null, $sort_field=null, $sorted=false) |
| | | function set_search_set($set) |
| | | { |
| | | if (is_array($str) && $msgs === null) |
| | | list($str, $msgs, $charset, $sort_field, $sorted) = $str; |
| | | $set = (array)$set; |
| | | |
| | | $this->search_string = $str; |
| | | $this->search_set = $msgs; |
| | | $this->search_charset = $charset; |
| | | $this->search_sort_field = $sort_field; |
| | | $this->search_sorted = $sorted; |
| | | $this->search_string = $set[0]; |
| | | $this->search_set = $set[1]; |
| | | $this->search_charset = $set[2]; |
| | | $this->search_sort_field = $set[3]; |
| | | $this->search_sorted = $set[4]; |
| | | $this->search_threads = is_a($this->search_set, 'rcube_result_thread'); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Return the saved search set as hash array |
| | | * |
| | | * @param bool $clone Clone result object |
| | | * |
| | | * @return array Search set |
| | | */ |
| | |
| | | |
| | | $results = $this->_search_index($mailbox, $str, $charset, $sort_field); |
| | | |
| | | $this->set_search_set($str, $results, $charset, $sort_field, |
| | | $this->threading || $this->search_sorted ? true : false); |
| | | $this->set_search_set(array($str, $results, $charset, $sort_field, |
| | | $this->threading || $this->search_sorted ? true : false)); |
| | | } |
| | | |
| | | |
| | |
| | | |
| | | |
| | | /** |
| | | * Check if the given message UID is part of the current search set |
| | | * |
| | | * @param string $msgid Message UID |
| | | * |
| | | * @return boolean True on match or if no search request is stored |
| | | */ |
| | | function in_searchset($uid) |
| | | { |
| | | if (!empty($this->search_string)) { |
| | | return $this->search_set->exists($uid); |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Return message headers object of a specific message |
| | | * |
| | | * @param int $id Message sequence ID or UID |
| | |
| | | return $headers; |
| | | } |
| | | |
| | | $struct = &$this->_structure_part($structure, 0, '', $headers); |
| | | $struct = $this->_structure_part($structure, 0, '', $headers); |
| | | |
| | | // don't trust given content-type |
| | | if (empty($struct->parts) && !empty($headers->ctype)) { |
| | |
| | | * @param string $parent |
| | | * @access private |
| | | */ |
| | | function &_structure_part($part, $count=0, $parent='', $mime_headers=null) |
| | | private function _structure_part($part, $count=0, $parent='', $mime_headers=null) |
| | | { |
| | | $struct = new rcube_message_part; |
| | | $struct->mime_id = empty($parent) ? (string)$count : "$parent.$count"; |
| | |
| | | } |
| | | |
| | | if (is_string($mime_headers)) |
| | | $struct->headers = $this->_parse_headers($mime_headers) + $struct->headers; |
| | | $struct->headers = rcube_mime::parse_headers($mime_headers) + $struct->headers; |
| | | else if (is_object($mime_headers)) |
| | | $struct->headers = get_object_vars($mime_headers) + $struct->headers; |
| | | |
| | |
| | | else |
| | | $charset = rc_detect_encoding($filename_mime, $this->default_charset); |
| | | |
| | | $part->filename = rcube_imap::decode_mime_string($filename_mime, $charset); |
| | | $part->filename = rcube_mime::decode_mime_string($filename_mime, $charset); |
| | | } |
| | | else if (!empty($filename_encoded)) { |
| | | // decode filename according to RFC 2231, Section 4 |
| | |
| | | * |
| | | * @return string Message/part body if not printed |
| | | */ |
| | | function &get_message_part($uid, $part=1, $o_part=NULL, $print=NULL, $fp=NULL, $skip_charset_conv=false) |
| | | function get_message_part($uid, $part=1, $o_part=NULL, $print=NULL, $fp=NULL, $skip_charset_conv=false) |
| | | { |
| | | // get part data if not provided |
| | | if (!is_object($o_part)) { |
| | |
| | | * @return string $part Message/part body |
| | | * @see rcube_imap::get_message_part() |
| | | */ |
| | | function &get_body($uid, $part=1) |
| | | function get_body($uid, $part=1) |
| | | { |
| | | $headers = $this->get_headers($uid); |
| | | return rcube_charset_convert($this->get_message_part($uid, $part, NULL), |
| | |
| | | * |
| | | * @return string Message source string |
| | | */ |
| | | function &get_raw_body($uid, $fp=null) |
| | | function get_raw_body($uid, $fp=null) |
| | | { |
| | | return $this->conn->handlePartBody($this->mailbox, $uid, |
| | | true, null, null, false, $fp); |
| | |
| | | * @param int $uid Message UID |
| | | * @return string Message headers string |
| | | */ |
| | | function &get_raw_headers($uid) |
| | | function get_raw_headers($uid) |
| | | { |
| | | return $this->conn->fetchPartHeader($this->mailbox, $uid, true); |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Getter for messages cache object |
| | | */ |
| | |
| | | return $this->mcache; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Clears the messages cache. |
| | | * |
| | |
| | | { |
| | | if ($mcache = $this->get_mcache_engine()) { |
| | | $mcache->clear($mailbox, $uids); |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /* -------------------------------- |
| | | * encoding/decoding methods |
| | | * --------------------------------*/ |
| | | |
| | | /** |
| | | * Split an address list into a structured array list |
| | | * |
| | | * @param string $input Input string |
| | | * @param int $max List only this number of addresses |
| | | * @param boolean $decode Decode address strings |
| | | * @return array Indexed list of addresses |
| | | */ |
| | | function decode_address_list($input, $max=null, $decode=true) |
| | | { |
| | | $a = $this->_parse_address_list($input, $decode); |
| | | $out = array(); |
| | | // Special chars as defined by RFC 822 need to in quoted string (or escaped). |
| | | $special_chars = '[\(\)\<\>\\\.\[\]@,;:"]'; |
| | | |
| | | if (!is_array($a)) |
| | | return $out; |
| | | |
| | | $c = count($a); |
| | | $j = 0; |
| | | |
| | | foreach ($a as $val) { |
| | | $j++; |
| | | $address = trim($val['address']); |
| | | $name = trim($val['name']); |
| | | |
| | | if ($name && $address && $name != $address) |
| | | $string = sprintf('%s <%s>', preg_match("/$special_chars/", $name) ? '"'.addcslashes($name, '"').'"' : $name, $address); |
| | | else if ($address) |
| | | $string = $address; |
| | | else if ($name) |
| | | $string = $name; |
| | | |
| | | $out[$j] = array( |
| | | 'name' => $name, |
| | | 'mailto' => $address, |
| | | 'string' => $string |
| | | ); |
| | | |
| | | if ($max && $j==$max) |
| | | break; |
| | | } |
| | | |
| | | return $out; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Decode a message header value |
| | | * |
| | | * @param string $input Header value |
| | | * @param boolean $remove_quotas Remove quotes if necessary |
| | | * @return string Decoded string |
| | | */ |
| | | function decode_header($input, $remove_quotes=false) |
| | | { |
| | | $str = rcube_imap::decode_mime_string((string)$input, $this->default_charset); |
| | | if ($str[0] == '"' && $remove_quotes) |
| | | $str = str_replace('"', '', $str); |
| | | |
| | | return $str; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Decode a mime-encoded string to internal charset |
| | | * |
| | | * @param string $input Header value |
| | | * @param string $fallback Fallback charset if none specified |
| | | * |
| | | * @return string Decoded string |
| | | * @static |
| | | */ |
| | | public static function decode_mime_string($input, $fallback=null) |
| | | { |
| | | if (!empty($fallback)) { |
| | | $default_charset = $fallback; |
| | | } |
| | | else { |
| | | $default_charset = rcmail::get_instance()->config->get('default_charset', 'ISO-8859-1'); |
| | | } |
| | | |
| | | // rfc: all line breaks or other characters not found |
| | | // in the Base64 Alphabet must be ignored by decoding software |
| | | // delete all blanks between MIME-lines, differently we can |
| | | // receive unnecessary blanks and broken utf-8 symbols |
| | | $input = preg_replace("/\?=\s+=\?/", '?==?', $input); |
| | | |
| | | // encoded-word regexp |
| | | $re = '/=\?([^?]+)\?([BbQq])\?([^\n]*?)\?=/'; |
| | | |
| | | // Find all RFC2047's encoded words |
| | | if (preg_match_all($re, $input, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER)) { |
| | | // Initialize variables |
| | | $tmp = array(); |
| | | $out = ''; |
| | | $start = 0; |
| | | |
| | | foreach ($matches as $idx => $m) { |
| | | $pos = $m[0][1]; |
| | | $charset = $m[1][0]; |
| | | $encoding = $m[2][0]; |
| | | $text = $m[3][0]; |
| | | $length = strlen($m[0][0]); |
| | | |
| | | // Append everything that is before the text to be decoded |
| | | if ($start != $pos) { |
| | | $substr = substr($input, $start, $pos-$start); |
| | | $out .= rcube_charset_convert($substr, $default_charset); |
| | | $start = $pos; |
| | | } |
| | | $start += $length; |
| | | |
| | | // Per RFC2047, each string part "MUST represent an integral number |
| | | // of characters . A multi-octet character may not be split across |
| | | // adjacent encoded-words." However, some mailers break this, so we |
| | | // try to handle characters spanned across parts anyway by iterating |
| | | // through and aggregating sequential encoded parts with the same |
| | | // character set and encoding, then perform the decoding on the |
| | | // aggregation as a whole. |
| | | |
| | | $tmp[] = $text; |
| | | if ($next_match = $matches[$idx+1]) { |
| | | if ($next_match[0][1] == $start |
| | | && $next_match[1][0] == $charset |
| | | && $next_match[2][0] == $encoding |
| | | ) { |
| | | continue; |
| | | } |
| | | } |
| | | |
| | | $count = count($tmp); |
| | | $text = ''; |
| | | |
| | | // Decode and join encoded-word's chunks |
| | | if ($encoding == 'B' || $encoding == 'b') { |
| | | // base64 must be decoded a segment at a time |
| | | for ($i=0; $i<$count; $i++) |
| | | $text .= base64_decode($tmp[$i]); |
| | | } |
| | | else { //if ($encoding == 'Q' || $encoding == 'q') { |
| | | // quoted printable can be combined and processed at once |
| | | for ($i=0; $i<$count; $i++) |
| | | $text .= $tmp[$i]; |
| | | |
| | | $text = str_replace('_', ' ', $text); |
| | | $text = quoted_printable_decode($text); |
| | | } |
| | | |
| | | $out .= rcube_charset_convert($text, $charset); |
| | | $tmp = array(); |
| | | } |
| | | |
| | | // add the last part of the input string |
| | | if ($start != strlen($input)) { |
| | | $out .= rcube_charset_convert(substr($input, $start), $default_charset); |
| | | } |
| | | |
| | | // return the results |
| | | return $out; |
| | | } |
| | | |
| | | // no encoding information, use fallback |
| | | return rcube_charset_convert($input, $default_charset); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Decode a mime part |
| | | * |
| | | * @param string $input Input string |
| | | * @param string $encoding Part encoding |
| | | * @return string Decoded string |
| | | */ |
| | | function mime_decode($input, $encoding='7bit') |
| | | { |
| | | switch (strtolower($encoding)) { |
| | | case 'quoted-printable': |
| | | return quoted_printable_decode($input); |
| | | case 'base64': |
| | | return base64_decode($input); |
| | | case 'x-uuencode': |
| | | case 'x-uue': |
| | | case 'uue': |
| | | case 'uuencode': |
| | | return convert_uudecode($input); |
| | | case '7bit': |
| | | default: |
| | | return $input; |
| | | } |
| | | } |
| | | |
| | |
| | | * |
| | | * @return int Message UID |
| | | */ |
| | | function id2uid($id, $mailbox = null) |
| | | private function id2uid($id, $mailbox = null) |
| | | { |
| | | if (!strlen($mailbox)) { |
| | | $mailbox = $this->mailbox; |
| | |
| | | } |
| | | $this->update_cache('messagecount', $a_mailbox_cache); |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Split RFC822 header string into an associative array |
| | | * @access private |
| | | */ |
| | | private function _parse_headers($headers) |
| | | { |
| | | $a_headers = array(); |
| | | $headers = preg_replace('/\r?\n(\t| )+/', ' ', $headers); |
| | | $lines = explode("\n", $headers); |
| | | $c = count($lines); |
| | | |
| | | for ($i=0; $i<$c; $i++) { |
| | | if ($p = strpos($lines[$i], ': ')) { |
| | | $field = strtolower(substr($lines[$i], 0, $p)); |
| | | $value = trim(substr($lines[$i], $p+1)); |
| | | if (!empty($value)) |
| | | $a_headers[$field] = $value; |
| | | } |
| | | } |
| | | |
| | | return $a_headers; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * @access private |
| | | */ |
| | | private function _parse_address_list($str, $decode=true) |
| | | { |
| | | // remove any newlines and carriage returns before |
| | | $str = preg_replace('/\r?\n(\s|\t)?/', ' ', $str); |
| | | |
| | | // extract list items, remove comments |
| | | $str = self::explode_header_string(',;', $str, true); |
| | | $result = array(); |
| | | |
| | | // simplified regexp, supporting quoted local part |
| | | $email_rx = '(\S+|("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+"))@\S+'; |
| | | |
| | | foreach ($str as $key => $val) { |
| | | $name = ''; |
| | | $address = ''; |
| | | $val = trim($val); |
| | | |
| | | if (preg_match('/(.*)<('.$email_rx.')>$/', $val, $m)) { |
| | | $address = $m[2]; |
| | | $name = trim($m[1]); |
| | | } |
| | | else if (preg_match('/^('.$email_rx.')$/', $val, $m)) { |
| | | $address = $m[1]; |
| | | $name = ''; |
| | | } |
| | | else { |
| | | $name = $val; |
| | | } |
| | | |
| | | // dequote and/or decode name |
| | | if ($name) { |
| | | if ($name[0] == '"' && $name[strlen($name)-1] == '"') { |
| | | $name = substr($name, 1, -1); |
| | | $name = stripslashes($name); |
| | | } |
| | | if ($decode) { |
| | | $name = $this->decode_header($name); |
| | | } |
| | | } |
| | | |
| | | if (!$address && $name) { |
| | | $address = $name; |
| | | } |
| | | |
| | | if ($address) { |
| | | $result[$key] = array('name' => $name, 'address' => $address); |
| | | } |
| | | } |
| | | |
| | | return $result; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Explodes header (e.g. address-list) string into array of strings |
| | | * using specified separator characters with proper handling |
| | | * of quoted-strings and comments (RFC2822) |
| | | * |
| | | * @param string $separator String containing separator characters |
| | | * @param string $str Header string |
| | | * @param bool $remove_comments Enable to remove comments |
| | | * |
| | | * @return array Header items |
| | | */ |
| | | static function explode_header_string($separator, $str, $remove_comments=false) |
| | | { |
| | | $length = strlen($str); |
| | | $result = array(); |
| | | $quoted = false; |
| | | $comment = 0; |
| | | $out = ''; |
| | | |
| | | for ($i=0; $i<$length; $i++) { |
| | | // we're inside a quoted string |
| | | if ($quoted) { |
| | | if ($str[$i] == '"') { |
| | | $quoted = false; |
| | | } |
| | | else if ($str[$i] == '\\') { |
| | | if ($comment <= 0) { |
| | | $out .= '\\'; |
| | | } |
| | | $i++; |
| | | } |
| | | } |
| | | // we're inside a comment string |
| | | else if ($comment > 0) { |
| | | if ($str[$i] == ')') { |
| | | $comment--; |
| | | } |
| | | else if ($str[$i] == '(') { |
| | | $comment++; |
| | | } |
| | | else if ($str[$i] == '\\') { |
| | | $i++; |
| | | } |
| | | continue; |
| | | } |
| | | // separator, add to result array |
| | | else if (strpos($separator, $str[$i]) !== false) { |
| | | if ($out) { |
| | | $result[] = $out; |
| | | } |
| | | $out = ''; |
| | | continue; |
| | | } |
| | | // start of quoted string |
| | | else if ($str[$i] == '"') { |
| | | $quoted = true; |
| | | } |
| | | // start of comment |
| | | else if ($remove_comments && $str[$i] == '(') { |
| | | $comment++; |
| | | } |
| | | |
| | | if ($comment <= 0) { |
| | | $out .= $str[$i]; |
| | | } |
| | | } |
| | | |
| | | if ($out && $comment <= 0) { |
| | | $result[] = $out; |
| | | } |
| | | |
| | | return $result; |
| | | } |
| | | |
| | | |
| | |
| | | * @var rcube_imap |
| | | */ |
| | | private $imap; |
| | | |
| | | /** |
| | | * Instance of mime class |
| | | * |
| | | * @var rcube_mime |
| | | */ |
| | | private $mime; |
| | | private $opt = array(); |
| | | private $inline_parts = array(); |
| | | private $parse_alternative = false; |
| | |
| | | * |
| | | * @param string $uid The message UID. |
| | | * |
| | | * @uses rcmail::get_instance() |
| | | * @uses rcube_imap::decode_mime_string() |
| | | * @uses self::set_safe() |
| | | * |
| | | * @see self::$app, self::$imap, self::$opt, self::$structure |
| | | */ |
| | | function __construct($uid) |
| | | { |
| | | $this->app = rcmail::get_instance(); |
| | | $this->uid = $uid; |
| | | $this->app = rcmail::get_instance(); |
| | | $this->imap = $this->app->imap; |
| | | $this->imap->get_all_headers = true; |
| | | |
| | | $this->uid = $uid; |
| | | $this->headers = $this->imap->get_message($uid); |
| | | |
| | | if (!$this->headers) |
| | | return; |
| | | |
| | | $this->subject = rcube_imap::decode_mime_string( |
| | | $this->headers->subject, $this->headers->charset); |
| | | list(, $this->sender) = each($this->imap->decode_address_list($this->headers->from)); |
| | | $this->mime = new rcube_mime($this->headers->charset); |
| | | |
| | | $this->subject = $this->mime->decode_mime_string($this->headers->subject); |
| | | list(, $this->sender) = each($this->mime->decode_address_list($this->headers->from, 1)); |
| | | |
| | | $this->set_safe((intval($_GET['_safe']) || $_SESSION['safe_messages'][$uid])); |
| | | $this->opt = array( |
| | |
| | | */ |
| | | public function get_header($name, $raw = false) |
| | | { |
| | | if (empty($this->headers)) |
| | | return null; |
| | | |
| | | if ($this->headers->$name) |
| | | $value = $this->headers->$name; |
| | | else if ($this->headers->others[$name]) |
| | | $value = $this->headers->others[$name]; |
| | | |
| | | return $raw ? $value : $this->imap->decode_header($value); |
| | | return $raw ? $value : $this->mime->decode_header($value); |
| | | } |
| | | |
| | | |
| | |
| | | |
| | | return $parts; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Interpret a format=flowed message body according to RFC 2646 |
| | | * |
| | | * @param string $text Raw body formatted as flowed text |
| | | * @return string Interpreted text with unwrapped lines and stuffed space removed |
| | | */ |
| | | public static function unfold_flowed($text) |
| | | { |
| | | $text = preg_split('/\r?\n/', $text); |
| | | $last = -1; |
| | | $q_level = 0; |
| | | |
| | | foreach ($text as $idx => $line) { |
| | | if ($line[0] == '>' && preg_match('/^(>+\s*)/', $line, $regs)) { |
| | | $q = strlen(str_replace(' ', '', $regs[0])); |
| | | $line = substr($line, strlen($regs[0])); |
| | | |
| | | if ($q == $q_level && $line |
| | | && isset($text[$last]) |
| | | && $text[$last][strlen($text[$last])-1] == ' ' |
| | | ) { |
| | | $text[$last] .= $line; |
| | | unset($text[$idx]); |
| | | } |
| | | else { |
| | | $last = $idx; |
| | | } |
| | | } |
| | | else { |
| | | $q = 0; |
| | | if ($line == '-- ') { |
| | | $last = $idx; |
| | | } |
| | | else { |
| | | // remove space-stuffing |
| | | $line = preg_replace('/^\s/', '', $line); |
| | | |
| | | if (isset($text[$last]) && $line |
| | | && $text[$last] != '-- ' |
| | | && $text[$last][strlen($text[$last])-1] == ' ' |
| | | ) { |
| | | $text[$last] .= $line; |
| | | unset($text[$idx]); |
| | | } |
| | | else { |
| | | $text[$idx] = $line; |
| | | $last = $idx; |
| | | } |
| | | } |
| | | } |
| | | $q_level = $q; |
| | | } |
| | | |
| | | return implode("\r\n", $text); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Wrap the given text to comply with RFC 2646 |
| | | * |
| | | * @param string $text Text to wrap |
| | | * @param int $length Length |
| | | * @return string Wrapped text |
| | | */ |
| | | public static function format_flowed($text, $length = 72) |
| | | { |
| | | $text = preg_split('/\r?\n/', $text); |
| | | |
| | | foreach ($text as $idx => $line) { |
| | | if ($line != '-- ') { |
| | | if ($line[0] == '>' && preg_match('/^(>+)/', $line, $regs)) { |
| | | $prefix = $regs[0]; |
| | | $level = strlen($prefix); |
| | | $line = rtrim(substr($line, $level)); |
| | | $line = $prefix . rc_wordwrap($line, $length - $level - 2, " \r\n$prefix "); |
| | | } |
| | | else if ($line) { |
| | | $line = rc_wordwrap(rtrim($line), $length - 2, " \r\n"); |
| | | // space-stuffing |
| | | $line = preg_replace('/(^|\r\n)(From| |>)/', '\\1 \\2', $line); |
| | | } |
| | | |
| | | $text[$idx] = $line; |
| | | } |
| | | } |
| | | |
| | | return implode("\r\n", $text); |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | <?php |
| | | |
| | | /** |
| | | +-----------------------------------------------------------------------+ |
| | | | program/include/rcube_mime.php | |
| | | | | |
| | | | This file is part of the Roundcube Webmail client | |
| | | | Copyright (C) 2005-2012, The Roundcube Dev Team | |
| | | | Copyright (C) 2011-2012, Kolab Systems AG | |
| | | | Licensed under the GNU GPL | |
| | | | | |
| | | | PURPOSE: | |
| | | | MIME message parsing utilities | |
| | | | | |
| | | +-----------------------------------------------------------------------+ |
| | | | Author: Thomas Bruederli <roundcube@gmail.com> | |
| | | | Author: Aleksander Machniak <alec@alec.pl> | |
| | | +-----------------------------------------------------------------------+ |
| | | |
| | | $Id$ |
| | | |
| | | */ |
| | | |
| | | |
| | | /** |
| | | * Class for parsing MIME messages |
| | | * |
| | | * @package Mail |
| | | * @author Thomas Bruederli <roundcube@gmail.com> |
| | | * @author Aleksander Machniak <alec@alec.pl> |
| | | */ |
| | | class rcube_mime |
| | | { |
| | | private static $default_charset = RCMAIL_CHARSET; |
| | | |
| | | |
| | | /** |
| | | * Object constructor. |
| | | */ |
| | | function __construct($default_charset = null) |
| | | { |
| | | if ($default_charset) { |
| | | self::$default_charset = $default_charset; |
| | | } |
| | | else { |
| | | self::$default_charset = rcmail::get_instance()->config->get('default_charset', RCMAIL_CHARSET); |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Split an address list into a structured array list |
| | | * |
| | | * @param string $input Input string |
| | | * @param int $max List only this number of addresses |
| | | * @param boolean $decode Decode address strings |
| | | * @param string $fallback Fallback charset if none specified |
| | | * |
| | | * @return array Indexed list of addresses |
| | | */ |
| | | static function decode_address_list($input, $max = null, $decode = true, $fallback = null) |
| | | { |
| | | $a = self::parse_address_list($input, $decode, $fallback); |
| | | $out = array(); |
| | | $j = 0; |
| | | |
| | | // Special chars as defined by RFC 822 need to in quoted string (or escaped). |
| | | $special_chars = '[\(\)\<\>\\\.\[\]@,;:"]'; |
| | | |
| | | if (!is_array($a)) |
| | | return $out; |
| | | |
| | | foreach ($a as $val) { |
| | | $j++; |
| | | $address = trim($val['address']); |
| | | $name = trim($val['name']); |
| | | |
| | | if ($name && $address && $name != $address) |
| | | $string = sprintf('%s <%s>', preg_match("/$special_chars/", $name) ? '"'.addcslashes($name, '"').'"' : $name, $address); |
| | | else if ($address) |
| | | $string = $address; |
| | | else if ($name) |
| | | $string = $name; |
| | | |
| | | $out[$j] = array( |
| | | 'name' => $name, |
| | | 'mailto' => $address, |
| | | 'string' => $string |
| | | ); |
| | | |
| | | if ($max && $j==$max) |
| | | break; |
| | | } |
| | | |
| | | return $out; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Decode a message header value |
| | | * |
| | | * @param string $input Header value |
| | | * @param string $fallback Fallback charset if none specified |
| | | * |
| | | * @return string Decoded string |
| | | */ |
| | | public static function decode_header($input, $fallback = null) |
| | | { |
| | | $str = self::decode_mime_string((string)$input, $fallback); |
| | | |
| | | return $str; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Decode a mime-encoded string to internal charset |
| | | * |
| | | * @param string $input Header value |
| | | * @param string $fallback Fallback charset if none specified |
| | | * |
| | | * @return string Decoded string |
| | | */ |
| | | public static function decode_mime_string($input, $fallback = null) |
| | | { |
| | | $default_charset = !empty($fallback) ? $fallback : self::$default_charset; |
| | | |
| | | // rfc: all line breaks or other characters not found |
| | | // in the Base64 Alphabet must be ignored by decoding software |
| | | // delete all blanks between MIME-lines, differently we can |
| | | // receive unnecessary blanks and broken utf-8 symbols |
| | | $input = preg_replace("/\?=\s+=\?/", '?==?', $input); |
| | | |
| | | // encoded-word regexp |
| | | $re = '/=\?([^?]+)\?([BbQq])\?([^\n]*?)\?=/'; |
| | | |
| | | // Find all RFC2047's encoded words |
| | | if (preg_match_all($re, $input, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER)) { |
| | | // Initialize variables |
| | | $tmp = array(); |
| | | $out = ''; |
| | | $start = 0; |
| | | |
| | | foreach ($matches as $idx => $m) { |
| | | $pos = $m[0][1]; |
| | | $charset = $m[1][0]; |
| | | $encoding = $m[2][0]; |
| | | $text = $m[3][0]; |
| | | $length = strlen($m[0][0]); |
| | | |
| | | // Append everything that is before the text to be decoded |
| | | if ($start != $pos) { |
| | | $substr = substr($input, $start, $pos-$start); |
| | | $out .= rcube_charset_convert($substr, $default_charset); |
| | | $start = $pos; |
| | | } |
| | | $start += $length; |
| | | |
| | | // Per RFC2047, each string part "MUST represent an integral number |
| | | // of characters . A multi-octet character may not be split across |
| | | // adjacent encoded-words." However, some mailers break this, so we |
| | | // try to handle characters spanned across parts anyway by iterating |
| | | // through and aggregating sequential encoded parts with the same |
| | | // character set and encoding, then perform the decoding on the |
| | | // aggregation as a whole. |
| | | |
| | | $tmp[] = $text; |
| | | if ($next_match = $matches[$idx+1]) { |
| | | if ($next_match[0][1] == $start |
| | | && $next_match[1][0] == $charset |
| | | && $next_match[2][0] == $encoding |
| | | ) { |
| | | continue; |
| | | } |
| | | } |
| | | |
| | | $count = count($tmp); |
| | | $text = ''; |
| | | |
| | | // Decode and join encoded-word's chunks |
| | | if ($encoding == 'B' || $encoding == 'b') { |
| | | // base64 must be decoded a segment at a time |
| | | for ($i=0; $i<$count; $i++) |
| | | $text .= base64_decode($tmp[$i]); |
| | | } |
| | | else { //if ($encoding == 'Q' || $encoding == 'q') { |
| | | // quoted printable can be combined and processed at once |
| | | for ($i=0; $i<$count; $i++) |
| | | $text .= $tmp[$i]; |
| | | |
| | | $text = str_replace('_', ' ', $text); |
| | | $text = quoted_printable_decode($text); |
| | | } |
| | | |
| | | $out .= rcube_charset_convert($text, $charset); |
| | | $tmp = array(); |
| | | } |
| | | |
| | | // add the last part of the input string |
| | | if ($start != strlen($input)) { |
| | | $out .= rcube_charset_convert(substr($input, $start), $default_charset); |
| | | } |
| | | |
| | | // return the results |
| | | return $out; |
| | | } |
| | | |
| | | // no encoding information, use fallback |
| | | return rcube_charset_convert($input, $default_charset); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Decode a mime part |
| | | * |
| | | * @param string $input Input string |
| | | * @param string $encoding Part encoding |
| | | * @return string Decoded string |
| | | */ |
| | | public static function decode($input, $encoding = '7bit') |
| | | { |
| | | switch (strtolower($encoding)) { |
| | | case 'quoted-printable': |
| | | return quoted_printable_decode($input); |
| | | case 'base64': |
| | | return base64_decode($input); |
| | | case 'x-uuencode': |
| | | case 'x-uue': |
| | | case 'uue': |
| | | case 'uuencode': |
| | | return convert_uudecode($input); |
| | | case '7bit': |
| | | default: |
| | | return $input; |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Split RFC822 header string into an associative array |
| | | * @access private |
| | | */ |
| | | public static function parse_headers($headers) |
| | | { |
| | | $a_headers = array(); |
| | | $headers = preg_replace('/\r?\n(\t| )+/', ' ', $headers); |
| | | $lines = explode("\n", $headers); |
| | | $c = count($lines); |
| | | |
| | | for ($i=0; $i<$c; $i++) { |
| | | if ($p = strpos($lines[$i], ': ')) { |
| | | $field = strtolower(substr($lines[$i], 0, $p)); |
| | | $value = trim(substr($lines[$i], $p+1)); |
| | | if (!empty($value)) |
| | | $a_headers[$field] = $value; |
| | | } |
| | | } |
| | | |
| | | return $a_headers; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * @access private |
| | | */ |
| | | private static function parse_address_list($str, $decode = true, $fallback = null) |
| | | { |
| | | // remove any newlines and carriage returns before |
| | | $str = preg_replace('/\r?\n(\s|\t)?/', ' ', $str); |
| | | |
| | | // extract list items, remove comments |
| | | $str = self::explode_header_string(',;', $str, true); |
| | | $result = array(); |
| | | |
| | | // simplified regexp, supporting quoted local part |
| | | $email_rx = '(\S+|("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+"))@\S+'; |
| | | |
| | | foreach ($str as $key => $val) { |
| | | $name = ''; |
| | | $address = ''; |
| | | $val = trim($val); |
| | | |
| | | if (preg_match('/(.*)<('.$email_rx.')>$/', $val, $m)) { |
| | | $address = $m[2]; |
| | | $name = trim($m[1]); |
| | | } |
| | | else if (preg_match('/^('.$email_rx.')$/', $val, $m)) { |
| | | $address = $m[1]; |
| | | $name = ''; |
| | | } |
| | | else { |
| | | $name = $val; |
| | | } |
| | | |
| | | // dequote and/or decode name |
| | | if ($name) { |
| | | if ($name[0] == '"' && $name[strlen($name)-1] == '"') { |
| | | $name = substr($name, 1, -1); |
| | | $name = stripslashes($name); |
| | | } |
| | | if ($decode) { |
| | | $name = self::decode_header($name, $fallback); |
| | | } |
| | | } |
| | | |
| | | if (!$address && $name) { |
| | | $address = $name; |
| | | } |
| | | |
| | | if ($address) { |
| | | $result[$key] = array('name' => $name, 'address' => $address); |
| | | } |
| | | } |
| | | |
| | | return $result; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Explodes header (e.g. address-list) string into array of strings |
| | | * using specified separator characters with proper handling |
| | | * of quoted-strings and comments (RFC2822) |
| | | * |
| | | * @param string $separator String containing separator characters |
| | | * @param string $str Header string |
| | | * @param bool $remove_comments Enable to remove comments |
| | | * |
| | | * @return array Header items |
| | | */ |
| | | public static function explode_header_string($separator, $str, $remove_comments = false) |
| | | { |
| | | $length = strlen($str); |
| | | $result = array(); |
| | | $quoted = false; |
| | | $comment = 0; |
| | | $out = ''; |
| | | |
| | | for ($i=0; $i<$length; $i++) { |
| | | // we're inside a quoted string |
| | | if ($quoted) { |
| | | if ($str[$i] == '"') { |
| | | $quoted = false; |
| | | } |
| | | else if ($str[$i] == "\\") { |
| | | if ($comment <= 0) { |
| | | $out .= "\\"; |
| | | } |
| | | $i++; |
| | | } |
| | | } |
| | | // we are inside a comment string |
| | | else if ($comment > 0) { |
| | | if ($str[$i] == ')') { |
| | | $comment--; |
| | | } |
| | | else if ($str[$i] == '(') { |
| | | $comment++; |
| | | } |
| | | else if ($str[$i] == "\\") { |
| | | $i++; |
| | | } |
| | | continue; |
| | | } |
| | | // separator, add to result array |
| | | else if (strpos($separator, $str[$i]) !== false) { |
| | | if ($out) { |
| | | $result[] = $out; |
| | | } |
| | | $out = ''; |
| | | continue; |
| | | } |
| | | // start of quoted string |
| | | else if ($str[$i] == '"') { |
| | | $quoted = true; |
| | | } |
| | | // start of comment |
| | | else if ($remove_comments && $str[$i] == '(') { |
| | | $comment++; |
| | | } |
| | | |
| | | if ($comment <= 0) { |
| | | $out .= $str[$i]; |
| | | } |
| | | } |
| | | |
| | | if ($out && $comment <= 0) { |
| | | $result[] = $out; |
| | | } |
| | | |
| | | return $result; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Interpret a format=flowed message body according to RFC 2646 |
| | | * |
| | | * @param string $text Raw body formatted as flowed text |
| | | * |
| | | * @return string Interpreted text with unwrapped lines and stuffed space removed |
| | | */ |
| | | public static function unfold_flowed($text) |
| | | { |
| | | $text = preg_split('/\r?\n/', $text); |
| | | $last = -1; |
| | | $q_level = 0; |
| | | |
| | | foreach ($text as $idx => $line) { |
| | | if ($line[0] == '>' && preg_match('/^(>+\s*)/', $line, $regs)) { |
| | | $q = strlen(str_replace(' ', '', $regs[0])); |
| | | $line = substr($line, strlen($regs[0])); |
| | | |
| | | if ($q == $q_level && $line |
| | | && isset($text[$last]) |
| | | && $text[$last][strlen($text[$last])-1] == ' ' |
| | | ) { |
| | | $text[$last] .= $line; |
| | | unset($text[$idx]); |
| | | } |
| | | else { |
| | | $last = $idx; |
| | | } |
| | | } |
| | | else { |
| | | $q = 0; |
| | | if ($line == '-- ') { |
| | | $last = $idx; |
| | | } |
| | | else { |
| | | // remove space-stuffing |
| | | $line = preg_replace('/^\s/', '', $line); |
| | | |
| | | if (isset($text[$last]) && $line |
| | | && $text[$last] != '-- ' |
| | | && $text[$last][strlen($text[$last])-1] == ' ' |
| | | ) { |
| | | $text[$last] .= $line; |
| | | unset($text[$idx]); |
| | | } |
| | | else { |
| | | $text[$idx] = $line; |
| | | $last = $idx; |
| | | } |
| | | } |
| | | } |
| | | $q_level = $q; |
| | | } |
| | | |
| | | return implode("\r\n", $text); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Wrap the given text to comply with RFC 2646 |
| | | * |
| | | * @param string $text Text to wrap |
| | | * @param int $length Length |
| | | * |
| | | * @return string Wrapped text |
| | | */ |
| | | public static function format_flowed($text, $length = 72) |
| | | { |
| | | $text = preg_split('/\r?\n/', $text); |
| | | |
| | | foreach ($text as $idx => $line) { |
| | | if ($line != '-- ') { |
| | | if ($line[0] == '>' && preg_match('/^(>+)/', $line, $regs)) { |
| | | $prefix = $regs[0]; |
| | | $level = strlen($prefix); |
| | | $line = rtrim(substr($line, $level)); |
| | | $line = $prefix . rc_wordwrap($line, $length - $level - 2, " \r\n$prefix "); |
| | | } |
| | | else if ($line) { |
| | | $line = rc_wordwrap(rtrim($line), $length - 2, " \r\n"); |
| | | // space-stuffing |
| | | $line = preg_replace('/(^|\r\n)(From| |>)/', '\\1 \\2', $line); |
| | | } |
| | | |
| | | $text[$idx] = $line; |
| | | } |
| | | } |
| | | |
| | | return implode("\r\n", $text); |
| | | } |
| | | |
| | | } |
| | |
| | | |
| | | if (!empty($_POST['_address']) && is_object($CONTACTS)) |
| | | { |
| | | $contact_arr = $RCMAIL->imap->decode_address_list(get_input_value('_address', RCUBE_INPUT_POST, true), 1, false); |
| | | $contact_arr = rcube_mime::decode_address_list(get_input_value('_address', RCUBE_INPUT_POST, true), 1, false); |
| | | |
| | | if (!empty($contact_arr[1]['mailto'])) { |
| | | $contact = array( |
| | |
| | | // extract all recipients of the reply-message |
| | | if (is_object($MESSAGE->headers) && in_array($compose_mode, array(RCUBE_COMPOSE_REPLY, RCUBE_COMPOSE_FORWARD))) |
| | | { |
| | | $a_to = $RCMAIL->imap->decode_address_list($MESSAGE->headers->to); |
| | | $a_to = rcube_mime::decode_address_list($MESSAGE->headers->to, null, true, $MESSAGE->headers->charset); |
| | | foreach ($a_to as $addr) { |
| | | if (!empty($addr['mailto'])) { |
| | | $a_recipients[] = strtolower($addr['mailto']); |
| | |
| | | } |
| | | |
| | | if (!empty($MESSAGE->headers->cc)) { |
| | | $a_cc = $RCMAIL->imap->decode_address_list($MESSAGE->headers->cc); |
| | | $a_cc = rcube_mime::decode_address_list($MESSAGE->headers->cc, null, true, $MESSAGE->headers->charset); |
| | | foreach ($a_cc as $addr) { |
| | | if (!empty($addr['mailto'])) { |
| | | $a_recipients[] = strtolower($addr['mailto']); |
| | |
| | | |
| | | // split recipients and put them back together in a unique way |
| | | if (!empty($fvalue) && in_array($header, array('to', 'cc', 'bcc'))) { |
| | | $to_addresses = $RCMAIL->imap->decode_address_list($fvalue, null, $decode_header); |
| | | $to_addresses = rcube_mime::decode_address_list($fvalue, null, $decode_header, $MESSAGE->headers->charset); |
| | | $fvalue = array(); |
| | | |
| | | foreach ($to_addresses as $addr_part) { |
| | |
| | | if ($body && $part && $part->ctype_secondary == 'plain' |
| | | && $part->ctype_parameters['format'] == 'flowed' |
| | | ) { |
| | | $body = rcube_message::unfold_flowed($body); |
| | | $body = rcube_mime::unfold_flowed($body); |
| | | } |
| | | } |
| | | } |
| | |
| | | global $RCMAIL, $MESSAGE, $LINE_LENGTH; |
| | | |
| | | // build reply prefix |
| | | $from = array_pop($RCMAIL->imap->decode_address_list($MESSAGE->get_header('from'), 1, false)); |
| | | $from = array_pop(rcube_mime::decode_address_list($MESSAGE->get_header('from'), 1, false, $MESSAGE->headers->charset)); |
| | | $prefix = rcube_label(array( |
| | | 'name' => 'mailreplyintro', |
| | | 'vars' => array( |
| | |
| | | $a_msg_cols = array(); |
| | | $a_msg_flags = array(); |
| | | |
| | | $RCMAIL->imap->set_charset(!empty($header->charset) ? $header->charset : $CONFIG['default_charset']); |
| | | |
| | | // format each col; similar as in rcmail_message_list() |
| | | foreach ($a_show_cols as $col) { |
| | | if (in_array($col, array('from', 'to', 'cc', 'replyto'))) |
| | | $cont = Q(rcmail_address_string($header->$col, 3), 'show'); |
| | | $cont = Q(rcmail_address_string($header->$col, 3, false, null, $header->charset), 'show'); |
| | | else if ($col=='subject') { |
| | | $cont = trim($RCMAIL->imap->decode_header($header->$col)); |
| | | $cont = trim(rcube_mime::decode_header($header->$col, $header->charset)); |
| | | if (!$cont) $cont = rcube_label('nosubject'); |
| | | $cont = Q($cont); |
| | | } |
| | |
| | | } |
| | | else if ($hkey == 'replyto') { |
| | | if ($headers['replyto'] != $headers['from']) |
| | | $header_value = rcmail_address_string($value, null, true, $attrib['addicon']); |
| | | $header_value = rcmail_address_string($value, null, true, $attrib['addicon'], $headers['charset']); |
| | | else |
| | | continue; |
| | | } |
| | |
| | | if ($headers['mail-replyto'] != $headers['reply-to'] |
| | | && $headers['reply-to'] != $headers['from'] |
| | | ) |
| | | $header_value = rcmail_address_string($value, null, true, $attrib['addicon']); |
| | | $header_value = rcmail_address_string($value, null, true, $attrib['addicon'], $headers['charset']); |
| | | else |
| | | continue; |
| | | } |
| | | else if ($hkey == 'mail-followup-to') { |
| | | $header_value = rcmail_address_string($value, null, true, $attrib['addicon']); |
| | | $header_value = rcmail_address_string($value, null, true, $attrib['addicon'], $headers['charset']); |
| | | } |
| | | else if (in_array($hkey, array('from', 'to', 'cc', 'bcc'))) |
| | | $header_value = rcmail_address_string($value, null, true, $attrib['addicon']); |
| | | $header_value = rcmail_address_string($value, null, true, $attrib['addicon'], $headers['charset']); |
| | | else if ($hkey == 'subject' && empty($value)) |
| | | $header_value = rcube_label('nosubject'); |
| | | else |
| | | $header_value = trim($RCMAIL->imap->decode_header($value)); |
| | | $header_value = trim(rcube_mime::decode_header($value, $headers['charset'])); |
| | | |
| | | $output_headers[$hkey] = array( |
| | | 'title' => rcube_label(preg_replace('/(^mail-|-)/', '', $hkey)), |
| | |
| | | /** |
| | | * decode address string and re-format it as HTML links |
| | | */ |
| | | function rcmail_address_string($input, $max=null, $linked=false, $addicon=null) |
| | | function rcmail_address_string($input, $max=null, $linked=false, $addicon=null, $default_charset=null) |
| | | { |
| | | global $RCMAIL, $PRINT_MODE, $CONFIG; |
| | | |
| | | $a_parts = $RCMAIL->imap->decode_address_list($input); |
| | | $a_parts = rcube_mime::decode_address_list($input, null, true, $default_charset); |
| | | |
| | | if (!sizeof($a_parts)) |
| | | return $input; |
| | |
| | | { |
| | | $identity = $RCMAIL->user->get_identity(); |
| | | $sender = format_email_recipient($identity['email'], $identity['name']); |
| | | $recipient = array_shift($RCMAIL->imap->decode_address_list($message->headers->mdn_to)); |
| | | $recipient = array_shift(rcube_mime::decode_address_list( |
| | | $message->headers->mdn_to, 1, true, $message->headers->charset)); |
| | | $mailto = $recipient['mailto']; |
| | | |
| | | $compose = new Mail_mime("\r\n"); |
| | |
| | | |
| | | // compose format=flowed content if enabled |
| | | if ($flowed = $RCMAIL->config->get('send_format_flowed', true)) |
| | | $message_body = rcube_message::format_flowed($message_body, min($LINE_LENGTH+2, 79)); |
| | | $message_body = rcube_mime::format_flowed($message_body, min($LINE_LENGTH+2, 79)); |
| | | else |
| | | $message_body = rc_wordwrap($message_body, $LINE_LENGTH, "\r\n"); |
| | | |
| | |
| | | header("Content-Type: text/plain; charset={$charset}"); |
| | | |
| | | if (!empty($_GET['_save'])) { |
| | | $filename = ($headers->subject ? $RCMAIL->imap->decode_header($headers->subject) : 'roundcube') . '.eml'; |
| | | $subject = rcube_mime::decode_header($headers->subject, $headers->charset); |
| | | $filename = ($subject ? $subject : $RCMAIL->config->get('product_name', 'email')) . '.eml'; |
| | | $browser = $RCMAIL->output->browser; |
| | | |
| | | if ($browser->ie && $browser->ver < 7) |
| | |
| | | function __construct() |
| | | { |
| | | $this->UnitTestCase('Mail headers decoding tests'); |
| | | |
| | | $this->app = rcmail::get_instance(); |
| | | $this->app->imap_init(false); |
| | | } |
| | | |
| | | /** |
| | | * Test decoding of single e-mail address strings |
| | | * Uses rcube_imap::decode_address_list() |
| | | * Uses rcube_mime::decode_address_list() |
| | | */ |
| | | function test_decode_single_address() |
| | | { |
| | |
| | | ); |
| | | |
| | | foreach ($headers as $idx => $header) { |
| | | $res = $this->app->imap->decode_address_list($header); |
| | | $res = rcube_mime::decode_address_list($header); |
| | | |
| | | $this->assertEqual($results[$idx][0], count($res), "Rows number in result for header: " . $header); |
| | | $this->assertEqual($results[$idx][1], $res[1]['name'], "Name part decoding for header: " . $header); |
| | |
| | | |
| | | /** |
| | | * Test decoding of header values |
| | | * Uses rcube_imap::decode_mime_string() |
| | | * Uses rcube_mime::decode_mime_string() |
| | | */ |
| | | function test_header_decode_qp() |
| | | { |
| | |
| | | ); |
| | | |
| | | foreach ($test as $idx => $item) { |
| | | $res = $this->app->imap->decode_mime_string($item['in'], 'UTF-8'); |
| | | $res = rcube_mime::decode_mime_string($item['in'], 'UTF-8'); |
| | | $res = quoted_printable_encode($res); |
| | | |
| | | $this->assertEqual($item['out'], $res, "Header decoding for: " . $idx); |