Happy New Year
[squirrelmail.git] / functions / imap_asearch.php
index 0c2c21c6960fe6c9136cde9b39197b930f9e59e2..6f3a24c2da7389094adb32cc58882f44700daab4 100644 (file)
 /**
  * imap_search.php
  *
- * Copyright (c) 1999-2004 The SquirrelMail Project Team
- * Licensed under the GNU GPL. For full terms see the file COPYING.
- *
  * IMAP asearch routines
  *
  * Subfolder search idea from Patch #806075 by Thomas Pohl xraven at users.sourceforge.net. Thanks Thomas!
  *
+ * @author Alex Lemaresquier - Brainstorm <alex at brainstorm.fr>
+ * @copyright 1999-2018 The SquirrelMail Project Team
+ * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  * @version $Id$
  * @package squirrelmail
  * @subpackage imap
  * @see search.php
  * @link http://www.ietf.org/rfc/rfc3501.txt
- * @author Alex Lemaresquier - Brainstorm - alex at brainstorm.fr
  */
 
 /** This functionality requires the IMAP and date functions
  */
-require_once(SM_PATH . 'functions/imap_general.php');
-require_once(SM_PATH . 'functions/date.php');
+//require_once(SM_PATH . 'functions/imap_general.php');
+//require_once(SM_PATH . 'functions/date.php');
 
-/** Set to TRUE to dump the imap dialogue
+/** Set to TRUE to dump the IMAP dialogue
  * @global bool $imap_asearch_debug_dump
  */
 $imap_asearch_debug_dump = FALSE;
 
-/** Imap SEARCH keys
+/** IMAP SEARCH keys
  * @global array $imap_asearch_opcodes
  */
+global $imap_asearch_opcodes;
 $imap_asearch_opcodes = array(
-/* <sequence-set> => 'asequence', */   // Special handling, @see sqimap_asearch_build_criteria()
+/* <sequence-set> => 'asequence', */    // Special handling, @see sqimap_asearch_build_criteria()
 /*'ALL' is binary operator */
-       'ANSWERED' => '',
-       'BCC' => 'astring',
-       'BEFORE' => 'adate',
-       'BODY' => 'astring',
-       'CC' => 'astring',
-       'DELETED' => '',
-       'DRAFT' => '',
-       'FLAGGED' => '',
-       'FROM' => 'astring',
-       'HEADER' => 'afield',   // Special syntax for this one, @see sqimap_asearch_build_criteria()
-       'KEYWORD' => 'akeyword',
-       'LARGER' => 'anum',
-       'NEW' => '',
+    'ANSWERED' => '',
+    'BCC' => 'astring',
+    'BEFORE' => 'adate',
+    'BODY' => 'astring',
+    'CC' => 'astring',
+    'DELETED' => '',
+    'DRAFT' => '',
+    'FLAGGED' => '',
+    'FROM' => 'astring',
+    'HEADER' => 'afield',    // Special syntax for this one, @see sqimap_asearch_build_criteria()
+    'KEYWORD' => 'akeyword',
+    'LARGER' => 'anum',
+    'NEW' => '',
 /*'NOT' is unary operator */
-       'OLD' => '',
-       'ON' => 'adate',
+    'OLD' => '',
+    'ON' => 'adate',
 /*'OR' is binary operator */
-       'RECENT' => '',
-       'SEEN' => '',
-       'SENTBEFORE' => 'adate',
-       'SENTON' => 'adate',
-       'SENTSINCE' => 'adate',
-       'SINCE' => 'adate',
-       'SMALLER' => 'anum',
-       'SUBJECT' => 'astring',
-       'TEXT' => 'astring',
-       'TO' => 'astring',
-       'UID' => 'asequence',
-       'UNANSWERED' => '',
-       'UNDELETED' => '',
-       'UNDRAFT' => '',
-       'UNFLAGGED' => '',
-       'UNKEYWORD' => 'akeyword',
-       'UNSEEN' => ''
+    'RECENT' => '',
+    'SEEN' => '',
+    'SENTBEFORE' => 'adate',
+    'SENTON' => 'adate',
+    'SENTSINCE' => 'adate',
+    'SINCE' => 'adate',
+    'SMALLER' => 'anum',
+    'SUBJECT' => 'astring',
+    'TEXT' => 'astring',
+    'TO' => 'astring',
+    'UID' => 'asequence',
+    'UNANSWERED' => '',
+    'UNDELETED' => '',
+    'UNDRAFT' => '',
+    'UNFLAGGED' => '',
+    'UNKEYWORD' => 'akeyword',
+    'UNSEEN' => ''
 );
 
