From 21b160f38c98bf41ebc843e7639b5b1af588b489 Mon Sep 17 00:00:00 2001 From: thomascube <thomas@roundcube.net> Date: Sun, 08 Feb 2009 15:38:54 -0500 Subject: [PATCH] Added TNEF support to decode MS Outlook (winmail.dat) attachments --- program/include/rcube_imap.php | 195 ++++++++++++++++++++++++++++++++---------------- 1 files changed, 129 insertions(+), 66 deletions(-) diff --git a/program/include/rcube_imap.php b/program/include/rcube_imap.php index 9b50702..0e2dd6a 100644 --- a/program/include/rcube_imap.php +++ b/program/include/rcube_imap.php @@ -5,7 +5,7 @@ | program/include/rcube_imap.php | | | | This file is part of the RoundCube Webmail client | - | Copyright (C) 2005-2008, RoundCube Dev. - Switzerland | + | Copyright (C) 2005-2009, RoundCube Dev. - Switzerland | | Licensed under the GNU GPL | | | | PURPOSE: | @@ -26,6 +26,7 @@ */ require_once('lib/imap.inc'); require_once('lib/mime.inc'); +require_once('lib/tnef_decoder.inc'); /** @@ -66,6 +67,7 @@ var $search_sort_field = ''; var $debug_level = 1; var $error_code = 0; + var $options = array('imap' => 'check'); /** @@ -90,7 +92,7 @@ * @return boolean TRUE on success, FALSE on failure * @access public */ - function connect($host, $user, $pass, $port=143, $use_ssl=null, $auth_type=null) + function connect($host, $user, $pass, $port=143, $use_ssl=null) { global $ICL_SSL, $ICL_PORT, $IMAP_USE_INTERNAL_DATE; @@ -107,7 +109,7 @@ $ICL_PORT = $port; $IMAP_USE_INTERNAL_DATE = false; - $this->conn = iil_Connect($host, $user, $pass, array('imap' => $auth_type ? $auth_type : 'check')); + $this->conn = iil_Connect($host, $user, $pass, $this->options); $this->host = $host; $this->user = $user; $this->pass = $pass; @@ -172,6 +174,13 @@ iil_C_Select($this->conn, $this->mailbox); } + /** + * Set options to be used in iil_Connect() + */ + function set_options($opt) + { + $this->options = array_merge($this->options, (array)$opt); + } /** * Set a root folder for the IMAP connection. @@ -188,6 +197,7 @@ $root = substr($root, 0, -1); $this->root_dir = $root; + $this->options['rootdir'] = $root; if (empty($this->delimiter)) $this->get_hierarchy_delimiter(); @@ -294,7 +304,7 @@ $msgs = split(',', $msgs); $this->search_string = $str; - $this->search_set = (array)$msgs; + $this->search_set = $msgs; $this->search_charset = $charset; $this->search_sort_field = $sort_field; } @@ -345,8 +355,9 @@ */ function check_permflag($flag) { - $flagsmap = $GLOBALS['IMAP_FLAGS']; - return (($imap_flag = $flagsmap[strtoupper($flag)]) && in_array_nocase($imap_flag, $this->conn->permanentflags)); + $flag = strtoupper($flag); + $imap_flag = $GLOBALS['IMAP_FLAGS'][$flag]; + return (in_array_nocase($imap_flag, $this->conn->permanentflags)); } @@ -676,9 +687,9 @@ $cnt = count($msgs); if ($cnt > 300 && $cnt > $this->page_size) { // experimantal value for best result // use memory less expensive (and quick) method for big result set - $a_index = $this->message_index($mailbox, $this->sort_field, $this->sort_order); + $a_index = $this->message_index('', $this->sort_field, $this->sort_order); // get messages uids for one page... - $msgs = array_slice(array_keys($a_index), $start_msg, min($cnt-$start_msg, $this->page_size)); + $msgs = array_slice($a_index, $start_msg, min($cnt-$start_msg, $this->page_size)); // ...and fetch headers $this->_fetch_headers($mailbox, join(',', $msgs), $a_msg_headers, NULL); @@ -811,7 +822,7 @@ // we have a saved search result. get index from there if (!isset($this->cache[$key]) && $this->search_string && $mailbox == $this->mailbox) { - $this->cache[$key] = $a_msg_headers = array(); + $this->cache[$key] = array(); if ($this->get_capability('sort')) { @@ -832,7 +843,7 @@ else if ($this->sort_order=="DESC") arsort($a_index); - $this->cache[$key] = $a_index; + $this->cache[$key] = array_keys($a_index); } } @@ -848,9 +859,8 @@ if ($cache_status>0) { $a_index = $this->get_message_cache_index($cache_key, TRUE, $this->sort_field, $this->sort_order); - return array_values($a_index); + return array_keys($a_index); } - // fetch complete message index $msg_count = $this->_messagecount($mailbox); @@ -870,7 +880,7 @@ else if ($this->sort_order=="DESC") arsort($a_index); - $this->cache[$key] = $a_index; + $this->cache[$key] = array_keys($a_index); } return $this->cache[$key]; @@ -1039,9 +1049,10 @@ * @param int Message ID * @param string Mailbox to read from * @param boolean True if $id is the message UID + * @param boolean True if we need also BODYSTRUCTURE in headers * @return object Message headers representation */ - function get_headers($id, $mbox_name=NULL, $is_uid=TRUE) + function get_headers($id, $mbox_name=NULL, $is_uid=TRUE, $bodystr=FALSE) { $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox; $uid = $is_uid ? $id : $this->_id2uid($id); @@ -1050,7 +1061,7 @@ if ($uid && ($headers = &$this->get_cached_message($mailbox.'.msg', $uid))) return $headers; - $headers = iil_C_FetchHeader($this->conn, $mailbox, $id, $is_uid); + $headers = iil_C_FetchHeader($this->conn, $mailbox, $id, $is_uid, $bodystr); // write headers cache if ($headers) @@ -1070,9 +1081,10 @@ * an object structure similar to the one generated by PEAR::Mail_mimeDecode * * @param int Message UID to fetch + * @param string Message BODYSTRUCTURE string (optional) * @return object rcube_message_part Message part tree or False on failure */ - function &get_structure($uid) + function &get_structure($uid, $structure_str='') { $cache_key = $this->mailbox.'.msg'; $headers = &$this->get_cached_message($cache_key, $uid, true); @@ -1087,7 +1099,8 @@ return FALSE; } - $structure_str = iil_C_FetchStructureString($this->conn, $this->mailbox, $msg_id); + if (!$structure_str) + $structure_str = iil_C_FetchStructureString($this->conn, $this->mailbox, $msg_id); $structure = iml_GetRawStructureArray($structure_str); $struct = false; @@ -1122,7 +1135,7 @@ * * @access private */ - function &_structure_part($part, $count=0, $parent='') + function &_structure_part($part, $count=0, $parent='', $raw_headers=null) { $struct = new rcube_message_part; $struct->mime_id = empty($parent) ? (string)$count : "$parent.$count"; @@ -1142,11 +1155,25 @@ $struct->mimetype = 'multipart/'.$struct->ctype_secondary; + // build parts list for headers pre-fetching + for ($i=0, $count=0; $i<count($part); $i++) + if (is_array($part[$i]) && count($part[$i]) > 3) + // fetch message headers if message/rfc822 or named part (could contain Content-Location header) + if (strtolower($part[$i][0]) == 'message' || + (in_array('name', (array)$part[$i][2]) && (empty($part[$i][3]) || $part[$i][3]=='NIL'))) { + $part_headers[] = $struct->mime_id ? $struct->mime_id.'.'.$i+1 : $i+1; + } + + // pre-fetch headers of all parts (in one command for better performance) + if ($part_headers) + $part_headers = iil_C_FetchMIMEHeaders($this->conn, $this->mailbox, $this->_msg_id, $part_headers); + $struct->parts = array(); for ($i=0, $count=0; $i<count($part); $i++) if (is_array($part[$i]) && count($part[$i]) > 3) - $struct->parts[] = $this->_structure_part($part[$i], ++$count, $struct->mime_id); - + $struct->parts[] = $this->_structure_part($part[$i], ++$count, $struct->mime_id, + $part_headers[$struct->mime_id ? $struck->mime_id.'.'.$i+1 : $i+1]); + return $struct; } @@ -1211,8 +1238,9 @@ // fetch message headers if message/rfc822 or named part (could contain Content-Location header) if ($struct->ctype_primary == 'message' || ($struct->ctype_parameters['name'] && !$struct->content_id)) { - $part_headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, $struct->mime_id); - $struct->headers = $this->_parse_headers($part_headers) + $struct->headers; + if (empty($raw_headers)) + $raw_headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, $struct->mime_id); + $struct->headers = $this->_parse_headers($raw_headers) + $struct->headers; } if ($struct->ctype_primary=='message') { @@ -1221,7 +1249,7 @@ } // normalize filename property - $this->_set_part_filename($struct); + $this->_set_part_filename($struct, $raw_headers); return $struct; } @@ -1232,8 +1260,9 @@ * * @access private * @param object rcube_message_part Part object + * @param string Part's raw headers */ - function _set_part_filename(&$part) + function _set_part_filename(&$part, $headers=null) { if (!empty($part->d_parameters['filename'])) $filename_mime = $part->d_parameters['filename']; @@ -1251,10 +1280,9 @@ } // some servers (eg. dovecot-1.x) have no support for parameter value continuations // we must fetch and parse headers "manually" - //TODO: fetching headers for a second time is not effecient, this code should be moved somewhere earlier --tensor if ($i<2) { - // TODO: fetch only Content-Type/Content-Disposition header - $headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, $part->mime_id); + if (!$headers) + $headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, $part->mime_id); $filename_mime = ''; $i = 0; while (preg_match('/filename\*'.$i.'\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) { @@ -1270,7 +1298,8 @@ $i++; } if ($i<2) { - $headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, $part->mime_id); + if (!$headers) + $headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, $part->mime_id); $filename_encoded = ''; $i = 0; $matches = array(); while (preg_match('/filename\*'.$i.'\*\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) { @@ -1286,7 +1315,8 @@ $i++; } if ($i<2) { - $headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, $part->mime_id); + if (!$headers) + $headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, $part->mime_id); $filename_mime = ''; $i = 0; $matches = array(); while (preg_match('/\s+name\*'.$i.'\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) { @@ -1302,7 +1332,8 @@ $i++; } if ($i<2) { - $headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, $part->mime_id); + if (!$headers) + $headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, $part->mime_id); $filename_encoded = ''; $i = 0; $matches = array(); while (preg_match('/\s+name\*'.$i.'\*\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) { @@ -1364,6 +1395,8 @@ } // TODO: Add caching for message parts + + if (!$part) $part = 'TEXT'; if ($print) { @@ -1436,10 +1469,7 @@ if (!($msg_id = $this->_uid2id($uid))) return FALSE; - $body = iil_C_FetchPartHeader($this->conn, $this->mailbox, $msg_id, NULL); - $body .= iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id, NULL, 1); - - return $body; + return iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id); } @@ -1470,8 +1500,6 @@ if (!($msg_id = $this->_uid2id($uid))) return FALSE; - print iil_C_FetchPartHeader($this->conn, $this->mailbox, $msg_id, NULL); - flush(); iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id, NULL, 2); } @@ -1545,7 +1573,6 @@ */ function save_message($mbox_name, &$message) { - $mbox_name = stripslashes($mbox_name); $mailbox = $this->_mod_mailbox($mbox_name); // make sure mailbox exists @@ -1572,13 +1599,11 @@ */ function move_message($uids, $to_mbox, $from_mbox='') { - $to_mbox_in = stripslashes($to_mbox); - $from_mbox = stripslashes($from_mbox); - $to_mbox = $this->_mod_mailbox($to_mbox_in); + $to_mbox = $this->_mod_mailbox($to_mbox); $from_mbox = $from_mbox ? $this->_mod_mailbox($from_mbox) : $this->mailbox; // make sure mailbox exists - if (!in_array($to_mbox, $this->_list_mailboxes())) + if ($to_mbox != 'INBOX' && !in_array($to_mbox, $this->_list_mailboxes())) { if (in_array($to_mbox_in, $this->default_folders)) $this->create_mailbox($to_mbox_in, TRUE); @@ -1649,7 +1674,6 @@ */ function delete_message($uids, $mbox_name='') { - $mbox_name = stripslashes($mbox_name); $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox; // convert the list of uids to array @@ -1706,7 +1730,6 @@ */ function clear_mailbox($mbox_name=NULL) { - $mbox_name = stripslashes($mbox_name); $mailbox = !empty($mbox_name) ? $this->_mod_mailbox($mbox_name) : $this->mailbox; $msg_count = $this->_messagecount($mailbox, 'ALL'); @@ -1739,7 +1762,6 @@ */ function expunge($mbox_name='', $clear_cache=TRUE) { - $mbox_name = stripslashes($mbox_name); $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox; return $this->_expunge($mailbox, $clear_cache); } @@ -1858,9 +1880,6 @@ { $result = FALSE; - // replace backslashes - $name = preg_replace('/[\\\]+/', '-', $name); - // reduce mailbox name to 100 chars $name = substr($name, 0, 100); @@ -1889,9 +1908,6 @@ { $result = FALSE; - // replace backslashes - $name = preg_replace('/[\\\]+/', '-', $new_name); - // encode mailbox name and reduce it to 100 chars $name = substr($new_name, 0, 100); @@ -2149,8 +2165,7 @@ { $this->db->query( "UPDATE ".get_table_name('cache')." - SET created=".$this->db->now().", - data=? + SET created=". $this->db->now().", data=? WHERE user_id=? AND cache_key=?", $data, @@ -2337,7 +2352,7 @@ { if (empty($key) || !is_object($headers) || empty($headers->uid)) return; - + // add to internal (fast) cache $this->cache['__single_msg'][$headers->uid] = $headers; $this->cache['__single_msg'][$headers->uid]->structure = $struct; @@ -2478,6 +2493,40 @@ return $out; } + + + /** + * Decode a Microsoft Outlook TNEF part (winmail.dat) + * + * @param object rcube_message_part Message part to decode + * @param string UID of the message + * @return array List of rcube_message_parts extracted from windmail.dat + */ + function tnef_decode(&$part, $uid) + { + if (!isset($part->body)) + $part->body = $this->get_message_part($uid, $part->mime_id, $part); + + $pid = 0; + $tnef_parts = array(); + $tnef_arr = tnef_decode($part->body); + foreach ($tnef_arr as $winatt) { + $tpart = new rcube_message_part; + $tpart->filename = $winatt["name"]; + $tpart->encoding = 'stream'; + $tpart->ctype_primary = $winatt["type0"]; + $tpart->ctype_secondary = $winatt["type1"]; + $tpart->mimetype = strtolower($winatt["type0"] . "/" . $winatt["type1"]); + $tpart->mime_id = "winmail." . $part->mime_id . ".$pid"; + $tpart->size = $winatt["size"]; + $tpart->body = $winatt['stream']; + + $tnef_parts[] = $tpart; + $pid++; + } + + return $tnef_parts; + } /** @@ -2715,31 +2764,42 @@ $folders[$folder] = rc_strtolower(rcube_charset_convert($folder, 'UTF-7')); } + // sort folders and place defaults on the top asort($folders, SORT_LOCALE_STRING); ksort($a_defaults); - $folders = array_merge($a_defaults, array_keys($folders)); // finally we must rebuild the list to move // subfolders of default folders to their place... // ...also do this for the rest of folders because // asort() is not properly sorting case sensitive names - - // set the type of folder name variable (#1485527) while (list($key, $folder) = each($folders)) { + // set the type of folder name variable (#1485527) $a_out[] = (string) $folder; unset($folders[$key]); - foreach ($folders as $idx => $f) { - if (strpos($f, $folder.$delimiter) === 0) { - $a_out[] = (string) $f; - unset($folders[$idx]); - } - } - reset($folders); + $this->_rsort($folder, $delimiter, $folders, $a_out); } return $a_out; } + + + /** + * @access private + */ + function _rsort($folder, $delimiter, &$list, &$out) + { + while (list($key, $name) = each($list)) { + if (strpos($name, $folder.$delimiter) === 0) { + // set the type of folder name variable (#1485527) + $out[] = (string) $name; + unset($list[$key]); + $this->_rsort($name, $delimiter, $list, $out); + } + } + reset($list); + } + /** * @access private @@ -2900,7 +2960,7 @@ // remove any newlines and carriage returns before $a = $this->_explode_quoted_string('[,;]', preg_replace( "/[\r\n]/", " ", $str)); $result = array(); - + foreach ($a as $key => $val) { $val = preg_replace("/([\"\w])</", "$1 <", $val); @@ -2909,14 +2969,17 @@ foreach ($sub_a as $k => $v) { - if (strpos($v, '@') > 0) - $result[$key]['address'] = str_replace('<', '', str_replace('>', '', $v)); + // use angle brackets in regexp to not handle names with @ sign + if (preg_match('/^<\S+@\S+>$/', $v)) + $result[$key]['address'] = trim($v, '<>'); else $result[$key]['name'] .= (empty($result[$key]['name'])?'':' ').str_replace("\"",'',stripslashes($v)); } if (empty($result[$key]['name'])) $result[$key]['name'] = $result[$key]['address']; + elseif (empty($result[$key]['address'])) + $result[$key]['address'] = $result[$key]['name']; } return $result; -- Gitblit v1.9.1