Source for file mime.php

Documentation is available at mime.php

  1. <?php
  2.  
  3. /**
  4.  * mime.php
  5.  *
  6.  * This contains the functions necessary to detect and decode MIME
  7.  * messages.
  8.  *
  9.  * @copyright &copy; 1999-2006 The SquirrelMail Project Team
  10.  * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  11.  * @version $Id: mime.php,v 1.372 2006/10/06 22:02:52 stevetruckstuff Exp $
  12.  * @package squirrelmail
  13.  */
  14.  
  15. /**
  16.  * dependency information
  17.    functions       dependency
  18.    mime_structure
  19.         class/mime/Message.class.php
  20.             Message::parseStructure
  21.         functions/page_header.php
  22.             displayPageHeader
  23.         functions/display_messages.php
  24.             plain_error_message
  25.    mime_fetch_body
  26.         functions/imap_general.php
  27.             sqimap_run_command
  28.    mime_print_body_lines
  29.  
  30.  
  31.  
  32. functions/imap.php
  33. functions/attachment_common.php
  34. functions/display_messages.php
  35.  
  36. magicHtml => url_parser
  37. translateText => url_parser
  38.  
  39. */
  40.  
  41.  
  42. /* -------------------------------------------------------------------------- */
  43. /* MIME DECODING                                                              */
  44. /* -------------------------------------------------------------------------- */
  45.  
  46. /**
  47.  * Get the MIME structure
  48.  *
  49.  * This function gets the structure of a message and stores it in the "message" class.
  50.  * It will return this object for use with all relevant header information and
  51.  * fully parsed into the standard "message" object format.
  52.  */
  53. function mime_structure ($bodystructure$flags=array()) {
  54.  
  55.     /* Isolate the body structure and remove beginning and end parenthesis. */
  56.     $read trim(substr ($bodystructurestrpos(strtolower($bodystructure)'bodystructure'13));
  57.     $read trim(substr ($read0-1));
  58.     $i 0;
  59.     $msg Message::parseStructure($read,$i);
  60.     if (!is_object($msg)) {
  61.         global $color$mailbox;
  62.         /* removed urldecode because $_GET is auto urldecoded ??? */
  63.         displayPageHeader$color$mailbox );
  64.         $errormessage  _("SquirrelMail could not decode the bodystructure of the message");
  65.         $errormessage .= '<br />'._("The bodystructure provided by your IMAP server:").'<br /><br />';
  66.         $errormessage .= '<pre>' htmlspecialchars($read'</pre>';
  67.         plain_error_message$errormessage$color );
  68.         echo '</body></html>';
  69.         exit;
  70.     }
  71.     if (count($flags)) {
  72.         foreach ($flags as $flag{
  73.             $char strtoupper($flag{1});
  74.             switch ($char{
  75.                 case 'S':
  76.                     if (strtolower($flag== '\\seen'{
  77.                         $msg->is_seen true;
  78.                     }
  79.                     break;
  80.                 case 'A':
  81.                     if (strtolower($flag== '\\answered'{
  82.                         $msg->is_answered true;
  83.                     }
  84.                     break;
  85.                 case 'D':
  86.                     if (strtolower($flag== '\\deleted'{
  87.                         $msg->is_deleted true;
  88.                     }
  89.                     break;
  90.                 case 'F':
  91.                     if (strtolower($flag== '\\flagged'{
  92.                         $msg->is_flagged true;
  93.                     }
  94.                     break;
  95.                 case 'M':
  96.                     if (strtolower($flag== '$mdnsent'{
  97.                         $msg->is_mdnsent true;
  98.                     }
  99.                     break;
  100.                 default:
  101.                     break;
  102.             }
  103.         }
  104.     }
  105.     //    listEntities($msg);
  106.     return $msg;
  107. }
  108.  
  109.  
  110.  
  111. /* This starts the parsing of a particular structure.  It is called recursively,
  112.  * so it can be passed different structures.  It returns an object of type
  113.  * $message.
  114.  * First, it checks to see if it is a multipart message.  If it is, then it
  115.  * handles that as it sees is necessary.  If it is just a regular entity,
  116.  * then it parses it and adds the necessary header information (by calling out
  117.  * to mime_get_elements()
  118.  */
  119.  
  120. function mime_fetch_body($imap_stream$id$ent_id=1$fetch_size=0{
  121.     /* Do a bit of error correction.  If we couldn't find the entity id, just guess
  122.      * that it is the first one.  That is usually the case anyway.
  123.      */
  124.  
  125.     if (!$ent_id{
  126.         $cmd "FETCH $id BODY[]";
  127.     else {
  128.         $cmd "FETCH $id BODY[$ent_id]";
  129.     }
  130.  
  131.     if ($fetch_size!=0$cmd .= "<0.$fetch_size>";
  132.  
  133.     $data sqimap_run_command ($imap_stream$cmdtrue$response$messageTRUE);
  134.     do {
  135.         $topline trim(array_shift($data));
  136.     while($topline && ($topline[0== '*'&& !preg_match('/\* [0-9]+ FETCH.*/i'$topline)) ;
  137.  
  138.     $wholemessage implode(''$data);
  139.     if (ereg('\\{([^\\}]*)\\}'$topline$regs)) {
  140.         $ret substr($wholemessage0$regs[1]);
  141.         /* There is some information in the content info header that could be important
  142.          * in order to parse html messages. Let's get them here.
  143.          */
  144. //        if ($ret{0} == '<') {
  145. //            $data = sqimap_run_command ($imap_stream, "FETCH $id BODY[$ent_id.MIME]", true, $response, $message, TRUE);
  146. //        }
  147.     else if (ereg('"([^"]*)"'$topline$regs)) {
  148.         $ret $regs[1];
  149.     else {
  150.         global $where$what$mailbox$passed_id$startMessage;
  151.         $par 'mailbox=' urlencode($mailbox'&amp;passed_id=' $passed_id;
  152.         if (isset($where&& isset($what)) {
  153.             $par .= '&amp;where=' urlencode($where'&amp;what=' urlencode($what);
  154.         else {
  155.             $par .= '&amp;startMessage=' $startMessage '&amp;show_more=0';
  156.         }
  157.         $par .= '&amp;response=' urlencode($response.
  158.             '&amp;message='  urlencode($message)  .
  159.             '&amp;topline='  urlencode($topline);
  160.  
  161.         echo '<tt><br />' .
  162.             '<table width="80%"><tr>' .
  163.             '<tr><td colspan="2">' .
  164.             _("Body retrieval error. The reason for this is most probably that the message is malformed.".
  165.             '</td></tr>' .
  166.             '<tr><td><b>' _("Command:""</td><td>$cmd</td></tr>.
  167.             '<tr><td><b>' _("Response:""</td><td>$response</td></tr>.
  168.             '<tr><td><b>' _("Message:""</td><td>$message</td></tr>.
  169.             '<tr><td><b>' _("FETCH line:""</td><td>$topline</td></tr>.
  170.             "</table><br /></tt></font><hr />";
  171.  
  172.         $data sqimap_run_command ($imap_stream"FETCH $passed_id BODY[]"true$response$messageTRUE);
  173.         array_shift($data);
  174.         $wholemessage implode(''$data);
  175.  
  176.         $ret $wholemessage;
  177.     }
  178.     return $ret;
  179. }
  180.  
  181. function mime_print_body_lines ($imap_stream$id$ent_id=1$encoding$rStream='php://stdout'{
  182.  
  183.     /* Don't kill the connection if the browser is over a dialup
  184.      * and it would take over 30 seconds to download it.
  185.      * Don't call set_time_limit in safe mode.
  186.      */
  187.  
  188.     if (!ini_get('safe_mode')) {
  189.         set_time_limit(0);
  190.     }
  191.     /* in case of base64 encoded attachments, do not buffer them.
  192.        Instead, echo the decoded attachment directly to screen */
  193.     if (strtolower($encoding== 'base64'{
  194.         if (!$ent_id{
  195.             $query "FETCH $id BODY[]";
  196.         else {
  197.             $query "FETCH $id BODY[$ent_id]";
  198.         }
  199.         sqimap_run_command($imap_stream,$query,true,$response,$message,TRUE,'sqimap_base64_decode',$rStream,true);
  200.     else {
  201.         $body mime_fetch_body ($imap_stream$id$ent_id);
  202.         if (is_resource($rStream)) {
  203.             fputs($rStream,decodeBody($body,$encoding));
  204.         else {
  205.             echo decodeBody($body$encoding);
  206.         }
  207.     }
  208.  
  209.     /*
  210.        TODO, use the same method for quoted printable.
  211.        However, I assume that quoted printable attachments aren't that large
  212.        so the performancegain / memory usage drop will be minimal.
  213.        If we decide to add that then we need to adapt sqimap_fread because
  214.        we need to split te result on \n and fread doesn't stop at \n. That
  215.        means we also should provide $results from sqimap_fread (by ref) to
  216.        te function and set $no_return to false. The $filter function for
  217.        quoted printable should handle unsetting of $results.
  218.      */
  219.     /*
  220.        TODO 2: find out how we write to the output stream php://stdout. fwrite
  221.        doesn't work because 'php://stdout isn't a stream.
  222.      */
  223.  
  224.     return;
  225. }
  226.  
  227. /* -[ END MIME DECODING ]----------------------------------------------------------- */
  228.  
  229. /* This is here for debugging purposes.  It will print out a list
  230.  * of all the entity IDs that are in the $message object.
  231.  */
  232. function listEntities ($message{
  233.     if ($message{
  234.         echo "<tt>" $message->entity_id ' : ' $message->type0 '/' $message->type1 ' parent = '$message->parent->entity_id'<br />';
  235.         for ($i 0isset($message->entities[$i])$i++{
  236.             echo "$i : ";
  237.             $msg listEntities($message->entities[$i]);
  238.  
  239.             if ($msg{
  240.                 echo "return: ";
  241.                 return $msg;
  242.             }
  243.         }
  244.     }
  245. }
  246.  
  247. function getPriorityStr($priority{
  248.     $priority_level substr($priority,0,1);
  249.  
  250.     switch($priority_level{
  251.         /* Check for a higher then normal priority. */
  252.         case '1':
  253.         case '2':
  254.             $priority_string _("High");
  255.             break;
  256.  
  257.         /* Check for a lower then normal priority. */
  258.         case '4':
  259.         case '5':
  260.             $priority_string _("Low");
  261.             break;
  262.  
  263.         /* Check for a normal priority. */
  264.         case '3':
  265.         default:
  266.             $priority_level '3';
  267.             $priority_string _("Normal");
  268.             break;
  269.  
  270.     }
  271.     return $priority_string;
  272. }
  273.  
  274. /* returns a $message object for a particular entity id */
  275. function getEntity ($message$ent_id{
  276.     return $message->getEntity($ent_id);
  277. }
  278.  
  279. /* translateText
  280.  * Extracted from strings.php 23/03/2002
  281.  */
  282.  
  283. function translateText(&$body$wrap_at$charset{
  284.     global $where$what;   /* from searching */
  285.     global $color;          /* color theme */
  286.  
  287.     // require_once(SM_PATH . 'functions/url_parser.php');
  288.  
  289.     $body_ary explode("\n"$body);
  290.     for ($i=0$i count($body_ary)$i++{
  291.         $line $body_ary[$i];
  292.         if (strlen($line>= $wrap_at{
  293.             sqWordWrap($line$wrap_at$charset);
  294.         }
  295.         $line charset_decode($charset$line);
  296.         $line str_replace("\t"'        '$line);
  297.  
  298.         parseUrl ($line);
  299.  
  300.         $quotes 0;
  301.         $pos 0;
  302.         $j strlen($line);
  303.  
  304.         while ($pos $j{
  305.             if ($line[$pos== ' '{
  306.                 $pos++;
  307.             else if (strpos($line'&gt;'$pos=== $pos{
  308.                 $pos += 4;
  309.                 $quotes++;
  310.             else {
  311.                 break;
  312.             }
  313.         }
  314.  
  315.         if ($quotes 2{
  316.             $line '<span class="quote1">' $line '</style>';
  317.         elseif ($quotes{
  318.             $line '<span class="quote2">' $line '</style>';
  319.         }
  320.  
  321.         $body_ary[$i$line;
  322.     }
  323.     $body '<pre>' implode("\n"$body_ary'</pre>';
  324. }
  325.  
  326. /**
  327.  * This returns a parsed string called $body. That string can then
  328.  * be displayed as the actual message in the HTML. It contains
  329.  * everything needed, including HTML Tags, Attachments at the
  330.  * bottom, etc.
  331.  *
  332.  * Since 1.2.0 function uses message_body hook.
  333.  * Till 1.3.0 function included output of formatAttachments().
  334.  *
  335.  * @param resource $imap_stream imap connection resource
  336.  * @param object $message squirrelmail message object
  337.  * @param array $color squirrelmail color theme array
  338.  * @param integer $wrap_at number of characters per line
  339.  * @param string $ent_num (since 1.3.0) message part id
  340.  * @param integer $id (since 1.3.0) message id
  341.  * @param string $mailbox (since 1.3.0) imap folder name
  342.  * @param boolean $clean (since 1.5.1) Do not output stuff that's irrelevant for the printable version.
  343.  * @return string html formated message text
  344.  */
  345. function formatBody($imap_stream$message$color$wrap_at$ent_num$id$mailbox='INBOX'$clean=FALSE{
  346.     /* This if statement checks for the entity to show as the
  347.      * primary message. To add more of them, just put them in the
  348.      * order that is their priority.
  349.      */
  350.     global $startMessage$languages$squirrelmail_language,
  351.            $show_html_default$sort$has_unsafe_images$passed_ent_id,
  352.            $use_iframe$iframe_height$download_and_unsafe_link,
  353.            $download_href$unsafe_image_toggle_href$unsafe_image_toggle_text;
  354.  
  355.     // workaround for not updated config.php
  356.     if (isset($use_iframe)) $use_iframe false;
  357.  
  358.     if!sqgetGlobalVar('view_unsafe_images'$view_unsafe_imagesSQ_GET) ) {
  359.         $view_unsafe_images false;
  360.     }
  361.  
  362.     $body '';
  363.     $urlmailbox urlencode($mailbox);
  364.     $body_message getEntity($message$ent_num);
  365.     if (($body_message->header->type0 == 'text'||
  366.             ($body_message->header->type0 == 'rfc822')) {
  367.         $body mime_fetch_body ($imap_stream$id$ent_num);
  368.         $body decodeBody($body$body_message->header->encoding);
  369.  
  370.         if (isset($languages[$squirrelmail_language]['XTRA_CODE']&&
  371.                 function_exists($languages[$squirrelmail_language]['XTRA_CODE''_decode')) {
  372.             if (mb_detect_encoding($body!= 'ASCII'{
  373.                 $body call_user_func($languages[$squirrelmail_language]['XTRA_CODE''_decode',$body);
  374.             }
  375.         }
  376.         $hookResults do_hook("message_body"$body);
  377.         $body $hookResults[1];
  378.  
  379.         /* If there are other types that shouldn't be formatted, add
  380.          * them here.
  381.          */
  382.  
  383.         if ($body_message->header->type1 == 'html'{
  384.             if ($show_html_default <> 1{
  385.                 $entity_conv array('&nbsp;' => ' ',
  386.                                      '<p>'    => "\n",
  387.                                      '<P>'    => "\n",
  388.                                      '<br>'   => "\n",
  389.                                      '<BR>'   => "\n",
  390.                                      '<br />' => "\n",
  391.                                      '<BR />' => "\n",
  392.                                      '&gt;'   => '>',
  393.                                      '&lt;'   => '<');
  394.                 $body strtr($body$entity_conv);
  395.                 $body strip_tags($body);
  396.                 $body trim($body);
  397.                 translateText($body$wrap_at,
  398.                         $body_message->header->getParameter('charset'));
  399.             elseif ($use_iframe && $clean{
  400.                 // $clean is used to remove iframe in printable view.
  401.  
  402.                 /**
  403.                  * If we don't add html message between iframe tags,
  404.                  * we must detect unsafe images and modify $has_unsafe_images.
  405.                  */
  406.                 $html_body magicHTML($body$id$message$mailbox);
  407.                 // Convert character set in order to display html mails in different character set
  408.                 $html_body charset_decode($body_message->header->getParameter('charset'),$html_body,false,true);
  409.  
  410.                 // creating iframe url
  411.                 $iframeurl=sqm_baseuri().'src/view_html.php?'
  412.                     . 'mailbox=' $urlmailbox
  413.                     . '&amp;passed_id=' $id
  414.                     . '&amp;ent_id=' $ent_num
  415.                     . '&amp;view_unsafe_images=' . (int) $view_unsafe_images;
  416.  
  417.                 // adding warning message
  418.                 $body html_tag('div',_("Viewing HTML formatted email"),'center');
  419.  
  420.                 /**
  421.                  * height can't be set to 100%, because it does not work as expected when
  422.                  * iframe is inside the table. Browsers do not create full height objects
  423.                  * even when iframe is not nested. Maybe there is some way to get full size
  424.                  * with CSS. Tested in firefox 1.02 and opera 7.53
  425.                  *
  426.                  * width="100%" does not work as expected, when table width is not set (automatic)
  427.                  *
  428.                  * tokul: I think <iframe> are safer sandbox than <object>. Objects might
  429.                  * need special handling for IE and IE6SP2.
  430.                  */
  431.                 $body.= "<div><iframe name=\"message_frame\" width=\"100%\" height=\"$iframe_height\" src=\"$iframeurl\""
  432.                     .' frameborder="1" marginwidth="0" marginheight="0" scrolling="auto">' "\n";
  433.  
  434.                 // Message for browsers without iframe support
  435.                 //$body.= _("Your browser does not support inline frames.
  436.                 // You can view HTML formated message by following below link.");
  437.                 //$body.= "<br /><a href=\"$iframeurl\">"._("View HTML Message")."</a>";
  438.  
  439.                 // if browser can't render iframe, it renders html message.
  440.                 $body.= $html_body;
  441.  
  442.                 // close iframe
  443.                 $body.="</iframe></div>\n";
  444.             else {
  445.                 // old way of html rendering
  446.                 $body magicHTML($body$id$message$mailbox);
  447.                 /**
  448.                  * convert character set. charset_decode does not remove html special chars
  449.                  * applied by magicHTML functions and does not sanitize them second time if
  450.                  * fourth argument is true.
  451.                  */
  452.                 $body charset_decode($body_message->header->getParameter('charset'),$body,false,true);
  453.             }
  454.         else {
  455.             translateText($body$wrap_at,
  456.                     $body_message->header->getParameter('charset'));
  457.         }
  458.  
  459.         // if this is the clean display (i.e. printer friendly), stop here.
  460.         if $clean {
  461.             return $body;
  462.         }
  463.  
  464.         $download_and_unsafe_link '';
  465.  
  466.         $link 'passed_id=' $id '&amp;ent_id='.$ent_num.
  467.             '&amp;mailbox=' $urlmailbox .'&amp;sort=' $sort .
  468.             '&amp;startMessage=' $startMessage '&amp;show_more=0';
  469.         if (isset($passed_ent_id)) {
  470.             $link .= '&amp;passed_ent_id='.$passed_ent_id;
  471.         }
  472.         $download_href SM_PATH 'src/download.php?absolute_dl=true&amp;' $link;
  473.         $download_and_unsafe_link .= '&nbsp;|&nbsp;<a href="'$download_href .'">' _("Download this as a file".  '</a>';
  474.         if ($view_unsafe_images{
  475.             $text _("Hide Unsafe Images");
  476.         else {
  477.             if (isset($has_unsafe_images&& $has_unsafe_images{
  478.                 $link .= '&amp;view_unsafe_images=1';
  479.                 $text _("View Unsafe Images");
  480.             else {
  481.                 $text '';
  482.             }
  483.         }
  484.         if($text != ''{
  485.             $unsafe_image_toggle_href SM_PATH 'src/read_body.php?'.$link;
  486.             $unsafe_image_toggle_text