Changeset d311d80 in github


Ignore:
Timestamp:
May 28, 2010 5:38:41 AM (3 years ago)
Author:
alecpl <alec@…>
Branches:
master, HEAD, courier-fix, dev-browser-capabilities, pdo, release-0.6, release-0.7, release-0.8
Children:
b231f68b
Parents:
89e31be
Message:
  • Fix forwarding of messages with winmail attachments
  • Remove some redundant code for winmail handling in get.inc, move tnef_decode() to rcube_message
  • Fix handling of uuencoded attachments in message body (#1485839)
  • Extend rc_mime_content_type() to work with string buffer
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • CHANGELOG

    r6f4e7db rd311d80  
    22=========================== 
    33 
     4- Fix forwarding of messages with winmail attachments 
     5- Fix handling of uuencoded attachments in message body (#1485839) 
    46- Added list_mailboxes hook in rcube_imap::list_unsubscribed() (#1486668) 
    57- Fix wrong message on file upload error (#1486725) 
  • program/include/rcube_imap.php

    r6f4e7db rd311d80  
    473473            if ($mode == 'UNSEEN') 
    474474                $search_str .= " UNSEEN"; 
    475  
    476475            // get message count using SEARCH 
    477476            // not very performant but more precise (using UNDELETED) 
     
    20352034 
    20362035        // convert charset (if text or message part) 
    2037         if ($o_part->ctype_primary=='text' || $o_part->ctype_primary=='message') { 
     2036        if ($o_part->ctype_primary == 'text' || $o_part->ctype_primary == 'message') { 
    20382037            // assume default if no charset specified 
    20392038            if (empty($o_part->charset) || strtolower($o_part->charset) == 'us-ascii') 
     
    33843383 
    33853384    /** 
    3386      * Decode a Microsoft Outlook TNEF part (winmail.dat) 
    3387      * 
    3388      * @param object rcube_message_part Message part to decode 
    3389      * @param string UID of the message 
    3390      * @return array List of rcube_message_parts extracted from windmail.dat 
    3391      */ 
    3392     function tnef_decode(&$part, $uid) 
    3393     { 
    3394         if (!isset($part->body)) 
    3395             $part->body = $this->get_message_part($uid, $part->mime_id, $part); 
    3396  
    3397         require_once('lib/tnef_decoder.inc'); 
    3398  
    3399         $pid = 0; 
    3400         $tnef_parts = array(); 
    3401         $tnef_arr = tnef_decode($part->body); 
    3402  
    3403         foreach ($tnef_arr as $winatt) { 
    3404             $tpart = new rcube_message_part; 
    3405             $tpart->filename = trim($winatt['name']); 
    3406             $tpart->encoding = 'stream'; 
    3407             $tpart->ctype_primary = trim(strtolower($winatt['type0'])); 
    3408             $tpart->ctype_secondary = trim(strtolower($winatt['type1'])); 
    3409             $tpart->mimetype = $tpart->ctype_primary . '/' . $tpart->ctype_secondary; 
    3410             $tpart->mime_id = "winmail." . $part->mime_id . ".$pid"; 
    3411             $tpart->size = $winatt['size']; 
    3412             $tpart->body = $winatt['stream']; 
    3413  
    3414             $tnef_parts[] = $tpart; 
    3415             $pid++; 
    3416         } 
    3417  
    3418         return $tnef_parts; 
    3419     } 
    3420  
    3421  
    3422     /** 
    34233385     * Decode a message header value 
    34243386     * 
  • program/include/rcube_message.php

    r6b6f2e8 rd311d80  
    3030class rcube_message 
    3131{ 
    32   private $app; 
    33   private $imap; 
    34   private $opt = array(); 
    35   private $inline_parts = array(); 
    36   private $parse_alternative = false; 
     32    private $app; 
     33    private $imap; 
     34    private $opt = array(); 
     35    private $inline_parts = array(); 
     36    private $parse_alternative = false; 
    3737   
    38   public $uid = null; 
    39   public $headers; 
    40   public $structure; 
    41   public $parts = array(); 
    42   public $mime_parts = array(); 
    43   public $attachments = array(); 
    44   public $subject = ''; 
    45   public $sender = null; 
    46   public $is_safe = false; 
     38    public $uid = null; 
     39    public $headers; 
     40    public $structure; 
     41    public $parts = array(); 
     42    public $mime_parts = array(); 
     43    public $attachments = array(); 
     44    public $subject = ''; 
     45    public $sender = null; 
     46    public $is_safe = false; 
    4747   
    4848 
    49   /** 
    50    * __construct 
    51    * 
    52    * Provide a uid, and parse message structure. 
    53    * 
    54    * @param string $uid The message UID. 
    55    * 
    56    * @uses rcmail::get_instance() 
    57    * @uses rcube_imap::decode_mime_string() 
    58    * @uses self::set_safe() 
    59    * 
    60    * @see self::$app, self::$imap, self::$opt, self::$structure 
    61    */ 
    62   function __construct($uid) 
    63   { 
    64     $this->app = rcmail::get_instance(); 
    65     $this->imap = $this->app->imap; 
     49    /** 
     50     * __construct 
     51     * 
     52     * Provide a uid, and parse message structure. 
     53     * 
     54     * @param string $uid The message UID. 
     55     * 
     56     * @uses rcmail::get_instance() 
     57     * @uses rcube_imap::decode_mime_string() 
     58     * @uses self::set_safe() 
     59     * 
     60     * @see self::$app, self::$imap, self::$opt, self::$structure 
     61     */ 
     62    function __construct($uid) 
     63    { 
     64        $this->app = rcmail::get_instance(); 
     65        $this->imap = $this->app->imap; 
    6666     
    67     $this->uid = $uid; 
    68     $this->headers = $this->imap->get_headers($uid, NULL, true, true); 
    69  
    70     $this->subject = rcube_imap::decode_mime_string($this->headers->subject, $this->headers->charset); 
    71     list(, $this->sender) = each($this->imap->decode_address_list($this->headers->from)); 
     67        $this->uid = $uid; 
     68        $this->headers = $this->imap->get_headers($uid, NULL, true, true); 
     69 
     70        $this->subject = rcube_imap::decode_mime_string( 
     71            $this->headers->subject, $this->headers->charset); 
     72        list(, $this->sender) = each($this->imap->decode_address_list($this->headers->from)); 
    7273     
    73     $this->set_safe((intval($_GET['_safe']) || $_SESSION['safe_messages'][$uid])); 
    74     $this->opt = array( 
    75       'safe' => $this->is_safe, 
    76       'prefer_html' => $this->app->config->get('prefer_html'), 
    77       'get_url' => rcmail_url('get', array('_mbox' => $this->imap->get_mailbox_name(), '_uid' => $uid)) 
    78     ); 
    79  
    80     if ($this->structure = $this->imap->get_structure($uid, $this->headers->body_structure)) { 
    81       $this->get_mime_numbers($this->structure); 
    82       $this->parse_structure($this->structure); 
    83     } 
    84     else { 
    85       $this->body = $this->imap->get_body($uid); 
    86     } 
    87      
    88     // notify plugins and let them analyze this structured message object 
    89     $this->app->plugins->exec_hook('message_load', array('object' => $this)); 
    90   } 
     74        $this->set_safe((intval($_GET['_safe']) || $_SESSION['safe_messages'][$uid])); 
     75        $this->opt = array( 
     76            'safe' => $this->is_safe, 
     77            'prefer_html' => $this->app->config->get('prefer_html'), 
     78            'get_url' => rcmail_url('get', array( 
     79                '_mbox' => $this->imap->get_mailbox_name(), '_uid' => $uid)) 
     80        ); 
     81 
     82        if ($this->structure = $this->imap->get_structure($uid, $this->headers->body_structure)) { 
     83            $this->get_mime_numbers($this->structure); 
     84            $this->parse_structure($this->structure); 
     85        } 
     86        else { 
     87            $this->body = $this->imap->get_body($uid); 
     88        } 
     89 
     90        // notify plugins and let them analyze this structured message object 
     91        $this->app->plugins->exec_hook('message_load', array('object' => $this)); 
     92    } 
    9193   
    9294   
    93   /** 
    94    * Return a (decoded) message header 
    95    * 
    96    * @param string Header name 
    97    * @param bool   Don't mime-decode the value 
    98    * @return string Header value 
    99    */ 
    100   public function get_header($name, $raw = false) 
    101   { 
    102     $value = $this->headers->$name; 
    103     return $raw ? $value : $this->imap->decode_header($value); 
    104   } 
     95    /** 
     96     * Return a (decoded) message header 
     97     * 
     98     * @param string Header name 
     99     * @param bool   Don't mime-decode the value 
     100     * @return string Header value 
     101     */ 
     102    public function get_header($name, $raw = false) 
     103    { 
     104        $value = $this->headers->$name; 
     105        return $raw ? $value : $this->imap->decode_header($value); 
     106    } 
     107 
    105108   
    106   /** 
    107    * Set is_safe var and session data 
    108    * 
    109    * @param bool enable/disable 
    110    */ 
    111   public function set_safe($safe = true) 
    112   { 
    113     $this->is_safe = $safe; 
    114     $_SESSION['safe_messages'][$this->uid] = $this->is_safe; 
    115   } 
    116    
    117   /** 
    118    * Compose a valid URL for getting a message part 
    119    * 
    120    * @param string Part MIME-ID 
    121    * @return string URL or false if part does not exist 
    122    */ 
    123   public function get_part_url($mime_id) 
    124   { 
    125     if ($this->mime_parts[$mime_id]) 
    126       return $this->opt['get_url'] . "&_part=" . $mime_id; 
    127     else 
    128       return false; 
    129   } 
    130    
    131    
    132   /** 
    133    * Get content of a specific part of this message 
    134    * 
    135    * @param string Part MIME-ID 
    136    * @param resource File pointer to save the message part 
    137    * @return string Part content 
    138    */ 
    139   public function get_part_content($mime_id, $fp=NULL) 
    140   { 
    141     if ($part = $this->mime_parts[$mime_id]) 
    142       return $this->imap->get_message_part($this->uid, $mime_id, $part, NULL, $fp); 
    143     else 
    144       return null; 
    145   } 
    146    
    147    
    148   /** 
    149    * Determine if the message contains a HTML part 
    150    * 
    151    * @return bool True if a HTML is available, False if not 
    152    */ 
    153   function has_html_part() 
    154   { 
    155      // check all message parts 
    156      foreach ($this->parts as $pid => $part) { 
    157         $mimetype = strtolower($part->ctype_primary . '/' . $part->ctype_secondary); 
    158         if ($mimetype == 'text/html') 
    159            return true; 
    160      } 
    161  
    162      return false; 
    163   } 
    164  
    165   /** 
    166    * Return the first HTML part of this message 
    167    * 
    168    * @return string HTML message part content 
    169    */ 
    170   function first_html_part() 
    171     { 
    172     // check all message parts 
    173     foreach ($this->mime_parts as $mime_id => $part) { 
    174       $mimetype = strtolower($part->ctype_primary . '/' . $part->ctype_secondary); 
    175       if ($mimetype == 'text/html') { 
    176         return $this->imap->get_message_part($this->uid, $mime_id, $part); 
    177       } 
    178     } 
    179   } 
    180  
    181  
    182   /** 
    183    * Return the first text part of this message 
    184    * 
    185    * @return string Plain text message/part content 
    186    */ 
    187   function first_text_part() 
    188     { 
    189     // no message structure, return complete body 
    190     if (empty($this->parts)) 
    191       return $this->body; 
     109    /** 
     110     * Set is_safe var and session data 
     111     * 
     112     * @param bool enable/disable 
     113     */ 
     114    public function set_safe($safe = true) 
     115    { 
     116        $this->is_safe = $safe; 
     117        $_SESSION['safe_messages'][$this->uid] = $this->is_safe; 
     118    } 
     119 
     120 
     121    /** 
     122     * Compose a valid URL for getting a message part 
     123     * 
     124     * @param string Part MIME-ID 
     125     * @return string URL or false if part does not exist 
     126     */ 
     127    public function get_part_url($mime_id) 
     128    { 
     129        if ($this->mime_parts[$mime_id]) 
     130            return $this->opt['get_url'] . '&_part=' . $mime_id; 
     131        else 
     132            return false; 
     133    } 
     134 
     135 
     136    /** 
     137     * Get content of a specific part of this message 
     138     * 
     139     * @param string Part MIME-ID 
     140     * @param resource File pointer to save the message part 
     141     * @return string Part content 
     142     */ 
     143    public function get_part_content($mime_id, $fp=NULL) 
     144    { 
     145        if ($part = $this->mime_parts[$mime_id]) { 
     146            // stored in message structure (winmail/inline-uuencode) 
     147            if ($part->encoding == 'stream') { 
     148                if ($fp) { 
     149                    fwrite($fp, $part->body); 
     150                } 
     151                return $fp ? true : $part->body; 
     152            } 
     153            // get from IMAP 
     154            return $this->imap->get_message_part($this->uid, $mime_id, $part, NULL, $fp); 
     155        } else 
     156            return null; 
     157    } 
     158 
     159 
     160    /** 
     161     * Determine if the message contains a HTML part 
     162     * 
     163     * @return bool True if a HTML is available, False if not 
     164     */ 
     165    function has_html_part() 
     166    { 
     167        // check all message parts 
     168        foreach ($this->parts as $pid => $part) { 
     169            $mimetype = strtolower($part->ctype_primary . '/' . $part->ctype_secondary); 
     170            if ($mimetype == 'text/html') 
     171                return true; 
     172        } 
     173 
     174        return false; 
     175    } 
     176 
     177 
     178    /** 
     179     * Return the first HTML part of this message 
     180     * 
     181     * @return string HTML message part content 
     182     */ 
     183    function first_html_part() 
     184    { 
     185        // check all message parts 
     186        foreach ($this->mime_parts as $mime_id => $part) { 
     187            $mimetype = strtolower($part->ctype_primary . '/' . $part->ctype_secondary); 
     188            if ($mimetype == 'text/html') { 
     189                return $this->imap->get_message_part($this->uid, $mime_id, $part); 
     190            } 
     191        } 
     192    } 
     193 
     194 
     195    /** 
     196     * Return the first text part of this message 
     197     * 
     198     * @return string Plain text message/part content 
     199     */ 
     200    function first_text_part() 
     201    { 
     202        // no message structure, return complete body 
     203        if (empty($this->parts)) 
     204            return $this->body; 
     205 
     206        $out = null; 
     207 
     208        // check all message parts 
     209        foreach ($this->mime_parts as $mime_id => $part) { 
     210            $mimetype = $part->ctype_primary . '/' . $part->ctype_secondary; 
     211 
     212            if ($mimetype == 'text/plain') { 
     213                $out = $this->imap->get_message_part($this->uid, $mime_id, $part); 
     214         
     215                // re-format format=flowed content 
     216                if ($part->ctype_secondary == 'plain' && $part->ctype_parameters['format'] == 'flowed') 
     217                    $out = self::unfold_flowed($out); 
     218                break; 
     219            } 
     220            else if ($mimetype == 'text/html') { 
     221                $html_part = $this->imap->get_message_part($this->uid, $mime_id, $part); 
     222 
     223                // remove special chars encoding 
     224                $trans = array_flip(get_html_translation_table(HTML_ENTITIES)); 
     225                $html_part = strtr($html_part, $trans); 
     226 
     227                // create instance of html2text class 
     228                $txt = new html2text($html_part); 
     229                $out = $txt->get_text(); 
     230                break; 
     231            } 
     232        } 
     233 
     234        return $out; 
     235    } 
     236 
     237 
     238    /** 
     239     * Raad the message structure returend by the IMAP server 
     240     * and build flat lists of content parts and attachments 
     241     * 
     242     * @param object rcube_message_part Message structure node 
     243     * @param bool  True when called recursively 
     244     */ 
     245    private function parse_structure($structure, $recursive = false) 
     246    { 
     247        $message_ctype_primary = $structure->ctype_primary; 
     248        $message_ctype_secondary = $structure->ctype_secondary; 
     249        $mimetype = $structure->mimetype; 
     250 
     251        // real content-type of message/rfc822 part 
     252        if ($mimetype == 'message/rfc822') { 
     253            if ($structure->real_mimetype) { 
     254                $mimetype = $structure->real_mimetype; 
     255                list($message_ctype_primary, $message_ctype_secondary) = explode('/', $mimetype); 
     256            } 
     257        } 
     258 
     259        // show message headers 
     260        if ($recursive && is_array($structure->headers) && isset($structure->headers['subject'])) { 
     261            $c = new stdClass; 
     262            $c->type = 'headers'; 
     263            $c->headers = &$structure->headers; 
     264            $this->parts[] = $c; 
     265        } 
     266 
     267        // print body if message doesn't have multiple parts 
     268        if ($message_ctype_primary == 'text' && !$recursive) { 
     269            $structure->type = 'content'; 
     270            $this->parts[] = &$structure; 
     271             
     272            // Parse simple (plain text) message body 
     273            if ($message_ctype_secondary == 'plain') 
     274                foreach ((array)$this->uu_decode($structure) as $uupart) { 
     275                    $this->mime_parts[$uupart->mime_id] = $uupart; 
     276                    $this->attachments[] = $uupart; 
     277                } 
     278 
     279            // @TODO: plugin hook? 
     280        } 
     281        // the same for pgp signed messages 
     282        else if ($mimetype == 'application/pgp' && !$recursive) { 
     283            $structure->type = 'content'; 
     284            $this->parts[] = &$structure; 
     285        } 
     286        // message contains alternative parts 
     287        else if ($mimetype == 'multipart/alternative' && is_array($structure->parts)) { 
     288            // get html/plaintext parts 
     289            $plain_part = $html_part = $print_part = $related_part = null; 
     290 
     291            foreach ($structure->parts as $p => $sub_part) { 
     292                $sub_mimetype = $sub_part->mimetype; 
     293         
     294                // check if sub part is 
     295                if ($sub_mimetype == 'text/plain') 
     296                    $plain_part = $p; 
     297                else if ($sub_mimetype == 'text/html') 
     298                    $html_part = $p; 
     299                else if ($sub_mimetype == 'text/enriched') 
     300                    $enriched_part = $p; 
     301                else if (in_array($sub_mimetype, array('multipart/related', 'multipart/mixed', 'multipart/alternative'))) 
     302                    $related_part = $p; 
     303            } 
     304 
     305            // parse related part (alternative part could be in here) 
     306            if ($related_part !== null && !$this->parse_alternative) { 
     307                $this->parse_alternative = true; 
     308                $this->parse_structure($structure->parts[$related_part], true); 
     309                $this->parse_alternative = false; 
     310         
     311                // if plain part was found, we should unset it if html is preferred 
     312                if ($this->opt['prefer_html'] && count($this->parts)) 
     313                    $plain_part = null; 
     314            } 
     315 
     316            // choose html/plain part to print 
     317            if ($html_part !== null && $this->opt['prefer_html']) { 
     318                $print_part = &$structure->parts[$html_part]; 
     319            } 
     320            else if ($enriched_part !== null) { 
     321                $print_part = &$structure->parts[$enriched_part]; 
     322            } 
     323            else if ($plain_part !== null) { 
     324                $print_part = &$structure->parts[$plain_part]; 
     325            } 
     326 
     327            // add the right message body 
     328            if (is_object($print_part)) { 
     329                $print_part->type = 'content'; 
     330                $this->parts[] = $print_part; 
     331            } 
     332            // show plaintext warning 
     333            else if ($html_part !== null && empty($this->parts)) { 
     334                $c = new stdClass; 
     335                $c->type            = 'content'; 
     336                $c->ctype_primary   = 'text'; 
     337                $c->ctype_secondary = 'plain'; 
     338                $c->body            = rcube_label('htmlmessage'); 
     339 
     340                $this->parts[] = $c; 
     341            } 
     342 
     343            // add html part as attachment 
     344            if ($html_part !== null && $structure->parts[$html_part] !== $print_part) { 
     345                $html_part = &$structure->parts[$html_part]; 
     346                $html_part->filename = rcube_label('htmlmessage'); 
     347                $html_part->mimetype = 'text/html'; 
     348 
     349                $this->attachments[] = $html_part; 
     350            } 
     351        } 
     352        // this is an ecrypted message -> create a plaintext body with the according message 
     353        else if ($mimetype == 'multipart/encrypted') { 
     354            $p = new stdClass; 
     355            $p->type            = 'content'; 
     356            $p->ctype_primary   = 'text'; 
     357            $p->ctype_secondary = 'plain'; 
     358            $p->body            = rcube_label('encryptedmessage'); 
     359            $p->size            = strlen($p->body); 
    192360       
    193     $out = null; 
    194  
    195     // check all message parts 
    196     foreach ($this->mime_parts as $mime_id => $part) { 
    197       $mimetype = strtolower($part->ctype_primary . '/' . $part->ctype_secondary); 
    198  
    199       if ($mimetype == 'text/plain') { 
    200         $out = $this->imap->get_message_part($this->uid, $mime_id, $part); 
     361            // maybe some plugins are able to decode this encrypted message part 
     362            $data = $this->app->plugins->exec_hook('message_part_encrypted', 
     363                array('object' => $this, 'struct' => $structure, 'part' => $p)); 
     364 
     365            if (is_array($data['parts'])) { 
     366                $this->parts = array_merge($this->parts, $data['parts']); 
     367            } 
     368            else if ($data['part']) { 
     369                $this->parts[] = $p; 
     370            } 
     371        } 
     372        // message contains multiple parts 
     373        else if (is_array($structure->parts) && !empty($structure->parts)) { 
     374            // iterate over parts 
     375            for ($i=0; $i < count($structure->parts); $i++) { 
     376                $mail_part      = &$structure->parts[$i]; 
     377                $primary_type   = $mail_part->ctype_primary; 
     378                $secondary_type = $mail_part->ctype_secondary; 
     379 
     380                // real content-type of message/rfc822 
     381                if ($mail_part->real_mimetype) { 
     382                    $part_orig_mimetype = $mail_part->mimetype; 
     383                    $part_mimetype = $mail_part->real_mimetype; 
     384                    list($primary_type, $secondary_type) = explode('/', $part_mimetype); 
     385                } 
     386                else 
     387                    $part_mimetype = $mail_part->mimetype; 
     388 
     389                // multipart/alternative 
     390                if ($primary_type == 'multipart') { 
     391                    $this->parse_structure($mail_part, true); 
     392 
     393                    // list message/rfc822 as attachment as well (mostly .eml) 
     394                    if ($part_orig_mimetype == 'message/rfc822' && !empty($mail_part->filename)) 
     395                        $this->attachments[] = $mail_part; 
     396                } 
     397                // part text/[plain|html] OR message/delivery-status 
     398                else if ((($part_mimetype == 'text/plain' || $part_mimetype == 'text/html') && $mail_part->disposition != 'attachment') || 
     399                    $part_mimetype == 'message/delivery-status' || $part_mimetype == 'message/disposition-notification' 
     400                ) { 
     401                    // add text part if it matches the prefs 
     402                    if (!$this->parse_alternative || 
     403                        ($secondary_type == 'html' && $this->opt['prefer_html']) || 
     404                        ($secondary_type == 'plain' && !$this->opt['prefer_html']) 
     405                    ) { 
     406                        $mail_part->type = 'content'; 
     407                        $this->parts[] = $mail_part; 
     408                    } 
     409           
     410                    // list as attachment as well 
     411                    if (!empty($mail_part->filename)) 
     412                        $this->attachments[] = $mail_part; 
     413                } 
     414                // part message/* 
     415                else if ($primary_type=='message') { 
     416                    $this->parse_structure($mail_part, true); 
     417 
     418                    // list as attachment as well (mostly .eml) 
     419                    if (!empty($mail_part->filename)) 
     420                        $this->attachments[] = $mail_part; 
     421                } 
     422                // ignore "virtual" protocol parts 
     423                else if ($primary_type == 'protocol') { 
     424                    continue; 
     425                } 
     426                // part is Microsoft Outlook TNEF (winmail.dat) 
     427                else if ($part_mimetype == 'application/ms-tnef') { 
     428                    foreach ((array)$this->tnef_decode($mail_part) as $tpart) { 
     429                        $this->mime_parts[$tpart->mime_id] = $tpart; 
     430                        $this->attachments[] = $tpart; 
     431                    } 
     432                } 
     433                // part is a file/attachment 
     434                else if (preg_match('/^(inline|attach)/', $mail_part->disposition) || 
     435                    $mail_part->headers['content-id'] || (empty($mail_part->disposition) && $mail_part->filename) 
     436                ) { 
     437                    // skip apple resource forks 
     438                    if ($message_ctype_secondary == 'appledouble' && $secondary_type == 'applefile') 
     439                        continue; 
     440 
     441                    // part belongs to a related message and is linked 
     442                    if ($mimetype == 'multipart/related' 
     443                        && preg_match('!^image/!', $part_mimetype) 
     444                        && ($mail_part->headers['content-id'] || $mail_part->headers['content-location'])) { 
     445                        if ($mail_part->headers['content-id']) 
     446                            $mail_part->content_id = preg_replace(array('/^</', '/>$/'), '', $mail_part->headers['content-id']); 
     447                        if ($mail_part->headers['content-location']) 
     448                            $mail_part->content_location = $mail_part->headers['content-base'] . $mail_part->headers['content-location']; 
     449 
     450                        $this->inline_parts[] = $mail_part; 
     451                    } 
     452                    // attachment encapsulated within message/rfc822 part needs further decoding (#1486743) 
     453                    else if ($part_orig_mimetype == 'message/rfc822') { 
     454                        $this->parse_structure($mail_part, true); 
     455                    } 
     456                    // is a regular attachment 
     457                    else if (preg_match('!^[a-z0-9-.+]+/[a-z0-9-.+]+$!i', $part_mimetype)) { 
     458                        if (!$mail_part->filename) 
     459                            $mail_part->filename = 'Part '.$mail_part->mime_id; 
     460                        $this->attachments[] = $mail_part; 
     461                    } 
     462                } 
     463            } 
     464 
     465            // if this was a related part try to resolve references 
     466            if ($mimetype == 'multipart/related' && sizeof($this->inline_parts)) { 
     467                $a_replaces = array(); 
     468 
     469                foreach ($this->inline_parts as $inline_object) { 
     470                    $part_url = $this->get_part_url($inline_object->mime_id); 
     471                    if ($inline_object->content_id) 
     472                        $a_replaces['cid:'.$inline_object->content_id] = $part_url; 
     473                    if ($inline_object->content_location) 
     474                        $a_replaces[$inline_object->content_location] = $part_url; 
     475                } 
     476 
     477                // add replace array to each content part 
     478                // (will be applied later when part body is available) 
     479                foreach ($this->parts as $i => $part) { 
     480                    if ($part->type == 'content') 
     481                        $this->parts[$i]->replaces = $a_replaces; 
     482                } 
     483            } 
     484        } 
     485        // message is a single part non-text 
     486        else if ($structure->filename) { 
     487            $this->attachments[] = $structure; 
     488        } 
     489    } 
     490 
     491 
     492    /** 
     493     * Fill aflat array with references to all parts, indexed by part numbers 
     494     * 
     495     * @param object rcube_message_part Message body structure 
     496     */ 
     497    private function get_mime_numbers(&$part) 
     498    { 
     499        if (strlen($part->mime_id)) 
     500            $this->mime_parts[$part->mime_id] = &$part; 
     501       
     502        if (is_array($part->parts)) 
     503            for ($i=0; $i<count($part->parts); $i++) 
     504                $this->get_mime_numbers($part->parts[$i]); 
     505    } 
     506 
     507 
     508    /** 
     509     * Decode a Microsoft Outlook TNEF part (winmail.dat) 
     510     * 
     511     * @param object rcube_message_part Message part to decode 
     512     */ 
     513    function tnef_decode(&$part) 
     514    { 
     515        // @TODO: attachment may be huge, hadle it via file 
     516        if (!isset($part->body)) 
     517            $part->body = $this->imap->get_message_part($this->uid, $part->mime_id, $part); 
     518 
     519        require_once('lib/tnef_decoder.inc'); 
     520 
     521        $parts = array(); 
     522        $tnef_arr = tnef_decode($part->body); 
     523 
     524        foreach ($tnef_arr as $pid => $winatt) { 
     525            $tpart = new rcube_message_part; 
     526 
     527            $tpart->filename        = trim($winatt['name']); 
     528            $tpart->encoding        = 'stream'; 
     529            $tpart->ctype_primary   = trim(strtolower($winatt['type0'])); 
     530            $tpart->ctype_secondary = trim(strtolower($winatt['type1'])); 
     531            $tpart->mimetype        = $tpart->ctype_primary . '/' . $tpart->ctype_secondary; 
     532            $tpart->mime_id         = 'winmail.' . $part->mime_id . '.' . $pid; 
     533            $tpart->size            = $winatt['size']; 
     534            $tpart->body            = $winatt['stream']; 
     535 
     536            $parts[] = $tpart; 
     537            unset($tnef_arr[$pid]); 
     538        } 
    201539         
    202         // re-format format=flowed content 
    203         if ($part->ctype_secondary == "plain" && $part->ctype_parameters['format'] == "flowed") 
    204           $out = self::unfold_flowed($out); 
    205         break; 
    206       } 
    207       else if ($mimetype == 'text/html') { 
    208         $html_part = $this->imap->get_message_part($this->uid, $mime_id, $part); 
    209  
    210         // remove special chars encoding 
    211         $trans = array_flip(get_html_translation_table(HTML_ENTITIES)); 
    212         $html_part = strtr($html_part, $trans); 
    213  
    214         // create instance of html2text class 
    215         $txt = new html2text($html_part); 
    216         $out = $txt->get_text(); 
    217         break; 
    218       } 
    219     } 
    220  
    221     return $out; 
    222   } 
    223  
    224  
    225   /** 
    226    * Raad the message structure returend by the IMAP server 
    227    * and build flat lists of content parts and attachments 
    228    * 
    229    * @param object rcube_message_part Message structure node 
    230    * @param bool  True when called recursively 
    231    */ 
    232   private function parse_structure($structure, $recursive = false) 
    233   { 
    234     $message_ctype_primary = $structure->ctype_primary; 
    235     $message_ctype_secondary = $structure->ctype_secondary; 
    236     $mimetype = $structure->mimetype; 
    237  
    238     // real content-type of message/rfc822 part 
    239     if ($mimetype == 'message/rfc822') { 
    240       if ($structure->real_mimetype) { 
    241         $mimetype = $structure->real_mimetype; 
    242         list($message_ctype_primary, $message_ctype_secondary) = explode('/', $mimetype); 
    243       } 
    244     } 
    245  
    246     // show message headers 
    247     if ($recursive && is_array($structure->headers) && isset($structure->headers['subject'])) { 
    248       $c = new stdClass; 
    249       $c->type = 'headers'; 
    250       $c->headers = &$structure->headers; 
    251       $this->parts[] = $c; 
    252     } 
    253  
    254     // print body if message doesn't have multiple parts 
    255     if ($message_ctype_primary == 'text' && !$recursive) { 
    256       $structure->type = 'content'; 
    257       $this->parts[] = &$structure; 
    258     } 
    259     // the same for pgp signed messages 
    260     else if ($mimetype == 'application/pgp' && !$recursive) { 
    261       $structure->type = 'content'; 
    262       $this->parts[] = &$structure; 
    263     } 
    264     // message contains alternative parts 
    265     else if ($mimetype == 'multipart/alternative' && is_array($structure->parts)) { 
    266       // get html/plaintext parts 
    267       $plain_part = $html_part = $print_part = $related_part = null; 
    268  
    269       foreach ($structure->parts as $p => $sub_part) { 
    270         $sub_mimetype = $sub_part->mimetype; 
     540        return $parts; 
     541    } 
     542 
     543 
     544    /** 
     545     * Parse message body for UUencoded attachments bodies 
     546     * 
     547     * @param object rcube_message_part Message part to decode 
     548     */ 
     549    function uu_decode(&$part) 
     550    { 
     551        // @TODO: messages may be huge, hadle body via file 
     552        if (!isset($part->body)) 
     553            $part->body = $this->imap->get_message_part($this->uid, $part->mime_id, $part); 
     554 
     555        $parts = array(); 
     556        // FIXME: line length is max.65? 
     557        $uu_regexp = '/begin [0-7]{3,4} ([^\n]+)\n(([\x21-\x7E]{0,65}\n)+)`\nend/s'; 
     558 
     559        if (preg_match_all($uu_regexp, $part->body, $matches, PREG_SET_ORDER)) { 
     560            // remove attachments bodies from the message body 
     561            $part->body = preg_replace($uu_regexp, '', $part->body); 
     562            // update message content-type 
     563            $part->ctype_primary   = 'multipart'; 
     564            $part->ctype_secondary = 'mixed'; 
     565            $part->mimetype        = $part->ctype_primary . '/' . $part->ctype_secondary; 
     566 
     567            // add attachments to the structure 
     568            foreach ($matches as $pid => $att) { 
     569                $uupart = new rcube_message_part; 
     570 
     571                $uupart->filename = trim($att[1]); 
     572                $uupart->encoding = 'stream'; 
     573                $uupart->body     = convert_uudecode($att[2]); 
     574                $uupart->size     = strlen($uupart->body); 
     575                $uupart->mime_id  = 'uu.' . $part->mime_id . '.' . $pid; 
     576 
     577                $ctype = rc_mime_content_type($uupart->body, $uupart->filename, 'application/octet-stream', true); 
     578                $uupart->mimetype = $ctype; 
     579                list($uupart->ctype_primary, $uupart->ctype_secondary) = explode('/', $ctype); 
     580 
     581                $parts[] = $uupart; 
     582                unset($matches[$pid]); 
     583            } 
     584        } 
    271585         
    272         // check if sub part is 
    273         if ($sub_mimetype == 'text/plain') 
    274           $plain_part = $p; 
    275         else if ($sub_mimetype == 'text/html') 
    276           $html_part = $p; 
    277         else if ($sub_mimetype == 'text/enriched') 
    278           $enriched_part = $p; 
    279         else if (in_array($sub_mimetype, array('multipart/related', 'multipart/mixed', 'multipart/alternative'))) 
    280           $related_part = $p; 
    281       } 
    282  
    283       // parse related part (alternative part could be in here) 
    284       if ($related_part !== null && !$this->parse_alternative) { 
    285         $this->parse_alternative = true; 
    286         $this->parse_structure($structure->parts[$related_part], true); 
    287         $this->parse_alternative = false; 
    288          
    289         // if plain part was found, we should unset it if html is preferred 
    290         if ($this->opt['prefer_html'] && count($this->parts)) 
    291           $plain_part = null; 
    292       } 
    293  
    294       // choose html/plain part to print 
    295       if ($html_part !== null && $this->opt['prefer_html']) { 
    296         $print_part = &$structure->parts[$html_part]; 
    297       } 
    298       else if ($enriched_part !== null) { 
    299         $print_part = &$structure->parts[$enriched_part]; 
    300       } 
    301       else if ($plain_part !== null) { 
    302         $print_part = &$structure->parts[$plain_part]; 
    303       } 
    304  
    305       // add the right message body 
    306       if (is_object($print_part)) { 
    307         $print_part->type = 'content'; 
    308         $this->parts[] = $print_part; 
    309       } 
    310       // show plaintext warning 
    311       else if ($html_part !== null && empty($this->parts)) { 
    312         $c = new stdClass; 
    313         $c->type = 'content'; 
    314         $c->body = rcube_label('htmlmessage'); 
    315         $c->ctype_primary = 'text'; 
    316         $c->ctype_secondary = 'plain'; 
    317  
    318         $this->parts[] = $c; 
    319       } 
    320  
    321       // add html part as attachment 
    322       if ($html_part !== null && $structure->parts[$html_part] !== $print_part) { 
    323         $html_part = &$structure->parts[$html_part]; 
    324         $html_part->filename = rcube_label('htmlmessage'); 
    325         $html_part->mimetype = 'text/html'; 
    326  
    327         $this->attachments[] = $html_part; 
    328       } 
    329     } 
    330     // this is an ecrypted message -> create a plaintext body with the according message 
    331     else if ($mimetype == 'multipart/encrypted') { 
    332       $p = new stdClass; 
    333       $p->type = 'content'; 
    334       $p->ctype_primary = 'text'; 
    335       $p->ctype_secondary = 'plain'; 
    336       $p->body = rcube_label('encryptedmessage'); 
    337       $p->size = strlen($p->body); 
    338        
    339       // maybe some plugins are able to decode this encrypted message part 
    340       $data = $this->app->plugins->exec_hook('message_part_encrypted', array('object' => $this, 'struct' => $structure, 'part' => $p)); 
    341       if (is_array($data['parts'])) { 
    342         $this->parts = array_merge($this->parts, $data['parts']); 
    343       } 
    344       else if ($data['part']) { 
    345         $this->parts[] = $p; 
    346       } 
    347     } 
    348     // message contains multiple parts 
    349     else if (is_array($structure->parts) && !empty($structure->parts)) { 
    350       // iterate over parts 
    351       for ($i=0; $i < count($structure->parts); $i++) { 
    352         $mail_part = &$structure->parts[$i]; 
    353         $primary_type = $mail_part->ctype_primary; 
    354         $secondary_type = $mail_part->ctype_secondary; 
    355  
    356         // real content-type of message/rfc822 
    357         if ($mail_part->real_mimetype) { 
    358           $part_orig_mimetype = $mail_part->mimetype; 
    359           $part_mimetype = $mail_part->real_mimetype; 
    360           list($primary_type, $secondary_type) = explode('/', $part_mimetype); 
    361         } 
    362         else 
    363           $part_mimetype = $mail_part->mimetype; 
    364  
    365         // multipart/alternative 
    366         if ($primary_type=='multipart') { 
    367           $this->parse_structure($mail_part, true); 
    368  
    369           // list message/rfc822 as attachment as well (mostly .eml) 
    370           if ($part_orig_mimetype == 'message/rfc822' && !empty($mail_part->filename)) 
    371             $this->attachments[] = $mail_part; 
    372         } 
    373         // part text/[plain|html] OR message/delivery-status 
    374         else if ((($part_mimetype == 'text/plain' || $part_mimetype == 'text/html') && $mail_part->disposition != 'attachment') || 
    375             $part_mimetype == 'message/delivery-status' || $part_mimetype == 'message/disposition-notification') { 
    376  
    377           // add text part if it matches the prefs 
    378           if (!$this->parse_alternative || 
    379               ($secondary_type == 'html' && $this->opt['prefer_html']) || 
    380               ($secondary_type == 'plain' && !$this->opt['prefer_html'])) { 
    381             $mail_part->type = 'content'; 
    382             $this->parts[] = $mail_part; 
    383           } 
    384            
    385           // list as attachment as well 
    386           if (!empty($mail_part->filename)) 
    387             $this->attachments[] = $mail_part; 
    388         } 
    389         // part message/* 
    390         else if ($primary_type=='message') { 
    391           $this->parse_structure($mail_part, true); 
    392  
    393           // list as attachment as well (mostly .eml) 
    394           if (!empty($mail_part->filename)) 
    395             $this->attachments[] = $mail_part; 
    396         } 
    397         // ignore "virtual" protocol parts 
    398         else if ($primary_type == 'protocol') 
    399           continue; 
    400  
    401         // part is Microsoft Outlook TNEF (winmail.dat) 
    402         else if ($part_mimetype == 'application/ms-tnef') { 
    403           foreach ((array)$this->imap->tnef_decode($mail_part, $structure->headers['uid']) as $tnef_part) { 
    404             $this->mime_parts[$tnef_part->mime_id] = $tnef_part; 
    405             $this->attachments[] = $tnef_part; 
    406           } 
    407         } 
    408         // part is a file/attachment 
    409         else if (preg_match('/^(inline|attach)/', $mail_part->disposition) || 
    410                  $mail_part->headers['content-id'] || (empty($mail_part->disposition) && $mail_part->filename)) { 
    411  
    412           // skip apple resource forks 
    413           if ($message_ctype_secondary == 'appledouble' && $secondary_type == 'applefile') 
    414             continue; 
    415  
    416           // part belongs to a related message and is linked 
    417           if ($mimetype == 'multipart/related' 
    418               && preg_match('!^image/!', $part_mimetype) 
    419               && ($mail_part->headers['content-id'] || $mail_part->headers['content-location'])) { 
    420             if ($mail_part->headers['content-id']) 
    421               $mail_part->content_id = preg_replace(array('/^</', '/>$/'), '', $mail_part->headers['content-id']); 
    422             if ($mail_part->headers['content-location']) 
    423               $mail_part->content_location = $mail_part->headers['content-base'] . $mail_part->headers['content-location']; 
    424  
    425             $this->inline_parts[] = $mail_part; 
    426           } 
    427           // attachment encapsulated within message/rfc822 part needs further decoding (#1486743) 
    428           else if ($part_orig_mimetype == 'message/rfc822') { 
    429             $this->parse_structure($mail_part, true); 
    430           } 
    431           // is a regular attachment 
    432           else if (preg_match('!^[a-z0-9-.+]+/[a-z0-9-.+]+$!i', $part_mimetype)) { 
    433             if (!$mail_part->filename) 
    434               $mail_part->filename = 'Part '.$mail_part->mime_id; 
    435             $this->attachments[] = $mail_part; 
    436           } 
    437         } 
    438       } 
    439  
    440       // if this was a related part try to resolve references 
    441       if ($mimetype == 'multipart/related' && sizeof($this->inline_parts)) { 
    442         $a_replaces = array(); 
    443  
    444         foreach ($this->inline_parts as $inline_object) { 
    445           $part_url = $this->get_part_url($inline_object->mime_id); 
    446           if ($inline_object->content_id) 
    447             $a_replaces['cid:'.$inline_object->content_id] = $part_url; 
    448           if ($inline_object->content_location) 
    449             $a_replaces[$inline_object->content_location] = $part_url; 
    450         } 
    451  
    452         // add replace array to each content part 
    453         // (will be applied later when part body is available) 
    454         foreach ($this->parts as $i => $part) { 
    455           if ($part->type == 'content') 
    456             $this->parts[$i]->replaces = $a_replaces; 
    457         } 
    458       } 
    459     } 
    460  
    461     // message is a single part non-text 
    462     else if ($structure->filename) { 
    463       $this->attachments[] = $structure; 
    464     } 
    465   } 
    466  
    467  
    468   /** 
    469    * Fill aflat array with references to all parts, indexed by part numbers 
    470    * 
    471    * @param object rcube_message_part Message body structure 
    472    */ 
    473   private function get_mime_numbers(&$part) 
    474   { 
    475     if (strlen($part->mime_id)) 
    476       $this->mime_parts[$part->mime_id] = &$part; 
    477        
    478     if (is_array($part->parts)) 
    479       for ($i=0; $i<count($part->parts); $i++) 
    480         $this->get_mime_numbers($part->parts[$i]); 
    481   } 
    482  
    483  
    484   /** 
    485    * Interpret a format=flowed message body according to RFC 2646 
    486    * 
    487    * @param string  Raw body formatted as flowed text 
    488    * @return string Interpreted text with unwrapped lines and stuffed space removed 
    489    */ 
    490   public static function unfold_flowed($text) 
    491   { 
    492     return preg_replace( 
    493       array('/-- (\r?\n)/',   '/^ /m',  '/(.) \r?\n/',  '/--%SIGEND%(\r?\n)/'), 
    494       array('--%SIGEND%\\1',  '',       '\\1 ',         '-- \\1'), 
    495       $text); 
    496   } 
    497    
    498   /** 
    499    * Wrap the given text to comply with RFC 2646 
    500    */ 
    501   public static function format_flowed($text, $length = 72) 
    502   { 
    503     $out = ''; 
     586        return $parts; 
     587    } 
     588 
     589 
     590    /** 
     591     * Interpret a format=flowed message body according to RFC 2646 
     592     * 
     593     * @param string  Raw body formatted as flowed text 
     594     * @return string Interpreted text with unwrapped lines and stuffed space removed 
     595     */ 
     596    public static function unfold_flowed($text) 
     597    { 
     598        return preg_replace( 
     599            array('/-- (\r?\n)/',   '/^ /m',  '/(.) \r?\n/',  '/--%SIGEND%(\r?\n)/'), 
     600            array('--%SIGEND%\\1',  '',       '\\1 ',         '-- \\1'), 
     601            $text); 
     602    } 
     603 
     604 
     605    /** 
     606     * Wrap the given text to comply with RFC 2646 
     607     */ 
     608    public static function format_flowed($text, $length = 72) 
     609    { 
     610        $out = ''; 
    504611     
    505     foreach (preg_split('/\r?\n/', trim($text)) as $line) { 
    506       // don't wrap quoted lines (to avoid wrapping problems) 
    507       if ($line[0] != '>') 
    508         $line = rc_wordwrap(rtrim($line), $length - 1, " \r\n"); 
    509  
    510       $out .= $line . "\r\n"; 
    511     } 
     612        foreach (preg_split('/\r?\n/', trim($text)) as $line) { 
     613            // don't wrap quoted lines (to avoid wrapping problems) 
     614            if ($line[0] != '>') 
     615                $line = rc_wordwrap(rtrim($line), $length - 1, " \r\n"); 
     616 
     617            $out .= $line . "\r\n"; 
     618        } 
    512619     
    513     return $out; 
    514   } 
     620        return $out; 
     621    } 
    515622 
    516623} 
    517  
  • program/include/rcube_shared.inc

    r8ad5c89 rd311d80  
    431431 * A method to guess the mime_type of an attachment. 
    432432 * 
    433  * @param string $path     Path to the file. 
    434  * @param string $name     File name (with suffix) 
    435  * @param string $failover Mime type supplied for failover. 
     433 * @param string $path      Path to the file. 
     434 * @param string $name      File name (with suffix) 
     435 * @param string $failover  Mime type supplied for failover. 
     436 * @param string $is_stream Set to True if $path contains file body 
    436437 * 
    437438 * @return string 
     
    440441 * @see    http://de2.php.net/mime_content_type 
    441442 */ 
    442 function rc_mime_content_type($path, $name, $failover = 'application/octet-stream') 
     443function rc_mime_content_type($path, $name, $failover = 'application/octet-stream', $is_stream=false) 
    443444{ 
    444445    $mime_type = null; 
     
    454455    if (!$mime_type && function_exists('finfo_open')) { 
    455456        if ($finfo = finfo_open(FILEINFO_MIME, $mime_magic)) { 
    456             $mime_type = finfo_file($finfo, $path); 
     457            if ($is_stream) 
     458                $mime_type = finfo_buffer($finfo, $path); 
     459            else 
     460                $mime_type = finfo_file($finfo, $path); 
    457461            finfo_close($finfo); 
    458462        } 
    459463    } 
    460464    // try PHP's mime_content_type 
    461     if (!$mime_type && function_exists('mime_content_type')) { 
    462       $mime_type = mime_content_type($path); 
     465    if (!$mime_type && !$is_stream && function_exists('mime_content_type')) { 
     466      $mime_type = @mime_content_type($path); 
    463467    } 
    464468    // fall back to user-submitted string 
  • program/steps/mail/compose.inc

    r8f2b463 rd311d80  
    726726  { 
    727727    if (($part->ctype_primary != 'message' || !$bodyIsHtml) && $part->ctype_primary != 'multipart' &&  
    728         ($part->disposition == 'attachment' || ($part->disposition == 'inline' && $bodyIsHtml) || $part->filename)) 
    729     { 
     728        ($part->disposition == 'attachment' || ($part->disposition == 'inline' && $bodyIsHtml) || $part->filename) 
     729        && $part->mimetype != 'application/ms-tnef' 
     730    ) { 
    730731      $skip = false; 
    731732      if ($part->mimetype == 'message/rfc822') { 
  • program/steps/mail/get.inc

    r1097a3c rd311d80  
    5151 
    5252else if ($pid = get_input_value('_part', RCUBE_INPUT_GET)) { 
    53   // TNEF encoded attachment part 
    54   if (preg_match('/^winmail\.([0-9.]+)\.([0-9]+)$/', $pid, $nt)) { 
    55     $pid = $nt[1]; $i = $nt[2]; 
    56     if ($part = $MESSAGE->mime_parts[$pid]) { 
    57       $tnef_arr = $IMAP->tnef_decode($part, $MESSAGE->uid); 
    58       if (is_a($tnef_arr[$i], 'rcube_message_part')) 
    59         $MESSAGE->mime_parts[$pid] = $tnef_arr[$i]; 
    60     } 
    61   } 
    62    
     53 
    6354  if ($part = $MESSAGE->mime_parts[$pid]) { 
    6455    $ctype_primary = strtolower($part->ctype_primary); 
Note: See TracChangeset for help on using the changeset viewer.