Source for file imap_mailbox.php

Documentation is available at imap_mailbox.php

  1. <?php
  2.  
  3. /**
  4.  * imap_mailbox.php
  5.  *
  6.  * This implements all functions that manipulate mailboxes
  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_mailbox.php,v 1.277 2006/09/30 07:34:47 tokul Exp $
  11.  * @package squirrelmail
  12.  * @subpackage imap
  13.  */
  14.  
  15. /** UTF7 support */
  16. require_once(SM_PATH 'functions/imap_utf7_local.php');
  17.  
  18.  
  19. /**
  20.  * Mailboxes class
  21.  *
  22.  * FIXME. This class should be extracted and placed in a separate file that
  23.  * can be included before we start the session. That makes caching of the tree
  24.  * possible. On a refresh mailboxes from left_main.php the only function that
  25.  * should be called is the sqimap_get_status_mbx_tree. In case of subscribe
  26.  * / rename / delete / new we have to create methods for adding/changing the
  27.  * mailbox in the mbx_tree without the need for a refresh.
  28.  *
  29.  * Some code fragments are present in 1.3.0 - 1.4.4.
  30.  * @package squirrelmail
  31.  * @subpackage imap
  32.  * @since 1.5.0
  33.  */
  34. class mailboxes {
  35.     var $mailboxname_full = ''$mailboxname_sub''$is_noselect = false$is_noinferiors = false,
  36.         $is_special = false$is_root = false$is_inbox = false$is_sent = false,
  37.         $is_trash = false$is_draft = false,  $mbxs = array(),
  38.         $unseen = false$total = false$recent = false;
  39.  
  40.     function addMbx($mbx$delimiter$start$specialfirst{
  41.         $ary explode($delimiter$mbx->mailboxname_full);
  42.         $mbx_parent =$this;
  43.         for ($i $start$c count($ary)-1$i $c$i++{
  44.             $mbx_childs =$mbx_parent->mbxs;
  45.             $found false;
  46.             if ($mbx_childs{
  47.                 foreach ($mbx_childs as $key => $parent{
  48.                     if ($parent->mailboxname_sub == $ary[$i]{
  49.                         $mbx_parent =$mbx_parent->mbxs[$key];
  50.                         $found true;
  51.                         break;
  52.                     }
  53.                 }
  54.             }
  55.             if (!$found{
  56.                 $no_select_mbx new mailboxes();
  57.                 if (isset($mbx_parent->mailboxname_full&& $mbx_parent->mailboxname_full != ''{
  58.                     $no_select_mbx->mailboxname_full $mbx_parent->mailboxname_full.$delimiter.$ary[$i];
  59.                 else {
  60.                     $no_select_mbx->mailboxname_full $ary[$i];
  61.                 }
  62.                 $no_select_mbx->mailboxname_sub $ary[$i];
  63.                 $no_select_mbx->is_noselect true;
  64.                 $mbx_parent->mbxs[$no_select_mbx;
  65.                 $i--;
  66.             }
  67.         }
  68.         $mbx_parent->mbxs[$mbx;
  69.         if ($mbx->is_special && $specialfirst{
  70.             usort($mbx_parent->mbxs'sortSpecialMbx');
  71.         }
  72.     }
  73. }
  74.  
  75. /**
  76.  * array callback used for sorting in mailboxes class
  77.  * @param object $a 
  78.  * @param object $b 
  79.  * @return integer see php strnatcasecmp()
  80.  * @since 1.3.0
  81.  */
  82. function sortSpecialMbx($a$b{
  83.     if ($a->is_inbox{
  84.         $acmp '0'$a->mailboxname_full;
  85.     else if ($a->is_special{
  86.         $acmp '1'$a->mailboxname_full;
  87.     else {
  88.         $acmp '2' $a->mailboxname_full;
  89.     }
  90.     if ($b->is_inbox{
  91.         $bcmp '0'$b->mailboxname_full;
  92.     }else if ($b->is_special{
  93.         $bcmp '1' $b->mailboxname_full;
  94.     else {
  95.         $bcmp '2' $b->mailboxname_full;
  96.     }
  97.     return strnatcasecmp($acmp$bcmp);
  98. }
  99.  
  100. /**
  101.  * @param array $ary 
  102.  * @return array 
  103.  * @since 1.5.0
  104.  */
  105. function compact_mailboxes_response($ary{
  106.     /*
  107.      * Workaround for mailboxes returned as literal
  108.      * FIXME : Doesn't work if the mailbox name is multiple lines
  109.      * (larger then fgets buffer)
  110.      */
  111.     for ($i 0$iCnt=count($ary)$i $iCnt$i++{
  112.         if (isset($ary[$i 1]&& substr($ary[$i]-3== "}\r\n"{
  113.             if (ereg("^(\\* [A-Z]+.*)\\{[0-9]+\\}([ \n\r\t]*)$",
  114.                  $ary[$i]$regs)) {
  115.                 $ary[$i$regs[1'"' addslashes(trim($ary[$i+1])) '"' $regs[2];
  116.                 array_splice($ary$i+12);
  117.             }
  118.         }
  119.     }
  120.     /* remove duplicates and ensure array is contiguous */
  121.     return array_values(array_unique($ary));
  122. }
  123.  
  124. /**
  125.  * Extract the mailbox name from an untagged LIST (7.2.2) or LSUB (7.2.3) answer
  126.  * (LIST|LSUB) (<Flags list>) (NIL|"<separator atom>") <mailbox name string>\r\n
  127.  * mailbox name in quoted string MUST be unquoted and stripslashed (sm API)
  128.  *
  129.  * Originally stored in functions/strings.php. Since 1.2.6 stored in
  130.  * functions/imap_mailbox.php
  131.  * @param string $line imap LIST/LSUB response line
  132.  * @return string mailbox name
  133.  */
  134. function find_mailbox_name($line{
  135.     if (preg_match('/^\* (?:LIST|LSUB) \([^\)]*\) (?:NIL|\"[^\"]*\") ([^\r\n]*)[\r\n]*$/i'$line$regs)) {
  136.         if (substr($regs[1]01== '"')
  137.             return stripslashes(substr($regs[1]1-1));
  138.         return $regs[1];
  139.     }
  140.     return '';
  141. }
  142.  
  143. /**
  144.  * Detects if mailbox has noselect flag (can't store messages)
  145.  * In versions older than 1.4.5 function checks only LSUB responses
  146.  * and can produce pcre warnings.
  147.  * @param string $lsub_line mailbox line from untagged LIST or LSUB response
  148.  * @return bool whether this is a Noselect mailbox.
  149.  * @since 1.3.2
  150.  */
  151. function check_is_noselect ($lsub_line{
  152.     return preg_match("/^\* (LSUB|LIST) \([^\)]*\\\\Noselect[^\)]*\)/i"$lsub_line);
  153. }
  154.  
  155. /**
  156.  * Detects if mailbox has noinferiors flag (can't store subfolders)
  157.  * @param string $lsub_line mailbox line from untagged LIST or LSUB response
  158.  * @return bool whether this is a Noinferiors mailbox.
  159.  * @since 1.5.0
  160.  */
  161. function check_is_noinferiors ($lsub_line{
  162.     return preg_match("/^\* (LSUB|LIST) \([^\)]*\\\\Noinferiors[^\)]*\)/i"$lsub_line);
  163. }
  164.  
  165. /**
  166.  * Detects mailbox's parent folder
  167.  *
  168.  * If $haystack is a full mailbox name, and $needle is the mailbox
  169.  * separator character, returns the second last part of the full
  170.  * mailbox name (i.e. the mailbox's parent mailbox)
  171.  *
  172.  * Originally stored in functions/strings.php. Since 1.2.6 stored in
  173.  * functions/imap_mailbox.php
  174.  * @param string $haystack full mailbox name
  175.  * @param string $needle delimiter
  176.  * @return string parent mailbox
  177.  */
  178. function readMailboxParent($haystack$needle{
  179.     if ($needle == ''{
  180.         $ret '';
  181.     else {
  182.         $parts explode($needle$haystack);
  183.         $elem array_pop($parts);
  184.         while ($elem == '' && count($parts)) {
  185.             $elem array_pop($parts);
  186.         }
  187.         $ret join($needle$parts);
  188.     }
  189.     return$ret );
  190. }
  191.  
  192. /**
  193.  * Check if $subbox is below the specified $parentbox
  194.  * @param string $subbox potential sub folder
  195.  * @param string $parentbox potential parent
  196.  * @return boolean 
  197.  * @since 1.2.3
  198.  */
  199. function isBoxBelow$subbox$parentbox {
  200.     global $delimiter;
  201.     /*
  202.      * Eliminate the obvious mismatch, where the
  203.      * subfolder path is shorter than that of the potential parent
  204.      */
  205.     if strlen($subboxstrlen($parentbox) ) {
  206.       return false;
  207.     }
  208.     /* check for delimiter */
  209.     if (substr($parentbox,-1!= $delimiter{
  210.         $parentbox .= $delimiter;
  211.     }
  212.  
  213.     return (substr($subbox,0,strlen($parentbox)) == $parentbox);
  214. }
  215.  
  216. /**
  217.  * Defines special mailboxes: given a mailbox name, it checks if this is a
  218.  * "special" one: INBOX, Trash, Sent or Draft.
  219.  *
  220.  * Since 1.2.5 function includes special_mailbox hook.<br>
  221.  * Since 1.4.3 hook supports more than one plugin.
  222.  * @param string $box mailbox name
  223.  * @param boolean $include_subs (since 1.5.2) if true, subfolders of system
  224.  *   folders are special. if false, subfolders are not special mailboxes
  225.  *   unless they are tagged as special in 'special_mailbox' hook.
  226.  * @return boolean 
  227.  * @since 1.2.3
  228.  */
  229. function isSpecialMailbox($box,$include_subs=true{
  230.     $ret ( (strtolower($box== 'inbox'||
  231.              isTrashMailbox($box,$include_subs|| 
  232.              isSentMailbox($box,$include_subs|| 
  233.              isDraftMailbox($box,$include_subs) );
  234.  
  235.     if !$ret {
  236.         $ret boolean_hook_function('special_mailbox',$box,1);
  237.     }
  238.     return $ret;
  239. }
  240.  
  241. /**
  242.  * Detects if mailbox is a Trash folder or subfolder of Trash
  243.  * @param string $box mailbox name
  244.  * @param boolean $include_subs (since 1.5.2) if true, subfolders of system
  245.  *   folders are special. if false, subfolders are not special mailboxes.
  246.  * @return bool whether this is a Trash folder
  247.  * @since 1.4.0
  248.  */
  249. function isTrashMailbox ($box,$include_subs=true{
  250.     global $trash_folder$move_to_trash;
  251.     return $move_to_trash && $trash_folder &&
  252.            $box == $trash_folder || 
  253.              ($include_subs && isBoxBelow($box$trash_folder)) );
  254. }
  255.  
  256. /**
  257.  * Detects if mailbox is a Sent folder or subfolder of Sent
  258.  * @param string $box mailbox name
  259.  * @param boolean $include_subs (since 1.5.2) if true, subfolders of system
  260.  *   folders are special. if false, subfolders are not special mailboxes.
  261.  * @return bool whether this is a Sent folder
  262.  * @since 1.4.0
  263.  */
  264. function isSentMailbox($box,$include_subs=true{
  265.    global $sent_folder$move_to_sent;
  266.    return $move_to_sent && $sent_folder &&
  267.           $box == $sent_folder || 
  268.             ($include_subs && isBoxBelow($box$sent_folder)) );
  269. }
  270.  
  271. /**
  272.  * Detects if mailbox is a Drafts folder or subfolder of Drafts
  273.  * @param string $box mailbox name
  274.  * @param boolean $include_subs (since 1.5.2) if true, subfolders of system
  275.  *   folders are special. if false, subfolders are not special mailboxes.
  276.  * @return bool whether this is a Draft folder
  277.  * @since 1.4.0
  278.  */
  279. function isDraftMailbox($box,$include_subs=true{
  280.    global $draft_folder$save_as_draft;
  281.    return $save_as_draft &&
  282.           $box == $draft_folder || 
  283.             ($include_subs && isBoxBelow($box$draft_folder)) );
  284. }
  285.  
  286. /**
  287.  * Expunges a mailbox
  288.  *
  289.  * WARNING: Select mailbox before calling this function.
  290.  *
  291.  * permanently removes all messages that have the \Deleted flag
  292.  * set from the selected mailbox. See EXPUNGE command chapter in
  293.  * IMAP RFC.
  294.  * @param stream $imap_stream imap connection resource
  295.  * @param string $mailbox mailbox name (unused since 1.1.3).
  296.  * @param boolean $handle_errors error handling control (displays error_box on error).
  297.  * @param mixed $id (since 1.3.0) integer message id or array with integer ids
  298.  * @return integer number of expunged messages
  299.  * @since 1.0 or older
  300.  */
  301. function sqimap_mailbox_expunge ($imap_stream$mailbox$handle_errors true$id=''{
  302.     if ($id{
  303.         if (is_array($id)) {
  304.             $id sqimap_message_list_squisher($id);
  305.         }
  306.         $id ' '.$id;
  307.         $uid TRUE;
  308.     else {
  309.         $uid false;
  310.     }
  311.     $read sqimap_run_command($imap_stream'EXPUNGE'.$id$handle_errors,
  312.                                $response$message$uid);
  313.     $cnt 0;
  314.  
  315.     if (is_array($read)) {
  316.         foreach ($read as $r{
  317.             if (preg_match('/^\*\s[0-9]+\sEXPUNGE/AUi',$r,$regs)) {
  318.                 $cnt++;
  319.             }
  320.         }
  321.     }
  322.     return $cnt;
  323. }
  324.  
  325. /**
  326.  * Checks whether or not the specified mailbox exists
  327.  *
  328.  * @param stream $imap_stream imap connection resource
  329.  * @param string $mailbox mailbox name
  330.  * @param array $mailboxlist (since 1.5.1) optional array of mailboxes from
  331.  *   sqimap_get_mailboxes() (to avoid having to talk to imap server)
  332.  * @return boolean 
  333.  * @since 1.0 or older
  334.  */
  335. function sqimap_mailbox_exists ($imap_stream$mailbox$mailboxlist=null{
  336.     if (!isset($mailbox|| empty($mailbox)) {
  337.         return false;
  338.     }
  339.  
  340.     if (is_array($mailboxlist)) {
  341.         // use previously retrieved mailbox list
  342.         foreach ($mailboxlist as $mbox{
  343.             if ($mbox['unformatted-dm'== $mailboxreturn true}
  344.         }
  345.         return false;
  346.     else {
  347.         // go to imap server
  348.         $mbx sqimap_run_command($imap_stream'LIST "" ' sqimap_encode_mailbox_name($mailbox),
  349.                                   true$response$message);
  350.         return isset($mbx[0]);
  351.     }
  352. }
  353.  
  354. /**
  355.  * Selects a mailbox
  356.  * Before 1.3.0 used more arguments and returned data depended on those arguments.
  357.  * @param stream $imap_stream imap connection resource
  358.  * @param string $mailbox mailbox name
  359.  * @return array results of select command (on success - permanentflags, flags and rights)
  360.  * @since 1.0 or older
  361.  */
  362. function sqimap_mailbox_select ($imap_stream$mailbox{
  363.     // FIX ME: WHAAAA DO NOT USE "None" for something that does not exist. Use false or NULL instead
  364.     if ($mailbox == 'None'{
  365.         return;
  366.     }
  367.  
  368.     // cleanup $mailbox in order to prevent IMAP injection attacks
  369.     $mailbox str_replace(array("\r","\n")array("",""),$mailbox);
  370.  
  371.     /**
  372.      * Default UW IMAP server configuration allows to access other files
  373.      * on server. $imap_server_type is not checked because interface can
  374.      * be used with 'other' or any other server type setting. $mailbox
  375.      * variable can be modified in any script that uses variable from GET
  376.      * or POST. This code blocks all standard SquirrelMail IMAP API requests
  377.      * that use mailbox with full path (/etc/passwd) or with ../ characters
  378.      * in path (../../etc/passwd)
  379.      */
  380.     if (strstr($mailbox'../'|| substr($mailbox01== '/'{
  381.         global $oTemplate;
  382.         error_box(sprintf(_("Invalid mailbox name: %s"),htmlspecialchars($mailbox)));
  383.         sqimap_logout($imap_stream);
  384.         $oTemplate->display('footer.tpl');
  385.         die();
  386.     }
  387.  
  388.     $read sqimap_run_command($imap_stream'SELECT ' sqimap_encode_mailbox_name($mailbox),
  389.                                true$response$message);
  390.     $result array();
  391.     for ($i 0$cnt count($read)$i $cnt$i++{
  392.         if (preg_match('/^\*\s+OK\s\[(\w+)\s(\w+)\]/',$read[$i]$regs)) {
  393.             $result[strtoupper($regs[1])$regs[2];
  394.         else if (preg_match('/^\*\s([0-9]+)\s(\w+)/',$read[$i]$regs)) {
  395.             $result[strtoupper($regs[2])$regs[1];
  396.         else {
  397.             if (preg_match("/PERMANENTFLAGS(.*)/i",$read[$i]$regs)) {
  398.                 $regs[1]=trim(preg_replace (  array ("/\(/","/\)/","/\]/",''$regs[1])) ;
  399.                 $result['PERMANENTFLAGS'explode(' ',strtolower($regs[1]));
  400.             else if (preg_match("/FLAGS(.*)/i",$read[$i]$regs)) {
  401.                 $regs[1]=trim(preg_replace (  array ("/\(/","/\)/",''$regs[1])) ;
  402.                 $result['FLAGS'explode(' ',strtolower($regs[1]));
  403.             }
  404.         }
  405.     }
  406.     if (!isset($result['PERMANENTFLAGS'])) {
  407.         $result['PERMANENTFLAGS'$result['FLAGS'];
  408.     }
  409.     if (preg_match('/^\[(.+)\]/',$message$regs)) {
  410.         $result['RIGHTS']=strtoupper($regs[1]);
  411.     }
  412.  
  413.     return $result;
  414. }
  415.  
  416. /**
  417.  * Creates a folder.
  418.  *
  419.  * Mailbox is automatically subscribed.
  420.  *
  421.  * Set $type to string that does not match 'noselect' (case insensitive),
  422.  * if you don't want to prepend delimiter to mailbox name. Please note
  423.  * that 'noinferiors' might be used someday as keyword for folders
  424.  * that store only messages.
  425.  * @param stream $imap_steam imap connection resource
  426.  * @param string $mailbox mailbox name
  427.  * @param string $type folder type.
  428.  * @since 1.0 or older
  429.  */
  430. function sqimap_mailbox_create ($imap_stream$mailbox$type{
  431.     global $delimiter;
  432.     if (strtolower($type== 'noselect'{
  433.         $create_mailbox $mailbox $delimiter;
  434.     else {
  435.         $create_mailbox $mailbox;
  436.     }
  437.  
  438.     $read_ary sqimap_run_command($imap_stream'CREATE ' .
  439.                                    sqimap_encode_mailbox_name($create_mailbox),
  440.                                    true$response$message);
  441.     sqimap_subscribe ($imap_stream$mailbox);
  442. }
  443.  
  444. /**
  445.  * Subscribes to an existing folder.
  446.  * @param stream $imap_stream imap connection resource
  447.  * @param string $mailbox mailbox name
  448.  * @param boolean $debug (since 1.5.1)
  449.  * @since 1.0 or older
  450.  */
  451. function sqimap_subscribe ($imap_stream$mailbox,$debug=true{
  452.     $read_ary sqimap_run_command($imap_stream'SUBSCRIBE ' .
  453.                                    sqimap_encode_mailbox_name($mailbox),
  454.                                    $debug$response$message);
  455. }
  456.  
  457. /**
  458.  * Unsubscribes from an existing folder
  459.  * @param stream $imap_stream imap connection resource
  460.  * @param string $mailbox mailbox name
  461.  * @since 1.0 or older
  462.  */
  463. function sqimap_unsubscribe ($imap_stream$mailbox{
  464.     $read_ary sqimap_run_command($imap_stream'UNSUBSCRIBE ' .
  465.                                    sqimap_encode_mailbox_name($mailbox),
  466.                                    false$response$message);
  467. }
  468.  
  469. /**
  470.  * Deletes the given folder
  471.  * Since 1.2.6 and 1.3.0 contains rename_or_delete_folder hook
  472.  * @param stream $imap_stream imap connection resource
  473.  * @param string $mailbox mailbox name
  474.  * @since 1.0 or older
  475.  */
  476. function sqimap_mailbox_delete ($imap_stream$mailbox{
  477.     global $data_dir$username;
  478.     sqimap_unsubscribe ($imap_stream$mailbox);
  479.  
  480.     if (sqimap_mailbox_exists($imap_stream$mailbox)) {
  481.  
  482.         $read_ary sqimap_run_command($imap_stream'DELETE ' .
  483.                                        sqimap_encode_mailbox_name($mailbox),
  484.                                        true$response$message);
  485.         if ($response !== 'OK'{
  486.             // subscribe again
  487.             sqimap_subscribe ($imap_stream$mailbox);
  488.         else {
  489.             do_hook_function('rename_or_delete_folder'$args array($mailbox'delete'''));
  490.             removePref($data_dir$username"thread_$mailbox");
  491.             removePref($data_dir$username"collapse_folder_$mailbox");