-/** Imap SEARCH month names encoding
+/** IMAP SEARCH month names encoding
  * @global array $imap_asearch_months
  */
 $imap_asearch_months = array(
-       '01' => 'jan',
-       '02' => 'feb',
-       '03' => 'mar',
-       '04' => 'apr',
-       '05' => 'may',
-       '06' => 'jun',
-       '07' => 'jul',
-       '08' => 'aug',
-       '09' => 'sep',
-       '10' => 'oct',
-       '11' => 'nov',
-       '12' => 'dec'
-);
-
-/** Error message titles according to imap server returned code
- * @global array $imap_error_titles
- */
-$imap_error_titles = array(
-       'OK' => '',
-       'NO' => _("ERROR : Could not complete request."),
-       'BAD' => _("ERROR : Bad or malformed request."),
-       'BYE' => _("ERROR : Imap server closed the connection."),
-       '' => _("ERROR : Connection dropped by imap-server.")
+    '01' => 'jan',
+    '02' => 'feb',
+    '03' => 'mar',
+    '04' => 'apr',
+    '05' => 'may',
+    '06' => 'jun',
+    '07' => 'jul',
+    '08' => 'aug',
+    '09' => 'sep',
+    '10' => 'oct',
+    '11' => 'nov',
+    '12' => 'dec'
 );
 
 /**
- * Function to display an error related to an IMAP-query.
+ * Function to display an error related to an IMAP query.
  * We need to do our own error management since we may receive NO responses on purpose (even BAD with SORT or THREAD)
  * so we call sqimap_error_box() if the function exists (sm >= 1.5) or use our own embedded code
  * @global array imap_error_titles
@@ -112,33 +101,27 @@ $imap_error_titles = array(
 //@global array color sm colors array
 function sqimap_asearch_error_box($response, $query, $message, $link = '')
 {
-       global $imap_error_titles;
-
-       if (!array_key_exists($response, $imap_error_titles))
-               $title = _("ERROR : Unknown imap response.");
-       else
-               $title = $imap_error_titles[$response];
-       if ($link == '')
-               $message_title = _("Reason Given: ");
-       else
-               $message_title = _("Possible reason : ");
-       if (function_exists('sqimap_error_box'))
-               sqimap_error_box($title, $query, $message_title, $message, $link);
-       else {  //Straight copy of 1.5 imap_general.php:sqimap_error_box(). Can be removed at a later time
-               global $color;
-    require_once(SM_PATH . 'functions/display_messages.php');
-    $string = "<font color=$color[2]><b>\n" . $title . "</b><br>\n";
-    if ($query != '')
-        $string .= _("Query:") . ' ' . htmlspecialchars($query) . '<br>';
-    if ($message_title != '')
-        $string .= $message_title;
-    if ($message != '')
-        $string .= htmlspecialchars($message);
-    if ($link != '')
-        $string .= $link;
-    $string .= "</font><br>\n";
-    error_box($string,$color);
-       }
+    global $color;
+    // Error message titles according to IMAP server returned code
+    $imap_error_titles = array(
+        'OK' => '',
+        'NO' => _("ERROR: Could not complete request."),
+        'BAD' => _("ERROR: Bad or malformed request."),
+        'BYE' => _("ERROR: IMAP server closed the connection."),
+        '' => _("ERROR: Connection dropped by IMAP server.")
+    );
+
+
+    if (!array_key_exists($response, $imap_error_titles))
+        $title = _("ERROR: Unknown IMAP response.");
+    else
+        $title = $imap_error_titles[$response];
+    if ($link == '')
+        $message_title = _("Reason Given:");
+    else
+        $message_title = _("Possible reason:");
+    $message_title .= ' ';
+    sqimap_error_box($title, $query, $message_title, $message, $link);
 }
 
 /**
@@ -149,9 +132,9 @@ function sqimap_asearch_error_box($response, $query, $message, $link = '')
  */
 function asearch_nz(&$var, $def = '')
 {
-       if (isset($var))
-               return $var;
-       return $def;
+    if (isset($var))
+        return $var;
+    return $def;
 }
 
 /**
@@ -161,56 +144,34 @@ function asearch_nz(&$var, $def = '')
  * @return string decoded string
  */
 function asearch_unhtmlentities($string) {
-       $trans_tbl = array_flip(get_html_translation_table(HTML_ENTITIES));
-       for ($i=127; $i<255; $i++)      /* Add &#<dec>; entities */
-               $trans_tbl['&#' . $i . ';'] = chr($i);
-       return strtr($string, $trans_tbl);
+    $trans_tbl = array_flip(get_html_translation_table(HTML_ENTITIES));
+    for ($i=127; $i<255; $i++)    /* Add &#<dec>; entities */
+        $trans_tbl['&#' . $i . ';'] = chr($i);
+    return strtr($string, $trans_tbl);
 /* I think the one above is quicker, though it should be benchmarked
-       $string = strtr($string, array_flip(get_html_translation_table(HTML_ENTITIES)));
-       return preg_replace("/&#([0-9]+);/E", "chr('\\1')", $string);
-*/
-}
-
-/**
- * Provide an easy way to dump the imap dialogue if $imap_asearch_debug_dump is TRUE
- * @global bool imap_asearch_debug_dump
- * @param string $var_name
- * @param string $var_var
+    $string = strtr($string, array_flip(get_html_translation_table(HTML_ENTITIES)));
+    return preg_replace("/&#([0-9]+);/E", "chr('\\1')", $string);
  */
