From: Michael M Slusarz Date: Fri, 23 Apr 2010 05:08:53 +0000 (-0600) Subject: Add support for CATENATE extension (RFC 4469). X-Git-Url: https://git.internetallee.de/?a=commitdiff_plain;h=a83fc6c20fce54c2d398da3a6cd7a510e6e54e59;p=horde.git Add support for CATENATE extension (RFC 4469). --- diff --git a/framework/Imap_Client/lib/Horde/Imap/Client/Base.php b/framework/Imap_Client/lib/Horde/Imap/Client/Base.php index 5530648c7..c84570b68 100644 --- a/framework/Imap_Client/lib/Horde/Imap/Client/Base.php +++ b/framework/Imap_Client/lib/Horde/Imap/Client/Base.php @@ -1159,8 +1159,15 @@ abstract class Horde_Imap_Client_Base * each embedded array having the following * entries: *
-     * 'data' - (mixed) The data to append. Either a string or a stream
-     *          resource.
+     * 'data' - (mixed) The data to append. If a string or a stream resource,
+     *          this will be used as the entire contents of a single message.
+     *          If an array, will catenate all given parts into a single
+     *          message. This array contains one or more arrays with two keys:
+     *            't' - (string) Either 'url' or 'text'.
+     *            'v' - (mixed) If 't' is 'url', this is the IMAP URL to the
+     *                  message part to append. If 't' is 'text', this is
+     *                  either a string or resource representation of the
+     *                  message part data.
      *          DEFAULT: NONE (entry is MANDATORY)
      * 'flags' - (array) An array of flags/keywords to set on the appended
      *           message.
@@ -1705,10 +1712,11 @@ abstract class Horde_Imap_Client_Base
      *               returned, the starting position is identified here.
      *               DEFAULT: The entire text is returned.
      *   Return key: 'headertext'
