X-Git-Url: https://vcs.fsf.org/?a=blobdiff_plain;f=functions%2Fimap_general.php;h=d1e81cdaaf83198d9354499c5e52fd97846ac104;hb=3b6dcf425854f5b8b1f996b5a167983c01dd8a26;hp=332fee63aaaaf1d274e35f36b633302b09ead283;hpb=b3837b0f2505e7d24304a0bb121fc5e3d06116b2;p=squirrelmail.git
diff --git a/functions/imap_general.php b/functions/imap_general.php
index 332fee63..d1e81cda 100755
--- a/functions/imap_general.php
+++ b/functions/imap_general.php
@@ -1,317 +1,1079 @@
\n" .
+ _("ERROR: No available IMAP stream.") .
+//FIXME: NO HTML IN CORE!
+ "\n";
+ error_box($string);
+ return false;
+ }
}
-/******************************************************************************
-** Both send a command and accept the result from the command. This is
-** to allow proper session number handling.
-******************************************************************************/
+/**
+ * @param stream $imap_stream imap connection resource
+ * @param string $query imap command
+ * @param boolean $handle_errors see sqimap_retrieve_imap_response()
+ * @param array $response empty string, if return = false
+ * @param array $message empty string, if return = false
+ * @param boolean $unique_id (since 1.3.0) see sqimap_session_id()
+ * @param boolean $filter (since 1.4.1 and 1.5.0) see sqimap_fread()
+ * @param mixed $outputstream (since 1.4.1 and 1.5.0) see sqimap_fread()
+ * @param boolean $no_return (since 1.4.1 and 1.5.0) see sqimap_fread()
+ * @return mixed returns false on imap error. displays error message
+ * if imap stream is not available.
+ * @since 1.2.3
+ */
+function sqimap_run_command ($imap_stream, $query, $handle_errors, &$response,
+ &$message, $unique_id = false,$filter=false,
+ $outputstream=false,$no_return=false) {
+ if ($imap_stream) {
+ $sid = sqimap_session_id($unique_id);
+ fputs ($imap_stream, $sid . ' ' . $query . "\r\n");
+ $tag_uid_a = explode(' ',trim($sid));
+ $tag = $tag_uid_a[0];
-function sqimap_run_command_list ($imap_stream, $query, $handle_errors, &$response, &$message) {
- $sid = sqimap_session_id();
- fputs ($imap_stream, $sid . ' ' . $query . "\r\n");
- $read = sqimap_read_data_list ($imap_stream, $sid, $handle_errors, $response, $message, $query );
- return $read;
-}
+ $read = sqimap_read_data ($imap_stream, $tag, $handle_errors, $response,
+ $message, $query,$filter,$outputstream,$no_return);
+ if (empty($read)) { //IMAP server dropped its connection
+ $response = '';
+ $message = '';
+ return false;
+ }
+ /* retrieve the response and the message */
+ $response = $response[$tag];
+ $message = $message[$tag];
-function sqimap_run_command ($imap_stream, $query, $handle_errors, &$response, &$message) {
- $sid = sqimap_session_id();
- fputs ($imap_stream, $sid . ' ' . $query . "\r\n");
- $read = sqimap_read_data ($imap_stream, $sid, $handle_errors, $response, $message, $query);
- return $read;
+ if (!empty($read[$tag])) {
+ return $read[$tag][0];
+ } else {
+ return $read[$tag];
+ }
+//FIXME: obey $handle_errors below!
+ } else {
+ global $squirrelmail_language, $color;
+ set_up_language($squirrelmail_language);
+//FIXME: NO HTML IN CORE!
+ $string = "\n" .
+ _("ERROR: No available IMAP stream.") .
+//FIXME: NO HTML IN CORE!
+ "\n";
+ error_box($string);
+ return false;
+ }
}
+/**
+ * @param mixed $new_query
+ * @param string $tag
+ * @param array $aQuery
+ * @param boolean $unique_id see sqimap_session_id()
+ * @since 1.5.0
+ */
+function sqimap_prepare_pipelined_query($new_query,&$tag,&$aQuery,$unique_id) {
+ $sid = sqimap_session_id($unique_id);
+ $tag_uid_a = explode(' ',trim($sid));
+ $tag = $tag_uid_a[0];
+ $query = $sid . ' '.$new_query."\r\n";
+ $aQuery[$tag] = $query;
+}
-/*
- * Reads the output from the IMAP stream. If handle_errors is set to true,
- * this will also handle all errors that are received. If it is not set,
- * the errors will be sent back through $response and $message
+/**
+ * @param stream $imap_stream imap stream
+ * @param array $aQueryList
+ * @param boolean $handle_errors
+ * @param array $aServerResponse
+ * @param array $aServerMessage
+ * @param boolean $unique_id see sqimap_session_id()
+ * @param boolean $filter see sqimap_fread()
+ * @param mixed $outputstream see sqimap_fread()
+ * @param boolean $no_return see sqimap_fread()
+ * @since 1.5.0
*/
+function sqimap_run_pipelined_command ($imap_stream, $aQueryList, $handle_errors,
+ &$aServerResponse, &$aServerMessage, $unique_id = false,
+ $filter=false,$outputstream=false,$no_return=false) {
+ $aResponse = false;
-function sqimap_read_data_list ($imap_stream, $pre, $handle_errors,
- &$response, &$message, $query = '') {
- global $color, $squirrelmail_language;
+ /*
+ Do not fire all calls at once to the IMAP server but split the calls up
+ in portions of $iChunkSize. If we do not do that I think we misbehave as
+ IMAP client or should handle BYE calls if the IMAP server drops the
+ connection because the number of queries is to large. This isn't tested
+ but a wild guess how it could work in the field.
- $read = '';
- $bufsize = 9096;
- $resultlist = array();
+ After testing it on Exchange 2000 we discovered that a chunksize of 32
+ was quicker then when we raised it to 128.
+ */
+ $iQueryCount = count($aQueryList);
+ $iChunkSize = 32;
+ // array_chunk would also do the job but it's supported from php > 4.2
+ $aQueryChunks = array();
+ $iLoops = floor($iQueryCount / $iChunkSize);
- $more_msgs = true;
- while ($more_msgs) {
- $data = array();
- $total_size = 0;
+ if ($iLoops * $iChunkSize != $iQueryCount) ++$iLoops;
- while (strpos($read, "\n") === false) {
- if(!($read .= fgets($imap_stream, $bufsize))) {
- break;
+ if (!function_exists('array_chunk')) { // arraychunk replacement
+ reset($aQueryList);
+ for($i=0;$i<$iLoops;++$i) {
+ for($j=0;$j<$iChunkSize;++$j) {
+ $key = key($aQueryList);
+ $aTmp[$key] = $aQueryList[$key];
+ if (next($aQueryList) === false) break;
}
+ $aQueryChunks[] = $aTmp;
}
+ } else {
+ $aQueryChunks = array_chunk($aQueryList,$iChunkSize,true);
+ }
- // if (ereg("^\\* [0-9]+ FETCH.*\\{([0-9]+)\\}", $read, $regs)) {
- if (preg_match('/^\* [0-9]+ FETCH.*\{([0-9]+)\}/', $read, $regs)) {
- $size = $regs[1];
- } else if (ereg("^\\* [0-9]+ FETCH", $read, $regs)) {
- // Sizeless response, probably single-line
- $size = -1;
- $data[] = $read;
- $read = fgets($imap_stream, $bufsize);
- } else {
- $size = -1;
+ for ($i=0;$i<$iLoops;++$i) {
+ $aQuery = $aQueryChunks[$i];
+ foreach($aQuery as $tag => $query) {
+ fputs($imap_stream,$query);
+ $aResults[$tag] = false;
}
- while (1) {
- while (strpos($read, "\n") === false) {
- if(!($read .= fgets($imap_stream, $bufsize))) {
- break;
+ foreach($aQuery as $tag => $query) {
+ if ($aResults[$tag] == false) {
+ $aReturnedResponse = sqimap_retrieve_imap_response ($imap_stream, $tag,
+ $handle_errors, $response, $message, $query,
+ $filter,$outputstream,$no_return);
+ foreach ($aReturnedResponse as $returned_tag => $aResponse) {
+ if (!empty($aResponse)) {
+ $aResults[$returned_tag] = $aResponse[0];
+ } else {
+ $aResults[$returned_tag] = $aResponse;
+ }
+ $aServerResponse[$returned_tag] = $response[$returned_tag];
+ $aServerMessage[$returned_tag] = $message[$returned_tag];
}
}
- // If we know the size, no need to look at the end parameters
- if ($size > 0) {
- if ($total_size == $size) {
- // We've reached the end of this 'message', switch to the next one.
- $data[] = $read;
- break;
- } else if ($total_size > $size) {
- $difference = $total_size - $size;
- $total_size = $total_size - strlen($read);
- $data[] = substr ($read, 0, strlen($read)-$difference);
- $read = substr ($read, strlen($read)-$difference, strlen($read));
- break;
- } else {
- $data[] = $read;
- $read = fgets($imap_stream, $bufsize);
- while (strpos($read, "\n") === false) {
- $read .= fgets($imap_stream, $bufsize);
+ }
+ }
+ return $aResults;
+}
+
+/**
+ * Custom fgets function: gets a line from the IMAP server,
+ * no matter how big it may be.
+ * @param stream $imap_stream the stream to read from
+ * @return string a line
+ * @since 1.2.8
+ */
+function sqimap_fgets($imap_stream) {
+ $read = '';
+ $buffer = 4096;
+ $results = '';
+ $offset = 0;
+ while (strpos($results, "\r\n", $offset) === false) {
+ if (!($read = fgets($imap_stream, $buffer))) {
+ /* this happens in case of an error */
+ /* reset $results because it's useless */
+ $results = false;
+ break;
+ }
+ if ( $results != '' ) {
+ $offset = strlen($results) - 1;
+ }
+ $results .= $read;
+ }
+ return $results;
+}
+
+/**
+ * @param stream $imap_stream
+ * @param integer $iSize
+ * @param boolean $filter
+ * @param mixed $outputstream stream or 'php://stdout' string
+ * @param boolean $no_return controls data returned by function
+ * @return string
+ * @since 1.4.1
+ */
+function sqimap_fread($imap_stream,$iSize,$filter=false,
+ $outputstream=false, $no_return=false) {
+ if (!$filter || !$outputstream) {
+ $iBufferSize = $iSize;
+ } else {
+ // see php bug 24033. They changed fread behaviour %$^&$%
+ $iBufferSize = 7800; // multiple of 78 in case of base64 decoding.
+ }
+ if ($iSize < $iBufferSize) {
+ $iBufferSize = $iSize;
+ }
+
+ $iRetrieved = 0;
+ $results = '';
+ $sRead = $sReadRem = '';
+ // NB: fread can also stop at end of a packet on sockets.
+ while ($iRetrieved < $iSize) {
+ $sRead = fread($imap_stream,$iBufferSize);
+ $iLength = strlen($sRead);
+ $iRetrieved += $iLength ;
+ $iRemaining = $iSize - $iRetrieved;
+ if ($iRemaining < $iBufferSize) {
+ $iBufferSize = $iRemaining;
+ }
+ if ($sRead == '') {
+ $results = false;
+ break;
+ }
+ if ($sReadRem != '') {
+ $sRead = $sReadRem . $sRead;
+ $sReadRem = '';
+ }
+
+ if ($filter && $sRead != '') {
+ // in case the filter is base64 decoding we return a remainder
+ $sReadRem = $filter($sRead);
+ }
+ if ($outputstream && $sRead != '') {
+ if (is_resource($outputstream)) {
+ fwrite($outputstream,$sRead);
+ } else if ($outputstream == 'php://stdout') {
+ echo $sRead;
+ }
+ }
+ if ($no_return) {
+ $sRead = '';
+ } else {
+ $results .= $sRead;
+ }
+ }
+ return $results;
+}
+
+
+/**
+ * Obsolete function, inform plugins that use it
+ * @param stream $imap_stream
+ * @param string $tag
+ * @param boolean $handle_errors
+ * @param array $response
+ * @param array $message
+ * @param string $query
+ * @since 1.1.3
+ * @deprecated (since 1.5.0) use sqimap_run_command or sqimap_run_command_list instead
+ */
+function sqimap_read_data_list($imap_stream, $tag, $handle_errors,
+ &$response, &$message, $query = '') {
+ global $color, $oTemplate, $squirrelmail_language;
+ set_up_language($squirrelmail_language);
+//FIXME: NO HTML IN CORE!
+ $string = "\n" .
+ _("ERROR: Bad function call.") .
+//FIXME: NO HTML IN CORE!
+ "
\n" .
+ _("Reason:") . ' '.
+ 'There is a plugin installed which make use of the
' .
+ 'SquirrelMail internal function sqimap_read_data_list.
'.
+ 'Please adapt the installed plugin and let it use
'.
+ 'sqimap_run_command or sqimap_run_command_list instead
'.
+ 'The following query was issued:
'.
+//FIXME: NO HTML IN CORE!
+ htmlspecialchars($query) . '
' . "
\n";
+ error_box($string);
+ $oTemplate->display('footer.tpl');
+ exit;
+}
+
+/**
+ * Function to display an error related to an IMAP query.
+ * @param string title the caption of the error box
+ * @param string query the query that went wrong
+ * @param string message_title optional message title
+ * @param string message optional error message
+ * @param string $link an optional link to try again
+ * @return void
+ * @since 1.5.0
+ */
+function sqimap_error_box($title, $query = '', $message_title = '', $message = '', $link = '')
+{
+ global $color, $squirrelmail_language;
+
+ set_up_language($squirrelmail_language);
+//FIXME: NO HTML IN CORE!
+ $string = "\n" . $title . "
\n";
+ $cmd = explode(' ',$query);
+ $cmd= strtolower($cmd[0]);
+
+ if ($query != '' && $cmd != 'login')
+ $string .= _("Query:") . ' ' . htmlspecialchars($query) . '
';
+ if ($message_title != '')
+ $string .= $message_title;
+ if ($message != '')
+ $string .= htmlspecialchars($message);
+//FIXME: NO HTML IN CORE!
+ $string .= "
\n";
+ if ($link != '')
+ $string .= $link;
+ error_box($string);
+}
+
+/**
+ * Reads the output from the IMAP stream. If handle_errors is set to true,
+ * this will also handle all errors that are received. If it is not set,
+ * the errors will be sent back through $response and $message.
+ * @param stream $imap_stream imap stream
+ * @param string $tag
+ * @param boolean $handle_errors handle errors internally or send them in $response and $message.
+ * @param array $response
+ * @param array $message
+ * @param string $query command that can be printed if something fails
+ * @param boolean $filter see sqimap_fread()
+ * @param mixed $outputstream see sqimap_fread()
+ * @param boolean $no_return see sqimap_fread()
+ * @since 1.5.0
+ */
+function sqimap_retrieve_imap_response($imap_stream, $tag, $handle_errors,
+ &$response, &$message, $query = '',
+ $filter = false, $outputstream = false, $no_return = false) {
+ global $color, $squirrelmail_language;
+ $read = '';
+ if (!is_array($message)) $message = array();
+ if (!is_array($response)) $response = array();
+ $aResponse = '';
+ $resultlist = array();
+ $data = array();
+ $sCommand = '';
+ if (preg_match("/^(\w+)\s*/",$query,$aMatch)) {
+ $sCommand = strtoupper($aMatch[1]);
+ } else {
+ // error reporting (shouldn't happen)
+ }
+ $read = sqimap_fgets($imap_stream);
+ $i = 0;
+ while ($read) {
+ $char = $read{0};
+ switch ($char)
+ {
+ case '+':
+ default:
+ $read = sqimap_fgets($imap_stream);
+ break;
+
+ case $tag{0}:
+ {
+ /* get the command */
+ $arg = '';
+ $i = strlen($tag)+1;
+ $s = substr($read,$i);
+ if (($j = strpos($s,' ')) || ($j = strpos($s,"\n"))) {
+ $arg = substr($s,0,$j);
+ }
+ $found_tag = substr($read,0,$i-1);
+ if ($found_tag) {
+ switch ($arg)
+ {
+ case 'OK':
+ case 'BAD':
+ case 'NO':
+ case 'BYE':
+ case 'PREAUTH':
+ $response[$found_tag] = $arg;
+ $message[$found_tag] = trim(substr($read,$i+strlen($arg)));
+ if (!empty($data)) {
+ $resultlist[] = $data;
+ }
+ $aResponse[$found_tag] = $resultlist;
+ $data = $resultlist = array();
+ if ($found_tag == $tag) {
+ break 3; /* switch switch while */
+ }
+ break;
+ default:
+ /* this shouldn't happen */
+ $response[$found_tag] = $arg;
+ $message[$found_tag] = trim(substr($read,$i+strlen($arg)));
+ if (!empty($data)) {
+ $resultlist[] = $data;
+ }
+ $aResponse[$found_tag] = $resultlist;
+ $data = $resultlist = array();
+ if ($found_tag == $tag) {
+ break 3; /* switch switch while */
}
}
- $total_size += strlen($read);
+ }
+ $read = sqimap_fgets($imap_stream);
+ if ($read === false) { /* error */
+ break 2; /* switch while */
+ }
+ break;
+ } // end case $tag{0}
+
+ case '*':
+ {
+ if (($sCommand == "FETCH" || $sCommand == "STORE") && preg_match('/^\*\s\d+\sFETCH/',$read)) {
+ /* check for literal */
+ $s = substr($read,-3);
+ $fetch_data = array();
+ do { /* outer loop, continue until next untagged fetch
+ or tagged reponse */
+ do { /* innerloop for fetching literals. with this loop
+ we prohibid that literal responses appear in the
+ outer loop so we can trust the untagged and
+ tagged info provided by $read */
+ $read_literal = false;
+ if ($s === "}\r\n") {
+ $j = strrpos($read,'{');
+ $iLit = substr($read,$j+1,-3);
+ $fetch_data[] = $read;
+ $sLiteral = sqimap_fread($imap_stream,$iLit,$filter,$outputstream,$no_return);
+ if ($sLiteral === false) { /* error */
+ break 4; /* while while switch while */
+ }
+ /* backwards compattibility */
+ $aLiteral = explode("\n", $sLiteral);
+ /* release not neaded data */
+ unset($sLiteral);
+ foreach ($aLiteral as $line) {
+ $fetch_data[] = $line ."\n";
+ }
+ /* release not neaded data */
+ unset($aLiteral);
+ /* next fgets belongs to this fetch because
+ we just got the exact literalsize and there
+ must follow data to complete the response */
+ $read = sqimap_fgets($imap_stream);
+ if ($read === false) { /* error */
+ break 4; /* while while switch while */
+ }
+ $s = substr($read,-3);
+ $read_literal = true;
+ continue;
+ } else {
+ $fetch_data[] = $read;
+ }
+ /* retrieve next line and check in the while
+ statements if it belongs to this fetch response */
+ $read = sqimap_fgets($imap_stream);
+ if ($read === false) { /* error */
+ break 4; /* while while switch while */
+ }
+ /* check for next untagged reponse and break */
+ if ($read{0} == '*') break 2;
+ $s = substr($read,-3);
+ } while ($s === "}\r\n" || $read_literal);
+ $s = substr($read,-3);
+ } while ($read{0} !== '*' &&
+ substr($read,0,strlen($tag)) !== $tag);
+ $resultlist[] = $fetch_data;
+ /* release not neaded data */
+ unset ($fetch_data);
} else {
- if (preg_match("/^$pre (OK|BAD|NO)(.*)/", $read, $regs) ||
- // if (ereg("^$pre (OK|BAD|NO)(.*)", $read, $regs) ||
- (($size == -1) && ereg("^\\* [0-9]+ FETCH.*", $read, $regs))) {
- break;
- } else if ( preg_match("/^\* OK \[PARSE.*/", $read, $regs ) ) {
- /*
- This block has been added in order to avoid the problem
- caused by the * OK [PARSE] Missing parameter answer
- Please, replace it with a better parsing if you know how.
- */
- $read = fgets ($imap_stream, $bufsize);
- $data[] = $read;
- $read = fgets ($imap_stream, $bufsize);
- } else {
- $data[] = $read;
- $read = fgets ($imap_stream, $bufsize);
- }
+ $s = substr($read,-3);
+ do {
+ if ($s === "}\r\n") {
+ $j = strrpos($read,'{');
+ $iLit = substr($read,$j+1,-3);
+ // check for numeric value to avoid that untagged responses like:
+ // * OK [PARSE] Unexpected characters at end of address: {SET:debug=51}
+ // will trigger literal fetching ({SET:debug=51} !== int )
+ if (is_numeric($iLit)) {
+ $data[] = $read;
+ $sLiteral = fread($imap_stream,$iLit);
+ if ($sLiteral === false) { /* error */
+ $read = false;
+ break 3; /* while switch while */
+ }
+ $data[] = $sLiteral;
+ $data[] = sqimap_fgets($imap_stream);
+ } else {
+ $data[] = $read;
+ }
+ } else {
+ $data[] = $read;
+ }
+ $read = sqimap_fgets($imap_stream);
+ if ($read === false) {
+ break 3; /* while switch while */
+ } else if ($read{0} == '*') {
+ break;
+ }
+ $s = substr($read,-3);
+ } while ($s === "}\r\n");
+ break 1;
}
- }
+ break;
+ } // end case '*'
+ } // end switch
+ } // end while
- // while (($more_msgs = !ereg("^$pre (OK|BAD|NO)(.*)$", $read, $regs)) &&
- // !ereg("^\\* [0-9]+ FETCH.*", $read, $regs)) {
- while (($more_msgs = !preg_match("/^$pre (OK|BAD|NO)(.*)$/", $read, $regs)) &&
- !preg_match('/^\* [0-9]+ FETCH.*/', $read, $regs)) {
- $read = fgets($imap_stream, $bufsize);
+ /* error processing in case $read is false */
+ if ($read === false) {
+ // try to retrieve an untagged bye respons from the results
+ $sResponse = array_pop($data);
+ if ($sResponse !== NULL && strpos($sResponse,'* BYE') !== false) {
+ if (!$handle_errors) {
+ $query = '';
+ }
+ sqimap_error_box(_("ERROR: IMAP server closed the connection."), $query, _("Server responded:"),$sResponse);
+//FIXME: NO HTML IN CORE!
+ echo '