From: Michael M Slusarz Date: Thu, 22 Apr 2010 22:45:09 +0000 (-0600) Subject: Handle binary literal data in a single location. X-Git-Url: https://git.internetallee.de/?a=commitdiff_plain;h=a65ebc8dfc6c9476c0eccd71934954c8aba2426b;p=horde.git Handle binary literal data in a single location. Benefit: cleans up append() implementation, and allows append data to be handled entirely via streams. --- diff --git a/framework/Imap_Client/lib/Horde/Imap/Client/Base.php b/framework/Imap_Client/lib/Horde/Imap/Client/Base.php index c8ad696b3..c8b7a6073 100644 --- a/framework/Imap_Client/lib/Horde/Imap/Client/Base.php +++ b/framework/Imap_Client/lib/Horde/Imap/Client/Base.php @@ -2796,14 +2796,15 @@ abstract class Horde_Imap_Client_Base * @param string $out The unprocessed command string. * @param array $query An array with the following format: *
-     * (null) Ignored
-     * (string) - Output as-is (raw)
      * (array)
      *   * Array with keys 't' and 'v'
      *     + 't' key = IMAP data type (Horde_Imap_Client::DATA_* constants)
      *     + 'v' key = Data value
      *   * Array with only values
      *     + Treated as a parenthesized list
+     * (null) Ignored
+     * (resource) Treated as literal data
+     * (string) Output as-is (raw)
      * 