-     *   Return format: (mixed) If 'parse' is true, a Horde_Mime_Headers
-     *                  object. Else, the raw text of the header (or the
-     *                  (portion of the text delineated by the 'start' &
-     *                  'length' parameters).
+     *   Return format: (array) An array of header text entries. Keys are
+     *                  the 'id'. If 'parse' is true, values are
+     *                  Horde_Mime_Headers objects. Otherwise, values are the
+     *                  raw text of the header (or the portion of the text
+     *                  delineated by the 'start' & 'length' parameters).
      *
      * Key: Horde_Imap_Client::FETCH_BODYTEXT
      *   Desc: Returns the body text. Body text is defined only for the
@@ -2886,6 +2894,119 @@ abstract class Horde_Imap_Client_Base
     }
 
     /**
+     * Given an IMAP body section string, fetches the corresponding part.
+     *
+     * @param string $mailbox  The IMAP mailbox name.
+     * @param integer $uid     The IMAP UID.
+     * @param string $section  The IMAP section string.
+     *
+     * @return resource  The section contents in a stream.
+     * @throws Horde_Imap_Client_Exception
+     */
+    public function fetchFromSectionString($mailbox, $uid, $section = null)
+    {
+        $section = trim($section);
+
+        // BODY[]
+        if (!strlen($section)) {
+            $fetch = $this->fetch($mailbox, array(
+                Horde_Imap_Client::FETCH_FULLMSG => array(
+                    'peek' => true,
+                    'stream' => true
+                )
+            ), array('ids' => array($uid)));
+            return $fetch[$uid]['fullmsg'];
+        }
+
+        // BODY[<#.>HEADER.FIELDS<.NOT>()]
+        if (($pos = stripos($section, 'HEADER.FIELDS')) !== false) {
+            $hdr_pos = strpos($section, '(');
+            $cmd = substr($section, 0, $hdr_pos);
+
+            $fetch = $this->fetch($mailbox, array(
+                Horde_Imap_Client::FETCH_HEADERS => array(
+                    array(
+                        'headers' => explode(' ', substr($section, $hdr_pos + 1, strrpos($section, ')') - $hdr_pos)),
+                        'id' => ($pos ? substr($section, 0, $pos - 1) : 0),
+                        'label' => 'section',
+                        'notsearch' => (stripos($cmd, '.NOT') !== false),
+                        'peek' => true
+                    )
+                )
+            ), array('ids' => array($uid)));
+
+            $stream = fopen('php://temp', 'w+');
+            fwrite($stream, $fetch[$uid]['headers']['section']);
+            return $stream;
+        }
+
+        // BODY[#]
+        if (is_numeric(substr($section, -1))) {
+            $fetch = $this->fetch($mailbox, array(
+                Horde_Imap_Client::FETCH_BODYPART => array(
+                    array(
+                        'id' => $section,
+                        'peek' => true,
+                        'stream' => true
+                    )
+                )
+            ), array('ids' => array($uid)));
+            return $fetch[$uid]['bodypart'][$section];
+        }
+
+        // BODY[<#.>HEADER]
+        if (($pos = stripos($section, 'HEADER')) !== false) {
+            $id = ($pos ? substr($section, 0, $pos - 1) : 0);
+            $fetch = $this->fetch($mailbox, array(
+                Horde_Imap_Client::FETCH_HEADERTEXT => array(
+                    array(
+                        'id' => $id,
+                        'peek' => true
+                    )
+                )
+            ), array('ids' => array($uid)));
+
+            $stream = fopen('php://temp', 'w+');
+            fwrite($stream, $fetch[$uid]['headertext'][$id]);
+            return $stream;
+        }
+
+        // BODY[<#.>TEXT]
+        if (($pos = stripos($section, 'TEXT')) !== false) {
+            $id = ($pos ? substr($section, 0, $pos - 1) : 0);
+            $fetch = $this->fetch($mailbox, array(
+                Horde_Imap_Client::FETCH_BODYTEXT => array(
+                    array(
+                        'id' => $id,
+                        'peek' => true,
+                        'stream' => true
+                    )
+                )
+            ), array('ids' => array($uid)));
+            return $fetch[$uid]['bodytext'][$id];
+        }
+
+        // BODY[<#.>MIMEHEADER]
+        if (($pos = stripos($section, 'MIME')) !== false) {
+            $id = ($pos ? substr($section, 0, $pos - 1) : 0);
+            $fetch = $this->fetch($mailbox, array(
+                Horde_Imap_Client::FETCH_MIMEHEADER => array(
+                    array(
+                        'id' => $id,
+                        'peek' => true
+                    )
+                )
+            ), array('ids' => array($uid)));
+
+            $stream = fopen('php://temp', 'w+');
+            fwrite($stream, $fetch[$uid]['mimeheader'][$id]);
+            return $stream;
+        }
+
+        return null;
+    }
+
+    /**
      * Returns UIDs for an ALL search, or for a sequence number -> UID lookup.
      *
      * @param mixed $ids    If null, return all UIDs for the mailbox. If an
@@ -3140,4 +3261,78 @@ abstract class Horde_Imap_Client_Base
         $this->cache->setMetaData($mailbox, $uidvalid, $data);
     }
 
+    /**
+     * Prepares append message data for insertion into the IMAP command
+     * string.
+     *
+     * @param mixed $data  Either a resource or a string.
+     *
+     * @param resource  A stream containing the message data.
+     */
+    protected function _prepareAppendData($data)
+    {
+        $stream = fopen('php://temp', 'w+');
+        stream_filter_register('horde_eol', 'Horde_Stream_Filter_Eol');
+        stream_filter_append($stream, 'horde_eol', STREAM_FILTER_WRITE);
+
+        if (is_resource($data)) {
+            rewind($data);
+            stream_copy_to_stream($data, $stream);
+        } else {
+            fwrite($stream, $data);
+        }
+
+        return $stream;
+    }
+
+    /**
+     * Builds a stream from CATENATE input to append().
+     *
+     * @param array $data  See append() - array input for the 'data' key to
+     *                     the $data parameter.
+     *
+     * @return resource  The data combined into a single stream.
+     * @throws Horde_Imap_Client_Exception
+     */
+    protected function _buildCatenateData($data)
+    {
+        $parts = array();
+
+        foreach (array_keys($data) as $key2) {
+            switch ($data[$key2]['t']) {
+            case 'text':
+                $parts[] = $this->_prepareAppendData($data[$key2]['v']);
+                break;
+
+            case 'url':
+                $part = null;
+                $url = $this->utils->parseUrl($data[$key2]['v']);
+
+                if (isset($url['mailbox']) &&
+                    isset($url['uid'])) {
+                    try {
+                        $status_res = isset($url['uidvalidity'])
+                            ? $this->status($url['mailbox'], Horde_Imap_Client::STATUS_UIDVALIDITY)
+                            : null;
+
+                        if (is_null($status_res) ||
+                            ($status_res['uidvalidity'] == $url['uidvalidity'])) {
+                            $part = $this->fetchFromSectionString($url['mailbox'], $url['uid'], isset($url['section']) ? $url['section'] : null);
+                        }
+                    } catch (Horde_Imap_Client_Exception $e) {}
+                }
+
+                if (is_null($part)) {
+                    throw new Horde_Imap_Client_Exception('Bad IMAP URL given in CATENATE data', Horde_Imap_Client_Exception::CATENATE_BADURL);
+                } else {
+                    $parts[] = $part;
+                }
+                break;
+            }
+        }
+
+        $swrapper = new Horde_Support_CombineStream($parts);
+        return $swrapper->fopen();
+    }
+
 }
