Source for file imap_general.php

Documentation is available at imap_general.php

  1. <?php
  2.  
  3. /**
  4.  * imap_general.php
  5.  *
  6.  * This implements all functions that do general IMAP functions.
  7.  *
  8.  * @copyright &copy; 1999-2006 The SquirrelMail Project Team
  9.  * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  10.  * @version $Id: imap_general.php,v 1.237 2006/10/04 19:25:25 stekkel Exp $
  11.  * @package squirrelmail
  12.  * @subpackage imap
  13.  */
  14.  
  15. /** Includes.. */
  16.  
  17. require_once(SM_PATH 'functions/rfc822address.php');
  18.  
  19.  
  20. /**
  21.  * Generates a new session ID by incrementing the last one used;
  22.  * this ensures that each command has a unique ID.
  23.  * @param bool $unique_id (since 1.3.0) controls use of unique
  24.  *   identifiers/message sequence numbers in IMAP commands. See IMAP
  25.  *   rfc 'UID command' chapter.
  26.  * @return string IMAP session id of the form 'A000'.
  27.  * @since 1.2.0
  28.  */
  29. function sqimap_session_id($unique_id FALSE{
  30.     static $sqimap_session_id 1;
  31.  
  32.     if (!$unique_id{
  33.         returnsprintf("A%03d"$sqimap_session_id++) );
  34.     else {
  35.         returnsprintf("A%03d"$sqimap_session_id++' UID' );
  36.     }
  37. }
  38.  
  39. /**
  40.  * Both send a command and accept the result from the command.
  41.  * This is to allow proper session number handling.
  42.  * @param stream $imap_stream imap connection resource
  43.  * @param string $query imap command
  44.  * @param boolean $handle_errors see sqimap_retrieve_imap_response()
  45.  * @param array $response 
  46.  * @param array $message 
  47.  * @param boolean $unique_id (since 1.3.0) see sqimap_session_id().
  48.  * @return mixed returns false on imap error. displays error message
  49.  *   if imap stream is not available.
  50.  * @since 1.2.3
  51.  */
  52. function sqimap_run_command_list ($imap_stream$query$handle_errors&$response&$message$unique_id false{
  53.     if ($imap_stream{
  54.         $sid sqimap_session_id($unique_id);
  55.         fputs ($imap_stream$sid ' ' $query "\r\n");
  56.         $tag_uid_a explode(' ',trim($sid));
  57.         $tag $tag_uid_a[0];
  58.         $read sqimap_retrieve_imap_response ($imap_stream$tag$handle_errors$response$message$query );
  59.         /* get the response and the message */
  60.         $message $message[$tag];
  61.         $response $response[$tag];
  62.         return $read[$tag];
  63.     else {
  64.         global $squirrelmail_language$color;
  65.         set_up_language($squirrelmail_language);
  66.         $string "<b><font color=\"$color[2]\">\n.
  67.                 _("ERROR: No available IMAP stream.".
  68.                 "</b></font>\n";
  69.         error_box($string);
  70.         return false;
  71.     }
  72. }
  73.  
  74. /**
  75.  * @param stream $imap_stream imap connection resource
  76.  * @param string $query imap command
  77.  * @param boolean $handle_errors see sqimap_retrieve_imap_response()
  78.  * @param array $response empty string, if return = false
  79.  * @param array $message empty string, if return = false
  80.  * @param boolean $unique_id (since 1.3.0) see sqimap_session_id()
  81.  * @param boolean $filter (since 1.4.1 and 1.5.0) see sqimap_fread()
  82.  * @param mixed $outputstream (since 1.4.1 and 1.5.0) see sqimap_fread()
  83.  * @param boolean $no_return (since 1.4.1 and 1.5.0) see sqimap_fread()
  84.  * @return mixed returns false on imap error. displays error message
  85.  *   if imap stream is not available.
  86.  * @since 1.2.3
  87.  */
  88. function sqimap_run_command ($imap_stream$query$handle_errors&$response,
  89.                             &$message$unique_id false,$filter=false,
  90.                              $outputstream=false,$no_return=false{
  91.     if ($imap_stream{
  92.         $sid sqimap_session_id($unique_id);
  93.         fputs ($imap_stream$sid ' ' $query "\r\n");
  94.         $tag_uid_a explode(' ',trim($sid));
  95.         $tag $tag_uid_a[0];
  96.  
  97.         $read sqimap_read_data ($imap_stream$tag$handle_errors$response,
  98.                                   $message$query,$filter,$outputstream,$no_return);
  99.         if (empty($read)) {    //IMAP server dropped its connection
  100.             $response '';
  101.             $message '';
  102.             return false;
  103.         }
  104.         /* retrieve the response and the message */
  105.         $response $response[$tag];
  106.         $message  $message[$tag];
  107.  
  108.         if (!empty($read[$tag])) {
  109.             return $read[$tag][0];
  110.         else {
  111.             return $read[$tag];
  112.         }
  113.     else {
  114.         global $squirrelmail_language$color;
  115.         set_up_language($squirrelmail_language);
  116.         $string "<b><font color=\"$color[2]\">\n.
  117.                 _("ERROR: No available IMAP stream.".
  118.                 "</b></font>\n";
  119.         error_box($string);
  120.         return false;
  121.     }
  122. }
  123.  
  124. /**
  125.  * @param mixed $new_query 
  126.  * @param string $tag 
  127.  * @param array $aQuery 
  128.  * @param boolean $unique_id see sqimap_session_id()
  129.  * @since 1.5.0
  130.  */
  131. function sqimap_prepare_pipelined_query($new_query,&$tag,&$aQuery,$unique_id{
  132.     $sid sqimap_session_id($unique_id);
  133.     $tag_uid_a explode(' ',trim($sid));
  134.     $tag $tag_uid_a[0];
  135.     $query $sid ' '.$new_query."\r\n";
  136.     $aQuery[$tag$query;
  137. }
  138.  
  139. /**
  140.  * @param stream $imap_stream imap stream
  141.  * @param array $aQueryList 
  142.  * @param boolean $handle_errors 
  143.  * @param array $aServerResponse 
  144.  * @param array $aServerMessage 
  145.  * @param boolean $unique_id see sqimap_session_id()
  146.  * @param boolean $filter see sqimap_fread()
  147.  * @param mixed $outputstream see sqimap_fread()
  148.  * @param boolean $no_return see sqimap_fread()
  149.  * @since 1.5.0
  150.  */
  151. function sqimap_run_pipelined_command ($imap_stream$aQueryList$handle_errors,
  152.                        &$aServerResponse&$aServerMessage$unique_id false,
  153.                        $filter=false,$outputstream=false,$no_return=false{
  154.     $aResponse false;
  155.  
  156.     /*
  157.        Do not fire all calls at once to the IMAP server but split the calls up
  158.        in portions of $iChunkSize. If we do not do that I think we misbehave as
  159.        IMAP client or should handle BYE calls if the IMAP server drops the
  160.        connection because the number of queries is to large. This isn't tested
  161.        but a wild guess how it could work in the field.
  162.  
  163.        After testing it on Exchange 2000 we discovered that a chunksize of 32
  164.        was quicker then when we raised it to 128.
  165.     */
  166.     $iQueryCount count($aQueryList);
  167.     $iChunkSize 32;
  168.     // array_chunk would also do the job but it's supported from php > 4.2
  169.     $aQueryChunks array();
  170.     $iLoops floor($iQueryCount $iChunkSize);
  171.  
  172.     if ($iLoops $iChunkSize != $iQueryCount++$iLoops;
  173.  
  174.     if (!function_exists('array_chunk')) // arraychunk replacement
  175.         reset($aQueryList);
  176.         for($i=0;$i<$iLoops;++$i{
  177.             for($j=0;$j<$iChunkSize;++$j{
  178.                 $key key($aQueryList);
  179.                 $aTmp[$key$aQueryList[$key];
  180.                 if (next($aQueryList=== falsebreak;
  181.             }
  182.             $aQueryChunks[$aTmp;
  183.         }
  184.     else {
  185.         $aQueryChunks array_chunk($aQueryList,$iChunkSize,true);
  186.     }
  187.  
  188.     for ($i=0;$i<$iLoops;++$i{
  189.         $aQuery $aQueryChunks[$i];
  190.         foreach($aQuery as $tag => $query{
  191.             fputs($imap_stream,$query);
  192.             $aResults[$tagfalse;
  193.         }
  194.         foreach($aQuery as $tag => $query{
  195.             if ($aResults[$tag== false{
  196.                 $aReturnedResponse sqimap_retrieve_imap_response ($imap_stream$tag,
  197.                                     $handle_errors$response$message$query,
  198.                                     $filter,$outputstream,$no_return);
  199.                 foreach ($aReturnedResponse as $returned_tag => $aResponse{
  200.                     if (!empty($aResponse)) {
  201.                         $aResults[$returned_tag$aResponse[0];
  202.                     else {
  203.                         $aResults[$returned_tag$aResponse;
  204.                     }
  205.                     $aServerResponse[$returned_tag$response[$returned_tag];
  206.                     $aServerMessage[$returned_tag$message[$returned_tag];
  207.                 }
  208.             }
  209.         }
  210.     }
  211.     return $aResults;
  212. }
  213.  
  214. /**
  215.  * Custom fgets function: gets a line from the IMAP server,
  216.  * no matter how big it may be.
  217.  * @param stream $imap_stream the stream to read from
  218.  * @return string a line
  219.  * @since 1.2.8
  220.  */
  221. function sqimap_fgets($imap_stream{
  222.     $read '';
  223.     $buffer 4096;
  224.     $results '';
  225.     $offset 0;
  226.     while (strpos($results"\r\n"$offset=== false{
  227.         if (!($read fgets($imap_stream$buffer))) {
  228.         /* this happens in case of an error */
  229.         /* reset $results because it's useless */
  230.         $results false;
  231.             break;
  232.         }
  233.         if $results != '' {
  234.             $offset strlen($results1;
  235.         }
  236.         $results .= $read;
  237.     }
  238.     return $results;
  239. }
  240.  
  241. /**
  242.  * @param stream $imap_stream 
  243.  * @param integer $iSize 
  244.  * @param boolean $filter 
  245.  * @param mixed $outputstream stream or 'php://stdout' string
  246.  * @param boolean $no_return controls data returned by function
  247.  * @return string 
  248.  * @since 1.4.1
  249.  */
  250. function sqimap_fread($imap_stream,$iSize,$filter=false,
  251.                       $outputstream=false$no_return=false{
  252.     if (!$filter || !$outputstream{
  253.         $iBufferSize $iSize;
  254.     else {
  255.         // see php bug 24033. They changed fread behaviour %$^&$%
  256.         $iBufferSize 7800// multiple of 78 in case of base64 decoding.
  257.     }
  258.     if ($iSize $iBufferSize{
  259.         $iBufferSize $iSize;
  260.     }
  261.  
  262.     $iRetrieved 0;
  263.     $results '';
  264.     $sRead $sReadRem '';
  265.     // NB: fread can also stop at end of a packet on sockets.
  266.     while ($iRetrieved $iSize{
  267.         $sRead fread($imap_stream,$iBufferSize);
  268.         $iLength strlen($sRead);
  269.         $iRetrieved += $iLength ;
  270.         $iRemaining $iSize $iRetrieved;
  271.         if ($iRemaining $iBufferSize{
  272.             $iBufferSize $iRemaining;
  273.         }
  274.         if ($sRead == ''{
  275.             $results false;
  276.             break;
  277.         }
  278.         if ($sReadRem != ''{
  279.             $sRead $sReadRem $sRead;
  280.             $sReadRem '';
  281.         }
  282.  
  283.         if ($filter && $sRead != ''{
  284.            // in case the filter is base64 decoding we return a remainder
  285.            $sReadRem $filter($sRead);
  286.         }
  287.         if ($outputstream && $sRead != ''{
  288.            if (is_resource($outputstream)) {
  289.                fwrite($outputstream,$sRead);
  290.            else if ($outputstream == 'php://stdout'{
  291.                echo $sRead;
  292.            }
  293.         }
  294.         if ($no_return{
  295.             $sRead '';
  296.         else {
  297.             $results .= $sRead;
  298.         }
  299.     }
  300.     return $results;
  301. }
  302.  
  303.  
  304. /**
  305.  * Obsolete function, inform plugins that use it
  306.  * @param stream $imap_stream 
  307.  * @param string $tag 
  308.  * @param boolean $handle_errors 
  309.  * @param array $response 
  310.  * @param array $message 
  311.  * @param string $query 
  312.  * @since 1.1.3
  313.  * @deprecated (since 1.5.0) use sqimap_run_command or sqimap_run_command_list instead
  314.  */
  315. function sqimap_read_data_list($imap_stream$tag$handle_errors,
  316.           &$response&$message$query ''{
  317.     global $color$oTemplate$squirrelmail_language;
  318.     set_up_language($squirrelmail_language);
  319.     $string "<b><font color=\"$color[2]\">\n.
  320.         _("ERROR: Bad function call.".
  321.         "</b><br />\n" .
  322.         _("Reason:"' '.
  323.           'There is a plugin installed which make use of the  <br />' .
  324.           'SquirrelMail internal function sqimap_read_data_list.<br />'.
  325.           'Please adapt the installed plugin and let it use<br />'.
  326.           'sqimap_run_command or sqimap_run_command_list instead<br /><br />'.
  327.           'The following query was issued:<br />'.
  328.            htmlspecialchars($query'<br />' "</font><br />\n";
  329.     error_box($string);
  330.     $oTemplate->display('footer.tpl');
  331.     exit;
  332. }
  333.  
  334. /**
  335.  * Function to display an error related to an IMAP query.
  336.  * @param string title the caption of the error box
  337.  * @param string query the query that went wrong
  338.  * @param string message_title optional message title
  339.  * @param string message optional error message
  340.  * @param string $link an optional link to try again
  341.  * @return void 
  342.  * @since 1.5.0
  343.  */
  344. function sqimap_error_box($title$query ''$message_title ''$message ''$link '')
  345. {
  346.     global $color$squirrelmail_language;
  347.  
  348.     set_up_language($squirrelmail_language);
  349.     $string "<font color=\"$color[2]\"><b>\n$title "</b><br />\n";
  350.     $cmd explode(' ',$query);
  351.     $cmdstrtolower($cmd[0]);
  352.  
  353.     if ($query != '' &&  $cmd != 'login')
  354.         $string .= _("Query:"' ' htmlspecialchars($query'<br />';
  355.     if ($message_title != '')
  356.         $string .= $message_title;
  357.     if ($message != '')
  358.         $string .= htmlspecialchars($message);
  359.     $string .= "</font><br />\n";
  360.     if ($link != '')
  361.         $string .= $link;
  362.     error_box($string);
  363. }
  364.  
  365. /**
  366.  * Reads the output from the IMAP stream.  If handle_errors is set to true,
  367.  * this will also handle all errors that are received.  If it is not set,
  368.  * the errors will be sent back through $response and $message.
  369.  * @param stream $imap_stream imap stream
  370.  * @param string $tag 
  371.  * @param boolean $handle_errors handle errors internally or send them in $response and $message.
  372.  * @param array $response 
  373.  * @param array $message 
  374.  * @param string $query command that can be printed if something fails
  375.  * @param boolean $filter see sqimap_fread()
  376.  * @param mixed $outputstream  see sqimap_fread()
  377.  * @param boolean $no_return  see sqimap_fread()
  378.  * @since 1.5.0
  379.  */
  380. function sqimap_retrieve_imap_response($imap_stream$tag$handle_errors,
  381.           &$response&$message$query '',
  382.            $filter false$outputstream false$no_return false{
  383.     global $color$squirrelmail_language;
  384.     $read '';
  385.     if (!is_array($message)) $message array();
  386.     if (!is_array($response)) $response array();
  387.     $aResponse '';
  388.     $resultlist array();
  389.     $data array();
  390.     $sCommand '';
  391.     if (preg_match("/^(\w+)\s*/",$query,$aMatch)) {
  392.         $sCommand strtoupper($aMatch[1]);
  393.     else {
  394.         // error reporting (shouldn't happen)
  395.     }
  396.     $read sqimap_fgets($imap_stream);
  397.     $i 0;
  398.     while ($read{
  399.         $char $read{0};
  400.         switch ($char)
  401.         {
  402.           case '+':
  403.           default:
  404.             $read sqimap_fgets($imap_stream);
  405.             break;
  406.  
  407.           case $tag{0}:
  408.           {
  409.             /* get the command */
  410.             $arg '';
  411.             $i strlen($tag)+1;
  412.             $s substr($read,$i);
  413.             if (($j strpos($s,' ')) || ($j strpos($s,"\n"))) {
  414.                 $arg substr($s,0,$j);
  415.             }
  416.             $found_tag substr($read,0,$i-1);
  417.             if ($found_tag{
  418.                 switch ($arg)
  419.                 {
  420.                   case 'OK':
  421.                   case 'BAD':
  422.                   case 'NO':
  423.                   case 'BYE':
  424.                   case 'PREAUTH':
  425.                     $response[$found_tag$arg;
  426.                     $message[$found_tagtrim(substr($read,$i+strlen($arg)));
  427.                     if (!empty($data)) {
  428.                         $resultlist[$data;
  429.                     }
  430.                     $aResponse[$found_tag$resultlist;
  431.                     $data $resultlist array();
  432.                     if ($found_tag == $tag{
  433.                         break 3/* switch switch while */
  434.                     }
  435.                   break;
  436.                   default:
  437.                     /* this shouldn't happen */
  438.                     $response[$found_tag$arg;
  439.                     $message[$found_tagtrim(substr($read,$i+strlen($arg)));
  440.                     if (!empty($data)) {
  441.                         $resultlist[$data;
  442.                     }
  443.                     $aResponse[$found_tag$resultlist;
  444.                     $data $resultlist array();
  445.                     if ($found_tag == $tag{
  446.                         break 3/* switch switch while */
  447.                     }
  448.                 }
  449.             }
  450.             $read sqimap_fgets($imap_stream);
  451.             if ($read === false/* error */
  452.                  break 2/* switch while */
  453.             }
  454.             break;
  455.           // end case $tag{0}
  456.  
  457.           case '*':
  458.           {
  459.             if (($sCommand == "FETCH" || $sCommand == "STORE")  && preg_match('/^\*\s\d+\sFETCH/',$read)) {
  460.                 /* check for literal */
  461.                 $s substr($read,-3);
  462.                 $fetch_data array();
  463.                 do /* outer loop, continue until next untagged fetch
  464.                         or tagged reponse */
  465.                     do /* innerloop for fetching literals. with this loop
  466.                             we prohibid that literal responses appear in the
  467.                             outer loop so we can trust the untagged and
  468.                             tagged info provided by $read */
  469.                         if ($s === "}\r\n"{
  470.                             $j strrpos($read,'{');
  471.                             $iLit substr($read,$j+1,-3);
  472.                             $fetch_data[$read;
  473.                             $sLiteral sqimap_fread($imap_stream,$iLit,$filter,$outputstream,$no_return);
  474.                             if ($sLiteral === false/* error */
  475.                                 break 4;&nbs