-function s_debug_dump($var_name, $var_var)
-{
-       global $imap_asearch_debug_dump;
-       if ($imap_asearch_debug_dump) {
-               if (function_exists('sm_print_r'))      //Only exists since 1.4.2
-                       sm_print_r($var_name, $var_var);        //Better be the 'varargs' version ;)
-               else {
-                       echo '<pre>';
-                       echo htmlentities($var_name);
-                       print_r($var_var);
-                       echo '</pre>';
-               }
-       }
 }
 
 /** Encode a string to quoted or literal as defined in rfc 3501
  *
- * - § 4.3 String:
- *     A quoted string is a sequence of zero or more 7-bit characters,
- *      excluding CR and LF, with double quote (<">) characters at each end.
- * - § 9. Formal Syntax:
- *     quoted-specials = DQUOTE / "\"
+ * -  4.3 String:
+ *        A quoted string is a sequence of zero or more 7-bit characters,
+ *         excluding CR and LF, with double quote (<">) characters at each end.
+ * -  9. Formal Syntax:
+ *        quoted-specials = DQUOTE / "\"
  * @param string $what string to encode
  * @param string $charset search charset used
  * @return string encoded string
  */
 function sqimap_asearch_encode_string($what, $charset)
 {
-       if (strtoupper($charset) == 'ISO-2022-JP')      // This should be now handled in imap_utf7_local?
-               $what = mb_convert_encoding($what, 'JIS', 'auto');
-//if (ereg("[\"\\\r\n\x80-\xff]", $what))
-       if (preg_match('/["\\\\\r\n\x80-\xff]/', $what))
-               return '{' . strlen($what) . "}\r\n" . $what;   // 4.3 literal form
-       return '"' . $what . '"';       // 4.3 quoted string form
+    if (strtoupper($charset) == 'ISO-2022-JP')    // This should be now handled in imap_utf7_local?
+        $what = mb_convert_encoding($what, 'JIS', 'auto');
+    if (preg_match('/["\\\\\r\n\x80-\xff]/', $what))
+        return '{' . strlen($what) . "}\r\n" . $what;    // 4.3 literal form
+    return '"' . $what . '"';    // 4.3 quoted string form
 }
 
 /**
@@ -226,32 +187,32 @@ function sqimap_asearch_encode_string($what, $charset)
  */
 function sqimap_asearch_parse_date($what)
 {
-       global $imap_asearch_months;
-
-       $what = trim($what);
-       $what = ereg_replace('[ /\\.,]+', '-', $what);
-       if ($what) {
-               preg_match('/^([0-9]+)-+([^\-]+)-+([0-9]+)$/', $what, $what_parts);
-               if (count($what_parts) == 4) {
-                       $what_month = strtolower(asearch_unhtmlentities($what_parts[2]));
-/*             if (!in_array($what_month, $imap_asearch_months)) {*/
-                               foreach ($imap_asearch_months as $month_number => $month_code) {
-                                       if (($what_month == $month_number)
-                                        || ($what_month == $month_code)
-                                        || ($what_month == strtolower(asearch_unhtmlentities(getMonthName($month_number))))
-                                        || ($what_month == strtolower(asearch_unhtmlentities(getMonthAbrv($month_number))))
-                                        ) {
-                                               $what_parts[2] = $month_number;
-                                               $what_parts[0] = $what_parts[1] . '-' . $month_code . '-' . $what_parts[3];
-                                               break;
-                                       }
-                               }
-/*             }*/
-               }
-       }
-       else
-               $what_parts = array();
-       return $what_parts;
+    global $imap_asearch_months;
+
+    $what = trim($what);
+    $what = preg_replace('/[ \/\\.,]+/', '-', $what);
+    if ($what) {
+        preg_match('/^([0-9]+)-+([^\-]+)-+([0-9]+)$/', $what, $what_parts);
+        if (count($what_parts) == 4) {
+            $what_month = strtolower(asearch_unhtmlentities($what_parts[2]));
+/*                if (!in_array($what_month, $imap_asearch_months)) {*/
+                foreach ($imap_asearch_months as $month_number => $month_code) {
+                    if (($what_month == $month_number)
+                    || ($what_month == $month_code)
+                    || ($what_month == strtolower(asearch_unhtmlentities(getMonthName($month_number))))
+                    || ($what_month == strtolower(asearch_unhtmlentities(getMonthAbrv($month_number))))
+                    ) {
+                        $what_parts[2] = $month_number;
+                        $what_parts[0] = $what_parts[1] . '-' . $month_code . '-' . $what_parts[3];
+                        break;
+                    }
+                }
+/*                }*/
+        }
+    }
+    else
+        $what_parts = array();
+    return $what_parts;
 }
 
 /**
@@ -264,55 +225,55 @@ function sqimap_asearch_parse_date($what)
  */
 function sqimap_asearch_build_criteria($opcode, $what, $charset)
 {
-       global $imap_asearch_opcodes;
-
-       $criteria = '';
-       switch ($imap_asearch_opcodes[$opcode]) {
-               default:
-               case 'anum':
-                       $what = str_replace(' ', '', $what);
-                       $what = ereg_replace('[^0-9]+[^KMG]$', '', strtoupper($what));
-                       if ($what != '') {
-                               switch (substr($what, -1)) {
-                                       case 'G':
-                                               $what = substr($what, 0, -1) << 30;
-                                       break;
-                                       case 'M':
-                                               $what = substr($what, 0, -1) << 20;
-                                       break;
-                                       case 'K':
-                                               $what = substr($what, 0, -1) << 10;
-                                       break;
-                               }
-                               $criteria = $opcode . ' ' . $what . ' ';
-                       }
-               break;
-               case '':        //aflag
-                       $criteria = $opcode . ' ';
-               break;
-               case 'afield':  /* HEADER field-name: field-body */
-                       preg_match('/^([^:]+):(.*)$/', $what, $what_parts);
-                       if (count($what_parts) == 3)
-                               $criteria = $opcode . ' ' . 
-                                       sqimap_asearch_encode_string($what_parts[1], $charset) . ' ' .
-                                       sqimap_asearch_encode_string($what_parts[2], $charset) . ' ';
-               break;
-               case 'adate':
-                       $what_parts = sqimap_asearch_parse_date($what);
-                       if (isset($what_parts[0]))
-                               $criteria = $opcode . ' ' . $what_parts[0] . ' ';
-               break;
-               case 'akeyword':
-               case 'astring':
-                       $criteria = $opcode . ' ' . sqimap_asearch_encode_string($what, $charset) . ' ';
-               break;
-               case 'asequence':
-                       $what = ereg_replace('[^0-9:\(\)]+', '', $what);
-                       if ($what != '')
-                               $criteria = $opcode . ' ' . $what . ' ';
-               break;
-       }
-       return $criteria;
+    global $imap_asearch_opcodes;
+
+    $criteria = '';
+    switch ($imap_asearch_opcodes[$opcode]) {
+        default:
+        case 'anum':
+            $what = str_replace(' ', '', $what);
+            $what = preg_replace('/[^0-9]+[^KMG]$/', '', strtoupper($what));
+            if ($what != '') {
+                switch (substr($what, -1)) {
+                    case 'G':
+                        $what = substr($what, 0, -1) << 30;
+                    break;
+                    case 'M':
+                        $what = substr($what, 0, -1) << 20;
+                    break;
+                    case 'K':
+                        $what = substr($what, 0, -1) << 10;
+                    break;
+                }
+                $criteria = $opcode . ' ' . $what . ' ';
+            }
+        break;
+        case '':    //aflag
+            $criteria = $opcode . ' ';
+        break;
+        case 'afield':    /* HEADER field-name: field-body */
+            preg_match('/^([^:]+):(.*)$/', $what, $what_parts);
+            if (count($what_parts) == 3)
+                $criteria = $opcode . ' ' .
+                    sqimap_asearch_encode_string($what_parts[1], $charset) . ' ' .
+                    sqimap_asearch_encode_string($what_parts[2], $charset) . ' ';
+        break;
+        case 'adate':
+            $what_parts = sqimap_asearch_parse_date($what);
+            if (isset($what_parts[0]))
+                $criteria = $opcode . ' ' . $what_parts[0] . ' ';
+        break;
+        case 'akeyword':
+        case 'astring':
+            $criteria = $opcode . ' ' . sqimap_asearch_encode_string($what, $charset) . ' ';
+        break;
+        case 'asequence':
+            $what = preg_replace('/[^0-9:()]+/', '', $what);
+            if ($what != '')
+                $criteria = $opcode . ' ' . $what . ' ';
+        break;
+    }
+    return $criteria;
 }
 
 /**
@@ -323,226 +284,55 @@ function sqimap_asearch_build_criteria($opcode, $what, $charset)
  */
 function sqimap_array_merge_unique(&$to, $from)
 {
-       if (empty($to))
-               return $from;
-       $count = count($from);
-       for ($i = 0; $i < $count; $i++) {
-               if (!in_array($from[$i], $to))
-                       $to[] = $from[$i];
-       }
-       return $to;
+    if (empty($to))
+        return $from;
+    $count = count($from);
+    for ($i = 0; $i < $count; $i++) {
+        if (!in_array($from[$i], $to))
+            $to[] = $from[$i];
+    }
+    return $to;
 }
 
 /**
- * Run the imap SEARCH command as defined in rfc 3501
+ * Run the IMAP SEARCH command as defined in rfc 3501
  * @link http://www.ietf.org/rfc/rfc3501.txt
  * @param resource $imapConnection the current imap stream
  * @param string $search_string the full search expression eg "ALL RECENT"
  * @param string $search_charset charset to use or zls ('')
  * @return array an IDs or UIDs array of matching messages or an empty array
+ * @since 1.5.0
  */
 function sqimap_run_search($imapConnection, $search_string, $search_charset)
 {
-       //For some reason, this seems to happen and forbids searching servers not allowing OPTIONAL [CHARSET]
-       if (strtoupper($search_charset) == 'US-ASCII')
-               $search_charset = '';
-       /* 6.4.4 try OPTIONAL [CHARSET] specification first */
-       if ($search_charset != '')
-               $query = 'SEARCH CHARSET "' . strtoupper($search_charset) . '" ALL ' . $search_string;
-       else
-               $query = 'SEARCH ALL ' . $search_string;
-       s_debug_dump('C:', $query);
-       $readin = sqimap_run_command($imapConnection, $query, false, $response, $message, TRUE);
-
-       /* 6.4.4 try US-ASCII charset if we tried an OPTIONAL [CHARSET] and received a tagged NO response (SHOULD be [BADCHARSET]) */
-       if (($search_charset != '')  && (strtoupper($response) == 'NO')) {
-               $query = 'SEARCH CHARSET US-ASCII ALL ' . $search_string;
-               s_debug_dump('C:', $query);
-               $readin = sqimap_run_command($imapConnection, $query, false, $response, $message, TRUE);
-       }
-       if (strtoupper($response) != 'OK') {
-               sqimap_asearch_error_box($response, $query, $message);
-               return array();
-       }
-
-       // Keep going till we find the * SEARCH response
-       foreach ($readin as $readin_part) {
-               s_debug_dump('S:', $readin_part);
-               if (substr($readin_part, 0, 9) == '* SEARCH ') {
-                       //EIMS returns multiple SEARCH responses, and this allowed according to Mark Crispin
-                       $messagelist = sqimap_array_merge_unique($messagelist, preg_split("/ /", substr($readin_part, 9)));
-               }
-       }
-
-       if (empty($messagelist))        //Empty search response, ie '* SEARCH'
-               return array();
-
-       $cnt = count($messagelist);
-       for ($q = 0; $q < $cnt; $q++)
-               $id[$q] = trim($messagelist[$q]);
-       return $id;
-}
-
-/**
- * Run the imap SORT command as defined in 
- * @link http://www.ietf.org/internet-drafts/draft-ietf-imapext-sort-13.txt
- * @param resource $imapConnection the current imap stream
- * @param string $search_string the full search expression as defined in rfc 3501
- * @param string $search_charset mandatory charset
- * @param string $sort_criteria the full sort criteria expression eg "SUBJECT REVERSE DATE"
- * @return array an IDs or UIDs array of matching messages or an empty array
- */
-function sqimap_run_sort($imapConnection, $search_string, $search_charset, $sort_criteria)
-{
-       if ($search_charset == '')
-               $search_charset = 'US-ASCII';
-       $query = 'SORT (' . $sort_criteria . ') "' . strtoupper($search_charset) . '" ALL ' . $search_string;
-       s_debug_dump('C:', $query);
-       $readin = sqimap_run_command($imapConnection, $query, false, $response, $message, TRUE);
-       s_debug_dump('S:', $response);
-
-       /* 6.4 try US-ASCII charset if we received a tagged NO response (SHOULD be [BADCHARSET]) */
-       if (($search_charset != 'US-ASCII')  && (strtoupper($response) == 'NO')) {
-               s_debug_dump('S:', $readin);
-               $query = 'SORT (' . $sort_criteria . ') US-ASCII ALL ' . $search_string;
-               s_debug_dump('C:', $query);
-               $readin = sqimap_run_command($imapConnection, $query, false, $response, $message, TRUE);
-               s_debug_dump('S:', $response);
-       }
-
-       if (strtoupper($response) != 'OK') {
-               s_debug_dump('S:', $readin);
-//     sqimap_asearch_error_box($response, $query, $message);
-//     return array();
-               return sqimap_run_search($imapConnection, $search_string, $search_charset);     // Fell back to standard search
-       }
-
-       /* Keep going till we find the * SORT response */
-       foreach ($readin as $readin_part) {
-               s_debug_dump('S:', $readin_part);
-               if (substr($readin_part, 0, 7) == '* SORT ') {
-                       //SORT returns untagged responses
-                       $messagelist = sqimap_array_merge_unique($messagelist, preg_split("/ /", substr($readin_part, 7)));
-               }
-       }
-
-       if (empty($messagelist))        //Empty search response, ie '* SORT'
-               return array();
-
-       $cnt = count($messagelist);
-       for ($q = 0; $q < $cnt; $q++)
-               $id[$q] = trim($messagelist[$q]);
-       return $id;
-}
-
-/**
- * Run the imap THREAD command as defined in 
- * @link http://www.ietf.org/internet-drafts/draft-ietf-imapext-sort-13.txt
- * @param resource $imapConnection the current imap stream
- * @param string $search_string the full search expression as defined in rfc 3501
- * @param string $search_charset mandatory charset
- * @param string $thread_algorithm the threading algorithm "ORDEREDSUBJECT" or "REFERENCES"
- * @return array an IDs or UIDs array of matching messages or an empty array
- * @global array thread_new will be used by thread view in mailbox_display
- * @global array server_sort_array will be used by thread view in mailbox_display
- */
-function sqimap_run_thread($imapConnection, $search_string, $search_charset, $thread_algorithm)
-{
-       global $thread_new, $server_sort_array;
-
-       if (sqsession_is_registered('thread_new'))
-               sqsession_unregister('thread_new');
-       if (sqsession_is_registered('server_sort_array'))
-               sqsession_unregister('server_sort_array');
-
-       $thread_new = array();
-       $thread_new[0] = "";
-
-       $server_sort_array = array();
-
-       if ($search_charset == '')
-               $search_charset = 'US-ASCII';
-       $query = 'THREAD ' . $thread_algorithm . ' "' . strtoupper($search_charset) . '" ALL ' . $search_string;
-       s_debug_dump('C:', $query);
-       $readin = sqimap_run_command($imapConnection, $query, false, $response, $message, TRUE);
-       s_debug_dump('S:', $response);
-
-       /* 6.4 try US-ASCII charset if we received a tagged NO response (SHOULD be [BADCHARSET]) */
-       if (($search_charset != 'US-ASCII')  && (strtoupper($response) == 'NO')) {
-               s_debug_dump('S:', $readin);
-               $query = 'THREAD ' . $thread_algorithm . ' US-ASCII ALL ' . $search_string;
-               s_debug_dump('C:', $query);
-               $readin = sqimap_run_command($imapConnection, $query, false, $response, $message, TRUE);
-               s_debug_dump('S:', $response);
-       }
-
-       if (strtoupper($response) != 'OK') {
-               s_debug_dump('S:', $readin);
-               if (empty($response)) { //imap server closed connection. We can't go further.
-/* we should at this point:
-       - warn the user that the THREAD call has failed
-       - (offer him a way to) disconnect it permanently in the prefs
-       - perform the regular search instead or provide a way to do it in one click
-*/
-                       global $sort, $mailbox, $php_self;
-                       $message = _("The imap server failed to handle threading.");
-                       $unthread = _("Click here to unset thread view for this mailbox and start again.");
-                       if (preg_match('/^(.+)\?.+$/', $php_self, $regs))
-                               $source_url = $regs[1];
-       else
-                               $source_url = $php_self;
-                       $link = '<a href=' . $source_url . '?sort=' . $sort . '&start_messages=1&set_thread=0&mailbox=' . urlencode($mailbox) . '>' . $unthread . '</a>';
-                       sqimap_asearch_error_box($response, $query, $message, $link);
-                       return array();
-               }
-               return sqimap_run_search($imapConnection, $search_string, $search_charset);     // Fell back to standard search
-       }
-
-       /* Keep going till we find the * THREAD response */
-       foreach ($readin as $readin_part) {
-               s_debug_dump('S:', $readin_part);
-               if (substr($readin_part, 0, 9) == '* THREAD ') {
-                       $thread_temp = preg_split("//", substr($readin_part, 9), -1, PREG_SPLIT_NO_EMPTY);
-                       break;  // Should be the last anyway
-               }
-       }
-
-       if (empty($thread_temp))        //Empty search response, ie '* THREAD'
-               return array();
-
-       $char_count = count($thread_temp);
-       $counter = 0;
-       $k = 0;
-       for ($i=0;$i<$char_count;$i++) {
-        if ($thread_temp[$i] != ')' && $thread_temp[$i] != '(') {
-                $thread_new[$k] = $thread_new[$k] . $thread_temp[$i];
-        }
-        elseif ($thread_temp[$i] == '(') {
-                $thread_new[$k] .= $thread_temp[$i];
-                $counter++;
-        }
-        elseif ($thread_temp[$i] == ')') {
-                if ($counter > 1) {
-                        $thread_new[$k] .= $thread_temp[$i];
-                        $counter = $counter - 1;
-                }
-                else {
-                        $thread_new[$k] .= $thread_temp[$i];
-                        $k++;
-                        $thread_new[$k] = "";
-                        $counter = $counter - 1;
-                }
-        }
-       }
-       sqsession_register($thread_new, 'thread_new');
-       $thread_new = array_reverse($thread_new);
-       $thread_list = implode(" ", $thread_new);
-       $thread_list = str_replace("(", " ", $thread_list);
-       $thread_list = str_replace(")", " ", $thread_list);
-       $thread_list = preg_split("/\s/", $thread_list, -1, PREG_SPLIT_NO_EMPTY);
-       $server_sort_array = $thread_list;
-       sqsession_register($server_sort_array, 'server_sort_array');
-       return $thread_list;
+    //For some reason, this seems to happen and forbids searching servers not allowing OPTIONAL [CHARSET]
+    if (strtoupper($search_charset) == 'US-ASCII')
+        $search_charset = '';
+    /* 6.4.4 try OPTIONAL [CHARSET] specification first */
+    if ($search_charset != '')
+        $query = 'SEARCH CHARSET "' . strtoupper($search_charset) . '" ' . $search_string;
+    else
+        $query = 'SEARCH ' . $search_string;
+    $readin = sqimap_run_command_list($imapConnection, $query, false, $response, $message, TRUE);
+
+    /* 6.4.4 try US-ASCII charset if we tried an OPTIONAL [CHARSET] and received a tagged NO response (SHOULD be [BADCHARSET]) */
+    if (($search_charset != '')  && (strtoupper($response) == 'NO')) {
+        $query = 'SEARCH CHARSET US-ASCII ' . $search_string;
+        $readin = sqimap_run_command_list($imapConnection, $query, false, $response, $message, TRUE);
+    }
+    if (strtoupper($response) != 'OK') {
+        sqimap_asearch_error_box($response, $query, $message);
+        return array();
+    }
+    $messagelist = parseUidList($readin,'SEARCH');
+
+    if (empty($messagelist))    //Empty search response, ie '* SEARCH'
+        return array();
+
+    $cnt = count($messagelist);
+    for ($q = 0; $q < $cnt; $q++)
+        $id[$q] = trim($messagelist[$q]);
+    return $id;
 }
 
 /**
@@ -553,15 +343,15 @@ function sqimap_run_thread($imapConnection, $search_string, $search_charset, $th
  */
 function sqimap_asearch_get_charset()
 {
-       global $allow_charset_search, $languages, $squirrelmail_language;
+    global $allow_charset_search, $languages, $squirrelmail_language;
 
-       if ($allow_charset_search)
-               return $languages[$squirrelmail_language]['CHARSET'];
-       return '';
+    if ($allow_charset_search)
+        return $languages[$squirrelmail_language]['CHARSET'];
+    return '';
 }
 
 /**
- * Convert sm internal sort to imap sort taking care of:
+ * Convert SquirrelMail internal sort to IMAP sort taking care of:
  * - user defined date sorting (ARRIVAL vs DATE)
  * - if the searched mailbox is the sent folder then TO is being used instead of FROM
  * - reverse order by using REVERSE
@@ -573,16 +363,17 @@ function sqimap_asearch_get_charset()
  */
 function sqimap_asearch_get_sort_criteria($mailbox, $sort_by)
 {
-       global $internal_date_sort, $sent_folder;
-
-       $sort_opcodes = array ('DATE', 'FROM', 'SUBJECT', 'SIZE');
-       if ($internal_date_sort == true)
-               $sort_opcodes[0] = 'ARRIVAL';
-//     if (handleAsSent($mailbox))
-//     if (isSentFolder($mailbox))
-       if ($mailbox == $sent_folder)
-               $sort_opcodes[1] = 'TO';
-       return (($sort_by % 2) ? '' : 'REVERSE ') . $sort_opcodes[($sort_by >> 1) & 3];
+    global $internal_date_sort, $sent_folder;
+
+    $sort_opcodes = array ('DATE', 'FROM', 'SUBJECT', 'SIZE');
+    if ($internal_date_sort == true)
+        $sort_opcodes[0] = 'ARRIVAL';
+// FIXME: Why are these commented out?  I have no idea what this code does, but both of these functions sound more robust than the simple string check that's being used now.  Someone who understands this code should either fix this or remove these lines completely or document why they are here commented out
+//        if (handleAsSent($mailbox))
+//        if (isSentFolder($mailbox))
+    if ($mailbox == $sent_folder)
+        $sort_opcodes[1] = 'TO';
+    return (($sort_by % 2) ? '' : 'REVERSE ') . $sort_opcodes[($sort_by >> 1) & 3];
 }
 
 /**
@@ -592,17 +383,17 @@ function sqimap_asearch_get_sort_criteria($mailbox, $sort_by)
  */
 function sqimap_asearch_get_sub_mailboxes($cur_mailbox, &$mboxes_array)
 {
-       $sub_mboxes_array = array();
-       $boxcount = count($mboxes_array);
-       for ($boxnum=0; $boxnum < $boxcount; $boxnum++) {
-               if (isBoxBelow($mboxes_array[$boxnum], $cur_mailbox))
-                       $sub_mboxes_array[] = $mboxes_array[$boxnum];
-       }
-       return $sub_mboxes_array;
+    $sub_mboxes_array = array();
+    $boxcount = count($mboxes_array);
+    for ($boxnum=0; $boxnum < $boxcount; $boxnum++) {
+        if (isBoxBelow($mboxes_array[$boxnum], $cur_mailbox))
+            $sub_mboxes_array[] = $mboxes_array[$boxnum];
+    }
+    return $sub_mboxes_array;
 }
 
 /**
- * Performs the search, given all the criteria, merging results for every mailbox
+ * Create the search query strings for all given criteria and merge results for every mailbox
  * @param resource $imapConnection
  * @param array $mailbox_array (reference)
  * @param array $biop_array (reference)
@@ -612,93 +403,87 @@ function sqimap_asearch_get_sub_mailboxes($cur_mailbox, &$mboxes_array)
  * @param array $exclude_array (reference)
  * @param array $sub_array (reference)
  * @param array $mboxes_array selectable unformatted mailboxes names (reference)
- * @global bool allow_server_sort comes from config.php
- * @global integer sort sm internal sort order
- * @global bool allow_thread_sort comes from config.php
- * @global bool thread_sort_messages does it really need to global?
- * @global integer sort_by_ref thread by references
- * @global string data_dir
- * @global string username
  * @return array array(mailbox => array(UIDs))
  */
 function sqimap_asearch($imapConnection, &$mailbox_array, &$biop_array, &$unop_array, &$where_array, &$what_array, &$exclude_array, &$sub_array, &$mboxes_array)
 {
-       global $allow_server_sort, $sort, $allow_thread_sort, $thread_sort_messages, $sort_by_ref;
-       global $data_dir, $username;
 
-       $search_charset = sqimap_asearch_get_charset();
-       $mbox_msgs = array();
-       $search_string = '';
-       $cur_mailbox = $mailbox_array[0];
-       $cur_biop = ''; /* Start with ALL */
-       /* We loop one more time than the real array count, so the last search gets fired */
-       for ($cur_crit = 0; $cur_crit <= count($where_array); $cur_crit++) {
-               if (empty($exclude_array[$cur_crit])) {
-                       $next_mailbox = $mailbox_array[$cur_crit];
-                       if ($next_mailbox != $cur_mailbox) {
-                               $search_string = trim($search_string);  /* Trim out last space */
-                               if ($cur_mailbox == 'All Folders')
-                                               $search_mboxes = $mboxes_array;
-                               else if ((!empty($sub_array[$cur_crit - 1])) || (!in_array($cur_mailbox, $mboxes_array)))
-                                       $search_mboxes = sqimap_asearch_get_sub_mailboxes($cur_mailbox, $mboxes_array);
-                               else
-                                       $search_mboxes = array($cur_mailbox);
-                               foreach ($search_mboxes as $cur_mailbox) {
-                                       s_debug_dump('C:SELECT:', $cur_mailbox);
-                                       sqimap_mailbox_select($imapConnection, $cur_mailbox);
-                                       $thread_sort_messages = $allow_thread_sort && getPref($data_dir, $username, 'thread_' . $cur_mailbox);
-                                       if ($thread_sort_messages) {
-                                               if ($sort_by_ref == 1)
-                                                       $thread_algorithm = 'REFERENCES';
-                                               else
-                                                       $thread_algorithm = 'ORDEREDSUBJECT';
-                                               $found_msgs = sqimap_run_thread($imapConnection, $search_string, $search_charset, $thread_algorithm);
-                                       }
-                                       else
-                                       if (($allow_server_sort) && ($sort < 6)) {
-                                               $sort_criteria = sqimap_asearch_get_sort_criteria($cur_mailbox, $sort);
-                                               $found_msgs = sqimap_run_sort($imapConnection, $search_string, $search_charset, $sort_criteria);
-                                       }
-                                       else
-                                               $found_msgs = sqimap_run_search($imapConnection, $search_string, $search_charset);
-                                       if (isset($mbox_msgs[$cur_mailbox])) {
-                                               if ($cur_biop == 'OR')  /* Merge with previous results */
-                                                       $mbox_msgs[$cur_mailbox] = sqimap_array_merge_unique($mbox_msgs[$cur_mailbox], $found_msgs);
-                                               else    /* Intersect previous results */
-                                                       $mbox_msgs[$cur_mailbox] = array_values(array_intersect($found_msgs, $mbox_msgs[$cur_mailbox]));
-                                       }
-                                       else /* No previous results */
-                                               $mbox_msgs[$cur_mailbox] = $found_msgs;
-                                       if (empty($mbox_msgs[$cur_mailbox]))    /* Can happen with intersect, and we need at the end a contiguous array */
-                                               unset($mbox_msgs[$cur_mailbox]);
-                               }
-                               $cur_mailbox = $next_mailbox;
-                               $search_string = '';
-                       }
-                       if (isset($where_array[$cur_crit])) {
-                               $criteria = sqimap_asearch_build_criteria($where_array[$cur_crit], $what_array[$cur_crit], $search_charset);
-                               if (!empty($criteria)) {
-                                       $unop = $unop_array[$cur_crit];
-                                       if (!empty($unop))
-                                               $criteria = $unop . ' ' . $criteria;
-                                       /* We need to infix the next non-excluded criteria's biop if it's the same mailbox */
-                                       $next_biop = '';
-                                       for ($next_crit = $cur_crit+1; $next_crit <= count($where_array); $next_crit++) {
-                                               if (empty($exclude_array[$next_crit])) {
-                                                       if (asearch_nz($mailbox_array[$next_crit]) == $cur_mailbox)
-                                                               $next_biop = asearch_nz($biop_array[$next_crit]);
-                                                       break;
-                                               }
-                                       }
-                                       if ($next_biop == 'OR')
-                                               $criteria = $next_biop . ' ' . $criteria;
-                                       $search_string .= $criteria;
-                                       $cur_biop = asearch_nz($biop_array[$cur_crit]);
-                               }
-                       }
-               }
-       }
-       return $mbox_msgs;
+    $search_charset = sqimap_asearch_get_charset();
+    $mbox_search = array();
+    $search_string = '';
+    $cur_mailbox = $mailbox_array[0];
+    $cur_biop = '';    /* Start with ALL */
+    /* We loop one more time than the real array count, so the last search gets fired */
+    for ($cur_crit=0,$iCnt=count($where_array); $cur_crit <= $iCnt; ++$cur_crit) {
+        if (empty($exclude_array[$cur_crit])) {
+            $next_mailbox = (isset($mailbox_array[$cur_crit])) ? $mailbox_array[$cur_crit] : false;
+            if ($next_mailbox != $cur_mailbox) {
+                $search_string = trim($search_string);    /* Trim out last space */
+                if ($cur_mailbox == 'All Folders')
+                    $search_mboxes = $mboxes_array;
+                else if ((!empty($sub_array[$cur_crit - 1])) || (!in_array($cur_mailbox, $mboxes_array)))
+                    $search_mboxes = sqimap_asearch_get_sub_mailboxes($cur_mailbox, $mboxes_array);
+                else
+                    $search_mboxes = array($cur_mailbox);
+                foreach ($search_mboxes as $cur_mailbox) {
+                    if (isset($mbox_search[$cur_mailbox])) {
+                        $mbox_search[$cur_mailbox]['search'] .= ' ' . $search_string;
+                    } else {
+                        $mbox_search[$cur_mailbox]['search'] = $search_string;
+                    }
+                    $mbox_search[$cur_mailbox]['charset'] = $search_charset;
+                }
+                $cur_mailbox = $next_mailbox;
+                $search_string = '';
+            }
+            if (isset($where_array[$cur_crit]) && empty($exclude_array[$cur_crit])) {
+                $aCriteria = array();
+                for ($crit = $cur_crit; $crit < count($where_array); $crit++) {
+                    $criteria = trim(sqimap_asearch_build_criteria($where_array[$crit], $what_array[$crit], $search_charset));
+                    if (!empty($criteria) && empty($exclude_array[$crit])) {
+                        if (asearch_nz($mailbox_array[$crit]) == $cur_mailbox) {
+                            $unop = $unop_array[$crit];
+                            if (!empty($unop)) {
+                                $criteria = $unop . ' ' . $criteria;
+                            }
+                            $aCriteria[] = array($biop_array[$crit], $criteria);
+                        }
+                    }
+                    // unset something
+                    $exclude_array[$crit] = true;
+                }
+                $aSearch = array();
+                for($i=0,$iCnt=count($aCriteria);$i<$iCnt;++$i) {
+                    $cur_biop = $aCriteria[$i][0];
+                    $next_biop = (isset($aCriteria[$i+1][0])) ? $aCriteria[$i+1][0] : false;
+                    if ($next_biop != $cur_biop && $next_biop == 'OR') {
+                        $aSearch[] = 'OR '.$aCriteria[$i][1];
+                    } else if ($cur_biop != 'OR') {
+                        $aSearch[] = 'ALL '.$aCriteria[$i][1];
+                    } else { // OR only supports 2 search keys so we need to create a parenthesized list
+                        $prev_biop = (isset($aCriteria[$i-1][0])) ? $aCriteria[$i-1][0] : false;
+                        if ($prev_biop == $cur_biop) {
+                            $last = $aSearch[$i-1];
+                            if (!substr($last,-1) == ')') {
+                                $aSearch[$i-1] = "(OR $last";
+                                $aSearch[] = $aCriteria[$i][1].')';
+                            } else {
+                                $sEnd = '';
+                                while ($last && substr($last,-1) == ')') {
+                                    $last = substr($last,0,-1);
+                                    $sEnd .= ')';
+                                }
+                                $aSearch[$i-1] = "(OR $last";
+                                $aSearch[] = $aCriteria[$i][1].$sEnd.')';
+                            }
+                        } else {
+                            $aSearch[] = $aCriteria[$i][1];
+                        }
+                    }
+                }
+                $search_string .= implode(' ',$aSearch);
+            }
+        }
+    }
+    return ($mbox_search);
 }
-
-?>