* @param callback $callback A callback function to use if literal data * is found. Two arguments are passed: the @@ -2869,6 +2870,12 @@ abstract class Horde_Imap_Client_Base } else { $out = rtrim($this->parseCommandArray($val, $out . '(', $callback)) . ') '; } + } elseif (is_resource($val)) { + /* Resource indicates literal data. Absolutely nothing we can + * do without a callback function here. */ + if (!is_null($callback)) { + $out = call_user_func_array($callback, array($out, $val)); + } } else { $out .= $val . ' '; } diff --git a/framework/Imap_Client/lib/Horde/Imap/Client/Socket.php b/framework/Imap_Client/lib/Horde/Imap/Client/Socket.php index dfff507c2..5559d9e90 100644 --- a/framework/Imap_Client/lib/Horde/Imap/Client/Socket.php +++ b/framework/Imap_Client/lib/Horde/Imap/Client/Socket.php @@ -1363,6 +1363,19 @@ class Horde_Imap_Client_Socket extends Horde_Imap_Client_Base { $this->login(); + // Check for MULTIAPPEND extension (RFC 3502) + if ((count($data) > 1) && + !$this->queryCapability('MULTIAPPEND')) { + $result = array(); + foreach (array_keys($data) as $key) { + $res = $this->_append($mailbox, array($data[$key]), $options); + $result = ($res === true) + ? true + : array_merge($result, $res); + } + return $result; + } + // If the mailbox is currently selected read-only, we need to close // because some IMAP implementations won't allow an append. if (($this->_selected == $mailbox) && @@ -1370,95 +1383,53 @@ class Horde_Imap_Client_Socket extends Horde_Imap_Client_Base $this->close(); } - // Check for MULTIAPPEND extension (RFC 3502) - $multiappend = $this->queryCapability('MULTIAPPEND'); - $t = &$this->_temp; $t['appenduid'] = array(); $t['trycreate'] = null; $t['uidplusmbox'] = $mailbox; - $cnt = count($data); - $i = 0; - $notag = false; - $literaldata = true; - reset($data); - while (list(,$m_data) = each($data)) { - if (!$i++ || !$multiappend) { - $cmd = array( - 'APPEND', - array('t' => Horde_Imap_Client::DATA_MAILBOX, 'v' => $mailbox) - ); - } else { - $cmd = array(); - $notag = true; - } + $cmd = array( + 'APPEND', + array('t' => Horde_Imap_Client::DATA_MAILBOX, 'v' => $mailbox) + ); + + stream_filter_register('horde_eol', 'Horde_Stream_Filter_Eol'); - if (!empty($m_data['flags'])) { + foreach (array_keys($data) as $key) { + if (!empty($data[$key]['flags'])) { $tmp = array(); - foreach ($m_data['flags'] as $val) { + foreach ($data[$key]['flags'] as $val) { $tmp[] = array('t' => Horde_Imap_Client::DATA_ATOM, 'v' => $val); } $cmd[] = $tmp; } - if (!empty($m_data['internaldate'])) { - $cmd[] = $m_data['internaldate']->format('j-M-Y H:i:s O'); - } - - /* @todo There is no way I am aware of to determine the length of - * a stream. Having a user pass in the length of a stream is - * cumbersome, and they would most likely have to do just as much - * work to get the length of the stream as we have to do here. So - * for now, simply grab the contents of the stream and do a - * strlen() call to determine the literal size to send to the - * IMAP server. */ - if (is_resource($m_data['data'])) { - rewind($m_data['data']); - } - $text = $this->utils->removeBareNewlines(is_resource($m_data['data']) ? stream_get_contents($m_data['data']) : $m_data['data']); - $datalength = strlen($text); - - /* RFC 3516/4466 says we should be able to append binary data - * using literal8 "~{#} format", but it doesn't seem to work in - * all servers tried (UW-IMAP/Cyrus). However, there is no other - * way to append null data, so try anyway. */ - $binary = (strpos($text, "\0") !== false); - - /* Need to add 2 additional characters (we send CRLF at the end of - * a line) to literal count for multiappend messages to ensure the - * server will accept the next line of information, which contains - * the next append request. */ - if ($multiappend) { - if ($i == $cnt) { - $literaldata = false; - } else { - $datalength += 2; - } - } else { - $literaldata = false; + if (!empty($data[$key]['internaldate'])) { + $cmd[] = $data[$key]['internaldate']->format('j-M-Y H:i:s O'); } - try { - $this->_sendLine($cmd, array( - 'binary' => $binary, - 'literal' => $datalength, - 'notag' => $notag - )); - } catch (Horde_Imap_Client_Exception $e) { - if (!empty($options['create']) && $this->_temp['trycreate']) { - $this->createMailbox($mailbox); - unset($options['create']); - return $this->_append($mailbox, $data, $options); - } - throw $e; + $text = fopen('php://temp', 'w+'); + stream_filter_append($text, 'horde_eol', STREAM_FILTER_WRITE); + + if (is_resource($data[$key]['data'])) { + rewind($data[$key]['data']); + stream_copy_to_stream($data[$key]['data'], $text); + } else { + fwrite($text, $data[$key]['data']); } - // Send data. - $this->_sendLine($text, array( - 'literaldata' => $literaldata, - 'notag' => true - )); + $cmd[] = $text; + } + + try { + $this->_sendLine($cmd); + } catch (Horde_Imap_Client_Exception $e) { + if (!empty($options['create']) && $this->_temp['trycreate']) { + $this->createMailbox($mailbox); + unset($options['create']); + return $this->_append($mailbox, $data, $options); + } + throw $e; } /* If we reach this point and have data in $_temp['appenduid'], @@ -3572,11 +3543,12 @@ class Horde_Imap_Client_Socket extends Horde_Imap_Client_Base * meant for a command while scanning for untagged responses * unilaterally sent by the server. * - * @param mixed $query The IMAP command to execute. If string TODO. If - * array TODO. + * @param mixed $data The IMAP command to execute. If string output as + * is. If array, parsed via parseCommandArray(). If + * resource, output directly to server. * @param array $options Additional options: *
-     * 'binary' - (boolean) Does $query contain binary data?  If so, and the
+     * 'binary' - (boolean) Does $data contain binary data?  If so, and the
      *            'BINARY' extension is available on the server, the data
      *            will be sent in literal8 format. If not available, an
      *            exception will be returned. 'binary' requires literal to
@@ -3602,7 +3574,7 @@ class Horde_Imap_Client_Socket extends Horde_Imap_Client_Base
      *
      * @throws Horde_Imap_Client_Exception
      */
-    protected function _sendLine($query, $options = array())
+    protected function _sendLine($data, $options = array())
     {
         $out = '';
 
@@ -3616,14 +3588,14 @@ class Horde_Imap_Client_Socket extends Horde_Imap_Client_Base
             );
         }
 
