Source for file imap_messages.php

Documentation is available at imap_messages.php

  1. <?php
  2.  
  3. /**
  4.  * imap_messages.php
  5.  *
  6.  * This implements functions that manipulate messages
  7.  * NOTE: Quite a few functions in this file are obsolete
  8.  *
  9.  * @copyright 1999-2020 The SquirrelMail Project Team
  10.  * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  11.  * @version $Id: imap_messages.php 14845 2020-01-07 08:09:34Z pdontthink $
  12.  * @package squirrelmail
  13.  * @subpackage imap
  14.  */
  15.  
  16.  
  17. /**
  18.  * Copy a set of messages ($id) to another mailbox ($mailbox)
  19.  * @param int $imap_stream The resource ID for the IMAP socket
  20.  * @param string $id The list of messages to copy
  21.  * @param string $mailbox The destination to copy to
  22.  * @param bool $handle_errors Show error messages in case of a NO, BAD or BYE response
  23.  * @return bool If the copy completed without errors
  24.  */
  25. function sqimap_msgs_list_copy($imap_stream, $id, $mailbox, $handle_errors = true) {
  26.     $msgs_id = sqimap_message_list_squisher($id);
  27.     $read = sqimap_run_command ($imap_stream, "COPY $msgs_id " . sqimap_encode_mailbox_name($mailbox), $handle_errors, $response, $message, TRUE);
  28.     if ($response == 'OK') {
  29.         return true;
  30.     } else {
  31.         return false;
  32.     }
  33. }
  34.  
  35.  
  36. /**
  37.  * Move a set of messages ($id) to another mailbox. Deletes the originals.
  38.  * @param int $imap_stream The resource ID for the IMAP socket
  39.  * @param string $id The list of messages to move
  40.  * @param string $mailbox The destination to move to
  41.  * @param bool $handle_errors Show error messages in case of a NO, BAD or BYE response
  42.  * @param string $source_mailbox (since 1.5.1) name of source mailbox. It is used to
  43.  *   validate that target mailbox != source mailbox.
  44.  * @return bool If the move completed without errors
  45.  */
  46. function sqimap_msgs_list_move($imap_stream, $id, $mailbox, $handle_errors = true, $source_mailbox = false) {
  47.     if ($source_mailbox!==false && $source_mailbox==$mailbox) {
  48.         return false;
  49.     }
  50.     if (sqimap_msgs_list_copy ($imap_stream, $id, $mailbox, $handle_errors)) {
  51.         return sqimap_toggle_flag($imap_stream, $id, '\\Deleted', true, true);
  52.     } else {
  53.         return false;
  54.     }
  55. }
  56.  
  57.  
  58. /**
  59.  * Deletes a message and move it to trash or expunge the mailbox
  60.  * @param  resource imap connection
  61.  * @param  string $mailbox mailbox, used for checking if it concerns the trash_folder
  62.  * @param  array $id list with uid's
  63.  * @param  bool   $bypass_trash (since 1.5.0) skip copy to trash
  64.  * @return array  $aMessageList array with messages containing the new flags and UID @see parseFetch
  65.  * @since 1.4.0
  66.  */
  67. function sqimap_msgs_list_delete($imap_stream, $mailbox, $id, $bypass_trash=false) {
  68.     // FIXME: Remove globals by introducing an associative array with properties as 4th argument as replacement for the $bypass_trash variable.
  69.     global $move_to_trash, $trash_folder;
  70.     if (($move_to_trash == true) && ($bypass_trash != true) &&
  71.         (sqimap_mailbox_exists($imap_stream, $trash_folder) &&  ($mailbox != $trash_folder)) ) {
  72.         /**
  73.          * turn off internal error handling (fourth argument = false) and
  74.          * ignore copy to trash errors (allows to delete messages when overquota)
  75.          */
  76.         sqimap_msgs_list_copy ($imap_stream, $id, $trash_folder, false);
  77.     }
  78.     return sqimap_toggle_flag($imap_stream, $id, '\\Deleted', true, true);
  79. }
  80.  
  81.  
  82. /**
  83.  * Set a flag on the provided uid list
  84.  * @param  resource imap connection
  85.  * @param  array $id list with uid's
  86.  * @param  string $flag Flags to set/unset flags can be i.e.'\Seen', '\Answered', '\Seen \Answered'
  87.  * @param  bool   $set  add (true) or remove (false) the provided flag
  88.  * @param  bool   $handle_errors Show error messages in case of a NO, BAD or BYE response
  89.  * @return array  $aMessageList array with messages containing the new flags and UID @see parseFetch
  90.  */
  91. function sqimap_toggle_flag($imap_stream, $id, $flag, $set, $handle_errors) {
  92.     $msgs_id = sqimap_message_list_squisher($id);
  93.     $set_string = ($set ? '+' : '-');
  94.  
  95.     for ($i=0; $i<sizeof($id); $i++) {
  96.         $aMessageList["$id[$i]"] = array();
  97.     }
  98.  
  99.     $aResponse = sqimap_run_command_list($imap_stream, "STORE $msgs_id ".$set_string."FLAGS ($flag)", $handle_errors, $response, $message, TRUE);
  100.  
  101.     // parse the fetch response
  102.     $parseFetchResults=parseFetch($aResponse,$aMessageList);
  103.  
  104.     // some broken IMAP servers do not return UID elements on UID STORE
  105.     // if this is the case, then we need to do a UID FETCH
  106.     $testkey=$id[0];
  107.     if (!isset($parseFetchResults[$testkey]['UID'])) {
  108.         $aResponse = sqimap_run_command_list($imap_stream, "FETCH $msgs_id (FLAGS)", $handle_errors, $response, $message, TRUE);
  109.         $parseFetchResults = parseFetch($aResponse,$aMessageList);
  110.     }
  111.  
  112.     return ($parseFetchResults);
  113. }
  114.  
  115.  
  116. /**
  117.  * Sort the message list and crunch to be as small as possible
  118.  * (overflow could happen, so make it small if possible)
  119.  * @param array $aUid array with uid's
  120.  * @return string $s message set string
  121.  */
  122. function sqimap_message_list_squisher($aUid) {
  123.     if( !is_array( $aUid ) ) {
  124.         return $aUid;
  125.     }
  126.     sort($aUid, SORT_NUMERIC);
  127.  
  128.     if (count($aUid)) {
  129.         $s = '';
  130.         for ($i=0,$iCnt=count($aUid);$i<$iCnt;++$i) {
  131.             $iStart = $aUid[$i];
  132.             $iEnd = $iStart;
  133.             while ($i<($iCnt-1) && $aUid[$i+1] == $iEnd +1) {
  134.                 $iEnd = $aUid[$i+1];
  135.                 ++$i;
  136.             }
  137.             if ($s) {
  138.                 $s .= ',';
  139.             }
  140.             $s .= $iStart;
  141.             if ($iStart != $iEnd) {
  142.                 $s .= ':' . $iEnd;
  143.             }
  144.         }
  145.     }
  146.     return $s;
  147. }
  148.  
  149.  
  150. /**
  151.  * Retrieves an array with a sorted uid list. Sorting is done on the imap server
  152.  * @link http://www.ietf.org/internet-drafts/draft-ietf-imapext-sort-17.txt
  153.  * @param resource $imap_stream IMAP socket connection
  154.  * @param string $sSortField Field to sort on
  155.  * @param bool $reverse Reverse order search
  156.  * @return array $id sorted uid list
  157.  */
  158. function sqimap_get_sort_order($imap_stream, $sSortField, $reverse, $search='ALL') {
  159.     global  $default_charset;
  160.  
  161.     if ($sSortField) {
  162.         if ($reverse) {
  163.             $sSortField = 'REVERSE '.$sSortField;
  164.         }
  165.         $query = "SORT ($sSortField) ".strtoupper($default_charset)." $search";
  166.         // FIXME: sqimap_run_command() should return the parsed data accessible by $aDATA['SORT']
  167.         // use sqimap_run_command_list() in case of unsolicited responses. If we don't we could loose the SORT response.
  168.         $aData = sqimap_run_command_list ($imap_stream, $query, false, $response, $message, TRUE);
  169.         /* fallback to default charset */
  170.         if ($response == 'NO') {
  171.             if (strpos($message,'BADCHARSET') !== false ||
  172.                 strpos($message,'character') !== false) {
  173.                 sqm_trigger_imap_error('SQM_IMAP_BADCHARSET',$query, $response, $message);
  174.                 $query = "SORT ($sSortField) US-ASCII $search";
  175.                 $aData = sqimap_run_command_list ($imap_stream, $query, true, $response, $message, TRUE);
  176.             } else {
  177.                 sqm_trigger_imap_error('SQM_IMAP_ERROR',$query, $response, $message);
  178.             }
  179.         } else if ($response == 'BAD') {
  180.             sqm_trigger_imap_error('SQM_IMAP_NO_SORT',$query, $response, $message);
  181.         }
  182.     }
  183.  
  184.     if ($response == 'OK') {
  185.         return parseUidList($aData,'SORT');
  186.     } else {
  187.         return false;
  188.     }
  189. }
  190.  
  191.  
  192. /**
  193.  * Parses a UID list returned on a SORT or SEARCH request
  194.  * @param array $aData imap response (retrieved from sqimap_run_command_list)
  195.  * @param string $sCommand issued imap command (SEARCH or SORT)
  196.  * @return array $aUid uid list
  197.  */
  198. function parseUidList($aData,$sCommand) {
  199.     $aUid = array();
  200.     if (isset($aData) && count($aData)) {
  201.         for ($i=0,$iCnt=count($aData);$i<$iCnt;++$i) {
  202.             for ($j=0,$jCnt=count($aData[$i]);$j<$jCnt;++$j) {
  203.                 if (preg_match("/^\* $sCommand (.+)$/", $aData[$i][$j], $aMatch)) {
  204.                     $aUid += explode(' ', trim($aMatch[1]));
  205.                 }
  206.             }
  207.         }
  208.     }
  209.     return array_unique($aUid);
  210. }
  211.  
  212. /**
  213.  * Retrieves an array with a sorted uid list. Sorting is done by SquirrelMail
  214.  *
  215.  * @param resource $imap_stream IMAP socket connection
  216.  * @param string $sSortField Field to sort on
  217.  * @param bool $reverse Reverse order search
  218.  * @param array $aUid limit the search to the provided array with uid's default sqimap_get_small_headers uses 1:*
  219.  * @return array $aUid sorted uid list
  220.  */
  221. function get_squirrel_sort($imap_stream, $sSortField, $reverse = false, $aUid = NULL) {
  222.     if ($sSortField != 'RFC822.SIZE' && $sSortField != 'INTERNALDATE') {
  223.         $msgs = sqimap_get_small_header_list($imap_stream, $aUid,
  224.                                       array($sSortField), array());
  225.     } else {
  226.         $msgs = sqimap_get_small_header_list($imap_stream, $aUid,
  227.                                       array(), array($sSortField));
  228.     }
  229.  
  230.     // sqimap_get_small_header (see above) returns fields in lower case,
  231.     // but the code below uses all upper case
  232.     foreach ($msgs as $k => $v) 
  233.         if (isset($msgs[$k][strtolower($sSortField)])) 
  234.             $msgs[$k][strtoupper($sSortField)] = $msgs[$k][strtolower($sSortField)];
  235.  
  236.     $aUid = array();
  237.     $walk = false;
  238.     switch ($sSortField) {
  239.       // natcasesort section
  240.       case 'FROM':
  241.       case 'TO':
  242.       case 'CC':
  243.         if(!$walk) {
  244.             array_walk($msgs, create_function('&$v,&$k,$f',
  245.                 '$v[$f] = (isset($v[$f])) ? $v[$f] : "";
  246.                  $addr = reset(parseRFC822Address($v[$f],1));
  247.                  $sPersonal = (isset($addr[SQM_ADDR_PERSONAL]) && $addr[SQM_ADDR_PERSONAL]) ?
  248.                    $addr[SQM_ADDR_PERSONAL] : "";
  249.                  $sEmail = ($addr[SQM_ADDR_HOST]) ?
  250.                       $addr[SQM_ADDR_MAILBOX] . "@".$addr[SQM_ADDR_HOST] :
  251.                       $addr[SQM_ADDR_HOST];
  252.                  $v[$f] = ($sPersonal) ? decodeHeader($sPersonal, true, false):$sEmail;'),$sSortField);
  253.             $walk = true;
  254.         }
  255.         // nobreak
  256.       case 'SUBJECT':
  257.         if(!$walk) {
  258.             array_walk($msgs, create_function('&$v,&$k,$f',
  259.                 '$v[$f] = (isset($v[$f])) ? $v[$f] : "";
  260.                  $v[$f] = strtolower(decodeHeader(trim($v[$f]), true, false));
  261.                  $v[$f] = (preg_match("/^(?:(?:vedr|sv|re|aw|fw|fwd|\[\w\]):\s*)*\s*(.*)$/si", $v[$f], $matches)) ?
  262.                                     $matches[1] : $v[$f];'),$sSortField);
  263.             $walk = true;
  264.         }
  265.         foreach ($msgs as $item) {
  266.             $aUid[$item['UID']] = $item[$sSortField];
  267.         }
  268.         natcasesort($aUid);
  269.         $aUid = array_keys($aUid);
  270.         if ($reverse) {
  271.              $aUid = array_reverse($aUid);
  272.         }
  273.         break;
  274.         //  \natcasesort section
  275.       // sort_numeric section
  276.       case 'DATE':
  277.       case 'INTERNALDATE':
  278.         if(!$walk) {
  279.             array_walk($msgs, create_function('&$v,$k,$f',
  280.                 '$v[$f] = (isset($v[$f])) ? $v[$f] : "";
  281.                  $v[$f] = getTimeStamp(explode(" ",$v[$f]));'),$sSortField);
  282.             $walk = true;
  283.         }
  284.         // nobreak;
  285.       case 'RFC822.SIZE':
  286.         if(!$walk) {
  287.             // redefine $sSortField to maintain the same namespace between
  288.             // server-side sorting and SquirrelMail sorting
  289.             $sSortField = 'SIZE';
  290.         }
  291.         foreach ($msgs as $item) {
  292.             $aUid[$item['UID']] = (isset($item[$sSortField])) ? $item[$sSortField] : 0;
  293.         }
  294.         if ($reverse) {
  295.             arsort($aUid,SORT_NUMERIC);
  296.         } else {
  297.             asort($aUid, SORT_NUMERIC);
  298.         }
  299.         $aUid = array_keys($aUid);
  300.         break;
  301.         // \sort_numeric section
  302.       case 'UID':
  303.         $aUid = array_reverse($msgs);
  304.         break;
  305.     }
  306.     return $aUid;
  307. }
  308.  
  309. /**
  310.  * Returns an array with each element as a string representing one
  311.  * message-thread as returned by the IMAP server.
  312.  * @param resource $imap_stream IMAP socket connection
  313.  * @param string $search optional search string
  314.  * @return array 
  315.  * @link http://www.ietf.org/internet-drafts/draft-ietf-imapext-sort-13.txt
  316.  */
  317. function get_thread_sort($imap_stream, $search='ALL') {
  318.     global $sort_by_ref, $default_charset;
  319.  
  320.     if ($sort_by_ref == 1) {
  321.         $sort_type = 'REFERENCES';
  322.     } else {
  323.         $sort_type = 'ORDEREDSUBJECT';
  324.     }
  325.     $query = "THREAD $sort_type ".strtoupper($default_charset)." $search";
  326.  
  327.     // TODO use sqimap_run_command_list as we do in get_server_sort()
  328.     $sRead = sqimap_run_command ($imap_stream, $query, false, $response, $message, TRUE);
  329.  
  330.     /* fallback to default charset */
  331.     if ($response == 'NO') {
  332.         if (strpos($message,'BADCHARSET') !== false ||
  333.             strpos($message,'character') !== false) {
  334.             sqm_trigger_imap_error('SQM_IMAP_BADCHARSET',$query, $response, $message);
  335.             $query = "THREAD $sort_type US-ASCII $search";
  336.             $sRead = sqimap_run_command ($imap_stream, $query, true, $response, $message, TRUE);
  337.         } else {
  338.             sqm_trigger_imap_error('SQM_IMAP_ERROR',$query, $response, $message);
  339.         }
  340.     } elseif ($response == 'BAD') {
  341.         sqm_trigger_imap_error('SQM_IMAP_NO_THREAD',$query, $response, $message);
  342.     }
  343.     $sThreadResponse = '';
  344.     if (isset($sRead[0])) {
  345.         for ($i=0,$iCnt=count($sRead);$i<$iCnt;++$i) {
  346.             if (preg_match("/^\* THREAD (.+)$/", $sRead[$i], $aMatch)) {
  347.                 $sThreadResponse = trim($aMatch[1]);
  348.                 break;
  349.             }
  350.         }
  351.     }
  352.     unset($sRead);
  353.  
  354.     if ($response !== 'OK') {
  355.         return false;
  356.     }
  357.  
  358.     /* Example response
  359.      *  S: * THREAD (2)(3 6 (4 23)(44 7 96))
  360.      * -- 2
  361.      *
  362.      * -- 3
  363.      *    \-- 6
  364.      *        |-- 4
  365.      *        |   \-- 23
  366.      *        |
  367.      *        \-- 44
  368.      *             \-- 7
  369.      *                 \-- 96
  370.      */
  371. /*
  372.  * Notes for future work:
  373.  * indent_array should contain: indent_level, parent and flags,
  374.  * sibling nodes ..
  375.  * To achieve that we  need to define the following flags:
  376.  * 0: hasnochildren
  377.  * 1: haschildren
  378.  * 2: is first
  379.  * 4: is last
  380.  * a node has sibling nodes if it's not the last node
  381.  * a node has no sibling nodes if it's the last node
  382.  * By using binary comparations we can store the flag in one var
  383.  *
  384.  * example:
  385.  * -1      par = 0, level = 0, flag = 1 + 2 + 4 = 7 (haschildren,   isfirst, islast)
  386.  *  \-2    par = 1, level = 1, flag = 0 + 2     = 2 (hasnochildren, isfirst)
  387.  *  |-3    par = 1, level = 1, flag = 1 + 4     = 5 (haschildren,   islast)
  388.  *   \-4   par = 3, level = 2, flag = 1 + 2 + 4 = 7 (haschildren,   isfirst, islast)
  389.  *     \-5 par = 4, level = 3, flag = 0 + 2 + 4 = 6 (hasnochildren, isfirst, islast)
  390.  */
  391.  
  392.     $j = 0;
  393.     $k = 0;
  394.     $l = 0;
  395.     $aUidThread = array();
  396.     $aIndent = array();
  397.     $aUidSubThread = array();
  398.     $aDepthStack = array();
  399.     $sUid = '';
  400.  
  401.     if ($sThreadResponse) {
  402.         for ($i=0,$iCnt = strlen($sThreadResponse);$i<$iCnt;++$i) {
  403.             $cChar = $sThreadResponse{$i};
  404.             switch ($cChar) {
  405.                 case '(': // new sub thread
  406.                     // correction for a subthread of a thread with no parents in thread
  407.                     if (!count($aUidSubThread) && $j > 0) {
  408.                        --$l;
  409.                     }
  410.                     $aDepthStack[$j] = $l;
  411.                     ++$j;
  412.                     break;
  413.                 case ')': // close sub thread
  414.                     if($sUid !== '') {
  415.                         $aUidSubThread[] = $sUid;
  416.                         $aIndent[$sUid] = $j + $l - 1;
  417.                         ++$l;
  418.                         $sUid = '';
  419.                     }
  420.                     --$j;
  421.                     if ($j === 0) {
  422.                         // show message that starts the thread first.
  423.                         $aUidSubThread = array_reverse($aUidSubThread);
  424.                         // do not use array_merge because it's extremely slow and is causing timeouts
  425.                         foreach ($aUidSubThread as $iUid) {
  426.                             $aUidThread[] = $iUid;
  427.                         }
  428.                         $aUidSubThread = array();
  429.                         $l = 0;
  430.                         $aDepthStack = array();
  431.                     } else {
  432.                         $l = $aDepthStack[$j];
  433.                     }
  434.                     break;
  435.                 case ' ': // new child
  436.                     if ($sUid !== '') {
  437.                         $aUidSubThread[] = $sUid;
  438.                         $aIndent[$sUid] = $j + $l - 1;
  439.                         ++$l;
  440.                         $sUid = '';
  441.                     }
  442.                     break;
  443.                 default: // part of UID
  444.                     $sUid .= $cChar;
  445.                     break;
  446.             }
  447.         }
  448.     }
  449.     unset($sThreadResponse);
  450.     // show newest threads first
  451.     $aUidThread = array_reverse($aUidThread);
  452.     return array($aUidThread,$aIndent);
  453. }
  454.  
  455.  
  456. function elapsedTime($start) {
  457.     $stop = gettimeofday();
  458.     $timepassed =  1000000 * ($stop['sec'] - $start['sec']) + $stop['usec'] - $start['usec'];
  459.     return $timepassed;
  460. }
  461.  
  462. /**
  463.  * Parses a string in an imap response. String starts with " or { which means it
  464.  * can handle double quoted strings and literal strings
  465.  *
  466.  * @param string $read imap response
  467.  * @param integer $i (reference) offset in string
  468.  * @return string $s parsed string without the double quotes or literal count
  469.  */
  470. function parseString($read,&$i) {
  471.     $char = $read{$i};
  472.     $s = '';
  473.     if ($char == '"') {
  474.         $iPos = ++$i;
  475.         while (true) {
  476.             $iPos = strpos($read,'"',$iPos);
  477.             if (!$iPos) break;
  478.             if ($iPos && $read{$iPos -1} != '\\') {
  479.                 $s = substr($read,$i,($iPos-$i));
  480.                 $i = $iPos;
  481.                 break;
  482.             }
  483.             $iPos++;
  484.             if ($iPos > strlen($read)) {
  485.                 break;
  486.             }
  487.         }
  488.     } else if ($char == '{') {
  489.         $lit_cnt = '';
  490.         ++$i;
  491.         $iPos = strpos($read,'}',$i);
  492.         if ($iPos) {
  493.             $lit_cnt = substr($read, $i, $iPos - $i);
  494.             $i += strlen($lit_cnt) + 3; /* skip } + \r + \n */
  495.             /* Now read the literal */
  496.             $s = ($lit_cnt ? substr($read,$i,$lit_cnt): '');
  497.             $i += $lit_cnt;
  498.             /* temp bugfix (SM 1.5 will have a working clean version)
  499.                too much work to implement that version right now */
  500.             --$i;
  501.         } else { /* should never happen */
  502.             $i += 3; /* } + \r + \n */
  503.             $s = '';
  504.         }
  505.     } else {
  506.         return false;
  507.     }
  508.     ++$i;
  509.     return $s;
  510. }
  511.  
  512.  
  513. /**
  514.  * Parses a string containing an array from an imap response. String starts with ( and end with )
  515.  *
  516.  * @param string $read imap response
  517.  * @param integer $i (reference) offset in string
  518.  * @return array $a
  519.  */
  520. function parseArray($read,&$i) {
  521.     $i = strpos($read,'(',$i);
  522.     $i_pos = strpos($read,')',$i);
  523.     $s = substr($read,$i+1,$i_pos - $i -1);
  524.     $a = explode(' ',$s);
  525.     if ($i_pos) {
  526.         $i = $i_pos+1;
  527.         return $a;
  528.     } else {
  529.         return false;
  530.     }
  531. }
  532.  
  533.  
  534. /**
  535.  * Retrieves a list with headers, flags, size or internaldate from the imap server
  536.  *
  537.  * WARNING: function is not portable between SquirrelMail 1.2.x, 1.4.x and 1.5.x.
  538.  * Output format, third argument and $msg_list array format requirements differ.
  539.  * @param stream $imap_stream imap connection
  540.  * @param array  $msg_list array with id's to create a msgs set from
  541.  * @param array  $aHeaderFields (since 1.5.0) requested header fields
  542.  * @param array  $aFetchItems (since 1.5.0) requested other fetch items like FLAGS, RFC822.SIZE
  543.  * @return array $aMessages associative array with messages. Key is the UID, value is an associative array
  544.  * @since 1.1.3
  545.  */
  546. function sqimap_get_small_header_list($imap_stream, $msg_list,
  547.     $aHeaderFields = array('Date', 'To', 'Cc', 'From', 'Subject', 'X-Priority', 'Content-Type'),
  548.     $aFetchItems = array('FLAGS', 'RFC822.SIZE', 'INTERNALDATE')) {
  549.  
  550.     $aMessageList = array();
  551.  
  552.     /**
  553.      * Catch other priority headers as well
  554.      */
  555.     if (in_array('X-Priority',$aHeaderFields,true)) {
  556.         $aHeaderFields[] = 'Importance';
  557.         $aHeaderFields[] = 'Priority';
  558.     }
  559.  
  560.     $bUidFetch = ! in_array('UID', $aFetchItems, true);
  561.  
  562.     /* Get the small headers for each message in $msg_list */
  563.     if ($msg_list !== NULL) {
  564.         $msgs_str = sqimap_message_list_squisher($msg_list);
  565.         /*
  566.         * We need to return the data in the same order as the caller supplied
  567.         * in $msg_list, but IMAP servers are free to return responses in
  568.         * whatever order they wish... So we need to re-sort manually
  569.         */
  570.         if ($bUidFetch) {
  571.             for ($i = 0; $i < sizeof($msg_list); $i++) {
  572.                 $aMessageList["$msg_list[$i]"] = array();
  573.             }
  574.         }
  575.     } else {
  576.         $msgs_str = '1:*';
  577.     }
  578.  
  579.     /*
  580.      * Create the query
  581.      */
  582.  
  583.     $sFetchItems = '';
  584.     $query = "FETCH $msgs_str (";
  585.     if (count($aFetchItems)) {
  586.         $sFetchItems = implode(' ',$aFetchItems);
  587.     }
  588.     if (count($aHeaderFields)) {
  589.         $sHeaderFields = implode(' ',$aHeaderFields);
  590.         $sFetchItems .= ' BODY.PEEK[HEADER.FIELDS ('.$sHeaderFields.')]';
  591.     }
  592.     $query .= trim($sFetchItems) . ')';
  593.     $aResponse = sqimap_run_command_list ($imap_stream, $query, true, $response, $message, $bUidFetch);
  594.     $aMessages = parseFetch($aResponse,$aMessageList);
  595.     array_reverse($aMessages);
  596.     return $aMessages;
  597. }
  598.  
  599.  
  600. /**
  601.  * Parses a fetch response, currently it can hande FLAGS, HEADERS, RFC822.SIZE, INTERNALDATE and UID
  602.  * @param array    $aResponse Imap response
  603.  * @param array    $aMessageList Placeholder array for results. The keys of the
  604.  *                  placeholder array should be the UID so we can reconstruct the order.
  605.  * @return array   $aMessageList associative array with messages. Key is the UID, value is an associative array
  606.  * @author Marc Groot Koerkamp
  607.  */
  608. function parseFetch(&$aResponse,$aMessageList = array()) {
  609.     for ($j=0,$iCnt=count($aResponse);$j<$iCnt;++$j) {
  610.         $aMsg = array();
  611.  
  612.         $read = implode('',$aResponse[$j]);
  613.         // free up memmory
  614.         unset($aResponse[$j]); /* unset does not reindex the array. the for loop is safe */
  615.         /*
  616.             * #id<space>FETCH<space>(
  617.         */
  618.  
  619.         /* extract the message id */
  620.         $i_space = strpos($read,' ',2);/* position 2ed <space> */
  621.         $id = substr($read,2/* skip "*<space>" */,$i_space -2);
  622.         $aMsg['ID'] = $id;
  623.         $fetch = substr($read,$i_space+1,5);
  624.         if (!is_numeric($id) && $fetch !== 'FETCH') {
  625.             $aMsg['ERROR'] = $read; // sm_encode_html_special_chars should be done just before display. this is backend code
  626.             break;
  627.         }
  628.         $i = strpos($read,'(',$i_space+5);
  629.         $read = substr($read,$i+1);
  630.         $i_len = strlen($read);
  631.         $i = 0;
  632.         while ($i < $i_len && $i !== false) {
  633.             /* get argument */
  634.             $read = trim(substr($read,$i));
  635.             $i_len = strlen($read);
  636.             $i = strpos($read,' ');
  637.             $arg = substr($read,0,$i);
  638.             ++$i;
  639.             /*
  640.              * use allcaps for imap items and lowcaps for headers as key for the $aMsg array
  641.              */
  642.             switch ($arg)
  643.             {
  644.             case 'UID':
  645.                 $i_pos = strpos($read,' ',$i);
  646.                 if (!$i_pos) {
  647.                     $i_pos = strpos($read,')',$i);
  648.                 }
  649.                 if ($i_pos) {
  650.                     $unique_id = substr($read,$i,$i_pos-$i);
  651.                     $i = $i_pos+1;
  652.                 } else {
  653.                     break 3;
  654.                 }
  655.                 break;
  656.             case 'FLAGS':
  657.                 $flags = parseArray($read,$i);
  658.                 if (!$flags) break 3;
  659.                 $aFlags = array();
  660.                 foreach ($flags as $flag) {
  661.                     $flag = strtolower($flag);
  662.                     $aFlags[$flag] = true;
  663.                 }
  664.                 $aMsg['FLAGS'] = $aFlags;
  665.                 break;
  666.             case 'RFC822.SIZE':
  667.                 $i_pos = strpos($read,' ',$i);
  668.                 if (!$i_pos) {
  669.                     $i_pos = strpos($read,')',$i);
  670.                 }
  671.                 if ($i_pos) {
  672.                     $aMsg['SIZE'] = substr($read,$i,$i_pos-$i);
  673.                     $i = $i_pos+1;
  674.                 } else {
  675.                     break 3;
  676.                 }
  677.                 break;
  678.             case 'ENVELOPE':
  679.                 // sqimap_parse_address($read,$i,$aMsg);
  680.                 break; // to be implemented, moving imap code out of the Message class
  681.             case 'BODYSTRUCTURE':
  682.                 break; // to be implemented, moving imap code out of the Message class
  683.             case 'INTERNALDATE':
  684.                 $aMsg['INTERNALDATE'] = trim(str_replace('  ', ' ',parseString($read,$i)));
  685.                 break;
  686.             case 'BODY.PEEK[HEADER.FIELDS':
  687.             case 'BODY[HEADER.FIELDS':
  688.                 $i = strpos($read,'{',$i); // header is always returned as literal because it contain \n characters
  689.                 $header = parseString($read,$i);
  690.                 if ($header === false) break 2;
  691.                 /* First we replace all \r\n by \n, and unfold the header */
  692.                 $hdr = trim(str_replace(array("\r\n", "\n\t", "\n "),array("\n", ' ', ' '), $header));
  693.                 /* Now we can make a new header array with
  694.                    each element representing a headerline  */
  695.                 $aHdr = explode("\n" , $hdr);
  696.                 $aReceived = array();
  697.                 foreach ($aHdr as $line) {
  698.                     $pos = strpos($line, ':');
  699.                     if ($pos > 0) {
  700.                         $field = strtolower(substr($line, 0, $pos));
  701.                         if (!strstr($field,' ')) { /* valid field */
  702.                             $value = trim(substr($line, $pos+1));
  703.                             switch($field) {
  704.                                 case 'date':
  705.                                     $aMsg['date'] = trim(str_replace('  ', ' ', $value));
  706.                                     break;
  707.                                 case 'x-priority': $aMsg['x-priority'] = ($value) ? (int) $value{0} : 3; break;
  708.                                 case 'priority':
  709.                                 case 'importance':
  710.                                     // duplicate code with Rfc822Header.cls:parsePriority()
  711.                                     if (!isset($aMsg['x-priority'])) {
  712.                                         $aPrio = preg_split('/\s/',trim($value));
  713.                                         $sPrio = strtolower(array_shift($aPrio));
  714.                                         if  (is_numeric($sPrio)) {
  715.                                             $iPrio = (int) $sPrio;
  716.                                         } elseif ( $sPrio == 'non-urgent' || $sPrio == 'low' ) {
  717.                                             $iPrio = 5;
  718.                                         } elseif ( $sPrio == 'urgent' || $sPrio == 'high' ) {
  719.                                             $iPrio = 1;
  720.                                         } else {
  721.                                             // default is normal priority
  722.                                             $iPrio = 3;
  723.                                         }
  724.                                         $aMsg['x-priority'] = $iPrio;
  725.                                     }
  726.                                     break;
  727.                                 case 'content-type':
  728.                                     $type = $value;
  729.                                     if ($pos = strpos($type, ";")) {
  730.                                         $type = substr($type, 0, $pos);
  731.                                     }
  732.                                     $type = explode("/", $type);
  733.                                     if(!is_array($type) || count($type) < 2) {
  734.                                         $aMsg['content-type'] = array('text','plain');
  735.                                     } else {
  736.                                         $aMsg['content-type'] = array(strtolower($type[0]),strtolower($type[1]));
  737.                                     }
  738.                                     break;
  739.                                 case 'received':
  740.                                     $aMsg['received'][] = $value;
  741.                                     break;
  742.                                 default:
  743.                                     $aMsg[$field] = $value;
  744.                                     break;
  745.                             }
  746.                         }
  747.                     }
  748.                 }
  749.                 break;
  750.             default:
  751.                 ++$i;
  752.                 break;
  753.             }
  754.         }
  755.         if (!empty($unique_id)) {
  756.             $msgi = "$unique_id";
  757.             $aMsg['UID'] = $unique_id;
  758.        } else {
  759.             $msgi = '';
  760.        }
  761.        $aMessageList[$msgi] = $aMsg;
  762.        $aResponse[$j] = NULL;
  763.     }
  764.     return $aMessageList;
  765. }
  766.  
  767. /**
  768.  * Work in process
  769.  * @private
  770.  * @author Marc Groot Koerkamp
  771.  */
  772. function sqimap_parse_envelope($read, &$i, &$msg) {
  773.     $arg_no = 0;
  774.     $arg_a = array();
  775.     ++$i;
  776.     for ($cnt = strlen($read); ($i < $cnt) && ($read{$i} != ')'); ++$i) {
  777.         $char = strtoupper($read{$i});
  778.         switch ($char) {
  779.             case '{':
  780.             case '"':
  781.                 $arg_a[] = parseString($read,$i);
  782.                 ++$arg_no;
  783.                 break;
  784.             case 'N':
  785.                 /* probably NIL argument */
  786.                 if (strtoupper(substr($read, $i, 3)) == 'NIL') {
  787.                     $arg_a[] = '';
  788.                     ++$arg_no;
  789.                     $i += 2;
  790.                 }
  791.                 break;
  792.             case '(':
  793.                 /* Address structure (with group support)
  794.                 * Note: Group support is useless on SMTP connections
  795.                 *       because the protocol doesn't support it
  796.                 */
  797.                 $addr_a = array();
  798.                 $group = '';
  799.                 $a=0;
  800.                 for (; $i < $cnt && $read{$i} != ')'; ++$i) {
  801.                     if ($read{$i} == '(') {
  802.                         $addr = sqimap_parse_address($read, $i);
  803.                         if (($addr[3] == '') && ($addr[2] != '')) {
  804.                             /* start of group */
  805.                             $group = $addr[2];
  806.                             $group_addr = $addr;
  807.                             $j = $a;
  808.                         } else if ($group && ($addr[3] == '') && ($addr[2] == '')) {
  809.                         /* end group */
  810.                             if ($a == ($j+1)) { /* no group members */
  811.                                 $group_addr[4] = $group;
  812.                                 $group_addr[2] = '';
  813.                                 $group_addr[0] = "$group: Undisclosed recipients;";
  814.                                 $addr_a[] = $group_addr;
  815.                                 $group ='';
  816.                             }
  817.                         } else {
  818.                             $addr[4] = $group;
  819.                             $addr_a[] = $addr;
  820.                         }
  821.                         ++$a;
  822.                     }
  823.                 }
  824.                 $arg_a[] = $addr_a;
  825.                 break;
  826.             default: break;
  827.         }
  828.     }
  829.  
  830.     if (count($arg_a) > 9) {
  831.         $d = strtr($arg_a[0], array('  ' => ' '));
  832.         $d = explode(' ', $d);
  833.         if (!$arg_a[1]) $arg_a[1] = '';
  834.         $msg['DATE'] = $d; /* argument 1: date */
  835.         $msg['SUBJECT'] = $arg_a[1];     /* argument 2: subject */
  836.         $msg['FROM'] = is_array($arg_a[2]) ? $arg_a[2][0] : '';     /* argument 3: from        */
  837.         $msg['SENDER'] = is_array($arg_a[3]) ? $arg_a[3][0] : '';   /* argument 4: sender      */
  838.         $msg['REPLY-TO'] = is_array($arg_a[4]) ? $arg_a[4][0] : '';  /* argument 5: reply-to    */
  839.         $msg['TO'] = $arg_a[5];          /* argument 6: to          */
  840.         $msg['CC'] = $arg_a[6];          /* argument 7: cc          */
  841.         $msg['BCC'] = $arg_a[7];         /* argument 8: bcc         */
  842.         $msg['IN-REPLY-TO'] = $arg_a[8];   /* argument 9: in-reply-to */
  843.         $msg['MESSAGE-ID'] = $arg_a[9];  /* argument 10: message-id */
  844.     }
  845. }
  846.  
  847.  
  848. /**
  849.  * Work in process
  850.  * @private
  851.  * @author Marc Groot Koerkamp
  852.  */
  853. function sqimap_parse_address($read, &$i) {
  854.     $arg_a = array();
  855.     for (; $read{$i} != ')'; ++$i) {
  856.         $char = strtoupper($read{$i});
  857.         switch ($char) {
  858.             case '{':
  859.             case '"': $arg_a[] =  parseString($read,$i); break;
  860.             case 'n':
  861.             case 'N':
  862.                 if (strtoupper(substr($read, $i, 3)) == 'NIL') {
  863.                     $arg_a[] = '';
  864.                     $i += 2;
  865.                 }
  866.                 break;
  867.             default: break;
  868.         }
  869.     }
  870.  
  871.     if (count($arg_a) == 4) {
  872.         return $arg_a;
  873.  
  874. //        $adr = new AddressStructure();
  875. //        $adr->personal = $arg_a[0];
  876. //        $adr->adl = $arg_a[1];
  877. //        $adr->mailbox = $arg_a[2];
  878. //        $adr->host = $arg_a[3];
  879.     } else {
  880.         $adr = '';
  881.     }
  882.     return $adr;
  883. }
  884.  
  885.  
  886. /**
  887.  * Returns a message array with all the information about a message.
  888.  * See the documentation folder for more information about this array.
  889.  *
  890.  * @param  resource $imap_stream imap connection
  891.  * @param  integer  $id uid of the message
  892.  * @param  string   $mailbox used for error handling, can be removed because we should return an error code and generate the message elsewhere
  893.  * @param  int      $hide Indicates whether or not to hide any errors: 0 = don't hide, 1 = hide (just exit), 2 = hide (return FALSE), 3 = hide (return error string) (OPTIONAL; default don't hide)
  894.  * @return mixed  Message object or FALSE/error string if error occurred and $hide is set to 2/3
  895.  */
  896. function sqimap_get_message($imap_stream, $id, $mailbox, $hide=0) {
  897.     // typecast to int to prohibit 1:* msgs sets
  898.     // Update: $id should always be sanitized into a BIGINT so this
  899.     // is being removed; leaving this code here in case something goes
  900.     // wrong, however
  901.     //$id = (int) $id;
  902.     $flags = array();
  903.     $read = sqimap_run_command($imap_stream, "FETCH $id (FLAGS BODYSTRUCTURE)", true, $response, $message, TRUE);
  904.     if ($read) {
  905.         if (preg_match('/.+FLAGS\s\((.*)\)\s/AUi',$read[0],$regs)) {
  906.             if (trim($regs[1])) {
  907.                 $flags = preg_split('/ /', $regs[1],-1,PREG_SPLIT_NO_EMPTY);
  908.             }
  909.         }
  910.     } else {
  911.  
  912.         if ($hide == 1) exit;
  913.         if ($hide == 2) return FALSE;
  914.  
  915.         /* the message was not found, maybe the mailbox was modified? */
  916.         global $sort, $startMessage;
  917.  
  918.         $errmessage = _("The server couldn't find the message you requested.");
  919.  
  920.         if ($hide == 3) return $errmessage;
  921.  
  922.         $errmessage .= '<p>'._("Most probably your message list was out of date and the message has been moved away or deleted (perhaps by another program accessing the same mailbox).");
  923.  
  924.         /* this will include a link back to the message list */
  925.         error_message($errmessage, $mailbox, $sort, (int) $startMessage);
  926.         exit;
  927.     }
  928.     $bodystructure = implode('',$read);
  929.     $msg =  mime_structure($bodystructure,$flags);
  930.     $read = sqimap_run_command($imap_stream, "FETCH $id BODY[HEADER]", true, $response, $message, TRUE);
  931.     $rfc822_header = new Rfc822Header();
  932.     $rfc822_header->parseHeader($read);
  933.     $msg->rfc822_header = $rfc822_header;
  934.  
  935.     parse_message_entities($msg, $id, $imap_stream);
  936.     return $msg;
  937.  }
  938.  
  939.  
  940. /**
  941.  * Recursively parse embedded messages (if any) in the given
  942.  * message, building correct rfc822 headers for each one
  943.  *
  944.  * @param object $msg The message object to scan for attached messages
  945.  *                     NOTE: this is passed by reference!  Changes made
  946.  *                     within will affect the caller's copy of $msg!
  947.  * @param int $id The top-level message UID on the IMAP server, even
  948.  *                 if the $msg being passed in is only an attached entity
  949.  *                 thereof.
  950.  * @param resource $imap_stream A live connection to the IMAP server.
  951.  *
  952.  * @return void 
  953.  *
  954.  * @since 1.5.2
  955.  *
  956.  */
  957. function parse_message_entities(&$msg, $id, $imap_stream) {
  958.     if (!empty($msg->entities)) foreach ($msg->entities as $i => $entity) {
  959.         if (is_object($entity) && strtolower(get_class($entity)) == 'message') {
  960.             if (!empty($entity->rfc822_header)) {
  961.                 $read = sqimap_run_command($imap_stream, "FETCH $id BODY[". $entity->entity_id .".HEADER]", true, $response, $message, TRUE);
  962.                 $rfc822_header = new Rfc822Header();
  963.                 $rfc822_header->parseHeader($read);
  964.                 $msg->entities[$i]->rfc822_header = $rfc822_header;
  965.             }
  966.             parse_message_entities($msg->entities[$i], $id, $imap_stream);
  967.         }
  968.     }
  969. }

Documentation generated on Mon, 13 Jan 2020 04:22:49 +0100 by phpDocumentor 1.4.3