diff --git a/framework/Imap_Client/lib/Horde/Imap/Client/Exception.php b/framework/Imap_Client/lib/Horde/Imap/Client/Exception.php
index 83ba9dcae..595ccee7a 100644
--- a/framework/Imap_Client/lib/Horde/Imap/Client/Exception.php
+++ b/framework/Imap_Client/lib/Horde/Imap/Client/Exception.php
@@ -56,6 +56,12 @@ class Horde_Imap_Client_Exception extends Exception
     // Thrown if read error for server response.
     const SERVER_READERROR = 12;
 
+    // Thrown on CATENATE if a bad IMAP URL is found.
+    const CATENATE_BADURL = 13;
+
+    // Thrown on CATENATE if the message was too big.
+    const CATENATE_TOOBIG = 14;
+
     /**
      * Define a callback function used to log the exception. Will be passed
      * a single parameter - a copy of this object.
diff --git a/framework/Imap_Client/lib/Horde/Imap/Client/Socket.php b/framework/Imap_Client/lib/Horde/Imap/Client/Socket.php
index 5559d9e90..fee4f61b9 100644
--- a/framework/Imap_Client/lib/Horde/Imap/Client/Socket.php
+++ b/framework/Imap_Client/lib/Horde/Imap/Client/Socket.php
@@ -24,6 +24,7 @@
  *   RFC 4315 - UIDPLUS
  *   RFC 4422 - SASL Authentication (for DIGEST-MD5)
  *   RFC 4466 - Collected extensions (updates RFCs 2088, 3501, 3502, 3516)
+ *   RFC 4469/5550 - CATENATE
  *   RFC 4551 - CONDSTORE
  *   RFC 4731 - ESEARCH
  *   RFC 4959 - SASL-IR
@@ -52,7 +53,6 @@
  *                    time by each HTTP/PHP request)
  *   RFC 2193 - MAILBOX-REFERRALS
  *   RFC 4467/5092/5524/5550 - URLAUTH, URLFETCH=BINARY, URL-PARTIAL
- *   RFC 4469/5550 - CATENATE
  *   RFC 4978 - COMPRESS=DEFLATE
  *              See: http://bugs.php.net/bug.php?id=48725
  *   RFC 5257 - ANNOTATE (Experimental)
@@ -1378,10 +1378,10 @@ class Horde_Imap_Client_Socket extends Horde_Imap_Client_Base
 
         // 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) &&
-            ($this->_mode == Horde_Imap_Client::OPEN_READONLY)) {
-            $this->close();
-        }
+        $this->close();
+
+        // Check for CATENATE extension (RFC 4469)
+        $catenate = $this->queryCapability('CATENATE');
 
         $t = &$this->_temp;
         $t['appenduid'] = array();
@@ -1393,8 +1393,6 @@ class Horde_Imap_Client_Socket extends Horde_Imap_Client_Base
             array('t' => Horde_Imap_Client::DATA_MAILBOX, 'v' => $mailbox)
         );
 
-        stream_filter_register('horde_eol', 'Horde_Stream_Filter_Eol');
-
         foreach (array_keys($data) as $key) {
             if (!empty($data[$key]['flags'])) {
                 $tmp = array();
@@ -1408,17 +1406,31 @@ class Horde_Imap_Client_Socket extends Horde_Imap_Client_Base
                 $cmd[] = $data[$key]['internaldate']->format('j-M-Y H:i:s O');
             }
 
-            $text = fopen('php://temp', 'w+');
-            stream_filter_append($text, 'horde_eol', STREAM_FILTER_WRITE);
+            if (is_array($data[$key]['data'])) {
+                if ($catenate) {
+                    $cmd[] = 'CATENATE';
 
-            if (is_resource($data[$key]['data'])) {
-                rewind($data[$key]['data']);
-                stream_copy_to_stream($data[$key]['data'], $text);
+                    $tmp = array();
+                    foreach (array_keys($data[$key]['data']) as $key2) {
+                        switch ($data[$key]['data'][$key2]['t']) {
+                        case 'text':
+                            $tmp[] = 'TEXT';
+                            $tmp[] = $this->_prepareAppendData($data[$key]['data'][$key2]['v']);
+                            break;
+
+                        case 'url':
+                            $tmp[] = 'URL';
+                            $tmp[] = $data[$key]['data'][$key2]['v'];
+                            break;
+                        }
+                    }
+                    $cmd[] = $tmp;
+                } else {
+                    $cmd[] = $this->_buildCatenateData($data[$key]['data']);
+                }
             } else {
-                fwrite($text, $data[$key]['data']);
+                $cmd[] = $this->_prepareAppendData($data[$key]['data']);
             }
-
-            $cmd[] = $text;
         }
 
         try {
@@ -4280,6 +4292,22 @@ class Horde_Imap_Client_Socket extends Horde_Imap_Client_Base
             $this->_temp['mailbox']['uidnotsticky'] = true;
             break;
 
+        case 'BADURL':
+            // Defined by RFC 4469 [4.1]
+            $this->_temp['parsestatuserr'] = array(
+                Horde_Imap_Client_Exception::CATENATE_BADURL,
+                substr($ob['line'], $end_pos + 2)
+            );
+            break;
+
+        case 'TOOBIG':
+            // Defined by RFC 4469 [4.2]
+            $this->_temp['parsestatuserr'] = array(
+                Horde_Imap_Client_Exception::CATENATE_TOOBIG,
+                substr($ob['line'], $end_pos + 2)
+            );
+            break;
+
         case 'HIGHESTMODSEQ':
             // Defined by RFC 4551 [3.1.1]
             $this->_temp['mailbox']['highestmodseq'] = $data;
diff --git a/framework/Imap_Client/package.xml b/framework/Imap_Client/package.xml
index af52e4494..55debacf0 100644
--- a/framework/Imap_Client/package.xml
+++ b/framework/Imap_Client/package.xml
@@ -31,7 +31,9 @@ http://pear.php.net/dtd/package-2.0.xsd">
   alpha
  
  LGPL
- * Correctly output 8-bit strings (RFC 3501 [4.3]).
+ * Add Horde_Imap_Client_Base::fetchFromSectionString().
+ * Add support for RFC 4469 (CATENATE).
+ * Correctly output 8-bit strings (RFC 3501 [4.3]).
  * Add Horde_Imap_Client_Utils::stripNonAtomChars().
  * Add sequence sort (numeric sort by UID/sequence number).
  * Add ability to disable server capabilities.
@@ -116,6 +118,10 @@ http://pear.php.net/dtd/package-2.0.xsd">
     Stream_Filter
     pear.horde.org
    
+   
+     Support
+     pear.horde.org
+   
    
     imap