-        if (is_array($query)) {
+        if (is_array($data)) {
             if (!empty($options['debug'])) {
                 $this->_temp['sendnodebug'] = true;
             }
-            $out = rtrim($this->parseCommandArray($query, $out, array($this, 'parseCommandArrayCallback')));
+            $out = rtrim($this->parseCommandArray($data, $out, array($this, 'parseCommandArrayCallback')));
             unset($this->_temp['sendnodebug']);
-        } else {
-            $out .= $query;
+        } elseif (is_string($data)) {
+            $out .= $data;
         }
 
         $continuation = $literalplus = false;
@@ -3653,10 +3625,23 @@ class Horde_Imap_Client_Socket extends Horde_Imap_Client_Base
         }
 
         if ($this->_debug && empty($this->_temp['sendnodebug'])) {
-            fwrite($this->_debug, '(' . microtime(true) . ') C: ' . (empty($options['debug']) ? $out : $options['debug']) . "\n");
+            fwrite($this->_debug, '(' . microtime(true) . ') C: ');
+            if (is_resource($data)) {
+                rewind($data);
+                while ($in = fread($data, 8192)) {
+                    fwrite($this->_debug, $in);
+                }
+            } else {
+                fwrite($this->_debug, (empty($options['debug']) ? $out : $options['debug']) . "\n");
+            }
         }
 
-        fwrite($this->_stream, $out . "\r\n");
+        if (is_resource($data)) {
+            rewind($data);
+            stream_copy_to_stream($data, $this->_stream);
+        } else {
+            fwrite($this->_stream, $out . "\r\n");
+        }
 
         if ($literalplus) {
             return;
@@ -3677,19 +3662,40 @@ class Horde_Imap_Client_Socket extends Horde_Imap_Client_Base
     /**
      * Callback for parseCommandArray() when literal data is found.
      *
-     * @param string $cmd      The unprocessed command string.
-     * @param string $literal  The literal data.
+     * @param string $cmd  The unprocessed command string.
+     * @param mixed $data  The literal data (either a string or a resource).
      *
      * @return string  The new unprocessed command string.
      */
-    public function parseCommandArrayCallback($cmd, $literal)
+    public function parseCommandArrayCallback($cmd, $data)
     {
-        $this->_sendLine($cmd, array(
-            'literal' => strlen($literal),
+        /* RFC 3516/4466 says we should be able to append binary data
+         * using literal8 "~{#} format", but it doesn't seem to work in
+         * all servers tried (UW-IMAP/Cyrus). However, there is no other
+         * way to append null data, so try anyway. */
+        if (is_string($data)) {
+            $binary = (strpos($data, "\0") !== false);
+            $len = strlen($data);
+        } else {
+            $binary = false;
+            rewind($data);
+            while (($in = fread($data, 8192))) {
+                if (strpos($in, "\0") !== false) {
+                    $binary = true;
+                    break;
+                }
+            }
+            fseek($data, 0, SEEK_END);
+            $len = ftell($data);
+        }
+
+        $this->_sendLine(rtrim($cmd), array(
+            'binary' => $binary,
+            'literal' => $len,
             'notag' => true
         ));
 
-        $this->_sendLine($literal, array(
+        $this->_sendLine($data, array(
             'literaldata' => true,
             'notag' => true
         ));
diff --git a/framework/Imap_Client/package.xml b/framework/Imap_Client/package.xml
index 4630645a0..af52e4494 100644
--- a/framework/Imap_Client/package.xml
+++ b/framework/Imap_Client/package.xml
@@ -112,6 +112,10 @@ http://pear.php.net/dtd/package-2.0.xsd">
      Serialize
      pear.horde.org
    
+   
+    Stream_Filter
+    pear.horde.org
+   
    
     imap