From 3e398182213cb66057ff40b605296092bb9fe83d Mon Sep 17 00:00:00 2001
From: alecpl <alec@alec.pl>
Date: Wed, 09 Mar 2011 05:30:15 -0500
Subject: [PATCH] - Add code for prevention from IMAP connection hangs when server closes socket unexpectedly

---
 CHANGELOG                              |    1 +
 program/include/rcube_imap_generic.php |   57 ++++++++++++++++++++++++++++++++++++++-------------------
 2 files changed, 39 insertions(+), 19 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG
index 8ea4aed..f7a12d1 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
 CHANGELOG Roundcube Webmail
 ===========================
 
+- Add code for prevention from IMAP connection hangs when server closes socket unexpectedly
 - Remove redundant DELETE query (for old session deletion) on login
 - Get around unreliable rand() and mt_rand() in session ID generation (#1486281)
 - Fix some emails are not shown using Cyrus IMAP (#1487820)
diff --git a/program/include/rcube_imap_generic.php b/program/include/rcube_imap_generic.php
index c867911..f21b6b0 100644
--- a/program/include/rcube_imap_generic.php
+++ b/program/include/rcube_imap_generic.php
@@ -213,31 +213,26 @@
     {
         $line = '';
 
-        if (!$this->fp) {
-            return NULL;
-        }
-
         if (!$size) {
             $size = 1024;
         }
 
         do {
-            if (feof($this->fp)) {
+            if ($this->eof()) {
                 return $line ? $line : NULL;
             }
 
             $buffer = fgets($this->fp, $size);
 
             if ($buffer === false) {
-                @fclose($this->fp);
-                $this->fp = null;
+                $this->closeSocket();
                 break;
             }
             if ($this->_debug) {
                 $this->debug('S: '. rtrim($buffer));
             }
             $line .= $buffer;
-        } while ($buffer[strlen($buffer)-1] != "\n");
+        } while (substr($buffer, -1) != "\n");
 
         return $line;
     }
@@ -267,7 +262,7 @@
     {
         $data = '';
         $len  = 0;
-        while ($len < $bytes && !feof($this->fp))
+        while ($len < $bytes && !$this->eof())
         {
             $d = fread($this->fp, $bytes-$len);
             if ($this->_debug) {
@@ -312,8 +307,7 @@
             } else if ($res == 'BAD') {
                 $this->errornum = self::ERROR_BAD;
             } else if ($res == 'BYE') {
-                @fclose($this->fp);
-                $this->fp = null;
+                $this->closeSocket();
                 $this->errornum = self::ERROR_BYE;
             }
 
@@ -339,6 +333,32 @@
         return self::ERROR_UNKNOWN;
     }
 
+    private function eof()
+    {
+        if (!is_resource($this->fp)) {
+            return true;
+        }
+
+        // If a connection opened by fsockopen() wasn't closed
+        // by the server, feof() will hang.
+        $start = microtime(true);
+
+        if (feof($this->fp) || 
+            ($this->prefs['timeout'] && (microtime(true) - $start > $this->prefs['timeout']))
+        ) {
+            $this->closeSocket();
+            return true;
+        }
+
+        return false;
+    }
+
+    private function closeSocket()
+    {
+        @fclose($this->fp);
+        $this->fp = null;
+    }
+
     function setError($code, $msg='')
     {
         $this->errornum = $code;
@@ -360,8 +380,7 @@
         }
         if ($error && preg_match('/^\* (BYE|BAD) /i', $string, $m)) {
             if (strtoupper($m[1]) == 'BYE') {
-                @fclose($this->fp);
-                $this->fp = null;
+                $this->closeSocket();
             }
             return true;
         }
@@ -701,11 +720,12 @@
             $host = $this->prefs['ssl_mode'] . '://' . $host;
         }
 
+        if ($this->prefs['timeout'] <= 0) {
+            $this->prefs['timeout'] = ini_get('default_socket_timeout');
+        }
+
         // Connect
-        if ($this->prefs['timeout'] > 0)
-            $this->fp = @fsockopen($host, $this->prefs['port'], $errno, $errstr, $this->prefs['timeout']);
-        else
-            $this->fp = @fsockopen($host, $this->prefs['port'], $errno, $errstr);
+        $this->fp = @fsockopen($host, $this->prefs['port'], $errno, $errstr, $this->prefs['timeout']);
 
         if (!$this->fp) {
             $this->setError(self::ERROR_BAD, sprintf("Could not connect to %s:%d: %s", $host, $this->prefs['port'], $errstr));
@@ -848,8 +868,7 @@
             $this->readReply();
         }
 
-        @fclose($this->fp);
-        $this->fp = false;
+        $this->closeSocket();
     }
 
     /**

--
Gitblit v1.9.1