From 30cc01f89daea932d15a1a505d25b543913664ac Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Wed, 28 Nov 2012 20:21:09 +0100
Subject: Use Delivered-To header as a last resort for identity selection
 (#1488840)

---
 program/lib/Roundcube/rcube_imap_generic.php | 10 ++++++----
 program/lib/Roundcube/rcube_storage.php      |  1 +
 2 files changed, 7 insertions(+), 4 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_imap_generic.php b/program/lib/Roundcube/rcube_imap_generic.php
index 70fd6eb2c..ae0bfdd6c 100644
--- a/program/lib/Roundcube/rcube_imap_generic.php
+++ b/program/lib/Roundcube/rcube_imap_generic.php
@@ -2206,10 +2206,13 @@ class rcube_imap_generic
                             }
                             break;
                         default:
-                            if (strlen($field) > 2) {
-                                $result[$id]->others[$field] = $string;
+                            if (strlen($field) < 3) {
+                                break;
                             }
-                            break;
+                            if ($result[$id]->others[$field]) {
+                                $string = array_merge((array)$result[$id]->others[$field], (array)$string);
+                            }
+                            $result[$id]->others[$field] = $string;
                         }
                     }
                 }
@@ -2217,7 +2220,6 @@ class rcube_imap_generic
 
             // VANISHED response (QRESYNC RFC5162)
             // Sample: * VANISHED (EARLIER) 300:310,405,411
-
             else if (preg_match('/^\* VANISHED [()EARLIER]*/i', $line, $match)) {
                 $line   = substr($line, strlen($match[0]));
                 $v_data = $this->tokenizeResponse($line, 1);
diff --git a/program/lib/Roundcube/rcube_storage.php b/program/lib/Roundcube/rcube_storage.php
index 1556aae41..245d911c0 100644
--- a/program/lib/Roundcube/rcube_storage.php
+++ b/program/lib/Roundcube/rcube_storage.php
@@ -64,6 +64,7 @@ abstract class rcube_storage
         'MAIL-FOLLOWUP-TO',
         'MAIL-REPLY-TO',
         'RETURN-PATH',
+        'DELIVERED-TO',
     );
 
     const UNKNOWN       = 0;
-- 
cgit v1.2.3


From 0247b89c38c7f7ef1a2111239c6a1c8c13394d93 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Wed, 28 Nov 2012 20:40:07 +0100
Subject: Move code for identity selection to function, move identities
 formatting to rcube_user::list_identities()

---
 program/lib/Roundcube/rcube_user.php |  15 ++-
 program/steps/mail/compose.inc       | 185 +++++++++++++++++------------------
 2 files changed, 102 insertions(+), 98 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_user.php b/program/lib/Roundcube/rcube_user.php
index 864f2e098..b027506ac 100644
--- a/program/lib/Roundcube/rcube_user.php
+++ b/program/lib/Roundcube/rcube_user.php
@@ -240,10 +240,12 @@ class rcube_user
     /**
      * Return a list of all identities linked with this user
      *
-     * @param string $sql_add Optional WHERE clauses
+     * @param string $sql_add   Optional WHERE clauses
+     * @param bool   $formatted Format identity email and name
+     *
      * @return array List of identities
      */
-    function list_identities($sql_add = '')
+    function list_identities($sql_add = '', $formatted = false)
     {
         $result = array();
 
@@ -255,6 +257,15 @@ class rcube_user
             $this->ID);
 
         while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
+            if ($formatted) {
+                $ascii_email = format_email($sql_arr['email']);
+                $utf8_email  = format_email(rcube_utils::idn_to_utf8($ascii_email));
+
+                $sql_arr['email_ascii'] = $ascii_email;
+                $sql_arr['email']       = $utf8_email;
+                $sql_arr['ident']       = format_email_recipient($ascii_email, $ident['name']);
+            }
+
             $result[] = $sql_arr;
         }
 
diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc
index 60662b382..c039e42c6 100644
--- a/program/steps/mail/compose.inc
+++ b/program/steps/mail/compose.inc
@@ -249,18 +249,7 @@ else {
 $MESSAGE->compose = array();
 
 // get user's identities
-$MESSAGE->identities = $RCMAIL->user->list_identities();
-if (count($MESSAGE->identities))
-{
-  foreach ($MESSAGE->identities as $idx => $ident) {
-    $ident['email'] = format_email($ident['email']);
-    $email = format_email(rcube_idn_to_utf8($ident['email']));
-
-    $MESSAGE->identities[$idx]['email_ascii'] = $ident['email'];
-    $MESSAGE->identities[$idx]['ident']       = format_email_recipient($ident['email'], $ident['name']);
-    $MESSAGE->identities[$idx]['email']       = $email;
-  }
-}
+$MESSAGE->identities = $RCMAIL->user->list_identities(null, true);
 
 // Set From field value
 if (!empty($_POST['_from'])) {
@@ -270,92 +259,10 @@ else if (!empty($COMPOSE['param']['from'])) {
   $MESSAGE->compose['from'] = $COMPOSE['param']['from'];
 }
 else if (count($MESSAGE->identities)) {
-  $a_recipients = array();
-  $a_names      = array();
-
-  // extract all recipients of the reply-message
-  if (is_object($MESSAGE->headers) && in_array($compose_mode, array(RCUBE_COMPOSE_REPLY, RCUBE_COMPOSE_FORWARD)))
-  {
-    $a_to = rcube_mime::decode_address_list($MESSAGE->headers->to, null, true, $MESSAGE->headers->charset);
-    foreach ($a_to as $addr) {
-      if (!empty($addr['mailto'])) {
-        $a_recipients[] = format_email($addr['mailto']);
-        $a_names[]      = $addr['name'];
-      }
-    }
-
-    if (!empty($MESSAGE->headers->cc)) {
-      $a_cc = rcube_mime::decode_address_list($MESSAGE->headers->cc, null, true, $MESSAGE->headers->charset);
-      foreach ($a_cc as $addr) {
-        if (!empty($addr['mailto'])) {
-          $a_recipients[] = format_email($addr['mailto']);
-          $a_names[]      = $addr['name'];
-        }
-      }
-    }
-  }
-
-  $from_idx         = null;
-  $found_idx        = null;
-  $default_identity = 0; // default identity is always first on the list
-
-  // Select identity
-  foreach ($MESSAGE->identities as $idx => $ident) {
-    // use From header
-    if (in_array($compose_mode, array(RCUBE_COMPOSE_DRAFT, RCUBE_COMPOSE_EDIT))) {
-      if ($MESSAGE->headers->from == $ident['ident']) {
-        $from_idx = $idx;
-        break;
-      }
-    }
-    // reply to yourself
-    else if ($compose_mode == RCUBE_COMPOSE_REPLY && $MESSAGE->headers->from == $ident['ident']) {
-      $from_idx = $idx;
-      break;
-    }
-    // use replied message recipients
-    else if (($found = array_search($ident['email_ascii'], $a_recipients)) !== false) {
-      if ($found_idx === null) {
-        $found_idx = $idx;
-      }
-      // match identity name
-      if ($a_names[$found] && $ident['name'] && $a_names[$found] == $ident['name']) {
-        $from_idx = $idx;
-        break;
-      }
-    }
-  }
-
-  // If matching by name+address doesn't found any amtches, get first found address (identity)
-  if ($from_idx === null) {
-    $from_idx = $found_idx;
-  }
-
-  // Try Return-Path
-  if ($from_idx === null && ($return_path = $MESSAGE->headers->others['return-path'])) {
-    foreach ($MESSAGE->identities as $idx => $ident) {
-      if (strpos($return_path, str_replace('@', '=', $ident['email_ascii']).'@') !== false) {
-        $from_idx = $idx;
-        break;
-      }
-    }
-  }
-
-  // Fallback using Delivered-To
-  if ($from_idx === null && ($delivered_to = $MESSAGE->headers->others['delivered-to'])) {
-    foreach ($MESSAGE->identities as $idx => $ident) {
-      if (in_array($ident['email_ascii'], $delivered_to)) {
-        $from_idx = $idx;
-        break;
-      }
-    }
-  }
-
-  $ident   = $MESSAGE->identities[$from_idx !== null ? $from_idx : $default_identity];
-  $from_id = $ident['identity_id'];
+  $ident = rcmail_identity_select($MESSAGE, $MESSAGE->identities, $compose_mode);
 
   $MESSAGE->compose['from_email'] = $ident['email'];
-  $MESSAGE->compose['from']       = $from_id;
+  $MESSAGE->compose['from']       = $ident['identity_id'];
 }
 
 // Set other headers
@@ -472,6 +379,92 @@ $MESSAGE_BODY = rcmail_prepare_message_body();
 
 /****** compose mode functions ********/
 
+function rcmail_identity_select($MESSAGE, $identities, $compose_mode)
+{
+  $a_recipients = array();
+  $a_names      = array();
+
+  // extract all recipients of the reply-message
+  if (is_object($MESSAGE->headers) && in_array($compose_mode, array(RCUBE_COMPOSE_REPLY, RCUBE_COMPOSE_FORWARD))) {
+    $a_to = rcube_mime::decode_address_list($MESSAGE->headers->to, null, true, $MESSAGE->headers->charset);
+    foreach ($a_to as $addr) {
+      if (!empty($addr['mailto'])) {
+        $a_recipients[] = format_email($addr['mailto']);
+        $a_names[]      = $addr['name'];
+      }
+    }
+
+    if (!empty($MESSAGE->headers->cc)) {
+      $a_cc = rcube_mime::decode_address_list($MESSAGE->headers->cc, null, true, $MESSAGE->headers->charset);
+      foreach ($a_cc as $addr) {
+        if (!empty($addr['mailto'])) {
+          $a_recipients[] = format_email($addr['mailto']);
+          $a_names[]      = $addr['name'];
+        }
+      }
+    }
+  }
+
+  $from_idx         = null;
+  $found_idx        = null;
+  $default_identity = 0; // default identity is always first on the list
+
+  // Select identity
+  foreach ($identities as $idx => $ident) {
+    // use From header
+    if (in_array($compose_mode, array(RCUBE_COMPOSE_DRAFT, RCUBE_COMPOSE_EDIT))) {
+      if ($MESSAGE->headers->from == $ident['ident']) {
+        $from_idx = $idx;
+        break;
+      }
+    }
+    // reply to yourself
+    else if ($compose_mode == RCUBE_COMPOSE_REPLY && $MESSAGE->headers->from == $ident['ident']) {
+      $from_idx = $idx;
+      break;
+    }
+    // use replied message recipients
+    else if (($found = array_search($ident['email_ascii'], $a_recipients)) !== false) {
+      if ($found_idx === null) {
+        $found_idx = $idx;
+      }
+      // match identity name
+      if ($a_names[$found] && $ident['name'] && $a_names[$found] == $ident['name']) {
+        $from_idx = $idx;
+        break;
+      }
+    }
+  }
+
+  // If matching by name+address doesn't found any amtches, get first found address (identity)
+  if ($from_idx === null) {
+    $from_idx = $found_idx;
+  }
+
+  // Try Return-Path
+  if ($from_idx === null && ($return_path = $MESSAGE->headers->others['return-path'])) {
+    foreach ($identities as $idx => $ident) {
+      if (strpos($return_path, str_replace('@', '=', $ident['email_ascii']).'@') !== false) {
+        $from_idx = $idx;
+        break;
+      }
+    }
+  }
+
+  // Fallback using Delivered-To
+  if ($from_idx === null && ($delivered_to = $MESSAGE->headers->others['delivered-to'])) {
+    foreach ($identities as $idx => $ident) {
+      if (in_array($ident['email_ascii'], $delivered_to)) {
+        $from_idx = $idx;
+        break;
+      }
+    }
+  }
+
+  return $identities[$from_idx !== null ? $from_idx : $default_identity];
+}
+
+
 function rcmail_compose_headers($attrib)
 {
   global $MESSAGE;
-- 
cgit v1.2.3


From 7eb7806b211147b40c191d17a983ba34f26b8f1c Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Thu, 29 Nov 2012 13:51:49 +0100
Subject: Fix broken message/part bodies when FETCH response contains more
 untagged lines (#1488836)

---
 CHANGELOG                                    |   1 +
 program/lib/Roundcube/rcube_imap_generic.php | 186 +++++++++++++--------------
 2 files changed, 94 insertions(+), 93 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/CHANGELOG b/CHANGELOG
index ae6d27398..a47c95dcf 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
 CHANGELOG Roundcube Webmail
 ===========================
 
+- Fix broken message/part bodies when FETCH response contains more untagged lines (#1488836)
 - Fix empty email on identities list after identity update (#1488834)
 - Add new identities_level: (4) one identity with possibility to edit only signature
 - Use Delivered-To header as a last resort for identity selection (#1488840)
diff --git a/program/lib/Roundcube/rcube_imap_generic.php b/program/lib/Roundcube/rcube_imap_generic.php
index ae0bfdd6c..0f32d83d1 100644
--- a/program/lib/Roundcube/rcube_imap_generic.php
+++ b/program/lib/Roundcube/rcube_imap_generic.php
@@ -2410,8 +2410,9 @@ class rcube_imap_generic
         $partial    = $max_bytes ? sprintf('<0.%d>', $max_bytes) : '';
 
         // format request
-        $key       = $this->nextTag();
-        $request   = $key . ($is_uid ? ' UID' : '') . " FETCH $id ($fetch_mode.PEEK[$part]$partial)";
+        $key     = $this->nextTag();
+        $request = $key . ($is_uid ? ' UID' : '') . " FETCH $id ($fetch_mode.PEEK[$part]$partial)";
+        $result  = false;
 
         // send request
         if (!$this->putLine($request)) {
@@ -2424,118 +2425,117 @@ class rcube_imap_generic
             $mode = -1;
         }
 
-        // receive reply line
         do {
-            $line = rtrim($this->readLine(1024));
-            $a    = explode(' ', $line);
-        } while (!($end = $this->startsWith($line, $key, true)) && $a[2] != 'FETCH');
-
-        $len    = strlen($line);
-        $result = false;
+            $line = trim($this->readLine(1024));
 
-        if ($a[2] != 'FETCH') {
-        }
-        // handle empty "* X FETCH ()" response
-        else if ($line[$len-1] == ')' && $line[$len-2] != '(') {
-            // one line response, get everything between first and last quotes
-            if (substr($line, -4, 3) == 'NIL') {
-                // NIL response
-                $result = '';
-            } else {
-                $from = strpos($line, '"') + 1;
-                $to   = strrpos($line, '"');
-                $len  = $to - $from;
-                $result = substr($line, $from, $len);
+            if (!$line) {
+                break;
             }
 
-            if ($mode == 1) {
-                $result = base64_decode($result);
-            }
-            else if ($mode == 2) {
-                $result = quoted_printable_decode($result);
-            }
-            else if ($mode == 3) {
-                $result = convert_uudecode($result);
+            if (!preg_match('/^\* ([0-9]+) FETCH (.*)$/', $line, $m)) {
+                continue;
             }
 
-        } else if ($line[$len-1] == '}') {
-            // multi-line request, find sizes of content and receive that many bytes
-            $from     = strpos($line, '{') + 1;
-            $to       = strrpos($line, '}');
-            $len      = $to - $from;
-            $sizeStr  = substr($line, $from, $len);
-            $bytes    = (int)$sizeStr;
-            $prev     = '';
+            $line = $m[2];
+            $last = substr($line, -1);
 
-            while ($bytes > 0) {
-                $line = $this->readLine(8192);
+            // handle one line response
+            if ($line[0] == '(' && $last == ')') {
+                // tokenize content inside brackets
+                $tokens = $this->tokenizeResponse(preg_replace('/(^\(|\$)/', '', $line));
+                $result = count($tokens) == 1 ? $tokens[0] : false;
 
-                if ($line === NULL) {
-                    break;
+                if ($result !== false) {
+                    if ($mode == 1) {
+                        $result = base64_decode($result);
+                    }
+                    else if ($mode == 2) {
+                        $result = quoted_printable_decode($result);
+                    }
+                    else if ($mode == 3) {
+                        $result = convert_uudecode($result);
+                    }
                 }
+            }
+            // response with string literal
+            else if (preg_match('/\{([0-9]+)\}$/', $line, $m)) {
+                $bytes = (int) $m[1];
+                $prev  = '';
+
+                while ($bytes > 0) {
+                    $line = $this->readLine(8192);
 
-                $len = strlen($line);
+                    if ($line === NULL) {
+                        break;
+                    }
 
-                if ($len > $bytes) {
-                    $line = substr($line, 0, $bytes);
                     $len = strlen($line);
-                }
-                $bytes -= $len;
-
-                // BASE64
-                if ($mode == 1) {
-                    $line = rtrim($line, "\t\r\n\0\x0B");
-                    // create chunks with proper length for base64 decoding
-                    $line = $prev.$line;
-                    $length = strlen($line);
-                    if ($length % 4) {
-                        $length = floor($length / 4) * 4;
-                        $prev = substr($line, $length);
-                        $line = substr($line, 0, $length);
+
+                    if ($len > $bytes) {
+                        $line = substr($line, 0, $bytes);
+                        $len  = strlen($line);
+                    }
+                    $bytes -= $len;
+
+                    // BASE64
+                    if ($mode == 1) {
+                        $line = rtrim($line, "\t\r\n\0\x0B");
+                        // create chunks with proper length for base64 decoding
+                        $line = $prev.$line;
+                        $length = strlen($line);
+                        if ($length % 4) {
+                            $length = floor($length / 4) * 4;
+                            $prev = substr($line, $length);
+                            $line = substr($line, 0, $length);
+                        }
+                        else {
+                            $prev = '';
+                        }
+                        $line = base64_decode($line);
+                    }
+                    // QUOTED-PRINTABLE
+                    else if ($mode == 2) {
+                        $line = rtrim($line, "\t\r\0\x0B");
+                        $line = quoted_printable_decode($line);
+                    }
+                    // UUENCODE
+                    else if ($mode == 3) {
+                        $line = rtrim($line, "\t\r\n\0\x0B");
+                        if ($line == 'end' || preg_match('/^begin\s+[0-7]+\s+.+$/', $line)) {
+                            continue;
+                        }
+                        $line = convert_uudecode($line);
+                    }
+                    // default
+                    else if ($formatted) {
+                        $line = rtrim($line, "\t\r\n\0\x0B") . "\n";
                     }
-                    else
-                        $prev = '';
-                    $line = base64_decode($line);
-                // QUOTED-PRINTABLE
-                } else if ($mode == 2) {
-                    $line = rtrim($line, "\t\r\0\x0B");
-                    $line = quoted_printable_decode($line);
-                // UUENCODE
-                } else if ($mode == 3) {
-                    $line = rtrim($line, "\t\r\n\0\x0B");
-                    if ($line == 'end' || preg_match('/^begin\s+[0-7]+\s+.+$/', $line))
-                        continue;
-                    $line = convert_uudecode($line);
-                // default
-                } else if ($formatted) {
-                    $line = rtrim($line, "\t\r\n\0\x0B") . "\n";
-                }
 
-                if ($file) {
-                    if (fwrite($file, $line) === false)
-                        break;
+                    if ($file) {
+                        if (fwrite($file, $line) === false) {
+                            break;
+                        }
+                    }
+                    else if ($print) {
+                        echo $line;
+                    }
+                    else {
+                        $result .= $line;
+                    }
                 }
-                else if ($print)
-                    echo $line;
-                else
-                    $result .= $line;
             }
-        }
-
-        // read in anything up until last line
-        if (!$end)
-            do {
-                $line = $this->readLine(1024);
-            } while (!$this->startsWith($line, $key, true));
+        } while (!$this->startsWith($line, $key, true));
 
         if ($result !== false) {
             if ($file) {
                 return fwrite($file, $result);
-            } else if ($print) {
+            }
+            else if ($print) {
                 echo $result;
-            } else
-                return $result;
-            return true;
+                return true;
+            }
+
+            return $result;
         }
 
         return false;
-- 
cgit v1.2.3


From 0fa54df638a0b0f514d1bfba3cefb93e38991a35 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Sat, 1 Dec 2012 20:02:34 +0100
Subject: enriched.inc -> rcube_enriched

---
 program/include/bc.php                   |   6 +-
 program/lib/Roundcube/rcube_enriched.php | 147 +++++++++++++++++++++++++++++++
 program/lib/enriched.inc                 | 114 ------------------------
 program/steps/mail/compose.inc           |   9 +-
 program/steps/mail/func.inc              |   3 +-
 tests/Framework/Enriched.php             |  74 ++++++++++++++++
 tests/phpunit.xml                        |   1 +
 7 files changed, 231 insertions(+), 123 deletions(-)
 create mode 100644 program/lib/Roundcube/rcube_enriched.php
 delete mode 100644 program/lib/enriched.inc
 create mode 100644 tests/Framework/Enriched.php

(limited to 'program/lib/Roundcube')

diff --git a/program/include/bc.php b/program/include/bc.php
index 5047e0a84..12110c0ad 100644
--- a/program/include/bc.php
+++ b/program/include/bc.php
@@ -399,7 +399,11 @@ function get_boolean($str)
     return rcube_utils::get_boolean($str);
 }
 
+function enriched_to_html($data)
+{
+    return rcube_enriched::to_html($data);
+}
+
 class rcube_html_page extends rcmail_html_page
 {
-    
 }
diff --git a/program/lib/Roundcube/rcube_enriched.php b/program/lib/Roundcube/rcube_enriched.php
new file mode 100644
index 000000000..8b64fe054
--- /dev/null
+++ b/program/lib/Roundcube/rcube_enriched.php
@@ -0,0 +1,147 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/include/rcube_enriched.php                                    |
+ |                                                                       |
+ | This file is part of the Roundcube Webmail client                     |
+ | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
+ |                                                                       |
+ | Licensed under the GNU General Public License version 3 or            |
+ | any later version with exceptions for skins & plugins.                |
+ | See the README file for a full license statement.                     |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Helper class to convert Enriched to HTML format (RFC 1523, 1896)    |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Aleksander Machniak <alec@alec.pl>                            |
+ | Author: Ryo Chijiiwa (IlohaMail)                                      |
+ +-----------------------------------------------------------------------+
+*/
+
+
+/**
+ * Class for Enriched to HTML conversion
+ *
+ * @package    Framework
+ * @subpackage Utils
+ */
+class rcube_enriched
+{
+    protected static function convert_newlines($body)
+    {
+        // remove single newlines, convert N newlines to N-1
+        $body = str_replace("\r\n", "\n", $body);
+        $len  = strlen($body);
+        $nl   = 0;
+        $out  = '';
+
+        for ($i=0; $i<$len; $i++) {
+            $c = $body[$i];
+            if (ord($c) == 10)
+                $nl++;
+            if ($nl && ord($c) != 10)
+                $nl = 0;
+            if ($nl != 1)
+                $out .= $c;
+            else
+                $out .= ' ';
+        }
+
+        return $out;
+    }
+
+    protected static function convert_formatting($body)
+    {
+        $replace = array(
+            '<bold>'        => '<b>',            '</bold>'   => '</b>',
+            '<italic>'      => '<i>',            '</italic>' => '</i>',
+            '<fixed>'       => '<tt>',           '</fixed>'  => '</tt>',
+            '<smaller>'     => '<font size=-1>', '</smaller>'=> '</font>',
+            '<bigger>'      => '<font size=+1>', '</bigger>' => '</font>',
+            '<underline>'   => '<span style="text-decoration: underline">', '</underline>'   => '</span>',
+            '<flushleft>'   => '<span style="text-align: left">',           '</flushleft>'   => '</span>',
+            '<flushright>'  => '<span style="text-align: right">',          '</flushright>'  => '</span>',
+            '<flushboth>'   => '<span style="text-align: justified">',      '</flushboth>'   => '</span>',
+            '<indent>'      => '<span style="padding-left: 20px">',         '</indent>'      => '</span>',
+            '<indentright>' => '<span style="padding-right: 20px">',        '</indentright>' => '</span>',
+        );
+
+        return str_ireplace(array_keys($replace), array_values($replace), $body);
+    }
+
+    protected static function convert_font($body)
+    {
+        $pattern = '/(.*)\<fontfamily\>\<param\>(.*)\<\/param\>(.*)\<\/fontfamily\>(.*)/ims';
+
+        while (preg_match($pattern, $body, $a)) {
+            if (count($a) != 5)
+                continue;
+
+            $body = $a[1].'<span style="font-family: '.$a[2].'">'.$a[3].'</span>'.$a[4];
+        }
+
+        return $body;
+    }
+
+    protected static function convert_color($body)
+    {
+        $pattern = '/(.*)\<color\>\<param\>(.*)\<\/param\>(.*)\<\/color\>(.*)/ims';
+
+        while (preg_match($pattern, $body, $a)) {
+            if (count($a) != 5)
+                continue;
+
+            // extract color (either by name, or ####,####,####)
+            if (strpos($a[2],',')) {
+                $rgb   = explode(',',$a[2]);
+                $color = '#';
+                for ($i=0; $i<3; $i++)
+                    $color .= substr($rgb[$i], 0, 2); // just take first 2 bytes
+            }
+            else {
+                $color = $a[2];
+            }
+
+            // put it all together
+            $body = $a[1].'<span style="color: '.$color.'">'.$a[3].'</span>'.$a[4];
+        }
+
+        return $body;
+    }
+
+    protected static function convert_excerpt($body)
+    {
+        $pattern = '/(.*)\<excerpt\>(.*)\<\/excerpt\>(.*)/i';
+
+        while (preg_match($pattern, $body, $a)) {
+            if (count($a) != 4)
+                continue;
+
+            $quoted = '';
+            $lines  = explode('<br>', $a[2]);
+
+            foreach ($lines as $n => $line)
+                $quoted .= '&gt;'.$line.'<br>';
+
+            $body = $a[1].'<span class="quotes">'.$quoted.'</span>'.$a[3];
+        }
+
+        return $body;
+    }
+
+    public static function to_html($body)
+    {
+        $body = str_replace('<<','&lt;',$body);
+        $body = self::convert_newlines($body);
+        $body = str_replace("\n", '<br>', $body);
+        $body = self::convert_formatting($body);
+        $body = self::convert_color($body);
+        $body = self::convert_font($body);
+        $body = self::convert_excerpt($body);
+        //$body = nl2br($body);
+
+        return $body;
+    }
+}
diff --git a/program/lib/enriched.inc b/program/lib/enriched.inc
deleted file mode 100644
index e3abd8c4f..000000000
--- a/program/lib/enriched.inc
+++ /dev/null
@@ -1,114 +0,0 @@
-<?php
-/*
-	File:		read_enriched.inc
-	Author: 	Ryo Chijiiwa
-	License:	GPL (part of IlohaMail)
-	Purpose: 	functions for handling text/enriched messages
-	Reference: 	RFC 1523, 1896
-*/
-
-
-function enriched_convert_newlines($str){
-	//remove single newlines, convert N newlines to N-1
-	
-	$str = str_replace("\r\n","\n",$str);
-	$len = strlen($str);
-	
-	$nl = 0;
-	$out = '';
-	for($i=0;$i<$len;$i++){
-		$c = $str[$i];
-		if (ord($c)==10) $nl++;
-		if ($nl && ord($c)!=10) $nl = 0;
-		if ($nl!=1) $out.=$c;
-		else $out.=' ';		
-	}
-	return $out;
-}
-
-function enriched_convert_formatting($body){
-	$a=array('<bold>'=>'<b>','</bold>'=>'</b>','<italic>'=>'<i>',
-			'</italic>'=>'</i>','<fixed>'=>'<tt>','</fixed>'=>'</tt>',
-			'<smaller>'=>'<font size=-1>','</smaller>'=>'</font>',
-			'<bigger>'=>'<font size=+1>','</bigger>'=>'</font>',
-			'<underline>'=>'<span style="text-decoration: underline">',
-			'</underline>'=>'</span>',
-			'<flushleft>'=>'<span style="text-align:left">',
-			'</flushleft>'=>'</span>',
-			'<flushright>'=>'<span style="text-align:right">',
-			'</flushright>'=>'</span>',
-			'<flushboth>'=>'<span style="text-align:justified">',
-			'</flushboth>'=>'</span>',
-			'<indent>'=>'<span style="padding-left: 20px">',
-			'</indent>'=>'</span>',
-			'<indentright>'=>'<span style="padding-right: 20px">',
-			'</indentright>'=>'</span>');
-	
-	while(list($find,$replace)=each($a)){
-		$body = preg_replace('#'.$find.'#i', $replace, $body);
-	}
-	return $body;
-}
-
-function enriched_font($body){
-	$pattern = '/(.*)\<fontfamily\>\<param\>(.*)\<\/param\>(.*)\<\/fontfamily\>(.*)/ims';
-	while(preg_match($pattern,$body,$a)){
-		//print_r($a);
-		if (count($a)!=5) continue;
-		$body=$a[1].'<span style="font-family: '.$a[2].'">'.$a[3].'</span>'.$a[4];
-	}
-
-	return $body;
-}
-
-
-function enriched_color($body){
-	$pattern = '/(.*)\<color\>\<param\>(.*)\<\/param\>(.*)\<\/color\>(.*)/ims';
-	while(preg_match($pattern,$body,$a)){
-		//print_r($a);
-		if (count($a)!=5) continue;
-
-		//extract color (either by name, or ####,####,####)
-		if (strpos($a[2],',')){
-			$rgb = explode(',',$a[2]);
-			$color ='#';
-			for($i=0;$i<3;$i++) $color.=substr($rgb[$i],0,2); //just take first 2 bytes
-		}else{
-			$color = $a[2];
-		}
-		
-		//put it all together
-		$body = $a[1].'<span style="color: '.$color.'">'.$a[3].'</span>'.$a[4];
-	}
-
-	return $body;
-}
-
-function enriched_excerpt($body){
-
-	$pattern = '/(.*)\<excerpt\>(.*)\<\/excerpt\>(.*)/i';
-	while(preg_match($pattern,$body,$a)){
-		//print_r($a);
-		if (count($a)!=4) continue;
-		$quoted = '';
-		$lines = explode('<br>',$a[2]);
-		foreach($lines as $n=>$line) $quoted.='&gt;'.$line.'<br>';
-		$body=$a[1].'<span class="quotes">'.$quoted.'</span>'.$a[3];
-	}
-
-	return $body;
-}
-
-function enriched_to_html($body){
-	$body = str_replace('<<','&lt;',$body);
-	$body = enriched_convert_newlines($body);
-	$body = str_replace("\n", '<br>', $body);
-	$body = enriched_convert_formatting($body);
-	$body = enriched_color($body);
-	$body = enriched_font($body);
-	$body = enriched_excerpt($body);
-	//$body = nl2br($body);
-	return $body;
-}
-
-?>
\ No newline at end of file
diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc
index c039e42c6..96391c88b 100644
--- a/program/steps/mail/compose.inc
+++ b/program/steps/mail/compose.inc
@@ -733,8 +733,7 @@ function rcmail_compose_part_body($part, $isHtml = false)
         if ($part->ctype_secondary == 'html') {
         }
         else if ($part->ctype_secondary == 'enriched') {
-            require_once(INSTALL_PATH . 'program/lib/enriched.inc');
-            $body = enriched_to_html($body);
+            $body = rcube_enriched::to_html($body);
         }
         else {
             // try to remove the signature
@@ -750,8 +749,7 @@ function rcmail_compose_part_body($part, $isHtml = false)
     }
     else {
         if ($part->ctype_secondary == 'enriched') {
-            require_once(INSTALL_PATH . 'program/lib/enriched.inc');
-            $body = enriched_to_html($body);
+            $body = rcube_enriched::to_html($body);
             $part->ctype_secondary = 'html';
         }
 
@@ -763,8 +761,7 @@ function rcmail_compose_part_body($part, $isHtml = false)
             $body = $txt->get_text();
         }
         else if ($part->ctype_secondary == 'enriched') {
-            require_once(INSTALL_PATH . 'program/lib/enriched.inc');
-            $body = enriched_to_html($body);
+            $body = rcube_enriched::to_html($body);
         }
         else {
             if ($part->ctype_secondary == 'plain' && $part->ctype_parameters['format'] == 'flowed') {
diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc
index cb1a5ddae..80dac716e 100644
--- a/program/steps/mail/func.inc
+++ b/program/steps/mail/func.inc
@@ -751,8 +751,7 @@ function rcmail_print_body($part, $p = array())
   }
   // text/enriched
   else if ($data['type'] == 'enriched') {
-    require_once(INSTALL_PATH . 'program/lib/enriched.inc');
-    $body = enriched_to_html($data['body']);
+    $body = rcube_enriched::to_html($data['body']);
     $body = rcmail_wash_html($body, $data, $part->replaces);
     $part->ctype_secondary = 'html';
   }
diff --git a/tests/Framework/Enriched.php b/tests/Framework/Enriched.php
new file mode 100644
index 000000000..26bbc3b4e
--- /dev/null
+++ b/tests/Framework/Enriched.php
@@ -0,0 +1,74 @@
+<?php
+
+/**
+ * Test class to test rcube_enriched class
+ *
+ * @package Tests
+ */
+class Framework_Enriched extends PHPUnit_Framework_TestCase
+{
+
+    /**
+     * Class constructor
+     */
+    function test_class()
+    {
+        $object = new rcube_enriched();
+
+        $this->assertInstanceOf('rcube_enriched', $object, "Class constructor");
+    }
+
+    /**
+     * Test to_html()
+     */
+    function test_to_html()
+    {
+        $enriched = '<bold><italic>the-text</italic></bold>';
+        $expected = '<b><i>the-text</i></b>';
+        $result   = rcube_enriched::to_html($enriched);
+
+        $this->assertSame($expected, $result);
+    }
+
+    /**
+     * Data for test_formatting()
+     */
+    function data_formatting()
+    {
+        return array(
+            array('<bold>', '<b>'),
+            array('</bold>', '</b>'),
+            array('<italic>', '<i>'),
+            array('</italic>', '</i>'),
+            array('<fixed>', '<tt>'),
+            array('</fixed>', '</tt>'),
+            array('<smaller>', '<font size=-1>'),
+            array('</smaller>', '</font>'),
+            array('<bigger>', '<font size=+1>'),
+            array('</bigger>', '</font>'),
+            array('<underline>', '<span style="text-decoration: underline">'),
+            array('</underline>', '</span>'),
+            array('<flushleft>', '<span style="text-align: left">'),
+            array('</flushleft>', '</span>'),
+            array('<flushright>', '<span style="text-align: right">'),
+            array('</flushright>', '</span>'),
+            array('<flushboth>', '<span style="text-align: justified">'),
+            array('</flushboth>', '</span>'),
+            array('<indent>', '<span style="padding-left: 20px">'),
+            array('</indent>', '</span>'),
+            array('<indentright>', '<span style="padding-right: 20px">'),
+            array('</indentright>', '</span>'),
+        );
+    }
+
+    /**
+     * Test formatting conversion
+     * @dataProvider data_formatting
+     */
+    function test_formatting($enriched, $expected)
+    {
+        $result = rcube_enriched::to_html($enriched);
+
+        $this->assertSame($expected, $result);
+    }
+}
diff --git a/tests/phpunit.xml b/tests/phpunit.xml
index 36ab6d714..c9e229e97 100644
--- a/tests/phpunit.xml
+++ b/tests/phpunit.xml
@@ -10,6 +10,7 @@
             <file>Framework/Charset.php</file>
             <file>Framework/ContentFilter.php</file>
             <file>Framework/Csv2vcard.php</file>
+            <file>Framework/Enriched.php</file>
             <file>Framework/Html.php</file>
             <file>Framework/Imap.php</file>
             <file>Framework/ImapGeneric.php</file>
-- 
cgit v1.2.3


From 996af3bfd9bfcac84396790a9a215d177b17c79e Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Fri, 7 Dec 2012 11:13:11 +0100
Subject: Some more rcmail -> rcube cleanup

---
 program/lib/Roundcube/rcube.php             |  2 +-
 program/lib/Roundcube/rcube_addressbook.php |  4 ++--
 program/lib/Roundcube/rcube_plugin.php      | 14 +++++++-------
 3 files changed, 10 insertions(+), 10 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube.php b/program/lib/Roundcube/rcube.php
index c3aa8ffa5..cc4905a14 100644
--- a/program/lib/Roundcube/rcube.php
+++ b/program/lib/Roundcube/rcube.php
@@ -36,7 +36,7 @@ class rcube
     /**
      * Singleton instace of rcube
      *
-     * @var rcmail
+     * @var rcube
      */
     static protected $instance;
 
diff --git a/program/lib/Roundcube/rcube_addressbook.php b/program/lib/Roundcube/rcube_addressbook.php
index d14fc587a..ea8df700c 100644
--- a/program/lib/Roundcube/rcube_addressbook.php
+++ b/program/lib/Roundcube/rcube_addressbook.php
@@ -209,13 +209,13 @@ abstract class rcube_addressbook
      */
     public function validate(&$save_data, $autofix = false)
     {
-        $rcmail = rcube::get_instance();
+        $rcube = rcube::get_instance();
 
         // check validity of email addresses
         foreach ($this->get_col_values('email', $save_data, true) as $email) {
             if (strlen($email)) {
                 if (!rcube_utils::check_email(rcube_utils::idn_to_ascii($email))) {
-                    $error = $rcmail->gettext(array('name' => 'emailformaterror', 'vars' => array('email' => $email)));
+                    $error = $rcube->gettext(array('name' => 'emailformaterror', 'vars' => array('email' => $email)));
                     $this->set_error(self::ERROR_VALIDATE, $error);
                     return false;
                 }
diff --git a/program/lib/Roundcube/rcube_plugin.php b/program/lib/Roundcube/rcube_plugin.php
index dbb15e8be..5db85025d 100644
--- a/program/lib/Roundcube/rcube_plugin.php
+++ b/program/lib/Roundcube/rcube_plugin.php
@@ -203,23 +203,23 @@ abstract class rcube_plugin
       foreach ($texts as $key => $value)
         $add[$domain.'.'.$key] = $value;
 
-      $rcmail = rcube::get_instance();
-      $rcmail->load_language($lang, $add);
+      $rcube = rcube::get_instance();
+      $rcube->load_language($lang, $add);
 
       // add labels to client
       if ($add2client) {
         $js_labels = is_array($add2client) ? array_map(array($this, 'label_map_callback'), $add2client) : array_keys($add);
-        $rcmail->output->add_label($js_labels);
+        $rcube->output->add_label($js_labels);
       }
     }
   }
 
   /**
-   * Wrapper for rcmail::gettext() adding the plugin ID as domain
+   * Wrapper for rcube::gettext() adding the plugin ID as domain
    *
    * @param string $p Message identifier
    * @return string Localized text
-   * @see rcmail::gettext()
+   * @see rcube::gettext()
    */
   public function gettext($p)
   {
@@ -336,8 +336,8 @@ abstract class rcube_plugin
    */
   public function local_skin_path()
   {
-    $rcmail = rcube::get_instance();
-    foreach (array($rcmail->config->get('skin'), 'larry') as $skin) {
+    $rcube = rcube::get_instance();
+    foreach (array($rcube->config->get('skin'), 'larry') as $skin) {
       $skin_path = 'skins/' . $skin;
       if (is_dir(realpath(slashify($this->home) . $skin_path)))
         break;
-- 
cgit v1.2.3


From a3985963f0df4fffa1a6d272c777f781ebd86d50 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Fri, 7 Dec 2012 12:38:08 +0100
Subject: Fix big memory consumption of DB layer (#1488856)

---
 CHANGELOG                          |   1 +
 program/lib/Roundcube/rcube_db.php | 100 ++++++++++++-------------------------
 2 files changed, 32 insertions(+), 69 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/CHANGELOG b/CHANGELOG
index 5a1b1acd5..79f19b904 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
 CHANGELOG Roundcube Webmail
 ===========================
 
+- Fix big memory consumption of DB layer (#1488856)
 - Add workaround for IE<=8 bug where Content-Disposition:inline was ignored (#1488844)
 - Fix XSS vulnerability in vbscript: and data:text links handling (#1488850)
 - Fix broken message/part bodies when FETCH response contains more untagged lines (#1488836)
diff --git a/program/lib/Roundcube/rcube_db.php b/program/lib/Roundcube/rcube_db.php
index 5d8c4a534..2c471e74d 100644
--- a/program/lib/Roundcube/rcube_db.php
+++ b/program/lib/Roundcube/rcube_db.php
@@ -37,12 +37,11 @@ class rcube_db
     protected $db_mode;               // Connection mode
     protected $dbh;                   // Connection handle
 
-    protected $db_error        = false;
-    protected $db_error_msg    = '';
-    protected $conn_failure    = false;
-    protected $a_query_results = array('dummy');
-    protected $last_res_id     = 0;
-    protected $db_index        = 0;
+    protected $db_error     = false;
+    protected $db_error_msg = '';
+    protected $conn_failure = false;
+    protected $db_index     = 0;
+    protected $last_result;
     protected $tables;
     protected $variables;
 
@@ -267,14 +266,14 @@ class rcube_db
     /**
      * Getter for error state
      *
-     * @param int $res_id Optional query result identifier
+     * @param mixed $result Optional query result
      *
      * @return string Error message
      */
-    public function is_error($res_id = null)
+    public function is_error($result = null)
     {
-        if ($res_id !== null) {
-            return $this->_get_result($res_id) === false ? $this->db_error_msg : null;
+        if ($result !== null) {
+            return $result === false ? $this->db_error_msg : null;
         }
 
         return $this->db_error ? $this->db_error_msg : null;
@@ -343,7 +342,7 @@ class rcube_db
      * @param int    Number of rows for LIMIT statement
      * @param mixed  Values to be inserted in query
      *
-     * @return int Query handle identifier
+     * @return PDOStatement|bool Query handle or False on error
      */
     public function limitquery()
     {
@@ -363,7 +362,7 @@ class rcube_db
      * @param int    $numrows Number of rows for LIMIT statement
      * @param array  $params  Values to be inserted in query
      *
-     * @return int Query handle identifier
+     * @return PDOStatement|bool Query handle or False on error
      */
     protected function _query($query, $offset, $numrows, $params)
     {
@@ -374,7 +373,7 @@ class rcube_db
 
         // check connection before proceeding
         if (!$this->is_connected()) {
-            return null;
+            return $this->last_result = false;
         }
 
         if ($numrows || $offset) {
@@ -417,20 +416,21 @@ class rcube_db
                 'message' => $this->db_error_msg), true, false);
         }
 
-        // add result, even if it's an error
-        return $this->_add_result($query);
+        $this->last_result = $query;
+
+        return $query;
     }
 
     /**
      * Get number of affected rows for the last query
      *
-     * @param  number $res_id Optional query handle identifier
+     * @param mixed $result Optional query handle
      *
      * @return int Number of rows or false on failure
      */
-    public function affected_rows($res_id = null)
+    public function affected_rows($result = null)
     {
-        if ($result = $this->_get_result($res_id)) {
+        if ($result || ($result === null && ($result = $this->last_result))) {
             return $result->rowCount();
         }
 
@@ -464,13 +464,12 @@ class rcube_db
      * Get an associative array for one row
      * If no query handle is specified, the last query will be taken as reference
      *
-     * @param int $res_id Optional query handle identifier
+     * @param mixed $result Optional query handle
      *
      * @return mixed Array with col values or false on failure
      */
-    public function fetch_assoc($res_id = null)
+    public function fetch_assoc($result = null)
     {
-        $result = $this->_get_result($res_id);
         return $this->_fetch_row($result, PDO::FETCH_ASSOC);
     }
 
@@ -478,31 +477,30 @@ class rcube_db
      * Get an index array for one row
      * If no query handle is specified, the last query will be taken as reference
      *
-     * @param int $res_id Optional query handle identifier
+     * @param mixed $result Optional query handle
      *
      * @return mixed Array with col values or false on failure
      */
-    public function fetch_array($res_id = null)
+    public function fetch_array($result = null)
     {
-        $result = $this->_get_result($res_id);
         return $this->_fetch_row($result, PDO::FETCH_NUM);
     }
 
     /**
      * Get col values for a result row
      *
-     * @param PDOStatement $result Result handle
-     * @param int          $mode   Fetch mode identifier
+     * @param mixed $result Optional query handle
+     * @param int   $mode   Fetch mode identifier
      *
      * @return mixed Array with col values or false on failure
      */
     protected function _fetch_row($result, $mode)
     {
-        if (!is_object($result) || !$this->is_connected()) {
-            return false;
+        if ($result || ($result === null && ($result = $this->last_result))) {
+            return $result->fetch($mode);
         }
 
-        return $result->fetch($mode);
+        return false;
     }
 
     /**
@@ -538,8 +536,8 @@ class rcube_db
         if ($this->tables === null) {
             $q = $this->query('SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES ORDER BY TABLE_NAME');
 
-            if ($res = $this->_get_result($q)) {
-                $this->tables = $res->fetchAll(PDO::FETCH_COLUMN, 0);
+            if ($q) {
+                $this->tables = $q->fetchAll(PDO::FETCH_COLUMN, 0);
             }
             else {
                 $this->tables = array();
@@ -561,8 +559,8 @@ class rcube_db
         $q = $this->query('SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = ?',
             array($table));
 
-        if ($res = $this->_get_result($q)) {
-            return $res->fetchAll(PDO::FETCH_COLUMN, 0);
+        if ($q) {
+            return $q->fetchAll(PDO::FETCH_COLUMN, 0);
         }
 
         return array();
@@ -776,42 +774,6 @@ class rcube_db
         return utf8_decode($input);
     }
 
-    /**
-     * Adds a query result and returns a handle ID
-     *
-     * @param object $res Query handle
-     *
-     * @return int Handle ID
-     */
-    protected function _add_result($res)
-    {
-        $this->last_res_id = sizeof($this->a_query_results);
-        $this->a_query_results[$this->last_res_id] = $res;
-
-        return $this->last_res_id;
-    }
-
-    /**
-     * Resolves a given handle ID and returns the according query handle
-     * If no ID is specified, the last resource handle will be returned
-     *
-     * @param int $res_id Handle ID
-     *
-     * @return mixed Resource handle or false on failure
-     */
-    protected function _get_result($res_id = null)
-    {
-        if ($res_id == null) {
-            $res_id = $this->last_res_id;
-        }
-
-        if (!empty($this->a_query_results[$res_id])) {
-            return $this->a_query_results[$res_id];
-        }
-
-        return false;
-    }
-
     /**
      * Return correct name for a specific database table
      *
-- 
cgit v1.2.3


From 7c5d4b0d4d01c66768a71e8e6138f5b637d44e4a Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Fri, 7 Dec 2012 14:54:35 +0100
Subject: Fix typo in identity data parser ('ident' item wasn't set correctly)

---
 program/lib/Roundcube/rcube_user.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_user.php b/program/lib/Roundcube/rcube_user.php
index b027506ac..f6b77f5e1 100644
--- a/program/lib/Roundcube/rcube_user.php
+++ b/program/lib/Roundcube/rcube_user.php
@@ -263,7 +263,7 @@ class rcube_user
 
                 $sql_arr['email_ascii'] = $ascii_email;
                 $sql_arr['email']       = $utf8_email;
-                $sql_arr['ident']       = format_email_recipient($ascii_email, $ident['name']);
+                $sql_arr['ident']       = format_email_recipient($ascii_email, $sql_arr['name']);
             }
 
             $result[] = $sql_arr;
-- 
cgit v1.2.3


From bc1ec6c1a103632e4809bf2ee1c39e486bfe3038 Mon Sep 17 00:00:00 2001
From: Thomas Bruederli <thomas@roundcube.net>
Date: Sat, 8 Dec 2012 14:53:28 +0100
Subject: Added README file for the Roundcube framework

---
 program/lib/Roundcube/README.md | 101 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 101 insertions(+)
 create mode 100644 program/lib/Roundcube/README.md

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/README.md b/program/lib/Roundcube/README.md
new file mode 100644
index 000000000..06c6f6a53
--- /dev/null
+++ b/program/lib/Roundcube/README.md
@@ -0,0 +1,101 @@
+Roundcube Framework
+===================
+
+INTRODUCTION
+------------
+The Roundcube Framework is the basic library used for the Roundcube Webmail
+application. It is an extract of classes providing the core functionality for
+an email system. They can be used individually or as package for the following
+tasks:
+
+- IMAP mailbox access with optional caching
+- MIME message handling
+- Email message creation and sending through SMTP
+- General caching utilities using the local database
+- Database abstraction using PDO
+- VCard parsing and writing
+
+
+INSTALLATION
+------------
+Copy all files of this directory to your project or install it in the default
+include_path directory of your webserver. Some classes of the framework require
+one or multiple of the following [PEAR][pear] libraries:
+
+- Mail_Mime 1.8.1 or newer
+- Mail_mimeDecode 1.5.5 or newer
+- Net_SMTP (latest from https://github.com/pear/Net_SMTP/)
+- Net_IDNA2 0.1.1 or newer
+- Auth_SASL 1.0.6 or newer
+
+
+USAGE
+-----
+The Roundcube Framework provides a bootstrapping file which registers an
+autoloader and sets up the environment necessary for the Roundcube classes.
+In order to make use of the framework, simply include the bootstrap.php file
+from this directory in your application and start using the classes by simply
+instantiating them.
+
+If you wanna use more complex functionality like IMAP access with database
+caching or plugins, the rcube singleton helps you loading the necessary files:
+
+<?php
+
+define('RCUBE_CONFIG_DIR',  '<path-to-config-directory>');
+define('RCUBE_PLUGINS_DIR', '<path-to-roundcube-plugins-directory');
+
+require_once '<path-to-roundcube-framework/bootstrap.php';
+
+$rcube = rcube::get_instance(rcube::INIT_WITH_DB | rcube::INIT_WITH_PLUGINS);
+$imap = $rcube->get_storage();
+
+// do cool stuff here...
+
+?>
+
+
+LICENSE
+-------
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License (**with exceptions
+for plugins**) as published by the Free Software Foundation, either
+version 3 of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see [www.gnu.org/licenses/][gpl].
+
+This file forms part of the Roundcube Webmail Framework for which the
+following exception is added: Plugins which merely make function calls to the
+Roundcube Webmail Framework, and for that purpose include it by reference
+shall not be considered modifications of the software.
+
+If you wish to use this file in another project or create a modified
+version that will not be part of the Roundcube Webmail Framework, you
+may remove the exception above and use this source code under the
+original version of the license.
+
+For more details about licensing and the exceptions for skins and plugins
+see [roundcube.net/license][license]
+
+
+CONTACT
+-------
+For any bug reports or feature requests please refer to the tracking system
+at [trac.roundcube.net][tracreport] or subscribe to our mailing list.
+See [roundcube.net/support][support] for details.
+
+You're always welcome to send a message to the project admins:
+hello(at)roundcube(dot)net
+
+
+[pear]:         http://pear.php.net
+[gpl]:          http://www.gnu.org/licenses/
+[license]:      http://roundcube.net/license
+[support]:      http://roundcube.net/support
+[tracreport]:   http://trac.roundcube.net/wiki/Howto_ReportIssues
\ No newline at end of file
-- 
cgit v1.2.3


From d414cc05a34eacc579a0678f59f433a7fb11d7bb Mon Sep 17 00:00:00 2001
From: "Thomas B." <thomas@roundcube.net>
Date: Sat, 8 Dec 2012 15:01:55 +0100
Subject: Add github syntax highlighting to php code snippet

---
 program/lib/Roundcube/README.md | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/README.md b/program/lib/Roundcube/README.md
index 06c6f6a53..88f2d076e 100644
--- a/program/lib/Roundcube/README.md
+++ b/program/lib/Roundcube/README.md
@@ -40,6 +40,7 @@ instantiating them.
 If you wanna use more complex functionality like IMAP access with database
 caching or plugins, the rcube singleton helps you loading the necessary files:
 
+```php
 <?php
 
 define('RCUBE_CONFIG_DIR',  '<path-to-config-directory>');
@@ -53,7 +54,7 @@ $imap = $rcube->get_storage();
 // do cool stuff here...
 
 ?>
-
+```
 
 LICENSE
 -------
-- 
cgit v1.2.3


From 3bb75a5cc7a7c77950eeb6070631c9c8b76a5a16 Mon Sep 17 00:00:00 2001
From: Thomas Bruederli <thomas@roundcube.net>
Date: Sat, 8 Dec 2012 15:36:55 +0100
Subject: Add default path for mime.types file; map jpg => image/jpeg in
 fallback list

---
 program/lib/Roundcube/rcube_mime.php | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_mime.php b/program/lib/Roundcube/rcube_mime.php
index 17cb3f015..4bb5b483f 100644
--- a/program/lib/Roundcube/rcube_mime.php
+++ b/program/lib/Roundcube/rcube_mime.php
@@ -717,6 +717,7 @@ class rcube_mime
             $file_paths[] = $mime_types;
 
         // try common locations
+        $file_paths[] = '/etc/mime.types';
         $file_paths[] = '/etc/httpd/mime.types';
         $file_paths[] = '/etc/httpd2/mime.types';
         $file_paths[] = '/etc/apache/mime.types';
@@ -749,7 +750,7 @@ class rcube_mime
         // fallback to some well-known types most important for daily emails
         if (empty($mime_types)) {
             $mime_extensions = @include(RCUBE_CONFIG_DIR . '/mimetypes.php');
-            $mime_extensions += array('gif' => 'image/gif', 'png' => 'image/png', 'jpg' => 'image/jpg', 'jpeg' => 'image/jpeg', 'tif' => 'image/tiff');
+            $mime_extensions += array('gif' => 'image/gif', 'png' => 'image/png', 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'tif' => 'image/tiff');
 
             foreach ($mime_extensions as $ext => $mime)
                 $mime_types[$mime][] = $ext;
-- 
cgit v1.2.3


From 4f1c887eaaadcc2b6ab2c6578481991698ea74a3 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Sun, 9 Dec 2012 12:12:27 +0100
Subject: Add support for IMAP BINARY (RFC3516)

---
 program/lib/Roundcube/rcube_imap.php         |  7 ++++---
 program/lib/Roundcube/rcube_imap_generic.php | 26 ++++++++++++++++++--------
 2 files changed, 22 insertions(+), 11 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php
index 8ca24dec7..a2495462a 100644
--- a/program/lib/Roundcube/rcube_imap.php
+++ b/program/lib/Roundcube/rcube_imap.php
@@ -2226,10 +2226,11 @@ class rcube_imap extends rcube_storage
      * @param boolean $is_file True if $message is a filename
      * @param array   $flags   Message flags
      * @param mixed   $date    Message internal date
+     * @param bool    $binary  Enables BINARY append
      *
      * @return int|bool Appended message UID or True on success, False on error
      */
-    public function save_message($folder, &$message, $headers='', $is_file=false, $flags = array(), $date = null)
+    public function save_message($folder, &$message, $headers='', $is_file=false, $flags = array(), $date = null, $binary = false)
     {
         if (!strlen($folder)) {
             $folder = $this->folder;
@@ -2247,10 +2248,10 @@ class rcube_imap extends rcube_storage
         $date = $this->date_format($date);
 
         if ($is_file) {
-            $saved = $this->conn->appendFromFile($folder, $message, $headers, $flags, $date);
+            $saved = $this->conn->appendFromFile($folder, $message, $headers, $flags, $date, $binary);
         }
         else {
-            $saved = $this->conn->append($folder, $message, $flags, $date);
+            $saved = $this->conn->append($folder, $message, $flags, $date, $binary);
         }
 
         if ($saved) {
diff --git a/program/lib/Roundcube/rcube_imap_generic.php b/program/lib/Roundcube/rcube_imap_generic.php
index 0f32d83d1..28d56c16f 100644
--- a/program/lib/Roundcube/rcube_imap_generic.php
+++ b/program/lib/Roundcube/rcube_imap_generic.php
@@ -2548,10 +2548,11 @@ class rcube_imap_generic
      * @param string $message Message content
      * @param array  $flags   Message flags
      * @param string $date    Message internal date
+     * @param bool   $binary  Enable BINARY append (RFC3516)
      *
      * @return string|bool On success APPENDUID response (if available) or True, False on failure
      */
-    function append($mailbox, &$message, $flags = array(), $date = null)
+    function append($mailbox, &$message, $flags = array(), $date = null, $binary = false)
     {
         unset($this->data['APPENDUID']);
 
@@ -2559,8 +2560,13 @@ class rcube_imap_generic
             return false;
         }
 
-        $message = str_replace("\r", '', $message);
-        $message = str_replace("\n", "\r\n", $message);
+        $binary       = $binary && $this->getCapability('BINARY');
+        $literal_plus = !$binary && $this->prefs['literal+'];
+
+        if (!$binary) {
+            $message = str_replace("\r", '', $message);
+            $message = str_replace("\n", "\r\n", $message);
+        }
 
         $len = strlen($message);
         if (!$len) {
@@ -2573,12 +2579,12 @@ class rcube_imap_generic
         if (!empty($date)) {
             $request .= ' ' . $this->escape($date);
         }
-        $request .= ' {' . $len . ($this->prefs['literal+'] ? '+' : '') . '}';
+        $request .= ' ' . ($binary ? '~' : '') . '{' . $len . ($literal_plus ? '+' : '') . '}';
 
         // send APPEND command
         if ($this->putLine($request)) {
             // Do not wait when LITERAL+ is supported
-            if (!$this->prefs['literal+']) {
+            if (!$literal_plus) {
                 $line = $this->readReply();
 
                 if ($line[0] != '+') {
@@ -2620,10 +2626,11 @@ class rcube_imap_generic
      * @param string $headers Message headers
      * @param array  $flags   Message flags
      * @param string $date    Message internal date
+     * @param bool   $binary  Enable BINARY append (RFC3516)
      *
      * @return string|bool On success APPENDUID response (if available) or True, False on failure
      */
-    function appendFromFile($mailbox, $path, $headers=null, $flags = array(), $date = null)
+    function appendFromFile($mailbox, $path, $headers=null, $flags = array(), $date = null, $binary = false)
     {
         unset($this->data['APPENDUID']);
 
@@ -2654,18 +2661,21 @@ class rcube_imap_generic
             $len += strlen($headers) + strlen($body_separator);
         }
 
+        $binary       = $binary && $this->getCapability('BINARY');
+        $literal_plus = !$binary && $this->prefs['literal+'];
+
         // build APPEND command
         $key = $this->nextTag();
         $request = "$key APPEND " . $this->escape($mailbox) . ' (' . $this->flagsToStr($flags) . ')';
         if (!empty($date)) {
             $request .= ' ' . $this->escape($date);
         }
-        $request .= ' {' . $len . ($this->prefs['literal+'] ? '+' : '') . '}';
+        $request .= ' ' . ($binary ? '~' : '') . '{' . $len . ($literal_plus ? '+' : '') . '}';
 
         // send APPEND command
         if ($this->putLine($request)) {
             // Don't wait when LITERAL+ is supported
-            if (!$this->prefs['literal+']) {
+            if (!$literal_plus) {
                 $line = $this->readReply();
 
                 if ($line[0] != '+') {
-- 
cgit v1.2.3


From 1aaa4bc3937187c54039a8459409a2efd9cb3cd7 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Mon, 10 Dec 2012 12:42:45 +0100
Subject: Fix PHP Warning:  ldap_parse_virtuallist_control() expects parameter
 2 to be resource, null given. This happens on Administrative Limit Exceeded
 error when using VLV.

---
 program/lib/Roundcube/rcube_ldap.php | 1 +
 1 file changed, 1 insertion(+)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_ldap.php b/program/lib/Roundcube/rcube_ldap.php
index c9a14d863..c32cea728 100644
--- a/program/lib/Roundcube/rcube_ldap.php
+++ b/program/lib/Roundcube/rcube_ldap.php
@@ -1455,6 +1455,7 @@ class rcube_ldap extends rcube_addressbook
                 if ($this->vlv_active && function_exists('ldap_parse_virtuallist_control')) {
                     if (ldap_parse_result($this->conn, $this->ldap_result,
                         $errcode, $matcheddn, $errmsg, $referrals, $serverctrls)
+                        && $serverctrls // can be null e.g. in case of adm. limit error
                     ) {
                         ldap_parse_virtuallist_control($this->conn, $serverctrls,
                             $last_offset, $this->vlv_count, $vresult);
-- 
cgit v1.2.3


From a8a72e2e7ee89caa04f8f13b6067e1b4ad870612 Mon Sep 17 00:00:00 2001
From: Thomas Bruederli <thomas@roundcube.net>
Date: Mon, 10 Dec 2012 22:26:45 +0100
Subject: Nicely render headers of message/rfc822 parts

---
 program/lib/Roundcube/rcube_message.php        |  9 +++++++-
 program/lib/Roundcube/rcube_message_header.php | 16 +++++++++++++
 program/steps/mail/func.inc                    | 31 +++++++++++++++++++-------
 skins/classic/mail.css                         | 11 +++++++++
 skins/larry/mail.css                           | 24 ++++++++++++++++++--
 skins/larry/templates/message.html             |  2 +-
 skins/larry/templates/messagepreview.html      |  2 +-
 7 files changed, 82 insertions(+), 13 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_message.php b/program/lib/Roundcube/rcube_message.php
index 4ef534a0a..c626af08a 100644
--- a/program/lib/Roundcube/rcube_message.php
+++ b/program/lib/Roundcube/rcube_message.php
@@ -320,8 +320,15 @@ class rcube_message
     private function parse_structure($structure, $recursive = false)
     {
         // real content-type of message/rfc822 part
-        if ($structure->mimetype == 'message/rfc822' && $structure->real_mimetype)
+        if ($structure->mimetype == 'message/rfc822' && $structure->real_mimetype) {
             $mimetype = $structure->real_mimetype;
+
+            // parse headers from message/rfc822 part
+            if (!isset($structure->headers['subject'])) {
+                list($headers, $dump) = explode("\r\n\r\n", $this->get_part_content($structure->mime_id, null, true, 4096));
+                $structure->headers = rcube_mime::parse_headers($headers);
+            }
+        }
         else
             $mimetype = $structure->mimetype;
 
diff --git a/program/lib/Roundcube/rcube_message_header.php b/program/lib/Roundcube/rcube_message_header.php
index 445d0bd39..7009a00af 100644
--- a/program/lib/Roundcube/rcube_message_header.php
+++ b/program/lib/Roundcube/rcube_message_header.php
@@ -235,6 +235,22 @@ class rcube_message_header
             $this->others[$name] = $value;
         }
     }
+
+
+    /**
+     * Factory method to instantiate headers from a data array
+     *
+     * @param array Hash array with header values
+     * @return object rcube_message_header instance filled with headers values
+     */
+    public static function from_array($arr)
+    {
+        $obj = new rcube_message_header;
+        foreach ($arr as $k => $v)
+            $obj->set($k, $v);
+
+        return $obj;
+    }
 }
 
 
diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc
index 80dac716e..8ae41017e 100644
--- a/program/steps/mail/func.inc
+++ b/program/steps/mail/func.inc
@@ -5,7 +5,7 @@
  | program/steps/mail/func.inc                                           |
  |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
- | Copyright (C) 2005-2010, The Roundcube Dev Team                       |
+ | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
  |                                                                       |
  | Licensed under the GNU General Public License version 3 or            |
  | any later version with exceptions for skins & plugins.                |
@@ -939,13 +939,13 @@ function rcmail_html_tag_callback($matches)
 /**
  * return table with message headers
  */
-function rcmail_message_headers($attrib, $headers=NULL)
+function rcmail_message_headers($attrib, $headers=null)
   {
   global $OUTPUT, $MESSAGE, $PRINT_MODE, $RCMAIL;
   static $sa_attrib;
 
   // keep header table attrib
-  if (is_array($attrib) && !$sa_attrib)
+  if (is_array($attrib) && !$sa_attrib && !$attrib['valueof'])
     $sa_attrib = $attrib;
   else if (!is_array($attrib) && is_array($sa_attrib))
     $attrib = $sa_attrib;
@@ -954,8 +954,13 @@ function rcmail_message_headers($attrib, $headers=NULL)
     return FALSE;
 
   // get associative array of headers object
-  if (!$headers)
-    $headers = is_object($MESSAGE->headers) ? get_object_vars($MESSAGE->headers) : $MESSAGE->headers;
+  if (!$headers) {
+    $headers_obj = $MESSAGE->headers;
+    $headers = get_object_vars($MESSAGE->headers);
+  }
+  else {
+    $headers_obj = rcube_message_header::from_array($headers);
+  }
 
   // show these headers
   $standard_headers = array('subject', 'from', 'to', 'cc', 'bcc', 'replyto',
@@ -1031,7 +1036,7 @@ function rcmail_message_headers($attrib, $headers=NULL)
   }
 
   $plugin = $RCMAIL->plugins->exec_hook('message_headers_output',
-    array('output' => $output_headers, 'headers' => $MESSAGE->headers, 'exclude' => $exclude_headers));
+    array('output' => $output_headers, 'headers' => $headers_obj, 'exclude' => $exclude_headers));
 
   // single header value is requested
   if (!empty($attrib['valueof']))
@@ -1110,8 +1115,9 @@ function rcmail_message_body($attrib)
 
   if (!empty($MESSAGE->parts)) {
     foreach ($MESSAGE->parts as $i => $part) {
-      if ($part->type == 'headers')
-        $out .= rcmail_message_headers(sizeof($header_attrib) ? $header_attrib : NULL, $part->headers);
+      if ($part->type == 'headers') {
+        $out .= html::div('message-partheaders', rcmail_message_headers(sizeof($header_attrib) ? $header_attrib : null, $part->headers));
+      }
       else if ($part->type == 'content') {
         // unsapported
         if ($part->realtype) {
@@ -1139,6 +1145,15 @@ function rcmail_message_body($attrib)
         if (!isset($part->body))
           $part->body = $MESSAGE->get_part_content($part->mime_id);
 
+        // extract headers from message/rfc822 parts
+        if ($part->mimetype == 'message/rfc822') {
+          list($hdrs, $body) = explode("\r\n\r\n", $part->body, 2);
+          if ($hdrs && $body && preg_match('/^[\w-]+:\s/i', $hdrs)) {
+            $out .= html::div('message-partheaders', rcmail_message_headers(sizeof($header_attrib) ? $header_attrib : null, rcube_mime::parse_headers($hdrs)));
+            $part->body = $body;
+          }
+        }
+
         // message is cached but not exists (#1485443), or other error
         if ($part->body === false) {
           rcmail_message_error($MESSAGE->uid);
diff --git a/skins/classic/mail.css b/skins/classic/mail.css
index 85c53d569..98325d9d2 100644
--- a/skins/classic/mail.css
+++ b/skins/classic/mail.css
@@ -1070,6 +1070,17 @@ table.headers-table
   background-color: #F4F4F4;
 }
 
+#messagebody table.headers-table
+{
+  margin: 16px 6px 6px 6px;
+}
+
+div.message-partheaders + div.message-part
+{
+  border-top: 0;
+  padding-top: 4px;
+}
+
 table.headers-table tr td
 {
   font-size: 11px;
diff --git a/skins/larry/mail.css b/skins/larry/mail.css
index 6512e52a3..12a2b7a82 100644
--- a/skins/larry/mail.css
+++ b/skins/larry/mail.css
@@ -999,12 +999,14 @@ div.hide-headers {
 }
 
 div.message-part,
-div.message-htmlpart {
-	padding: 0 2px 10px 2px;
+div.message-htmlpart,
+div.message-partheaders {
+	padding: 10px 2px;
 	border-top: 1px solid #ccc;
 }
 
 #messagebody div:first-child {
+	padding-top: 0;
 	border-top: 0;
 }
 
@@ -1045,6 +1047,24 @@ div.message-part blockquote blockquote blockquote {
 	border-right: 2px solid #bb0000;
 }
 
+div.message-partheaders {
+	margin-top: 8px;
+	padding: 8px 0;
+}
+
+div.message-partheaders .headers-table {
+	width: 100%;
+}
+
+div.message-partheaders .headers-table td.header-title {
+	width: auto;
+	padding-left: 0;
+}
+
+div.message-partheaders .headers-table td.header {
+	width: 88%;
+}
+
 #messagebody > hr {
 	color: #fff;
 	background: #fff;
diff --git a/skins/larry/templates/message.html b/skins/larry/templates/message.html
index f7e188f5f..04381f5e9 100644
--- a/skins/larry/templates/message.html
+++ b/skins/larry/templates/message.html
@@ -64,7 +64,7 @@
 </div>
 <div class="leftcol">
 <roundcube:object name="messageObjects" id="message-objects" />
-<roundcube:object name="messageBody" id="messagebody" />
+<roundcube:object name="messageBody" id="messagebody" headertableclass="message-partheaders headers-table" />
 </div>
 </div>
 
diff --git a/skins/larry/templates/messagepreview.html b/skins/larry/templates/messagepreview.html
index 9eb4d1e00..aef282ac9 100644
--- a/skins/larry/templates/messagepreview.html
+++ b/skins/larry/templates/messagepreview.html
@@ -47,7 +47,7 @@
 </div>
 <div class="leftcol">
 <roundcube:object name="messageObjects" id="message-objects" />
-<roundcube:object name="messageBody" id="messagebody" />
+<roundcube:object name="messageBody" id="messagebody" headertableclass="message-partheaders headers-table" />
 </div>
 </div>
 
-- 
cgit v1.2.3


From bb5d7282855dd83ccdd211cb77d0776dce71468e Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Wed, 12 Dec 2012 08:54:33 +0100
Subject: Use also Envelope-To for identity selection (#1488553)

---
 CHANGELOG                               |  2 +-
 program/lib/Roundcube/rcube_storage.php |  1 +
 program/steps/mail/compose.inc          | 10 ++++++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

(limited to 'program/lib/Roundcube')

diff --git a/CHANGELOG b/CHANGELOG
index ebc279622..8fd17b407 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -8,7 +8,7 @@ CHANGELOG Roundcube Webmail
 - Fix broken message/part bodies when FETCH response contains more untagged lines (#1488836)
 - Fix empty email on identities list after identity update (#1488834)
 - Add new identities_level: (4) one identity with possibility to edit only signature
-- Use Delivered-To header as a last resort for identity selection (#1488840)
+- Use Delivered-To and Envelope-To headers for identity selection (#1488840, #1488553)
 - Fix XSS vulnerability using Flash files (#1488828)
 - Fix absolute positioning in HTML messages (#1488819)
 - Fix cache (in)validation after setting \Deleted flag
diff --git a/program/lib/Roundcube/rcube_storage.php b/program/lib/Roundcube/rcube_storage.php
index 245d911c0..7ec05b7af 100644
--- a/program/lib/Roundcube/rcube_storage.php
+++ b/program/lib/Roundcube/rcube_storage.php
@@ -65,6 +65,7 @@ abstract class rcube_storage
         'MAIL-REPLY-TO',
         'RETURN-PATH',
         'DELIVERED-TO',
+        'ENVELOPE-TO',
     );
 
     const UNKNOWN       = 0;
diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc
index d181a72e6..d764f5289 100644
--- a/program/steps/mail/compose.inc
+++ b/program/steps/mail/compose.inc
@@ -455,6 +455,16 @@ function rcmail_identity_select($MESSAGE, $identities, $compose_mode)
     }
   }
 
+  // Fallback using Envelope-To
+  if ($from_idx === null && ($envelope_to = $MESSAGE->headers->others['envelope-to'])) {
+    foreach ($identities as $idx => $ident) {
+      if (in_array($ident['email_ascii'], (array)$envelope_to)) {
+        $from_idx = $idx;
+        break;
+      }
+    }
+  }
+
   return $identities[$from_idx !== null ? $from_idx : $default_identity];
 }
 
-- 
cgit v1.2.3


From 0435f40999564586dd9bd9669696ec04c16f2e32 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Wed, 12 Dec 2012 13:32:24 +0100
Subject: Add EXISTS mode to count() method - return number of all messages in
 a folder, event if skip_deleted is enabled and/or search is active.

---
 program/lib/Roundcube/rcube_imap.php    | 14 +++++++++-----
 program/lib/Roundcube/rcube_storage.php |  2 +-
 2 files changed, 10 insertions(+), 6 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php
index a2495462a..ab90fa23f 100644
--- a/program/lib/Roundcube/rcube_imap.php
+++ b/program/lib/Roundcube/rcube_imap.php
@@ -571,7 +571,7 @@ class rcube_imap extends rcube_storage
      * Get message count for a specific folder
      *
      * @param  string  $folder  Folder name
-     * @param  string  $mode    Mode for count [ALL|THREADS|UNSEEN|RECENT]
+     * @param  string  $mode    Mode for count [ALL|THREADS|UNSEEN|RECENT|EXISTS]
      * @param  boolean $force   Force reading from server and update cache
      * @param  boolean $status  Enables storing folder status info (max UID/count),
      *                          required for folder_status()
@@ -592,7 +592,7 @@ class rcube_imap extends rcube_storage
      * protected method for getting nr of messages
      *
      * @param string  $folder  Folder name
-     * @param string  $mode    Mode for count [ALL|THREADS|UNSEEN|RECENT]
+     * @param string  $mode    Mode for count [ALL|THREADS|UNSEEN|RECENT|EXISTS]
      * @param boolean $force   Force reading from server and update cache
      * @param boolean $status  Enables storing folder status info (max UID/count),
      *                         required for folder_status()
@@ -614,6 +614,10 @@ class rcube_imap extends rcube_storage
             }
         }
 
+        // EXISTS is a special alias for ALL, it allows to get the number
+        // of all messages in a folder also when search is active and with
+        // any skip_deleted setting
+
         $a_folder_cache = $this->get_cache('messagecount');
 
         // return cached value
@@ -644,7 +648,7 @@ class rcube_imap extends rcube_storage
             $count = $this->conn->countRecent($folder);
         }
         // use SEARCH for message counting
-        else if (!empty($this->options['skip_deleted'])) {
+        else if ($mode != 'EXISTS' && !empty($this->options['skip_deleted'])) {
             $search_str = "ALL UNDELETED";
             $keys       = array('COUNT');
 
@@ -683,8 +687,8 @@ class rcube_imap extends rcube_storage
             }
             else {
                 $count = $this->conn->countMessages($folder);
-                if ($status) {
-                    $this->set_folder_stats($folder,'cnt', $count);
+                if ($status && $mode == 'ALL') {
+                    $this->set_folder_stats($folder, 'cnt', $count);
                     $this->set_folder_stats($folder, 'maxuid', $count ? $this->id2uid($count, $folder) : 0);
                 }
             }
diff --git a/program/lib/Roundcube/rcube_storage.php b/program/lib/Roundcube/rcube_storage.php
index 7ec05b7af..763b9155e 100644
--- a/program/lib/Roundcube/rcube_storage.php
+++ b/program/lib/Roundcube/rcube_storage.php
@@ -354,7 +354,7 @@ abstract class rcube_storage
      * Get messages count for a specific folder.
      *
      * @param  string  $folder  Folder name
-     * @param  string  $mode    Mode for count [ALL|THREADS|UNSEEN|RECENT]
+     * @param  string  $mode    Mode for count [ALL|THREADS|UNSEEN|RECENT|EXISTS]
      * @param  boolean $force   Force reading from server and update cache
      * @param  boolean $status  Enables storing folder status info (max UID/count),
      *                          required for folder_status()
-- 
cgit v1.2.3


From 5b15700d11f7f1338039a436d5c911d297b4ac56 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Thu, 13 Dec 2012 14:41:36 +0100
Subject: Rename hook imap_connect to storage_connect

---
 program/lib/Roundcube/rcube_imap.php       | 2 +-
 program/lib/Roundcube/rcube_plugin_api.php | 3 ++-
 2 files changed, 3 insertions(+), 2 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php
index a2495462a..a92def54d 100644
--- a/program/lib/Roundcube/rcube_imap.php
+++ b/program/lib/Roundcube/rcube_imap.php
@@ -151,7 +151,7 @@ class rcube_imap extends rcube_storage
 
         $attempt = 0;
         do {
-            $data = rcube::get_instance()->plugins->exec_hook('imap_connect',
+            $data = rcube::get_instance()->plugins->exec_hook('storage_connect',
                 array_merge($this->options, array('host' => $host, 'user' => $user,
                     'attempt' => ++$attempt)));
 
diff --git a/program/lib/Roundcube/rcube_plugin_api.php b/program/lib/Roundcube/rcube_plugin_api.php
index 51cf5d246..47508a2ef 100644
--- a/program/lib/Roundcube/rcube_plugin_api.php
+++ b/program/lib/Roundcube/rcube_plugin_api.php
@@ -78,7 +78,8 @@ class rcube_plugin_api
     'identity_save'     => 'identity_update',
     // to be removed after 0.8
     'imap_init'         => 'storage_init',
-    'mailboxes_list'    => 'storage_folders', 
+    'mailboxes_list'    => 'storage_folders',
+    'imap_connect'      => 'storage_connect',
   );
 
   /**
-- 
cgit v1.2.3


From a072247dde60497f879b2a00790a6ec0a64fab4c Mon Sep 17 00:00:00 2001
From: Thomas Bruederli <thomas@roundcube.net>
Date: Sun, 16 Dec 2012 17:03:01 +0100
Subject: Fix package definitions and include framework classes in phpdoc

---
 bin/makedoc.sh                                 | 5 +++--
 program/include/bc.php                         | 1 +
 program/lib/Roundcube/rcube.php                | 3 ++-
 program/lib/Roundcube/rcube_db.php             | 2 +-
 program/lib/Roundcube/rcube_message_header.php | 3 ++-
 5 files changed, 9 insertions(+), 5 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/bin/makedoc.sh b/bin/makedoc.sh
index 40c75bf47..2a34254cb 100755
--- a/bin/makedoc.sh
+++ b/bin/makedoc.sh
@@ -1,10 +1,11 @@
 #!/bin/sh
 
-TITLE="Roundcube Classes"
+TITLE="Roundcube Webmail"
 PACKAGES="Core"
 
 INSTALL_PATH="`dirname $0`/.."
 PATH_PROJECT=$INSTALL_PATH/program/include
+PATH_FRAMEWORK=$INSTALL_PATH/program/lib/Roundcube
 PATH_DOCS=$INSTALL_PATH/doc/phpdoc
 BIN_PHPDOC="`/usr/bin/which phpdoc`"
 
@@ -20,6 +21,6 @@ TEMPLATE=earthli
 PRIVATE=off
 
 # make documentation
-$BIN_PHPDOC -d $PATH_PROJECT -t $PATH_DOCS -ti "$TITLE" -dn $PACKAGES \
+$BIN_PHPDOC -d $PATH_PROJECT,$PATH_FRAMEWORK -t $PATH_DOCS -ti "$TITLE" -dn $PACKAGES \
 -o $OUTPUTFORMAT:$CONVERTER:$TEMPLATE -pp $PRIVATE
 
diff --git a/program/include/bc.php b/program/include/bc.php
index 12110c0ad..dc4d54fd7 100644
--- a/program/include/bc.php
+++ b/program/include/bc.php
@@ -23,6 +23,7 @@
  * Roundcube Webmail deprecated functions
  *
  * @package Core
+ * @subpackage Legacy
  * @author Thomas Bruederli <roundcube@gmail.com>
  */
 
diff --git a/program/lib/Roundcube/rcube.php b/program/lib/Roundcube/rcube.php
index cc4905a14..a127eeb4f 100644
--- a/program/lib/Roundcube/rcube.php
+++ b/program/lib/Roundcube/rcube.php
@@ -1266,7 +1266,8 @@ class rcube
 /**
  * Lightweight plugin API class serving as a dummy if plugins are not enabled
  *
- * @package Core
+ * @package Framework
+ * @subpackage Core
  */
 class rcube_dummy_plugin_api
 {
diff --git a/program/lib/Roundcube/rcube_db.php b/program/lib/Roundcube/rcube_db.php
index 2c471e74d..e6e8c2ede 100644
--- a/program/lib/Roundcube/rcube_db.php
+++ b/program/lib/Roundcube/rcube_db.php
@@ -25,7 +25,7 @@
  * This is a wrapper for the PHP PDO.
  *
  * @package   Framework
- * @sbpackage Database
+ * @subpackage Database
  */
 class rcube_db
 {
diff --git a/program/lib/Roundcube/rcube_message_header.php b/program/lib/Roundcube/rcube_message_header.php
index 7009a00af..16a0aaac9 100644
--- a/program/lib/Roundcube/rcube_message_header.php
+++ b/program/lib/Roundcube/rcube_message_header.php
@@ -257,7 +257,8 @@ class rcube_message_header
 /**
  * Class for sorting an array of rcube_message_header objects in a predetermined order.
  *
- * @package Mail
+ * @package    Framework
+ * @subpackage Storage
  * @author  Aleksander Machniak <alec@alec.pl>
  */
 class rcube_message_header_sorter
-- 
cgit v1.2.3


From 9945f24274d84f6fe2124bdf23bc3bd2f128a6c9 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Tue, 18 Dec 2012 09:01:19 +0100
Subject: CS fixes

---
 program/lib/Roundcube/rcube_smtp.php | 764 +++++++++++++++++------------------
 1 file changed, 373 insertions(+), 391 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_smtp.php b/program/lib/Roundcube/rcube_smtp.php
index 96534c0b8..1a4958e2b 100644
--- a/program/lib/Roundcube/rcube_smtp.php
+++ b/program/lib/Roundcube/rcube_smtp.php
@@ -5,7 +5,7 @@
  | program/include/rcube_smtp.php                                        |
  |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
- | Copyright (C) 2005-2010, The Roundcube Dev Team                       |
+ | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
  |                                                                       |
  | Licensed under the GNU General Public License version 3 or            |
  | any later version with exceptions for skins & plugins.                |
@@ -19,9 +19,6 @@
  +-----------------------------------------------------------------------+
 */
 
-// define headers delimiter
-define('SMTP_MIME_CRLF', "\r\n");
-
 /**
  * Class to provide SMTP functionality using PEAR Net_SMTP
  *
@@ -32,439 +29,424 @@ define('SMTP_MIME_CRLF', "\r\n");
  */
 class rcube_smtp
 {
-
-  private $conn = null;
-  private $response;
-  private $error;
-
-
-  /**
-   * SMTP Connection and authentication
-   *
-   * @param string Server host
-   * @param string Server port
-   * @param string User name
-   * @param string Password
-   *
-   * @return bool  Returns true on success, or false on error
-   */
-  public function connect($host=null, $port=null, $user=null, $pass=null)
-  {
-    $rcube = rcube::get_instance();
-
-    // disconnect/destroy $this->conn
-    $this->disconnect();
-
-    // reset error/response var
-    $this->error = $this->response = null;
-
-    // let plugins alter smtp connection config
-    $CONFIG = $rcube->plugins->exec_hook('smtp_connect', array(
-      'smtp_server'    => $host ? $host : $rcube->config->get('smtp_server'),
-      'smtp_port'      => $port ? $port : $rcube->config->get('smtp_port', 25),
-      'smtp_user'      => $user ? $user : $rcube->config->get('smtp_user'),
-      'smtp_pass'      => $pass ? $pass : $rcube->config->get('smtp_pass'),
-      'smtp_auth_cid'  => $rcube->config->get('smtp_auth_cid'),
-      'smtp_auth_pw'   => $rcube->config->get('smtp_auth_pw'),
-      'smtp_auth_type' => $rcube->config->get('smtp_auth_type'),
-      'smtp_helo_host' => $rcube->config->get('smtp_helo_host'),
-      'smtp_timeout'   => $rcube->config->get('smtp_timeout'),
-      'smtp_auth_callbacks' => array(),
-    ));
-
-    $smtp_host = rcube_utils::parse_host($CONFIG['smtp_server']);
-    // when called from Installer it's possible to have empty $smtp_host here
-    if (!$smtp_host) $smtp_host = 'localhost';
-    $smtp_port = is_numeric($CONFIG['smtp_port']) ? $CONFIG['smtp_port'] : 25;
-    $smtp_host_url = parse_url($smtp_host);
-
-    // overwrite port
-    if (isset($smtp_host_url['host']) && isset($smtp_host_url['port']))
+    private $conn = null;
+    private $response;
+    private $error;
+
+    // define headers delimiter
+    const SMTP_MIME_CRLF = "\r\n";
+
+
+    /**
+     * SMTP Connection and authentication
+     *
+     * @param string Server host
+     * @param string Server port
+     * @param string User name
+     * @param string Password
+     *
+     * @return bool  Returns true on success, or false on error
+     */
+    public function connect($host=null, $port=null, $user=null, $pass=null)
     {
-      $smtp_host = $smtp_host_url['host'];
-      $smtp_port = $smtp_host_url['port'];
-    }
+        $rcube = rcube::get_instance();
 
-    // re-write smtp host
-    if (isset($smtp_host_url['host']) && isset($smtp_host_url['scheme']))
-      $smtp_host = sprintf('%s://%s', $smtp_host_url['scheme'], $smtp_host_url['host']);
+        // disconnect/destroy $this->conn
+        $this->disconnect();
 
-    // remove TLS prefix and set flag for use in Net_SMTP::auth()
-    if (preg_match('#^tls://#i', $smtp_host)) {
-      $smtp_host = preg_replace('#^tls://#i', '', $smtp_host);
-      $use_tls = true;
-    }
+        // reset error/response var
+        $this->error = $this->response = null;
+
+        // let plugins alter smtp connection config
+        $CONFIG = $rcube->plugins->exec_hook('smtp_connect', array(
+            'smtp_server'    => $host ? $host : $rcube->config->get('smtp_server'),
+            'smtp_port'      => $port ? $port : $rcube->config->get('smtp_port', 25),
+            'smtp_user'      => $user ? $user : $rcube->config->get('smtp_user'),
+            'smtp_pass'      => $pass ? $pass : $rcube->config->get('smtp_pass'),
+            'smtp_auth_cid'  => $rcube->config->get('smtp_auth_cid'),
+            'smtp_auth_pw'   => $rcube->config->get('smtp_auth_pw'),
+            'smtp_auth_type' => $rcube->config->get('smtp_auth_type'),
+            'smtp_helo_host' => $rcube->config->get('smtp_helo_host'),
+            'smtp_timeout'   => $rcube->config->get('smtp_timeout'),
+            'smtp_auth_callbacks' => array(),
+        ));
+
+        $smtp_host = rcube_utils::parse_host($CONFIG['smtp_server']);
+        // when called from Installer it's possible to have empty $smtp_host here
+        if (!$smtp_host) $smtp_host = 'localhost';
+        $smtp_port     = is_numeric($CONFIG['smtp_port']) ? $CONFIG['smtp_port'] : 25;
+        $smtp_host_url = parse_url($smtp_host);
+
+        // overwrite port
+        if (isset($smtp_host_url['host']) && isset($smtp_host_url['port'])) {
+            $smtp_host = $smtp_host_url['host'];
+            $smtp_port = $smtp_host_url['port'];
+        }
 
-    if (!empty($CONFIG['smtp_helo_host']))
-      $helo_host = $CONFIG['smtp_helo_host'];
-    else if (!empty($_SERVER['SERVER_NAME']))
-      $helo_host = preg_replace('/:\d+$/', '', $_SERVER['SERVER_NAME']);
-    else
-      $helo_host = 'localhost';
+        // re-write smtp host
+        if (isset($smtp_host_url['host']) && isset($smtp_host_url['scheme'])) {
+            $smtp_host = sprintf('%s://%s', $smtp_host_url['scheme'], $smtp_host_url['host']);
+        }
 
-    // IDNA Support
-    $smtp_host = rcube_utils::idn_to_ascii($smtp_host);
+        // remove TLS prefix and set flag for use in Net_SMTP::auth()
+        if (preg_match('#^tls://#i', $smtp_host)) {
+            $smtp_host = preg_replace('#^tls://#i', '', $smtp_host);
+            $use_tls   = true;
+        }
 
-    $this->conn = new Net_SMTP($smtp_host, $smtp_port, $helo_host);
+        if (!empty($CONFIG['smtp_helo_host'])) {
+            $helo_host = $CONFIG['smtp_helo_host'];
+        }
+        else if (!empty($_SERVER['SERVER_NAME'])) {
+            $helo_host = preg_replace('/:\d+$/', '', $_SERVER['SERVER_NAME']);
+        }
+        else {
+            $helo_host = 'localhost';
+        }
 
-    if ($rcube->config->get('smtp_debug'))
-      $this->conn->setDebug(true, array($this, 'debug_handler'));
+        // IDNA Support
+        $smtp_host = rcube_utils::idn_to_ascii($smtp_host);
 
-    // register authentication methods
-    if (!empty($CONFIG['smtp_auth_callbacks']) && method_exists($this->conn, 'setAuthMethod')) {
-      foreach ($CONFIG['smtp_auth_callbacks'] as $callback) {
-        $this->conn->setAuthMethod($callback['name'], $callback['function'],
-          isset($callback['prepend']) ? $callback['prepend'] : true);
-      }
-    }
+        $this->conn = new Net_SMTP($smtp_host, $smtp_port, $helo_host);
 
-    // try to connect to server and exit on failure
-    $result = $this->conn->connect($smtp_timeout);
+        if ($rcube->config->get('smtp_debug')) {
+            $this->conn->setDebug(true, array($this, 'debug_handler'));
+        }
 
-    if (PEAR::isError($result)) {
-      $this->response[] = "Connection failed: ".$result->getMessage();
-      $this->error = array('label' => 'smtpconnerror', 'vars' => array('code' => $this->conn->_code));
-      $this->conn = null;
-      return false;
-    }
+        // register authentication methods
+        if (!empty($CONFIG['smtp_auth_callbacks']) && method_exists($this->conn, 'setAuthMethod')) {
+            foreach ($CONFIG['smtp_auth_callbacks'] as $callback) {
+                $this->conn->setAuthMethod($callback['name'], $callback['function'],
+                    isset($callback['prepend']) ? $callback['prepend'] : true);
+            }
+        }
 
-    // workaround for timeout bug in Net_SMTP 1.5.[0-1] (#1487843)
-    if (method_exists($this->conn, 'setTimeout')
-      && ($timeout = ini_get('default_socket_timeout'))
-    ) {
-      $this->conn->setTimeout($timeout);
-    }
+        // try to connect to server and exit on failure
+        $result = $this->conn->connect($smtp_timeout);
 
-    $smtp_user = str_replace('%u', $rcube->get_user_name(), $CONFIG['smtp_user']);
-    $smtp_pass = str_replace('%p', $rcube->get_user_password(), $CONFIG['smtp_pass']);
-    $smtp_auth_type = empty($CONFIG['smtp_auth_type']) ? NULL : $CONFIG['smtp_auth_type'];
+        if (PEAR::isError($result)) {
+            $this->response[] = "Connection failed: ".$result->getMessage();
+            $this->error = array('label' => 'smtpconnerror', 'vars' => array('code' => $this->conn->_code));
+            $this->conn  = null;
+            return false;
+        }
 
-    if (!empty($CONFIG['smtp_auth_cid'])) {
-      $smtp_authz = $smtp_user;
-      $smtp_user  = $CONFIG['smtp_auth_cid'];
-      $smtp_pass  = $CONFIG['smtp_auth_pw'];
-    }
+        // workaround for timeout bug in Net_SMTP 1.5.[0-1] (#1487843)
+        if (method_exists($this->conn, 'setTimeout')
+            && ($timeout = ini_get('default_socket_timeout'))
+        ) {
+            $this->conn->setTimeout($timeout);
+        }
 
-    // attempt to authenticate to the SMTP server
-    if ($smtp_user && $smtp_pass)
-    {
-      // IDNA Support
-      if (strpos($smtp_user, '@')) {
-        $smtp_user = rcube_utils::idn_to_ascii($smtp_user);
-      }
-
-      $result = $this->conn->auth($smtp_user, $smtp_pass, $smtp_auth_type, $use_tls, $smtp_authz);
-
-      if (PEAR::isError($result))
-      {
-        $this->error = array('label' => 'smtpautherror', 'vars' => array('code' => $this->conn->_code));
-        $this->response[] .= 'Authentication failure: ' . $result->getMessage() . ' (Code: ' . $result->getCode() . ')';
-        $this->reset();
-        $this->disconnect();
-        return false;
-      }
-    }
+        $smtp_user = str_replace('%u', $rcube->get_user_name(), $CONFIG['smtp_user']);
+        $smtp_pass = str_replace('%p', $rcube->get_user_password(), $CONFIG['smtp_pass']);
+        $smtp_auth_type = empty($CONFIG['smtp_auth_type']) ? NULL : $CONFIG['smtp_auth_type'];
 
-    return true;
-  }
-
-
-  /**
-   * Function for sending mail
-   *
-   * @param string Sender e-Mail address
-   *
-   * @param mixed  Either a comma-seperated list of recipients
-   *               (RFC822 compliant), or an array of recipients,
-   *               each RFC822 valid. This may contain recipients not
-   *               specified in the headers, for Bcc:, resending
-   *               messages, etc.
-   * @param mixed  The message headers to send with the mail
-   *               Either as an associative array or a finally
-   *               formatted string
-   * @param mixed  The full text of the message body, including any Mime parts
-   *               or file handle
-   * @param array  Delivery options (e.g. DSN request)
-   *
-   * @return bool  Returns true on success, or false on error
-   */
-  public function send_mail($from, $recipients, &$headers, &$body, $opts=null)
-  {
-    if (!is_object($this->conn))
-      return false;
-
-    // prepare message headers as string
-    if (is_array($headers))
-    {
-      if (!($headerElements = $this->_prepare_headers($headers))) {
-        $this->reset();
-        return false;
-      }
+        if (!empty($CONFIG['smtp_auth_cid'])) {
+            $smtp_authz = $smtp_user;
+            $smtp_user  = $CONFIG['smtp_auth_cid'];
+            $smtp_pass  = $CONFIG['smtp_auth_pw'];
+        }
 
-      list($from, $text_headers) = $headerElements;
-    }
-    else if (is_string($headers))
-      $text_headers = $headers;
-    else
-    {
-      $this->reset();
-      $this->response[] = "Invalid message headers";
-      return false;
+        // attempt to authenticate to the SMTP server
+        if ($smtp_user && $smtp_pass) {
+            // IDNA Support
+            if (strpos($smtp_user, '@')) {
+                $smtp_user = rcube_utils::idn_to_ascii($smtp_user);
+            }
+
+            $result = $this->conn->auth($smtp_user, $smtp_pass, $smtp_auth_type, $use_tls, $smtp_authz);
+
+            if (PEAR::isError($result)) {
+                $this->error = array('label' => 'smtpautherror', 'vars' => array('code' => $this->conn->_code));
+                $this->response[] .= 'Authentication failure: ' . $result->getMessage() . ' (Code: ' . $result->getCode() . ')';
+                $this->reset();
+                $this->disconnect();
+                return false;
+            }
+        }
+
+        return true;
     }
 
-    // exit if no from address is given
-    if (!isset($from))
+    /**
+     * Function for sending mail
+     *
+     * @param string Sender e-Mail address
+     *
+     * @param mixed  Either a comma-seperated list of recipients
+     *               (RFC822 compliant), or an array of recipients,
+     *               each RFC822 valid. This may contain recipients not
+     *               specified in the headers, for Bcc:, resending
+     *               messages, etc.
+     * @param mixed  The message headers to send with the mail
+     *               Either as an associative array or a finally
+     *               formatted string
+     * @param mixed  The full text of the message body, including any Mime parts
+     *               or file handle
+     * @param array  Delivery options (e.g. DSN request)
+     *
+     * @return bool  Returns true on success, or false on error
+     */
+    public function send_mail($from, $recipients, &$headers, &$body, $opts=null)
     {
-      $this->reset();
-      $this->response[] = "No From address has been provided";
-      return false;
-    }
+        if (!is_object($this->conn)) {
+            return false;
+        }
 
-    // RFC3461: Delivery Status Notification
-    if ($opts['dsn']) {
-      $exts = $this->conn->getServiceExtensions();
+        // prepare message headers as string
+        if (is_array($headers)) {
+            if (!($headerElements = $this->_prepare_headers($headers))) {
+                $this->reset();
+                return false;
+            }
 
-      if (isset($exts['DSN'])) {
-        $from_params      = 'RET=HDRS';
-        $recipient_params = 'NOTIFY=SUCCESS,FAILURE';
-      }
-    }
+            list($from, $text_headers) = $headerElements;
+        }
+        else if (is_string($headers)) {
+            $text_headers = $headers;
+        }
+        else {
+            $this->reset();
+            $this->response[] = "Invalid message headers";
+            return false;
+        }
+
+        // exit if no from address is given
+        if (!isset($from)) {
+            $this->reset();
+            $this->response[] = "No From address has been provided";
+            return false;
+        }
+
+        // RFC3461: Delivery Status Notification
+        if ($opts['dsn']) {
+            $exts = $this->conn->getServiceExtensions();
+
+            if (isset($exts['DSN'])) {
+                $from_params      = 'RET=HDRS';
+                $recipient_params = 'NOTIFY=SUCCESS,FAILURE';
+            }
+        }
+
+        // RFC2298.3: remove envelope sender address
+        if (preg_match('/Content-Type: multipart\/report/', $text_headers)
+            && preg_match('/report-type=disposition-notification/', $text_headers)
+        ) {
+            $from = '';
+        }
+
+        // set From: address
+        if (PEAR::isError($this->conn->mailFrom($from, $from_params))) {
+            $err = $this->conn->getResponse();
+            $this->error = array('label' => 'smtpfromerror', 'vars' => array(
+                'from' => $from, 'code' => $this->conn->_code, 'msg' => $err[1]));
+            $this->response[] = "Failed to set sender '$from'";
+            $this->reset();
+            return false;
+        }
+
+        // prepare list of recipients
+        $recipients = $this->_parse_rfc822($recipients);
+        if (PEAR::isError($recipients)) {
+            $this->error = array('label' => 'smtprecipientserror');
+            $this->reset();
+            return false;
+        }
 
-    // RFC2298.3: remove envelope sender address
-    if (preg_match('/Content-Type: multipart\/report/', $text_headers)
-      && preg_match('/report-type=disposition-notification/', $text_headers)
-    ) {
-      $from = '';
+        // set mail recipients
+        foreach ($recipients as $recipient) {
+            if (PEAR::isError($this->conn->rcptTo($recipient, $recipient_params))) {
+                $err = $this->conn->getResponse();
+                $this->error = array('label' => 'smtptoerror', 'vars' => array(
+                    'to' => $recipient, 'code' => $this->conn->_code, 'msg' => $err[1]));
+                $this->response[] = "Failed to add recipient '$recipient'";
+                $this->reset();
+                return false;
+            }
+        }
+
+        if (is_resource($body)) {
+            // file handle
+            $data         = $body;
+            $text_headers = preg_replace('/[\r\n]+$/', '', $text_headers);
+        }
+        else {
+            // Concatenate headers and body so it can be passed by reference to SMTP_CONN->data
+            // so preg_replace in SMTP_CONN->quotedata will store a reference instead of a copy.
+            // We are still forced to make another copy here for a couple ticks so we don't really
+            // get to save a copy in the method call.
+            $data = $text_headers . "\r\n" . $body;
+
+            // unset old vars to save data and so we can pass into SMTP_CONN->data by reference.
+            unset($text_headers, $body);
+        }
+
+        // Send the message's headers and the body as SMTP data.
+        if (PEAR::isError($result = $this->conn->data($data, $text_headers))) {
+            $err = $this->conn->getResponse();
+            if (!in_array($err[0], array(354, 250, 221))) {
+                $msg = sprintf('[%d] %s', $err[0], $err[1]);
+            }
+            else {
+                $msg = $result->getMessage();
+            }
+
+            $this->error = array('label' => 'smtperror', 'vars' => array('msg' => $msg));
+            $this->response[] = "Failed to send data";
+            $this->reset();
+            return false;
+        }
+
+        $this->response[] = join(': ', $this->conn->getResponse());
+        return true;
     }
 
-    // set From: address
-    if (PEAR::isError($this->conn->mailFrom($from, $from_params)))
+    /**
+     * Reset the global SMTP connection
+     */
+    public function reset()
     {
-      $err = $this->conn->getResponse();
-      $this->error = array('label' => 'smtpfromerror', 'vars' => array(
-        'from' => $from, 'code' => $this->conn->_code, 'msg' => $err[1]));
-      $this->response[] = "Failed to set sender '$from'";
-      $this->reset();
-      return false;
+        if (is_object($this->conn)) {
+            $this->conn->rset();
+        }
     }
 
-    // prepare list of recipients
-    $recipients = $this->_parse_rfc822($recipients);
-    if (PEAR::isError($recipients))
+    /**
+     * Disconnect the global SMTP connection
+     */
+    public function disconnect()
     {
-      $this->error = array('label' => 'smtprecipientserror');
-      $this->reset();
-      return false;
+        if (is_object($this->conn)) {
+            $this->conn->disconnect();
+            $this->conn = null;
+        }
     }
 
-    // set mail recipients
-    foreach ($recipients as $recipient)
+
+    /**
+     * This is our own debug handler for the SMTP connection
+     */
+    public function debug_handler(&$smtp, $message)
     {
-      if (PEAR::isError($this->conn->rcptTo($recipient, $recipient_params))) {
-        $err = $this->conn->getResponse();
-        $this->error = array('label' => 'smtptoerror', 'vars' => array(
-          'to' => $recipient, 'code' => $this->conn->_code, 'msg' => $err[1]));
-        $this->response[] = "Failed to add recipient '$recipient'";
-        $this->reset();
-        return false;
-      }
+        rcube::write_log('smtp', preg_replace('/\r\n$/', '', $message));
     }
 
-    if (is_resource($body))
+    /**
+     * Get error message
+     */
+    public function get_error()
     {
-      // file handle
-      $data = $body;
-      $text_headers = preg_replace('/[\r\n]+$/', '', $text_headers);
-    } else {
-      // Concatenate headers and body so it can be passed by reference to SMTP_CONN->data
-      // so preg_replace in SMTP_CONN->quotedata will store a reference instead of a copy. 
-      // We are still forced to make another copy here for a couple ticks so we don't really 
-      // get to save a copy in the method call.
-      $data = $text_headers . "\r\n" . $body;
-
-      // unset old vars to save data and so we can pass into SMTP_CONN->data by reference.
-      unset($text_headers, $body);
+        return $this->error;
     }
 
-    // Send the message's headers and the body as SMTP data.
-    if (PEAR::isError($result = $this->conn->data($data, $text_headers)))
+    /**
+     * Get server response messages array
+     */
+    public function get_response()
     {
-      $err = $this->conn->getResponse();
-      if (!in_array($err[0], array(354, 250, 221)))
-        $msg = sprintf('[%d] %s', $err[0], $err[1]);
-      else
-        $msg = $result->getMessage();
-
-      $this->error = array('label' => 'smtperror', 'vars' => array('msg' => $msg));
-      $this->response[] = "Failed to send data";
-      $this->reset();
-      return false;
+         return $this->response;
     }
 
-    $this->response[] = join(': ', $this->conn->getResponse());
-    return true;
-  }
-
-
-  /**
-   * Reset the global SMTP connection
-   * @access public
-   */
-  public function reset()
-  {
-    if (is_object($this->conn))
-      $this->conn->rset();
-  }
-
-
-  /**
-   * Disconnect the global SMTP connection
-   * @access public
-   */
-  public function disconnect()
-  {
-    if (is_object($this->conn)) {
-      $this->conn->disconnect();
-      $this->conn = null;
-    }
-  }
-
-
-  /**
-   * This is our own debug handler for the SMTP connection
-   * @access public
-   */
-  public function debug_handler(&$smtp, $message)
-  {
-    rcube::write_log('smtp', preg_replace('/\r\n$/', '', $message));
-  }
-
-
-  /**
-   * Get error message
-   * @access public
-   */
-  public function get_error()
-  {
-    return $this->error;
-  }
-
-
-  /**
-   * Get server response messages array
-   * @access public
-   */
-  public function get_response()
-  {
-    return $this->response;
-  }
-
-
-  /**
-   * Take an array of mail headers and return a string containing
-   * text usable in sending a message.
-   *
-   * @param array $headers The array of headers to prepare, in an associative
-   *              array, where the array key is the header name (ie,
-   *              'Subject'), and the array value is the header
-   *              value (ie, 'test'). The header produced from those
-   *              values would be 'Subject: test'.
-   *
-   * @return mixed Returns false if it encounters a bad address,
-   *               otherwise returns an array containing two
-   *               elements: Any From: address found in the headers,
-   *               and the plain text version of the headers.
-   * @access private
-   */
-  private function _prepare_headers($headers)
-  {
-    $lines = array();
-    $from = null;
-
-    foreach ($headers as $key => $value)
+    /**
+     * Take an array of mail headers and return a string containing
+     * text usable in sending a message.
+     *
+     * @param array $headers The array of headers to prepare, in an associative
+     *              array, where the array key is the header name (ie,
+     *              'Subject'), and the array value is the header
+     *              value (ie, 'test'). The header produced from those
+     *              values would be 'Subject: test'.
+     *
+     * @return mixed Returns false if it encounters a bad address,
+     *               otherwise returns an array containing two
+     *               elements: Any From: address found in the headers,
+     *               and the plain text version of the headers.
+     */
+    private function _prepare_headers($headers)
     {
-      if (strcasecmp($key, 'From') === 0)
-      {
-        $addresses = $this->_parse_rfc822($value);
-
-        if (is_array($addresses))
-          $from = $addresses[0];
-
-        // Reject envelope From: addresses with spaces.
-        if (strpos($from, ' ') !== false)
-          return false;
-
-        $lines[] = $key . ': ' . $value;
-      }
-      else if (strcasecmp($key, 'Received') === 0)
-      {
-        $received = array();
-        if (is_array($value))
-        {
-          foreach ($value as $line)
-            $received[] = $key . ': ' . $line;
-        }
-        else
-        {
-          $received[] = $key . ': ' . $value;
+        $lines = array();
+        $from  = null;
+
+        foreach ($headers as $key => $value) {
+            if (strcasecmp($key, 'From') === 0) {
+                $addresses = $this->_parse_rfc822($value);
+
+                if (is_array($addresses)) {
+                    $from = $addresses[0];
+                }
+
+                // Reject envelope From: addresses with spaces.
+                if (strpos($from, ' ') !== false) {
+                    return false;
+                }
+
+                $lines[] = $key . ': ' . $value;
+            }
+            else if (strcasecmp($key, 'Received') === 0) {
+                $received = array();
+                if (is_array($value)) {
+                    foreach ($value as $line) {
+                        $received[] = $key . ': ' . $line;
+                    }
+                }
+                else {
+                    $received[] = $key . ': ' . $value;
+                }
+
+                // Put Received: headers at the top.  Spam detectors often
+                // flag messages with Received: headers after the Subject:
+                // as spam.
+                $lines = array_merge($received, $lines);
+            }
+            else {
+                // If $value is an array (i.e., a list of addresses), convert
+                // it to a comma-delimited string of its elements (addresses).
+                if (is_array($value)) {
+                    $value = implode(', ', $value);
+                }
+
+                $lines[] = $key . ': ' . $value;
+            }
         }
 
-        // Put Received: headers at the top.  Spam detectors often
-        // flag messages with Received: headers after the Subject:
-        // as spam.
-        $lines = array_merge($received, $lines);
-      }
-      else
-      {
-        // If $value is an array (i.e., a list of addresses), convert
-        // it to a comma-delimited string of its elements (addresses).
-        if (is_array($value))
-          $value = implode(', ', $value);
-
-        $lines[] = $key . ': ' . $value;
-      }
+        return array($from, join(self::SMTP_MIME_CRLF, $lines) . self::SMTP_MIME_CRLF);
     }
 
-    return array($from, join(SMTP_MIME_CRLF, $lines) . SMTP_MIME_CRLF);
-  }
-
-  /**
-   * Take a set of recipients and parse them, returning an array of
-   * bare addresses (forward paths) that can be passed to sendmail
-   * or an smtp server with the rcpt to: command.
-   *
-   * @param mixed Either a comma-seperated list of recipients
-   *              (RFC822 compliant), or an array of recipients,
-   *              each RFC822 valid.
-   *
-   * @return array An array of forward paths (bare addresses).
-   * @access private
-   */
-  private function _parse_rfc822($recipients)
-  {
-    // if we're passed an array, assume addresses are valid and implode them before parsing.
-    if (is_array($recipients))
-      $recipients = implode(', ', $recipients);
-
-    $addresses = array();
-    $recipients = rcube_utils::explode_quoted_string(',', $recipients);
-
-    reset($recipients);
-    while (list($k, $recipient) = each($recipients))
+    /**
+     * Take a set of recipients and parse them, returning an array of
+     * bare addresses (forward paths) that can be passed to sendmail
+     * or an smtp server with the rcpt to: command.
+     *
+     * @param mixed Either a comma-seperated list of recipients
+     *              (RFC822 compliant), or an array of recipients,
+     *              each RFC822 valid.
+     *
+     * @return array An array of forward paths (bare addresses).
+     */
+    private function _parse_rfc822($recipients)
     {
-      $a = rcube_utils::explode_quoted_string(' ', $recipient);
-      while (list($k2, $word) = each($a))
-      {
-        if (strpos($word, "@") > 0 && $word[strlen($word)-1] != '"')
-        {
-          $word = preg_replace('/^<|>$/', '', trim($word));
-          if (in_array($word, $addresses)===false)
-            array_push($addresses, $word);
+        // if we're passed an array, assume addresses are valid and implode them before parsing.
+        if (is_array($recipients)) {
+            $recipients = implode(', ', $recipients);
         }
-      }
-    }
 
-    return $addresses;
-  }
+        $addresses  = array();
+        $recipients = rcube_utils::explode_quoted_string(',', $recipients);
+
+        reset($recipients);
+        while (list($k, $recipient) = each($recipients)) {
+            $a = rcube_utils::explode_quoted_string(' ', $recipient);
+            while (list($k2, $word) = each($a)) {
+                if (strpos($word, "@") > 0 && $word[strlen($word)-1] != '"') {
+                    $word = preg_replace('/^<|>$/', '', trim($word));
+                    if (in_array($word, $addresses) === false) {
+                        array_push($addresses, $word);
+                    }
+                }
+            }
+        }
 
+        return $addresses;
+    }
 }
-- 
cgit v1.2.3


From d2534c63f2c0b640a39fce2a71b14a5dcda6e7fd Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Tue, 18 Dec 2012 09:07:00 +0100
Subject: Cleanup, remove file paths from doc

---
 program/lib/Roundcube/bootstrap.php             | 3 ---
 program/lib/Roundcube/html.php                  | 3 ---
 program/lib/Roundcube/rcube.php                 | 2 --
 program/lib/Roundcube/rcube_addressbook.php     | 3 ---
 program/lib/Roundcube/rcube_base_replacer.php   | 3 ---
 program/lib/Roundcube/rcube_browser.php         | 3 ---
 program/lib/Roundcube/rcube_cache.php           | 3 ---
 program/lib/Roundcube/rcube_charset.php         | 3 ---
 program/lib/Roundcube/rcube_config.php          | 3 ---
 program/lib/Roundcube/rcube_contacts.php        | 3 ---
 program/lib/Roundcube/rcube_content_filter.php  | 3 ---
 program/lib/Roundcube/rcube_csv2vcard.php       | 2 --
 program/lib/Roundcube/rcube_db.php              | 4 ----
 program/lib/Roundcube/rcube_db_mssql.php        | 4 ----
 program/lib/Roundcube/rcube_db_mysql.php        | 4 ----
 program/lib/Roundcube/rcube_db_pgsql.php        | 4 ----
 program/lib/Roundcube/rcube_db_sqlite.php       | 4 ----
 program/lib/Roundcube/rcube_db_sqlsrv.php       | 4 ----
 program/lib/Roundcube/rcube_enriched.php        | 4 ----
 program/lib/Roundcube/rcube_image.php           | 3 ---
 program/lib/Roundcube/rcube_imap.php            | 4 ----
 program/lib/Roundcube/rcube_imap_cache.php      | 4 ----
 program/lib/Roundcube/rcube_imap_generic.php    | 4 ----
 program/lib/Roundcube/rcube_ldap.php            | 4 ----
 program/lib/Roundcube/rcube_message.php         | 3 ---
 program/lib/Roundcube/rcube_message_header.php  | 3 ---
 program/lib/Roundcube/rcube_message_part.php    | 4 ----
 program/lib/Roundcube/rcube_mime.php            | 4 ----
 program/lib/Roundcube/rcube_output.php          | 4 +---
 program/lib/Roundcube/rcube_plugin.php          | 2 --
 program/lib/Roundcube/rcube_plugin_api.php      | 3 ---
 program/lib/Roundcube/rcube_result_index.php    | 4 ----
 program/lib/Roundcube/rcube_result_set.php      | 4 ----
 program/lib/Roundcube/rcube_result_thread.php   | 4 ----
 program/lib/Roundcube/rcube_session.php         | 3 ---
 program/lib/Roundcube/rcube_smtp.php            | 3 ---
 program/lib/Roundcube/rcube_spellchecker.php    | 4 ----
 program/lib/Roundcube/rcube_storage.php         | 4 ----
 program/lib/Roundcube/rcube_string_replacer.php | 4 ----
 program/lib/Roundcube/rcube_user.php            | 4 ----
 program/lib/Roundcube/rcube_utils.php           | 3 ---
 program/lib/Roundcube/rcube_vcard.php           | 3 ---
 42 files changed, 1 insertion(+), 143 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/bootstrap.php b/program/lib/Roundcube/bootstrap.php
index eed7db8c1..18c07ddab 100644
--- a/program/lib/Roundcube/bootstrap.php
+++ b/program/lib/Roundcube/bootstrap.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/bootstrap.php                                         |
- |                                                                       |
  | This file is part of the Roundcube PHP suite                          |
  | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
  |                                                                       |
@@ -13,7 +11,6 @@
  |                                                                       |
  | CONTENTS:                                                             |
  |   Roundcube Framework Initialization                                  |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
  | Author: Aleksander Machniak <alec@alec.pl>                            |
diff --git a/program/lib/Roundcube/html.php b/program/lib/Roundcube/html.php
index 5fb574b97..33b766c44 100644
--- a/program/lib/Roundcube/html.php
+++ b/program/lib/Roundcube/html.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/html.php                                              |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2005-2011, The Roundcube Dev Team                       |
  |                                                                       |
@@ -13,7 +11,6 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   Helper class to create valid XHTML code                             |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
  +-----------------------------------------------------------------------+
diff --git a/program/lib/Roundcube/rcube.php b/program/lib/Roundcube/rcube.php
index a127eeb4f..cde549052 100644
--- a/program/lib/Roundcube/rcube.php
+++ b/program/lib/Roundcube/rcube.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube.php                                             |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2008-2012, The Roundcube Dev Team                       |
  | Copyright (C) 2011-2012, Kolab Systems AG                             |
diff --git a/program/lib/Roundcube/rcube_addressbook.php b/program/lib/Roundcube/rcube_addressbook.php
index ea8df700c..a8f274a8f 100644
--- a/program/lib/Roundcube/rcube_addressbook.php
+++ b/program/lib/Roundcube/rcube_addressbook.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_addressbook.php                                 |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2006-2012, The Roundcube Dev Team                       |
  |                                                                       |
@@ -13,7 +11,6 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   Interface to the local address book database                        |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
  +-----------------------------------------------------------------------+
diff --git a/program/lib/Roundcube/rcube_base_replacer.php b/program/lib/Roundcube/rcube_base_replacer.php
index b2a0fc13c..fcd85c2c8 100644
--- a/program/lib/Roundcube/rcube_base_replacer.php
+++ b/program/lib/Roundcube/rcube_base_replacer.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_base_replacer.php                               |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
  |                                                                       |
@@ -13,7 +11,6 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   Provide basic functions for base URL replacement                    |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
  +-----------------------------------------------------------------------+
diff --git a/program/lib/Roundcube/rcube_browser.php b/program/lib/Roundcube/rcube_browser.php
index 154e7ef4e..d10fe2a2c 100644
--- a/program/lib/Roundcube/rcube_browser.php
+++ b/program/lib/Roundcube/rcube_browser.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_browser.php                                     |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2007-2009, The Roundcube Dev Team                       |
  |                                                                       |
@@ -13,7 +11,6 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   Class representing the client browser's properties                  |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
  +-----------------------------------------------------------------------+
diff --git a/program/lib/Roundcube/rcube_cache.php b/program/lib/Roundcube/rcube_cache.php
index 3e1ce4fc8..92f12a8bf 100644
--- a/program/lib/Roundcube/rcube_cache.php
+++ b/program/lib/Roundcube/rcube_cache.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_cache.php                                       |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2011, The Roundcube Dev Team                            |
  | Copyright (C) 2011, Kolab Systems AG                                  |
@@ -14,7 +12,6 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   Caching engine                                                      |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
  | Author: Aleksander Machniak <alec@alec.pl>                            |
diff --git a/program/lib/Roundcube/rcube_charset.php b/program/lib/Roundcube/rcube_charset.php
index 6135a5711..968d1c4b8 100644
--- a/program/lib/Roundcube/rcube_charset.php
+++ b/program/lib/Roundcube/rcube_charset.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_charset.php                                     |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
  | Copyright (C) 2011-2012, Kolab Systems AG                             |
@@ -15,7 +13,6 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   Provide charset conversion functionality                            |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
  | Author: Aleksander Machniak <alec@alec.pl>                            |
diff --git a/program/lib/Roundcube/rcube_config.php b/program/lib/Roundcube/rcube_config.php
index 615faf3ad..2190dc4c2 100644
--- a/program/lib/Roundcube/rcube_config.php
+++ b/program/lib/Roundcube/rcube_config.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_config.php                                      |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2008-2012, The Roundcube Dev Team                       |
  |                                                                       |
@@ -13,7 +11,6 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   Class to read configuration settings                                |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
  +-----------------------------------------------------------------------+
diff --git a/program/lib/Roundcube/rcube_contacts.php b/program/lib/Roundcube/rcube_contacts.php
index 5b4292a4c..a98b13865 100644
--- a/program/lib/Roundcube/rcube_contacts.php
+++ b/program/lib/Roundcube/rcube_contacts.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_contacts.php                                    |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2006-2012, The Roundcube Dev Team                       |
  |                                                                       |
@@ -13,7 +11,6 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   Interface to the local address book database                        |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
  +-----------------------------------------------------------------------+
diff --git a/program/lib/Roundcube/rcube_content_filter.php b/program/lib/Roundcube/rcube_content_filter.php
index 99916a300..b814bb71d 100644
--- a/program/lib/Roundcube/rcube_content_filter.php
+++ b/program/lib/Roundcube/rcube_content_filter.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_content_filter.php                              |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2011, The Roundcube Dev Team                            |
  |                                                                       |
@@ -13,7 +11,6 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   PHP stream filter to detect evil content in mail attachments        |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
  +-----------------------------------------------------------------------+
diff --git a/program/lib/Roundcube/rcube_csv2vcard.php b/program/lib/Roundcube/rcube_csv2vcard.php
index 850c0c4c3..9c28a3b49 100644
--- a/program/lib/Roundcube/rcube_csv2vcard.php
+++ b/program/lib/Roundcube/rcube_csv2vcard.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_csv2vcard.php                                   |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2008-2012, The Roundcube Dev Team                       |
  |                                                                       |
diff --git a/program/lib/Roundcube/rcube_db.php b/program/lib/Roundcube/rcube_db.php
index e6e8c2ede..47ddc81a6 100644
--- a/program/lib/Roundcube/rcube_db.php
+++ b/program/lib/Roundcube/rcube_db.php
@@ -2,8 +2,6 @@
 
 /**
  +-----------------------------------------------------------------------+
- | program/include/rcube_db.php                                          |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
  |                                                                       |
@@ -13,13 +11,11 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   Database wrapper class that implements PHP PDO functions            |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Aleksander Machniak <alec@alec.pl>                            |
  +-----------------------------------------------------------------------+
 */
 
-
 /**
  * Database independent query interface.
  * This is a wrapper for the PHP PDO.
diff --git a/program/lib/Roundcube/rcube_db_mssql.php b/program/lib/Roundcube/rcube_db_mssql.php
index c95663c74..84fe22bbc 100644
--- a/program/lib/Roundcube/rcube_db_mssql.php
+++ b/program/lib/Roundcube/rcube_db_mssql.php
@@ -2,8 +2,6 @@
 
 /**
  +-----------------------------------------------------------------------+
- | program/include/rcube_db_mssql.php                                    |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
  |                                                                       |
@@ -14,13 +12,11 @@
  | PURPOSE:                                                              |
  |   Database wrapper class that implements PHP PDO functions            |
  |   for MS SQL Server database                                          |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Aleksander Machniak <alec@alec.pl>                            |
  +-----------------------------------------------------------------------+
 */
 
-
 /**
  * Database independent query interface
  * This is a wrapper for the PHP PDO
diff --git a/program/lib/Roundcube/rcube_db_mysql.php b/program/lib/Roundcube/rcube_db_mysql.php
index 1c5ba1de7..c32cc259c 100644
--- a/program/lib/Roundcube/rcube_db_mysql.php
+++ b/program/lib/Roundcube/rcube_db_mysql.php
@@ -2,8 +2,6 @@
 
 /**
  +-----------------------------------------------------------------------+
- | program/include/rcube_db_mysql.php                                    |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
  |                                                                       |
@@ -14,13 +12,11 @@
  | PURPOSE:                                                              |
  |   Database wrapper class that implements PHP PDO functions            |
  |   for MySQL database                                                  |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Aleksander Machniak <alec@alec.pl>                            |
  +-----------------------------------------------------------------------+
 */
 
-
 /**
  * Database independent query interface
  *
diff --git a/program/lib/Roundcube/rcube_db_pgsql.php b/program/lib/Roundcube/rcube_db_pgsql.php
index 797860a84..cf23c5e48 100644
--- a/program/lib/Roundcube/rcube_db_pgsql.php
+++ b/program/lib/Roundcube/rcube_db_pgsql.php
@@ -2,8 +2,6 @@
 
 /**
  +-----------------------------------------------------------------------+
- | program/include/rcube_db_pgsql.php                                    |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
  |                                                                       |
@@ -14,13 +12,11 @@
  | PURPOSE:                                                              |
  |   Database wrapper class that implements PHP PDO functions            |
  |   for PostgreSQL database                                             |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Aleksander Machniak <alec@alec.pl>                            |
  +-----------------------------------------------------------------------+
 */
 
-
 /**
  * Database independent query interface
  * This is a wrapper for the PHP PDO
diff --git a/program/lib/Roundcube/rcube_db_sqlite.php b/program/lib/Roundcube/rcube_db_sqlite.php
index 65dcb6d6e..326c6a710 100644
--- a/program/lib/Roundcube/rcube_db_sqlite.php
+++ b/program/lib/Roundcube/rcube_db_sqlite.php
@@ -2,8 +2,6 @@
 
 /**
  +-----------------------------------------------------------------------+
- | program/include/rcube_db_sqlite.php                                   |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
  |                                                                       |
@@ -14,13 +12,11 @@
  | PURPOSE:                                                              |
  |   Database wrapper class that implements PHP PDO functions            |
  |   for SQLite database                                                 |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Aleksander Machniak <alec@alec.pl>                            |
  +-----------------------------------------------------------------------+
 */
 
-
 /**
  * Database independent query interface
  * This is a wrapper for the PHP PDO
diff --git a/program/lib/Roundcube/rcube_db_sqlsrv.php b/program/lib/Roundcube/rcube_db_sqlsrv.php
index 8b6ffe807..e69678025 100644
--- a/program/lib/Roundcube/rcube_db_sqlsrv.php
+++ b/program/lib/Roundcube/rcube_db_sqlsrv.php
@@ -2,8 +2,6 @@
 
 /**
  +-----------------------------------------------------------------------+
- | program/include/rcube_db_sqlsrv.php                                   |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
  |                                                                       |
@@ -14,13 +12,11 @@
  | PURPOSE:                                                              |
  |   Database wrapper class that implements PHP PDO functions            |
  |   for MS SQL Server database                                          |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Aleksander Machniak <alec@alec.pl>                            |
  +-----------------------------------------------------------------------+
 */
 
-
 /**
  * Database independent query interface
  * This is a wrapper for the PHP PDO
diff --git a/program/lib/Roundcube/rcube_enriched.php b/program/lib/Roundcube/rcube_enriched.php
index 8b64fe054..8c628c912 100644
--- a/program/lib/Roundcube/rcube_enriched.php
+++ b/program/lib/Roundcube/rcube_enriched.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_enriched.php                                    |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
  |                                                                       |
@@ -13,14 +11,12 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   Helper class to convert Enriched to HTML format (RFC 1523, 1896)    |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Aleksander Machniak <alec@alec.pl>                            |
  | Author: Ryo Chijiiwa (IlohaMail)                                      |
  +-----------------------------------------------------------------------+
 */
 
-
 /**
  * Class for Enriched to HTML conversion
  *
diff --git a/program/lib/Roundcube/rcube_image.php b/program/lib/Roundcube/rcube_image.php
index b72a24c51..ad96842d2 100644
--- a/program/lib/Roundcube/rcube_image.php
+++ b/program/lib/Roundcube/rcube_image.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_image.php                                       |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
  | Copyright (C) 2011-2012, Kolab Systems AG                             |
@@ -14,7 +12,6 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   Image resizer and converter                                         |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
  | Author: Aleksander Machniak <alec@alec.pl>                            |
diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php
index ea3743d02..74c1f5324 100644
--- a/program/lib/Roundcube/rcube_imap.php
+++ b/program/lib/Roundcube/rcube_imap.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_imap.php                                        |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
  | Copyright (C) 2011-2012, Kolab Systems AG                             |
@@ -14,14 +12,12 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   IMAP Storage Engine                                                 |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
  | Author: Aleksander Machniak <alec@alec.pl>                            |
  +-----------------------------------------------------------------------+
 */
 
-
 /**
  * Interface class for accessing an IMAP server
  *
diff --git a/program/lib/Roundcube/rcube_imap_cache.php b/program/lib/Roundcube/rcube_imap_cache.php
index 31214cfbf..f33ac076c 100644
--- a/program/lib/Roundcube/rcube_imap_cache.php
+++ b/program/lib/Roundcube/rcube_imap_cache.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_imap_cache.php                                  |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
  |                                                                       |
@@ -13,14 +11,12 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   Caching of IMAP folder contents (messages and index)                |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
  | Author: Aleksander Machniak <alec@alec.pl>                            |
  +-----------------------------------------------------------------------+
 */
 
-
 /**
  * Interface class for accessing Roundcube messages cache
  *
diff --git a/program/lib/Roundcube/rcube_imap_generic.php b/program/lib/Roundcube/rcube_imap_generic.php
index 28d56c16f..112e91350 100644
--- a/program/lib/Roundcube/rcube_imap_generic.php
+++ b/program/lib/Roundcube/rcube_imap_generic.php
@@ -2,8 +2,6 @@
 
 /**
  +-----------------------------------------------------------------------+
- | program/include/rcube_imap_generic.php                                |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
  | Copyright (C) 2011-2012, Kolab Systems AG                             |
@@ -19,14 +17,12 @@
  |   functionality built-in.                                             |
  |                                                                       |
  |   Based on Iloha IMAP Library. See http://ilohamail.org/ for details  |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Aleksander Machniak <alec@alec.pl>                            |
  | Author: Ryo Chijiiwa <Ryo@IlohaMail.org>                              |
  +-----------------------------------------------------------------------+
 */
 
-
 /**
  * PHP based wrapper class to connect to an IMAP server
  *
diff --git a/program/lib/Roundcube/rcube_ldap.php b/program/lib/Roundcube/rcube_ldap.php
index c32cea728..d4bc669fd 100644
--- a/program/lib/Roundcube/rcube_ldap.php
+++ b/program/lib/Roundcube/rcube_ldap.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_ldap.php                                        |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2006-2012, The Roundcube Dev Team                       |
  | Copyright (C) 2011-2012, Kolab Systems AG                             |
@@ -14,7 +12,6 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   Interface to an LDAP address directory                              |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
  |         Andreas Dick <andudi (at) gmx (dot) ch>                       |
@@ -22,7 +19,6 @@
  +-----------------------------------------------------------------------+
 */
 
-
 /**
  * Model class to access an LDAP address directory
  *
diff --git a/program/lib/Roundcube/rcube_message.php b/program/lib/Roundcube/rcube_message.php
index c626af08a..f41493d12 100644
--- a/program/lib/Roundcube/rcube_message.php
+++ b/program/lib/Roundcube/rcube_message.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_message.php                                     |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2008-2010, The Roundcube Dev Team                       |
  |                                                                       |
@@ -19,7 +17,6 @@
  +-----------------------------------------------------------------------+
 */
 
-
 /**
  * Logical representation of a mail message with all its data
  * and related functions
diff --git a/program/lib/Roundcube/rcube_message_header.php b/program/lib/Roundcube/rcube_message_header.php
index 16a0aaac9..274ae7f9f 100644
--- a/program/lib/Roundcube/rcube_message_header.php
+++ b/program/lib/Roundcube/rcube_message_header.php
@@ -2,8 +2,6 @@
 
 /**
  +-----------------------------------------------------------------------+
- | program/include/rcube_message_header.php                              |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
  | Copyright (C) 2011-2012, Kolab Systems AG                             |
@@ -14,7 +12,6 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   E-mail message headers representation                               |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Aleksander Machniak <alec@alec.pl>                            |
  +-----------------------------------------------------------------------+
diff --git a/program/lib/Roundcube/rcube_message_part.php b/program/lib/Roundcube/rcube_message_part.php
index c9c9257eb..4222ba390 100644
--- a/program/lib/Roundcube/rcube_message_part.php
+++ b/program/lib/Roundcube/rcube_message_part.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_message_part.php                                |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
  | Copyright (C) 2011-2012, Kolab Systems AG                             |
@@ -14,14 +12,12 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   Class representing a message part                                   |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
  | Author: Aleksander Machniak <alec@alec.pl>                            |
  +-----------------------------------------------------------------------+
 */
 
-
 /**
  * Class representing a message part
  *
diff --git a/program/lib/Roundcube/rcube_mime.php b/program/lib/Roundcube/rcube_mime.php
index 4bb5b483f..eef8ca17c 100644
--- a/program/lib/Roundcube/rcube_mime.php
+++ b/program/lib/Roundcube/rcube_mime.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_mime.php                                        |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
  | Copyright (C) 2011-2012, Kolab Systems AG                             |
@@ -14,14 +12,12 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   MIME message parsing utilities                                      |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
  | Author: Aleksander Machniak <alec@alec.pl>                            |
  +-----------------------------------------------------------------------+
 */
 
-
 /**
  * Class for parsing MIME messages
  *
diff --git a/program/lib/Roundcube/rcube_output.php b/program/lib/Roundcube/rcube_output.php
index 4ef42f598..b8ae86cf6 100644
--- a/program/lib/Roundcube/rcube_output.php
+++ b/program/lib/Roundcube/rcube_output.php
@@ -2,17 +2,15 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_output.php                                      |
- |                                                                       |
  | This file is part of the Roundcube PHP suite                          |
  | Copyright (C) 2005-2012 The Roundcube Dev Team                        |
  |                                                                       |
  | Licensed under the GNU General Public License version 3 or            |
  | any later version with exceptions for skins & plugins.                |
  | See the README file for a full license statement.                     |
+ |                                                                       |
  | CONTENTS:                                                             |
  |   Abstract class for output generation                                |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
  | Author: Aleksander Machniak <alec@alec.pl>                            |
diff --git a/program/lib/Roundcube/rcube_plugin.php b/program/lib/Roundcube/rcube_plugin.php
index 5db85025d..06247d456 100644
--- a/program/lib/Roundcube/rcube_plugin.php
+++ b/program/lib/Roundcube/rcube_plugin.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_plugin.php                                      |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2008-2009, The Roundcube Dev Team                       |
  |                                                                       |
diff --git a/program/lib/Roundcube/rcube_plugin_api.php b/program/lib/Roundcube/rcube_plugin_api.php
index 47508a2ef..b4626441d 100644
--- a/program/lib/Roundcube/rcube_plugin_api.php
+++ b/program/lib/Roundcube/rcube_plugin_api.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_plugin_api.php                                  |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2008-2011, The Roundcube Dev Team                       |
  |                                                                       |
@@ -13,7 +11,6 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   Plugins repository                                                  |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
  +-----------------------------------------------------------------------+
diff --git a/program/lib/Roundcube/rcube_result_index.php b/program/lib/Roundcube/rcube_result_index.php
index 4d1ae13b6..5f592c54f 100644
--- a/program/lib/Roundcube/rcube_result_index.php
+++ b/program/lib/Roundcube/rcube_result_index.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_result_index.php                                |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2005-2011, The Roundcube Dev Team                       |
  | Copyright (C) 2011, Kolab Systems AG                                  |
@@ -14,14 +12,12 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   SORT/SEARCH/ESEARCH response handler                                |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
  | Author: Aleksander Machniak <alec@alec.pl>                            |
  +-----------------------------------------------------------------------+
 */
 
-
 /**
  * Class for accessing IMAP's SORT/SEARCH/ESEARCH result
  *
diff --git a/program/lib/Roundcube/rcube_result_set.php b/program/lib/Roundcube/rcube_result_set.php
index 456d1c9d6..1391e5e4b 100644
--- a/program/lib/Roundcube/rcube_result_set.php
+++ b/program/lib/Roundcube/rcube_result_set.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_result_set.php                                  |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2006-2011, The Roundcube Dev Team                       |
  |                                                                       |
@@ -13,13 +11,11 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   Class representing an address directory result set                  |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
  +-----------------------------------------------------------------------+
 */
 
-
 /**
  * Roundcube result set class.
  * Representing an address directory result set.
diff --git a/program/lib/Roundcube/rcube_result_thread.php b/program/lib/Roundcube/rcube_result_thread.php
index c609bdc39..7657550be 100644
--- a/program/lib/Roundcube/rcube_result_thread.php
+++ b/program/lib/Roundcube/rcube_result_thread.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_result_thread.php                               |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2005-2011, The Roundcube Dev Team                       |
  | Copyright (C) 2011, Kolab Systems AG                                  |
@@ -14,14 +12,12 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   THREAD response handler                                             |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
  | Author: Aleksander Machniak <alec@alec.pl>                            |
  +-----------------------------------------------------------------------+
 */
 
-
 /**
  * Class for accessing IMAP's THREAD result
  *
diff --git a/program/lib/Roundcube/rcube_session.php b/program/lib/Roundcube/rcube_session.php
index fdbf668ca..69eaabedc 100644
--- a/program/lib/Roundcube/rcube_session.php
+++ b/program/lib/Roundcube/rcube_session.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_session.php                                     |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
  | Copyright (C) 2011, Kolab Systems AG                                  |
@@ -14,7 +12,6 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   Provide database supported session management                       |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
  | Author: Aleksander Machniak <alec@alec.pl>                            |
diff --git a/program/lib/Roundcube/rcube_smtp.php b/program/lib/Roundcube/rcube_smtp.php
index 1a4958e2b..79ffcfbf8 100644
--- a/program/lib/Roundcube/rcube_smtp.php
+++ b/program/lib/Roundcube/rcube_smtp.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_smtp.php                                        |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
  |                                                                       |
@@ -13,7 +11,6 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   Provide SMTP functionality using socket connections                 |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
  +-----------------------------------------------------------------------+
diff --git a/program/lib/Roundcube/rcube_spellchecker.php b/program/lib/Roundcube/rcube_spellchecker.php
index fce2cac75..e9e1ceb0f 100644
--- a/program/lib/Roundcube/rcube_spellchecker.php
+++ b/program/lib/Roundcube/rcube_spellchecker.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_spellchecker.php                                |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2011, Kolab Systems AG                                  |
  | Copyright (C) 2008-2011, The Roundcube Dev Team                       |
@@ -14,14 +12,12 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   Spellchecking using different backends                              |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Aleksander Machniak <machniak@kolabsys.com>                   |
  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
  +-----------------------------------------------------------------------+
 */
 
-
 /**
  * Helper class for spellchecking with Googielspell and PSpell support.
  *
diff --git a/program/lib/Roundcube/rcube_storage.php b/program/lib/Roundcube/rcube_storage.php
index 763b9155e..65de2660c 100644
--- a/program/lib/Roundcube/rcube_storage.php
+++ b/program/lib/Roundcube/rcube_storage.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_storage.php                                     |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
  | Copyright (C) 2012, Kolab Systems AG                                  |
@@ -14,14 +12,12 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   Mail Storage Engine                                                 |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
  | Author: Aleksander Machniak <alec@alec.pl>                            |
  +-----------------------------------------------------------------------+
 */
 
-
 /**
  * Abstract class for accessing mail messages storage server
  *
diff --git a/program/lib/Roundcube/rcube_string_replacer.php b/program/lib/Roundcube/rcube_string_replacer.php
index 584b9f68c..68288f54c 100644
--- a/program/lib/Roundcube/rcube_string_replacer.php
+++ b/program/lib/Roundcube/rcube_string_replacer.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_string_replacer.php                             |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2009-2012, The Roundcube Dev Team                       |
  |                                                                       |
@@ -13,13 +11,11 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   Handle string replacements based on preg_replace_callback           |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
  +-----------------------------------------------------------------------+
 */
 
-
 /**
  * Helper class for string replacements based on preg_replace_callback
  *
diff --git a/program/lib/Roundcube/rcube_user.php b/program/lib/Roundcube/rcube_user.php
index f6b77f5e1..505b190d1 100644
--- a/program/lib/Roundcube/rcube_user.php
+++ b/program/lib/Roundcube/rcube_user.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_user.inc                                        |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
  |                                                                       |
@@ -14,14 +12,12 @@
  | PURPOSE:                                                              |
  |   This class represents a system user linked and provides access      |
  |   to the related database records.                                    |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
  | Author: Aleksander Machniak <alec@alec.pl>                            |
  +-----------------------------------------------------------------------+
 */
 
-
 /**
  * Class representing a system user
  *
diff --git a/program/lib/Roundcube/rcube_utils.php b/program/lib/Roundcube/rcube_utils.php
index 500f2c371..4b687111e 100644
--- a/program/lib/Roundcube/rcube_utils.php
+++ b/program/lib/Roundcube/rcube_utils.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_utils.php                                       |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2008-2012, The Roundcube Dev Team                       |
  | Copyright (C) 2011-2012, Kolab Systems AG                             |
@@ -20,7 +18,6 @@
  +-----------------------------------------------------------------------+
 */
 
-
 /**
  * Utility class providing common functions
  *
diff --git a/program/lib/Roundcube/rcube_vcard.php b/program/lib/Roundcube/rcube_vcard.php
index 45ee601e5..a5c5ccec8 100644
--- a/program/lib/Roundcube/rcube_vcard.php
+++ b/program/lib/Roundcube/rcube_vcard.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_vcard.php                                       |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2008-2012, The Roundcube Dev Team                       |
  |                                                                       |
@@ -19,7 +17,6 @@
  +-----------------------------------------------------------------------+
 */
 
-
 /**
  * Logical representation of a vcard-based address record
  * Provides functions to parse and export vCard data format
-- 
cgit v1.2.3


From 8cacecb2ff8b2c819f573bbd47f6bc8171d26ee8 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Tue, 18 Dec 2012 09:30:15 +0100
Subject: CS fixes

---
 program/lib/Roundcube/rcube_vcard.php | 1463 +++++++++++++++++----------------
 1 file changed, 758 insertions(+), 705 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_vcard.php b/program/lib/Roundcube/rcube_vcard.php
index a5c5ccec8..e6fa5b248 100644
--- a/program/lib/Roundcube/rcube_vcard.php
+++ b/program/lib/Roundcube/rcube_vcard.php
@@ -26,765 +26,818 @@
  */
 class rcube_vcard
 {
-  private static $values_decoded = false;
-  private $raw = array(
-    'FN' => array(),
-    'N' => array(array('','','','','')),
-  );
-  private static $fieldmap = array(
-    'phone'    => 'TEL',
-    'birthday' => 'BDAY',
-    'website'  => 'URL',
-    'notes'    => 'NOTE',
-    'email'    => 'EMAIL',
-    'address'  => 'ADR',
-    'jobtitle' => 'TITLE',
-    'department'  => 'X-DEPARTMENT',
-    'gender'      => 'X-GENDER',
-    'maidenname'  => 'X-MAIDENNAME',
-    'anniversary' => 'X-ANNIVERSARY',
-    'assistant'   => 'X-ASSISTANT',
-    'manager'     => 'X-MANAGER',
-    'spouse'      => 'X-SPOUSE',
-    'edit'        => 'X-AB-EDIT',
-  );
-  private $typemap = array('IPHONE' => 'mobile', 'CELL' => 'mobile', 'WORK,FAX' => 'workfax');
-  private $phonetypemap = array('HOME1' => 'HOME', 'BUSINESS1' => 'WORK', 'BUSINESS2' => 'WORK2', 'BUSINESSFAX' => 'WORK,FAX', 'MOBILE' => 'CELL');
-  private $addresstypemap = array('BUSINESS' => 'WORK');
-  private $immap = array('X-JABBER' => 'jabber', 'X-ICQ' => 'icq', 'X-MSN' => 'msn', 'X-AIM' => 'aim', 'X-YAHOO' => 'yahoo', 'X-SKYPE' => 'skype', 'X-SKYPE-USERNAME' => 'skype');
-
-  public $business = false;
-  public $displayname;
-  public $surname;
-  public $firstname;
-  public $middlename;
-  public $nickname;
-  public $organization;
-  public $email = array();
-
-  public static $eol = "\r\n";
-
-  /**
-   * Constructor
-   */
-  public function __construct($vcard = null, $charset = RCUBE_CHARSET, $detect = false, $fieldmap = array())
-  {
-    if (!empty($fielmap))
-      $this->extend_fieldmap($fieldmap);
-
-    if (!empty($vcard))
-      $this->load($vcard, $charset, $detect);
-  }
-
-
-  /**
-   * Load record from (internal, unfolded) vcard 3.0 format
-   *
-   * @param string vCard string to parse
-   * @param string Charset of string values
-   * @param boolean True if loading a 'foreign' vcard and extra heuristics for charset detection is required
-   */
-  public function load($vcard, $charset = RCUBE_CHARSET, $detect = false)
-  {
-    self::$values_decoded = false;
-    $this->raw = self::vcard_decode($vcard);
-
-    // resolve charset parameters
-    if ($charset == null) {
-      $this->raw = self::charset_convert($this->raw);
+    private static $values_decoded = false;
+    private $raw = array(
+        'FN' => array(),
+        'N'  => array(array('','','','','')),
+    );
+    private static $fieldmap = array(
+        'phone'    => 'TEL',
+        'birthday' => 'BDAY',
+        'website'  => 'URL',
+        'notes'    => 'NOTE',
+        'email'    => 'EMAIL',
+        'address'  => 'ADR',
+        'jobtitle' => 'TITLE',
+        'department'  => 'X-DEPARTMENT',
+        'gender'      => 'X-GENDER',
+        'maidenname'  => 'X-MAIDENNAME',
+        'anniversary' => 'X-ANNIVERSARY',
+        'assistant'   => 'X-ASSISTANT',
+        'manager'     => 'X-MANAGER',
+        'spouse'      => 'X-SPOUSE',
+        'edit'        => 'X-AB-EDIT',
+    );
+    private $typemap = array(
+        'IPHONE'   => 'mobile',
+        'CELL'     => 'mobile',
+        'WORK,FAX' => 'workfax',
+    );
+    private $phonetypemap = array(
+        'HOME1'       => 'HOME',
+        'BUSINESS1'   => 'WORK',
+        'BUSINESS2'   => 'WORK2',
+        'BUSINESSFAX' => 'WORK,FAX',
+        'MOBILE'      => 'CELL',
+    );
+    private $addresstypemap = array(
+        'BUSINESS' => 'WORK',
+    );
+    private $immap = array(
+        'X-JABBER' => 'jabber',
+        'X-ICQ'    => 'icq',
+        'X-MSN'    => 'msn',
+        'X-AIM'    => 'aim',
+        'X-YAHOO'  => 'yahoo',
+        'X-SKYPE'  => 'skype',
+        'X-SKYPE-USERNAME' => 'skype',
+    );
+
+    public $business = false;
+    public $displayname;
+    public $surname;
+    public $firstname;
+    public $middlename;
+    public $nickname;
+    public $organization;
+    public $email = array();
+
+    public static $eol = "\r\n";
+
+
+    /**
+     * Constructor
+     */
+    public function __construct($vcard = null, $charset = RCUBE_CHARSET, $detect = false, $fieldmap = array())
+    {
+        if (!empty($fielmap)) {
+            $this->extend_fieldmap($fieldmap);
+        }
+
+        if (!empty($vcard)) {
+            $this->load($vcard, $charset, $detect);
+        }
     }
-    // vcard has encoded values and charset should be detected
-    else if ($detect && self::$values_decoded &&
-      ($detected_charset = self::detect_encoding(self::vcard_encode($this->raw))) && $detected_charset != RCUBE_CHARSET) {
-        $this->raw = self::charset_convert($this->raw, $detected_charset);
+
+    /**
+     * Load record from (internal, unfolded) vcard 3.0 format
+     *
+     * @param string vCard string to parse
+     * @param string Charset of string values
+     * @param boolean True if loading a 'foreign' vcard and extra heuristics for charset detection is required
+     */
+    public function load($vcard, $charset = RCUBE_CHARSET, $detect = false)
+    {
+        self::$values_decoded = false;
+        $this->raw = self::vcard_decode($vcard);
+
+        // resolve charset parameters
+        if ($charset == null) {
+            $this->raw = self::charset_convert($this->raw);
+        }
+        // vcard has encoded values and charset should be detected
+        else if ($detect && self::$values_decoded
+            && ($detected_charset = self::detect_encoding(self::vcard_encode($this->raw)))
+            && $detected_charset != RCUBE_CHARSET
+        ) {
+            $this->raw = self::charset_convert($this->raw, $detected_charset);
+        }
+
+        // consider FN empty if the same as the primary e-mail address
+        if ($this->raw['FN'][0][0] == $this->raw['EMAIL'][0][0]) {
+            $this->raw['FN'][0][0] = '';
+        }
+
+        // find well-known address fields
+        $this->displayname  = $this->raw['FN'][0][0];
+        $this->surname      = $this->raw['N'][0][0];
+        $this->firstname    = $this->raw['N'][0][1];
+        $this->middlename   = $this->raw['N'][0][2];
+        $this->nickname     = $this->raw['NICKNAME'][0][0];
+        $this->organization = $this->raw['ORG'][0][0];
+        $this->business     = ($this->raw['X-ABSHOWAS'][0][0] == 'COMPANY') || (join('', (array)$this->raw['N'][0]) == '' && !empty($this->organization));
+
+        foreach ((array)$this->raw['EMAIL'] as $i => $raw_email) {
+            $this->email[$i] = is_array($raw_email) ? $raw_email[0] : $raw_email;
+        }
+
+        // make the pref e-mail address the first entry in $this->email
+        $pref_index = $this->get_type_index('EMAIL', 'pref');
+        if ($pref_index > 0) {
+            $tmp = $this->email[0];
+            $this->email[0] = $this->email[$pref_index];
+            $this->email[$pref_index] = $tmp;
+        }
     }
 
-    // consider FN empty if the same as the primary e-mail address
-    if ($this->raw['FN'][0][0] == $this->raw['EMAIL'][0][0])
-      $this->raw['FN'][0][0] = '';
-
-    // find well-known address fields
-    $this->displayname = $this->raw['FN'][0][0];
-    $this->surname = $this->raw['N'][0][0];
-    $this->firstname = $this->raw['N'][0][1];
-    $this->middlename = $this->raw['N'][0][2];
-    $this->nickname = $this->raw['NICKNAME'][0][0];
-    $this->organization = $this->raw['ORG'][0][0];
-    $this->business = ($this->raw['X-ABSHOWAS'][0][0] == 'COMPANY') || (join('', (array)$this->raw['N'][0]) == '' && !empty($this->organization));
-
-    foreach ((array)$this->raw['EMAIL'] as $i => $raw_email)
-      $this->email[$i] = is_array($raw_email) ? $raw_email[0] : $raw_email;
-
-    // make the pref e-mail address the first entry in $this->email
-    $pref_index = $this->get_type_index('EMAIL', 'pref');
-    if ($pref_index > 0) {
-      $tmp = $this->email[0];
-      $this->email[0] = $this->email[$pref_index];
-      $this->email[$pref_index] = $tmp;
+    /**
+     * Return vCard data as associative array to be unsed in Roundcube address books
+     *
+     * @return array Hash array with key-value pairs
+     */
+    public function get_assoc()
+    {
+        $out     = array('name' => $this->displayname);
+        $typemap = $this->typemap;
+
+        // copy name fields to output array
+        foreach (array('firstname','surname','middlename','nickname','organization') as $col) {
+            if (strlen($this->$col)) {
+                $out[$col] = $this->$col;
+            }
+        }
+
+        if ($this->raw['N'][0][3])
+            $out['prefix'] = $this->raw['N'][0][3];
+        if ($this->raw['N'][0][4])
+            $out['suffix'] = $this->raw['N'][0][4];
+
+        // convert from raw vcard data into associative data for Roundcube
+        foreach (array_flip(self::$fieldmap) as $tag => $col) {
+            foreach ((array)$this->raw[$tag] as $i => $raw) {
+                if (is_array($raw)) {
+                    $k       = -1;
+                    $key     = $col;
+                    $subtype = '';
+
+                    if (!empty($raw['type'])) {
+                        $combined = join(',', self::array_filter((array)$raw['type'], 'internet,pref', true));
+                        $combined = strtoupper($combined);
+
+                        if ($typemap[$combined]) {
+                            $subtype = $typemap[$combined];
+                        }
+                        else if ($typemap[$raw['type'][++$k]]) {
+                            $subtype = $typemap[$raw['type'][$k]];
+                        }
+                        else {
+                            $subtype = strtolower($raw['type'][$k]);
+                        }
+
+                        while ($k < count($raw['type']) && ($subtype == 'internet' || $subtype == 'pref')) {
+                            $subtype = $typemap[$raw['type'][++$k]] ? $typemap[$raw['type'][$k]] : strtolower($raw['type'][$k]);
+                        }
+                    }
+
+                    // read vcard 2.1 subtype
+                    if (!$subtype) {
+                        foreach ($raw as $k => $v) {
+                            if (!is_numeric($k) && $v === true && ($k = strtolower($k))
+                                && !in_array($k, array('pref','internet','voice','base64'))
+                            ) {
+                                $k_uc    = strtoupper($k);
+                                $subtype = $typemap[$k_uc] ? $typemap[$k_uc] : $k;
+                                break;
+                            }
+                        }
+                    }
+
+                    // force subtype if none set
+                    if (!$subtype && preg_match('/^(email|phone|address|website)/', $key)) {
+                        $subtype = 'other';
+                    }
+
+                    if ($subtype) {
+                        $key .= ':' . $subtype;
+                    }
+
+                    // split ADR values into assoc array
+                    if ($tag == 'ADR') {
+                        list(,, $value['street'], $value['locality'], $value['region'], $value['zipcode'], $value['country']) = $raw;
+                        $out[$key][] = $value;
+                    }
+                    else {
+                        $out[$key][] = $raw[0];
+                    }
+                }
+                else {
+                    $out[$col][] = $raw;
+                }
+            }
+        }
+
+        // handle special IM fields as used by Apple
+        foreach ($this->immap as $tag => $type) {
+            foreach ((array)$this->raw[$tag] as $i => $raw) {
+                $out['im:'.$type][] = $raw[0];
+            }
+        }
+
+        // copy photo data
+        if ($this->raw['PHOTO']) {
+            $out['photo'] = $this->raw['PHOTO'][0][0];
+        }
+
+        return $out;
     }
-  }
-
-
-  /**
-   * Return vCard data as associative array to be unsed in Roundcube address books
-   *
-   * @return array Hash array with key-value pairs
-   */
-  public function get_assoc()
-  {
-    $out = array('name' => $this->displayname);
-    $typemap = $this->typemap;
-
-    // copy name fields to output array
-    foreach (array('firstname','surname','middlename','nickname','organization') as $col) {
-      if (strlen($this->$col))
-        $out[$col] = $this->$col;
+
+    /**
+     * Convert the data structure into a vcard 3.0 string
+     */
+    public function export($folded = true)
+    {
+        $vcard = self::vcard_encode($this->raw);
+        return $folded ? self::rfc2425_fold($vcard) : $vcard;
     }
 
-    if ($this->raw['N'][0][3])
-      $out['prefix'] = $this->raw['N'][0][3];
-    if ($this->raw['N'][0][4])
-      $out['suffix'] = $this->raw['N'][0][4];
-
-    // convert from raw vcard data into associative data for Roundcube
-    foreach (array_flip(self::$fieldmap) as $tag => $col) {
-      foreach ((array)$this->raw[$tag] as $i => $raw) {
-        if (is_array($raw)) {
-          $k = -1;
-          $key = $col;
-          $subtype = '';
-
-          if (!empty($raw['type'])) {
-            $combined = join(',', self::array_filter((array)$raw['type'], 'internet,pref', true));
-            $combined = strtoupper($combined);
-
-            if ($typemap[$combined]) {
-                $subtype = $typemap[$combined];
-            }
-            else if ($typemap[$raw['type'][++$k]]) {
-                $subtype = $typemap[$raw['type'][$k]];
+    /**
+     * Clear the given fields in the loaded vcard data
+     *
+     * @param array List of field names to be reset
+     */
+    public function reset($fields = null)
+    {
+        if (!$fields) {
+            $fields = array_merge(array_values(self::$fieldmap), array_keys($this->immap),
+                array('FN','N','ORG','NICKNAME','EMAIL','ADR','BDAY'));
+        }
+
+        foreach ($fields as $f) {
+            unset($this->raw[$f]);
+        }
+
+        if (!$this->raw['N']) {
+            $this->raw['N'] = array(array('','','','',''));
+        }
+        if (!$this->raw['FN']) {
+            $this->raw['FN'] = array();
+        }
+
+        $this->email = array();
+    }
+
+    /**
+     * Setter for address record fields
+     *
+     * @param string Field name
+     * @param string Field value
+     * @param string Type/section name
+     */
+    public function set($field, $value, $type = 'HOME')
+    {
+        $field   = strtolower($field);
+        $type_uc = strtoupper($type);
+
+        switch ($field) {
+        case 'name':
+        case 'displayname':
+            $this->raw['FN'][0][0] = $this->displayname = $value;
+            break;
+
+        case 'surname':
+            $this->raw['N'][0][0] = $this->surname = $value;
+            break;
+
+        case 'firstname':
+            $this->raw['N'][0][1] = $this->firstname = $value;
+            break;
+
+        case 'middlename':
+            $this->raw['N'][0][2] = $this->middlename = $value;
+            break;
+
+        case 'prefix':
+            $this->raw['N'][0][3] = $value;
+            break;
+
+        case 'suffix':
+            $this->raw['N'][0][4] = $value;
+            break;
+
+        case 'nickname':
+            $this->raw['NICKNAME'][0][0] = $this->nickname = $value;
+            break;
+
+        case 'organization':
+            $this->raw['ORG'][0][0] = $this->organization = $value;
+            break;
+
+        case 'photo':
+            if (strpos($value, 'http:') === 0) {
+                // TODO: fetch file from URL and save it locally?
+                $this->raw['PHOTO'][0] = array(0 => $value, 'url' => true);
             }
             else {
-                $subtype = strtolower($raw['type'][$k]);
+                $this->raw['PHOTO'][0] = array(0 => $value, 'base64' => (bool) preg_match('![^a-z0-9/=+-]!i', $value));
+            }
+            break;
+
+        case 'email':
+            $this->raw['EMAIL'][] = array(0 => $value, 'type' => array_filter(array('INTERNET', $type_uc)));
+            $this->email[] = $value;
+            break;
+
+        case 'im':
+            // save IM subtypes into extension fields
+            $typemap = array_flip($this->immap);
+            if ($field = $typemap[strtolower($type)]) {
+                $this->raw[$field][] = array(0 => $value);
             }
+            break;
 
-            while ($k < count($raw['type']) && ($subtype == 'internet' || $subtype == 'pref'))
-              $subtype = $typemap[$raw['type'][++$k]] ? $typemap[$raw['type'][$k]] : strtolower($raw['type'][$k]);
-          }
-
-          // read vcard 2.1 subtype
-          if (!$subtype) {
-            foreach ($raw as $k => $v) {
-              if (!is_numeric($k) && $v === true && ($k = strtolower($k))
-                && !in_array($k, array('pref','internet','voice','base64'))
-              ) {
-                $k_uc    = strtoupper($k);
-                $subtype = $typemap[$k_uc] ? $typemap[$k_uc] : $k;
-                break;
-              }
+        case 'birthday':
+        case 'anniversary':
+            if (($val = rcube_utils::strtotime($value)) && ($fn = self::$fieldmap[$field])) {
+                $this->raw[$fn][] = array(0 => date('Y-m-d', $val), 'value' => array('date'));
             }
-          }
+            break;
 
-          // force subtype if none set
-          if (!$subtype && preg_match('/^(email|phone|address|website)/', $key))
-            $subtype = 'other';
+        case 'address':
+            if ($this->addresstypemap[$type_uc]) {
+                $type = $this->addresstypemap[$type_uc];
+            }
 
-          if ($subtype)
-            $key .= ':' . $subtype;
+            $value = $value[0] ? $value : array('', '', $value['street'], $value['locality'], $value['region'], $value['zipcode'], $value['country']);
 
-          // split ADR values into assoc array
-          if ($tag == 'ADR') {
-            list(,, $value['street'], $value['locality'], $value['region'], $value['zipcode'], $value['country']) = $raw;
-            $out[$key][] = $value;
-          }
-          else
-            $out[$key][] = $raw[0];
-        }
-        else {
-          $out[$col][] = $raw;
+            // fall through if not empty
+            if (!strlen(join('', $value))) {
+                break;
+            }
+
+        default:
+            if ($field == 'phone' && $this->phonetypemap[$type_uc]) {
+                $type = $this->phonetypemap[$type_uc];
+             }
+
+            if (($tag = self::$fieldmap[$field]) && (is_array($value) || strlen($value))) {
+                $index = count($this->raw[$tag]);
+                $this->raw[$tag][$index] = (array)$value;
+                if ($type) {
+                    $typemap = array_flip($this->typemap);
+                    $this->raw[$tag][$index]['type'] = explode(',', ($typemap[$type_uc] ? $typemap[$type_uc] : $type));
+                }
+            }
+            break;
         }
-      }
     }
 
-    // handle special IM fields as used by Apple
-    foreach ($this->immap as $tag => $type) {
-      foreach ((array)$this->raw[$tag] as $i => $raw) {
-        $out['im:'.$type][] = $raw[0];
-      }
+    /**
+     * Setter for individual vcard properties
+     *
+     * @param string VCard tag name
+     * @param array Value-set of this vcard property
+     * @param boolean Set to true if the value-set should be appended instead of replacing any existing value-set
+     */
+    public function set_raw($tag, $value, $append = false)
+    {
+        $index = $append ? count($this->raw[$tag]) : 0;
+        $this->raw[$tag][$index] = (array)$value;
     }
 
-    // copy photo data
-    if ($this->raw['PHOTO'])
-      $out['photo'] = $this->raw['PHOTO'][0][0];
-
-    return $out;
-  }
-
-
-  /**
-   * Convert the data structure into a vcard 3.0 string
-   */
-  public function export($folded = true)
-  {
-    $vcard = self::vcard_encode($this->raw);
-    return $folded ? self::rfc2425_fold($vcard) : $vcard;
-  }
-
-
-  /**
-   * Clear the given fields in the loaded vcard data
-   *
-   * @param array List of field names to be reset
-   */
-  public function reset($fields = null)
-  {
-    if (!$fields)
-      $fields = array_merge(array_values(self::$fieldmap), array_keys($this->immap), array('FN','N','ORG','NICKNAME','EMAIL','ADR','BDAY'));
-
-    foreach ($fields as $f)
-      unset($this->raw[$f]);
-
-    if (!$this->raw['N'])
-      $this->raw['N'] = array(array('','','','',''));
-    if (!$this->raw['FN'])
-      $this->raw['FN'] = array();
-
-    $this->email = array();
-  }
-
-
-  /**
-   * Setter for address record fields
-   *
-   * @param string Field name
-   * @param string Field value
-   * @param string Type/section name
-   */
-  public function set($field, $value, $type = 'HOME')
-  {
-    $field   = strtolower($field);
-    $type_uc = strtoupper($type);
-
-    switch ($field) {
-      case 'name':
-      case 'displayname':
-        $this->raw['FN'][0][0] = $this->displayname = $value;
-        break;
-
-      case 'surname':
-        $this->raw['N'][0][0] = $this->surname = $value;
-        break;
-
-      case 'firstname':
-        $this->raw['N'][0][1] = $this->firstname = $value;
-        break;
-
-      case 'middlename':
-        $this->raw['N'][0][2] = $this->middlename = $value;
-        break;
-
-      case 'prefix':
-        $this->raw['N'][0][3] = $value;
-        break;
-
-      case 'suffix':
-        $this->raw['N'][0][4] = $value;
-        break;
-
-      case 'nickname':
-        $this->raw['NICKNAME'][0][0] = $this->nickname = $value;
-        break;
-
-      case 'organization':
-        $this->raw['ORG'][0][0] = $this->organization = $value;
-        break;
-
-      case 'photo':
-        if (strpos($value, 'http:') === 0) {
-            // TODO: fetch file from URL and save it locally?
-            $this->raw['PHOTO'][0] = array(0 => $value, 'url' => true);
-        }
-        else {
-            $this->raw['PHOTO'][0] = array(0 => $value, 'base64' => (bool) preg_match('![^a-z0-9/=+-]!i', $value));
-        }
-        break;
-
-      case 'email':
-        $this->raw['EMAIL'][] = array(0 => $value, 'type' => array_filter(array('INTERNET', $type_uc)));
-        $this->email[] = $value;
-        break;
-
-      case 'im':
-        // save IM subtypes into extension fields
-        $typemap = array_flip($this->immap);
-        if ($field = $typemap[strtolower($type)])
-          $this->raw[$field][] = array(0 => $value);
-        break;
-
-      case 'birthday':
-      case 'anniversary':
-        if (($val = rcube_utils::strtotime($value)) && ($fn = self::$fieldmap[$field]))
-          $this->raw[$fn][] = array(0 => date('Y-m-d', $val), 'value' => array('date'));
-        break;
-
-      case 'address':
-        if ($this->addresstypemap[$type_uc])
-          $type = $this->addresstypemap[$type_uc];
-
-        $value = $value[0] ? $value : array('', '', $value['street'], $value['locality'], $value['region'], $value['zipcode'], $value['country']);
-
-        // fall through if not empty
-        if (!strlen(join('', $value)))
-          break;
-
-      default:
-        if ($field == 'phone' && $this->phonetypemap[$type_uc])
-          $type = $this->phonetypemap[$type_uc];
-
-        if (($tag = self::$fieldmap[$field]) && (is_array($value) || strlen($value))) {
-          $index = count($this->raw[$tag]);
-          $this->raw[$tag][$index] = (array)$value;
-          if ($type) {
-            $typemap = array_flip($this->typemap);
-            $this->raw[$tag][$index]['type'] = explode(',', ($typemap[$type_uc] ? $typemap[$type_uc] : $type));
-          }
-        }
-        break;
-    }
-  }
-
-  /**
-   * Setter for individual vcard properties
-   *
-   * @param string VCard tag name
-   * @param array Value-set of this vcard property
-   * @param boolean Set to true if the value-set should be appended instead of replacing any existing value-set
-   */
-  public function set_raw($tag, $value, $append = false)
-  {
-    $index = $append ? count($this->raw[$tag]) : 0;
-    $this->raw[$tag][$index] = (array)$value;
-  }
-
-
-  /**
-   * Find index with the '$type' attribute
-   *
-   * @param string Field name
-   * @return int Field index having $type set
-   */
-  private function get_type_index($field, $type = 'pref')
-  {
-    $result = 0;
-    if ($this->raw[$field]) {
-      foreach ($this->raw[$field] as $i => $data) {
-        if (is_array($data['type']) && in_array_nocase('pref', $data['type']))
-          $result = $i;
-      }
+    /**
+     * Find index with the '$type' attribute
+     *
+     * @param string Field name
+     * @return int Field index having $type set
+     */
+    private function get_type_index($field, $type = 'pref')
+    {
+        $result = 0;
+        if ($this->raw[$field]) {
+            foreach ($this->raw[$field] as $i => $data) {
+                if (is_array($data['type']) && in_array_nocase('pref', $data['type'])) {
+                    $result = $i;
+                }
+            }
+        }
+
+        return $result;
     }
 
-    return $result;
-  }
-
-
-  /**
-   * Convert a whole vcard (array) to UTF-8.
-   * If $force_charset is null, each member value that has a charset parameter will be converted
-   */
-  private static function charset_convert($card, $force_charset = null)
-  {
-    foreach ($card as $key => $node) {
-      foreach ($node as $i => $subnode) {
-        if (is_array($subnode) && (($charset = $force_charset) || ($subnode['charset'] && ($charset = $subnode['charset'][0])))) {
-          foreach ($subnode as $j => $value) {
-            if (is_numeric($j) && is_string($value))
-              $card[$key][$i][$j] = rcube_charset::convert($value, $charset);
-          }
-          unset($card[$key][$i]['charset']);
-        }
-      }
+    /**
+     * Convert a whole vcard (array) to UTF-8.
+     * If $force_charset is null, each member value that has a charset parameter will be converted
+     */
+    private static function charset_convert($card, $force_charset = null)
+    {
+        foreach ($card as $key => $node) {
+            foreach ($node as $i => $subnode) {
+                if (is_array($subnode) && (($charset = $force_charset) || ($subnode['charset'] && ($charset = $subnode['charset'][0])))) {
+                    foreach ($subnode as $j => $value) {
+                        if (is_numeric($j) && is_string($value)) {
+                            $card[$key][$i][$j] = rcube_charset::convert($value, $charset);
+                        }
+                    }
+                    unset($card[$key][$i]['charset']);
+                }
+            }
+        }
+
+        return $card;
     }
 
-    return $card;
-  }
-
-
-  /**
-   * Extends fieldmap definition
-   */
-  public function extend_fieldmap($map)
-  {
-    if (is_array($map))
-      self::$fieldmap = array_merge($map, self::$fieldmap);
-  }
-
-
-  /**
-   * Factory method to import a vcard file
-   *
-   * @param string vCard file content
-   * @return array List of rcube_vcard objects
-   */
-  public static function import($data)
-  {
-    $out = array();
-
-    // check if charsets are specified (usually vcard version < 3.0 but this is not reliable)
-    if (preg_match('/charset=/i', substr($data, 0, 2048)))
-      $charset = null;
-    // detect charset and convert to utf-8
-    else if (($charset = self::detect_encoding($data)) && $charset != RCUBE_CHARSET) {
-      $data = rcube_charset::convert($data, $charset);
-      $data = preg_replace(array('/^[\xFE\xFF]{2}/', '/^\xEF\xBB\xBF/', '/^\x00+/'), '', $data); // also remove BOM
-      $charset = RCUBE_CHARSET;
+    /**
+     * Extends fieldmap definition
+     */
+    public function extend_fieldmap($map)
+    {
+        if (is_array($map)) {
+            self::$fieldmap = array_merge($map, self::$fieldmap);
+        }
     }
 
-    $vcard_block = '';
-    $in_vcard_block = false;
+    /**
+     * Factory method to import a vcard file
+     *
+     * @param string vCard file content
+     *
+     * @return array List of rcube_vcard objects
+     */
+    public static function import($data)
+    {
+        $out = array();
+
+        // check if charsets are specified (usually vcard version < 3.0 but this is not reliable)
+        if (preg_match('/charset=/i', substr($data, 0, 2048))) {
+            $charset = null;
+        }
+        // detect charset and convert to utf-8
+        else if (($charset = self::detect_encoding($data)) && $charset != RCUBE_CHARSET) {
+            $data = rcube_charset::convert($data, $charset);
+            $data = preg_replace(array('/^[\xFE\xFF]{2}/', '/^\xEF\xBB\xBF/', '/^\x00+/'), '', $data); // also remove BOM
+            $charset = RCUBE_CHARSET;
+        }
 
-    foreach (preg_split("/[\r\n]+/", $data) as $i => $line) {
-      if ($in_vcard_block && !empty($line))
-        $vcard_block .= $line . "\n";
+        $vcard_block    = '';
+        $in_vcard_block = false;
 
-      $line = trim($line);
+        foreach (preg_split("/[\r\n]+/", $data) as $i => $line) {
+            if ($in_vcard_block && !empty($line)) {
+                $vcard_block .= $line . "\n";
+            }
 
-      if (preg_match('/^END:VCARD$/i', $line)) {
-        // parse vcard
-        $obj = new rcube_vcard(self::cleanup($vcard_block), $charset, true, self::$fieldmap);
-        if (!empty($obj->displayname) || !empty($obj->email))
-          $out[] = $obj;
+            $line = trim($line);
 
-        $in_vcard_block = false;
-      }
-      else if (preg_match('/^BEGIN:VCARD$/i', $line)) {
-        $vcard_block = $line . "\n";
-        $in_vcard_block = true;
-      }
+            if (preg_match('/^END:VCARD$/i', $line)) {
+                // parse vcard
+                $obj = new rcube_vcard(self::cleanup($vcard_block), $charset, true, self::$fieldmap);
+                if (!empty($obj->displayname) || !empty($obj->email)) {
+                    $out[] = $obj;
+                }
+
+                $in_vcard_block = false;
+            }
+            else if (preg_match('/^BEGIN:VCARD$/i', $line)) {
+                $vcard_block    = $line . "\n";
+                $in_vcard_block = true;
+            }
+        }
+
+        return $out;
     }
 
-    return $out;
-  }
-
-
-  /**
-   * Normalize vcard data for better parsing
-   *
-   * @param string vCard block
-   * @return string Cleaned vcard block
-   */
-  private static function cleanup($vcard)
-  {
-    // Convert special types (like Skype) to normal type='skype' classes with this simple regex ;)
-    $vcard = preg_replace(
-      '/item(\d+)\.(TEL|EMAIL|URL)([^:]*?):(.*?)item\1.X-ABLabel:(?:_\$!<)?([\w-() ]*)(?:>!\$_)?./s',
-      '\2;type=\5\3:\4',
-      $vcard);
-
-    // convert Apple X-ABRELATEDNAMES into X-* fields for better compatibility
-    $vcard = preg_replace_callback(
-      '/item(\d+)\.(X-ABRELATEDNAMES)([^:]*?):(.*?)item\1.X-ABLabel:(?:_\$!<)?([\w-() ]*)(?:>!\$_)?./s',
-      array('self', 'x_abrelatednames_callback'),
-      $vcard);
-
-    // Remove cruft like item1.X-AB*, item1.ADR instead of ADR, and empty lines
-    $vcard = preg_replace(array('/^item\d*\.X-AB.*$/m', '/^item\d*\./m', "/\n+/"), array('', '', "\n"), $vcard);
-
-    // convert X-WAB-GENDER to X-GENDER
-    if (preg_match('/X-WAB-GENDER:(\d)/', $vcard, $matches)) {
-      $value = $matches[1] == '2' ? 'male' : 'female';
-      $vcard = preg_replace('/X-WAB-GENDER:\d/', 'X-GENDER:' . $value, $vcard);
+    /**
+     * Normalize vcard data for better parsing
+     *
+     * @param string vCard block
+     *
+     * @return string Cleaned vcard block
+     */
+    private static function cleanup($vcard)
+    {
+        // Convert special types (like Skype) to normal type='skype' classes with this simple regex ;)
+        $vcard = preg_replace(
+            '/item(\d+)\.(TEL|EMAIL|URL)([^:]*?):(.*?)item\1.X-ABLabel:(?:_\$!<)?([\w-() ]*)(?:>!\$_)?./s',
+            '\2;type=\5\3:\4',
+            $vcard);
+
+        // convert Apple X-ABRELATEDNAMES into X-* fields for better compatibility
+        $vcard = preg_replace_callback(
+            '/item(\d+)\.(X-ABRELATEDNAMES)([^:]*?):(.*?)item\1.X-ABLabel:(?:_\$!<)?([\w-() ]*)(?:>!\$_)?./s',
+            array('self', 'x_abrelatednames_callback'),
+            $vcard);
+
+        // Remove cruft like item1.X-AB*, item1.ADR instead of ADR, and empty lines
+        $vcard = preg_replace(array('/^item\d*\.X-AB.*$/m', '/^item\d*\./m', "/\n+/"), array('', '', "\n"), $vcard);
+
+        // convert X-WAB-GENDER to X-GENDER
+        if (preg_match('/X-WAB-GENDER:(\d)/', $vcard, $matches)) {
+            $value = $matches[1] == '2' ? 'male' : 'female';
+            $vcard = preg_replace('/X-WAB-GENDER:\d/', 'X-GENDER:' . $value, $vcard);
+        }
+
+        // if N doesn't have any semicolons, add some 
+        $vcard = preg_replace('/^(N:[^;\R]*)$/m', '\1;;;;', $vcard);
+
+        return $vcard;
     }
 
-    // if N doesn't have any semicolons, add some 
-    $vcard = preg_replace('/^(N:[^;\R]*)$/m', '\1;;;;', $vcard);
-
-    return $vcard;
-  }
-
-  private static function x_abrelatednames_callback($matches)
-  {
-    return 'X-' . strtoupper($matches[5]) . $matches[3] . ':'. $matches[4];
-  }
-
-  private static function rfc2425_fold_callback($matches)
-  {
-    // chunk_split string and avoid lines breaking multibyte characters
-    $c = 71;
-    $out .= substr($matches[1], 0, $c);
-    for ($n = $c; $c < strlen($matches[1]); $c++) {
-      // break if length > 75 or mutlibyte character starts after position 71
-      if ($n > 75 || ($n > 71 && ord($matches[1][$c]) >> 6 == 3)) {
-        $out .= "\r\n ";
-        $n = 0;
-      }
-      $out .= $matches[1][$c];
-      $n++;
+    private static function x_abrelatednames_callback($matches)
+    {
+        return 'X-' . strtoupper($matches[5]) . $matches[3] . ':'. $matches[4];
     }
 
-    return $out;
-  }
-
-  public static function rfc2425_fold($val)
-  {
-    return preg_replace_callback('/([^\n]{72,})/', array('self', 'rfc2425_fold_callback'), $val);
-  }
-
-
-  /**
-   * Decodes a vcard block (vcard 3.0 format, unfolded)
-   * into an array structure
-   *
-   * @param string vCard block to parse
-   * @return array Raw data structure
-   */
-  private static function vcard_decode($vcard)
-  {
-    // Perform RFC2425 line unfolding and split lines
-    $vcard = preg_replace(array("/\r/", "/\n\s+/"), '', $vcard);
-    $lines = explode("\n", $vcard);
-    $data  = array();
-
-    for ($i=0; $i < count($lines); $i++) {
-      if (!preg_match('/^([^:]+):(.+)$/', $lines[$i], $line))
-        continue;
-
-      if (preg_match('/^(BEGIN|END)$/i', $line[1]))
-        continue;
-
-      // convert 2.1-style "EMAIL;internet;home:" to 3.0-style "EMAIL;TYPE=internet;TYPE=home:"
-      if (($data['VERSION'][0] == "2.1") && preg_match('/^([^;]+);([^:]+)/', $line[1], $regs2) && !preg_match('/^TYPE=/i', $regs2[2])) {
-        $line[1] = $regs2[1];
-        foreach (explode(';', $regs2[2]) as $prop)
-          $line[1] .= ';' . (strpos($prop, '=') ? $prop : 'TYPE='.$prop);
-      }
-
-      if (preg_match_all('/([^\\;]+);?/', $line[1], $regs2)) {
-        $entry = array();
-        $field = strtoupper($regs2[1][0]);
-        $enc   = null;
-
-        foreach($regs2[1] as $attrid => $attr) {
-          if ((list($key, $value) = explode('=', $attr)) && $value) {
-            $value = trim($value);
-            if ($key == 'ENCODING') {
-              $value = strtoupper($value);
-              // add next line(s) to value string if QP line end detected
-              if ($value == 'QUOTED-PRINTABLE') {
-                while (preg_match('/=$/', $lines[$i]))
-                  $line[2] .= "\n" . $lines[++$i];
-              }
-              $enc = $value;
+    private static function rfc2425_fold_callback($matches)
+    {
+        // chunk_split string and avoid lines breaking multibyte characters
+        $c = 71;
+        $out .= substr($matches[1], 0, $c);
+        for ($n = $c; $c < strlen($matches[1]); $c++) {
+            // break if length > 75 or mutlibyte character starts after position 71
+            if ($n > 75 || ($n > 71 && ord($matches[1][$c]) >> 6 == 3)) {
+                $out .= "\r\n ";
+                $n = 0;
             }
-            else {
-              $lc_key = strtolower($key);
-              $entry[$lc_key] = array_merge((array)$entry[$lc_key], (array)self::vcard_unquote($value, ','));
-            }
-          }
-          else if ($attrid > 0) {
-            $entry[strtolower($key)] = true;  // true means attr without =value
-          }
+            $out .= $matches[1][$c];
+            $n++;
         }
 
-        // decode value
-        if ($enc || !empty($entry['base64'])) {
-          // save encoding type (#1488432)
-          if ($enc == 'B') {
-            $entry['encoding'] = 'B';
-            // should we use vCard 3.0 instead?
-            // $entry['base64'] = true;
-          }
-          $line[2] = self::decode_value($line[2], $enc ? $enc : 'base64');
-        }
+        return $out;
+    }
+
+    public static function rfc2425_fold($val)
+    {
+        return preg_replace_callback('/([^\n]{72,})/', array('self', 'rfc2425_fold_callback'), $val);
+    }
 
-        if ($enc != 'B' && empty($entry['base64'])) {
-          $line[2] = self::vcard_unquote($line[2]);
+    /**
+     * Decodes a vcard block (vcard 3.0 format, unfolded)
+     * into an array structure
+     *
+     * @param string vCard block to parse
+     *
+     * @return array Raw data structure
+     */
+    private static function vcard_decode($vcard)
+    {
+        // Perform RFC2425 line unfolding and split lines
+        $vcard = preg_replace(array("/\r/", "/\n\s+/"), '', $vcard);
+        $lines = explode("\n", $vcard);
+        $data  = array();
+
+        for ($i=0; $i < count($lines); $i++) {
+            if (!preg_match('/^([^:]+):(.+)$/', $lines[$i], $line))
+                continue;
+
+            if (preg_match('/^(BEGIN|END)$/i', $line[1]))
+                continue;
+
+            // convert 2.1-style "EMAIL;internet;home:" to 3.0-style "EMAIL;TYPE=internet;TYPE=home:"
+            if ($data['VERSION'][0] == "2.1"
+                && preg_match('/^([^;]+);([^:]+)/', $line[1], $regs2)
+                && !preg_match('/^TYPE=/i', $regs2[2])
+            ) {
+                $line[1] = $regs2[1];
+                foreach (explode(';', $regs2[2]) as $prop) {
+                    $line[1] .= ';' . (strpos($prop, '=') ? $prop : 'TYPE='.$prop);
+                }
+            }
+
+            if (preg_match_all('/([^\\;]+);?/', $line[1], $regs2)) {
+                $entry = array();
+                $field = strtoupper($regs2[1][0]);
+                $enc   = null;
+
+                foreach($regs2[1] as $attrid => $attr) {
+                    if ((list($key, $value) = explode('=', $attr)) && $value) {
+                        $value = trim($value);
+                        if ($key == 'ENCODING') {
+                            $value = strtoupper($value);
+                            // add next line(s) to value string if QP line end detected
+                            if ($value == 'QUOTED-PRINTABLE') {
+                                while (preg_match('/=$/', $lines[$i])) {
+                                    $line[2] .= "\n" . $lines[++$i];
+                                }
+                            }
+                            $enc = $value;
+                        }
+                        else {
+                            $lc_key = strtolower($key);
+                            $entry[$lc_key] = array_merge((array)$entry[$lc_key], (array)self::vcard_unquote($value, ','));
+                        }
+                    }
+                    else if ($attrid > 0) {
+                        $entry[strtolower($key)] = true;  // true means attr without =value
+                    }
+                }
+
+                // decode value
+                if ($enc || !empty($entry['base64'])) {
+                    // save encoding type (#1488432)
+                    if ($enc == 'B') {
+                        $entry['encoding'] = 'B';
+                        // should we use vCard 3.0 instead?
+                        // $entry['base64'] = true;
+                    }
+                    $line[2] = self::decode_value($line[2], $enc ? $enc : 'base64');
+                }
+
+                if ($enc != 'B' && empty($entry['base64'])) {
+                    $line[2] = self::vcard_unquote($line[2]);
+                }
+
+                $entry = array_merge($entry, (array) $line[2]);
+                $data[$field][] = $entry;
+            }
         }
 
-        $entry = array_merge($entry, (array) $line[2]);
-        $data[$field][] = $entry;
-      }
+        unset($data['VERSION']);
+        return $data;
     }
 
-    unset($data['VERSION']);
-    return $data;
-  }
-
-
-  /**
-   * Decode a given string with the encoding rule from ENCODING attributes
-   *
-   * @param string String to decode
-   * @param string Encoding type (quoted-printable and base64 supported)
-   * @return string Decoded 8bit value
-   */
-  private static function decode_value($value, $encoding)
-  {
-    switch (strtolower($encoding)) {
-      case 'quoted-printable':
-        self::$values_decoded = true;
-        return quoted_printable_decode($value);
-
-      case 'base64':
-      case 'b':
-        self::$values_decoded = true;
-        return base64_decode($value);
-
-      default:
-        return $value;
+    /**
+     * Decode a given string with the encoding rule from ENCODING attributes
+     *
+     * @param string String to decode
+     * @param string Encoding type (quoted-printable and base64 supported)
+     *
+     * @return string Decoded 8bit value
+     */
+    private static function decode_value($value, $encoding)
+    {
+        switch (strtolower($encoding)) {
+        case 'quoted-printable':
+            self::$values_decoded = true;
+            return quoted_printable_decode($value);
+
+        case 'base64':
+        case 'b':
+            self::$values_decoded = true;
+            return base64_decode($value);
+
+        default:
+            return $value;
+        }
     }
-  }
-
-
-  /**
-   * Encodes an entry for storage in our database (vcard 3.0 format, unfolded)
-   *
-   * @param array Raw data structure to encode
-   * @return string vCard encoded string
-   */
-  static function vcard_encode($data)
-  {
-    foreach((array)$data as $type => $entries) {
-      /* valid N has 5 properties */
-      while ($type == "N" && is_array($entries[0]) && count($entries[0]) < 5)
-        $entries[0][] = "";
-
-      // make sure FN is not empty (required by RFC2426)
-      if ($type == "FN" && empty($entries))
-        $entries[0] = $data['EMAIL'][0][0];
-
-      foreach((array)$entries as $entry) {
-        $attr = '';
-        if (is_array($entry)) {
-          $value = array();
-          foreach($entry as $attrname => $attrvalues) {
-            if (is_int($attrname)) {
-              if (!empty($entry['base64']) || $entry['encoding'] == 'B') {
-                $attrvalues = base64_encode($attrvalues);
-              }
-              $value[] = $attrvalues;
+
+    /**
+     * Encodes an entry for storage in our database (vcard 3.0 format, unfolded)
+     *
+     * @param array Raw data structure to encode
+     *
+     * @return string vCard encoded string
+     */
+    static function vcard_encode($data)
+    {
+        foreach ((array)$data as $type => $entries) {
+            // valid N has 5 properties
+            while ($type == "N" && is_array($entries[0]) && count($entries[0]) < 5) {
+                $entries[0][] = "";
             }
-            else if (is_bool($attrvalues)) {
-              if ($attrvalues) {
-                $attr .= strtoupper(";$attrname");    // true means just tag, not tag=value, as in PHOTO;BASE64:...
-              }
+
+            // make sure FN is not empty (required by RFC2426)
+            if ($type == "FN" && empty($entries)) {
+                $entries[0] = $data['EMAIL'][0][0];
             }
-            else {
-              foreach((array)$attrvalues as $attrvalue)
-                $attr .= strtoupper(";$attrname=") . self::vcard_quote($attrvalue, ',');
+
+            foreach ((array)$entries as $entry) {
+                $attr = '';
+                if (is_array($entry)) {
+                    $value = array();
+                    foreach ($entry as $attrname => $attrvalues) {
+                        if (is_int($attrname)) {
+                            if (!empty($entry['base64']) || $entry['encoding'] == 'B') {
+                                $attrvalues = base64_encode($attrvalues);
+                            }
+                            $value[] = $attrvalues;
+                        }
+                        else if (is_bool($attrvalues)) {
+                            // true means just tag, not tag=value, as in PHOTO;BASE64:...
+                            if ($attrvalues) {
+                                $attr .= strtoupper(";$attrname");
+                            }
+                        }
+                        else {
+                            foreach((array)$attrvalues as $attrvalue) {
+                                $attr .= strtoupper(";$attrname=") . self::vcard_quote($attrvalue, ',');
+                            }
+                        }
+                    }
+                }
+                else {
+                    $value = $entry;
+                }
+
+                // skip empty entries
+                if (self::is_empty($value)) {
+                    continue;
+                }
+
+                $vcard .= self::vcard_quote($type) . $attr . ':' . self::vcard_quote($value) . self::$eol;
             }
-          }
         }
-        else {
-          $value = $entry;
-        }
-
-        // skip empty entries
-        if (self::is_empty($value))
-          continue;
 
-        $vcard .= self::vcard_quote($type) . $attr . ':' . self::vcard_quote($value) . self::$eol;
-      }
+        return 'BEGIN:VCARD' . self::$eol . 'VERSION:3.0' . self::$eol . $vcard . 'END:VCARD';
     }
 
-    return 'BEGIN:VCARD' . self::$eol . 'VERSION:3.0' . self::$eol . $vcard . 'END:VCARD';
-  }
-
-
-  /**
-   * Join indexed data array to a vcard quoted string
-   *
-   * @param array Field data
-   * @param string Separator
-   * @return string Joined and quoted string
-   */
-  private static function vcard_quote($s, $sep = ';')
-  {
-    if (is_array($s)) {
-      foreach($s as $part) {
-        $r[] = self::vcard_quote($part, $sep);
-      }
-      return(implode($sep, (array)$r));
-    }
-    else {
-      return strtr($s, array('\\' => '\\\\', "\r" => '', "\n" => '\n', ',' => '\,', ';' => '\;'));
-    }
-  }
-
-
-  /**
-   * Split quoted string
-   *
-   * @param string vCard string to split
-   * @param string Separator char/string
-   * @return array List with splited values
-   */
-  private static function vcard_unquote($s, $sep = ';')
-  {
-    // break string into parts separated by $sep, but leave escaped $sep alone
-    if (count($parts = explode($sep, strtr($s, array("\\$sep" => "\007")))) > 1) {
-      foreach($parts as $s) {
-        $result[] = self::vcard_unquote(strtr($s, array("\007" => "\\$sep")), $sep);
-      }
-      return $result;
+    /**
+     * Join indexed data array to a vcard quoted string
+     *
+     * @param array Field data
+     * @param string Separator
+     *
+     * @return string Joined and quoted string
+     */
+    private static function vcard_quote($s, $sep = ';')
+    {
+        if (is_array($s)) {
+            foreach($s as $part) {
+                $r[] = self::vcard_quote($part, $sep);
+            }
+            return(implode($sep, (array)$r));
+        }
+
+        return strtr($s, array('\\' => '\\\\', "\r" => '', "\n" => '\n', ',' => '\,', ';' => '\;'));
     }
-    else {
-      return strtr($s, array("\r" => '', '\\\\' => '\\', '\n' => "\n", '\N' => "\n", '\,' => ',', '\;' => ';', '\:' => ':'));
+
+    /**
+     * Split quoted string
+     *
+     * @param string vCard string to split
+     * @param string Separator char/string
+     *
+     * @return array List with splited values
+     */
+    private static function vcard_unquote($s, $sep = ';')
+    {
+        // break string into parts separated by $sep, but leave escaped $sep alone
+        if (count($parts = explode($sep, strtr($s, array("\\$sep" => "\007")))) > 1) {
+            foreach($parts as $s) {
+                $result[] = self::vcard_unquote(strtr($s, array("\007" => "\\$sep")), $sep);
+            }
+            return $result;
+        }
+
+        return strtr($s, array("\r" => '', '\\\\' => '\\', '\n' => "\n", '\N' => "\n", '\,' => ',', '\;' => ';', '\:' => ':'));
     }
-  }
-
-
-  /**
-   * Check if vCard entry is empty: empty string or an array with
-   * all entries empty.
-   *
-   * @param mixed $value Attribute value (string or array)
-   *
-   * @return bool True if the value is empty, False otherwise
-   */
-  private static function is_empty($value)
-  {
-    foreach ((array)$value as $v) {
-      if (((string)$v) !== '') {
-        return false;
-      }
+
+    /**
+     * Check if vCard entry is empty: empty string or an array with
+     * all entries empty.
+     *
+     * @param mixed $value Attribute value (string or array)
+     *
+     * @return bool True if the value is empty, False otherwise
+     */
+    private static function is_empty($value)
+    {
+        foreach ((array)$value as $v) {
+            if (((string)$v) !== '') {
+                return false;
+            }
+        }
+
+        return true;
     }
 
-    return true;
-  }
-
-  /**
-   * Extract array values by a filter
-   *
-   * @param array Array to filter
-   * @param keys Array or comma separated list of values to keep
-   * @param boolean Invert key selection: remove the listed values
-   * @return array The filtered array
-   */
-  private static function array_filter($arr, $values, $inverse = false)
-  {
-    if (!is_array($values))
-      $values = explode(',', $values);
-
-    $result = array();
-    $keep = array_flip((array)$values);
-    foreach ($arr as $key => $val)
-      if ($inverse != isset($keep[strtolower($val)]))
-        $result[$key] = $val;
-
-    return $result;
-  }
-
-  /**
-   * Returns UNICODE type based on BOM (Byte Order Mark)
-   *
-   * @param string Input string to test
-   * @return string Detected encoding
-   */
-  private static function detect_encoding($string)
-  {
-    $fallback = rcube::get_instance()->config->get('default_charset', 'ISO-8859-1'); // fallback to Latin-1
-
-    return rcube_charset::detect($string, $fallback);
-  }
+    /**
+     * Extract array values by a filter
+     *
+     * @param array Array to filter
+     * @param keys Array or comma separated list of values to keep
+     * @param boolean Invert key selection: remove the listed values
+     *
+     * @return array The filtered array
+     */
+    private static function array_filter($arr, $values, $inverse = false)
+    {
+        if (!is_array($values)) {
+            $values = explode(',', $values);
+        }
+
+        $result = array();
+        $keep   = array_flip((array)$values);
 
+        foreach ($arr as $key => $val) {
+            if ($inverse != isset($keep[strtolower($val)])) {
+                $result[$key] = $val;
+            }
+        }
+
+        return $result;
+    }
+
+    /**
+     * Returns UNICODE type based on BOM (Byte Order Mark)
+     *
+     * @param string Input string to test
+     *
+     * @return string Detected encoding
+     */
+    private static function detect_encoding($string)
+    {
+        $fallback = rcube::get_instance()->config->get('default_charset', 'ISO-8859-1'); // fallback to Latin-1
+
+        return rcube_charset::detect($string, $fallback);
+    }
 }
-- 
cgit v1.2.3


From 83370e5ff14f55f6af435807713956160f91abfa Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Tue, 18 Dec 2012 12:54:38 +0100
Subject: Display 'Sender' header in message preview

---
 CHANGELOG                               |  1 +
 program/lib/Roundcube/rcube_storage.php |  1 +
 program/localization/en_US/labels.inc   |  1 +
 program/steps/mail/func.inc             | 10 +++++++++-
 4 files changed, 12 insertions(+), 1 deletion(-)

(limited to 'program/lib/Roundcube')

diff --git a/CHANGELOG b/CHANGELOG
index a89e02930..8cfeaf89d 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
 CHANGELOG Roundcube Webmail
 ===========================
 
+- Display 'Sender' header in message preview
 - Plugin API: Added message_before_send hook
 - Fix contact copy/add-to-group operations on search result (#1488862)
 - Use matching identity in MDN response (#1488864)
diff --git a/program/lib/Roundcube/rcube_storage.php b/program/lib/Roundcube/rcube_storage.php
index 65de2660c..8a36f1f9d 100644
--- a/program/lib/Roundcube/rcube_storage.php
+++ b/program/lib/Roundcube/rcube_storage.php
@@ -53,6 +53,7 @@ abstract class rcube_storage
     protected $all_headers = array(
         'IN-REPLY-TO',
         'BCC',
+        'SENDER',
         'MESSAGE-ID',
         'CONTENT-TRANSFER-ENCODING',
         'REFERENCES',
diff --git a/program/localization/en_US/labels.inc b/program/localization/en_US/labels.inc
index abb0dca5d..730e6af09 100644
--- a/program/localization/en_US/labels.inc
+++ b/program/localization/en_US/labels.inc
@@ -41,6 +41,7 @@ $labels['junk']   = 'Junk';
 // message listing
 $labels['subject'] = 'Subject';
 $labels['from']    = 'From';
+$labels['sender']  = 'Sender';
 $labels['to']      = 'To';
 $labels['cc']      = 'Cc';
 $labels['bcc']     = 'Bcc';
diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc
index 88391b102..f5165399b 100644
--- a/program/steps/mail/func.inc
+++ b/program/steps/mail/func.inc
@@ -967,7 +967,7 @@ function rcmail_message_headers($attrib, $headers=null)
   }
 
   // show these headers
-  $standard_headers = array('subject', 'from', 'to', 'cc', 'bcc', 'replyto',
+  $standard_headers = array('subject', 'from', 'sender', 'to', 'cc', 'bcc', 'replyto',
     'mail-reply-to', 'mail-followup-to', 'date', 'priority');
   $exclude_headers = $attrib['exclude'] ? explode(',', $attrib['exclude']) : array();
   $output_headers = array();
@@ -1018,6 +1018,14 @@ function rcmail_message_headers($attrib, $headers=null)
       else
         continue;
     }
+    else if ($hkey == 'sender') {
+      if ($headers['sender'] != $headers['from']) {
+        $header_value = rcmail_address_string($value, $attrib['max'], true, $attrib['addicon'], $headers['charset'], $header_title);
+        $ishtml = true;
+      }
+      else
+        continue;
+    }
     else if ($hkey == 'mail-followup-to') {
       $header_value = rcmail_address_string($value, $attrib['max'], true, $attrib['addicon'], $headers['charset'], $header_title);
       $ishtml = true;
-- 
cgit v1.2.3


From 0d214498d04439c29c9764fa291dcfde49701467 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Tue, 18 Dec 2012 19:02:53 +0100
Subject: CS fixes

---
 program/lib/Roundcube/html.php                  |   19 +-
 program/lib/Roundcube/rcube_addressbook.php     |    4 +-
 program/lib/Roundcube/rcube_session.php         | 1159 ++++++++++++-----------
 program/lib/Roundcube/rcube_string_replacer.php |  298 +++---
 4 files changed, 754 insertions(+), 726 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/html.php b/program/lib/Roundcube/html.php
index 33b766c44..522a82305 100644
--- a/program/lib/Roundcube/html.php
+++ b/program/lib/Roundcube/html.php
@@ -169,7 +169,7 @@ class html
             $attr = array('href' => $attr);
         }
         return self::tag('a', $attr, $cont, array_merge(self::$common_attrib,
-        array('href','target','name','rel','onclick','onmouseover','onmouseout','onmousedown','onmouseup')));
+            array('href','target','name','rel','onclick','onmouseover','onmouseout','onmousedown','onmouseup')));
     }
 
     /**
@@ -675,7 +675,7 @@ class html_table extends html
         }
 
         $cell = new stdClass;
-        $cell->attrib = $attr;
+        $cell->attrib  = $attr;
         $cell->content = $cont;
 
         $this->rows[$this->rowindex]->cells[$this->colindex] = $cell;
@@ -699,16 +699,16 @@ class html_table extends html
         }
 
         $cell = new stdClass;
-        $cell->attrib = $attr;
-        $cell->content = $cont;
+        $cell->attrib   = $attr;
+        $cell->content  = $cont;
         $this->header[] = $cell;
     }
 
-     /**
+    /**
      * Remove a column from a table
      * Useful for plugins making alterations
-     * 
-     * @param string $class 
+     *
+     * @param string $class
      */
     public function remove_column($class)
     {
@@ -788,8 +788,9 @@ class html_table extends html
      */
     public function show($attrib = null)
     {
-        if (is_array($attrib))
+        if (is_array($attrib)) {
             $this->attrib = array_merge($this->attrib, $attrib);
+        }
 
         $thead = $tbody = "";
 
@@ -831,7 +832,7 @@ class html_table extends html
      */
     public function size()
     {
-      return count($this->rows);
+        return count($this->rows);
     }
 
     /**
diff --git a/program/lib/Roundcube/rcube_addressbook.php b/program/lib/Roundcube/rcube_addressbook.php
index a8f274a8f..98d8f98ee 100644
--- a/program/lib/Roundcube/rcube_addressbook.php
+++ b/program/lib/Roundcube/rcube_addressbook.php
@@ -138,7 +138,7 @@ abstract class rcube_addressbook
      */
     function get_error()
     {
-      return $this->error;
+        return $this->error;
     }
 
     /**
@@ -149,7 +149,7 @@ abstract class rcube_addressbook
      */
     protected function set_error($type, $message)
     {
-      $this->error = array('type' => $type, 'message' => $message);
+        $this->error = array('type' => $type, 'message' => $message);
     }
 
     /**
diff --git a/program/lib/Roundcube/rcube_session.php b/program/lib/Roundcube/rcube_session.php
index 69eaabedc..1aa5d5856 100644
--- a/program/lib/Roundcube/rcube_session.php
+++ b/program/lib/Roundcube/rcube_session.php
@@ -28,602 +28,629 @@
  */
 class rcube_session
 {
-  private $db;
-  private $ip;
-  private $start;
-  private $changed;
-  private $unsets = array();
-  private $gc_handlers = array();
-  private $cookiename = 'roundcube_sessauth';
-  private $vars;
-  private $key;
-  private $now;
-  private $secret = '';
-  private $ip_check = false;
-  private $logging = false;
-  private $memcache;
-
-  /**
-   * Default constructor
-   */
-  public function __construct($db, $config)
-  {
-    $this->db      = $db;
-    $this->start   = microtime(true);
-    $this->ip      = $_SERVER['REMOTE_ADDR'];
-    $this->logging = $config->get('log_session', false);
-
-    $lifetime = $config->get('session_lifetime', 1) * 60;
-    $this->set_lifetime($lifetime);
-
-    // use memcache backend
-    if ($config->get('session_storage', 'db') == 'memcache') {
-      $this->memcache = rcube::get_instance()->get_memcache();
-
-      // set custom functions for PHP session management if memcache is available
-      if ($this->memcache) {
-        session_set_save_handler(
-          array($this, 'open'),
-          array($this, 'close'),
-          array($this, 'mc_read'),
-          array($this, 'mc_write'),
-          array($this, 'mc_destroy'),
-          array($this, 'gc'));
-      }
-      else {
-        rcube::raise_error(array('code' => 604, 'type' => 'db',
-          'line' => __LINE__, 'file' => __FILE__,
-          'message' => "Failed to connect to memcached. Please check configuration"),
-          true, true);
-      }
+    private $db;
+    private $ip;
+    private $start;
+    private $changed;
+    private $unsets = array();
+    private $gc_handlers = array();
+    private $cookiename = 'roundcube_sessauth';
+    private $vars;
+    private $key;
+    private $now;
+    private $secret = '';
+    private $ip_check = false;
+    private $logging = false;
+    private $memcache;
+
+
+    /**
+     * Default constructor
+     */
+    public function __construct($db, $config)
+    {
+        $this->db      = $db;
+        $this->start   = microtime(true);
+        $this->ip      = $_SERVER['REMOTE_ADDR'];
+        $this->logging = $config->get('log_session', false);
+
+        $lifetime = $config->get('session_lifetime', 1) * 60;
+        $this->set_lifetime($lifetime);
+
+        // use memcache backend
+        if ($config->get('session_storage', 'db') == 'memcache') {
+            $this->memcache = rcube::get_instance()->get_memcache();
+
+            // set custom functions for PHP session management if memcache is available
+            if ($this->memcache) {
+                session_set_save_handler(
+                    array($this, 'open'),
+                    array($this, 'close'),
+                    array($this, 'mc_read'),
+                    array($this, 'mc_write'),
+                    array($this, 'mc_destroy'),
+                    array($this, 'gc'));
+            }
+            else {
+                rcube::raise_error(array('code' => 604, 'type' => 'db',
+                    'line' => __LINE__, 'file' => __FILE__,
+                    'message' => "Failed to connect to memcached. Please check configuration"),
+                true, true);
+            }
+        }
+        else {
+            // set custom functions for PHP session management
+            session_set_save_handler(
+                array($this, 'open'),
+                array($this, 'close'),
+                array($this, 'db_read'),
+                array($this, 'db_write'),
+                array($this, 'db_destroy'),
+                array($this, 'db_gc'));
+        }
     }
-    else {
-      // set custom functions for PHP session management
-      session_set_save_handler(
-        array($this, 'open'),
-        array($this, 'close'),
-        array($this, 'db_read'),
-        array($this, 'db_write'),
-        array($this, 'db_destroy'),
-        array($this, 'db_gc'));
-      }
-  }
-
-
-  public function open($save_path, $session_name)
-  {
-    return true;
-  }
-
-
-  public function close()
-  {
-    return true;
-  }
-
-
-  /**
-   * Delete session data for the given key
-   *
-   * @param string Session ID
-   */
-  public function destroy($key)
-  {
-    return $this->memcache ? $this->mc_destroy($key) : $this->db_destroy($key);
-  }
-
-
-  /**
-   * Read session data from database
-   *
-   * @param string Session ID
-   * @return string Session vars
-   */
-  public function db_read($key)
-  {
-    $sql_result = $this->db->query(
-      "SELECT vars, ip, changed FROM ".$this->db->table_name('session')
-      ." WHERE sess_id = ?", $key);
-
-    if ($sql_result && ($sql_arr = $this->db->fetch_assoc($sql_result))) {
-      $this->changed = strtotime($sql_arr['changed']);
-      $this->ip      = $sql_arr['ip'];
-      $this->vars    = base64_decode($sql_arr['vars']);
-      $this->key     = $key;
-
-      return !empty($this->vars) ? (string) $this->vars : '';
+
+
+    public function open($save_path, $session_name)
+    {
+        return true;
+    }
+
+
+    public function close()
+    {
+        return true;
     }
 
-    return null;
-  }
-
-
-  /**
-   * Save session data.
-   * handler for session_read()
-   *
-   * @param string Session ID
-   * @param string Serialized session vars
-   * @return boolean True on success
-   */
-  public function db_write($key, $vars)
-  {
-    $ts = microtime(true);
-    $now = $this->db->fromunixtime((int)$ts);
-
-    // no session row in DB (db_read() returns false)
-    if (!$this->key) {
-      $oldvars = null;
+
+    /**
+     * Delete session data for the given key
+     *
+     * @param string Session ID
+     */
+    public function destroy($key)
+    {
+        return $this->memcache ? $this->mc_destroy($key) : $this->db_destroy($key);
+    }
+
+
+    /**
+     * Read session data from database
+     *
+     * @param string Session ID
+     *
+     * @return string Session vars
+     */
+    public function db_read($key)
+    {
+        $sql_result = $this->db->query(
+            "SELECT vars, ip, changed FROM ".$this->db->table_name('session')
+            ." WHERE sess_id = ?", $key);
+
+        if ($sql_result && ($sql_arr = $this->db->fetch_assoc($sql_result))) {
+            $this->changed = strtotime($sql_arr['changed']);
+            $this->ip      = $sql_arr['ip'];
+            $this->vars    = base64_decode($sql_arr['vars']);
+            $this->key     = $key;
+
+            return !empty($this->vars) ? (string) $this->vars : '';
+        }
+
+        return null;
     }
-    // use internal data from read() for fast requests (up to 0.5 sec.)
-    else if ($key == $this->key && (!$this->vars || $ts - $this->start < 0.5)) {
-      $oldvars = $this->vars;
+
+
+    /**
+     * Save session data.
+     * handler for session_read()
+     *
+     * @param string Session ID
+     * @param string Serialized session vars
+     *
+     * @return boolean True on success
+     */
+    public function db_write($key, $vars)
+    {
+        $ts  = microtime(true);
+        $now = $this->db->fromunixtime((int)$ts);
+
+        // no session row in DB (db_read() returns false)
+        if (!$this->key) {
+            $oldvars = null;
+        }
+        // use internal data from read() for fast requests (up to 0.5 sec.)
+        else if ($key == $this->key && (!$this->vars || $ts - $this->start < 0.5)) {
+            $oldvars = $this->vars;
+        }
+        else { // else read data again from DB
+            $oldvars = $this->db_read($key);
+        }
+
+        if ($oldvars !== null) {
+            $newvars = $this->_fixvars($vars, $oldvars);
+
+            if ($newvars !== $oldvars) {
+                $this->db->query(
+                    sprintf("UPDATE %s SET vars=?, changed=%s WHERE sess_id=?",
+                        $this->db->table_name('session'), $now),
+                        base64_encode($newvars), $key);
+            }
+            else if ($ts - $this->changed > $this->lifetime / 2) {
+                $this->db->query("UPDATE ".$this->db->table_name('session')
+                    ." SET changed=$now WHERE sess_id=?", $key);
+            }
+        }
+        else {
+            $this->db->query(
+                sprintf("INSERT INTO %s (sess_id, vars, ip, created, changed) ".
+                    "VALUES (?, ?, ?, %s, %s)",
+                    $this->db->table_name('session'), $now, $now),
+                    $key, base64_encode($vars), (string)$this->ip);
+        }
+
+        return true;
+    }
+
+
+    /**
+     * Merge vars with old vars and apply unsets
+     */
+    private function _fixvars($vars, $oldvars)
+    {
+        if ($oldvars !== null) {
+            $a_oldvars = $this->unserialize($oldvars);
+            if (is_array($a_oldvars)) {
+                foreach ((array)$this->unsets as $k)
+                    unset($a_oldvars[$k]);
+
+                $newvars = $this->serialize(array_merge(
+                    (array)$a_oldvars, (array)$this->unserialize($vars)));
+            }
+            else {
+                $newvars = $vars;
+            }
+        }
+
+        $this->unsets = array();
+        return $newvars;
     }
-    else { // else read data again from DB
-      $oldvars = $this->db_read($key);
+
+
+    /**
+     * Handler for session_destroy()
+     *
+     * @param string Session ID
+     *
+     * @return boolean True on success
+     */
+    public function db_destroy($key)
+    {
+        if ($key) {
+            $this->db->query(sprintf("DELETE FROM %s WHERE sess_id = ?",
+                $this->db->table_name('session')), $key);
+        }
+
+        return true;
     }
 
-    if ($oldvars !== null) {
-      $newvars = $this->_fixvars($vars, $oldvars);
 
-      if ($newvars !== $oldvars) {
+    /**
+     * Garbage collecting function
+     *
+     * @param string Session lifetime in seconds
+     * @return boolean True on success
+     */
+    public function db_gc($maxlifetime)
+    {
+        // just delete all expired sessions
         $this->db->query(
-          sprintf("UPDATE %s SET vars=?, changed=%s WHERE sess_id=?",
-            $this->db->table_name('session'), $now),
-          base64_encode($newvars), $key);
-      }
-      else if ($ts - $this->changed > $this->lifetime / 2) {
-        $this->db->query("UPDATE ".$this->db->table_name('session')." SET changed=$now WHERE sess_id=?", $key);
-      }
+            sprintf("DELETE FROM %s WHERE changed < %s",
+                $this->db->table_name('session'), $this->db->fromunixtime(time() - $maxlifetime)));
+
+        $this->gc();
+
+        return true;
+    }
+
+
+    /**
+     * Read session data from memcache
+     *
+     * @param string Session ID
+     * @return string Session vars
+     */
+    public function mc_read($key)
+    {
+        if ($value = $this->memcache->get($key)) {
+            $arr = unserialize($value);
+            $this->changed = $arr['changed'];
+            $this->ip      = $arr['ip'];
+            $this->vars    = $arr['vars'];
+            $this->key     = $key;
+
+            return !empty($this->vars) ? (string) $this->vars : '';
+        }
+
+        return null;
+    }
+
+
+    /**
+     * Save session data.
+     * handler for session_read()
+     *
+     * @param string Session ID
+     * @param string Serialized session vars
+     *
+     * @return boolean True on success
+     */
+    public function mc_write($key, $vars)
+    {
+        $ts = microtime(true);
+
+        // no session data in cache (mc_read() returns false)
+        if (!$this->key)
+            $oldvars = null;
+        // use internal data for fast requests (up to 0.5 sec.)
+        else if ($key == $this->key && (!$this->vars || $ts - $this->start < 0.5))
+            $oldvars = $this->vars;
+        else // else read data again
+            $oldvars = $this->mc_read($key);
+
+        $newvars = $oldvars !== null ? $this->_fixvars($vars, $oldvars) : $vars;
+
+        if ($newvars !== $oldvars || $ts - $this->changed > $this->lifetime / 2) {
+            return $this->memcache->set($key, serialize(array('changed' => time(), 'ip' => $this->ip, 'vars' => $newvars)),
+                MEMCACHE_COMPRESSED, $this->lifetime);
+        }
+
+        return true;
+    }
+
+
+    /**
+     * Handler for session_destroy() with memcache backend
+     *
+     * @param string Session ID
+     *
+     * @return boolean True on success
+     */
+    public function mc_destroy($key)
+    {
+        if ($key) {
+            // #1488592: use 2nd argument
+            $this->memcache->delete($key, 0);
+        }
+
+        return true;
+    }
+
+
+    /**
+     * Execute registered garbage collector routines
+     */
+    public function gc()
+    {
+        foreach ($this->gc_handlers as $fct) {
+            call_user_func($fct);
+        }
     }
-    else {
-      $this->db->query(
-        sprintf("INSERT INTO %s (sess_id, vars, ip, created, changed) ".
-          "VALUES (?, ?, ?, %s, %s)",
-          $this->db->table_name('session'), $now, $now),
-        $key, base64_encode($vars), (string)$this->ip);
+
+
+    /**
+     * Register additional garbage collector functions
+     *
+     * @param mixed Callback function
+     */
+    public function register_gc_handler($func)
+    {
+        foreach ($this->gc_handlers as $handler) {
+            if ($handler == $func) {
+                return;
+            }
+        }
+
+        $this->gc_handlers[] = $func;
     }
 
-    return true;
-  }
-
-
-  /**
-   * Merge vars with old vars and apply unsets
-   */
-  private function _fixvars($vars, $oldvars)
-  {
-    if ($oldvars !== null) {
-      $a_oldvars = $this->unserialize($oldvars);
-      if (is_array($a_oldvars)) {
-        foreach ((array)$this->unsets as $k)
-          unset($a_oldvars[$k]);
-
-        $newvars = $this->serialize(array_merge(
-          (array)$a_oldvars, (array)$this->unserialize($vars)));
-      }
-      else
-        $newvars = $vars;
+
+    /**
+     * Generate and set new session id
+     *
+     * @param boolean $destroy If enabled the current session will be destroyed
+     */
+    public function regenerate_id($destroy=true)
+    {
+        session_regenerate_id($destroy);
+
+        $this->vars = null;
+        $this->key  = session_id();
+
+        return true;
     }
 
-    $this->unsets = array();
-    return $newvars;
-  }
-
-
-  /**
-   * Handler for session_destroy()
-   *
-   * @param string Session ID
-   *
-   * @return boolean True on success
-   */
-  public function db_destroy($key)
-  {
-    if ($key) {
-      $this->db->query(sprintf("DELETE FROM %s WHERE sess_id = ?", $this->db->table_name('session')), $key);
+
+    /**
+     * Unset a session variable
+     *
+     * @param string Varibale name
+     * @return boolean True on success
+     */
+    public function remove($var=null)
+    {
+        if (empty($var)) {
+            return $this->destroy(session_id());
+        }
+
+        $this->unsets[] = $var;
+        unset($_SESSION[$var]);
+
+        return true;
+    }
+
+
+    /**
+     * Kill this session
+     */
+    public function kill()
+    {
+        $this->vars = null;
+        $this->ip = $_SERVER['REMOTE_ADDR']; // update IP (might have changed)
+        $this->destroy(session_id());
+        rcube_utils::setcookie($this->cookiename, '-del-', time() - 60);
+    }
+
+
+    /**
+     * Re-read session data from storage backend
+     */
+    public function reload()
+    {
+        if ($this->key && $this->memcache)
+            $data = $this->mc_read($this->key);
+        else if ($this->key)
+            $data = $this->db_read($this->key);
+
+        if ($data)
+            session_decode($data);
+    }
+
+
+    /**
+     * Serialize session data
+     */
+    private function serialize($vars)
+    {
+        $data = '';
+        if (is_array($vars)) {
+            foreach ($vars as $var=>$value)
+                $data .= $var.'|'.serialize($value);
+        }
+        else {
+            $data = 'b:0;';
+        }
+
+        return $data;
     }
 
-    return true;
-  }
-
-
-  /**
-   * Garbage collecting function
-   *
-   * @param string Session lifetime in seconds
-   * @return boolean True on success
-   */
-  public function db_gc($maxlifetime)
-  {
-    // just delete all expired sessions
-    $this->db->query(
-      sprintf("DELETE FROM %s WHERE changed < %s",
-        $this->db->table_name('session'), $this->db->fromunixtime(time() - $maxlifetime)));
-
-    $this->gc();
-
-    return true;
-  }
-
-
-  /**
-   * Read session data from memcache
-   *
-   * @param string Session ID
-   * @return string Session vars
-   */
-  public function mc_read($key)
-  {
-    if ($value = $this->memcache->get($key)) {
-      $arr = unserialize($value);
-      $this->changed = $arr['changed'];
-      $this->ip      = $arr['ip'];
-      $this->vars    = $arr['vars'];
-      $this->key     = $key;
-
-      return !empty($this->vars) ? (string) $this->vars : '';
+
+    /**
+     * Unserialize session data
+     * http://www.php.net/manual/en/function.session-decode.php#56106
+     */
+    private function unserialize($str)
+    {
+        $str    = (string)$str;
+        $endptr = strlen($str);
+        $p      = 0;
+
+        $serialized = '';
+        $items      = 0;
+        $level      = 0;
+
+        while ($p < $endptr) {
+            $q = $p;
+            while ($str[$q] != '|')
+                if (++$q >= $endptr)
+                    break 2;
+
+            if ($str[$p] == '!') {
+                $p++;
+                $has_value = false;
+            }
+            else {
+                $has_value = true;
+            }
+
+            $name = substr($str, $p, $q - $p);
+            $q++;
+
+            $serialized .= 's:' . strlen($name) . ':"' . $name . '";';
+
+            if ($has_value) {
+                for (;;) {
+                    $p = $q;
+                    switch (strtolower($str[$q])) {
+                    case 'n': // null
+                    case 'b': // boolean
+                    case 'i': // integer
+                    case 'd': // decimal
+                        do $q++;
+                        while ( ($q < $endptr) && ($str[$q] != ';') );
+                        $q++;
+                        $serialized .= substr($str, $p, $q - $p);
+                        if ($level == 0)
+                            break 2;
+                        break;
+                    case 'r': // reference
+                        $q+= 2;
+                        for ($id = ''; ($q < $endptr) && ($str[$q] != ';'); $q++)
+                            $id .= $str[$q];
+                        $q++;
+                        // increment pointer because of outer array
+                        $serialized .= 'R:' . ($id + 1) . ';';
+                        if ($level == 0)
+                            break 2;
+                        break;
+                    case 's': // string
+                        $q+=2;
+                        for ($length=''; ($q < $endptr) && ($str[$q] != ':'); $q++)
+                            $length .= $str[$q];
+                        $q+=2;
+                        $q+= (int)$length + 2;
+                        $serialized .= substr($str, $p, $q - $p);
+                        if ($level == 0)
+                            break 2;
+                        break;
+                    case 'a': // array
+                    case 'o': // object
+                        do $q++;
+                        while ($q < $endptr && $str[$q] != '{');
+                        $q++;
+                        $level++;
+                        $serialized .= substr($str, $p, $q - $p);
+                        break;
+                    case '}': // end of array|object
+                        $q++;
+                        $serialized .= substr($str, $p, $q - $p);
+                        if (--$level == 0)
+                            break 2;
+                        break;
+                    default:
+                        return false;
+                    }
+                }
+            }
+            else {
+                $serialized .= 'N;';
+                $q += 2;
+            }
+            $items++;
+            $p = $q;
+        }
+
+        return unserialize( 'a:' . $items . ':{' . $serialized . '}' );
     }
 
-    return null;
-  }
-
-
-  /**
-   * Save session data.
-   * handler for session_read()
-   *
-   * @param string Session ID
-   * @param string Serialized session vars
-   * @return boolean True on success
-   */
-  public function mc_write($key, $vars)
-  {
-    $ts = microtime(true);
-
-    // no session data in cache (mc_read() returns false)
-    if (!$this->key)
-      $oldvars = null;
-    // use internal data for fast requests (up to 0.5 sec.)
-    else if ($key == $this->key && (!$this->vars || $ts - $this->start < 0.5))
-      $oldvars = $this->vars;
-    else // else read data again
-      $oldvars = $this->mc_read($key);
-
-    $newvars = $oldvars !== null ? $this->_fixvars($vars, $oldvars) : $vars;
-
-    if ($newvars !== $oldvars || $ts - $this->changed > $this->lifetime / 2)
-      return $this->memcache->set($key, serialize(array('changed' => time(), 'ip' => $this->ip, 'vars' => $newvars)), MEMCACHE_COMPRESSED, $this->lifetime);
-
-    return true;
-  }
-
-
-  /**
-   * Handler for session_destroy() with memcache backend
-   *
-   * @param string Session ID
-   *
-   * @return boolean True on success
-   */
-  public function mc_destroy($key)
-  {
-    if ($key) {
-      // #1488592: use 2nd argument
-      $this->memcache->delete($key, 0);
+
+    /**
+     * Setter for session lifetime
+     */
+    public function set_lifetime($lifetime)
+    {
+        $this->lifetime = max(120, $lifetime);
+
+        // valid time range is now - 1/2 lifetime to now + 1/2 lifetime
+        $now = time();
+        $this->now = $now - ($now % ($this->lifetime / 2));
     }
 
-    return true;
-  }
 
+    /**
+     * Getter for remote IP saved with this session
+     */
+    public function get_ip()
+    {
+        return $this->ip;
+    }
 
-  /**
-   * Execute registered garbage collector routines
-   */
-  public function gc()
-  {
-    foreach ($this->gc_handlers as $fct) {
-      call_user_func($fct);
+
+    /**
+     * Setter for cookie encryption secret
+     */
+    function set_secret($secret)
+    {
+        $this->secret = $secret;
     }
-  }
-
-
-  /**
-   * Register additional garbage collector functions
-   *
-   * @param mixed Callback function
-   */
-  public function register_gc_handler($func)
-  {
-    foreach ($this->gc_handlers as $handler) {
-      if ($handler == $func) {
-        return;
-      }
+
+
+    /**
+     * Enable/disable IP check
+     */
+    function set_ip_check($check)
+    {
+        $this->ip_check = $check;
     }
 
-    $this->gc_handlers[] = $func;
-  }
-
-
-  /**
-   * Generate and set new session id
-   *
-   * @param boolean $destroy If enabled the current session will be destroyed
-   */
-  public function regenerate_id($destroy=true)
-  {
-    session_regenerate_id($destroy);
-
-    $this->vars = null;
-    $this->key  = session_id();
-
-    return true;
-  }
-
-
-  /**
-   * Unset a session variable
-   *
-   * @param string Varibale name
-   * @return boolean True on success
-   */
-  public function remove($var=null)
-  {
-    if (empty($var))
-      return $this->destroy(session_id());
-
-    $this->unsets[] = $var;
-    unset($_SESSION[$var]);
-
-    return true;
-  }
-
-
-  /**
-   * Kill this session
-   */
-  public function kill()
-  {
-    $this->vars = null;
-    $this->ip = $_SERVER['REMOTE_ADDR']; // update IP (might have changed)
-    $this->destroy(session_id());
-    rcube_utils::setcookie($this->cookiename, '-del-', time() - 60);
-  }
-
-
-  /**
-   * Re-read session data from storage backend
-   */
-  public function reload()
-  {
-    if ($this->key && $this->memcache)
-      $data = $this->mc_read($this->key);
-    else if ($this->key)
-      $data = $this->db_read($this->key);
-
-    if ($data)
-     session_decode($data);
-  }
-
-
-  /**
-   * Serialize session data
-   */
-  private function serialize($vars)
-  {
-    $data = '';
-    if (is_array($vars))
-      foreach ($vars as $var=>$value)
-        $data .= $var.'|'.serialize($value);
-    else
-      $data = 'b:0;';
-    return $data;
-  }
-
-
-  /**
-   * Unserialize session data
-   * http://www.php.net/manual/en/function.session-decode.php#56106
-   */
-  private function unserialize($str)
-  {
-    $str = (string)$str;
-    $endptr = strlen($str);
-    $p = 0;
-
-    $serialized = '';
-    $items = 0;
-    $level = 0;
-
-    while ($p < $endptr) {
-      $q = $p;
-      while ($str[$q] != '|')
-        if (++$q >= $endptr) break 2;
-
-      if ($str[$p] == '!') {
-        $p++;
-        $has_value = false;
-      } else {
-        $has_value = true;
-      }
-
-      $name = substr($str, $p, $q - $p);
-      $q++;
-
-      $serialized .= 's:' . strlen($name) . ':"' . $name . '";';
-
-      if ($has_value) {
-        for (;;) {
-          $p = $q;
-          switch (strtolower($str[$q])) {
-            case 'n': /* null */
-            case 'b': /* boolean */
-            case 'i': /* integer */
-            case 'd': /* decimal */
-              do $q++;
-              while ( ($q < $endptr) && ($str[$q] != ';') );
-              $q++;
-              $serialized .= substr($str, $p, $q - $p);
-              if ($level == 0) break 2;
-              break;
-            case 'r': /* reference  */
-              $q+= 2;
-              for ($id = ''; ($q < $endptr) && ($str[$q] != ';'); $q++) $id .= $str[$q];
-              $q++;
-              $serialized .= 'R:' . ($id + 1) . ';'; /* increment pointer because of outer array */
-              if ($level == 0) break 2;
-              break;
-            case 's': /* string */
-              $q+=2;
-              for ($length=''; ($q < $endptr) && ($str[$q] != ':'); $q++) $length .= $str[$q];
-              $q+=2;
-              $q+= (int)$length + 2;
-              $serialized .= substr($str, $p, $q - $p);
-              if ($level == 0) break 2;
-              break;
-            case 'a': /* array */
-            case 'o': /* object */
-              do $q++;
-              while ( ($q < $endptr) && ($str[$q] != '{') );
-              $q++;
-              $level++;
-              $serialized .= substr($str, $p, $q - $p);
-              break;
-            case '}': /* end of array|object */
-              $q++;
-              $serialized .= substr($str, $p, $q - $p);
-              if (--$level == 0) break 2;
-              break;
-            default:
-              return false;
-          }
+
+    /**
+     * Setter for the cookie name used for session cookie
+     */
+    function set_cookiename($cookiename)
+    {
+        if ($cookiename) {
+            $this->cookiename = $cookiename;
         }
-      } else {
-        $serialized .= 'N;';
-        $q += 2;
-      }
-      $items++;
-      $p = $q;
     }
 
-    return unserialize( 'a:' . $items . ':{' . $serialized . '}' );
-  }
-
-
-  /**
-   * Setter for session lifetime
-   */
-  public function set_lifetime($lifetime)
-  {
-      $this->lifetime = max(120, $lifetime);
-
-      // valid time range is now - 1/2 lifetime to now + 1/2 lifetime
-      $now = time();
-      $this->now = $now - ($now % ($this->lifetime / 2));
-  }
-
-
-  /**
-   * Getter for remote IP saved with this session
-   */
-  public function get_ip()
-  {
-    return $this->ip;
-  }
-
-
-  /**
-   * Setter for cookie encryption secret
-   */
-  function set_secret($secret)
-  {
-    $this->secret = $secret;
-  }
-
-
-  /**
-   * Enable/disable IP check
-   */
-  function set_ip_check($check)
-  {
-    $this->ip_check = $check;
-  }
-
-
-  /**
-   * Setter for the cookie name used for session cookie
-   */
-  function set_cookiename($cookiename)
-  {
-    if ($cookiename)
-      $this->cookiename = $cookiename;
-  }
-
-
-  /**
-   * Check session authentication cookie
-   *
-   * @return boolean True if valid, False if not
-   */
-  function check_auth()
-  {
-    $this->cookie = $_COOKIE[$this->cookiename];
-    $result = $this->ip_check ? $_SERVER['REMOTE_ADDR'] == $this->ip : true;
-
-    if (!$result)
-      $this->log("IP check failed for " . $this->key . "; expected " . $this->ip . "; got " . $_SERVER['REMOTE_ADDR']);
-
-    if ($result && $this->_mkcookie($this->now) != $this->cookie) {
-      $this->log("Session auth check failed for " . $this->key . "; timeslot = " . date('Y-m-d H:i:s', $this->now));
-      $result = false;
-
-      // Check if using id from a previous time slot
-      for ($i = 1; $i <= 2; $i++) {
-        $prev = $this->now - ($this->lifetime / 2) * $i;
-        if ($this->_mkcookie($prev) == $this->cookie) {
-          $this->log("Send new auth cookie for " . $this->key . ": " . $this->cookie);
-          $this->set_auth_cookie();
-          $result = true;
+
+    /**
+     * Check session authentication cookie
+     *
+     * @return boolean True if valid, False if not
+     */
+    function check_auth()
+    {
+        $this->cookie = $_COOKIE[$this->cookiename];
+        $result = $this->ip_check ? $_SERVER['REMOTE_ADDR'] == $this->ip : true;
+
+        if (!$result) {
+            $this->log("IP check failed for " . $this->key . "; expected " . $this->ip . "; got " . $_SERVER['REMOTE_ADDR']);
         }
-      }
+
+        if ($result && $this->_mkcookie($this->now) != $this->cookie) {
+            $this->log("Session auth check failed for " . $this->key . "; timeslot = " . date('Y-m-d H:i:s', $this->now));
+            $result = false;
+
+            // Check if using id from a previous time slot
+            for ($i = 1; $i <= 2; $i++) {
+                $prev = $this->now - ($this->lifetime / 2) * $i;
+                if ($this->_mkcookie($prev) == $this->cookie) {
+                    $this->log("Send new auth cookie for " . $this->key . ": " . $this->cookie);
+                    $this->set_auth_cookie();
+                    $result = true;
+                }
+            }
+        }
+
+        if (!$result) {
+            $this->log("Session authentication failed for " . $this->key
+                . "; invalid auth cookie sent; timeslot = " . date('Y-m-d H:i:s', $prev));
+        }
+
+        return $result;
+    }
+
+
+    /**
+     * Set session authentication cookie
+     */
+    function set_auth_cookie()
+    {
+        $this->cookie = $this->_mkcookie($this->now);
+        rcube_utils::setcookie($this->cookiename, $this->cookie, 0);
+        $_COOKIE[$this->cookiename] = $this->cookie;
     }
 
-    if (!$result)
-      $this->log("Session authentication failed for " . $this->key . "; invalid auth cookie sent; timeslot = " . date('Y-m-d H:i:s', $prev));
-
-    return $result;
-  }
-
-
-  /**
-   * Set session authentication cookie
-   */
-  function set_auth_cookie()
-  {
-    $this->cookie = $this->_mkcookie($this->now);
-    rcube_utils::setcookie($this->cookiename, $this->cookie, 0);
-    $_COOKIE[$this->cookiename] = $this->cookie;
-  }
-
-
-  /**
-   * Create session cookie from session data
-   *
-   * @param int Time slot to use
-   */
-  function _mkcookie($timeslot)
-  {
-    $auth_string = "$this->key,$this->secret,$timeslot";
-    return "S" . (function_exists('sha1') ? sha1($auth_string) : md5($auth_string));
-  }
-
-  /**
-   * Writes debug information to the log
-   */
-  function log($line)
-  {
-    if ($this->logging)
-      rcube::write_log('session', $line);
-  }
 
+    /**
+     * Create session cookie from session data
+     *
+     * @param int Time slot to use
+     */
+    function _mkcookie($timeslot)
+    {
+        $auth_string = "$this->key,$this->secret,$timeslot";
+        return "S" . (function_exists('sha1') ? sha1($auth_string) : md5($auth_string));
+    }
+
+    /**
+     * Writes debug information to the log
+     */
+    function log($line)
+    {
+        if ($this->logging) {
+            rcube::write_log('session', $line);
+        }
+    }
 }
diff --git a/program/lib/Roundcube/rcube_string_replacer.php b/program/lib/Roundcube/rcube_string_replacer.php
index 68288f54c..0fe982b26 100644
--- a/program/lib/Roundcube/rcube_string_replacer.php
+++ b/program/lib/Roundcube/rcube_string_replacer.php
@@ -24,164 +24,164 @@
  */
 class rcube_string_replacer
 {
-  public static $pattern = '/##str_replacement\[([0-9]+)\]##/';
-  public $mailto_pattern;
-  public $link_pattern;
-  private $values = array();
-
-
-  function __construct()
-  {
-    // Simplified domain expression for UTF8 characters handling
-    // Support unicode/punycode in top-level domain part
-    $utf_domain = '[^?&@"\'\\/()\s\r\t\n]+\\.?([^\\x00-\\x2f\\x3b-\\x40\\x5b-\\x60\\x7b-\\x7f]{2,}|xn--[a-zA-Z0-9]{2,})';
-    $url1 = '.:;,';
-    $url2 = 'a-zA-Z0-9%=#$@+?!&\\/_~\\[\\]{}\*-';
-
-    $this->link_pattern = "/([\w]+:\/\/|\W[Ww][Ww][Ww]\.|^[Ww][Ww][Ww]\.)($utf_domain([$url1]?[$url2]+)*)/";
-    $this->mailto_pattern = "/("
-        ."[-\w!\#\$%&\'*+~\/^`|{}=]+(?:\.[-\w!\#\$%&\'*+~\/^`|{}=]+)*"  // local-part
-        ."@$utf_domain"                                                 // domain-part
-        ."(\?[$url1$url2]+)?"                                           // e.g. ?subject=test...
-        .")/";
-  }
-
-  /**
-   * Add a string to the internal list
-   *
-   * @param string String value 
-   * @return int Index of value for retrieval
-   */
-  public function add($str)
-  {
-    $i = count($this->values);
-    $this->values[$i] = $str;
-    return $i;
-  }
-
-  /**
-   * Build replacement string
-   */
-  public function get_replacement($i)
-  {
-    return '##str_replacement['.$i.']##';
-  }
-
-  /**
-   * Callback function used to build HTML links around URL strings
-   *
-   * @param array Matches result from preg_replace_callback
-   * @return int Index of saved string value
-   */
-  public function link_callback($matches)
-  {
-    $i = -1;
-    $scheme = strtolower($matches[1]);
-
-    if (preg_match('!^(http|ftp|file)s?://!i', $scheme)) {
-      $url = $matches[1] . $matches[2];
+    public static $pattern = '/##str_replacement\[([0-9]+)\]##/';
+    public $mailto_pattern;
+    public $link_pattern;
+    private $values = array();
+
+
+    function __construct()
+    {
+        // Simplified domain expression for UTF8 characters handling
+        // Support unicode/punycode in top-level domain part
+        $utf_domain = '[^?&@"\'\\/()\s\r\t\n]+\\.?([^\\x00-\\x2f\\x3b-\\x40\\x5b-\\x60\\x7b-\\x7f]{2,}|xn--[a-zA-Z0-9]{2,})';
+        $url1       = '.:;,';
+        $url2       = 'a-zA-Z0-9%=#$@+?!&\\/_~\\[\\]{}\*-';
+
+        $this->link_pattern = "/([\w]+:\/\/|\W[Ww][Ww][Ww]\.|^[Ww][Ww][Ww]\.)($utf_domain([$url1]?[$url2]+)*)/";
+        $this->mailto_pattern = "/("
+            ."[-\w!\#\$%&\'*+~\/^`|{}=]+(?:\.[-\w!\#\$%&\'*+~\/^`|{}=]+)*"  // local-part
+            ."@$utf_domain"                                                 // domain-part
+            ."(\?[$url1$url2]+)?"                                           // e.g. ?subject=test...
+            .")/";
     }
-    else if (preg_match('/^(\W*)(www\.)$/i', $matches[1], $m)) {
-      $url        = $m[2] . $matches[2];
-      $url_prefix = 'http://';
-      $prefix     = $m[1];
+
+    /**
+     * Add a string to the internal list
+     *
+     * @param string String value 
+     * @return int Index of value for retrieval
+     */
+    public function add($str)
+    {
+        $i = count($this->values);
+        $this->values[$i] = $str;
+        return $i;
     }
 
-    if ($url) {
-      $suffix = $this->parse_url_brackets($url);
-      $i = $this->add($prefix . html::a(array(
-          'href' => $url_prefix . $url,
-          'target' => '_blank'
-        ), rcube::Q($url)) . $suffix);
+    /**
+     * Build replacement string
+     */
+    public function get_replacement($i)
+    {
+        return '##str_replacement['.$i.']##';
     }
 
-    // Return valid link for recognized schemes, otherwise, return the unmodified string for unrecognized schemes.
-    return $i >= 0 ? $this->get_replacement($i) : $matches[0];
-  }
-
-  /**
-   * Callback function used to build mailto: links around e-mail strings
-   *
-   * @param array Matches result from preg_replace_callback
-   * @return int Index of saved string value
-   */
-  public function mailto_callback($matches)
-  {
-    $href   = $matches[1];
-    $suffix = $this->parse_url_brackets($href);
-    $i = $this->add(html::a('mailto:' . $href, rcube::Q($href)) . $suffix);
-
-    return $i >= 0 ? $this->get_replacement($i) : '';
-  }
-
-  /**
-   * Look up the index from the preg_replace matches array
-   * and return the substitution value.
-   *
-   * @param array Matches result from preg_replace_callback
-   * @return string Value at index $matches[1]
-   */
-  public function replace_callback($matches)
-  {
-    return $this->values[$matches[1]];
-  }
-
-  /**
-   * Replace all defined (link|mailto) patterns with replacement string
-   *
-   * @param string $str Text
-   *
-   * @return string Text
-   */
-  public function replace($str)
-  {
-    // search for patterns like links and e-mail addresses
-    $str = preg_replace_callback($this->link_pattern, array($this, 'link_callback'), $str);
-    $str = preg_replace_callback($this->mailto_pattern, array($this, 'mailto_callback'), $str);
-
-    return $str;
-  }
-
-  /**
-   * Replace substituted strings with original values
-   */
-  public function resolve($str)
-  {
-    return preg_replace_callback(self::$pattern, array($this, 'replace_callback'), $str);
-  }
-
-  /**
-   * Fixes bracket characters in URL handling
-   */
-  public static function parse_url_brackets(&$url)
-  {
-    // #1487672: special handling of square brackets,
-    // URL regexp allows [] characters in URL, for example:
-    // "http://example.com/?a[b]=c". However we need to handle
-    // properly situation when a bracket is placed at the end
-    // of the link e.g. "[http://example.com]"
-    if (preg_match('/(\\[|\\])/', $url)) {
-      $in = false;
-      for ($i=0, $len=strlen($url); $i<$len; $i++) {
-        if ($url[$i] == '[') {
-          if ($in)
-            break;
-          $in = true;
+    /**
+     * Callback function used to build HTML links around URL strings
+     *
+     * @param array Matches result from preg_replace_callback
+     * @return int Index of saved string value
+     */
+    public function link_callback($matches)
+    {
+        $i = -1;
+        $scheme = strtolower($matches[1]);
+
+        if (preg_match('!^(http|ftp|file)s?://!i', $scheme)) {
+            $url = $matches[1] . $matches[2];
+        }
+        else if (preg_match('/^(\W*)(www\.)$/i', $matches[1], $m)) {
+            $url        = $m[2] . $matches[2];
+            $url_prefix = 'http://';
+            $prefix     = $m[1];
         }
-        else if ($url[$i] == ']') {
-          if (!$in)
-            break;
-          $in = false;
+
+        if ($url) {
+            $suffix = $this->parse_url_brackets($url);
+            $i = $this->add($prefix . html::a(array(
+                'href'   => $url_prefix . $url,
+                'target' => '_blank'
+            ), rcube::Q($url)) . $suffix);
         }
-      }
 
-      if ($i<$len) {
-        $suffix = substr($url, $i);
-        $url    = substr($url, 0, $i);
-      }
+        // Return valid link for recognized schemes, otherwise
+        // return the unmodified string for unrecognized schemes.
+        return $i >= 0 ? $this->get_replacement($i) : $matches[0];
     }
 
-    return $suffix;
-  }
+    /**
+     * Callback function used to build mailto: links around e-mail strings
+     *
+     * @param array Matches result from preg_replace_callback
+     * @return int Index of saved string value
+     */
+    public function mailto_callback($matches)
+    {
+        $href   = $matches[1];
+        $suffix = $this->parse_url_brackets($href);
+        $i = $this->add(html::a('mailto:' . $href, rcube::Q($href)) . $suffix);
+
+        return $i >= 0 ? $this->get_replacement($i) : '';
+    }
 
+    /**
+     * Look up the index from the preg_replace matches array
+     * and return the substitution value.
+     *
+     * @param array Matches result from preg_replace_callback
+     * @return string Value at index $matches[1]
+     */
+    public function replace_callback($matches)
+    {
+        return $this->values[$matches[1]];
+    }
+
+    /**
+     * Replace all defined (link|mailto) patterns with replacement string
+     *
+     * @param string $str Text
+     *
+     * @return string Text
+     */
+    public function replace($str)
+    {
+        // search for patterns like links and e-mail addresses
+        $str = preg_replace_callback($this->link_pattern, array($this, 'link_callback'), $str);
+        $str = preg_replace_callback($this->mailto_pattern, array($this, 'mailto_callback'), $str);
+
+        return $str;
+    }
+
+    /**
+     * Replace substituted strings with original values
+     */
+    public function resolve($str)
+    {
+        return preg_replace_callback(self::$pattern, array($this, 'replace_callback'), $str);
+    }
+
+    /**
+     * Fixes bracket characters in URL handling
+     */
+    public static function parse_url_brackets(&$url)
+    {
+        // #1487672: special handling of square brackets,
+        // URL regexp allows [] characters in URL, for example:
+        // "http://example.com/?a[b]=c". However we need to handle
+        // properly situation when a bracket is placed at the end
+        // of the link e.g. "[http://example.com]"
+        if (preg_match('/(\\[|\\])/', $url)) {
+            $in = false;
+            for ($i=0, $len=strlen($url); $i<$len; $i++) {
+                if ($url[$i] == '[') {
+                    if ($in)
+                        break;
+                    $in = true;
+                }
+                else if ($url[$i] == ']') {
+                    if (!$in)
+                        break;
+                    $in = false;
+                }
+            }
+
+            if ($i < $len) {
+                $suffix = substr($url, $i);
+                $url    = substr($url, 0, $i);
+            }
+        }
+
+        return $suffix;
+    }
 }
-- 
cgit v1.2.3


From c5d7c941aa16b5bd98cfd56f12bbe7c39bddc608 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Wed, 19 Dec 2012 11:43:52 +0100
Subject: Add unsupported alternative parts to attachments list (#1488870)

---
 CHANGELOG                               |  1 +
 program/lib/Roundcube/rcube_message.php | 63 ++++++++++++++++++++++++---------
 2 files changed, 47 insertions(+), 17 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/CHANGELOG b/CHANGELOG
index 41e266d0c..c4e63aaeb 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
 CHANGELOG Roundcube Webmail
 ===========================
 
+- Add unsupported alternative parts to attachments list (#1488870)
 - Add Compose button on message view page (#1488747)
 - Display 'Sender' header in message preview
 - Plugin API: Added message_before_send hook
diff --git a/program/lib/Roundcube/rcube_message.php b/program/lib/Roundcube/rcube_message.php
index f41493d12..08b94d8d9 100644
--- a/program/lib/Roundcube/rcube_message.php
+++ b/program/lib/Roundcube/rcube_message.php
@@ -106,6 +106,7 @@ class rcube_message
         if (!empty($this->headers->structure)) {
             $this->get_mime_numbers($this->headers->structure);
             $this->parse_structure($this->headers->structure);
+            $this->parse_attachments();
         }
         else {
             $this->body = $this->storage->get_body($uid);
@@ -333,7 +334,7 @@ class rcube_message
         if ($recursive && is_array($structure->headers) && isset($structure->headers['subject'])) {
             $c = new stdClass;
             $c->type = 'headers';
-            $c->headers = &$structure->headers;
+            $c->headers = $structure->headers;
             $this->parts[] = $c;
         }
 
@@ -351,7 +352,7 @@ class rcube_message
         // print body if message doesn't have multiple parts
         if ($message_ctype_primary == 'text' && !$recursive) {
             $structure->type = 'content';
-            $this->parts[] = &$structure;
+            $this->parts[] = $structure;
 
             // Parse simple (plain text) message body
             if ($message_ctype_secondary == 'plain')
@@ -363,32 +364,39 @@ class rcube_message
         // the same for pgp signed messages
         else if ($mimetype == 'application/pgp' && !$recursive) {
             $structure->type = 'content';
-            $this->parts[] = &$structure;
+            $this->parts[] = $structure;
         }
         // message contains (more than one!) alternative parts
         else if ($mimetype == 'multipart/alternative'
             && is_array($structure->parts) && count($structure->parts) > 1
         ) {
-            // get html/plaintext parts
-            $plain_part = $html_part = $print_part = $related_part = null;
+            $plain_part   = null;
+            $html_part    = null;
+            $print_part   = null;
+            $related_part = null;
+            $attach_part  = null;
 
+            // get html/plaintext parts, other add to attachments list
             foreach ($structure->parts as $p => $sub_part) {
                 $sub_mimetype = $sub_part->mimetype;
+                $is_multipart = in_array($sub_mimetype, array('multipart/related', 'multipart/mixed', 'multipart/alternative'));
 
                 // skip empty text parts
-                if (!$sub_part->size && preg_match('#^text/(plain|html|enriched)$#', $sub_mimetype)) {
+                if (!$sub_part->size && !$is_multipart) {
                     continue;
                 }
 
                 // check if sub part is
-                if ($sub_mimetype == 'text/plain')
+                if ($is_multipart)
+                    $related_part = $p;
+                else if ($sub_mimetype == 'text/plain')
                     $plain_part = $p;
                 else if ($sub_mimetype == 'text/html')
                     $html_part = $p;
                 else if ($sub_mimetype == 'text/enriched')
                     $enriched_part = $p;
-                else if (in_array($sub_mimetype, array('multipart/related', 'multipart/mixed', 'multipart/alternative')))
-                    $related_part = $p;
+                else
+                    $attach_part = $p;
             }
 
             // parse related part (alternative part could be in here)
@@ -404,13 +412,13 @@ class rcube_message
 
             // choose html/plain part to print
             if ($html_part !== null && $this->opt['prefer_html']) {
-                $print_part = &$structure->parts[$html_part];
+                $print_part = $structure->parts[$html_part];
             }
             else if ($enriched_part !== null) {
-                $print_part = &$structure->parts[$enriched_part];
+                $print_part = $structure->parts[$enriched_part];
             }
             else if ($plain_part !== null) {
-                $print_part = &$structure->parts[$plain_part];
+                $print_part = $structure->parts[$plain_part];
             }
 
             // add the right message body
@@ -432,11 +440,16 @@ class rcube_message
 
             // add html part as attachment
             if ($html_part !== null && $structure->parts[$html_part] !== $print_part) {
-                $html_part = &$structure->parts[$html_part];
+                $html_part = $structure->parts[$html_part];
                 $html_part->mimetype = 'text/html';
 
                 $this->attachments[] = $html_part;
             }
+
+            // add unsupported/unrecognized parts to attachments list
+            if ($attach_part) {
+                $this->attachments[] = $structure->parts[$attach_part];
+            }
         }
         // this is an ecrypted message -> create a plaintext body with the according message
         else if ($mimetype == 'multipart/encrypted') {
@@ -561,9 +574,6 @@ class rcube_message
                     // regular attachment with valid content type
                     // (content-type name regexp according to RFC4288.4.2)
                     else if (preg_match('/^[a-z0-9!#$&.+^_-]+\/[a-z0-9!#$&.+^_-]+$/i', $part_mimetype)) {
-                        if (!$mail_part->filename)
-                            $mail_part->filename = 'Part '.$mail_part->mime_id;
-
                         $this->attachments[] = $mail_part;
                     }
                     // attachment with invalid content type
@@ -628,12 +638,31 @@ class rcube_message
         }
         // message is a single part non-text (without filename)
         else if (preg_match('/application\//i', $mimetype)) {
-            $structure->filename = 'Part '.$structure->mime_id;
             $this->attachments[] = $structure;
         }
     }
 
 
+    /**
+     * Parse attachment parts
+     */
+    private function parse_attachments()
+    {
+        // Attachment must have a name
+        foreach ($this->attachments as $attachment) {
+            if (!$attachment->filename) {
+                $ext = rcube_mime::get_mime_extensions($attachment->mimetype);
+                $ext = array_shift($ext);
+
+                $attachment->filename = 'Part_' . $attachment->mime_id;
+                if ($ext) {
+                    $attachment->filename .= '.' . $ext;
+                }
+            }
+        }
+    }
+
+
     /**
      * Fill aflat array with references to all parts, indexed by part numbers
      *
-- 
cgit v1.2.3


From 9ac96015f274b9451377d05001097d0bb7526a27 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Wed, 19 Dec 2012 12:27:52 +0100
Subject: Better GD module functions detection, should fix "Call to undefined
 function imagecreatefromjpeg()" error

---
 program/lib/Roundcube/rcube_image.php | 28 +++++++++++++++++-----------
 1 file changed, 17 insertions(+), 11 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_image.php b/program/lib/Roundcube/rcube_image.php
index ad96842d2..9695022da 100644
--- a/program/lib/Roundcube/rcube_image.php
+++ b/program/lib/Roundcube/rcube_image.php
@@ -128,17 +128,20 @@ class rcube_image
         }
 
         // use GD extension
-        $gd_types = array(IMAGETYPE_JPEG, IMAGETYPE_GIF, IMAGETYPE_PNG);
-        if ($props['gd_type'] && in_array($props['gd_type'], $gd_types)) {
-            if ($props['gd_type'] == IMAGETYPE_JPEG) {
+        if ($props['gd_type']) {
+            if ($props['gd_type'] == IMAGETYPE_JPEG && function_exists('imagecreatefromjpeg')) {
                 $image = imagecreatefromjpeg($this->image_file);
             }
-            elseif($props['gd_type'] == IMAGETYPE_GIF) {
+            else if($props['gd_type'] == IMAGETYPE_GIF && function_exists('imagecreatefromgif')) {
                 $image = imagecreatefromgif($this->image_file);
             }
-            elseif($props['gd_type'] == IMAGETYPE_PNG) {
+            else if($props['gd_type'] == IMAGETYPE_PNG && function_exists('imagecreatefrompng')) {
                 $image = imagecreatefrompng($this->image_file);
             }
+            else {
+                // @TODO: print error to the log?
+                return false;
+            }
 
             $scale  = $size / max($props['width'], $props['height']);
             $width  = $props['width']  * $scale;
@@ -216,19 +219,22 @@ class rcube_image
         }
 
         // use GD extension (TIFF isn't supported)
-        $props    = $this->props();
-        $gd_types = array(IMAGETYPE_JPEG, IMAGETYPE_GIF, IMAGETYPE_PNG);
+        $props = $this->props();
 
-        if ($props['gd_type'] && in_array($props['gd_type'], $gd_types)) {
-            if ($props['gd_type'] == IMAGETYPE_JPEG) {
+        if ($props['gd_type']) {
+            if ($props['gd_type'] == IMAGETYPE_JPEG && function_exists('imagecreatefromjpeg')) {
                 $image = imagecreatefromjpeg($this->image_file);
             }
-            else if ($props['gd_type'] == IMAGETYPE_GIF) {
+            else if ($props['gd_type'] == IMAGETYPE_GIF && function_exists('imagecreatefromgif')) {
                 $image = imagecreatefromgif($this->image_file);
             }
-            else if ($props['gd_type'] == IMAGETYPE_PNG) {
+            else if ($props['gd_type'] == IMAGETYPE_PNG && function_exists('imagecreatefrompng')) {
                 $image = imagecreatefrompng($this->image_file);
             }
+            else {
+                // @TODO: print error to the log?
+                return false;
+            }
 
             if ($type == self::TYPE_JPG) {
                 $result = imagejpeg($image, $filename, 75);
-- 
cgit v1.2.3


From c23dc87f2b76ac76968c73bc9f31920b316282c6 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Wed, 19 Dec 2012 13:01:45 +0100
Subject: Don't display message parts with unsupported text type, e.g.
 text/calendar

---
 program/lib/Roundcube/rcube_message.php | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_message.php b/program/lib/Roundcube/rcube_message.php
index 08b94d8d9..c45dbfcd0 100644
--- a/program/lib/Roundcube/rcube_message.php
+++ b/program/lib/Roundcube/rcube_message.php
@@ -351,15 +351,22 @@ class rcube_message
 
         // print body if message doesn't have multiple parts
         if ($message_ctype_primary == 'text' && !$recursive) {
+            // parts with unsupported type add to attachments list
+            if (!in_array($message_ctype_secondary, array('plain', 'html', 'enriched'))) {
+                $this->attachments[] = $structure;
+                return;
+            }
+
             $structure->type = 'content';
             $this->parts[] = $structure;
 
             // Parse simple (plain text) message body
-            if ($message_ctype_secondary == 'plain')
+            if ($message_ctype_secondary == 'plain') {
                 foreach ((array)$this->uu_decode($structure) as $uupart) {
                     $this->mime_parts[$uupart->mime_id] = $uupart;
                     $this->attachments[] = $uupart;
                 }
+            }
         }
         // the same for pgp signed messages
         else if ($mimetype == 'application/pgp' && !$recursive) {
-- 
cgit v1.2.3


From a8ffab3f4ff54af41c9041a30eb989954d6ffc17 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Wed, 19 Dec 2012 14:15:11 +0100
Subject: Fix Call to undefined method rcube_db_sqlite::_get_result()

---
 program/lib/Roundcube/rcube_db_sqlite.php | 7 +------
 1 file changed, 1 insertion(+), 6 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_db_sqlite.php b/program/lib/Roundcube/rcube_db_sqlite.php
index 326c6a710..145b8a371 100644
--- a/program/lib/Roundcube/rcube_db_sqlite.php
+++ b/program/lib/Roundcube/rcube_db_sqlite.php
@@ -120,12 +120,7 @@ class rcube_db_sqlite extends rcube_db
             $q = $this->query('SELECT name FROM sqlite_master'
                 .' WHERE type = \'table\' ORDER BY name');
 
-            if ($res = $this->_get_result($q)) {
-                $this->tables = $res->fetchAll(PDO::FETCH_COLUMN, 0);
-            }
-            else {
-                $this->tables = array();
-            }
+            $this->tables = $q ? $q->fetchAll(PDO::FETCH_COLUMN, 0) : array();
         }
 
         return $this->tables;
-- 
cgit v1.2.3


From a079269166d120bcbcb33d34f4b1c8f60d102e32 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Thu, 20 Dec 2012 08:53:48 +0100
Subject: Fix version comparisons with -stable suffix (#1488876)

---
 CHANGELOG                           |  1 +
 bin/installto.sh                    |  2 +-
 bin/update.sh                       |  4 ++--
 installer/rcube_install.php         |  6 +++---
 program/lib/Roundcube/bootstrap.php | 16 ++++++++++++++++
 tests/Framework/Bootstrap.php       |  8 ++++++++
 6 files changed, 31 insertions(+), 6 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/CHANGELOG b/CHANGELOG
index c4e63aaeb..02e20455c 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
 CHANGELOG Roundcube Webmail
 ===========================
 
+- Fix version comparisons with -stable suffix (#1488876)
 - Add unsupported alternative parts to attachments list (#1488870)
 - Add Compose button on message view page (#1488747)
 - Display 'Sender' header in message preview
diff --git a/bin/installto.sh b/bin/installto.sh
index de96bf004..e6cf79d7d 100755
--- a/bin/installto.sh
+++ b/bin/installto.sh
@@ -35,7 +35,7 @@ if (!preg_match('/define\(.RCMAIL_VERSION.,\s*.([0-9.]+[a-z-]*)/', $iniset, $m))
 
 $oldversion = $m[1];
 
-if (version_compare($oldversion, RCMAIL_VERSION, '>='))
+if (version_compare(version_parse($oldversion), version_parse(RCMAIL_VERSION), '>='))
   die("Installation at target location is up-to-date!\n");
 
 echo "Upgrading from $oldversion. Do you want to continue? (y/N)\n";
diff --git a/bin/update.sh b/bin/update.sh
index 59aa596dd..2015aa904 100755
--- a/bin/update.sh
+++ b/bin/update.sh
@@ -34,7 +34,7 @@ if (!$opts['version']) {
     $opts['version'] = $input;
 }
 
-if ($opts['version'] && version_compare($opts['version'], RCMAIL_VERSION, '>'))
+if ($opts['version'] && version_compare(version_parse($opts['version']), version_parse(RCMAIL_VERSION), '>'))
   die("Nothing to be done here. Bye!\n");
 
 
@@ -169,7 +169,7 @@ if ($RCI->configured) {
   }
   
   // index contacts for fulltext searching
-  if (version_compare($opts['version'], '0.6', '<')) {
+  if (version_compare(version_parse($opts['version']), '0.6.0', '<')) {
     system(INSTALL_PATH . 'bin/indexcontacts.sh');
   }
   
diff --git a/installer/rcube_install.php b/installer/rcube_install.php
index dfd63562d..530be3ebe 100644
--- a/installer/rcube_install.php
+++ b/installer/rcube_install.php
@@ -633,8 +633,8 @@ class rcube_install
    */
   function update_db($DB, $version)
   {
-    $version = strtolower($version);
-    $engine = isset($this->db_map[$DB->db_provider]) ? $this->db_map[$DB->db_provider] : $DB->db_provider;
+    $version = version_parse(strtolower($version));
+    $engine  = isset($this->db_map[$DB->db_provider]) ? $this->db_map[$DB->db_provider] : $DB->db_provider;
 
     // read schema file from /SQL/*
     $fname = INSTALL_PATH . "SQL/$engine.update.sql";
@@ -643,7 +643,7 @@ class rcube_install
       foreach ($lines as $line) {
         $is_comment = preg_match('/^--/', $line);
         if (!$from && $is_comment && preg_match('/from version\s([0-9.]+[a-z-]*)/', $line, $m)) {
-          $v = strtolower($m[1]);
+          $v = version_parse(strtolower($m[1]));
           if ($v == $version || version_compare($version, $v, '<='))
             $from = true;
         }
diff --git a/program/lib/Roundcube/bootstrap.php b/program/lib/Roundcube/bootstrap.php
index 18c07ddab..530a7f855 100644
--- a/program/lib/Roundcube/bootstrap.php
+++ b/program/lib/Roundcube/bootstrap.php
@@ -357,6 +357,22 @@ function format_email($email)
 }
 
 
+/**
+ * Fix version number so it can be used correctly in version_compare()
+ *
+ * @param string $version Version number string
+ *
+ * @param return Version number string
+ */
+function version_parse($version)
+{
+    return str_replace(
+        array('-stable', '-git'),
+        array('.0', '.99'),
+        $version);
+}
+
+
 /**
  * mbstring replacement functions
  */
diff --git a/tests/Framework/Bootstrap.php b/tests/Framework/Bootstrap.php
index d18fd371b..904be7e3b 100644
--- a/tests/Framework/Bootstrap.php
+++ b/tests/Framework/Bootstrap.php
@@ -207,4 +207,12 @@ class Framework_Bootstrap extends PHPUnit_Framework_TestCase
         $this->assertFalse($result, "Invalid ASCII (UTF-8 character [2])");
     }
 
+    /**
+     * bootstrap.php: version_parse()
+     */
+    function test_version_parse()
+    {
+        $this->assertEquals('0.9.0', version_parse('0.9-stable'));
+        $this->assertEquals('0.9.99', version_parse('0.9-git'));
+    }
 }
-- 
cgit v1.2.3


From a61326c141816b5365613c206ef9ac350bfd9935 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Thu, 20 Dec 2012 19:51:37 +0100
Subject: Fix locking issue in SQLite driver (#1488874)

---
 program/lib/Roundcube/rcube_db.php | 4 ++++
 1 file changed, 4 insertions(+)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_db.php b/program/lib/Roundcube/rcube_db.php
index 47ddc81a6..9283bb0d4 100644
--- a/program/lib/Roundcube/rcube_db.php
+++ b/program/lib/Roundcube/rcube_db.php
@@ -400,6 +400,10 @@ class rcube_db
 
         $this->debug($query);
 
+        // destroy reference to previous result, required for SQLite driver (#1488874)
+        $this->last_result = null;
+
+        // send query
         $query = $this->dbh->query($query);
 
         if ($query === false) {
-- 
cgit v1.2.3


From c4781306a54006727b05a5b8a5414e09c51b8bef Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Fri, 21 Dec 2012 09:13:11 +0100
Subject: CS fixes

---
 program/lib/Roundcube/rcube_plugin.php     | 647 +++++++++++----------
 program/lib/Roundcube/rcube_plugin_api.php | 891 +++++++++++++++--------------
 2 files changed, 782 insertions(+), 756 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_plugin.php b/program/lib/Roundcube/rcube_plugin.php
index 06247d456..66e77cce2 100644
--- a/program/lib/Roundcube/rcube_plugin.php
+++ b/program/lib/Roundcube/rcube_plugin.php
@@ -3,7 +3,7 @@
 /*
  +-----------------------------------------------------------------------+
  | This file is part of the Roundcube Webmail client                     |
- | Copyright (C) 2008-2009, The Roundcube Dev Team                       |
+ | Copyright (C) 2008-2012, The Roundcube Dev Team                       |
  |                                                                       |
  | Licensed under the GNU General Public License version 3 or            |
  | any later version with exceptions for skins & plugins.                |
@@ -25,334 +25,353 @@
  */
 abstract class rcube_plugin
 {
-  /**
-   * Class name of the plugin instance
-   *
-   * @var string
-   */
-  public $ID;
-
-  /**
-   * Instance of Plugin API
-   *
-   * @var rcube_plugin_api
-   */
-  public $api;
-
-  /**
-   * Regular expression defining task(s) to bind with 
-   *
-   * @var string
-   */
-  public $task;
-
-  /**
-   * Disables plugin in AJAX requests
-   *
-   * @var boolean
-   */
-  public $noajax = false;
-
-  /**
-   * Disables plugin in framed mode
-   *
-   * @var boolean
-   */
-  public $noframe = false;
-
-  protected $home;
-  protected $urlbase;
-  private $mytask;
-
-
-  /**
-   * Default constructor.
-   *
-   * @param rcube_plugin_api $api Plugin API
-   */
-  public function __construct($api)
-  {
-    $this->ID = get_class($this);
-    $this->api = $api;
-    $this->home = $api->dir . $this->ID;
-    $this->urlbase = $api->url . $this->ID . '/';
-  }
-
-  /**
-   * Initialization method, needs to be implemented by the plugin itself
-   */
-  abstract function init();
-
-
-  /**
-   * Attempt to load the given plugin which is required for the current plugin
-   *
-   * @param string Plugin name
-   * @return boolean True on success, false on failure
-   */
-  public function require_plugin($plugin_name)
-  {
-    return $this->api->load_plugin($plugin_name);
-  }
-
-
-  /**
-   * Load local config file from plugins directory.
-   * The loaded values are patched over the global configuration.
-   *
-   * @param string $fname Config file name relative to the plugin's folder
-   * @return boolean True on success, false on failure
-   */
-  public function load_config($fname = 'config.inc.php')
-  {
-    $fpath = $this->home.'/'.$fname;
-    $rcube = rcube::get_instance();
-    if (is_file($fpath) && !$rcube->config->load_from_file($fpath)) {
-      rcube::raise_error(array(
-        'code' => 527, 'type' => 'php',
-        'file' => __FILE__, 'line' => __LINE__,
-        'message' => "Failed to load config from $fpath"), true, false);
-      return false;
+    /**
+     * Class name of the plugin instance
+     *
+     * @var string
+     */
+    public $ID;
+
+    /**
+     * Instance of Plugin API
+     *
+     * @var rcube_plugin_api
+     */
+    public $api;
+
+    /**
+     * Regular expression defining task(s) to bind with 
+     *
+     * @var string
+     */
+    public $task;
+
+    /**
+     * Disables plugin in AJAX requests
+     *
+     * @var boolean
+     */
+    public $noajax = false;
+
+    /**
+     * Disables plugin in framed mode
+     *
+     * @var boolean
+     */
+    public $noframe = false;
+
+    protected $home;
+    protected $urlbase;
+    private $mytask;
+
+
+    /**
+     * Default constructor.
+     *
+     * @param rcube_plugin_api $api Plugin API
+     */
+    public function __construct($api)
+    {
+        $this->ID      = get_class($this);
+        $this->api     = $api;
+        $this->home    = $api->dir . $this->ID;
+        $this->urlbase = $api->url . $this->ID . '/';
     }
 
-    return true;
-  }
-
-  /**
-   * Register a callback function for a specific (server-side) hook
-   *
-   * @param string $hook Hook name
-   * @param mixed  $callback Callback function as string or array with object reference and method name
-   */
-  public function add_hook($hook, $callback)
-  {
-    $this->api->register_hook($hook, $callback);
-  }
-
-  /**
-   * Unregister a callback function for a specific (server-side) hook.
-   *
-   * @param string $hook Hook name
-   * @param mixed  $callback Callback function as string or array with object reference and method name
-   */
-  public function remove_hook($hook, $callback)
-  {
-    $this->api->unregister_hook($hook, $callback);
-  }
-
-  /**
-   * Load localized texts from the plugins dir
-   *
-   * @param string $dir Directory to search in
-   * @param mixed  $add2client Make texts also available on the client (array with list or true for all)
-   */
-  public function add_texts($dir, $add2client = false)
-  {
-    $domain = $this->ID;
-    $lang   = $_SESSION['language'];
-    $langs  = array_unique(array('en_US', $lang));
-    $locdir = slashify(realpath(slashify($this->home) . $dir));
-    $texts  = array();
-
-    // Language aliases used to find localization in similar lang, see below
-    $aliases = array(
-        'de_CH' => 'de_DE',
-        'es_AR' => 'es_ES',
-        'fa_AF' => 'fa_IR',
-        'nl_BE' => 'nl_NL',
-        'pt_BR' => 'pt_PT',
-        'zh_CN' => 'zh_TW',
-    );
-
-    // use buffering to handle empty lines/spaces after closing PHP tag
-    ob_start();
-
-    foreach ($langs as $lng) {
-      $fpath = $locdir . $lng . '.inc';
-      if (is_file($fpath) && is_readable($fpath)) {
-        include $fpath;
-        $texts = (array)$labels + (array)$messages + (array)$texts;
-      }
-      else if ($lng != 'en_US') {
-        // Find localization in similar language (#1488401)
-        $alias = null;
-        if (!empty($aliases[$lng])) {
-          $alias = $aliases[$lng];
+    /**
+     * Initialization method, needs to be implemented by the plugin itself
+     */
+    abstract function init();
+
+    /**
+     * Attempt to load the given plugin which is required for the current plugin
+     *
+     * @param string Plugin name
+     * @return boolean True on success, false on failure
+     */
+    public function require_plugin($plugin_name)
+    {
+        return $this->api->load_plugin($plugin_name);
+    }
+
+    /**
+     * Load local config file from plugins directory.
+     * The loaded values are patched over the global configuration.
+     *
+     * @param string $fname Config file name relative to the plugin's folder
+     *
+     * @return boolean True on success, false on failure
+     */
+    public function load_config($fname = 'config.inc.php')
+    {
+        $fpath = $this->home.'/'.$fname;
+        $rcube = rcube::get_instance();
+
+        if (is_file($fpath) && !$rcube->config->load_from_file($fpath)) {
+            rcube::raise_error(array(
+                'code' => 527, 'type' => 'php',
+                'file' => __FILE__, 'line' => __LINE__,
+                'message' => "Failed to load config from $fpath"), true, false);
+            return false;
         }
-        else if ($key = array_search($lng, $aliases)) {
-          $alias = $key;
+
+        return true;
+    }
+
+    /**
+     * Register a callback function for a specific (server-side) hook
+     *
+     * @param string $hook     Hook name
+     * @param mixed  $callback Callback function as string or array
+     *                         with object reference and method name
+     */
+    public function add_hook($hook, $callback)
+    {
+        $this->api->register_hook($hook, $callback);
+    }
+
+    /**
+     * Unregister a callback function for a specific (server-side) hook.
+     *
+     * @param string $hook     Hook name
+     * @param mixed  $callback Callback function as string or array
+     *                         with object reference and method name
+     */
+    public function remove_hook($hook, $callback)
+    {
+        $this->api->unregister_hook($hook, $callback);
+    }
+
+    /**
+     * Load localized texts from the plugins dir
+     *
+     * @param string $dir        Directory to search in
+     * @param mixed  $add2client Make texts also available on the client
+     *                           (array with list or true for all)
+     */
+    public function add_texts($dir, $add2client = false)
+    {
+        $domain = $this->ID;
+        $lang   = $_SESSION['language'];
+        $langs  = array_unique(array('en_US', $lang));
+        $locdir = slashify(realpath(slashify($this->home) . $dir));
+        $texts  = array();
+
+        // Language aliases used to find localization in similar lang, see below
+        $aliases = array(
+            'de_CH' => 'de_DE',
+            'es_AR' => 'es_ES',
+            'fa_AF' => 'fa_IR',
+            'nl_BE' => 'nl_NL',
+            'pt_BR' => 'pt_PT',
+            'zh_CN' => 'zh_TW',
+        );
+
+        // use buffering to handle empty lines/spaces after closing PHP tag
+        ob_start();
+
+        foreach ($langs as $lng) {
+            $fpath = $locdir . $lng . '.inc';
+            if (is_file($fpath) && is_readable($fpath)) {
+                include $fpath;
+                $texts = (array)$labels + (array)$messages + (array)$texts;
+            }
+            else if ($lng != 'en_US') {
+                // Find localization in similar language (#1488401)
+                $alias = null;
+                if (!empty($aliases[$lng])) {
+                    $alias = $aliases[$lng];
+                }
+                else if ($key = array_search($lng, $aliases)) {
+                    $alias = $key;
+                }
+
+                if (!empty($alias)) {
+                    $fpath = $locdir . $alias . '.inc';
+                    if (is_file($fpath) && is_readable($fpath)) {
+                        include $fpath;
+                        $texts = (array)$labels + (array)$messages + (array)$texts;
+                    }
+                }
+            }
         }
 
-        if (!empty($alias)) {
-          $fpath = $locdir . $alias . '.inc';
-          if (is_file($fpath) && is_readable($fpath)) {
-            include $fpath;
-            $texts = (array)$labels + (array)$messages + (array)$texts;
-          }
+        ob_end_clean();
+
+        // prepend domain to text keys and add to the application texts repository
+        if (!empty($texts)) {
+            $add = array();
+            foreach ($texts as $key => $value) {
+                $add[$domain.'.'.$key] = $value;
+            }
+
+            $rcube = rcube::get_instance();
+            $rcube->load_language($lang, $add);
+
+            // add labels to client
+            if ($add2client) {
+                if (is_array($add2client)) {
+                    $js_labels = array_map(array($this, 'label_map_callback'), $add2client);
+                }
+                else {
+                    $js_labels = array_keys($add);
+                }
+                $rcube->output->add_label($js_labels);
+            }
         }
-      }
     }
 
-    ob_end_clean();
+    /**
+     * Wrapper for rcube::gettext() adding the plugin ID as domain
+     *
+     * @param string $p Message identifier
+     *
+     * @return string Localized text
+     * @see rcube::gettext()
+     */
+    public function gettext($p)
+    {
+        return rcube::get_instance()->gettext($p, $this->ID);
+    }
 
-    // prepend domain to text keys and add to the application texts repository
-    if (!empty($texts)) {
-      $add = array();
-      foreach ($texts as $key => $value)
-        $add[$domain.'.'.$key] = $value;
+    /**
+     * Register this plugin to be responsible for a specific task
+     *
+     * @param string $task Task name (only characters [a-z0-9_.-] are allowed)
+     */
+    public function register_task($task)
+    {
+        if ($this->api->register_task($task, $this->ID)) {
+            $this->mytask = $task;
+        }
+    }
 
-      $rcube = rcube::get_instance();
-      $rcube->load_language($lang, $add);
+    /**
+     * Register a handler for a specific client-request action
+     *
+     * The callback will be executed upon a request like /?_task=mail&_action=plugin.myaction
+     *
+     * @param string $action  Action name (should be unique)
+     * @param mixed $callback Callback function as string
+     *                        or array with object reference and method name
+     */
+    public function register_action($action, $callback)
+    {
+        $this->api->register_action($action, $this->ID, $callback, $this->mytask);
+    }
 
-      // add labels to client
-      if ($add2client) {
-        $js_labels = is_array($add2client) ? array_map(array($this, 'label_map_callback'), $add2client) : array_keys($add);
-        $rcube->output->add_label($js_labels);
-      }
+    /**
+     * Register a handler function for a template object
+     *
+     * When parsing a template for display, tags like <roundcube:object name="plugin.myobject" />
+     * will be replaced by the return value if the registered callback function.
+     *
+     * @param string $name     Object name (should be unique and start with 'plugin.')
+     * @param mixed  $callback Callback function as string or array with object reference
+     *                         and method name
+     */
+    public function register_handler($name, $callback)
+    {
+        $this->api->register_handler($name, $this->ID, $callback);
     }
-  }
-
-  /**
-   * Wrapper for rcube::gettext() adding the plugin ID as domain
-   *
-   * @param string $p Message identifier
-   * @return string Localized text
-   * @see rcube::gettext()
-   */
-  public function gettext($p)
-  {
-    return rcube::get_instance()->gettext($p, $this->ID);
-  }
-
-  /**
-   * Register this plugin to be responsible for a specific task
-   *
-   * @param string $task Task name (only characters [a-z0-9_.-] are allowed)
-   */
-  public function register_task($task)
-  {
-    if ($this->api->register_task($task, $this->ID))
-      $this->mytask = $task;
-  }
-
-  /**
-    * Register a handler for a specific client-request action
-    *
-    * The callback will be executed upon a request like /?_task=mail&_action=plugin.myaction
-    *
-    * @param string $action  Action name (should be unique)
-    * @param mixed $callback Callback function as string or array with object reference and method name
-   */
-  public function register_action($action, $callback)
-  {
-    $this->api->register_action($action, $this->ID, $callback, $this->mytask);
-  }
-
-  /**
-   * Register a handler function for a template object
-   *
-   * When parsing a template for display, tags like <roundcube:object name="plugin.myobject" />
-   * will be replaced by the return value if the registered callback function.
-   *
-   * @param string $name Object name (should be unique and start with 'plugin.')
-   * @param mixed  $callback Callback function as string or array with object reference and method name
-   */
-  public function register_handler($name, $callback)
-  {
-    $this->api->register_handler($name, $this->ID, $callback);
-  }
-
-  /**
-   * Make this javascipt file available on the client
-   *
-   * @param string $fn File path; absolute or relative to the plugin directory
-   */
-  public function include_script($fn)
-  {
-    $this->api->include_script($this->resource_url($fn));
-  }
-
-  /**
-   * Make this stylesheet available on the client
-   *
-   * @param string $fn File path; absolute or relative to the plugin directory
-   */
-  public function include_stylesheet($fn)
-  {
-    $this->api->include_stylesheet($this->resource_url($fn));
-  }
-
-  /**
-   * Append a button to a certain container
-   *
-   * @param array $p Hash array with named parameters (as used in skin templates)
-   * @param string $container Container name where the buttons should be added to
-   * @see rcube_remplate::button()
-   */
-  public function add_button($p, $container)
-  {
-    if ($this->api->output->type == 'html') {
-      // fix relative paths
-      foreach (array('imagepas', 'imageact', 'imagesel') as $key)
-        if ($p[$key])
-          $p[$key] = $this->api->url . $this->resource_url($p[$key]);
-
-      $this->api->add_content($this->api->output->button($p), $container);
+
+    /**
+     * Make this javascipt file available on the client
+     *
+     * @param string $fn File path; absolute or relative to the plugin directory
+     */
+    public function include_script($fn)
+    {
+        $this->api->include_script($this->resource_url($fn));
     }
-  }
-
-  /**
-   * Generate an absolute URL to the given resource within the current
-   * plugin directory
-   *
-   * @param string $fn The file name
-   * @return string Absolute URL to the given resource
-   */
-  public function url($fn)
-  {
-      return $this->api->url . $this->resource_url($fn);
-  }
-
-  /**
-   * Make the given file name link into the plugin directory
-   *
-   * @param string $fn Filename
-   */
-  private function resource_url($fn)
-  {
-    if ($fn[0] != '/' && !preg_match('|^https?://|i', $fn))
-      return $this->ID . '/' . $fn;
-    else
-      return $fn;
-  }
-
-  /**
-   * Provide path to the currently selected skin folder within the plugin directory
-   * with a fallback to the default skin folder.
-   *
-   * @return string Skin path relative to plugins directory
-   */
-  public function local_skin_path()
-  {
-    $rcube = rcube::get_instance();
-    foreach (array($rcube->config->get('skin'), 'larry') as $skin) {
-      $skin_path = 'skins/' . $skin;
-      if (is_dir(realpath(slashify($this->home) . $skin_path)))
-        break;
+
+    /**
+     * Make this stylesheet available on the client
+     *
+     * @param string $fn File path; absolute or relative to the plugin directory
+     */
+    public function include_stylesheet($fn)
+    {
+        $this->api->include_stylesheet($this->resource_url($fn));
     }
 
-    return $skin_path;
-  }
+    /**
+     * Append a button to a certain container
+     *
+     * @param array $p Hash array with named parameters (as used in skin templates)
+     * @param string $container Container name where the buttons should be added to
+     *
+     * @see rcube_remplate::button()
+     */
+    public function add_button($p, $container)
+    {
+        if ($this->api->output->type == 'html') {
+            // fix relative paths
+            foreach (array('imagepas', 'imageact', 'imagesel') as $key) {
+                if ($p[$key]) {
+                    $p[$key] = $this->api->url . $this->resource_url($p[$key]);
+                }
+            }
+
+            $this->api->add_content($this->api->output->button($p), $container);
+        }
+    }
 
-  /**
-   * Callback function for array_map
-   *
-   * @param string $key Array key.
-   * @return string
-   */
-  private function label_map_callback($key)
-  {
-    return $this->ID.'.'.$key;
-  }
+    /**
+     * Generate an absolute URL to the given resource within the current
+     * plugin directory
+     *
+     * @param string $fn The file name
+     *
+     * @return string Absolute URL to the given resource
+     */
+    public function url($fn)
+    {
+        return $this->api->url . $this->resource_url($fn);
+    }
 
+    /**
+     * Make the given file name link into the plugin directory
+     *
+     * @param string $fn Filename
+     */
+    private function resource_url($fn)
+    {
+        if ($fn[0] != '/' && !preg_match('|^https?://|i', $fn)) {
+            return $this->ID . '/' . $fn;
+        }
+        else {
+            return $fn;
+        }
+    }
+
+    /**
+     * Provide path to the currently selected skin folder within the plugin directory
+     * with a fallback to the default skin folder.
+     *
+     * @return string Skin path relative to plugins directory
+     */
+    public function local_skin_path()
+    {
+        $rcube = rcube::get_instance();
+        foreach (array($rcube->config->get('skin'), 'larry') as $skin) {
+            $skin_path = 'skins/' . $skin;
+            if (is_dir(realpath(slashify($this->home) . $skin_path))) {
+                break;
+            }
+        }
+
+        return $skin_path;
+    }
+
+    /**
+     * Callback function for array_map
+     *
+     * @param string $key Array key.
+     * @return string
+     */
+    private function label_map_callback($key)
+    {
+        return $this->ID.'.'.$key;
+    }
 }
diff --git a/program/lib/Roundcube/rcube_plugin_api.php b/program/lib/Roundcube/rcube_plugin_api.php
index b4626441d..8a4cce215 100644
--- a/program/lib/Roundcube/rcube_plugin_api.php
+++ b/program/lib/Roundcube/rcube_plugin_api.php
@@ -3,7 +3,7 @@
 /*
  +-----------------------------------------------------------------------+
  | This file is part of the Roundcube Webmail client                     |
- | Copyright (C) 2008-2011, The Roundcube Dev Team                       |
+ | Copyright (C) 2008-2012, The Roundcube Dev Team                       |
  |                                                                       |
  | Licensed under the GNU General Public License version 3 or            |
  | any later version with exceptions for skins & plugins.                |
@@ -17,9 +17,9 @@
 */
 
 // location where plugins are loade from
-if (!defined('RCUBE_PLUGINS_DIR'))
-  define('RCUBE_PLUGINS_DIR', RCUBE_INSTALL_PATH . 'plugins/');
-
+if (!defined('RCUBE_PLUGINS_DIR')) {
+    define('RCUBE_PLUGINS_DIR', RCUBE_INSTALL_PATH . 'plugins/');
+}
 
 /**
  * The plugin loader and global API
@@ -29,469 +29,476 @@ if (!defined('RCUBE_PLUGINS_DIR'))
  */
 class rcube_plugin_api
 {
-  static protected $instance;
-
-  public $dir;
-  public $url = 'plugins/';
-  public $task = '';
-  public $output;
-
-  public $handlers = array();
-  protected $plugins = array();
-  protected $tasks = array();
-  protected $actions = array();
-  protected $actionmap = array();
-  protected $objectsmap = array();
-  protected $template_contents = array();
-  protected $active_hook = false;
-
-  // Deprecated names of hooks, will be removed after 0.5-stable release
-  protected $deprecated_hooks = array(
-    'create_user'       => 'user_create',
-    'kill_session'      => 'session_destroy',
-    'upload_attachment' => 'attachment_upload',
-    'save_attachment'   => 'attachment_save',
-    'get_attachment'    => 'attachment_get',
-    'cleanup_attachments' => 'attachments_cleanup',
-    'display_attachment' => 'attachment_display',
-    'remove_attachment' => 'attachment_delete',
-    'outgoing_message_headers' => 'message_outgoing_headers',
-    'outgoing_message_body' => 'message_outgoing_body',
-    'address_sources'   => 'addressbooks_list',
-    'get_address_book'  => 'addressbook_get',
-    'create_contact'    => 'contact_create',
-    'save_contact'      => 'contact_update',
-    'contact_save'      => 'contact_update',
-    'delete_contact'    => 'contact_delete',
-    'manage_folders'    => 'folders_list',
-    'list_mailboxes'    => 'mailboxes_list',
-    'save_preferences'  => 'preferences_save',
-    'user_preferences'  => 'preferences_list',
-    'list_prefs_sections' => 'preferences_sections_list',
-    'list_identities'   => 'identities_list',
-    'create_identity'   => 'identity_create',
-    'delete_identity'   => 'identity_delete',
-    'save_identity'     => 'identity_update',
-    'identity_save'     => 'identity_update',
-    // to be removed after 0.8
-    'imap_init'         => 'storage_init',
-    'mailboxes_list'    => 'storage_folders',
-    'imap_connect'      => 'storage_connect',
-  );
-
-  /**
-   * This implements the 'singleton' design pattern
-   *
-   * @return rcube_plugin_api The one and only instance if this class
-   */
-  static function get_instance()
-  {
-    if (!self::$instance) {
-      self::$instance = new rcube_plugin_api();
-    }
+    static protected $instance;
+
+    public $dir;
+    public $url = 'plugins/';
+    public $task = '';
+    public $output;
+    public $handlers = array();
+
+    protected $plugins = array();
+    protected $tasks = array();
+    protected $actions = array();
+    protected $actionmap = array();
+    protected $objectsmap = array();
+    protected $template_contents = array();
+    protected $active_hook = false;
+
+    // Deprecated names of hooks, will be removed after 0.5-stable release
+    protected $deprecated_hooks = array(
+        'create_user'       => 'user_create',
+        'kill_session'      => 'session_destroy',
+        'upload_attachment' => 'attachment_upload',
+        'save_attachment'   => 'attachment_save',
+        'get_attachment'    => 'attachment_get',
+        'cleanup_attachments' => 'attachments_cleanup',
+        'display_attachment' => 'attachment_display',
+        'remove_attachment' => 'attachment_delete',
+        'outgoing_message_headers' => 'message_outgoing_headers',
+        'outgoing_message_body' => 'message_outgoing_body',
+        'address_sources'   => 'addressbooks_list',
+        'get_address_book'  => 'addressbook_get',
+        'create_contact'    => 'contact_create',
+        'save_contact'      => 'contact_update',
+        'contact_save'      => 'contact_update',
+        'delete_contact'    => 'contact_delete',
+        'manage_folders'    => 'folders_list',
+        'list_mailboxes'    => 'mailboxes_list',
+        'save_preferences'  => 'preferences_save',
+        'user_preferences'  => 'preferences_list',
+        'list_prefs_sections' => 'preferences_sections_list',
+        'list_identities'   => 'identities_list',
+        'create_identity'   => 'identity_create',
+        'delete_identity'   => 'identity_delete',
+        'save_identity'     => 'identity_update',
+        'identity_save'     => 'identity_update',
+        // to be removed after 0.8
+        'imap_init'         => 'storage_init',
+        'mailboxes_list'    => 'storage_folders',
+        'imap_connect'      => 'storage_connect',
+    );
+
+    /**
+     * This implements the 'singleton' design pattern
+     *
+     * @return rcube_plugin_api The one and only instance if this class
+     */
+    static function get_instance()
+    {
+        if (!self::$instance) {
+            self::$instance = new rcube_plugin_api();
+        }
 
-    return self::$instance;
-  }
-
-
-  /**
-   * Private constructor
-   */
-  protected function __construct()
-  {
-    $this->dir = slashify(RCUBE_PLUGINS_DIR);
-  }
-
-
-  /**
-   * Initialize plugin engine
-   *
-   * This has to be done after rcmail::load_gui() or rcmail::json_init()
-   * was called because plugins need to have access to rcmail->output
-   *
-   * @param object rcube Instance of the rcube base class
-   * @param string Current application task (used for conditional plugin loading)
-   */
-  public function init($app, $task = '')
-  {
-    $this->task = $task;
-    $this->output = $app->output;
-
-    // register an internal hook
-    $this->register_hook('template_container', array($this, 'template_container_hook'));
-
-    // maybe also register a shudown function which triggers shutdown functions of all plugin objects
-  }
-
-
-  /**
-   * Load and init all enabled plugins
-   *
-   * This has to be done after rcmail::load_gui() or rcmail::json_init()
-   * was called because plugins need to have access to rcmail->output
-   *
-   * @param array List of configured plugins to load
-   * @param array List of plugins required by the application
-   */
-  public function load_plugins($plugins_enabled, $required_plugins = array())
-  {
-    foreach ($plugins_enabled as $plugin_name) {
-      $this->load_plugin($plugin_name);
+        return self::$instance;
     }
 
-    // check existance of all required core plugins
-    foreach ($required_plugins as $plugin_name) {
-      $loaded = false;
-      foreach ($this->plugins as $plugin) {
-        if ($plugin instanceof $plugin_name) {
-          $loaded = true;
-          break;
-        }
-      }
-
-      // load required core plugin if no derivate was found
-      if (!$loaded)
-        $loaded = $this->load_plugin($plugin_name);
-
-      // trigger fatal error if still not loaded
-      if (!$loaded) {
-        rcube::raise_error(array('code' => 520, 'type' => 'php',
-          'file' => __FILE__, 'line' => __LINE__,
-          'message' => "Requried plugin $plugin_name was not loaded"), true, true);
-      }
+    /**
+     * Private constructor
+     */
+    protected function __construct()
+    {
+        $this->dir = slashify(RCUBE_PLUGINS_DIR);
     }
-  }
-
-  /**
-   * Load the specified plugin
-   *
-   * @param string Plugin name
-   * @return boolean True on success, false if not loaded or failure
-   */
-  public function load_plugin($plugin_name)
-  {
-    static $plugins_dir;
-
-    if (!$plugins_dir) {
-      $dir = dir($this->dir);
-      $plugins_dir = unslashify($dir->path);
+
+    /**
+     * Initialize plugin engine
+     *
+     * This has to be done after rcmail::load_gui() or rcmail::json_init()
+     * was called because plugins need to have access to rcmail->output
+     *
+     * @param object rcube Instance of the rcube base class
+     * @param string Current application task (used for conditional plugin loading)
+     */
+    public function init($app, $task = '')
+    {
+        $this->task   = $task;
+        $this->output = $app->output;
+
+        // register an internal hook
+        $this->register_hook('template_container', array($this, 'template_container_hook'));
+
+        // maybe also register a shudown function which triggers
+        // shutdown functions of all plugin objects
     }
 
-    // plugin already loaded
-    if ($this->plugins[$plugin_name] || class_exists($plugin_name, false))
-      return true;
-
-    $fn = $plugins_dir . DIRECTORY_SEPARATOR . $plugin_name . DIRECTORY_SEPARATOR . $plugin_name . '.php';
-
-    if (file_exists($fn)) {
-      include($fn);
-
-      // instantiate class if exists
-      if (class_exists($plugin_name, false)) {
-        $plugin = new $plugin_name($this);
-        // check inheritance...
-        if (is_subclass_of($plugin, 'rcube_plugin')) {
-          // ... task, request type and framed mode
-          if ((!$plugin->task || preg_match('/^('.$plugin->task.')$/i', $this->task))
-              && (!$plugin->noajax || (is_object($this->output) && $this->output->type == 'html'))
-              && (!$plugin->noframe || empty($_REQUEST['_framed']))
-          ) {
-            $plugin->init();
-            $this->plugins[$plugin_name] = $plugin;
-          }
-          return true;
+    /**
+     * Load and init all enabled plugins
+     *
+     * This has to be done after rcmail::load_gui() or rcmail::json_init()
+     * was called because plugins need to have access to rcmail->output
+     *
+     * @param array List of configured plugins to load
+     * @param array List of plugins required by the application
+     */
+    public function load_plugins($plugins_enabled, $required_plugins = array())
+    {
+        foreach ($plugins_enabled as $plugin_name) {
+            $this->load_plugin($plugin_name);
+        }
+
+        // check existance of all required core plugins
+        foreach ($required_plugins as $plugin_name) {
+            $loaded = false;
+            foreach ($this->plugins as $plugin) {
+                if ($plugin instanceof $plugin_name) {
+                    $loaded = true;
+                    break;
+                }
+            }
+
+            // load required core plugin if no derivate was found
+            if (!$loaded) {
+                $loaded = $this->load_plugin($plugin_name);
+            }
+
+            // trigger fatal error if still not loaded
+            if (!$loaded) {
+                rcube::raise_error(array(
+                    'code' => 520, 'type' => 'php',
+                    'file' => __FILE__, 'line' => __LINE__,
+                    'message' => "Requried plugin $plugin_name was not loaded"), true, true);
+            }
         }
-      }
-      else {
-        rcube::raise_error(array('code' => 520, 'type' => 'php',
-          'file' => __FILE__, 'line' => __LINE__,
-          'message' => "No plugin class $plugin_name found in $fn"), true, false);
-      }
     }
-    else {
-      rcube::raise_error(array('code' => 520, 'type' => 'php',
-        'file' => __FILE__, 'line' => __LINE__,
-        'message' => "Failed to load plugin file $fn"), true, false);
+
+    /**
+     * Load the specified plugin
+     *
+     * @param string Plugin name
+     *
+     * @return boolean True on success, false if not loaded or failure
+     */
+    public function load_plugin($plugin_name)
+    {
+        static $plugins_dir;
+
+        if (!$plugins_dir) {
+            $dir         = dir($this->dir);
+            $plugins_dir = unslashify($dir->path);
+        }
+
+        // plugin already loaded
+        if ($this->plugins[$plugin_name] || class_exists($plugin_name, false)) {
+            return true;
+        }
+
+        $fn = $plugins_dir . DIRECTORY_SEPARATOR . $plugin_name
+            . DIRECTORY_SEPARATOR . $plugin_name . '.php';
+
+        if (file_exists($fn)) {
+            include $fn;
+
+            // instantiate class if exists
+            if (class_exists($plugin_name, false)) {
+                $plugin = new $plugin_name($this);
+                // check inheritance...
+                if (is_subclass_of($plugin, 'rcube_plugin')) {
+                    // ... task, request type and framed mode
+                    if ((!$plugin->task || preg_match('/^('.$plugin->task.')$/i', $this->task))
+                        && (!$plugin->noajax || (is_object($this->output) && $this->output->type == 'html'))
+                        && (!$plugin->noframe || empty($_REQUEST['_framed']))
+                    ) {
+                        $plugin->init();
+                        $this->plugins[$plugin_name] = $plugin;
+                    }
+                    return true;
+                }
+            }
+            else {
+                rcube::raise_error(array('code' => 520, 'type' => 'php',
+                    'file' => __FILE__, 'line' => __LINE__,
+                    'message' => "No plugin class $plugin_name found in $fn"),
+                    true, false);
+            }
+        }
+        else {
+            rcube::raise_error(array('code' => 520, 'type' => 'php',
+                'file' => __FILE__, 'line' => __LINE__,
+                'message' => "Failed to load plugin file $fn"), true, false);
+        }
+
+        return false;
     }
 
-    return false;
-  }
-
-
-  /**
-   * Allows a plugin object to register a callback for a certain hook
-   *
-   * @param string $hook Hook name
-   * @param mixed  $callback String with global function name or array($obj, 'methodname')
-   */
-  public function register_hook($hook, $callback)
-  {
-    if (is_callable($callback)) {
-      if (isset($this->deprecated_hooks[$hook])) {
-        rcube::raise_error(array('code' => 522, 'type' => 'php',
-          'file' => __FILE__, 'line' => __LINE__,
-          'message' => "Deprecated hook name. ".$hook.' -> '.$this->deprecated_hooks[$hook]), true, false);
-        $hook = $this->deprecated_hooks[$hook];
-      }
-      $this->handlers[$hook][] = $callback;
+    /**
+     * Allows a plugin object to register a callback for a certain hook
+     *
+     * @param string $hook Hook name
+     * @param mixed  $callback String with global function name or array($obj, 'methodname')
+     */
+    public function register_hook($hook, $callback)
+    {
+        if (is_callable($callback)) {
+            if (isset($this->deprecated_hooks[$hook])) {
+                rcube::raise_error(array('code' => 522, 'type' => 'php',
+                    'file' => __FILE__, 'line' => __LINE__,
+                    'message' => "Deprecated hook name. "
+                        . $hook . ' -> ' . $this->deprecated_hooks[$hook]), true, false);
+                $hook = $this->deprecated_hooks[$hook];
+            }
+            $this->handlers[$hook][] = $callback;
+        }
+        else {
+            rcube::raise_error(array('code' => 521, 'type' => 'php',
+                'file' => __FILE__, 'line' => __LINE__,
+                'message' => "Invalid callback function for $hook"), true, false);
+        }
     }
-    else
-      rcube::raise_error(array('code' => 521, 'type' => 'php',
-        'file' => __FILE__, 'line' => __LINE__,
-        'message' => "Invalid callback function for $hook"), true, false);
-  }
-
-  /**
-   * Allow a plugin object to unregister a callback.
-   *
-   * @param string $hook Hook name
-   * @param mixed  $callback String with global function name or array($obj, 'methodname')
-   */
-  public function unregister_hook($hook, $callback)
-  {
-    $callback_id = array_search($callback, $this->handlers[$hook]);
-    if ($callback_id !== false) {
-      unset($this->handlers[$hook][$callback_id]);
+
+    /**
+     * Allow a plugin object to unregister a callback.
+     *
+     * @param string $hook Hook name
+     * @param mixed  $callback String with global function name or array($obj, 'methodname')
+     */
+    public function unregister_hook($hook, $callback)
+    {
+        $callback_id = array_search($callback, $this->handlers[$hook]);
+        if ($callback_id !== false) {
+            unset($this->handlers[$hook][$callback_id]);
+        }
     }
-  }
-
-
-  /**
-   * Triggers a plugin hook.
-   * This is called from the application and executes all registered handlers
-   *
-   * @param string $hook Hook name
-   * @param array $args Named arguments (key->value pairs)
-   * @return array The (probably) altered hook arguments
-   */
-  public function exec_hook($hook, $args = array())
-  {
-    if (!is_array($args))
-      $args = array('arg' => $args);
-
-    $args += array('abort' => false);
-    $this->active_hook = $hook;
-
-    foreach ((array)$this->handlers[$hook] as $callback) {
-      $ret = call_user_func($callback, $args);
-      if ($ret && is_array($ret))
-        $args = $ret + $args;
-
-      if ($args['abort'])
-        break;
+
+    /**
+     * Triggers a plugin hook.
+     * This is called from the application and executes all registered handlers
+     *
+     * @param string $hook Hook name
+     * @param array $args Named arguments (key->value pairs)
+     *
+     * @return array The (probably) altered hook arguments
+     */
+    public function exec_hook($hook, $args = array())
+    {
+        if (!is_array($args)) {
+            $args = array('arg' => $args);
+        }
+
+        $args += array('abort' => false);
+        $this->active_hook = $hook;
+
+        foreach ((array)$this->handlers[$hook] as $callback) {
+            $ret = call_user_func($callback, $args);
+            if ($ret && is_array($ret)) {
+                $args = $ret + $args;
+            }
+
+            if ($args['abort']) {
+                break;
+            }
+        }
+
+        $this->active_hook = false;
+        return $args;
     }
 
-    $this->active_hook = false;
-    return $args;
-  }
-
-
-  /**
-   * Let a plugin register a handler for a specific request
-   *
-   * @param string $action Action name (_task=mail&_action=plugin.foo)
-   * @param string $owner Plugin name that registers this action
-   * @param mixed  $callback Callback: string with global function name or array($obj, 'methodname')
-   * @param string $task Task name registered by this plugin
-   */
-  public function register_action($action, $owner, $callback, $task = null)
-  {
-    // check action name
-    if ($task)
-      $action = $task.'.'.$action;
-    else if (strpos($action, 'plugin.') !== 0)
-      $action = 'plugin.'.$action;
-
-    // can register action only if it's not taken or registered by myself
-    if (!isset($this->actionmap[$action]) || $this->actionmap[$action] == $owner) {
-      $this->actions[$action] = $callback;
-      $this->actionmap[$action] = $owner;
+    /**
+     * Let a plugin register a handler for a specific request
+     *
+     * @param string $action   Action name (_task=mail&_action=plugin.foo)
+     * @param string $owner    Plugin name that registers this action
+     * @param mixed  $callback Callback: string with global function name or array($obj, 'methodname')
+     * @param string $task     Task name registered by this plugin
+     */
+    public function register_action($action, $owner, $callback, $task = null)
+    {
+        // check action name
+        if ($task)
+            $action = $task.'.'.$action;
+        else if (strpos($action, 'plugin.') !== 0)
+            $action = 'plugin.'.$action;
+
+        // can register action only if it's not taken or registered by myself
+        if (!isset($this->actionmap[$action]) || $this->actionmap[$action] == $owner) {
+            $this->actions[$action] = $callback;
+            $this->actionmap[$action] = $owner;
+        }
+        else {
+            rcube::raise_error(array('code' => 523, 'type' => 'php',
+                'file' => __FILE__, 'line' => __LINE__,
+                'message' => "Cannot register action $action;"
+                    ." already taken by another plugin"), true, false);
+        }
     }
-    else {
-      rcube::raise_error(array('code' => 523, 'type' => 'php',
-        'file' => __FILE__, 'line' => __LINE__,
-        'message' => "Cannot register action $action; already taken by another plugin"), true, false);
+
+    /**
+     * This method handles requests like _task=mail&_action=plugin.foo
+     * It executes the callback function that was registered with the given action.
+     *
+     * @param string $action Action name
+     */
+    public function exec_action($action)
+    {
+        if (isset($this->actions[$action])) {
+            call_user_func($this->actions[$action]);
+        }
+        else if (rcube::get_instance()->action != 'refresh') {
+            rcube::raise_error(array('code' => 524, 'type' => 'php',
+                'file' => __FILE__, 'line' => __LINE__,
+                'message' => "No handler found for action $action"), true, true);
+        }
     }
-  }
-
-
-  /**
-   * This method handles requests like _task=mail&_action=plugin.foo
-   * It executes the callback function that was registered with the given action.
-   *
-   * @param string $action Action name
-   */
-  public function exec_action($action)
-  {
-    if (isset($this->actions[$action])) {
-      call_user_func($this->actions[$action]);
+
+    /**
+     * Register a handler function for template objects
+     *
+     * @param string $name Object name
+     * @param string $owner Plugin name that registers this action
+     * @param mixed  $callback Callback: string with global function name or array($obj, 'methodname')
+     */
+    public function register_handler($name, $owner, $callback)
+    {
+        // check name
+        if (strpos($name, 'plugin.') !== 0) {
+            $name = 'plugin.' . $name;
+        }
+
+        // can register handler only if it's not taken or registered by myself
+        if (is_object($this->output)
+            && (!isset($this->objectsmap[$name]) || $this->objectsmap[$name] == $owner)
+        ) {
+            $this->output->add_handler($name, $callback);
+            $this->objectsmap[$name] = $owner;
+        }
+        else {
+            rcube::raise_error(array('code' => 525, 'type' => 'php',
+                'file' => __FILE__, 'line' => __LINE__,
+                'message' => "Cannot register template handler $name;"
+                    ." already taken by another plugin or no output object available"), true, false);
+        }
     }
-    else if (rcube::get_instance()->action != 'refresh') {
-      rcube::raise_error(array('code' => 524, 'type' => 'php',
-        'file' => __FILE__, 'line' => __LINE__,
-        'message' => "No handler found for action $action"), true, true);
+
+    /**
+     * Register this plugin to be responsible for a specific task
+     *
+     * @param string $task Task name (only characters [a-z0-9_.-] are allowed)
+     * @param string $owner Plugin name that registers this action
+     */
+    public function register_task($task, $owner)
+    {
+        // tasks are irrelevant in framework mode
+        if (!class_exists('rcmail', false)) {
+            return true;
+        }
+
+        if ($task != asciiwords($task)) {
+            rcube::raise_error(array('code' => 526, 'type' => 'php',
+                'file' => __FILE__, 'line' => __LINE__,
+                'message' => "Invalid task name: $task."
+                    ." Only characters [a-z0-9_.-] are allowed"), true, false);
+        }
+        else if (in_array($task, rcmail::$main_tasks)) {
+            rcube::raise_error(array('code' => 526, 'type' => 'php',
+                'file' => __FILE__, 'line' => __LINE__,
+                'message' => "Cannot register taks $task;"
+                    ." already taken by another plugin or the application itself"), true, false);
+        }
+        else {
+            $this->tasks[$task] = $owner;
+            rcmail::$main_tasks[] = $task;
+            return true;
+        }
+
+        return false;
     }
-  }
-
-
-  /**
-   * Register a handler function for template objects
-   *
-   * @param string $name Object name
-   * @param string $owner Plugin name that registers this action
-   * @param mixed  $callback Callback: string with global function name or array($obj, 'methodname')
-   */
-  public function register_handler($name, $owner, $callback)
-  {
-    // check name
-    if (strpos($name, 'plugin.') !== 0)
-      $name = 'plugin.'.$name;
-
-    // can register handler only if it's not taken or registered by myself
-    if (is_object($this->output) && (!isset($this->objectsmap[$name]) || $this->objectsmap[$name] == $owner)) {
-      $this->output->add_handler($name, $callback);
-      $this->objectsmap[$name] = $owner;
+
+    /**
+     * Checks whether the given task is registered by a plugin
+     *
+     * @param string $task Task name
+     *
+     * @return boolean True if registered, otherwise false
+     */
+    public function is_plugin_task($task)
+    {
+        return $this->tasks[$task] ? true : false;
     }
-    else {
-      rcube::raise_error(array('code' => 525, 'type' => 'php',
-        'file' => __FILE__, 'line' => __LINE__,
-        'message' => "Cannot register template handler $name; already taken by another plugin or no output object available"), true, false);
+
+    /**
+     * Check if a plugin hook is currently processing.
+     * Mainly used to prevent loops and recursion.
+     *
+     * @param string $hook Hook to check (optional)
+     *
+     * @return boolean True if any/the given hook is currently processed, otherwise false
+     */
+    public function is_processing($hook = null)
+    {
+        return $this->active_hook && (!$hook || $this->active_hook == $hook);
     }
-  }
-
-
-  /**
-   * Register this plugin to be responsible for a specific task
-   *
-   * @param string $task Task name (only characters [a-z0-9_.-] are allowed)
-   * @param string $owner Plugin name that registers this action
-   */
-  public function register_task($task, $owner)
-  {
-    // tasks are irrelevant in framework mode
-    if (!class_exists('rcmail', false))
-      return true;
-
-    if ($task != asciiwords($task)) {
-      rcube::raise_error(array('code' => 526, 'type' => 'php',
-        'file' => __FILE__, 'line' => __LINE__,
-        'message' => "Invalid task name: $task. Only characters [a-z0-9_.-] are allowed"), true, false);
+
+    /**
+     * Include a plugin script file in the current HTML page
+     *
+     * @param string $fn Path to script
+     */
+    public function include_script($fn)
+    {
+        if (is_object($this->output) && $this->output->type == 'html') {
+            $src = $this->resource_url($fn);
+            $this->output->add_header(html::tag('script',
+                array('type' => "text/javascript", 'src' => $src)));
+        }
     }
-    else if (in_array($task, rcmail::$main_tasks)) {
-      rcube::raise_error(array('code' => 526, 'type' => 'php',
-        'file' => __FILE__, 'line' => __LINE__,
-        'message' => "Cannot register taks $task; already taken by another plugin or the application itself"), true, false);
+
+    /**
+     * Include a plugin stylesheet in the current HTML page
+     *
+     * @param string $fn Path to stylesheet
+     */
+    public function include_stylesheet($fn)
+    {
+        if (is_object($this->output) && $this->output->type == 'html') {
+            $src = $this->resource_url($fn);
+            $this->output->include_css($src);
+        }
     }
-    else {
-      $this->tasks[$task] = $owner;
-      rcmail::$main_tasks[] = $task;
-      return true;
+
+    /**
+     * Save the given HTML content to be added to a template container
+     *
+     * @param string $html HTML content
+     * @param string $container Template container identifier
+     */
+    public function add_content($html, $container)
+    {
+        $this->template_contents[$container] .= $html . "\n";
     }
 
-    return false;
-  }
-
-
-  /**
-   * Checks whether the given task is registered by a plugin
-   *
-   * @param string $task Task name
-   * @return boolean True if registered, otherwise false
-   */
-  public function is_plugin_task($task)
-  {
-    return $this->tasks[$task] ? true : false;
-  }
-
-
-  /**
-   * Check if a plugin hook is currently processing.
-   * Mainly used to prevent loops and recursion.
-   *
-   * @param string $hook Hook to check (optional)
-   * @return boolean True if any/the given hook is currently processed, otherwise false
-   */
-  public function is_processing($hook = null)
-  {
-    return $this->active_hook && (!$hook || $this->active_hook == $hook);
-  }
-
-  /**
-   * Include a plugin script file in the current HTML page
-   *
-   * @param string $fn Path to script
-   */
-  public function include_script($fn)
-  {
-    if (is_object($this->output) && $this->output->type == 'html') {
-      $src = $this->resource_url($fn);
-      $this->output->add_header(html::tag('script', array('type' => "text/javascript", 'src' => $src)));
+    /**
+     * Returns list of loaded plugins names
+     *
+     * @return array List of plugin names
+     */
+    public function loaded_plugins()
+    {
+        return array_keys($this->plugins);
     }
-  }
-
-
-  /**
-   * Include a plugin stylesheet in the current HTML page
-   *
-   * @param string $fn Path to stylesheet
-   */
-  public function include_stylesheet($fn)
-  {
-    if (is_object($this->output) && $this->output->type == 'html') {
-      $src = $this->resource_url($fn);
-      $this->output->include_css($src);
+
+    /**
+     * Callback for template_container hooks
+     *
+     * @param array $attrib
+     * @return array
+     */
+    protected function template_container_hook($attrib)
+    {
+        $container = $attrib['name'];
+        return array('content' => $attrib['content'] . $this->template_contents[$container]);
     }
-  }
-
-
-  /**
-   * Save the given HTML content to be added to a template container
-   *
-   * @param string $html HTML content
-   * @param string $container Template container identifier
-   */
-  public function add_content($html, $container)
-  {
-    $this->template_contents[$container] .= $html . "\n";
-  }
-
-
-  /**
-   * Returns list of loaded plugins names
-   *
-   * @return array List of plugin names
-   */
-  public function loaded_plugins()
-  {
-    return array_keys($this->plugins);
-  }
-
-
-  /**
-   * Callback for template_container hooks
-   *
-   * @param array $attrib
-   * @return array
-   */
-  protected function template_container_hook($attrib)
-  {
-    $container = $attrib['name'];
-    return array('content' => $attrib['content'] . $this->template_contents[$container]);
-  }
-
-
-  /**
-   * Make the given file name link into the plugins directory
-   *
-   * @param string $fn Filename
-   * @return string 
-   */
-  protected function resource_url($fn)
-  {
-    if ($fn[0] != '/' && !preg_match('|^https?://|i', $fn))
-      return $this->url . $fn;
-    else
-      return $fn;
-  }
 
+    /**
+     * Make the given file name link into the plugins directory
+     *
+     * @param string $fn Filename
+     * @return string
+     */
+    protected function resource_url($fn)
+    {
+        if ($fn[0] != '/' && !preg_match('|^https?://|i', $fn))
+            return $this->url . $fn;
+        else
+            return $fn;
+    }
 }
-- 
cgit v1.2.3


From 679b375a4685ad84456860accdd6d719531c81cf Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Fri, 21 Dec 2012 09:50:08 +0100
Subject: Fix comment

---
 program/lib/Roundcube/rcube_db.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_db.php b/program/lib/Roundcube/rcube_db.php
index 9283bb0d4..3e4617948 100644
--- a/program/lib/Roundcube/rcube_db.php
+++ b/program/lib/Roundcube/rcube_db.php
@@ -426,7 +426,7 @@ class rcube_db
      *
      * @param mixed $result Optional query handle
      *
-     * @return int Number of rows or false on failure
+     * @return int Number of (matching) rows
      */
     public function affected_rows($result = null)
     {
-- 
cgit v1.2.3


From 7d88e614aec9d6dd3bfb6b85c774e2a53b741948 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Sat, 22 Dec 2012 15:42:14 +0100
Subject: Add hint about possible disabled fsockopen() function on connection
 error

---
 program/lib/Roundcube/rcube_imap_generic.php | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_imap_generic.php b/program/lib/Roundcube/rcube_imap_generic.php
index 112e91350..59a444da7 100644
--- a/program/lib/Roundcube/rcube_imap_generic.php
+++ b/program/lib/Roundcube/rcube_imap_generic.php
@@ -753,12 +753,16 @@ class rcube_imap_generic
         $this->fp = @fsockopen($host, $this->prefs['port'], $errno, $errstr, $this->prefs['timeout']);
 
         if (!$this->fp) {
+            if (!$errstr) {
+                $errstr = "Unknown reason (fsockopen() function disabled?)";
+            }
             $this->setError(self::ERROR_BAD, sprintf("Could not connect to %s:%d: %s", $host, $this->prefs['port'], $errstr));
             return false;
         }
 
-        if ($this->prefs['timeout'] > 0)
+        if ($this->prefs['timeout'] > 0) {
             stream_set_timeout($this->fp, $this->prefs['timeout']);
+        }
 
         $line = trim(fgets($this->fp, 8192));
 
-- 
cgit v1.2.3


From 0931a97c5fc7231df99fdf4cdeebb525392886ed Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Sun, 23 Dec 2012 15:09:56 +0100
Subject: Fix handling of parentheses in URLs

---
 program/lib/Roundcube/rcube_string_replacer.php | 27 ++++++++++++++++++++++++-
 tests/Framework/StringReplacer.php              |  6 ++++++
 2 files changed, 32 insertions(+), 1 deletion(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_string_replacer.php b/program/lib/Roundcube/rcube_string_replacer.php
index 0fe982b26..6b289886b 100644
--- a/program/lib/Roundcube/rcube_string_replacer.php
+++ b/program/lib/Roundcube/rcube_string_replacer.php
@@ -36,7 +36,7 @@ class rcube_string_replacer
         // Support unicode/punycode in top-level domain part
         $utf_domain = '[^?&@"\'\\/()\s\r\t\n]+\\.?([^\\x00-\\x2f\\x3b-\\x40\\x5b-\\x60\\x7b-\\x7f]{2,}|xn--[a-zA-Z0-9]{2,})';
         $url1       = '.:;,';
-        $url2       = 'a-zA-Z0-9%=#$@+?!&\\/_~\\[\\]{}\*-';
+        $url2       = 'a-zA-Z0-9%=#$@+?!&\\/_~\\[\\]\\(\\){}\*-';
 
         $this->link_pattern = "/([\w]+:\/\/|\W[Ww][Ww][Ww]\.|^[Ww][Ww][Ww]\.)($utf_domain([$url1]?[$url2]+)*)/";
         $this->mailto_pattern = "/("
@@ -161,6 +161,9 @@ class rcube_string_replacer
         // "http://example.com/?a[b]=c". However we need to handle
         // properly situation when a bracket is placed at the end
         // of the link e.g. "[http://example.com]"
+        // Yes, this is not perfect handles correctly only paired characters
+        // but it should work for common cases
+
         if (preg_match('/(\\[|\\])/', $url)) {
             $in = false;
             for ($i=0, $len=strlen($url); $i<$len; $i++) {
@@ -182,6 +185,28 @@ class rcube_string_replacer
             }
         }
 
+        // Do the same for parentheses
+        if (preg_match('/(\\(|\\))/', $url)) {
+            $in = false;
+            for ($i=0, $len=strlen($url); $i<$len; $i++) {
+                if ($url[$i] == '(') {
+                    if ($in)
+                        break;
+                    $in = true;
+                }
+                else if ($url[$i] == ')') {
+                    if (!$in)
+                        break;
+                    $in = false;
+                }
+            }
+
+            if ($i < $len) {
+                $suffix = substr($url, $i);
+                $url    = substr($url, 0, $i);
+            }
+        }
+
         return $suffix;
     }
 }
diff --git a/tests/Framework/StringReplacer.php b/tests/Framework/StringReplacer.php
index a76ba00ee..60399cf6b 100644
--- a/tests/Framework/StringReplacer.php
+++ b/tests/Framework/StringReplacer.php
@@ -29,6 +29,12 @@ class Framework_StringReplacer extends PHPUnit_Framework_TestCase
             array('Start http://localhost/?foo End', 'Start <a href="http://localhost/?foo" target="_blank">http://localhost/?foo</a> End'),
             array('www.domain.tld', '<a href="http://www.domain.tld" target="_blank">www.domain.tld</a>'),
             array('WWW.DOMAIN.TLD', '<a href="http://WWW.DOMAIN.TLD" target="_blank">WWW.DOMAIN.TLD</a>'),
+            array('[http://link.com]', '[<a href="http://link.com" target="_blank">http://link.com</a>]'),
+            array('http://link.com?a[]=1', '<a href="http://link.com?a[]=1" target="_blank">http://link.com?a[]=1</a>'),
+            array('http://link.com?a[]', '<a href="http://link.com?a[]" target="_blank">http://link.com?a[]</a>'),
+            array('(http://link.com)', '(<a href="http://link.com" target="_blank">http://link.com</a>)'),
+            array('http://link.com?a(b)c', '<a href="http://link.com?a(b)c" target="_blank">http://link.com?a(b)c</a>'),
+            array('http://link.com?(link)', '<a href="http://link.com?(link)" target="_blank">http://link.com?(link)</a>'),
         );
     }
 
-- 
cgit v1.2.3


From 7ac94421bf85eb04c00c5ed05390e1ea0c6bcb0b Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Tue, 25 Dec 2012 18:06:17 +0100
Subject: Move washtml class into Roundcube Framework (rcube_washtml), add some
 improvements

---
 program/include/bc.php                  |   4 +
 program/lib/Roundcube/rcube_washtml.php | 451 ++++++++++++++++++++++++++++++++
 program/lib/washtml.php                 | 330 -----------------------
 program/steps/mail/func.inc             |  68 +----
 tests/Framework/Washtml.php             |  28 ++
 tests/MailFunc.php                      |   2 +-
 tests/phpunit.xml                       |   1 +
 7 files changed, 486 insertions(+), 398 deletions(-)
 create mode 100644 program/lib/Roundcube/rcube_washtml.php
 delete mode 100644 program/lib/washtml.php
 create mode 100644 tests/Framework/Washtml.php

(limited to 'program/lib/Roundcube')

diff --git a/program/include/bc.php b/program/include/bc.php
index dc4d54fd7..05d15b9e3 100644
--- a/program/include/bc.php
+++ b/program/include/bc.php
@@ -408,3 +408,7 @@ function enriched_to_html($data)
 class rcube_html_page extends rcmail_html_page
 {
 }
+
+class washtml extends rcube_washtml
+{
+}
diff --git a/program/lib/Roundcube/rcube_washtml.php b/program/lib/Roundcube/rcube_washtml.php
new file mode 100644
index 000000000..715c46047
--- /dev/null
+++ b/program/lib/Roundcube/rcube_washtml.php
@@ -0,0 +1,451 @@
+<?php
+
+/**
+ +-----------------------------------------------------------------------+
+ | This file is part of the Roundcube Webmail client                     |
+ | Copyright (C) 2008-2012, The Roundcube Dev Team                       |
+ |                                                                       |
+ | Licensed under the GNU General Public License version 3 or            |
+ | any later version with exceptions for skins & plugins.                |
+ | See the README file for a full license statement.                     |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Utility class providing HTML sanityzer (based on Washtml class)     |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ | Author: Aleksander Machniak <alec@alec.pl>                            |
+ | Author: Frederic Motte <fmotte@ubixis.com>                            |
+ +-----------------------------------------------------------------------+
+ */
+
+/**
+ *                Washtml, a HTML sanityzer.
+ *
+ * Copyright (c) 2007 Frederic Motte <fmotte@ubixis.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * OVERVIEW:
+ *
+ * Wahstml take an untrusted HTML and return a safe html string.
+ *
+ * SYNOPSIS:
+ *
+ * $washer = new washtml($config);
+ * $washer->wash($html);
+ * It return a sanityzed string of the $html parameter without html and head tags.
+ * $html is a string containing the html code to wash.
+ * $config is an array containing options:
+ *   $config['allow_remote'] is a boolean to allow link to remote images.
+ *   $config['blocked_src'] string with image-src to be used for blocked remote images
+ *   $config['show_washed'] is a boolean to include washed out attributes as x-washed
+ *   $config['cid_map'] is an array where cid urls index urls to replace them.
+ *   $config['charset'] is a string containing the charset of the HTML document if it is not defined in it.
+ * $washer->extlinks is a reference to a boolean that is set to true if remote images were removed. (FE: show remote images link)
+ *
+ * INTERNALS:
+ *
+ * Only tags and attributes in the static lists $html_elements and $html_attributes
+ * are kept, inline styles are also filtered: all style identifiers matching
+ * /[a-z\-]/i are allowed. Values matching colors, sizes, /[a-z\-]/i and safe
+ * urls if allowed and cid urls if mapped are kept.
+ *
+ * Roundcube Changes:
+ * - added $block_elements
+ * - changed $ignore_elements behaviour
+ * - added RFC2397 support
+ * - base URL support
+ * - invalid HTML comments removal before parsing
+ * - "fixing" unitless CSS values for XHTML output
+ * - base url resolving
+ */
+
+/**
+ * Utility class providing HTML sanityzer
+ *
+ * @package    Framework
+ * @subpackage Utils
+ */
+class rcube_washtml
+{
+    /* Allowed HTML elements (default) */
+    static $html_elements = array('a', 'abbr', 'acronym', 'address', 'area', 'b',
+        'basefont', 'bdo', 'big', 'blockquote', 'br', 'caption', 'center',
+        'cite', 'code', 'col', 'colgroup', 'dd', 'del', 'dfn', 'dir', 'div', 'dl',
+        'dt', 'em', 'fieldset', 'font', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i',
+        'ins', 'label', 'legend', 'li', 'map', 'menu', 'nobr', 'ol', 'p', 'pre', 'q',
+        's', 'samp', 'small', 'span', 'strike', 'strong', 'sub', 'sup', 'table',
+        'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'tt', 'u', 'ul', 'var', 'wbr', 'img',
+        // form elements
+        'button', 'input', 'textarea', 'select', 'option', 'optgroup'
+    );
+
+    /* Ignore these HTML tags and their content */
+    static $ignore_elements = array('script', 'applet', 'embed', 'object', 'style');
+
+    /* Allowed HTML attributes */
+    static $html_attribs = array('name', 'class', 'title', 'alt', 'width', 'height',
+        'align', 'nowrap', 'col', 'row', 'id', 'rowspan', 'colspan', 'cellspacing',
+        'cellpadding', 'valign', 'bgcolor', 'color', 'border', 'bordercolorlight',
+        'bordercolordark', 'face', 'marginwidth', 'marginheight', 'axis', 'border',
+        'abbr', 'char', 'charoff', 'clear', 'compact', 'coords', 'vspace', 'hspace',
+        'cellborder', 'size', 'lang', 'dir', 'usemap', 'shape', 'media',
+        // attributes of form elements
+        'type', 'rows', 'cols', 'disabled', 'readonly', 'checked', 'multiple', 'value'
+    );
+
+    /* Block elements which could be empty but cannot be returned in short form (<tag />) */
+    static $block_elements = array('div', 'p', 'pre', 'blockquote', 'a', 'font', 'center',
+        'table', 'ul', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ol', 'dl', 'strong',
+        'i', 'b', 'u', 'span',
+    );
+
+    /* State for linked objects in HTML */
+    public $extlinks = false;
+
+    /* Current settings */
+    private $config = array();
+
+    /* Registered callback functions for tags */
+    private $handlers = array();
+
+    /* Allowed HTML elements */
+    private $_html_elements = array();
+
+    /* Ignore these HTML tags but process their content */
+    private $_ignore_elements = array();
+
+    /* Block elements which could be empty but cannot be returned in short form (<tag />) */
+    private $_block_elements = array();
+
+    /* Allowed HTML attributes */
+    private $_html_attribs = array();
+
+
+    /**
+     * Class constructor
+     */
+    public function __construct($p = array())
+    {
+        $this->_html_elements   = array_flip((array)$p['html_elements']) + array_flip(self::$html_elements) ;
+        $this->_html_attribs    = array_flip((array)$p['html_attribs']) + array_flip(self::$html_attribs);
+        $this->_ignore_elements = array_flip((array)$p['ignore_elements']) + array_flip(self::$ignore_elements);
+        $this->_block_elements  = array_flip((array)$p['block_elements']) + array_flip(self::$block_elements);
+
+        unset($p['html_elements'], $p['html_attribs'], $p['ignore_elements'], $p['block_elements']);
+
+        $this->config = $p + array('show_washed' => true, 'allow_remote' => false, 'cid_map' => array());
+    }
+
+    /**
+     * Register a callback function for a certain tag
+     */
+    public function add_callback($tagName, $callback)
+    {
+        $this->handlers[$tagName] = $callback;
+    }
+
+    /**
+     * Check CSS style
+     */
+    private function wash_style($style)
+    {
+        $s = '';
+
+        foreach (explode(';', $style) as $declaration) {
+            if (preg_match('/^\s*([a-z\-]+)\s*:\s*(.*)\s*$/i', $declaration, $match)) {
+                $cssid = $match[1];
+                $str   = $match[2];
+                $value = '';
+
+                while (sizeof($str) > 0 &&
+                    preg_match('/^(url\(\s*[\'"]?([^\'"\)]*)[\'"]?\s*\)'./*1,2*/
+                        '|rgb\(\s*[0-9]+\s*,\s*[0-9]+\s*,\s*[0-9]+\s*\)'.
+                        '|-?[0-9.]+\s*(em|ex|px|cm|mm|in|pt|pc|deg|rad|grad|ms|s|hz|khz|%)?'.
+                        '|#[0-9a-f]{3,6}'.
+                        '|[a-z0-9", -]+'.
+                        ')\s*/i', $str, $match)
+                ) {
+                    if ($match[2]) {
+                        if (($src = $this->config['cid_map'][$match[2]])
+                            || ($src = $this->config['cid_map'][$this->config['base_url'].$match[2]])
+                        ) {
+                            $value .= ' url('.htmlspecialchars($src, ENT_QUOTES) . ')';
+                        }
+                        else if (preg_match('!^(https?:)?//[a-z0-9/._+-]+$!i', $match[2], $url)) {
+                            if ($this->config['allow_remote']) {
+                                $value .= ' url('.htmlspecialchars($url[0], ENT_QUOTES).')';
+                            }
+                            else {
+                                $this->extlinks = true;
+                            }
+                        }
+                        else if (preg_match('/^data:.+/i', $match[2])) { // RFC2397
+                            $value .= ' url('.htmlspecialchars($match[2], ENT_QUOTES).')';
+                        }
+                    }
+                    else {
+                        // whitelist ?
+                        $value .= ' ' . $match[0];
+
+                        // #1488535: Fix size units, so width:800 would be changed to width:800px
+                        if (preg_match('/(left|right|top|bottom|width|height)/i', $cssid)
+                            && preg_match('/^[0-9]+$/', $match[0])
+                        ) {
+                            $value .= 'px';
+                        }
+                    }
+
+                    $str = substr($str, strlen($match[0]));
+                }
+
+                if (isset($value[0])) {
+                    $s .= ($s?' ':'') . $cssid . ':' . $value . ';';
+                }
+            }
+        }
+
+        return $s;
+    }
+
+    /**
+     * Take a node and return allowed attributes and check values
+     */
+    private function wash_attribs($node)
+    {
+        $t = '';
+        $washed = '';
+
+        foreach ($node->attributes as $key => $plop) {
+            $key   = strtolower($key);
+            $value = $node->getAttribute($key);
+
+            if (isset($this->_html_attribs[$key]) ||
+                ($key == 'href' && !preg_match('!^(javascript|vbscript|data:text)!i', $value)
+                    && preg_match('!^([a-z][a-z0-9.+-]+:|//|#).+!i', $value))
+            ) {
+                $t .= ' ' . $key . '="' . htmlspecialchars($value, ENT_QUOTES) . '"';
+            }
+            else if ($key == 'style' && ($style = $this->wash_style($value))) {
+                $quot = strpos($style, '"') !== false ? "'" : '"';
+                $t .= ' style=' . $quot . $style . $quot;
+            }
+            else if ($key == 'background' || ($key == 'src' && strtolower($node->tagName) == 'img')) { //check tagName anyway
+                if (($src = $this->config['cid_map'][$value])
+                    || ($src = $this->config['cid_map'][$this->config['base_url'].$value])
+                ) {
+                    $t .= ' ' . $key . '="' . htmlspecialchars($src, ENT_QUOTES) . '"';
+                }
+                else if (preg_match('/^(http|https|ftp):.+/i', $value)) {
+                    if ($this->config['allow_remote']) {
+                        $t .= ' ' . $key . '="' . htmlspecialchars($value, ENT_QUOTES) . '"';
+                    }
+                    else {
+                        $this->extlinks = true;
+                        if ($this->config['blocked_src']) {
+                            $t .= ' ' . $key . '="' . htmlspecialchars($this->config['blocked_src'], ENT_QUOTES) . '"';
+                        }
+                    }
+                }
+                else if (preg_match('/^data:.+/i', $value)) { // RFC2397
+                    $t .= ' ' . $key . '="' . htmlspecialchars($value, ENT_QUOTES) . '"';
+                }
+            }
+            else {
+                $washed .= ($washed ? ' ' : '') . $key;
+            }
+        }
+
+        return $t . ($washed && $this->config['show_washed'] ? ' x-washed="'.$washed.'"' : '');
+    }
+
+    /**
+     * The main loop that recurse on a node tree.
+     * It output only allowed tags with allowed attributes
+     * and allowed inline styles
+     */
+    private function dumpHtml($node)
+    {
+        if (!$node->hasChildNodes()) {
+            return '';
+        }
+
+        $node = $node->firstChild;
+        $dump = '';
+
+        do {
+            switch($node->nodeType) {
+            case XML_ELEMENT_NODE: //Check element
+                $tagName = strtolower($node->tagName);
+                if ($callback = $this->handlers[$tagName]) {
+                    $dump .= call_user_func($callback, $tagName,
+                        $this->wash_attribs($node), $this->dumpHtml($node), $this);
+                }
+                else if (isset($this->_html_elements[$tagName])) {
+                    $content = $this->dumpHtml($node);
+                    $dump .= '<' . $tagName . $this->wash_attribs($node) .
+                        ($content != '' || isset($this->_block_elements[$tagName]) ? ">$content</$tagName>" : ' />');
+                }
+                else if (isset($this->_ignore_elements[$tagName])) {
+                    $dump .= '<!-- ' . htmlspecialchars($tagName, ENT_QUOTES) . ' not allowed -->';
+                }
+                else {
+                    $dump .= '<!-- ' . htmlspecialchars($tagName, ENT_QUOTES) . ' ignored -->';
+                    $dump .= $this->dumpHtml($node); // ignore tags not its content
+                }
+                break;
+
+            case XML_CDATA_SECTION_NODE:
+                $dump .= $node->nodeValue;
+                break;
+
+            case XML_TEXT_NODE:
+                $dump .= htmlspecialchars($node->nodeValue);
+                break;
+
+            case XML_HTML_DOCUMENT_NODE:
+                $dump .= $this->dumpHtml($node);
+                break;
+
+            case XML_DOCUMENT_TYPE_NODE:
+                break;
+
+            default:
+                $dump . '<!-- node type ' . $node->nodeType . ' -->';
+            }
+        } while($node = $node->nextSibling);
+
+        return $dump;
+    }
+
+    /**
+     * Main function, give it untrusted HTML, tell it if you allow loading
+     * remote images and give it a map to convert "cid:" urls.
+     */
+    public function wash($html)
+    {
+        // Charset seems to be ignored (probably if defined in the HTML document)
+        $node = new DOMDocument('1.0', $this->config['charset']);
+        $this->extlinks = false;
+
+        $html = $this->cleanup($html);
+
+        // Find base URL for images
+        if (preg_match('/<base\s+href=[\'"]*([^\'"]+)/is', $html, $matches)) {
+            $this->config['base_url'] = $matches[1];
+        }
+        else {
+            $this->config['base_url'] = '';
+        }
+
+        @$node->loadHTML($html);
+        return $this->dumpHtml($node);
+    }
+
+    /**
+     * Getter for config parameters
+     */
+    public function get_config($prop)
+    {
+        return $this->config[$prop];
+    }
+
+    /**
+     * Clean HTML input
+     */
+    private function cleanup($html)
+    {
+        // special replacements (not properly handled by washtml class)
+        $html_search = array(
+            '/(<\/nobr>)(\s+)(<nobr>)/i',       // space(s) between <NOBR>
+            '/<title[^>]*>[^<]*<\/title>/i',    // PHP bug #32547 workaround: remove title tag
+            '/^(\0\0\xFE\xFF|\xFF\xFE\0\0|\xFE\xFF|\xFF\xFE|\xEF\xBB\xBF)/',    // byte-order mark (only outlook?)
+            '/<html\s[^>]+>/i',                 // washtml/DOMDocument cannot handle xml namespaces
+        );
+
+        $html_replace = array(
+            '\\1'.' &nbsp; '.'\\3',
+            '',
+            '',
+            '<html>',
+        );
+        $html = preg_replace($html_search, $html_replace, trim($html));
+
+        // PCRE errors handling (#1486856), should we use something like for every preg_* use?
+        if ($html === null && ($preg_error = preg_last_error()) != PREG_NO_ERROR) {
+            $errstr = "Could not clean up HTML message! PCRE Error: $preg_error.";
+
+            if ($preg_error == PREG_BACKTRACK_LIMIT_ERROR) {
+                $errstr .= " Consider raising pcre.backtrack_limit!";
+            }
+            if ($preg_error == PREG_RECURSION_LIMIT_ERROR) {
+                $errstr .= " Consider raising pcre.recursion_limit!";
+            }
+
+            rcube::raise_error(array('code' => 620, 'type' => 'php',
+                'line' => __LINE__, 'file' => __FILE__,
+                'message' => $errstr), true, false);
+            return '';
+        }
+
+        // fix (unknown/malformed) HTML tags before "wash"
+        $html = preg_replace_callback('/(<[\/]*)([^\s>]+)/', array($this, 'html_tag_callback'), $html);
+
+        // Remove invalid HTML comments (#1487759)
+        // Don't remove valid conditional comments
+        $html = preg_replace('/<!--[^->[\n]*>/', '', $html);
+
+        // turn relative into absolute urls
+        $html = self::resolve_base($html);
+
+        return $html;
+    }
+
+    /**
+     * Callback function for HTML tags fixing
+     */
+    public static function html_tag_callback($matches)
+    {
+        $tagname = $matches[2];
+        $tagname = preg_replace(array(
+            '/:.*$/',               // Microsoft's Smart Tags <st1:xxxx>
+            '/[^a-z0-9_\[\]\!-]/i', // forbidden characters
+        ), '', $tagname);
+
+        return $matches[1] . $tagname;
+    }
+
+    /**
+     * Convert all relative URLs according to a <base> in HTML
+     */
+    public static function resolve_base($body)
+    {
+        // check for <base href=...>
+        if (preg_match('!(<base.*href=["\']?)([hftps]{3,5}://[a-z0-9/.%-]+)!i', $body, $regs)) {
+            $replacer = new rcube_base_replacer($regs[2]);
+            $body     = $replacer->replace($body);
+        }
+
+        return $body;
+    }
+}
+
diff --git a/program/lib/washtml.php b/program/lib/washtml.php
deleted file mode 100644
index d13d66404..000000000
--- a/program/lib/washtml.php
+++ /dev/null
@@ -1,330 +0,0 @@
-<?php
-/*                Washtml, a HTML sanityzer.
- *
- * Copyright (c) 2007 Frederic Motte <fmotte@ubixis.com>
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/* Please send me your comments about this code if you have some, thanks, Fred. */
-
-/* OVERVIEW:
- *
- * Wahstml take an untrusted HTML and return a safe html string.
- *
- * SYNOPSIS:
- *
- * $washer = new washtml($config);
- * $washer->wash($html);
- * It return a sanityzed string of the $html parameter without html and head tags.
- * $html is a string containing the html code to wash.
- * $config is an array containing options:
- *   $config['allow_remote'] is a boolean to allow link to remote images.
- *   $config['blocked_src'] string with image-src to be used for blocked remote images
- *   $config['show_washed'] is a boolean to include washed out attributes as x-washed
- *   $config['cid_map'] is an array where cid urls index urls to replace them.
- *   $config['charset'] is a string containing the charset of the HTML document if it is not defined in it.
- * $washer->extlinks is a reference to a boolean that is set to true if remote images were removed. (FE: show remote images link)
- *
- * INTERNALS:
- *
- * Only tags and attributes in the static lists $html_elements and $html_attributes
- * are kept, inline styles are also filtered: all style identifiers matching
- * /[a-z\-]/i are allowed. Values matching colors, sizes, /[a-z\-]/i and safe
- * urls if allowed and cid urls if mapped are kept.
- *
- * BUGS: It MUST be safe !
- *  - Check regexp
- *  - urlencode URLs instead of htmlspecials
- *  - Check is a 3 bytes utf8 first char can eat '">'
- *  - Update PCRE: CVE-2007-1659 - CVE-2007-1660 - CVE-2007-1661 - CVE-2007-1662 
- *                 CVE-2007-4766 - CVE-2007-4767 - CVE-2007-4768  
- *    http://lists.debian.org/debian-security-announce/debian-security-announce-2007/msg00177.html 
- *  - ...
- *
- * MISSING:
- *  - relative links, can be implemented by prefixing an absolute path, ask me
- *    if you need it...
- *  - ...
- *
- * Dont be a fool:
- *  - Dont alter data on a GET: '<img src="http://yourhost/mail?action=delete&uid=3267" />'
- *  - ...
- *
- * Roundcube Changes:
- * - added $block_elements
- * - changed $ignore_elements behaviour
- * - added RFC2397 support
- * - base URL support
- * - invalid HTML comments removal before parsing
- * - "fixing" unitless CSS values for XHTML output
- */
-
-class washtml
-{
-  /* Allowed HTML elements (default) */
-  static $html_elements = array('a', 'abbr', 'acronym', 'address', 'area', 'b',
-    'basefont', 'bdo', 'big', 'blockquote', 'br', 'caption', 'center',
-    'cite', 'code', 'col', 'colgroup', 'dd', 'del', 'dfn', 'dir', 'div', 'dl',
-    'dt', 'em', 'fieldset', 'font', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i',
-    'ins', 'label', 'legend', 'li', 'map', 'menu', 'nobr', 'ol', 'p', 'pre', 'q',
-    's', 'samp', 'small', 'span', 'strike', 'strong', 'sub', 'sup', 'table',
-    'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'tt', 'u', 'ul', 'var', 'wbr', 'img',
-    // form elements
-    'button', 'input', 'textarea', 'select', 'option', 'optgroup'
-  );
-
-  /* Ignore these HTML tags and their content */
-  static $ignore_elements = array('script', 'applet', 'embed', 'object', 'style');
-
-  /* Allowed HTML attributes */
-  static $html_attribs = array('name', 'class', 'title', 'alt', 'width', 'height',
-    'align', 'nowrap', 'col', 'row', 'id', 'rowspan', 'colspan', 'cellspacing',
-    'cellpadding', 'valign', 'bgcolor', 'color', 'border', 'bordercolorlight',
-    'bordercolordark', 'face', 'marginwidth', 'marginheight', 'axis', 'border',
-    'abbr', 'char', 'charoff', 'clear', 'compact', 'coords', 'vspace', 'hspace',
-    'cellborder', 'size', 'lang', 'dir', 'usemap', 'shape', 'media',
-    // attributes of form elements
-    'type', 'rows', 'cols', 'disabled', 'readonly', 'checked', 'multiple', 'value'
-  );
-
-  /* Block elements which could be empty but cannot be returned in short form (<tag />) */
-  static $block_elements = array('div', 'p', 'pre', 'blockquote', 'a', 'font', 'center',
-    'table', 'ul', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ol', 'dl', 'strong', 'i', 'b', 'u', 'span');
-
-  /* State for linked objects in HTML */
-  public $extlinks = false;
-
-  /* Current settings */
-  private $config = array();
-
-  /* Registered callback functions for tags */
-  private $handlers = array();
-
-  /* Allowed HTML elements */
-  private $_html_elements = array();
-
-  /* Ignore these HTML tags but process their content */
-  private $_ignore_elements = array();
-
-  /* Block elements which could be empty but cannot be returned in short form (<tag />) */
-  private $_block_elements = array();
-
-  /* Allowed HTML attributes */
-  private $_html_attribs = array();
-
-
-  /* Constructor */
-  public function __construct($p = array())
-  {
-    $this->_html_elements = array_flip((array)$p['html_elements']) + array_flip(self::$html_elements) ;
-    $this->_html_attribs = array_flip((array)$p['html_attribs']) + array_flip(self::$html_attribs);
-    $this->_ignore_elements = array_flip((array)$p['ignore_elements']) + array_flip(self::$ignore_elements);
-    $this->_block_elements = array_flip((array)$p['block_elements']) + array_flip(self::$block_elements);
-    unset($p['html_elements'], $p['html_attribs'], $p['ignore_elements'], $p['block_elements']);
-    $this->config = $p + array('show_washed'=>true, 'allow_remote'=>false, 'cid_map'=>array());
-  }
-
-  /* Register a callback function for a certain tag */
-  public function add_callback($tagName, $callback)
-  {
-    $this->handlers[$tagName] = $callback;
-  }
-
-  /* Check CSS style */
-  private function wash_style($style)
-  {
-    $s = '';
-
-    foreach (explode(';', $style) as $declaration) {
-      if (preg_match('/^\s*([a-z\-]+)\s*:\s*(.*)\s*$/i', $declaration, $match)) {
-        $cssid = $match[1];
-        $str   = $match[2];
-        $value = '';
-
-        while (sizeof($str) > 0 &&
-          preg_match('/^(url\(\s*[\'"]?([^\'"\)]*)[\'"]?\s*\)'./*1,2*/
-                 '|rgb\(\s*[0-9]+\s*,\s*[0-9]+\s*,\s*[0-9]+\s*\)'.
-                 '|-?[0-9.]+\s*(em|ex|px|cm|mm|in|pt|pc|deg|rad|grad|ms|s|hz|khz|%)?'.
-                 '|#[0-9a-f]{3,6}'.
-                 '|[a-z0-9", -]+'.
-                 ')\s*/i', $str, $match)
-        ) {
-          if ($match[2]) {
-            if (($src = $this->config['cid_map'][$match[2]])
-                || ($src = $this->config['cid_map'][$this->config['base_url'].$match[2]])) {
-              $value .= ' url('.htmlspecialchars($src, ENT_QUOTES) . ')';
-            }
-            else if (preg_match('!^(https?:)?//[a-z0-9/._+-]+$!i', $match[2], $url)) {
-              if ($this->config['allow_remote'])
-                $value .= ' url('.htmlspecialchars($url[0], ENT_QUOTES).')';
-              else
-                $this->extlinks = true;
-            }
-            else if (preg_match('/^data:.+/i', $match[2])) { // RFC2397
-              $value .= ' url('.htmlspecialchars($match[2], ENT_QUOTES).')';
-            }
-          }
-          else { //whitelist ?
-            $value .= ' ' . $match[0];
-
-            // #1488535: Fix size units, so width:800 would be changed to width:800px
-            if (preg_match('/(left|right|top|bottom|width|height)/i', $cssid) && preg_match('/^[0-9]+$/', $match[0])) {
-              $value .= 'px';
-            }
-          }
-
-          $str = substr($str, strlen($match[0]));
-        }
-
-        if (isset($value[0])) {
-          $s .= ($s?' ':'') . $cssid . ':' . $value . ';';
-        }
-      }
-    }
-    return $s;
-  }
-
-  /* Take a node and return allowed attributes and check values */
-  private function wash_attribs($node)
-  {
-    $t = '';
-    $washed;
-
-    foreach ($node->attributes as $key => $plop) {
-      $key = strtolower($key);
-      $value = $node->getAttribute($key);
-      if (isset($this->_html_attribs[$key]) ||
-         ($key == 'href' && !preg_match('!^(javascript|vbscript|data:text)!i', $value)
-           && preg_match('!^([a-z][a-z0-9.+-]+:|//|#).+!i', $value))
-      ) {
-        $t .= ' ' . $key . '="' . htmlspecialchars($value, ENT_QUOTES) . '"';
-      }
-      else if ($key == 'style' && ($style = $this->wash_style($value))) {
-        $quot = strpos($style, '"') !== false ? "'" : '"';
-        $t .= ' style=' . $quot . $style . $quot;
-      }
-      else if ($key == 'background' || ($key == 'src' && strtolower($node->tagName) == 'img')) { //check tagName anyway
-        if (($src = $this->config['cid_map'][$value])
-            || ($src = $this->config['cid_map'][$this->config['base_url'].$value])) {
-          $t .= ' ' . $key . '="' . htmlspecialchars($src, ENT_QUOTES) . '"';
-        }
-        else if (preg_match('/^(http|https|ftp):.+/i', $value)) {
-          if ($this->config['allow_remote'])
-            $t .= ' ' . $key . '="' . htmlspecialchars($value, ENT_QUOTES) . '"';
-          else {
-            $this->extlinks = true;
-            if ($this->config['blocked_src'])
-              $t .= ' ' . $key . '="' . htmlspecialchars($this->config['blocked_src'], ENT_QUOTES) . '"';
-          }
-        }
-        else if (preg_match('/^data:.+/i', $value)) { // RFC2397
-          $t .= ' ' . $key . '="' . htmlspecialchars($value, ENT_QUOTES) . '"';
-        }
-      }
-      else
-        $washed .= ($washed?' ':'') . $key;
-    }
-    return $t . ($washed && $this->config['show_washed']?' x-washed="'.$washed.'"':'');
-  }
-
-  /* The main loop that recurse on a node tree.
-   * It output only allowed tags with allowed attributes
-   * and allowed inline styles */
-  private function dumpHtml($node)
-  {
-    if(!$node->hasChildNodes())
-      return '';
-
-    $node = $node->firstChild;
-    $dump = '';
-
-    do {
-      switch($node->nodeType) {
-      case XML_ELEMENT_NODE: //Check element
-        $tagName = strtolower($node->tagName);
-        if ($callback = $this->handlers[$tagName]) {
-          $dump .= call_user_func($callback, $tagName, $this->wash_attribs($node), $this->dumpHtml($node), $this);
-        }
-        else if (isset($this->_html_elements[$tagName])) {
-          $content = $this->dumpHtml($node);
-          $dump .= '<' . $tagName . $this->wash_attribs($node) .
-            ($content != '' || isset($this->_block_elements[$tagName]) ? ">$content</$tagName>" : ' />');
-        }
-        else if (isset($this->_ignore_elements[$tagName])) {
-          $dump .= '<!-- ' . htmlspecialchars($tagName, ENT_QUOTES) . ' not allowed -->';
-        }
-        else {
-          $dump .= '<!-- ' . htmlspecialchars($tagName, ENT_QUOTES) . ' ignored -->';
-          $dump .= $this->dumpHtml($node); // ignore tags not its content
-        }
-        break;
-      case XML_CDATA_SECTION_NODE:
-        $dump .= $node->nodeValue;
-        break;
-      case XML_TEXT_NODE:
-        $dump .= htmlspecialchars($node->nodeValue);
-        break;
-      case XML_HTML_DOCUMENT_NODE:
-        $dump .= $this->dumpHtml($node);
-        break;
-      case XML_DOCUMENT_TYPE_NODE:
-        break;
-      default:
-        $dump . '<!-- node type ' . $node->nodeType . ' -->';
-      }
-    } while($node = $node->nextSibling);
-
-    return $dump;
-  }
-
-  /* Main function, give it untrusted HTML, tell it if you allow loading
-   * remote images and give it a map to convert "cid:" urls. */
-  public function wash($html)
-  {
-    // Charset seems to be ignored (probably if defined in the HTML document)
-    $node = new DOMDocument('1.0', $this->config['charset']);
-    $this->extlinks = false;
-
-    // Find base URL for images
-    if (preg_match('/<base\s+href=[\'"]*([^\'"]+)/is', $html, $matches))
-      $this->config['base_url'] = $matches[1];
-    else
-      $this->config['base_url'] = '';
-
-    // Remove invalid HTML comments (#1487759)
-    // Don't remove valid conditional comments
-    $html = preg_replace('/<!--[^->[\n]*>/', '', $html);
-
-    @$node->loadHTML($html);
-    return $this->dumpHtml($node);
-  }
-
-  /**
-   * Getter for config parameters
-   */
-  public function get_config($prop)
-  {
-      return $this->config[$prop];
-  }
-
-}
diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc
index 70493766b..90f54cf1b 100644
--- a/program/steps/mail/func.inc
+++ b/program/steps/mail/func.inc
@@ -628,39 +628,6 @@ function rcmail_wash_html($html, $p, $cid_replaces)
 
   $p += array('safe' => false, 'inline_html' => true);
 
-  // special replacements (not properly handled by washtml class)
-  $html_search = array(
-    '/(<\/nobr>)(\s+)(<nobr>)/i',	// space(s) between <NOBR>
-    '/<title[^>]*>[^<]*<\/title>/i',	// PHP bug #32547 workaround: remove title tag
-    '/^(\0\0\xFE\xFF|\xFF\xFE\0\0|\xFE\xFF|\xFF\xFE|\xEF\xBB\xBF)/',	// byte-order mark (only outlook?)
-    '/<html\s[^>]+>/i',			// washtml/DOMDocument cannot handle xml namespaces
-  );
-  $html_replace = array(
-    '\\1'.' &nbsp; '.'\\3',
-    '',
-    '',
-    '<html>',
-  );
-  $html = preg_replace($html_search, $html_replace, trim($html));
-
-  // PCRE errors handling (#1486856), should we use something like for every preg_* use?
-  if ($html === null && ($preg_error = preg_last_error()) != PREG_NO_ERROR) {
-    $errstr = "Could not clean up HTML message! PCRE Error: $preg_error.";
-
-    if ($preg_error == PREG_BACKTRACK_LIMIT_ERROR)
-      $errstr .= " Consider raising pcre.backtrack_limit!";
-    if ($preg_error == PREG_RECURSION_LIMIT_ERROR)
-      $errstr .= " Consider raising pcre.recursion_limit!";
-
-    raise_error(array('code' => 620, 'type' => 'php',
-        'line' => __LINE__, 'file' => __FILE__,
-        'message' => $errstr), true, false);
-    return '';
-  }
-
-  // fix (unknown/malformed) HTML tags before "wash"
-  $html = preg_replace_callback('/(<[\/]*)([^\s>]+)/', 'rcmail_html_tag_callback', $html);
-
   // charset was converted to UTF-8 in rcube_storage::get_message_part(),
   // change/add charset specification in HTML accordingly,
   // washtml cannot work without that
@@ -674,9 +641,6 @@ function rcmail_wash_html($html, $p, $cid_replaces)
     $html = '<head>' . $meta . '</head>' . $html;
   }
 
-  // turn relative into absolute urls
-  $html = rcmail_resolve_base($html);
-
   // clean HTML with washhtml by Frederic Motte
   $wash_opts = array(
     'show_washed' => false,
@@ -702,7 +666,7 @@ function rcmail_wash_html($html, $p, $cid_replaces)
     $wash_opts['html_attribs'] = $p['html_attribs'];
 
   // initialize HTML washer
-  $washer = new washtml($wash_opts);
+  $washer = new rcube_washtml($wash_opts);
 
   if (!$p['skip_washer_form_callback'])
     $washer->add_callback('form', 'rcmail_washtml_callback');
@@ -920,22 +884,6 @@ function rcmail_washtml_callback($tagname, $attrib, $content, $washtml)
 }
 
 
-/**
- * Callback function for HTML tags fixing
- */
-function rcmail_html_tag_callback($matches)
-{
-  $tagname = $matches[2];
-
-  $tagname = preg_replace(array(
-    '/:.*$/',			// Microsoft's Smart Tags <st1:xxxx>
-    '/[^a-z0-9_\[\]\!-]/i',	// forbidden characters
-    ), '', $tagname);
-
-  return $matches[1].$tagname;
-}
-
-
 /**
  * return table with message headers
  */
@@ -1319,20 +1267,6 @@ function rcmail_part_image_type($part)
   }
 }
 
-/**
- * Convert all relative URLs according to a <base> in HTML
- */
-function rcmail_resolve_base($body)
-{
-  // check for <base href=...>
-  if (preg_match('!(<base.*href=["\']?)([hftps]{3,5}://[a-z0-9/.%-]+)!i', $body, $regs)) {
-    $replacer = new rcube_base_replacer($regs[2]);
-    $body     = $replacer->replace($body);
-  }
-
-  return $body;
-}
-
 
 /**
  * modify a HTML message that it can be displayed inside a HTML page
diff --git a/tests/Framework/Washtml.php b/tests/Framework/Washtml.php
new file mode 100644
index 000000000..088ac4a8c
--- /dev/null
+++ b/tests/Framework/Washtml.php
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * Test class to test rcube_washtml class
+ *
+ * @package Tests
+ */
+class Framework_Washtml extends PHPUnit_Framework_TestCase
+{
+
+    /**
+     * Test the elimination of some XSS vulnerabilities
+     */
+    function test_html_xss3()
+    {
+        // #1488850
+        $html = '<p><a href="data:text/html,&lt;script&gt;alert(document.cookie)&lt;/script&gt;">Firefox</a>'
+            .'<a href="vbscript:alert(document.cookie)">Internet Explorer</a></p>';
+
+        $washer = new rcube_washtml;
+
+        $washed = $washer->wash($html);
+
+        $this->assertNotRegExp('/data:text/', $washed, "Remove data:text/html links");
+        $this->assertNotRegExp('/vbscript:/', $washed, "Remove vbscript: links");
+    }
+
+}
diff --git a/tests/MailFunc.php b/tests/MailFunc.php
index 4d4250c22..38c0bac30 100644
--- a/tests/MailFunc.php
+++ b/tests/MailFunc.php
@@ -173,7 +173,7 @@ class MailFunc extends PHPUnit_Framework_TestCase
     function test_resolve_base()
     {
         $html = file_get_contents(TESTS_DIR . 'src/htmlbase.txt');
-        $html = rcmail_resolve_base($html);
+        $html = rcube_washtml::resolve_base($html);
 
         $this->assertRegExp('|src="http://alec\.pl/dir/img1\.gif"|', $html, "URI base resolving [1]");
         $this->assertRegExp('|src="http://alec\.pl/dir/img2\.gif"|', $html, "URI base resolving [2]");
diff --git a/tests/phpunit.xml b/tests/phpunit.xml
index c9e229e97..627b4120d 100644
--- a/tests/phpunit.xml
+++ b/tests/phpunit.xml
@@ -28,6 +28,7 @@
             <file>Framework/User.php</file>
             <file>Framework/Utils.php</file>
             <file>Framework/VCard.php</file>
+            <file>Framework/Washtml.php</file>
             <file>HtmlToText.php</file>
             <file>MailFunc.php</file>
         </testsuite>
-- 
cgit v1.2.3


From 66afd70b756a0637da3537e96f6bf6ce0a2c46e9 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Wed, 26 Dec 2012 12:14:34 +0100
Subject: Framework'ize html2text class

---
 program/include/bc.php                       |   4 +
 program/lib/Roundcube/rcube_html2text.php    | 691 ++++++++++++++++++++++++
 program/lib/Roundcube/rcube_message.php      |   2 +-
 program/lib/Roundcube/rcube_spellchecker.php |   2 +-
 program/lib/html2text.php                    | 755 ---------------------------
 program/steps/mail/compose.inc               |   4 +-
 program/steps/mail/func.inc                  |   2 +-
 program/steps/mail/sendmail.inc              |   2 +-
 program/steps/utils/html2text.inc            |   4 +-
 tests/Framework/Html2text.php                |  59 +++
 tests/HtmlToText.php                         |  59 ---
 tests/phpunit.xml                            |   2 +-
 12 files changed, 762 insertions(+), 824 deletions(-)
 create mode 100644 program/lib/Roundcube/rcube_html2text.php
 delete mode 100644 program/lib/html2text.php
 create mode 100644 tests/Framework/Html2text.php
 delete mode 100644 tests/HtmlToText.php

(limited to 'program/lib/Roundcube')

diff --git a/program/include/bc.php b/program/include/bc.php
index 05d15b9e3..3d9d46289 100644
--- a/program/include/bc.php
+++ b/program/include/bc.php
@@ -412,3 +412,7 @@ class rcube_html_page extends rcmail_html_page
 class washtml extends rcube_washtml
 {
 }
+
+class html2text extends rcube_html2text
+{
+}
diff --git a/program/lib/Roundcube/rcube_html2text.php b/program/lib/Roundcube/rcube_html2text.php
new file mode 100644
index 000000000..0b172ebfa
--- /dev/null
+++ b/program/lib/Roundcube/rcube_html2text.php
@@ -0,0 +1,691 @@
+<?php
+
+/**
+ +-----------------------------------------------------------------------+
+ | This file is part of the Roundcube Webmail client                     |
+ | Copyright (C) 2008-2012, The Roundcube Dev Team                       |
+ | Copyright (c) 2005-2007, Jon Abernathy <jon@chuggnutt.com>            |
+ |                                                                       |
+ | Licensed under the GNU General Public License version 3 or            |
+ | any later version with exceptions for skins & plugins.                |
+ | See the README file for a full license statement.                     |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Converts HTML to formatted plain text (based on html2text class)    |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ | Author: Aleksander Machniak <alec@alec.pl>                            |
+ | Author: Jon Abernathy <jon@chuggnutt.com>                             |
+ +-----------------------------------------------------------------------+
+ */
+
+/**
+ *  Takes HTML and converts it to formatted, plain text.
+ *
+ *  Thanks to Alexander Krug (http://www.krugar.de/) to pointing out and
+ *  correcting an error in the regexp search array. Fixed 7/30/03.
+ *
+ *  Updated set_html() function's file reading mechanism, 9/25/03.
+ *
+ *  Thanks to Joss Sanglier (http://www.dancingbear.co.uk/) for adding
+ *  several more HTML entity codes to the $search and $replace arrays.
+ *  Updated 11/7/03.
+ *
+ *  Thanks to Darius Kasperavicius (http://www.dar.dar.lt/) for
+ *  suggesting the addition of $allowed_tags and its supporting function
+ *  (which I slightly modified). Updated 3/12/04.
+ *
+ *  Thanks to Justin Dearing for pointing out that a replacement for the
+ *  <TH> tag was missing, and suggesting an appropriate fix.
+ *  Updated 8/25/04.
+ *
+ *  Thanks to Mathieu Collas (http://www.myefarm.com/) for finding a
+ *  display/formatting bug in the _build_link_list() function: email
+ *  readers would show the left bracket and number ("[1") as part of the
+ *  rendered email address.
+ *  Updated 12/16/04.
+ *
+ *  Thanks to Wojciech Bajon (http://histeria.pl/) for submitting code
+ *  to handle relative links, which I hadn't considered. I modified his
+ *  code a bit to handle normal HTTP links and MAILTO links. Also for
+ *  suggesting three additional HTML entity codes to search for.
+ *  Updated 03/02/05.
+ *
+ *  Thanks to Jacob Chandler for pointing out another link condition
+ *  for the _build_link_list() function: "https".
+ *  Updated 04/06/05.
+ *
+ *  Thanks to Marc Bertrand (http://www.dresdensky.com/) for
+ *  suggesting a revision to the word wrapping functionality; if you
+ *  specify a $width of 0 or less, word wrapping will be ignored.
+ *  Updated 11/02/06.
+ *
+ *  *** Big housecleaning updates below:
+ *
+ *  Thanks to Colin Brown (http://www.sparkdriver.co.uk/) for
+ *  suggesting the fix to handle </li> and blank lines (whitespace).
+ *  Christian Basedau (http://www.movetheweb.de/) also suggested the
+ *  blank lines fix.
+ *
+ *  Special thanks to Marcus Bointon (http://www.synchromedia.co.uk/),
+ *  Christian Basedau, Norbert Laposa (http://ln5.co.uk/),
+ *  Bas van de Weijer, and Marijn van Butselaar
+ *  for pointing out my glaring error in the <th> handling. Marcus also
+ *  supplied a host of fixes.
+ *
+ *  Thanks to Jeffrey Silverman (http://www.newtnotes.com/) for pointing
+ *  out that extra spaces should be compressed--a problem addressed with
+ *  Marcus Bointon's fixes but that I had not yet incorporated.
+ *
+ *  Thanks to Daniel Schledermann (http://www.typoconsult.dk/) for
+ *  suggesting a valuable fix with <a> tag handling.
+ *
+ *  Thanks to Wojciech Bajon (again!) for suggesting fixes and additions,
+ *  including the <a> tag handling that Daniel Schledermann pointed
+ *  out but that I had not yet incorporated. I haven't (yet)
+ *  incorporated all of Wojciech's changes, though I may at some
+ *  future time.
+ *
+ *  *** End of the housecleaning updates. Updated 08/08/07.
+ */
+
+/**
+ * Converts HTML to formatted plain text
+ *
+ * @package    Framework
+ * @subpackage Utils
+ */
+class rcube_html2text
+{
+    /**
+     * Contains the HTML content to convert.
+     *
+     * @var string $html
+     */
+    protected $html;
+
+    /**
+     * Contains the converted, formatted text.
+     *
+     * @var string $text
+     */
+    protected $text;
+
+    /**
+     * Maximum width of the formatted text, in columns.
+     *
+     * Set this value to 0 (or less) to ignore word wrapping
+     * and not constrain text to a fixed-width column.
+     *
+     * @var integer $width
+     */
+    protected $width = 70;
+
+    /**
+     * Target character encoding for output text
+     *
+     * @var string $charset
+     */
+    protected $charset = 'UTF-8';
+
+    /**
+     * List of preg* regular expression patterns to search for,
+     * used in conjunction with $replace.
+     *
+     * @var array $search
+     * @see $replace
+     */
+    protected $search = array(
+        "/\r/",                                  // Non-legal carriage return
+        "/[\n\t]+/",                             // Newlines and tabs
+        '/<head[^>]*>.*?<\/head>/i',             // <head>
+        '/<script[^>]*>.*?<\/script>/i',         // <script>s -- which strip_tags supposedly has problems with
+        '/<style[^>]*>.*?<\/style>/i',           // <style>s -- which strip_tags supposedly has problems with
+        '/<p[^>]*>/i',                           // <P>
+        '/<br[^>]*>/i',                          // <br>
+        '/<i[^>]*>(.*?)<\/i>/i',                 // <i>
+        '/<em[^>]*>(.*?)<\/em>/i',               // <em>
+        '/(<ul[^>]*>|<\/ul>)/i',                 // <ul> and </ul>
+        '/(<ol[^>]*>|<\/ol>)/i',                 // <ol> and </ol>
+        '/<li[^>]*>(.*?)<\/li>/i',               // <li> and </li>
+        '/<li[^>]*>/i',                          // <li>
+        '/<hr[^>]*>/i',                          // <hr>
+        '/<div[^>]*>/i',                         // <div>
+        '/(<table[^>]*>|<\/table>)/i',           // <table> and </table>
+        '/(<tr[^>]*>|<\/tr>)/i',                 // <tr> and </tr>
+        '/<td[^>]*>(.*?)<\/td>/i',               // <td> and </td>
+    );
+
+    /**
+     * List of pattern replacements corresponding to patterns searched.
+     *
+     * @var array $replace
+     * @see $search
+     */
+    protected $replace = array(
+        '',                                     // Non-legal carriage return
+        ' ',                                    // Newlines and tabs
+        '',                                     // <head>
+        '',                                     // <script>s -- which strip_tags supposedly has problems with
+        '',                                     // <style>s -- which strip_tags supposedly has problems with
+        "\n\n",                                 // <P>
+        "\n",                                   // <br>
+        '_\\1_',                                // <i>
+        '_\\1_',                                // <em>
+        "\n\n",                                 // <ul> and </ul>
+        "\n\n",                                 // <ol> and </ol>
+        "\t* \\1\n",                            // <li> and </li>
+        "\n\t* ",                               // <li>
+        "\n-------------------------\n",        // <hr>
+        "<div>\n",                              // <div>
+        "\n\n",                                 // <table> and </table>
+        "\n",                                   // <tr> and </tr>
+        "\t\t\\1\n",                            // <td> and </td>
+    );
+
+    /**
+     * List of preg* regular expression patterns to search for,
+     * used in conjunction with $ent_replace.
+     *
+     * @var array $ent_search
+     * @see $ent_replace
+     */
+    protected $ent_search = array(
+        '/&(nbsp|#160);/i',                      // Non-breaking space
+        '/&(quot|rdquo|ldquo|#8220|#8221|#147|#148);/i',
+                                         // Double quotes
+        '/&(apos|rsquo|lsquo|#8216|#8217);/i',   // Single quotes
+        '/&gt;/i',                               // Greater-than
+        '/&lt;/i',                               // Less-than
+        '/&(copy|#169);/i',                      // Copyright
+        '/&(trade|#8482|#153);/i',               // Trademark
+        '/&(reg|#174);/i',                       // Registered
+        '/&(mdash|#151|#8212);/i',               // mdash
+        '/&(ndash|minus|#8211|#8722);/i',        // ndash
+        '/&(bull|#149|#8226);/i',                // Bullet
+        '/&(pound|#163);/i',                     // Pound sign
+        '/&(euro|#8364);/i',                     // Euro sign
+        '/&(amp|#38);/i',                        // Ampersand: see _converter()
+        '/[ ]{2,}/',                             // Runs of spaces, post-handling
+    );
+
+    /**
+     * List of pattern replacements corresponding to patterns searched.
+     *
+     * @var array $ent_replace
+     * @see $ent_search
+     */
+    protected $ent_replace = array(
+        ' ',                                    // Non-breaking space
+        '"',                                    // Double quotes
+        "'",                                    // Single quotes
+        '>',
+        '<',
+        '(c)',
+        '(tm)',
+        '(R)',
+        '--',
+        '-',
+        '*',
+        '£',
+        'EUR',                                  // Euro sign. � ?
+        '|+|amp|+|',                            // Ampersand: see _converter()
+        ' ',                                    // Runs of spaces, post-handling
+    );
+
+    /**
+     * List of preg* regular expression patterns to search for
+     * and replace using callback function.
+     *
+     * @var array $callback_search
+     */
+    protected $callback_search = array(
+        '/<(a) [^>]*href=("|\')([^"\']+)\2[^>]*>(.*?)<\/a>/i', // <a href="">
+        '/<(h)[123456]( [^>]*)?>(.*?)<\/h[123456]>/i',         // h1 - h6
+        '/<(b)( [^>]*)?>(.*?)<\/b>/i',                         // <b>
+        '/<(strong)( [^>]*)?>(.*?)<\/strong>/i',               // <strong>
+        '/<(th)( [^>]*)?>(.*?)<\/th>/i',                       // <th> and </th>
+    );
+
+   /**
+    * List of preg* regular expression patterns to search for in PRE body,
+    * used in conjunction with $pre_replace.
+    *
+    * @var array $pre_search
+    * @see $pre_replace
+    */
+    protected $pre_search = array(
+        "/\n/",
+        "/\t/",
+        '/ /',
+        '/<pre[^>]*>/',
+        '/<\/pre>/'
+    );
+
+    /**
+     * List of pattern replacements corresponding to patterns searched for PRE body.
+     *
+     * @var array $pre_replace
+     * @see $pre_search
+     */
+    protected $pre_replace = array(
+        '<br>',
+        '&nbsp;&nbsp;&nbsp;&nbsp;',
+        '&nbsp;',
+        '',
+        ''
+    );
+
+    /**
+     * Contains a list of HTML tags to allow in the resulting text.
+     *
+     * @var string $allowed_tags
+     * @see set_allowed_tags()
+     */
+    protected $allowed_tags = '';
+
+    /**
+     * Contains the base URL that relative links should resolve to.
+     *
+     * @var string $url
+     */
+    protected $url;
+
+    /**
+     * Indicates whether content in the $html variable has been converted yet.
+     *
+     * @var boolean $_converted
+     * @see $html, $text
+     */
+    protected $_converted = false;
+
+    /**
+     * Contains URL addresses from links to be rendered in plain text.
+     *
+     * @var array $_link_list
+     * @see _build_link_list()
+     */
+    protected $_link_list = array();
+
+    /**
+     * Boolean flag, true if a table of link URLs should be listed after the text.
+     *
+     * @var boolean $_do_links
+     * @see __construct()
+     */
+    protected $_do_links = true;
+
+    /**
+     * Constructor.
+     *
+     * If the HTML source string (or file) is supplied, the class
+     * will instantiate with that source propagated, all that has
+     * to be done it to call get_text().
+     *
+     * @param string $source HTML content
+     * @param boolean $from_file Indicates $source is a file to pull content from
+     * @param boolean $do_links Indicate whether a table of link URLs is desired
+     * @param integer $width Maximum width of the formatted text, 0 for no limit
+     */
+    function __construct($source = '', $from_file = false, $do_links = true, $width = 75, $charset = 'UTF-8')
+    {
+        if (!empty($source)) {
+            $this->set_html($source, $from_file);
+        }
+
+        $this->set_base_url();
+
+        $this->_do_links = $do_links;
+        $this->width     = $width;
+        $this->charset   = $charset;
+    }
+
+    /**
+     * Loads source HTML into memory, either from $source string or a file.
+     *
+     * @param string $source HTML content
+     * @param boolean $from_file Indicates $source is a file to pull content from
+     */
+    function set_html($source, $from_file = false)
+    {
+        if ($from_file && file_exists($source)) {
+            $this->html = file_get_contents($source);
+        }
+        else {
+            $this->html = $source;
+        }
+
+        $this->_converted = false;
+    }
+
+    /**
+     * Returns the text, converted from HTML.
+     *
+     * @return string Plain text
+     */
+    function get_text()
+    {
+        if (!$this->_converted) {
+            $this->_convert();
+        }
+
+        return $this->text;
+    }
+
+    /**
+     * Prints the text, converted from HTML.
+     */
+    function print_text()
+    {
+        print $this->get_text();
+    }
+
+    /**
+     * Sets the allowed HTML tags to pass through to the resulting text.
+     *
+     * Tags should be in the form "<p>", with no corresponding closing tag.
+     */
+    function set_allowed_tags($allowed_tags = '')
+    {
+        if (!empty($allowed_tags)) {
+            $this->allowed_tags = $allowed_tags;
+        }
+    }
+
+    /**
+     * Sets a base URL to handle relative links.
+     */
+    function set_base_url($url = '')
+    {
+        if (empty($url)) {
+            if (!empty($_SERVER['HTTP_HOST'])) {
+                $this->url = 'http://' . $_SERVER['HTTP_HOST'];
+            }
+            else {
+                $this->url = '';
+            }
+        }
+        else {
+            // Strip any trailing slashes for consistency (relative
+            // URLs may already start with a slash like "/file.html")
+            if (substr($url, -1) == '/') {
+                $url = substr($url, 0, -1);
+            }
+            $this->url = $url;
+        }
+    }
+
+    /**
+     * Workhorse function that does actual conversion (calls _converter() method).
+     */
+    protected function _convert()
+    {
+        // Variables used for building the link list
+        $this->_link_list = array();
+
+        $text = trim(stripslashes($this->html));
+
+        // Convert HTML to TXT
+        $this->_converter($text);
+
+        // Add link list
+        if (!empty($this->_link_list)) {
+            $text .= "\n\nLinks:\n------\n";
+            foreach ($this->_link_list as $idx => $url) {
+                $text .= '[' . ($idx+1) . '] ' . $url . "\n";
+            }
+        }
+
+        $this->text       = $text;
+        $this->_converted = true;
+    }
+
+    /**
+     * Workhorse function that does actual conversion.
+     *
+     * First performs custom tag replacement specified by $search and
+     * $replace arrays. Then strips any remaining HTML tags, reduces whitespace
+     * and newlines to a readable format, and word wraps the text to
+     * $width characters.
+     *
+     * @param string Reference to HTML content string
+     */
+    protected function _converter(&$text)
+    {
+        // Convert <BLOCKQUOTE> (before PRE!)
+        $this->_convert_blockquotes($text);
+
+        // Convert <PRE>
+        $this->_convert_pre($text);
+
+        // Run our defined tags search-and-replace
+        $text = preg_replace($this->search, $this->replace, $text);
+
+        // Run our defined tags search-and-replace with callback
+        $text = preg_replace_callback($this->callback_search, array($this, 'tags_preg_callback'), $text);
+
+        // Strip any other HTML tags
+        $text = strip_tags($text, $this->allowed_tags);
+
+        // Run our defined entities/characters search-and-replace
+        $text = preg_replace($this->ent_search, $this->ent_replace, $text);
+
+        // Replace known html entities
+        $text = html_entity_decode($text, ENT_QUOTES, $this->charset);
+
+        // Remove unknown/unhandled entities (this cannot be done in search-and-replace block)
+        $text = preg_replace('/&([a-zA-Z0-9]{2,6}|#[0-9]{2,4});/', '', $text);
+
+        // Convert "|+|amp|+|" into "&", need to be done after handling of unknown entities
+        // This properly handles situation of "&amp;quot;" in input string
+        $text = str_replace('|+|amp|+|', '&', $text);
+
+        // Bring down number of empty lines to 2 max
+        $text = preg_replace("/\n\s+\n/", "\n\n", $text);
+        $text = preg_replace("/[\n]{3,}/", "\n\n", $text);
+
+        // remove leading empty lines (can be produced by eg. P tag on the beginning)
+        $text = ltrim($text, "\n");
+
+        // Wrap the text to a readable format
+        // for PHP versions >= 4.0.2. Default width is 75
+        // If width is 0 or less, don't wrap the text.
+        if ( $this->width > 0 ) {
+            $text = wordwrap($text, $this->width);
+        }
+    }
+
+    /**
+     * Helper function called by preg_replace() on link replacement.
+     *
+     * Maintains an internal list of links to be displayed at the end of the
+     * text, with numeric indices to the original point in the text they
+     * appeared. Also makes an effort at identifying and handling absolute
+     * and relative links.
+     *
+     * @param string $link URL of the link
+     * @param string $display Part of the text to associate number with
+     */
+    protected function _build_link_list( $link, $display )
+    {
+        if (!$this->_do_links || empty($link)) {
+            return $display;
+        }
+
+        // Ignored link types
+        if (preg_match('!^(javascript:|mailto:|#)!i', $link)) {
+            return $display;
+        }
+
+        if (preg_match('!^([a-z][a-z0-9.+-]+:)!i', $link)) {
+            $url = $link;
+        }
+        else {
+            $url = $this->url;
+            if (substr($link, 0, 1) != '/') {
+                $url .= '/';
+            }
+            $url .= "$link";
+        }
+
+        if (($index = array_search($url, $this->_link_list)) === false) {
+            $index = count($this->_link_list);
+            $this->_link_list[] = $url;
+        }
+
+        return $display . ' [' . ($index+1) . ']';
+    }
+
+    /**
+     * Helper function for PRE body conversion.
+     *
+     * @param string HTML content
+     */
+    protected function _convert_pre(&$text)
+    {
+        // get the content of PRE element
+        while (preg_match('/<pre[^>]*>(.*)<\/pre>/ismU', $text, $matches)) {
+            $this->pre_content = $matches[1];
+
+            // Run our defined tags search-and-replace with callback
+            $this->pre_content = preg_replace_callback($this->callback_search,
+                array($this, 'tags_preg_callback'), $this->pre_content);
+
+            // convert the content
+            $this->pre_content = sprintf('<div><br>%s<br></div>',
+                preg_replace($this->pre_search, $this->pre_replace, $this->pre_content));
+
+            // replace the content (use callback because content can contain $0 variable)
+            $text = preg_replace_callback('/<pre[^>]*>.*<\/pre>/ismU',
+                array($this, 'pre_preg_callback'), $text, 1);
+
+            // free memory
+            $this->pre_content = '';
+        }
+    }
+
+    /**
+     * Helper function for BLOCKQUOTE body conversion.
+     *
+     * @param string HTML content
+     */
+    protected function _convert_blockquotes(&$text)
+    {
+        if (preg_match_all('/<\/*blockquote[^>]*>/i', $text, $matches, PREG_OFFSET_CAPTURE)) {
+            $level = 0;
+            $diff = 0;
+            foreach ($matches[0] as $m) {
+                if ($m[0][0] == '<' && $m[0][1] == '/') {
+                    $level--;
+                    if ($level < 0) {
+                        $level = 0; // malformed HTML: go to next blockquote
+                    }
+                    else if ($level > 0) {
+                        // skip inner blockquote
+                    }
+                    else {
+                        $end  = $m[1];
+                        $len  = $end - $taglen - $start;
+                        // Get blockquote content
+                        $body = substr($text, $start + $taglen - $diff, $len);
+
+                        // Set text width
+                        $p_width = $this->width;
+                        if ($this->width > 0) $this->width -= 2;
+                        // Convert blockquote content
+                        $body = trim($body);
+                        $this->_converter($body);
+                        // Add citation markers and create PRE block
+                        $body = preg_replace('/((^|\n)>*)/', '\\1> ', trim($body));
+                        $body = '<pre>' . htmlspecialchars($body) . '</pre>';
+                        // Re-set text width
+                        $this->width = $p_width;
+                        // Replace content
+                        $text = substr($text, 0, $start - $diff)
+                            . $body . substr($text, $end + strlen($m[0]) - $diff);
+
+                        $diff = $len + $taglen + strlen($m[0]) - strlen($body);
+                        unset($body);
+                    }
+                }
+                else {
+                    if ($level == 0) {
+                        $start = $m[1];
+                        $taglen = strlen($m[0]);
+                    }
+                    $level ++;
+                }
+            }
+        }
+    }
+
+    /**
+     * Callback function for preg_replace_callback use.
+     *
+     * @param  array PREG matches
+     * @return string
+     */
+    public function tags_preg_callback($matches)
+    {
+        switch (strtolower($matches[1])) {
+        case 'b':
+        case 'strong':
+            return $this->_toupper($matches[3]);
+        case 'th':
+            return $this->_toupper("\t\t". $matches[3] ."\n");
+        case 'h':
+            return $this->_toupper("\n\n". $matches[3] ."\n\n");
+        case 'a':
+            // Remove spaces in URL (#1487805)
+            $url = str_replace(' ', '', $matches[3]);
+            return $this->_build_link_list($url, $matches[4]);
+        }
+    }
+
+    /**
+     * Callback function for preg_replace_callback use in PRE content handler.
+     *
+     * @param array PREG matches
+     * @return string
+     */
+    public function pre_preg_callback($matches)
+    {
+        return $this->pre_content;
+    }
+
+    /**
+     * Strtoupper function with HTML tags and entities handling.
+     *
+     * @param string $str Text to convert
+     * @return string Converted text
+     */
+    private function _toupper($str)
+    {
+        // string can containg HTML tags
+        $chunks = preg_split('/(<[^>]*>)/', $str, null, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
+
+        // convert toupper only the text between HTML tags
+        foreach ($chunks as $idx => $chunk) {
+            if ($chunk[0] != '<') {
+                $chunks[$idx] = $this->_strtoupper($chunk);
+            }
+        }
+
+        return implode($chunks);
+    }
+
+    /**
+     * Strtoupper multibyte wrapper function with HTML entities handling.
+     *
+     * @param string $str Text to convert
+     * @return string Converted text
+     */
+    private function _strtoupper($str)
+    {
+        $str = html_entity_decode($str, ENT_COMPAT, $this->charset);
+        $str = mb_strtoupper($str);
+        $str = htmlspecialchars($str, ENT_COMPAT, $this->charset);
+
+        return $str;
+    }
+}
diff --git a/program/lib/Roundcube/rcube_message.php b/program/lib/Roundcube/rcube_message.php
index c45dbfcd0..9fea8382a 100644
--- a/program/lib/Roundcube/rcube_message.php
+++ b/program/lib/Roundcube/rcube_message.php
@@ -272,7 +272,7 @@ class rcube_message
                 $out = $this->get_part_content($mime_id);
 
                 // create instance of html2text class
-                $txt = new html2text($out);
+                $txt = new rcube_html2text($out);
                 return $txt->get_text();
             }
         }
diff --git a/program/lib/Roundcube/rcube_spellchecker.php b/program/lib/Roundcube/rcube_spellchecker.php
index e9e1ceb0f..3d4d3a3d6 100644
--- a/program/lib/Roundcube/rcube_spellchecker.php
+++ b/program/lib/Roundcube/rcube_spellchecker.php
@@ -443,7 +443,7 @@ class rcube_spellchecker
 
     private function html2text($text)
     {
-        $h2t = new html2text($text, false, true, 0);
+        $h2t = new rcube_html2text($text, false, true, 0);
         return $h2t->get_text();
     }
 
diff --git a/program/lib/html2text.php b/program/lib/html2text.php
deleted file mode 100644
index 34c719302..000000000
--- a/program/lib/html2text.php
+++ /dev/null
@@ -1,755 +0,0 @@
-<?php
-
-/*************************************************************************
- *                                                                       *
- * class.html2text.inc                                                   *
- *                                                                       *
- *************************************************************************
- *                                                                       *
- * Converts HTML to formatted plain text                                 *
- *                                                                       *
- * Copyright (c) 2005-2007 Jon Abernathy <jon@chuggnutt.com>             *
- * All rights reserved.                                                  *
- *                                                                       *
- * This script is free software; you can redistribute it and/or modify   *
- * it under the terms of the GNU General Public License as published by  *
- * the Free Software Foundation; either version 2 of the License, or     *
- * (at your option) any later version.                                   *
- *                                                                       *
- * The GNU General Public License can be found at                        *
- * http://www.gnu.org/copyleft/gpl.html.                                 *
- *                                                                       *
- * This script is distributed in the hope that it will be useful,        *
- * but WITHOUT ANY WARRANTY; without even the implied warranty of        *
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the          *
- * GNU General Public License for more details.                          *
- *                                                                       *
- * Author(s): Jon Abernathy <jon@chuggnutt.com>                          *
- *                                                                       *
- * Last modified: 08/08/07                                               *
- *                                                                       *
- *************************************************************************/
-
-
-/**
- *  Takes HTML and converts it to formatted, plain text.
- *
- *  Thanks to Alexander Krug (http://www.krugar.de/) to pointing out and
- *  correcting an error in the regexp search array. Fixed 7/30/03.
- *
- *  Updated set_html() function's file reading mechanism, 9/25/03.
- *
- *  Thanks to Joss Sanglier (http://www.dancingbear.co.uk/) for adding
- *  several more HTML entity codes to the $search and $replace arrays.
- *  Updated 11/7/03.
- *
- *  Thanks to Darius Kasperavicius (http://www.dar.dar.lt/) for
- *  suggesting the addition of $allowed_tags and its supporting function
- *  (which I slightly modified). Updated 3/12/04.
- *
- *  Thanks to Justin Dearing for pointing out that a replacement for the
- *  <TH> tag was missing, and suggesting an appropriate fix.
- *  Updated 8/25/04.
- *
- *  Thanks to Mathieu Collas (http://www.myefarm.com/) for finding a
- *  display/formatting bug in the _build_link_list() function: email
- *  readers would show the left bracket and number ("[1") as part of the
- *  rendered email address.
- *  Updated 12/16/04.
- *
- *  Thanks to Wojciech Bajon (http://histeria.pl/) for submitting code
- *  to handle relative links, which I hadn't considered. I modified his
- *  code a bit to handle normal HTTP links and MAILTO links. Also for
- *  suggesting three additional HTML entity codes to search for.
- *  Updated 03/02/05.
- *
- *  Thanks to Jacob Chandler for pointing out another link condition
- *  for the _build_link_list() function: "https".
- *  Updated 04/06/05.
- *
- *  Thanks to Marc Bertrand (http://www.dresdensky.com/) for
- *  suggesting a revision to the word wrapping functionality; if you
- *  specify a $width of 0 or less, word wrapping will be ignored.
- *  Updated 11/02/06.
- *
- *  *** Big housecleaning updates below:
- *
- *  Thanks to Colin Brown (http://www.sparkdriver.co.uk/) for
- *  suggesting the fix to handle </li> and blank lines (whitespace).
- *  Christian Basedau (http://www.movetheweb.de/) also suggested the
- *  blank lines fix.
- *
- *  Special thanks to Marcus Bointon (http://www.synchromedia.co.uk/),
- *  Christian Basedau, Norbert Laposa (http://ln5.co.uk/),
- *  Bas van de Weijer, and Marijn van Butselaar
- *  for pointing out my glaring error in the <th> handling. Marcus also
- *  supplied a host of fixes.
- *
- *  Thanks to Jeffrey Silverman (http://www.newtnotes.com/) for pointing
- *  out that extra spaces should be compressed--a problem addressed with
- *  Marcus Bointon's fixes but that I had not yet incorporated.
- *
- *  Thanks to Daniel Schledermann (http://www.typoconsult.dk/) for
- *  suggesting a valuable fix with <a> tag handling.
- *
- *  Thanks to Wojciech Bajon (again!) for suggesting fixes and additions,
- *  including the <a> tag handling that Daniel Schledermann pointed
- *  out but that I had not yet incorporated. I haven't (yet)
- *  incorporated all of Wojciech's changes, though I may at some
- *  future time.
- *
- *  *** End of the housecleaning updates. Updated 08/08/07.
- *
- *  @author Jon Abernathy <jon@chuggnutt.com>
- *  @version 1.0.0
- *  @since PHP 4.0.2
- */
-class html2text
-{
-
-    /**
-     *  Contains the HTML content to convert.
-     *
-     *  @var string $html
-     *  @access public
-     */
-    var $html;
-
-    /**
-     *  Contains the converted, formatted text.
-     *
-     *  @var string $text
-     *  @access public
-     */
-    var $text;
-
-    /**
-     *  Maximum width of the formatted text, in columns.
-     *
-     *  Set this value to 0 (or less) to ignore word wrapping
-     *  and not constrain text to a fixed-width column.
-     *
-     *  @var integer $width
-     *  @access public
-     */
-    var $width = 70;
-
-    /**
-     *  Target character encoding for output text
-     *
-     *  @var string $charset
-     *  @access public
-     */
-    var $charset = 'UTF-8';
-
-    /**
-     *  List of preg* regular expression patterns to search for,
-     *  used in conjunction with $replace.
-     *
-     *  @var array $search
-     *  @access public
-     *  @see $replace
-     */
-    var $search = array(
-        "/\r/",                                  // Non-legal carriage return
-        "/[\n\t]+/",                             // Newlines and tabs
-        '/<head[^>]*>.*?<\/head>/i',             // <head>
-        '/<script[^>]*>.*?<\/script>/i',         // <script>s -- which strip_tags supposedly has problems with
-        '/<style[^>]*>.*?<\/style>/i',           // <style>s -- which strip_tags supposedly has problems with
-        '/<p[^>]*>/i',                           // <P>
-        '/<br[^>]*>/i',                          // <br>
-        '/<i[^>]*>(.*?)<\/i>/i',                 // <i>
-        '/<em[^>]*>(.*?)<\/em>/i',               // <em>
-        '/(<ul[^>]*>|<\/ul>)/i',                 // <ul> and </ul>
-        '/(<ol[^>]*>|<\/ol>)/i',                 // <ol> and </ol>
-        '/<li[^>]*>(.*?)<\/li>/i',               // <li> and </li>
-        '/<li[^>]*>/i',                          // <li>
-        '/<hr[^>]*>/i',                          // <hr>
-        '/<div[^>]*>/i',                         // <div>
-        '/(<table[^>]*>|<\/table>)/i',           // <table> and </table>
-        '/(<tr[^>]*>|<\/tr>)/i',                 // <tr> and </tr>
-        '/<td[^>]*>(.*?)<\/td>/i',               // <td> and </td>
-    );
-
-    /**
-     *  List of pattern replacements corresponding to patterns searched.
-     *
-     *  @var array $replace
-     *  @access public
-     *  @see $search
-     */
-    var $replace = array(
-        '',                                     // Non-legal carriage return
-        ' ',                                    // Newlines and tabs
-        '',                                     // <head>
-        '',                                     // <script>s -- which strip_tags supposedly has problems with
-        '',                                     // <style>s -- which strip_tags supposedly has problems with
-        "\n\n",                                 // <P>
-        "\n",                                   // <br>
-        '_\\1_',                                // <i>
-        '_\\1_',                                // <em>
-        "\n\n",                                 // <ul> and </ul>
-        "\n\n",                                 // <ol> and </ol>
-        "\t* \\1\n",                            // <li> and </li>
-        "\n\t* ",                               // <li>
-        "\n-------------------------\n",        // <hr>
-        "<div>\n",                              // <div>
-        "\n\n",                                 // <table> and </table>
-        "\n",                                   // <tr> and </tr>
-        "\t\t\\1\n",                            // <td> and </td>
-    );
-
-    /**
-     *  List of preg* regular expression patterns to search for,
-     *  used in conjunction with $ent_replace.
-     *
-     *  @var array $ent_search
-     *  @access public
-     *  @see $ent_replace
-     */
-    var $ent_search = array(
-        '/&(nbsp|#160);/i',                      // Non-breaking space
-        '/&(quot|rdquo|ldquo|#8220|#8221|#147|#148);/i',
-                                         // Double quotes
-        '/&(apos|rsquo|lsquo|#8216|#8217);/i',   // Single quotes
-        '/&gt;/i',                               // Greater-than
-        '/&lt;/i',                               // Less-than
-        '/&(copy|#169);/i',                      // Copyright
-        '/&(trade|#8482|#153);/i',               // Trademark
-        '/&(reg|#174);/i',                       // Registered
-        '/&(mdash|#151|#8212);/i',               // mdash
-        '/&(ndash|minus|#8211|#8722);/i',        // ndash
-        '/&(bull|#149|#8226);/i',                // Bullet
-        '/&(pound|#163);/i',                     // Pound sign
-        '/&(euro|#8364);/i',                     // Euro sign
-        '/&(amp|#38);/i',                        // Ampersand: see _converter()
-        '/[ ]{2,}/',                             // Runs of spaces, post-handling
-    );
-
-    /**
-     *  List of pattern replacements corresponding to patterns searched.
-     *
-     *  @var array $ent_replace
-     *  @access public
-     *  @see $ent_search
-     */
-    var $ent_replace = array(
-        ' ',                                    // Non-breaking space
-        '"',                                    // Double quotes
-        "'",                                    // Single quotes
-        '>',
-        '<',
-        '(c)',
-        '(tm)',
-        '(R)',
-        '--',
-        '-',
-        '*',
-        '£',
-        'EUR',                                  // Euro sign. � ?
-        '|+|amp|+|',                            // Ampersand: see _converter()
-        ' ',                                    // Runs of spaces, post-handling
-    );
-
-    /**
-     *  List of preg* regular expression patterns to search for
-     *  and replace using callback function.
-     *
-     *  @var array $callback_search
-     *  @access public
-     */
-    var $callback_search = array(
-        '/<(a) [^>]*href=("|\')([^"\']+)\2[^>]*>(.*?)<\/a>/i', // <a href="">
-        '/<(h)[123456]( [^>]*)?>(.*?)<\/h[123456]>/i',         // h1 - h6
-        '/<(b)( [^>]*)?>(.*?)<\/b>/i',                         // <b>
-        '/<(strong)( [^>]*)?>(.*?)<\/strong>/i',               // <strong>
-        '/<(th)( [^>]*)?>(.*?)<\/th>/i',                       // <th> and </th>
-    );
-
-   /**
-    *  List of preg* regular expression patterns to search for in PRE body,
-    *  used in conjunction with $pre_replace.
-    *
-    *  @var array $pre_search
-    *  @access public
-    *  @see $pre_replace
-    */
-    var $pre_search = array(
-        "/\n/",
-        "/\t/",
-        '/ /',
-        '/<pre[^>]*>/',
-        '/<\/pre>/'
-    );
-
-    /**
-     *  List of pattern replacements corresponding to patterns searched for PRE body.
-     *
-     *  @var array $pre_replace
-     *  @access public
-     *  @see $pre_search
-     */
-    var $pre_replace = array(
-        '<br>',
-        '&nbsp;&nbsp;&nbsp;&nbsp;',
-        '&nbsp;',
-        '',
-        ''
-    );
-
-    /**
-     *  Contains a list of HTML tags to allow in the resulting text.
-     *
-     *  @var string $allowed_tags
-     *  @access public
-     *  @see set_allowed_tags()
-     */
-    var $allowed_tags = '';
-
-    /**
-     *  Contains the base URL that relative links should resolve to.
-     *
-     *  @var string $url
-     *  @access public
-     */
-    var $url;
-
-    /**
-     *  Indicates whether content in the $html variable has been converted yet.
-     *
-     *  @var boolean $_converted
-     *  @access private
-     *  @see $html, $text
-     */
-    var $_converted = false;
-
-    /**
-     *  Contains URL addresses from links to be rendered in plain text.
-     *
-     *  @var array $_link_list
-     *  @access private
-     *  @see _build_link_list()
-     */
-    var $_link_list = array();
-
-    /**
-     * Boolean flag, true if a table of link URLs should be listed after the text.
-     *
-     * @var boolean $_do_links
-     * @access private
-     * @see html2text()
-     */
-    var $_do_links = true;
-
-    /**
-     *  Constructor.
-     *
-     *  If the HTML source string (or file) is supplied, the class
-     *  will instantiate with that source propagated, all that has
-     *  to be done it to call get_text().
-     *
-     *  @param string $source HTML content
-     *  @param boolean $from_file Indicates $source is a file to pull content from
-     *  @param boolean $do_links Indicate whether a table of link URLs is desired
-     *  @param integer $width Maximum width of the formatted text, 0 for no limit
-     *  @access public
-     *  @return void
-     */
-    function html2text( $source = '', $from_file = false, $do_links = true, $width = 75, $charset = 'UTF-8' )
-    {
-        if ( !empty($source) ) {
-            $this->set_html($source, $from_file);
-        }
-
-        $this->set_base_url();
-        $this->_do_links = $do_links;
-        $this->width = $width;
-        $this->charset = $charset;
-    }
-
-    /**
-     *  Loads source HTML into memory, either from $source string or a file.
-     *
-     *  @param string $source HTML content
-     *  @param boolean $from_file Indicates $source is a file to pull content from
-     *  @access public
-     *  @return void
-     */
-    function set_html( $source, $from_file = false )
-    {
-        if ( $from_file && file_exists($source) ) {
-            $this->html = file_get_contents($source);
-        }
-        else
-            $this->html = $source;
-
-        $this->_converted = false;
-    }
-
-    /**
-     *  Returns the text, converted from HTML.
-     *
-     *  @access public
-     *  @return string
-     */
-    function get_text()
-    {
-        if ( !$this->_converted ) {
-            $this->_convert();
-        }
-
-        return $this->text;
-    }
-
-    /**
-     *  Prints the text, converted from HTML.
-     *
-     *  @access public
-     *  @return void
-     */
-    function print_text()
-    {
-        print $this->get_text();
-    }
-
-    /**
-     *  Alias to print_text(), operates identically.
-     *
-     *  @access public
-     *  @return void
-     *  @see print_text()
-     */
-    function p()
-    {
-        print $this->get_text();
-    }
-
-    /**
-     *  Sets the allowed HTML tags to pass through to the resulting text.
-     *
-     *  Tags should be in the form "<p>", with no corresponding closing tag.
-     *
-     *  @access public
-     *  @return void
-     */
-    function set_allowed_tags( $allowed_tags = '' )
-    {
-        if ( !empty($allowed_tags) ) {
-            $this->allowed_tags = $allowed_tags;
-        }
-    }
-
-    /**
-     *  Sets a base URL to handle relative links.
-     *
-     *  @access public
-     *  @return void
-     */
-    function set_base_url( $url = '' )
-    {
-        if ( empty($url) ) {
-            if ( !empty($_SERVER['HTTP_HOST']) ) {
-                $this->url = 'http://' . $_SERVER['HTTP_HOST'];
-            } else {
-                $this->url = '';
-            }
-        } else {
-            // Strip any trailing slashes for consistency (relative
-            // URLs may already start with a slash like "/file.html")
-            if ( substr($url, -1) == '/' ) {
-                $url = substr($url, 0, -1);
-            }
-            $this->url = $url;
-        }
-    }
-
-    /**
-     *  Workhorse function that does actual conversion (calls _converter() method).
-     *
-     *  @access private
-     *  @return void
-     */
-    function _convert()
-    {
-        // Variables used for building the link list
-        $this->_link_list = array();
-
-        $text = trim(stripslashes($this->html));
-
-        // Convert HTML to TXT
-        $this->_converter($text);
-
-        // Add link list
-        if (!empty($this->_link_list)) {
-            $text .= "\n\nLinks:\n------\n";
-            foreach ($this->_link_list as $idx => $url) {
-                $text .= '[' . ($idx+1) . '] ' . $url . "\n";
-            }
-        }
-
-        $this->text = $text;
-
-        $this->_converted = true;
-    }
-
-    /**
-     *  Workhorse function that does actual conversion.
-     *
-     *  First performs custom tag replacement specified by $search and
-     *  $replace arrays. Then strips any remaining HTML tags, reduces whitespace
-     *  and newlines to a readable format, and word wraps the text to
-     *  $width characters.
-     *
-     *  @param string Reference to HTML content string
-     *
-     *  @access private
-     *  @return void
-     */
-    function _converter(&$text)
-    {
-        // Convert <BLOCKQUOTE> (before PRE!)
-        $this->_convert_blockquotes($text);
-
-        // Convert <PRE>
-        $this->_convert_pre($text);
-
-        // Run our defined tags search-and-replace
-        $text = preg_replace($this->search, $this->replace, $text);
-
-        // Run our defined tags search-and-replace with callback
-        $text = preg_replace_callback($this->callback_search, array('html2text', '_preg_callback'), $text);
-
-        // Strip any other HTML tags
-        $text = strip_tags($text, $this->allowed_tags);
-
-        // Run our defined entities/characters search-and-replace
-        $text = preg_replace($this->ent_search, $this->ent_replace, $text);
-
-        // Replace known html entities
-        $text = html_entity_decode($text, ENT_QUOTES, $this->charset);
-
-        // Remove unknown/unhandled entities (this cannot be done in search-and-replace block)
-        $text = preg_replace('/&([a-zA-Z0-9]{2,6}|#[0-9]{2,4});/', '', $text);
-
-        // Convert "|+|amp|+|" into "&", need to be done after handling of unknown entities
-        // This properly handles situation of "&amp;quot;" in input string
-        $text = str_replace('|+|amp|+|', '&', $text);
-
-        // Bring down number of empty lines to 2 max
-        $text = preg_replace("/\n\s+\n/", "\n\n", $text);
-        $text = preg_replace("/[\n]{3,}/", "\n\n", $text);
-
-        // remove leading empty lines (can be produced by eg. P tag on the beginning)
-        $text = ltrim($text, "\n");
-
-        // Wrap the text to a readable format
-        // for PHP versions >= 4.0.2. Default width is 75
-        // If width is 0 or less, don't wrap the text.
-        if ( $this->width > 0 ) {
-            $text = wordwrap($text, $this->width);
-        }
-    }
-
-    /**
-     *  Helper function called by preg_replace() on link replacement.
-     *
-     *  Maintains an internal list of links to be displayed at the end of the
-     *  text, with numeric indices to the original point in the text they
-     *  appeared. Also makes an effort at identifying and handling absolute
-     *  and relative links.
-     *
-     *  @param string $link URL of the link
-     *  @param string $display Part of the text to associate number with
-     *  @access private
-     *  @return string
-     */
-    function _build_link_list( $link, $display )
-    {
-        if (!$this->_do_links || empty($link)) {
-            return $display;
-        }
-
-        // Ignored link types
-        if (preg_match('!^(javascript:|mailto:|#)!i', $link)) {
-            return $display;
-        }
-
-        if (preg_match('!^([a-z][a-z0-9.+-]+:)!i', $link)) {
-            $url = $link;
-        }
-        else {
-            $url = $this->url;
-            if (substr($link, 0, 1) != '/') {
-                $url .= '/';
-            }
-            $url .= "$link";
-        }
-
-        if (($index = array_search($url, $this->_link_list)) === false) {
-            $index = count($this->_link_list);
-            $this->_link_list[] = $url;
-        }
-
-        return $display . ' [' . ($index+1) . ']';
-    }
-
-    /**
-     *  Helper function for PRE body conversion.
-     *
-     *  @param string HTML content
-     *  @access private
-     */
-    function _convert_pre(&$text)
-    {
-        // get the content of PRE element
-        while (preg_match('/<pre[^>]*>(.*)<\/pre>/ismU', $text, $matches)) {
-            $this->pre_content = $matches[1];
-
-            // Run our defined tags search-and-replace with callback
-            $this->pre_content = preg_replace_callback($this->callback_search,
-                array('html2text', '_preg_callback'), $this->pre_content);
-
-            // convert the content
-            $this->pre_content = sprintf('<div><br>%s<br></div>',
-                preg_replace($this->pre_search, $this->pre_replace, $this->pre_content));
-
-            // replace the content (use callback because content can contain $0 variable)
-            $text = preg_replace_callback('/<pre[^>]*>.*<\/pre>/ismU',
-                array('html2text', '_preg_pre_callback'), $text, 1);
-
-            // free memory
-            $this->pre_content = '';
-        }
-    }
-
-    /**
-     *  Helper function for BLOCKQUOTE body conversion.
-     *
-     *  @param string HTML content
-     *  @access private
-     */
-    function _convert_blockquotes(&$text)
-    {
-        if (preg_match_all('/<\/*blockquote[^>]*>/i', $text, $matches, PREG_OFFSET_CAPTURE)) {
-            $level = 0;
-            $diff = 0;
-            foreach ($matches[0] as $m) {
-                if ($m[0][0] == '<' && $m[0][1] == '/') {
-                    $level--;
-                    if ($level < 0) {
-                        $level = 0; // malformed HTML: go to next blockquote
-                    }
-                    else if ($level > 0) {
-                        // skip inner blockquote
-                    }
-                    else {
-                        $end  = $m[1];
-                        $len  = $end - $taglen - $start;
-                        // Get blockquote content
-                        $body = substr($text, $start + $taglen - $diff, $len);
-
-                        // Set text width
-                        $p_width = $this->width;
-                        if ($this->width > 0) $this->width -= 2;
-                        // Convert blockquote content
-                        $body = trim($body);
-                        $this->_converter($body);
-                        // Add citation markers and create PRE block
-                        $body = preg_replace('/((^|\n)>*)/', '\\1> ', trim($body));
-                        $body = '<pre>' . htmlspecialchars($body) . '</pre>';
-                        // Re-set text width
-                        $this->width = $p_width;
-                        // Replace content
-                        $text = substr($text, 0, $start - $diff)
-                            . $body . substr($text, $end + strlen($m[0]) - $diff);
-
-                        $diff = $len + $taglen + strlen($m[0]) - strlen($body);
-                        unset($body);
-                    }
-                }
-                else {
-                    if ($level == 0) {
-                        $start = $m[1];
-                        $taglen = strlen($m[0]);
-                    }
-                    $level ++;
-                }
-            }
-        }
-    }
-
-    /**
-     *  Callback function for preg_replace_callback use.
-     *
-     *  @param  array PREG matches
-     *  @return string
-     */
-    private function _preg_callback($matches)
-    {
-        switch (strtolower($matches[1])) {
-        case 'b':
-        case 'strong':
-            return $this->_toupper($matches[3]);
-        case 'th':
-            return $this->_toupper("\t\t". $matches[3] ."\n");
-        case 'h':
-            return $this->_toupper("\n\n". $matches[3] ."\n\n");
-        case 'a':
-            // Remove spaces in URL (#1487805)
-            $url = str_replace(' ', '', $matches[3]);
-            return $this->_build_link_list($url, $matches[4]);
-        }
-    }
-
-    /**
-     *  Callback function for preg_replace_callback use in PRE content handler.
-     *
-     *  @param  array PREG matches
-     *  @return string
-     */
-    private function _preg_pre_callback($matches)
-    {
-        return $this->pre_content;
-    }
-
-    /**
-     * Strtoupper function with HTML tags and entities handling.
-     *
-     * @param string $str Text to convert
-     * @return string Converted text
-     */
-    private function _toupper($str)
-    {
-        // string can containg HTML tags
-        $chunks = preg_split('/(<[^>]*>)/', $str, null, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
-
-        // convert toupper only the text between HTML tags
-        foreach ($chunks as $idx => $chunk) {
-            if ($chunk[0] != '<') {
-                $chunks[$idx] = $this->_strtoupper($chunk);
-            }
-        }
-
-        return implode($chunks);
-    }
-
-    /**
-     * Strtoupper multibyte wrapper function with HTML entities handling.
-     *
-     * @param string $str Text to convert
-     * @return string Converted text
-     */
-    private function _strtoupper($str)
-    {
-        $str = html_entity_decode($str, ENT_COMPAT, $this->charset);
-
-        if (function_exists('mb_strtoupper'))
-            $str = mb_strtoupper($str);
-        else
-            $str = strtoupper($str);
-
-        $str = htmlspecialchars($str, ENT_COMPAT, $this->charset);
-
-        return $str;
-    }
-}
diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc
index d07cf587f..379e920e5 100644
--- a/program/steps/mail/compose.inc
+++ b/program/steps/mail/compose.inc
@@ -470,7 +470,7 @@ function rcmail_compose_header_from($attrib)
         $text = $html = $sql_arr['signature'];
 
         if ($sql_arr['html_signature']) {
-            $h2t  = new html2text($sql_arr['signature'], false, false);
+            $h2t  = new rcube_html2text($sql_arr['signature'], false, false);
             $text = trim($h2t->get_text());
         }
         else {
@@ -667,7 +667,7 @@ function rcmail_compose_part_body($part, $isHtml = false)
             // use html part if it has been used for message (pre)viewing
             // decrease line length for quoting
             $len = $compose_mode == RCUBE_COMPOSE_REPLY ? $LINE_LENGTH-2 : $LINE_LENGTH;
-            $txt = new html2text($body, false, true, $len);
+            $txt = new rcube_html2text($body, false, true, $len);
             $body = $txt->get_text();
         }
         else if ($part->ctype_secondary == 'enriched') {
diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc
index 90f54cf1b..814adb64d 100644
--- a/program/steps/mail/func.inc
+++ b/program/steps/mail/func.inc
@@ -704,7 +704,7 @@ function rcmail_print_body($part, $p = array())
 
   // convert html to text/plain
   if ($data['type'] == 'html' && $data['plain']) {
-    $txt = new html2text($data['body'], false, true);
+    $txt = new rcube_html2text($data['body'], false, true);
     $body = $txt->get_text();
     $part->ctype_secondary = 'plain';
   }
diff --git a/program/steps/mail/sendmail.inc b/program/steps/mail/sendmail.inc
index 36d850f8f..eb0ba89c6 100644
--- a/program/steps/mail/sendmail.inc
+++ b/program/steps/mail/sendmail.inc
@@ -559,7 +559,7 @@ if ($isHtml) {
   $plugin['body'] = rcmail_replace_emoticons($plugin['body']);
 
   // add a plain text version of the e-mail as an alternative part.
-  $h2t = new html2text($plugin['body'], false, true, 0, $message_charset);
+  $h2t = new rcube_html2text($plugin['body'], false, true, 0, $message_charset);
   $plainTextPart = rc_wordwrap($h2t->get_text(), $LINE_LENGTH, "\r\n", false, $message_charset);
   $plainTextPart = wordwrap($plainTextPart, 998, "\r\n", true);
 
diff --git a/program/steps/utils/html2text.inc b/program/steps/utils/html2text.inc
index e17665fec..c6481b197 100644
--- a/program/steps/utils/html2text.inc
+++ b/program/steps/utils/html2text.inc
@@ -24,10 +24,8 @@ $html = $HTTP_RAW_POST_DATA;
 // Replace emoticon images with its text representation
 $html = rcmail_replace_emoticons($html);
 
-$converter = new html2text($html, false, true, 0);
+$converter = new rcube_html2text($html, false, true, 0);
 
 header('Content-Type: text/plain; charset=UTF-8');
 print rtrim($converter->get_text());
 exit;
-
-
diff --git a/tests/Framework/Html2text.php b/tests/Framework/Html2text.php
new file mode 100644
index 000000000..1d8963878
--- /dev/null
+++ b/tests/Framework/Html2text.php
@@ -0,0 +1,59 @@
+<?php
+
+/**
+ * Test class to test rcube_html2text class
+ *
+ * @package Tests
+ */
+class rc_html2text extends PHPUnit_Framework_TestCase
+{
+
+    function data_html2text()
+    {
+        return array(
+            0 => array(
+                'title' => 'Test entry',
+                'in'    => '',
+                'out'   => '',
+            ),
+            1 => array(
+                'title' => 'Basic HTML entities',
+                'in'    => '&quot;&amp;',
+                'out'   => '"&',
+            ),
+            2 => array(
+                'title' => 'HTML entity string',
+                'in'    => '&amp;quot;',
+                'out'   => '&quot;',
+            ),
+            3 => array(
+                'title' => 'HTML entity in STRONG tag',
+                'in'    => '<strong>&#347;</strong>', // ś
+                'out'   => 'Ś', // upper ś
+            ),
+            4 => array(
+                'title' => 'STRONG tag to upper-case conversion',
+                'in'    => '<strong>ś</strong>',
+                'out'   => 'Ś',
+            ),
+            5 => array(
+                'title' => 'STRONG inside B tag',
+                'in'    => '<b><strong>&#347;</strong></b>',
+                'out'   => 'Ś',
+            ),
+        );
+    }
+
+    /**
+     * @dataProvider data_html2text
+     */
+    function test_html2text($title, $in, $out)
+    {
+        $ht = new rcube_html2text(null, false, false);
+
+        $ht->set_html($in);
+        $res = $ht->get_text();
+
+        $this->assertEquals($out, $res, $title);
+    }
+}
diff --git a/tests/HtmlToText.php b/tests/HtmlToText.php
deleted file mode 100644
index b90c61adf..000000000
--- a/tests/HtmlToText.php
+++ /dev/null
@@ -1,59 +0,0 @@
-<?php
-
-/**
- * Test class to test html2text class
- *
- * @package Tests
- */
-class HtmlToText extends PHPUnit_Framework_TestCase
-{
-
-    function data_html2text()
-    {
-        return array(
-            0 => array(
-                'title' => 'Test entry',
-                'in'    => '',
-                'out'   => '',
-            ),
-            1 => array(
-                'title' => 'Basic HTML entities',
-                'in'    => '&quot;&amp;',
-                'out'   => '"&',
-            ),
-            2 => array(
-                'title' => 'HTML entity string',
-                'in'    => '&amp;quot;',
-                'out'   => '&quot;',
-            ),
-            3 => array(
-                'title' => 'HTML entity in STRONG tag',
-                'in'    => '<strong>&#347;</strong>', // ś
-                'out'   => 'Ś', // upper ś
-            ),
-            4 => array(
-                'title' => 'STRONG tag to upper-case conversion',
-                'in'    => '<strong>ś</strong>',
-                'out'   => 'Ś',
-            ),
-            5 => array(
-                'title' => 'STRONG inside B tag',
-                'in'    => '<b><strong>&#347;</strong></b>',
-                'out'   => 'Ś',
-            ),
-        );
-    }
-
-    /**
-     * @dataProvider data_html2text
-     */
-    function test_html2text($title, $in, $out)
-    {
-        $ht = new html2text(null, false, false);
-
-        $ht->set_html($in);
-        $res = $ht->get_text();
-
-        $this->assertEquals($out, $res, $title);
-    }
-}
diff --git a/tests/phpunit.xml b/tests/phpunit.xml
index 627b4120d..5a858111b 100644
--- a/tests/phpunit.xml
+++ b/tests/phpunit.xml
@@ -12,6 +12,7 @@
             <file>Framework/Csv2vcard.php</file>
             <file>Framework/Enriched.php</file>
             <file>Framework/Html.php</file>
+            <file>Framework/Html2text.php</file>
             <file>Framework/Imap.php</file>
             <file>Framework/ImapGeneric.php</file>
             <file>Framework/Image.php</file>
@@ -29,7 +30,6 @@
             <file>Framework/Utils.php</file>
             <file>Framework/VCard.php</file>
             <file>Framework/Washtml.php</file>
-            <file>HtmlToText.php</file>
             <file>MailFunc.php</file>
         </testsuite>
         <testsuite name="Managesieve Tests">
-- 
cgit v1.2.3


From 2b80d5dbf2ff301b0627b3a0e3c9a61e86658fd4 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Thu, 27 Dec 2012 10:25:36 +0100
Subject: Workaround UW-IMAP bug where hierarchy separator is added to the
 shared folder name (#1488879)

---
 CHANGELOG                                    | 1 +
 program/lib/Roundcube/rcube_imap_generic.php | 5 +++++
 2 files changed, 6 insertions(+)

(limited to 'program/lib/Roundcube')

diff --git a/CHANGELOG b/CHANGELOG
index 02e20455c..5f010acb3 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
 CHANGELOG Roundcube Webmail
 ===========================
 
+- Workaround UW-IMAP bug where hierarchy separator is added to the shared folder name (#1488879)
 - Fix version comparisons with -stable suffix (#1488876)
 - Add unsupported alternative parts to attachments list (#1488870)
 - Add Compose button on message view page (#1488747)
diff --git a/program/lib/Roundcube/rcube_imap_generic.php b/program/lib/Roundcube/rcube_imap_generic.php
index 59a444da7..8d84bf736 100644
--- a/program/lib/Roundcube/rcube_imap_generic.php
+++ b/program/lib/Roundcube/rcube_imap_generic.php
@@ -1311,6 +1311,11 @@ class rcube_imap_generic
                 if ($cmd == 'LIST' || $cmd == 'LSUB') {
                     list($opts, $delim, $mailbox) = $this->tokenizeResponse($line, 3);
 
+                    // Remove redundant separator at the end of folder name, UW-IMAP bug? (#1488879)
+                    if ($delim) {
+                        $mailbox = rtrim($mailbox, $delim);
+                    }
+
                     // Add to result array
                     if (!$lstatus) {
                         $folders[] = $mailbox;
-- 
cgit v1.2.3


From be72fb3597c21ca3aaa058adf41bb72d53d197c7 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Fri, 28 Dec 2012 12:40:57 +0100
Subject: Unified attachments filenames handling for message parts without a
 filename

---
 program/lib/Roundcube/rcube_message.php | 21 ---------------------
 program/localization/en_US/labels.inc   |  1 +
 program/steps/mail/compose.inc          | 11 +----------
 program/steps/mail/func.inc             | 30 +++++++++++++++++++++++++-----
 program/steps/mail/get.inc              | 21 ++-------------------
 program/steps/mail/show.inc             |  5 +----
 6 files changed, 30 insertions(+), 59 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_message.php b/program/lib/Roundcube/rcube_message.php
index 9fea8382a..d450bb439 100644
--- a/program/lib/Roundcube/rcube_message.php
+++ b/program/lib/Roundcube/rcube_message.php
@@ -106,7 +106,6 @@ class rcube_message
         if (!empty($this->headers->structure)) {
             $this->get_mime_numbers($this->headers->structure);
             $this->parse_structure($this->headers->structure);
-            $this->parse_attachments();
         }
         else {
             $this->body = $this->storage->get_body($uid);
@@ -650,26 +649,6 @@ class rcube_message
     }
 
 
-    /**
-     * Parse attachment parts
-     */
-    private function parse_attachments()
-    {
-        // Attachment must have a name
-        foreach ($this->attachments as $attachment) {
-            if (!$attachment->filename) {
-                $ext = rcube_mime::get_mime_extensions($attachment->mimetype);
-                $ext = array_shift($ext);
-
-                $attachment->filename = 'Part_' . $attachment->mime_id;
-                if ($ext) {
-                    $attachment->filename .= '.' . $ext;
-                }
-            }
-        }
-    }
-
-
     /**
      * Fill aflat array with references to all parts, indexed by part numbers
      *
diff --git a/program/localization/en_US/labels.inc b/program/localization/en_US/labels.inc
index 730e6af09..fa8f33d6d 100644
--- a/program/localization/en_US/labels.inc
+++ b/program/localization/en_US/labels.inc
@@ -377,6 +377,7 @@ $labels['edititem'] = 'Edit item';
 $labels['preferhtml'] = 'Display HTML';
 $labels['defaultcharset'] = 'Default Character Set';
 $labels['htmlmessage'] = 'HTML Message';
+$labels['messagepart'] = 'Part';
 $labels['dateformat'] = 'Date format';
 $labels['timeformat'] = 'Time format';
 $labels['prettydate'] = 'Pretty dates';
diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc
index 379e920e5..74c6d5f29 100644
--- a/program/steps/mail/compose.inc
+++ b/program/steps/mail/compose.inc
@@ -1154,16 +1154,7 @@ function rcmail_save_attachment(&$message, $pid)
   }
 
   $mimetype = $part->ctype_primary . '/' . $part->ctype_secondary;
-  $filename = $part->filename;
-  if (!strlen($filename)) {
-    if ($mimetype == 'text/html') {
-      $filename = rcube_label('htmlmessage');
-    }
-    else {
-      $filename = 'Part_'.$pid;
-    }
-    $filename .= '.' . $part->ctype_secondary;
-  }
+  $filename = rcmail_attachment_name($part);
 
   $attachment = array(
     'group' => $COMPOSE['id'],
diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc
index 814adb64d..bedd3e8ea 100644
--- a/program/steps/mail/func.inc
+++ b/program/steps/mail/func.inc
@@ -1598,10 +1598,7 @@ function rcmail_message_part_controls($attrib)
   $part  = $MESSAGE->mime_parts[$part];
   $table = new html_table(array('cols' => 3));
 
-  $filename = $part->filename;
-  if (empty($filename) && $attach_prop->mimetype == 'text/html') {
-    $filename = rcube_label('htmlmessage');
-  }
+  $filename = rcmail_attachment_name($part);
 
   if (!empty($filename)) {
     $table->add('title', Q(rcube_label('filename')));
@@ -1616,7 +1613,6 @@ function rcmail_message_part_controls($attrib)
 }
 
 
-
 function rcmail_message_part_frame($attrib)
 {
   global $MESSAGE;
@@ -1841,6 +1837,30 @@ function rcmail_fix_mimetype($name)
   return $name;
 }
 
+// return attachment filename, handle empty filename case
+function rcmail_attachment_name($attachment)
+{
+    $filename = $attachment->filename;
+
+    if ($filename === null || $filename === '') {
+        if ($attachment->mimetype == 'text/html') {
+            $filename = rcube_label('htmlmessage');
+        }
+        else {
+            $ext      = rcube_mime::get_mime_extensions($attachment->mimetype);
+            $ext      = array_shift($ext);
+            $filename = rcube_label('messagepart') . ' ' . $attachment->mime_id;
+            if ($ext) {
+                $filename .= '.' . $ext;
+            }
+        }
+    }
+
+    $filename = preg_replace('[\r\n]', '', $filename);
+
+    return $filename;
+}
+
 function rcmail_search_filter($attrib)
 {
   global $OUTPUT, $CONFIG;
diff --git a/program/steps/mail/get.inc b/program/steps/mail/get.inc
index 803716d61..37f728ebf 100644
--- a/program/steps/mail/get.inc
+++ b/program/steps/mail/get.inc
@@ -47,13 +47,7 @@ check_storage_status();
 // show part page
 if (!empty($_GET['_frame'])) {
   if (($part_id = get_input_value('_part', RCUBE_INPUT_GPC)) && ($part = $MESSAGE->mime_parts[$part_id])) {
-    $filename = $part->filename;
-    if (empty($filename) && $part->mimetype == 'text/html') {
-      $filename = rcube_label('htmlmessage');
-    }
-    if (!empty($filename)) {
-      $OUTPUT->set_pagetitle($filename);
-    }
+    $OUTPUT->set_pagetitle(rcmail_attachment_name($part));
   }
 
   $OUTPUT->send('messagepart');
@@ -236,18 +230,7 @@ else if (strlen($pid = get_input_value('_part', RCUBE_INPUT_GET))) {
       // don't kill the connection if download takes more than 30 sec.
       @set_time_limit(0);
 
-      if ($part->filename) {
-        $filename = $part->filename;
-      }
-      else if ($part->mimetype == 'text/html') {
-        $filename = rcube_label('htmlmessage');
-      }
-      else {
-        $ext = '.' . ($mimetype == 'text/plain' ? 'txt' : $ctype_secondary);
-        $filename = ($MESSAGE->subject ? $MESSAGE->subject : 'roundcube') . $ext;
-      }
-
-      $filename = preg_replace('[\r\n]', '', $filename);
+      $filename = rcmail_attachment_name($part);
 
       if ($browser->ie && $browser->ver < 7)
         $filename = rawurlencode(abbreviate_string($filename, 55));
diff --git a/program/steps/mail/show.inc b/program/steps/mail/show.inc
index 82594f3e4..22f4ff4c2 100644
--- a/program/steps/mail/show.inc
+++ b/program/steps/mail/show.inc
@@ -150,10 +150,7 @@ function rcmail_message_attachments($attrib)
 
   if (sizeof($MESSAGE->attachments)) {
     foreach ($MESSAGE->attachments as $attach_prop) {
-      $filename = $attach_prop->filename;
-      if (empty($filename) && $attach_prop->mimetype == 'text/html') {
-        $filename = rcube_label('htmlmessage');
-      }
+      $filename = rcmail_attachment_name($attach_prop);
 
       if ($PRINT_MODE) {
         $size = $RCMAIL->message_part_size($attach_prop);
-- 
cgit v1.2.3


From cb0f030ae98a51398bd01e3d5075c8e939d944bc Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Sun, 30 Dec 2012 13:09:52 +0100
Subject: Support "multipart/relative" as an alias for "multipart/related" type
 (#1488886)

---
 CHANGELOG                               | 1 +
 program/lib/Roundcube/rcube_message.php | 6 +++---
 2 files changed, 4 insertions(+), 3 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/CHANGELOG b/CHANGELOG
index 687fd8929..5a594c07e 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
 CHANGELOG Roundcube Webmail
 ===========================
 
+- Support "multipart/relative" as an alias for "multipart/related" type (#1488886)
 - Display PGP/MIME signature attachments as "Digital Signature" (#1488570)
 - Workaround UW-IMAP bug where hierarchy separator is added to the shared folder name (#1488879)
 - Fix version comparisons with -stable suffix (#1488876)
diff --git a/program/lib/Roundcube/rcube_message.php b/program/lib/Roundcube/rcube_message.php
index d450bb439..51b2242df 100644
--- a/program/lib/Roundcube/rcube_message.php
+++ b/program/lib/Roundcube/rcube_message.php
@@ -385,7 +385,7 @@ class rcube_message
             // get html/plaintext parts, other add to attachments list
             foreach ($structure->parts as $p => $sub_part) {
                 $sub_mimetype = $sub_part->mimetype;
-                $is_multipart = in_array($sub_mimetype, array('multipart/related', 'multipart/mixed', 'multipart/alternative'));
+                $is_multipart = preg_match('/^multipart\/(related|relative|mixed|alternative)/', $sub_mimetype);
 
                 // skip empty text parts
                 if (!$sub_part->size && !$is_multipart) {
@@ -560,7 +560,7 @@ class rcube_message
                         continue;
 
                     // part belongs to a related message and is linked
-                    if ($mimetype == 'multipart/related'
+                    if (preg_match('/^multipart\/(related|relative)/', $mimetype)
                         && ($mail_part->headers['content-id'] || $mail_part->headers['content-location'])) {
                         if ($mail_part->headers['content-id'])
                             $mail_part->content_id = preg_replace(array('/^</', '/>$/'), '', $mail_part->headers['content-id']);
@@ -599,7 +599,7 @@ class rcube_message
             }
 
             // if this was a related part try to resolve references
-            if ($mimetype == 'multipart/related' && sizeof($this->inline_parts)) {
+            if (preg_match('/^multipart\/(related|relative)/', $mimetype) && sizeof($this->inline_parts)) {
                 $a_replaces = array();
                 $img_regexp = '/^image\/(gif|jpe?g|png|tiff|bmp|svg)/';
 
-- 
cgit v1.2.3


From 232535f76e50c08e77d8cba599fabe7fe8ca42d4 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Wed, 2 Jan 2013 19:25:07 +0100
Subject: Add option to use envelope From address for MDN responses (#1488880)

---
 CHANGELOG                            | 1 +
 config/main.inc.php.dist             | 4 ++++
 program/lib/Roundcube/rcube_smtp.php | 3 ++-
 program/steps/mail/func.inc          | 5 ++++-
 4 files changed, 11 insertions(+), 2 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/CHANGELOG b/CHANGELOG
index d3ebabc3e..8b26fbc14 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
 CHANGELOG Roundcube Webmail
 ===========================
 
+- Add option to use envelope From address for MDN responses (#1488880)
 - Add possibility to search in message body only (#1488770)
 - Support "multipart/relative" as an alias for "multipart/related" type (#1488886)
 - Display PGP/MIME signature attachments as "Digital Signature" (#1488570)
diff --git a/config/main.inc.php.dist b/config/main.inc.php.dist
index 20b8795e1..e6cb9fd4c 100644
--- a/config/main.inc.php.dist
+++ b/config/main.inc.php.dist
@@ -353,6 +353,10 @@ $rcmail_config['line_length'] = 72;
 // send plaintext messages as format=flowed
 $rcmail_config['send_format_flowed'] = true;
 
+// According to RFC2298, return receipt envelope sender address must be empty.
+// If this option is true, Roundcube will use user's identity as envelope sender for MDN responses.
+$rcmail_config['mdn_use_from'] = false;
+
 // Set identities access level:
 // 0 - many identities with possibility to edit all params
 // 1 - many identities with possibility to edit all params but not email address
diff --git a/program/lib/Roundcube/rcube_smtp.php b/program/lib/Roundcube/rcube_smtp.php
index 79ffcfbf8..5c7d2203c 100644
--- a/program/lib/Roundcube/rcube_smtp.php
+++ b/program/lib/Roundcube/rcube_smtp.php
@@ -227,7 +227,8 @@ class rcube_smtp
         }
 
         // RFC2298.3: remove envelope sender address
-        if (preg_match('/Content-Type: multipart\/report/', $text_headers)
+        if (empty($opts['mdn_use_from'])
+            && preg_match('/Content-Type: multipart\/report/', $text_headers)
             && preg_match('/report-type=disposition-notification/', $text_headers)
         ) {
             $from = '';
diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc
index 44a1557c3..f82e60a36 100644
--- a/program/steps/mail/func.inc
+++ b/program/steps/mail/func.inc
@@ -1687,6 +1687,9 @@ function rcmail_send_mdn($message, &$smtp_error)
     if ($agent = $RCMAIL->config->get('useragent'))
       $headers['User-Agent'] = $agent;
 
+    if ($RCMAIL->config->get('mdn_use_from'))
+      $options['mdn_use_from'] = true;
+
     $body = rcube_label("yourmessage") . "\r\n\r\n" .
       "\t" . rcube_label("to") . ': ' . rcube_mime::decode_mime_string($message->headers->to, $message->headers->charset) . "\r\n" .
       "\t" . rcube_label("subject") . ': ' . $message->subject . "\r\n" .
@@ -1708,7 +1711,7 @@ function rcmail_send_mdn($message, &$smtp_error)
     $compose->setTXTBody(rc_wordwrap($body, 75, "\r\n"));
     $compose->addAttachment($report, 'message/disposition-notification', 'MDNPart2.txt', false, '7bit', 'inline');
 
-    $sent = rcmail_deliver_message($compose, $identity['email'], $mailto, $smtp_error, $body_file);
+    $sent = rcmail_deliver_message($compose, $identity['email'], $mailto, $smtp_error, $body_file, $options);
 
     if ($sent)
     {
-- 
cgit v1.2.3


From 21106b3d1c1dc9dea14f2abc8e6a2d4073b91f65 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Sat, 5 Jan 2013 20:16:06 +0100
Subject: Fix handling of escaped separator in vCard file (#1488896)

---
 CHANGELOG                             |  1 +
 program/lib/Roundcube/rcube_vcard.php | 18 ++++++++++++------
 tests/Framework/VCard.php             | 10 ++++++----
 3 files changed, 19 insertions(+), 10 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/CHANGELOG b/CHANGELOG
index 525aac44d..234c10c07 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
 CHANGELOG Roundcube Webmail
 ===========================
 
+- Fix handling of escaped separator in vCard file (#1488896)
 - Fix #countcontrols issue in IE<=8 when text is very long (#1488890)
 - Add option to use envelope From address for MDN responses (#1488880)
 - Add possibility to search in message body only (#1488770)
diff --git a/program/lib/Roundcube/rcube_vcard.php b/program/lib/Roundcube/rcube_vcard.php
index e6fa5b248..c2b30af59 100644
--- a/program/lib/Roundcube/rcube_vcard.php
+++ b/program/lib/Roundcube/rcube_vcard.php
@@ -770,15 +770,21 @@ class rcube_vcard
      */
     private static function vcard_unquote($s, $sep = ';')
     {
-        // break string into parts separated by $sep, but leave escaped $sep alone
-        if (count($parts = explode($sep, strtr($s, array("\\$sep" => "\007")))) > 1) {
-            foreach($parts as $s) {
-                $result[] = self::vcard_unquote(strtr($s, array("\007" => "\\$sep")), $sep);
+        // break string into parts separated by $sep
+        if (!empty($sep)) {
+            // Handle properly backslash escaping (#1488896)
+            $rep1 = array("\\\\" => "\010", "\\$sep" => "\007");
+            $rep2 = array("\007" => "\\$sep", "\010" => "\\\\");
+
+            if (count($parts = explode($sep, strtr($s, $rep1))) > 1) {
+                foreach ($parts as $s) {
+                    $result[] = self::vcard_unquote(strtr($s, $rep2));
+                }
+                return $result;
             }
-            return $result;
         }
 
-        return strtr($s, array("\r" => '', '\\\\' => '\\', '\n' => "\n", '\N' => "\n", '\,' => ',', '\;' => ';', '\:' => ':'));
+        return strtr($s, array("\r" => '', '\\\\' => '\\', '\n' => "\n", '\N' => "\n", '\,' => ',', '\;' => ';'));
     }
 
     /**
diff --git a/tests/Framework/VCard.php b/tests/Framework/VCard.php
index 3bc01b186..15aa5d816 100644
--- a/tests/Framework/VCard.php
+++ b/tests/Framework/VCard.php
@@ -55,12 +55,14 @@ class Framework_VCard extends PHPUnit_Framework_TestCase
      */
     function test_parse_four()
     {
-        $vcard = "BEGIN:VCARD\nVERSION:3.0\nN:last\\;;first\\\\;middle;;\nFN:test\nEND:VCARD";
+        $vcard = "BEGIN:VCARD\nVERSION:3.0\nN:last\\;;first\\\\;middle\\\\\\;\\\\;prefix;\nFN:test\nEND:VCARD";
         $vcard = new rcube_vcard($vcard, null);
+        $vcard = $vcard->get_assoc();
 
-        $this->assertEquals("last;", $vcard->surname, "Decode backslash character");
-        $this->assertEquals("first\\", $vcard->firstname, "Decode backslash character");
-        $this->assertEquals("middle", $vcard->middlename, "Decode backslash character");
+        $this->assertEquals("last;", $vcard['surname'], "Decode backslash character");
+        $this->assertEquals("first\\", $vcard['firstname'], "Decode backslash character");
+        $this->assertEquals("middle\\;\\", $vcard['middlename'], "Decode backslash character");
+        $this->assertEquals("prefix", $vcard['prefix'], "Decode backslash character");
     }
 
     function test_import()
-- 
cgit v1.2.3


From a5b8ef99d4e26bdb00e9b74221f107767a084a6e Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Sun, 6 Jan 2013 13:10:50 +0100
Subject: Improve charset detection by prioritizing charset according to user
 language (#1485669)

---
 CHANGELOG                               |  1 +
 program/lib/Roundcube/rcube.php         | 16 +++++++
 program/lib/Roundcube/rcube_charset.php | 85 +++++++++++++++++++++------------
 tests/Framework/Charset.php             | 18 +++++++
 4 files changed, 90 insertions(+), 30 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/CHANGELOG b/CHANGELOG
index 234c10c07..dd249885d 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
 CHANGELOG Roundcube Webmail
 ===========================
 
+- Improve charset detection by prioritizing charset according to user language (#1485669)
 - Fix handling of escaped separator in vCard file (#1488896)
 - Fix #countcontrols issue in IE<=8 when text is very long (#1488890)
 - Add option to use envelope From address for MDN responses (#1488880)
diff --git a/program/lib/Roundcube/rcube.php b/program/lib/Roundcube/rcube.php
index cde549052..a914ae65a 100644
--- a/program/lib/Roundcube/rcube.php
+++ b/program/lib/Roundcube/rcube.php
@@ -1258,6 +1258,22 @@ class rcube
             return $this->decrypt($_SESSION['password']);
         }
     }
+
+
+    /**
+     * Getter for logged user language code.
+     *
+     * @return string User language code
+     */
+    public function get_user_language()
+    {
+        if (is_object($this->user)) {
+            return $this->user->language;
+        }
+        else if (isset($_SESSION['language'])) {
+            return $_SESSION['language'];
+        }
+    }
 }
 
 
diff --git a/program/lib/Roundcube/rcube_charset.php b/program/lib/Roundcube/rcube_charset.php
index 968d1c4b8..a7f26a3f4 100644
--- a/program/lib/Roundcube/rcube_charset.php
+++ b/program/lib/Roundcube/rcube_charset.php
@@ -646,12 +646,13 @@ class rcube_charset
     /**
      * A method to guess character set of a string.
      *
-     * @param string $string    String.
-     * @param string $failover 	Default result for failover.
+     * @param string $string   String
+     * @param string $failover Default result for failover
+     * @param string $language User language
      *
      * @return string Charset name
      */
-    public static function detect($string, $failover='')
+    public static function detect($string, $failover = null, $language = null)
     {
         if (substr($string, 0, 4) == "\0\0\xFE\xFF") return 'UTF-32BE';  // Big Endian
         if (substr($string, 0, 4) == "\xFF\xFE\0\0") return 'UTF-32LE';  // Little Endian
@@ -666,38 +667,62 @@ class rcube_charset
         if ($string[0] != "\0" && $string[1] == "\0" && $string[2] != "\0" && $string[3] == "\0") return 'UTF-16LE';
 
         if (function_exists('mb_detect_encoding')) {
-            // FIXME: the order is important, because sometimes
-            // iso string is detected as euc-jp and etc.
-            $enc = array(
-                'UTF-8', 'SJIS', 'GB2312',
-                'ISO-8859-1', 'ISO-8859-2', 'ISO-8859-3', 'ISO-8859-4',
-                'ISO-8859-5', 'ISO-8859-6', 'ISO-8859-7', 'ISO-8859-8', 'ISO-8859-9',
-                'ISO-8859-10', 'ISO-8859-13', 'ISO-8859-14', 'ISO-8859-15', 'ISO-8859-16',
-                'WINDOWS-1252', 'WINDOWS-1251', 'EUC-JP', 'EUC-TW', 'KOI8-R', 'BIG5',
-                'ISO-2022-KR', 'ISO-2022-JP',
-            );
+            if (empty($language)) {
+                $rcube    = rcube::get_instance();
+                $language = $rcube->get_user_language();
+            }
+
+            // Prioritize charsets according to current language (#1485669)
+            switch ($language) {
+            case 'ja_JP': // for Japanese
+                $prio = array('ISO-2022-JP', 'JIS', 'UTF-8', 'EUC-JP', 'eucJP-win', 'SJIS', 'SJIS-win');
+                break;
+
+            case 'zh_CN': // for Chinese (Simplified)
+            case 'zh_TW': // for Chinese (Traditional)
+                $prio = array('UTF-8', 'BIG-5', 'GB2312', 'EUC-TW');
+                break;
+
+            case 'ko_KR': // for Korean
+                $prio = array('UTF-8', 'EUC-KR', 'ISO-2022-KR');
+                break;
+
+            case 'ru_RU': // for Russian
+                $prio = array('UTF-8', 'WINDOWS-1251', 'KOI8-R');
+                break;
 
-            $result = mb_detect_encoding($string, join(',', $enc));
+            default:
+                $prio = array('UTF-8', 'SJIS', 'GB2312',
+                    'ISO-8859-1', 'ISO-8859-2', 'ISO-8859-3', 'ISO-8859-4',
+                    'ISO-8859-5', 'ISO-8859-6', 'ISO-8859-7', 'ISO-8859-8', 'ISO-8859-9',
+                    'ISO-8859-10', 'ISO-8859-13', 'ISO-8859-14', 'ISO-8859-15', 'ISO-8859-16',
+                    'WINDOWS-1252', 'WINDOWS-1251', 'EUC-JP', 'EUC-TW', 'KOI8-R', 'BIG-5',
+                    'ISO-2022-KR', 'ISO-2022-JP',
+                );
+            }
+
+            $encodings = array_unique(array_merge($prio, mb_list_encodings()));
+
+            return mb_detect_encoding($string, $encodings);
         }
-        else {
-            // No match, check for UTF-8
-            // from http://w3.org/International/questions/qa-forms-utf-8.html
-            if (preg_match('/\A(
-                [\x09\x0A\x0D\x20-\x7E]
-                | [\xC2-\xDF][\x80-\xBF]
-                | \xE0[\xA0-\xBF][\x80-\xBF]
-                | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}
-                | \xED[\x80-\x9F][\x80-\xBF]
-                | \xF0[\x90-\xBF][\x80-\xBF]{2}
-                | [\xF1-\xF3][\x80-\xBF]{3}
-                | \xF4[\x80-\x8F][\x80-\xBF]{2}
-                )*\z/xs', substr($string, 0, 2048))
-            ) {
+
+        // No match, check for UTF-8
+        // from http://w3.org/International/questions/qa-forms-utf-8.html
+        if (preg_match('/\A(
+            [\x09\x0A\x0D\x20-\x7E]
+            | [\xC2-\xDF][\x80-\xBF]
+            | \xE0[\xA0-\xBF][\x80-\xBF]
+            | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}
+            | \xED[\x80-\x9F][\x80-\xBF]
+            | \xF0[\x90-\xBF][\x80-\xBF]{2}
+            | [\xF1-\xF3][\x80-\xBF]{3}
+            | \xF4[\x80-\x8F][\x80-\xBF]{2}
+            )*\z/xs', substr($string, 0, 2048))
+        ) {
             return 'UTF-8';
-            }
         }
 
-        return $result ? $result : $failover;
+        return $failover;
     }
 
 
diff --git a/tests/Framework/Charset.php b/tests/Framework/Charset.php
index 1fd1654dc..d3d3e88dd 100644
--- a/tests/Framework/Charset.php
+++ b/tests/Framework/Charset.php
@@ -159,4 +159,22 @@ class Framework_Charset extends PHPUnit_Framework_TestCase
         $this->assertEquals($output, rcube_charset::detect($input, $fallback));
     }
 
+    /**
+     * Data for test_detect()
+     */
+    function data_detect_with_lang()
+    {
+        return array(
+            array('��ܦW��,�D�n', 'zh_TW', 'BIG-5'),
+        );
+    }
+
+    /**
+     * @dataProvider data_detect_with_lang
+     */
+    function test_detect_with_lang($input, $lang, $output)
+    {
+        $this->assertEquals($output, rcube_charset::detect($input, $output, $lang));
+    }
+
 }
-- 
cgit v1.2.3


From 83f7077ec930952cdc9cfc8982b80cd4dad06b5f Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Mon, 7 Jan 2013 14:21:25 +0100
Subject: Fix searching by date in address book (#1488888)

---
 CHANGELOG                                   |  1 +
 program/js/app.js                           | 11 ++++---
 program/lib/Roundcube/rcube_addressbook.php | 50 ++++++++++++++++++++++++++---
 program/lib/Roundcube/rcube_contacts.php    | 25 +++------------
 program/lib/Roundcube/rcube_ldap.php        | 17 ++--------
 program/steps/addressbook/search.inc        |  6 +++-
 6 files changed, 65 insertions(+), 45 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/CHANGELOG b/CHANGELOG
index dd249885d..fe98dd00f 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
 CHANGELOG Roundcube Webmail
 ===========================
 
+- Fix searching by date in address book (#1488888)
 - Improve charset detection by prioritizing charset according to user language (#1485669)
 - Fix handling of escaped separator in vCard file (#1488896)
 - Fix #countcontrols issue in IE<=8 when text is very long (#1488890)
diff --git a/program/js/app.js b/program/js/app.js
index 5b8c2cd2f..c627983f4 100644
--- a/program/js/app.js
+++ b/program/js/app.js
@@ -361,7 +361,7 @@ function rcube_webmail()
 
         if (this.gui_objects.editform) {
           this.enable_command('save', true);
-          if (this.env.action == 'add' || this.env.action == 'edit')
+          if (this.env.action == 'add' || this.env.action == 'edit' || this.env.action == 'search')
               this.init_contact_form();
         }
 
@@ -4396,10 +4396,11 @@ function rcube_webmail()
   {
     var ref = this, col;
 
-    this.set_photo_actions($('#ff_photo').val());
-
-    for (col in this.env.coltypes)
-      this.init_edit_field(col, null);
+    if (this.env.coltypes) {
+      this.set_photo_actions($('#ff_photo').val());
+      for (col in this.env.coltypes)
+        this.init_edit_field(col, null);
+    }
 
     $('.contactfieldgroup .row a.deletebutton').click(function() {
       ref.delete_edit_field(this);
diff --git a/program/lib/Roundcube/rcube_addressbook.php b/program/lib/Roundcube/rcube_addressbook.php
index 98d8f98ee..ffe35097a 100644
--- a/program/lib/Roundcube/rcube_addressbook.php
+++ b/program/lib/Roundcube/rcube_addressbook.php
@@ -46,6 +46,7 @@ abstract class rcube_addressbook
     public $sort_order = 'ASC';
     public $coltypes = array('name' => array('limit'=>1), 'firstname' => array('limit'=>1), 'surname' => array('limit'=>1), 'email' => array('limit'=>1));
 
+    protected $date_types = array();
     protected $error;
 
     /**
@@ -222,7 +223,6 @@ abstract class rcube_addressbook
         return true;
     }
 
-
     /**
      * Create a new contact record
      *
@@ -407,7 +407,6 @@ abstract class rcube_addressbook
         return array();
     }
 
-
     /**
      * Utility function to return all values of a certain data column
      * either as flat list or grouped by subtype
@@ -440,7 +439,6 @@ abstract class rcube_addressbook
         return $out;
     }
 
-
     /**
      * Normalize the given string for fulltext search.
      * Currently only optimized for Latin-1 characters; to be extended
@@ -488,7 +486,6 @@ abstract class rcube_addressbook
         return $fn;
     }
 
-
     /**
      * Compose the name to display in the contacts list for the given contact record.
      * This respects the settings parameter how to list conacts.
@@ -526,5 +523,50 @@ abstract class rcube_addressbook
         return $fn;
     }
 
+    /**
+     * Compare search value with contact data
+     *
+     * @param string       $colname Data name
+     * @param string|array $value   Data value
+     * @param string       $search  Search value
+     * @param int          $mode    Search mode
+     *
+     * @return bool Comparision result
+     */
+    protected function compare_search_value($colname, $value, $search, $mode)
+    {
+        // The value is a date string, for date we'll
+        // use only strict comparison (mode = 1)
+        // @TODO: partial search, e.g. match only day and month
+        if (in_array($colname, $this->date_types)) {
+            return (($value = rcube_utils::strtotime($value))
+                && ($search = rcube_utils::strtotime($search))
+                && date('Ymd', $value) == date('Ymd', $search));
+        }
+
+        // composite field, e.g. address
+        foreach ((array)$value as $val) {
+            $val = mb_strtolower($val);
+            switch ($mode) {
+            case 1:
+                $got = ($val == $search);
+                break;
+
+            case 2:
+                $got = ($search == substr($val, 0, strlen($search)));
+                break;
+
+            default:
+                $got = (strpos($val, $search) !== false);
+            }
+
+            if ($got) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
 }
 
diff --git a/program/lib/Roundcube/rcube_contacts.php b/program/lib/Roundcube/rcube_contacts.php
index a98b13865..062bd1e19 100644
--- a/program/lib/Roundcube/rcube_contacts.php
+++ b/program/lib/Roundcube/rcube_contacts.php
@@ -45,6 +45,7 @@ class rcube_contacts extends rcube_addressbook
     private $fulltext_cols = array('name', 'firstname', 'surname', 'middlename', 'nickname',
       'jobtitle', 'organization', 'department', 'maidenname', 'email', 'phone',
       'address', 'street', 'locality', 'zipcode', 'region', 'country', 'website', 'im', 'notes');
+    protected $date_types = array('birthday', 'anniversary');
 
     // public properties
     public $primary_key = 'contact_id';
@@ -401,32 +402,16 @@ class rcube_contacts extends rcube_addressbook
             for ($i=0; $i<$pages; $i++) {
                 $this->list_records(null, $i, true);
                 while ($row = $this->result->next()) {
-                    $id = $row[$this->primary_key];
+                    $id    = $row[$this->primary_key];
                     $found = array();
                     foreach (preg_grep($regexp, array_keys($row)) as $col) {
                         $pos     = strpos($col, ':');
                         $colname = $pos ? substr($col, 0, $pos) : $col;
                         $search  = $post_search[$colname];
                         foreach ((array)$row[$col] as $value) {
-                            // composite field, e.g. address
-                            foreach ((array)$value as $val) {
-                                $val = mb_strtolower($val);
-                                switch ($mode) {
-                                case 1:
-                                    $got = ($val == $search);
-                                    break;
-                                case 2:
-                                    $got = ($search == substr($val, 0, strlen($search)));
-                                    break;
-                                default:
-                                    $got = (strpos($val, $search) !== false);
-                                    break;
-                                }
-
-                                if ($got) {
-                                    $found[$colname] = true;
-                                    break 2;
-                                }
+                            if ($this->compare_search_value($colname, $value, $search, $mode)) {
+                                $found[$colname] = true;
+                                break 2;
                             }
                         }
                     }
diff --git a/program/lib/Roundcube/rcube_ldap.php b/program/lib/Roundcube/rcube_ldap.php
index d4bc669fd..700c6f60c 100644
--- a/program/lib/Roundcube/rcube_ldap.php
+++ b/program/lib/Roundcube/rcube_ldap.php
@@ -794,27 +794,14 @@ class rcube_ldap extends rcube_addressbook
             $this->_debug("S: ".ldap_count_entries($this->conn, $this->ldap_result)." record(s)");
 
             // get all entries of this page and post-filter those that really match the query
-            $search = mb_strtolower($value);
+            $search  = mb_strtolower($value);
             $entries = ldap_get_entries($this->conn, $this->ldap_result);
 
             for ($i = 0; $i < $entries['count']; $i++) {
                 $rec = $this->_ldap2result($entries[$i]);
                 foreach ($fields as $f) {
                     foreach ((array)$rec[$f] as $val) {
-                        $val = mb_strtolower($val);
-                        switch ($mode) {
-                        case 1:
-                            $got = ($val == $search);
-                            break;
-                        case 2:
-                            $got = ($search == substr($val, 0, strlen($search)));
-                            break;
-                        default:
-                            $got = (strpos($val, $search) !== false);
-                            break;
-                        }
-
-                        if ($got) {
+                        if ($this->compare_search_value($f, $val, $search, $mode)) {
                             $this->result->add($rec);
                             $this->result->count++;
                             break 2;
diff --git a/program/steps/addressbook/search.inc b/program/steps/addressbook/search.inc
index 851325070..bbd9b9a76 100644
--- a/program/steps/addressbook/search.inc
+++ b/program/steps/addressbook/search.inc
@@ -300,9 +300,13 @@ function rcmail_contact_search_form($attrib)
             $label    = isset($colprop['label']) ? $colprop['label'] : rcube_label($col);
             $category = $colprop['category'] ? $colprop['category'] : 'other';
 
-            if ($ftype == 'text')
+            // load jquery UI datepicker for date fields 
+            if ($colprop['type'] == 'date')
+                $colprop['class'] .= ($colprop['class'] ? ' ' : '') . 'datepicker';
+            else if ($ftype == 'text')
                 $colprop['size'] = $i_size;
 
+
             $content  = html::div('row', html::div('contactfieldlabel label', Q($label))
                 . html::div('contactfieldcontent', rcmail_get_edit_field('search_'.$col, '', $colprop, $ftype)));
 
-- 
cgit v1.2.3


From 745d8697ba6ff7b35bda24d9c2319a9ed848152c Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Mon, 7 Jan 2013 15:06:09 +0100
Subject: Fix quoted data handling in CSV files (#1488899)

---
 program/lib/Roundcube/rcube_csv2vcard.php | 37 +++++++++++++++++++++++++------
 1 file changed, 30 insertions(+), 7 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_csv2vcard.php b/program/lib/Roundcube/rcube_csv2vcard.php
index 9c28a3b49..f94a7aac7 100644
--- a/program/lib/Roundcube/rcube_csv2vcard.php
+++ b/program/lib/Roundcube/rcube_csv2vcard.php
@@ -271,13 +271,7 @@ class rcube_csv2vcard
 
         // Parse file
         foreach (preg_split("/[\r\n]+/", $csv) as $i => $line) {
-            $line = trim($line);
-            if (empty($line)) {
-                continue;
-            }
-
-            $elements = rcube_utils::explode_quoted_string(',', $line);
-
+            $elements = $this->parse_line($line);
             if (empty($elements)) {
                 continue;
             }
@@ -304,6 +298,35 @@ class rcube_csv2vcard
         return $this->vcards;
     }
 
+    /**
+     * Parse CSV file line
+     */
+    protected function parse_line($line)
+    {
+        $line = trim($line);
+        if (empty($line)) {
+            return null;
+        }
+
+        $fields = rcube_utils::explode_quoted_string(',', $line);
+
+        // remove quotes if needed
+        if (!empty($fields)) {
+            foreach ($fields as $idx => $value) {
+                if (($len = strlen($value)) > 1 && $value[0] == '"' && $value[$len-1] == '"') {
+                    // remove surrounding quotes
+                    $value = substr($value, 1, -1);
+                    // replace doubled quotes inside the string with single quote
+                    $value = str_replace('""', '"', $value);
+
+                    $fields[$idx] = $value;
+                }
+            }
+        }
+
+        return $fields;
+    }
+
     /**
      * Parse CSV header line, detect fields mapping
      */
-- 
cgit v1.2.3


From 3e3767138e2ea62aea549917c13040849036fbf3 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Mon, 7 Jan 2013 15:26:02 +0100
Subject: Rename $date_types -> $date_cols

---
 program/lib/Roundcube/rcube_addressbook.php | 4 ++--
 program/lib/Roundcube/rcube_contacts.php    | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_addressbook.php b/program/lib/Roundcube/rcube_addressbook.php
index ffe35097a..0411f586c 100644
--- a/program/lib/Roundcube/rcube_addressbook.php
+++ b/program/lib/Roundcube/rcube_addressbook.php
@@ -45,8 +45,8 @@ abstract class rcube_addressbook
     public $sort_col = 'name';
     public $sort_order = 'ASC';
     public $coltypes = array('name' => array('limit'=>1), 'firstname' => array('limit'=>1), 'surname' => array('limit'=>1), 'email' => array('limit'=>1));
+    public $date_cols = array();
 
-    protected $date_types = array();
     protected $error;
 
     /**
@@ -538,7 +538,7 @@ abstract class rcube_addressbook
         // The value is a date string, for date we'll
         // use only strict comparison (mode = 1)
         // @TODO: partial search, e.g. match only day and month
-        if (in_array($colname, $this->date_types)) {
+        if (in_array($colname, $this->date_fields)) {
             return (($value = rcube_utils::strtotime($value))
                 && ($search = rcube_utils::strtotime($search))
                 && date('Ymd', $value) == date('Ymd', $search));
diff --git a/program/lib/Roundcube/rcube_contacts.php b/program/lib/Roundcube/rcube_contacts.php
index 062bd1e19..c66e98687 100644
--- a/program/lib/Roundcube/rcube_contacts.php
+++ b/program/lib/Roundcube/rcube_contacts.php
@@ -45,7 +45,6 @@ class rcube_contacts extends rcube_addressbook
     private $fulltext_cols = array('name', 'firstname', 'surname', 'middlename', 'nickname',
       'jobtitle', 'organization', 'department', 'maidenname', 'email', 'phone',
       'address', 'street', 'locality', 'zipcode', 'region', 'country', 'website', 'im', 'notes');
-    protected $date_types = array('birthday', 'anniversary');
 
     // public properties
     public $primary_key = 'contact_id';
@@ -61,6 +60,7 @@ class rcube_contacts extends rcube_addressbook
       'jobtitle', 'organization', 'department', 'assistant', 'manager',
       'gender', 'maidenname', 'spouse', 'email', 'phone', 'address',
       'birthday', 'anniversary', 'website', 'im', 'notes', 'photo');
+    public $date_cols = array('birthday', 'anniversary');
 
     const SEPARATOR = ',';
 
-- 
cgit v1.2.3


From b5767d94b1c3fd29adffb225526162d69b37d05d Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Mon, 7 Jan 2013 15:28:39 +0100
Subject: Fix typo

---
 program/lib/Roundcube/rcube_addressbook.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_addressbook.php b/program/lib/Roundcube/rcube_addressbook.php
index 0411f586c..421062772 100644
--- a/program/lib/Roundcube/rcube_addressbook.php
+++ b/program/lib/Roundcube/rcube_addressbook.php
@@ -538,7 +538,7 @@ abstract class rcube_addressbook
         // The value is a date string, for date we'll
         // use only strict comparison (mode = 1)
         // @TODO: partial search, e.g. match only day and month
-        if (in_array($colname, $this->date_fields)) {
+        if (in_array($colname, $this->date_cols)) {
             return (($value = rcube_utils::strtotime($value))
                 && ($search = rcube_utils::strtotime($search))
                 && date('Ymd', $value) == date('Ymd', $search));
-- 
cgit v1.2.3


From acf851f823fba5354c2227e48c3097a524312268 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Mon, 7 Jan 2013 17:53:37 +0100
Subject: Fix address fields import from CSV (#1488900)

---
 program/lib/Roundcube/rcube_csv2vcard.php | 9 +++++++++
 tests/Framework/Csv2vcard.php             | 1 +
 tests/src/Csv2vcard/tb_plain.vcf          | 2 ++
 3 files changed, 12 insertions(+)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_csv2vcard.php b/program/lib/Roundcube/rcube_csv2vcard.php
index f94a7aac7..e8202c6d4 100644
--- a/program/lib/Roundcube/rcube_csv2vcard.php
+++ b/program/lib/Roundcube/rcube_csv2vcard.php
@@ -390,6 +390,15 @@ class rcube_csv2vcard
             }
         }
 
+        // Convert address(es) to rcube_vcard data
+        foreach ($contact as $idx => $value) {
+            $name = explode(':', $idx);
+            if (in_array($name[0], array('street', 'locality', 'region', 'zipcode', 'country'))) {
+                $contact['address:'.$name[1]][$name[0]] = $value;
+                unset($contact[$idx]);
+            }
+        }
+
         // Create vcard object
         $vcard = new rcube_vcard();
         foreach ($contact as $name => $value) {
diff --git a/tests/Framework/Csv2vcard.php b/tests/Framework/Csv2vcard.php
index 6fa3e163c..f460c42af 100644
--- a/tests/Framework/Csv2vcard.php
+++ b/tests/Framework/Csv2vcard.php
@@ -31,6 +31,7 @@ class Framework_Csv2vcard extends PHPUnit_Framework_TestCase
 
         $vcf_text = trim(str_replace("\r\n", "\n", $vcf_text));
         $vcard    = trim(str_replace("\r\n", "\n", $vcard));
+echo $vcard;
         $this->assertEquals($vcf_text, $vcard);
     }
 
diff --git a/tests/src/Csv2vcard/tb_plain.vcf b/tests/src/Csv2vcard/tb_plain.vcf
index aace259d8..b001c3924 100644
--- a/tests/src/Csv2vcard/tb_plain.vcf
+++ b/tests/src/Csv2vcard/tb_plain.vcf
@@ -15,4 +15,6 @@ ORG:Organization
 URL;TYPE=homepage:http://page.com
 URL;TYPE=other:http://webpage.tld
 BDAY;VALUE=date:1970-11-15
+ADR;TYPE=home:;;Priv address;City;region;xx-xxx;USA
+ADR;TYPE=work:;;Addr work;;;33-333;Poland
 END:VCARD
-- 
cgit v1.2.3


From 16915ee2ad97060e0c0c9376adf7eca77516cd86 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Tue, 8 Jan 2013 12:13:44 +0100
Subject: Don't convert to link a text with < and > characters

---
 program/lib/Roundcube/rcube_string_replacer.php | 2 +-
 tests/Framework/StringReplacer.php              | 2 ++
 2 files changed, 3 insertions(+), 1 deletion(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_string_replacer.php b/program/lib/Roundcube/rcube_string_replacer.php
index 6b289886b..49a378166 100644
--- a/program/lib/Roundcube/rcube_string_replacer.php
+++ b/program/lib/Roundcube/rcube_string_replacer.php
@@ -34,7 +34,7 @@ class rcube_string_replacer
     {
         // Simplified domain expression for UTF8 characters handling
         // Support unicode/punycode in top-level domain part
-        $utf_domain = '[^?&@"\'\\/()\s\r\t\n]+\\.?([^\\x00-\\x2f\\x3b-\\x40\\x5b-\\x60\\x7b-\\x7f]{2,}|xn--[a-zA-Z0-9]{2,})';
+        $utf_domain = '[^?&@"\'\\/()<>\s\r\t\n]+\\.?([^\\x00-\\x2f\\x3b-\\x40\\x5b-\\x60\\x7b-\\x7f]{2,}|xn--[a-zA-Z0-9]{2,})';
         $url1       = '.:;,';
         $url2       = 'a-zA-Z0-9%=#$@+?!&\\/_~\\[\\]\\(\\){}\*-';
 
diff --git a/tests/Framework/StringReplacer.php b/tests/Framework/StringReplacer.php
index 60399cf6b..e630ebac0 100644
--- a/tests/Framework/StringReplacer.php
+++ b/tests/Framework/StringReplacer.php
@@ -35,6 +35,8 @@ class Framework_StringReplacer extends PHPUnit_Framework_TestCase
             array('(http://link.com)', '(<a href="http://link.com" target="_blank">http://link.com</a>)'),
             array('http://link.com?a(b)c', '<a href="http://link.com?a(b)c" target="_blank">http://link.com?a(b)c</a>'),
             array('http://link.com?(link)', '<a href="http://link.com?(link)" target="_blank">http://link.com?(link)</a>'),
+            array('http://<test>', 'http://<test>'),
+            array('http://', 'http://'),
         );
     }
 
-- 
cgit v1.2.3


From f96593772ce3d685f7add2c3357428b82a5a6c84 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Wed, 9 Jan 2013 08:48:28 +0100
Subject: Force autocommit mode in mysql database driver (#1488902)

---
 CHANGELOG                                | 4 ++++
 program/lib/Roundcube/rcube_db_mysql.php | 3 +++
 2 files changed, 7 insertions(+)

(limited to 'program/lib/Roundcube')

diff --git a/CHANGELOG b/CHANGELOG
index fe98dd00f..25d79d1e2 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,10 @@
 CHANGELOG Roundcube Webmail
 ===========================
 
+- Force autocommit mode in mysql database driver (#1488902)
+
+RELEASE 0.9-beta
+----------------
 - Fix searching by date in address book (#1488888)
 - Improve charset detection by prioritizing charset according to user language (#1485669)
 - Fix handling of escaped separator in vCard file (#1488896)
diff --git a/program/lib/Roundcube/rcube_db_mysql.php b/program/lib/Roundcube/rcube_db_mysql.php
index c32cc259c..8ab6403c8 100644
--- a/program/lib/Roundcube/rcube_db_mysql.php
+++ b/program/lib/Roundcube/rcube_db_mysql.php
@@ -126,6 +126,9 @@ class rcube_db_mysql extends rcube_db
         // Always return matching (not affected only) rows count
         $result[PDO::MYSQL_ATTR_FOUND_ROWS] = true;
 
+        // Enable AUTOCOMMIT mode (#1488902)
+        $dsn_options[PDO::ATTR_AUTOCOMMIT] = true;
+
         return $result;
     }
 
-- 
cgit v1.2.3


From db6f54ec5f604d2629041263d6e638fa8e0ec0c7 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Wed, 9 Jan 2013 15:09:00 +0100
Subject: Reset $db_error_msg on query

---
 program/lib/Roundcube/rcube_db.php | 1 +
 1 file changed, 1 insertion(+)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_db.php b/program/lib/Roundcube/rcube_db.php
index 3e4617948..086a38ab4 100644
--- a/program/lib/Roundcube/rcube_db.php
+++ b/program/lib/Roundcube/rcube_db.php
@@ -402,6 +402,7 @@ class rcube_db
 
         // destroy reference to previous result, required for SQLite driver (#1488874)
         $this->last_result = null;
+        $this->db_error_msg = null;
 
         // send query
         $query = $this->dbh->query($query);
-- 
cgit v1.2.3


From 18e23ab763bab2875d4c8fb70034e218ec7c6d14 Mon Sep 17 00:00:00 2001
From: Thomas Bruederli <thomas@roundcube.net>
Date: Wed, 9 Jan 2013 17:50:51 +0100
Subject: Welcome to 2013

---
 index.php                           | 2 +-
 program/include/iniset.php          | 2 +-
 program/lib/Roundcube/bootstrap.php | 2 +-
 skins/larry/templates/about.html    | 2 +-
 4 files changed, 4 insertions(+), 4 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/index.php b/index.php
index aee94e971..3e398c00d 100644
--- a/index.php
+++ b/index.php
@@ -4,7 +4,7 @@
  | Roundcube Webmail IMAP Client                                           |
  | Version 0.9-git                                                         |
  |                                                                         |
- | Copyright (C) 2005-2012, The Roundcube Dev Team                         |
+ | Copyright (C) 2005-2013, The Roundcube Dev Team                         |
  |                                                                         |
  | This program is free software: you can redistribute it and/or modify    |
  | it under the terms of the GNU General Public License (with exceptions   |
diff --git a/program/include/iniset.php b/program/include/iniset.php
index be71fc084..35b5522fc 100644
--- a/program/include/iniset.php
+++ b/program/include/iniset.php
@@ -5,7 +5,7 @@
  | program/include/iniset.php                                            |
  |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
- | Copyright (C) 2008-2012, The Roundcube Dev Team                       |
+ | Copyright (C) 2008-2013, The Roundcube Dev Team                       |
  |                                                                       |
  | Licensed under the GNU General Public License version 3 or            |
  | any later version with exceptions for skins & plugins.                |
diff --git a/program/lib/Roundcube/bootstrap.php b/program/lib/Roundcube/bootstrap.php
index 530a7f855..8cea48122 100644
--- a/program/lib/Roundcube/bootstrap.php
+++ b/program/lib/Roundcube/bootstrap.php
@@ -3,7 +3,7 @@
 /*
  +-----------------------------------------------------------------------+
  | This file is part of the Roundcube PHP suite                          |
- | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
+ | Copyright (C) 2005-2013, The Roundcube Dev Team                       |
  |                                                                       |
  | Licensed under the GNU General Public License version 3 or            |
  | any later version with exceptions for skins & plugins.                |
diff --git a/skins/larry/templates/about.html b/skins/larry/templates/about.html
index 2c18e8889..301c301a9 100644
--- a/skins/larry/templates/about.html
+++ b/skins/larry/templates/about.html
@@ -10,7 +10,7 @@
 <roundcube:object name="aboutcontent" />
 
 <h2 class="sysname">Roundcube Webmail <roundcube:object name="version" /></h2>
-<p class="copyright">Copyright &copy; 2005-2012, The Roundcube Dev Team</p>
+<p class="copyright">Copyright &copy; 2005-2013, The Roundcube Dev Team</p>
 <p class="license">This program is free software; you can redistribute it and/or modify
 it under the terms of the <a href="http://www.gnu.org/licenses/gpl.html" target="_blank">GNU General Public License</a>
 as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.<br/>
-- 
cgit v1.2.3


From c59ef9542a93a5cbacd99fe3dcfc0975bc749a12 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Fri, 11 Jan 2013 09:02:45 +0100
Subject: Support more Thunderbird CSV fields, added zh_TW localization for
 csv2vcard map (#1488901)

---
 program/lib/Roundcube/rcube_csv2vcard.php |  6 ++
 program/localization/zh_TW/csv2vcard.inc  | 99 +++++++++++++++++++++++++++++++
 tests/Framework/Csv2vcard.php             |  2 +-
 3 files changed, 106 insertions(+), 1 deletion(-)
 create mode 100644 program/localization/zh_TW/csv2vcard.inc

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_csv2vcard.php b/program/lib/Roundcube/rcube_csv2vcard.php
index e8202c6d4..0d3276b84 100644
--- a/program/lib/Roundcube/rcube_csv2vcard.php
+++ b/program/lib/Roundcube/rcube_csv2vcard.php
@@ -124,6 +124,12 @@ class rcube_csv2vcard
         //'work_address_2'        => '',
         'work_country'          => 'country:work',
         'work_zipcode'          => 'zipcode:work',
+        'last'                  => 'surname',
+        'first'                 => 'firstname',
+        'work_city'             => 'locality:work',
+        'work_state'            => 'region:work',
+        'home_city_short'       => 'locality:home',
+        'home_state_short'      => 'region:home',
     );
 
     /**
diff --git a/program/localization/zh_TW/csv2vcard.inc b/program/localization/zh_TW/csv2vcard.inc
new file mode 100644
index 000000000..9fcacc818
--- /dev/null
+++ b/program/localization/zh_TW/csv2vcard.inc
@@ -0,0 +1,99 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | localization/zh_TW/csv2vcard.inc                                      |
+ |                                                                       |
+ | Localization file of the Roundcube Webmail client                     |
+ | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
+ |                                                                       |
+ | Licensed under the GNU General Public License version 3 or            |
+ | any later version with exceptions for skins & plugins.                |
+ | See the README file for a full license statement.                     |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Aleksander Machniak <alec@alec.pl>                            |
+ +-----------------------------------------------------------------------+
+*/
+
+// This is a list of CSV column names specified in CSV file header
+// These must be original texts used in Outlook/Thunderbird exported csv files
+// Encoding UTF-8
+
+$map = array();
+
+// MS Outlook 2010
+$map['anniversary'] = "紀念日";
+$map['assistants_name'] = "助理";
+$map['assistants_phone'] = "助理電話";
+$map['birthday'] = "生日";
+$map['business_city'] = "商務 - 市/鎮";
+$map['business_countryregion'] = "商務 - 國家/地區";
+$map['business_fax'] = "商務傳真";
+$map['business_phone'] = "商務電話";
+$map['business_phone_2'] = "商務電話 2";
+$map['business_postal_code'] = "商務 - 郵遞區號";
+$map['business_state'] = "商務 - 縣市";
+$map['business_street'] = "商務 - 街";
+$map['car_phone'] = "汽車電話";
+$map['categories'] = "類別";
+$map['company'] = "公司";
+$map['department'] = "部門";
+$map['email_address'] = "電子郵件地址";
+$map['first_name'] = "名字";
+$map['gender'] = "性別";
+$map['home_city'] = "住家 - 市/鎮";
+$map['home_countryregion'] = "住家 - 國家/地區";
+$map['home_fax'] = "住家傳真";
+$map['home_phone'] = "住家電話";
+$map['home_phone_2'] = "住家電話 2";
+$map['home_postal_code'] = "住家 - 郵遞區號";
+$map['home_state'] = "住家 - 縣/市";
+$map['home_street'] = "住家 - 街";
+$map['job_title'] = "職稱";
+$map['last_name'] = "姓氏";
+$map['managers_name'] = "主管名稱";
+$map['middle_name'] = "中間名";
+$map['mobile_phone'] = "行動電話";
+$map['notes'] = "記事";
+$map['other_city'] = "其他 - 市/鎮";
+$map['other_countryregion'] = "其他 - 國家/地區";
+$map['other_fax'] = "其他傳真";
+$map['other_phone'] = "其他電話";
+$map['other_postal_code'] = "其他 - 郵遞區號";
+$map['other_state'] = "其他 - 縣/市";
+$map['other_street'] = "其他 - 街";
+$map['pager'] = "呼叫器";
+$map['primary_phone'] = "代表電話";
+$map['spouse'] = "配偶";
+$map['suffix'] = "稱謂";
+$map['title'] = "頭銜";
+$map['web_page'] = "網頁";
+
+// Thunderbird
+$map['last'] = "姓";
+$map['first'] = "名";
+$map['birth_day'] = "生日 (日)";
+$map['birth_month'] = "生日 (月)";
+$map['birth_year'] = "生日 (年)";
+$map['display_name'] = "顯示名稱";
+$map['fax_number'] = "傳真號碼";
+$map['home_address'] = "住家住址";
+$map['home_country'] = "居住國家";
+$map['home_zipcode'] = "住址郵遞區號";
+$map['mobile_number'] = "手機號碼";
+$map['nickname'] = "暱稱";
+$map['organization'] = "Organization";
+$map['pager_number'] = "呼叫器號碼";
+$map['primary_email'] = "主要 Email";
+$map['secondary_email'] = "次要 Email";
+$map['web_page_1'] = "網頁 1";
+$map['web_page_2'] = "網頁 2";
+$map['work_phone'] = "商務電話";
+$map['work_address'] = "商務地址";
+$map['work_country'] = "商務國家";
+$map['work_zipcode'] = "商務郵遞區號";
+$map['work_city'] = "商務市鎮";
+$map['work_state'] = "商務縣市";
+$map['home_city_short'] = "居住市鎮";
+$map['home_state_short'] = "居住縣市";
diff --git a/tests/Framework/Csv2vcard.php b/tests/Framework/Csv2vcard.php
index f460c42af..5d52efc58 100644
--- a/tests/Framework/Csv2vcard.php
+++ b/tests/Framework/Csv2vcard.php
@@ -31,7 +31,7 @@ class Framework_Csv2vcard extends PHPUnit_Framework_TestCase
 
         $vcf_text = trim(str_replace("\r\n", "\n", $vcf_text));
         $vcard    = trim(str_replace("\r\n", "\n", $vcard));
-echo $vcard;
+
         $this->assertEquals($vcf_text, $vcard);
     }
 
-- 
cgit v1.2.3


From 4f9edbd79936c2139d6a20b1121bfc6d8e46a2fa Mon Sep 17 00:00:00 2001
From: Thomas Bruederli <thomas@roundcube.net>
Date: Mon, 14 Jan 2013 21:41:02 +0100
Subject: Select 8 KB of message part for headers (to make sure we get them
 all)

---
 program/lib/Roundcube/rcube_message.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_message.php b/program/lib/Roundcube/rcube_message.php
index 51b2242df..b52b79b25 100644
--- a/program/lib/Roundcube/rcube_message.php
+++ b/program/lib/Roundcube/rcube_message.php
@@ -322,7 +322,7 @@ class rcube_message
 
             // parse headers from message/rfc822 part
             if (!isset($structure->headers['subject'])) {
-                list($headers, $dump) = explode("\r\n\r\n", $this->get_part_content($structure->mime_id, null, true, 4096));
+                list($headers, $dump) = explode("\r\n\r\n", $this->get_part_content($structure->mime_id, null, true, 8192));
                 $structure->headers = rcube_mime::parse_headers($headers);
             }
         }
-- 
cgit v1.2.3


From 6d41d8fd4bbd3f8854669fbf2fc5a4910803125a Mon Sep 17 00:00:00 2001
From: Thomas Bruederli <thomas@roundcube.net>
Date: Fri, 18 Jan 2013 21:26:18 +0100
Subject: Fix format=flowed unfolding on quoted lines; added tests for
 rcube_mime::format_flowed() and rcube_mime::unfold_flowed()

---
 program/lib/Roundcube/rcube_mime.php |  3 ++-
 tests/Framework/Mime.php             | 22 ++++++++++++++++++++++
 tests/src/format-flowed-unfolded.txt | 14 ++++++++++++++
 tests/src/format-flowed.txt          | 16 ++++++++++++++++
 4 files changed, 54 insertions(+), 1 deletion(-)
 create mode 100644 tests/src/format-flowed-unfolded.txt
 create mode 100644 tests/src/format-flowed.txt

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_mime.php b/program/lib/Roundcube/rcube_mime.php
index eef8ca17c..d5fb35bcd 100644
--- a/program/lib/Roundcube/rcube_mime.php
+++ b/program/lib/Roundcube/rcube_mime.php
@@ -480,7 +480,8 @@ class rcube_mime
                 $q = strlen(str_replace(' ', '', $regs[0]));
                 $line = substr($line, strlen($regs[0]));
 
-                if ($q == $q_level && $line
+                if ($q == $q_level
+                    && strlen($line[$last]) > 1  // don't hit if line only consist of one single white space
                     && isset($text[$last])
                     && $text[$last][strlen($text[$last])-1] == ' '
                 ) {
diff --git a/tests/Framework/Mime.php b/tests/Framework/Mime.php
index dcd55992a..1f9a8c58f 100644
--- a/tests/Framework/Mime.php
+++ b/tests/Framework/Mime.php
@@ -120,4 +120,26 @@ class Framework_Mime extends PHPUnit_Framework_TestCase
             $this->assertEquals($item['out'], $res, "Header decoding for: " . $idx);
         }
     }
+
+    /**
+     * Test format=flowed unfolding
+     */
+    function test_format_flowed()
+    {
+        $raw = file_get_contents(TESTS_DIR . 'src/format-flowed-unfolded.txt');
+        $flowed = file_get_contents(TESTS_DIR . 'src/format-flowed.txt');
+
+        $this->assertEquals($flowed, rcube_mime::format_flowed($raw, 80), "Test correct folding and space-stuffing");
+    }
+
+    /**
+     * Test format=flowed unfolding
+     */
+    function test_unfold_flowed()
+    {
+        $flowed = file_get_contents(TESTS_DIR . 'src/format-flowed.txt');
+        $unfolded = file_get_contents(TESTS_DIR . 'src/format-flowed-unfolded.txt');
+
+        $this->assertEquals($unfolded, rcube_mime::unfold_flowed($flowed), "Test correct unfolding of quoted lines");
+    }
 }
diff --git a/tests/src/format-flowed-unfolded.txt b/tests/src/format-flowed-unfolded.txt
new file mode 100644
index 000000000..8245d59d4
--- /dev/null
+++ b/tests/src/format-flowed-unfolded.txt
@@ -0,0 +1,14 @@
+I'm replying on this with a very long line which is then wrapped and space-stuffed because the draft is saved as format=flowed.
+From what's specified in RFC 2646 some lines need to be space-stuffed to avoid muning during transport.
+
+X
+
+On XX.YY.YYYY Y:YY, Somebody wrote:
+> This part is a reply wihtout any flowing lines. rcube_mime::unfold_flowed()
+> has to be careful with empty quoted lines because they might end with a
+> space but still shouldn't be considered as flowed!
+> 
+> The above empty line should persist after unfolding.
+> xxxxxxxxxx. xxxx xxxxx xxxxx xxxx xx xx.xx. xxxxxx xxxxxxxxxxxx, xxxx xx
+> 
+> ... and this one as well.
diff --git a/tests/src/format-flowed.txt b/tests/src/format-flowed.txt
new file mode 100644
index 000000000..522f829c6
--- /dev/null
+++ b/tests/src/format-flowed.txt
@@ -0,0 +1,16 @@
+I'm replying on this with a very long line which is then wrapped and 
+space-stuffed because the draft is saved as format=flowed.
+ From what's specified in RFC 2646 some lines need to be space-stuffed to 
+avoid muning during transport.
+
+X
+
+On XX.YY.YYYY Y:YY, Somebody wrote:
+> This part is a reply wihtout any flowing lines. rcube_mime::unfold_flowed()
+> has to be careful with empty quoted lines because they might end with a
+> space but still shouldn't be considered as flowed!
+> 
+> The above empty line should persist after unfolding.
+> xxxxxxxxxx. xxxx xxxxx xxxxx xxxx xx xx.xx. xxxxxx xxxxxxxxxxxx, xxxx xx
+> 
+> ... and this one as well.
-- 
cgit v1.2.3


From 7ae7cdf195f4a8b0449c9ab0d5f43f68a60efa76 Mon Sep 17 00:00:00 2001
From: Thomas Bruederli <thomas@roundcube.net>
Date: Fri, 18 Jan 2013 21:49:51 +0100
Subject: Don't rely on Subject header only

---
 program/lib/Roundcube/rcube_message.php | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_message.php b/program/lib/Roundcube/rcube_message.php
index b52b79b25..e0c3e3475 100644
--- a/program/lib/Roundcube/rcube_message.php
+++ b/program/lib/Roundcube/rcube_message.php
@@ -321,7 +321,7 @@ class rcube_message
             $mimetype = $structure->real_mimetype;
 
             // parse headers from message/rfc822 part
-            if (!isset($structure->headers['subject'])) {
+            if (!isset($structure->headers['subject']) && !isset($structure->headers['from'])) {
                 list($headers, $dump) = explode("\r\n\r\n", $this->get_part_content($structure->mime_id, null, true, 8192));
                 $structure->headers = rcube_mime::parse_headers($headers);
             }
@@ -330,7 +330,7 @@ class rcube_message
             $mimetype = $structure->mimetype;
 
         // show message headers
-        if ($recursive && is_array($structure->headers) && isset($structure->headers['subject'])) {
+        if ($recursive && is_array($structure->headers) && (isset($structure->headers['subject']) || isset($structure->headers['from']))) {
             $c = new stdClass;
             $c->type = 'headers';
             $c->headers = $structure->headers;
-- 
cgit v1.2.3


From aabd62828672c055205292c77f1463260f3b0c51 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Sun, 20 Jan 2013 11:12:24 +0100
Subject: Improve format=flowed text unfolding, add test for signature
 separator handling

---
 program/lib/Roundcube/rcube_mime.php | 18 +++++++++++-------
 tests/src/format-flowed-unfolded.txt |  3 +++
 tests/src/format-flowed.txt          |  3 +++
 3 files changed, 17 insertions(+), 7 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_mime.php b/program/lib/Roundcube/rcube_mime.php
index d5fb35bcd..fd91ca979 100644
--- a/program/lib/Roundcube/rcube_mime.php
+++ b/program/lib/Roundcube/rcube_mime.php
@@ -476,14 +476,18 @@ class rcube_mime
         $q_level = 0;
 
         foreach ($text as $idx => $line) {
-            if ($line[0] == '>' && preg_match('/^(>+\s*)/', $line, $regs)) {
-                $q = strlen(str_replace(' ', '', $regs[0]));
-                $line = substr($line, strlen($regs[0]));
-
+            if ($line[0] == '>') {
+                $len  = strlen($line);
+                $line = preg_replace('/^>+ {0,1}/', '', $line);
+                $q    = $len - strlen($line);
+
+                // The same paragraph (We join current line with the previous one) when:
+                // - the same level of quoting
+                // - previous line was flowed
+                // - previous line contains more than only one single space (and quote char(s))
                 if ($q == $q_level
-                    && strlen($line[$last]) > 1  // don't hit if line only consist of one single white space
-                    && isset($text[$last])
-                    && $text[$last][strlen($text[$last])-1] == ' '
+                    && isset($text[$last]) && $text[$last][strlen($text[$last])-1] == ' '
+                    && !preg_match('/^>+ {0,1}$/', $text[$last])
                 ) {
                     $text[$last] .= $line;
                     unset($text[$idx]);
diff --git a/tests/src/format-flowed-unfolded.txt b/tests/src/format-flowed-unfolded.txt
index 8245d59d4..b422f981b 100644
--- a/tests/src/format-flowed-unfolded.txt
+++ b/tests/src/format-flowed-unfolded.txt
@@ -12,3 +12,6 @@ On XX.YY.YYYY Y:YY, Somebody wrote:
 > xxxxxxxxxx. xxxx xxxxx xxxxx xxxx xx xx.xx. xxxxxx xxxxxxxxxxxx, xxxx xx
 > 
 > ... and this one as well.
+
+-- 
+Sig
diff --git a/tests/src/format-flowed.txt b/tests/src/format-flowed.txt
index 522f829c6..d3bd90eed 100644
--- a/tests/src/format-flowed.txt
+++ b/tests/src/format-flowed.txt
@@ -14,3 +14,6 @@ On XX.YY.YYYY Y:YY, Somebody wrote:
 > xxxxxxxxxx. xxxx xxxxx xxxxx xxxx xx xx.xx. xxxxxx xxxxxxxxxxxx, xxxx xx
 > 
 > ... and this one as well.
+
+-- 
+Sig
-- 
cgit v1.2.3


From 7ebed11b05fe9b3659d18ed797572e7ffccad5a3 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Sun, 20 Jan 2013 11:57:46 +0100
Subject: More improvements to format=flowed handling + fix in wordwrap() used
 internally by format_flowed()

---
 program/lib/Roundcube/rcube_mime.php | 17 ++++++++++-------
 tests/src/format-flowed-unfolded.txt |  2 ++
 tests/src/format-flowed.txt          |  2 ++
 3 files changed, 14 insertions(+), 7 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_mime.php b/program/lib/Roundcube/rcube_mime.php
index fd91ca979..e9d5cf148 100644
--- a/program/lib/Roundcube/rcube_mime.php
+++ b/program/lib/Roundcube/rcube_mime.php
@@ -477,9 +477,10 @@ class rcube_mime
 
         foreach ($text as $idx => $line) {
             if ($line[0] == '>') {
-                $len  = strlen($line);
-                $line = preg_replace('/^>+ {0,1}/', '', $line);
-                $q    = $len - strlen($line);
+                // remove quote chars, store level in $q
+                $line = preg_replace('/^>+/', '', $line, -1, $q);
+                // remove (optional) space-staffing
+                $line = preg_replace('/^ /', '', $line);
 
                 // The same paragraph (We join current line with the previous one) when:
                 // - the same level of quoting
@@ -540,10 +541,12 @@ class rcube_mime
 
         foreach ($text as $idx => $line) {
             if ($line != '-- ') {
-                if ($line[0] == '>' && preg_match('/^(>+ {0,1})+/', $line, $regs)) {
-                    $level  = substr_count($regs[0], '>');
+                if ($line[0] == '>') {
+                    // remove quote chars, store level in $level
+                    $line   = preg_replace('/^>+/', '', $line, -1, $level);
+                    // remove (optional) space-staffing
+                    $line   = preg_replace('/^ /', '', $line);
                     $prefix = str_repeat('>', $level) . ' ';
-                    $line   = rtrim(substr($line, strlen($regs[0])));
                     $line   = $prefix . self::wordwrap($line, $length - $level - 2, " \r\n$prefix", false, $charset);
                 }
                 else if ($line) {
@@ -583,7 +586,7 @@ class rcube_mime
         while (count($para)) {
             $line = array_shift($para);
             if ($line[0] == '>') {
-                $string .= $line.$break;
+                $string .= $line . (count($para) ? $break : '');
                 continue;
             }
 
diff --git a/tests/src/format-flowed-unfolded.txt b/tests/src/format-flowed-unfolded.txt
index b422f981b..14e526be4 100644
--- a/tests/src/format-flowed-unfolded.txt
+++ b/tests/src/format-flowed-unfolded.txt
@@ -13,5 +13,7 @@ On XX.YY.YYYY Y:YY, Somebody wrote:
 > 
 > ... and this one as well.
 
+> > text
+
 -- 
 Sig
diff --git a/tests/src/format-flowed.txt b/tests/src/format-flowed.txt
index d3bd90eed..a390ffd11 100644
--- a/tests/src/format-flowed.txt
+++ b/tests/src/format-flowed.txt
@@ -15,5 +15,7 @@ On XX.YY.YYYY Y:YY, Somebody wrote:
 > 
 > ... and this one as well.
 
+> > text
+
 -- 
 Sig
-- 
cgit v1.2.3


From 87a96809c7a067f8cc140884558898fd92c87032 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Mon, 21 Jan 2013 12:00:24 +0100
Subject: Rtrim() quoted lines on conversion to flowed format (according to
 RFC2646)

---
 program/lib/Roundcube/rcube_mime.php | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_mime.php b/program/lib/Roundcube/rcube_mime.php
index e9d5cf148..2f24a1bb3 100644
--- a/program/lib/Roundcube/rcube_mime.php
+++ b/program/lib/Roundcube/rcube_mime.php
@@ -544,8 +544,8 @@ class rcube_mime
                 if ($line[0] == '>') {
                     // remove quote chars, store level in $level
                     $line   = preg_replace('/^>+/', '', $line, -1, $level);
-                    // remove (optional) space-staffing
-                    $line   = preg_replace('/^ /', '', $line);
+                    // remove (optional) space-staffing and spaces before the line end
+                    $line   = preg_replace('/(^ | +$)/', '', $line);
                     $prefix = str_repeat('>', $level) . ' ';
                     $line   = $prefix . self::wordwrap($line, $length - $level - 2, " \r\n$prefix", false, $charset);
                 }
-- 
cgit v1.2.3


From 60753b05faa87b6ee6a7b0f22b85cb664478269f Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Tue, 22 Jan 2013 12:50:10 +0100
Subject: Support autofocus attribute on input elements

---
 program/lib/Roundcube/html.php | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/html.php b/program/lib/Roundcube/html.php
index 522a82305..a44f4d518 100644
--- a/program/lib/Roundcube/html.php
+++ b/program/lib/Roundcube/html.php
@@ -287,7 +287,7 @@ class html
             }
 
             // attributes with no value
-            if (in_array($key, array('checked', 'multiple', 'disabled', 'selected'))) {
+            if (in_array($key, array('checked', 'multiple', 'disabled', 'selected', 'autofocus'))) {
                 if ($value) {
                     $attrib_arr[] = $key . '="' . $key . '"';
                 }
@@ -350,6 +350,7 @@ class html_inputfield extends html
         'type','name','value','size','tabindex','autocapitalize',
         'autocomplete','checked','onchange','onclick','disabled','readonly',
         'spellcheck','results','maxlength','src','multiple','placeholder',
+        'autofocus',
     );
 
     /**
-- 
cgit v1.2.3


From 293a5798af154efb75d0f467639273452e35477e Mon Sep 17 00:00:00 2001
From: Thomas Bruederli <thomas@roundcube.net>
Date: Wed, 23 Jan 2013 17:58:09 +0100
Subject: Use the right variable for IPv6 check

---
 program/lib/Roundcube/rcube_utils.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_utils.php b/program/lib/Roundcube/rcube_utils.php
index 4b687111e..1ae782a25 100644
--- a/program/lib/Roundcube/rcube_utils.php
+++ b/program/lib/Roundcube/rcube_utils.php
@@ -156,7 +156,7 @@ class rcube_utils
     {
         // IPv6, but there's no build-in IPv6 support
         if (strpos($ip, ':') !== false && !defined('AF_INET6')) {
-            $parts = explode(':', $domain_part);
+            $parts = explode(':', $ip);
             $count = count($parts);
 
             if ($count > 8 || $count < 2) {
-- 
cgit v1.2.3


From e114a6040606afabf84f11651a040cef30bc55a2 Mon Sep 17 00:00:00 2001
From: Thomas Bruederli <thomas@roundcube.net>
Date: Wed, 23 Jan 2013 18:01:02 +0100
Subject: Use LDAP fallback hosts on connect + bind because with OpenLDAP 2.x
 ldap_connect() always succeeds but ldap_bind() will fail if host isn't
 reachable. Add option for LDAP bind timeout (sets LDAP_OPT_NETWORK_TIMEOUT on
 PHP > 5.3.0)

---
 config/main.inc.php.dist             |   1 +
 program/lib/Roundcube/rcube_ldap.php | 184 +++++++++++++++++++----------------
 2 files changed, 99 insertions(+), 86 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/config/main.inc.php.dist b/config/main.inc.php.dist
index e6cb9fd4c..b113b41a8 100644
--- a/config/main.inc.php.dist
+++ b/config/main.inc.php.dist
@@ -577,6 +577,7 @@ $rcmail_config['ldap_public']['Verisign'] = array(
   'port'          => 389,
   'use_tls'	      => false,
   'ldap_version'  => 3,       // using LDAPv3
+  'network_timeout' => 10,    // The timeout (in seconds) for connect + bind arrempts. This is only supported in PHP >= 5.3.0 with OpenLDAP 2.x
   'user_specific' => false,   // If true the base_dn, bind_dn and bind_pass default to the user's IMAP login.
   // %fu - The full username provided, assumes the username is an email
   //       address, uses the username_domain value if not an email address.
diff --git a/program/lib/Roundcube/rcube_ldap.php b/program/lib/Roundcube/rcube_ldap.php
index 700c6f60c..a2dd163e9 100644
--- a/program/lib/Roundcube/rcube_ldap.php
+++ b/program/lib/Roundcube/rcube_ldap.php
@@ -214,15 +214,16 @@ class rcube_ldap extends rcube_addressbook
         if (empty($this->prop['ldap_version']))
             $this->prop['ldap_version'] = 3;
 
-        foreach ($this->prop['hosts'] as $host)
-        {
+        // try to connect + bind for every host configured
+        // with OpenLDAP 2.x ldap_connect() always succeeds but ldap_bind will fail if host isn't reachable
+        // see http://www.php.net/manual/en/function.ldap-connect.php
+        foreach ($this->prop['hosts'] as $host) {
             $host     = rcube_utils::idn_to_ascii(rcube_utils::parse_host($host));
             $hostname = $host.($this->prop['port'] ? ':'.$this->prop['port'] : '');
 
             $this->_debug("C: Connect [$hostname] [{$this->prop['name']}]");
 
-            if ($lc = @ldap_connect($host, $this->prop['port']))
-            {
+            if ($lc = @ldap_connect($host, $this->prop['port'])) {
                 if ($this->prop['use_tls'] === true)
                     if (!ldap_start_tls($lc))
                         continue;
@@ -233,113 +234,124 @@ class rcube_ldap extends rcube_addressbook
                 $this->prop['host'] = $host;
                 $this->conn = $lc;
 
+                if (!empty($this->prop['network_timeout']))
+                  ldap_set_option($lc, LDAP_OPT_NETWORK_TIMEOUT, $this->prop['network_timeout']);
+
                 if (isset($this->prop['referrals']))
                     ldap_set_option($lc, LDAP_OPT_REFERRALS, $this->prop['referrals']);
-                break;
             }
-            $this->_debug("S: NOT OK");
-        }
-
-        // See if the directory is writeable.
-        if ($this->prop['writable']) {
-            $this->readonly = false;
-        }
-
-        if (!is_resource($this->conn)) {
-            rcube::raise_error(array('code' => 100, 'type' => 'ldap',
-                'file' => __FILE__, 'line' => __LINE__,
-                'message' => "Could not connect to any LDAP server, last tried $hostname"), true);
+            else {
+                $this->_debug("S: NOT OK");
+                continue;
+            }
 
-            return false;
-        }
+            // See if the directory is writeable.
+            if ($this->prop['writable']) {
+                $this->readonly = false;
+            }
 
-        $bind_pass = $this->prop['bind_pass'];
-        $bind_user = $this->prop['bind_user'];
-        $bind_dn   = $this->prop['bind_dn'];
+            $bind_pass = $this->prop['bind_pass'];
+            $bind_user = $this->prop['bind_user'];
+            $bind_dn   = $this->prop['bind_dn'];
 
-        $this->base_dn        = $this->prop['base_dn'];
-        $this->groups_base_dn = ($this->prop['groups']['base_dn']) ?
-        $this->prop['groups']['base_dn'] : $this->base_dn;
+            $this->base_dn        = $this->prop['base_dn'];
+            $this->groups_base_dn = ($this->prop['groups']['base_dn']) ?
+            $this->prop['groups']['base_dn'] : $this->base_dn;
 
-        // User specific access, generate the proper values to use.
-        if ($this->prop['user_specific']) {
-            // No password set, use the session password
-            if (empty($bind_pass)) {
-                $bind_pass = $rcube->get_user_password();
-            }
+            // User specific access, generate the proper values to use.
+            if ($this->prop['user_specific']) {
+                // No password set, use the session password
+                if (empty($bind_pass)) {
+                    $bind_pass = $rcube->get_user_password();
+                }
 
-            // Get the pieces needed for variable replacement.
-            if ($fu = $rcube->get_user_email())
-                list($u, $d) = explode('@', $fu);
-            else
-                $d = $this->mail_domain;
+                // Get the pieces needed for variable replacement.
+                if ($fu = $rcube->get_user_email())
+                    list($u, $d) = explode('@', $fu);
+                else
+                    $d = $this->mail_domain;
 
-            $dc = 'dc='.strtr($d, array('.' => ',dc=')); // hierarchal domain string
+                $dc = 'dc='.strtr($d, array('.' => ',dc=')); // hierarchal domain string
 
-            $replaces = array('%dn' => '', '%dc' => $dc, '%d' => $d, '%fu' => $fu, '%u' => $u);
+                $replaces = array('%dn' => '', '%dc' => $dc, '%d' => $d, '%fu' => $fu, '%u' => $u);
 
-            if ($this->prop['search_base_dn'] && $this->prop['search_filter']) {
-                if (!empty($this->prop['search_bind_dn']) && !empty($this->prop['search_bind_pw'])) {
-                    $this->bind($this->prop['search_bind_dn'], $this->prop['search_bind_pw']);
-                }
+                if ($this->prop['search_base_dn'] && $this->prop['search_filter']) {
+                    if (!empty($this->prop['search_bind_dn']) && !empty($this->prop['search_bind_pw'])) {
+                        $this->bind($this->prop['search_bind_dn'], $this->prop['search_bind_pw']);
+                    }
 
-                // Search for the dn to use to authenticate
-                $this->prop['search_base_dn'] = strtr($this->prop['search_base_dn'], $replaces);
-                $this->prop['search_filter'] = strtr($this->prop['search_filter'], $replaces);
+                    // Search for the dn to use to authenticate
+                    $this->prop['search_base_dn'] = strtr($this->prop['search_base_dn'], $replaces);
+                    $this->prop['search_filter'] = strtr($this->prop['search_filter'], $replaces);
 
-                $this->_debug("S: searching with base {$this->prop['search_base_dn']} for {$this->prop['search_filter']}");
+                    $this->_debug("S: searching with base {$this->prop['search_base_dn']} for {$this->prop['search_filter']}");
 
-                $res = @ldap_search($this->conn, $this->prop['search_base_dn'], $this->prop['search_filter'], array('uid'));
-                if ($res) {
-                    if (($entry = ldap_first_entry($this->conn, $res))
-                        && ($bind_dn = ldap_get_dn($this->conn, $entry))
-                    ) {
-                        $this->_debug("S: search returned dn: $bind_dn");
-                        $dn = ldap_explode_dn($bind_dn, 1);
-                        $replaces['%dn'] = $dn[0];
+                    $res = @ldap_search($this->conn, $this->prop['search_base_dn'], $this->prop['search_filter'], array('uid'));
+                    if ($res) {
+                        if (($entry = ldap_first_entry($this->conn, $res))
+                            && ($bind_dn = ldap_get_dn($this->conn, $entry))
+                        ) {
+                            $this->_debug("S: search returned dn: $bind_dn");
+                            $dn = ldap_explode_dn($bind_dn, 1);
+                            $replaces['%dn'] = $dn[0];
+                        }
                     }
-                }
-                else {
-                    $this->_debug("S: ".ldap_error($this->conn));
-                }
-
-                // DN not found
-                if (empty($replaces['%dn'])) {
-                    if (!empty($this->prop['search_dn_default']))
-                        $replaces['%dn'] = $this->prop['search_dn_default'];
                     else {
-                        rcube::raise_error(array(
-                            'code' => 100, 'type' => 'ldap',
-                            'file' => __FILE__, 'line' => __LINE__,
-                            'message' => "DN not found using LDAP search."), true);
-                        return false;
+                        $this->_debug("S: ".ldap_error($this->conn));
+                    }
+
+                    // DN not found
+                    if (empty($replaces['%dn'])) {
+                        if (!empty($this->prop['search_dn_default']))
+                            $replaces['%dn'] = $this->prop['search_dn_default'];
+                        else {
+                            rcube::raise_error(array(
+                                'code' => 100, 'type' => 'ldap',
+                                'file' => __FILE__, 'line' => __LINE__,
+                                'message' => "DN not found using LDAP search."), true);
+                            return false;
+                        }
                     }
                 }
-            }
 
-            // Replace the bind_dn and base_dn variables.
-            $bind_dn              = strtr($bind_dn, $replaces);
-            $this->base_dn        = strtr($this->base_dn, $replaces);
-            $this->groups_base_dn = strtr($this->groups_base_dn, $replaces);
+                // Replace the bind_dn and base_dn variables.
+                $bind_dn              = strtr($bind_dn, $replaces);
+                $this->base_dn        = strtr($this->base_dn, $replaces);
+                $this->groups_base_dn = strtr($this->groups_base_dn, $replaces);
 
-            if (empty($bind_user)) {
-                $bind_user = $u;
+                if (empty($bind_user)) {
+                    $bind_user = $u;
+                }
             }
-        }
 
-        if (empty($bind_pass)) {
-            $this->ready = true;
-        }
-        else {
-            if (!empty($bind_dn)) {
-                $this->ready = $this->bind($bind_dn, $bind_pass);
-            }
-            else if (!empty($this->prop['auth_cid'])) {
-                $this->ready = $this->sasl_bind($this->prop['auth_cid'], $bind_pass, $bind_user);
+            if (empty($bind_pass)) {
+                $this->ready = true;
             }
             else {
-                $this->ready = $this->sasl_bind($bind_user, $bind_pass);
+                if (!empty($bind_dn)) {
+                    $this->ready = $this->bind($bind_dn, $bind_pass);
+                }
+                else if (!empty($this->prop['auth_cid'])) {
+                    $this->ready = $this->sasl_bind($this->prop['auth_cid'], $bind_pass, $bind_user);
+                }
+                else {
+                    $this->ready = $this->sasl_bind($bind_user, $bind_pass);
+                }
             }
+
+            // connection established, we're done here
+            if ($this->ready) {
+                break;
+            }
+
+        }  // end foreach hosts
+
+        if (!is_resource($this->conn)) {
+            rcube::raise_error(array('code' => 100, 'type' => 'ldap',
+                'file' => __FILE__, 'line' => __LINE__,
+                'message' => "Could not connect to any LDAP server, last tried $hostname"), true);
+
+            return false;
         }
 
         return $this->ready;
-- 
cgit v1.2.3


From 18372a236d459f2a098c8604a0f912f9aa728f98 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Fri, 25 Jan 2013 12:36:50 +0100
Subject: Send LOGOUT only when closing connection in logged state (#1487784)

---
 program/lib/Roundcube/rcube_imap_generic.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_imap_generic.php b/program/lib/Roundcube/rcube_imap_generic.php
index 8d84bf736..b9a796c33 100644
--- a/program/lib/Roundcube/rcube_imap_generic.php
+++ b/program/lib/Roundcube/rcube_imap_generic.php
@@ -906,7 +906,7 @@ class rcube_imap_generic
      */
     function closeConnection()
     {
-        if ($this->putLine($this->nextTag() . ' LOGOUT')) {
+        if ($this->logged && $this->putLine($this->nextTag() . ' LOGOUT')) {
             $this->readReply();
         }
 
-- 
cgit v1.2.3


From 13dc9f2c862668554d87dcbf95f2f7bbaf221bf3 Mon Sep 17 00:00:00 2001
From: Thomas Bruederli <thomas@roundcube.net>
Date: Fri, 25 Jan 2013 14:15:12 +0100
Subject: Move rcmail_contact_key() to rcube_addressbook::compose_contact_key()

---
 program/lib/Roundcube/rcube_addressbook.php | 16 ++++++++++++++++
 program/steps/addressbook/delete.inc        |  2 +-
 program/steps/addressbook/export.inc        |  4 ++--
 program/steps/addressbook/func.inc          | 18 ------------------
 program/steps/addressbook/list.inc          |  2 +-
 program/steps/addressbook/search.inc        |  2 +-
 6 files changed, 21 insertions(+), 23 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_addressbook.php b/program/lib/Roundcube/rcube_addressbook.php
index 421062772..cbc3c6773 100644
--- a/program/lib/Roundcube/rcube_addressbook.php
+++ b/program/lib/Roundcube/rcube_addressbook.php
@@ -523,6 +523,22 @@ abstract class rcube_addressbook
         return $fn;
     }
 
+    /**
+     * Create a unique key for sorting contacts
+     */
+    public static function compose_contact_key($contact, $sort_col)
+    {
+        $key = $contact[$sort_col] . ':' . $row['sourceid'];
+
+        // add email to a key to not skip contacts with the same name (#1488375)
+        if (!empty($contact['email'])) {
+             $key .= ':' . implode(':', (array)$contact['email']);
+         }
+
+         return $key;
+    }
+
+
     /**
      * Compare search value with contact data
      *
diff --git a/program/steps/addressbook/delete.inc b/program/steps/addressbook/delete.inc
index 81b8a0970..56118583c 100644
--- a/program/steps/addressbook/delete.inc
+++ b/program/steps/addressbook/delete.inc
@@ -93,7 +93,7 @@ if (($search_request = $_REQUEST['_search']) && isset($_SESSION['search'][$searc
 
         while ($row = $result->next()) {
             $row['sourceid'] = $s;
-            $key = rcmail_contact_key($row, $sort_col);
+            $key = rcube_addressbook::compose_contact_key($row, $sort_col);
             $records[$key] = $row;
         }
         unset($result);
diff --git a/program/steps/addressbook/export.inc b/program/steps/addressbook/export.inc
index fc9f23fa1..15bf8b0d4 100644
--- a/program/steps/addressbook/export.inc
+++ b/program/steps/addressbook/export.inc
@@ -61,7 +61,7 @@ if (!empty($_REQUEST['_search']) && isset($_SESSION['search'][$_REQUEST['_search
             }
 
             $record['sourceid'] = $s;
-            $key = rcmail_contact_key($record, $sort_col);
+            $key = rcube_addressbook::compose_contact_key($record, $sort_col);
             $records[$key] = $record;
         }
 
@@ -109,7 +109,7 @@ else if (!empty($_REQUEST['_cid'])) {
             }
 
             $record['sourceid'] = $s;
-            $key = rcmail_contact_key($record, $sort_col);
+            $key = rcube_addressbook::compose_contact_key($record, $sort_col);
             $records[$key] = $record;
         }
     }
diff --git a/program/steps/addressbook/func.inc b/program/steps/addressbook/func.inc
index 2f47483de..7fb862d5e 100644
--- a/program/steps/addressbook/func.inc
+++ b/program/steps/addressbook/func.inc
@@ -733,24 +733,6 @@ function rcmail_format_date_col($val)
 }
 
 
-function rcmail_contact_key($row, $sort_col)
-{
-    $key = $row[$sort_col] . ':' . $row['sourceid'];
-
-    // add email to a key to not skip contacts with the same name (#1488375)
-    if (!empty($row['email'])) {
-         if (is_array($row['email'])) {
-             $key .= ':' . implode(':', $row['email']);
-         }
-         else {
-             $key .= ':' . $row['email'];
-         }
-     }
-
-     return $key;
-}
-
-
 /**
  * Returns contact ID(s) and source(s) from GET/POST data
  *
diff --git a/program/steps/addressbook/list.inc b/program/steps/addressbook/list.inc
index 06a1e10a3..1bb28658b 100644
--- a/program/steps/addressbook/list.inc
+++ b/program/steps/addressbook/list.inc
@@ -49,7 +49,7 @@ if (!empty($_REQUEST['_search']) && isset($_SESSION['search'][$_REQUEST['_search
 
         while ($row = $result->next()) {
             $row['sourceid'] = $s;
-            $key = rcmail_contact_key($row, $sort_col);
+            $key = rcube_addressbook::compose_contact_key($row, $sort_col);
             $records[$key] = $row;
         }
         unset($result);
diff --git a/program/steps/addressbook/search.inc b/program/steps/addressbook/search.inc
index bbd9b9a76..d153c255a 100644
--- a/program/steps/addressbook/search.inc
+++ b/program/steps/addressbook/search.inc
@@ -184,7 +184,7 @@ function rcmail_contact_search()
 
         while ($row = $result->next()) {
             $row['sourceid'] = $s['id'];
-            $key = rcmail_contact_key($row, $sort_col);
+            $key = rcube_addressbook::compose_contact_key($row, $sort_col);
             $records[$key] = $row;
         }
 
-- 
cgit v1.2.3


From bb6f4b2b5d0676ef0ed90f8050ad28e46f2dce35 Mon Sep 17 00:00:00 2001
From: Thomas Bruederli <thomas@roundcube.net>
Date: Fri, 25 Jan 2013 23:46:06 +0100
Subject: Refactored blockquote quotion routine in html2text conversion: it now
 correctly converts multiple and/or nested blockquotes

---
 program/lib/Roundcube/rcube_html2text.php | 87 ++++++++++++++++---------------
 tests/Framework/Html2text.php             | 19 +++++++
 2 files changed, 64 insertions(+), 42 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_html2text.php b/program/lib/Roundcube/rcube_html2text.php
index 0b172ebfa..3d32fe766 100644
--- a/program/lib/Roundcube/rcube_html2text.php
+++ b/program/lib/Roundcube/rcube_html2text.php
@@ -571,54 +571,57 @@ class rcube_html2text
      */
     protected function _convert_blockquotes(&$text)
     {
-        if (preg_match_all('/<\/*blockquote[^>]*>/i', $text, $matches, PREG_OFFSET_CAPTURE)) {
-            $level = 0;
-            $diff = 0;
-            foreach ($matches[0] as $m) {
-                if ($m[0][0] == '<' && $m[0][1] == '/') {
+        $level = 0;
+        $offset = 0;
+        while (($start = strpos($text, '<blockquote', $offset)) !== false) {
+            $offset = $start + 12;
+            do {
+                $end = strpos($text, '</blockquote>', $offset);
+                $next = strpos($text, '<blockquote', $offset);
+
+                // nested <blockquote>, skip
+                if ($next !== false && $next < $end) {
+                    $offset = $next + 12;
+                    $level++;
+                }
+                // nested </blockquote> tag
+                if ($end !== false && $level > 0) {
+                    $offset = $end + 12;
                     $level--;
-                    if ($level < 0) {
-                        $level = 0; // malformed HTML: go to next blockquote
-                    }
-                    else if ($level > 0) {
-                        // skip inner blockquote
-                    }
-                    else {
-                        $end  = $m[1];
-                        $len  = $end - $taglen - $start;
-                        // Get blockquote content
-                        $body = substr($text, $start + $taglen - $diff, $len);
-
-                        // Set text width
-                        $p_width = $this->width;
-                        if ($this->width > 0) $this->width -= 2;
-                        // Convert blockquote content
-                        $body = trim($body);
-                        $this->_converter($body);
-                        // Add citation markers and create PRE block
-                        $body = preg_replace('/((^|\n)>*)/', '\\1> ', trim($body));
-                        $body = '<pre>' . htmlspecialchars($body) . '</pre>';
-                        // Re-set text width
-                        $this->width = $p_width;
-                        // Replace content
-                        $text = substr($text, 0, $start - $diff)
-                            . $body . substr($text, $end + strlen($m[0]) - $diff);
-
-                        $diff = $len + $taglen + strlen($m[0]) - strlen($body);
-                        unset($body);
-                    }
                 }
-                else {
-                    if ($level == 0) {
-                        $start = $m[1];
-                        $taglen = strlen($m[0]);
-                    }
-                    $level ++;
+                // found matching end tag
+                else if ($end !== false && $level == 0) {
+                    $taglen = strpos($text, '>', $start) - $start;
+                    $startpos = $start + $taglen + 1;
+
+                    // get blockquote content
+                    $body = trim(substr($text, $startpos, $end - $startpos));
+
+                    // replace content with inner blockquotes
+                    $this->_converter($body);
+
+                    // Add citation markers and create <pre> block
+                    $body = preg_replace_callback('/((?:^|\n)>*)([^\n]*)/', array($this, 'blockquote_citation_ballback'), trim($body));
+                    $body = '<pre>' . htmlspecialchars($body) . '</pre>';
+
+                    $text = substr($text, 0, $start) . $body . "\n" . substr($text, $end + 13);
+                    $offset = 0;
+                    break;
                 }
-            }
+            } while ($end || $next);
         }
     }
 
+    /**
+     * Callback function to correctly add citation markers for blockquote contents
+     */
+    public function blockquote_citation_ballback($m)
+    {
+        $line = ltrim($m[2]);
+        $space = $line[0] == '>' ? '' : ' ';
+        return $m[1] . '>' . $space . $line;
+    }
+
     /**
      * Callback function for preg_replace_callback use.
      *
diff --git a/tests/Framework/Html2text.php b/tests/Framework/Html2text.php
index 1d8963878..3e0df48d9 100644
--- a/tests/Framework/Html2text.php
+++ b/tests/Framework/Html2text.php
@@ -56,4 +56,23 @@ class rc_html2text extends PHPUnit_Framework_TestCase
 
         $this->assertEquals($out, $res, $title);
     }
+
+    /**
+     *
+     */
+    function test_multiple_blockquotes()
+    {
+        $html = <<<EOF
+<br>Begin<br><blockquote>OUTER BEGIN<blockquote>INNER 1<br></blockquote><div><br></div><div>Par 1</div>
+<blockquote>INNER 2</blockquote><div><br></div><div>Par 2</div>
+<div><br></div><div>Par 3</div><div><br></div>
+<blockquote>INNER 3</blockquote>OUTER END</blockquote>
+EOF;
+        $ht = new rcube_html2text($html, false, false);
+        $res = $ht->get_text();
+
+        $this->assertContains('>> INNER 1', $res, 'Quote inner');
+        $this->assertContains('>> INNER 3', $res, 'Quote inner');
+        $this->assertContains('> OUTER END', $res, 'Quote outer');
+    }
 }
-- 
cgit v1.2.3


From 737b629c6f947b956cacf188c2f6d6a410c36f3a Mon Sep 17 00:00:00 2001
From: Thomas Bruederli <thomas@roundcube.net>
Date: Sat, 26 Jan 2013 17:13:52 +0100
Subject: Bring back lost text braking width adjustment when quoting blockquote
 parts

---
 program/lib/Roundcube/rcube_html2text.php | 7 +++++++
 1 file changed, 7 insertions(+)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_html2text.php b/program/lib/Roundcube/rcube_html2text.php
index 3d32fe766..9b248a3a8 100644
--- a/program/lib/Roundcube/rcube_html2text.php
+++ b/program/lib/Roundcube/rcube_html2text.php
@@ -597,9 +597,16 @@ class rcube_html2text
                     // get blockquote content
                     $body = trim(substr($text, $startpos, $end - $startpos));
 
+                    // adjust text wrapping width
+                    $p_width = $this->width;
+                    if ($this->width > 0) $this->width -= 2;
+
                     // replace content with inner blockquotes
                     $this->_converter($body);
 
+                    // resore text width
+                    $this->width = $p_width;
+
                     // Add citation markers and create <pre> block
                     $body = preg_replace_callback('/((?:^|\n)>*)([^\n]*)/', array($this, 'blockquote_citation_ballback'), trim($body));
                     $body = '<pre>' . htmlspecialchars($body) . '</pre>';
-- 
cgit v1.2.3


From 41db2bf47db6df8b6065986b7488f3fc538ebc14 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Thu, 31 Jan 2013 13:18:38 +0100
Subject: Slightly improve database driver chack

---
 program/lib/Roundcube/rcube_db.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_db.php b/program/lib/Roundcube/rcube_db.php
index 086a38ab4..a3475a2fd 100644
--- a/program/lib/Roundcube/rcube_db.php
+++ b/program/lib/Roundcube/rcube_db.php
@@ -70,7 +70,7 @@ class rcube_db
         $driver = isset($driver_map[$driver]) ? $driver_map[$driver] : $driver;
         $class  = "rcube_db_$driver";
 
-        if (!class_exists($class)) {
+        if (!$driver || !class_exists($class)) {
             rcube::raise_error(array('code' => 600, 'type' => 'db',
                 'line' => __LINE__, 'file' => __FILE__,
                 'message' => "Configuration error. Unsupported database driver: $driver"),
-- 
cgit v1.2.3


From 1cf15ef4a5a7e09247b9dfa7e9931ddbe25660a4 Mon Sep 17 00:00:00 2001
From: Thomas Bruederli <thomas@roundcube.net>
Date: Fri, 1 Feb 2013 15:18:12 +0100
Subject: Make rcube_result_set implement the PHP iterator interface

---
 program/lib/Roundcube/rcube_result_set.php | 47 ++++++++++++++++++++++--------
 1 file changed, 35 insertions(+), 12 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_result_set.php b/program/lib/Roundcube/rcube_result_set.php
index 1391e5e4b..a4b070e28 100644
--- a/program/lib/Roundcube/rcube_result_set.php
+++ b/program/lib/Roundcube/rcube_result_set.php
@@ -3,7 +3,7 @@
 /*
  +-----------------------------------------------------------------------+
  | This file is part of the Roundcube Webmail client                     |
- | Copyright (C) 2006-2011, The Roundcube Dev Team                       |
+ | Copyright (C) 2006-2013, The Roundcube Dev Team                       |
  |                                                                       |
  | Licensed under the GNU General Public License version 3 or            |
  | any later version with exceptions for skins & plugins.                |
@@ -17,20 +17,22 @@
 */
 
 /**
- * Roundcube result set class.
+ * Roundcube result set class
+ *
  * Representing an address directory result set.
+ * Implenets Iterator and thus be used in foreach() loops.
  *
  * @package    Framework
  * @subpackage Addressbook
  */
-class rcube_result_set
+class rcube_result_set implements Iterator
 {
-    var $count = 0;
-    var $first = 0;
-    var $current = 0;
-    var $searchonly = false;
-    var $records = array();
+    public $count = 0;
+    public $first = 0;
+    public $searchonly = false;
+    public $records = array();
 
+    private $current = 0;
 
     function __construct($c=0, $f=0)
     {
@@ -51,18 +53,39 @@ class rcube_result_set
     function first()
     {
         $this->current = 0;
-        return $this->records[$this->current++];
+        return $this->records[$this->current];
+    }
+
+    function seek($i)
+    {
+        $this->current = $i;
+    }
+
+    /***  PHP 5 Iterator interface  ***/
+
+    function rewind()
+    {
+        $this->current = 0;
+    }
+
+    function current()
+    {
+        return $this->records[$this->current];
+    }
+
+    function key()
+    {
+        return $this->current;
     }
 
-    // alias for iterate()
     function next()
     {
         return $this->iterate();
     }
 
-    function seek($i)
+    function valid()
     {
-        $this->current = $i;
+        return isset($this->records[$this->current]);
     }
 
 }
-- 
cgit v1.2.3


From a39fd4db67cbebc9aecb906818f578608c9180fc Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Fri, 1 Feb 2013 15:19:49 +0100
Subject: Set default error code (500) if not specified in raise_error()

---
 program/lib/Roundcube/rcube.php | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube.php b/program/lib/Roundcube/rcube.php
index a914ae65a..3ae511e1e 100644
--- a/program/lib/Roundcube/rcube.php
+++ b/program/lib/Roundcube/rcube.php
@@ -1073,14 +1073,17 @@ class rcube
     {
         // handle PHP exceptions
         if (is_object($arg) && is_a($arg, 'Exception')) {
-            $err = array(
+            $arg = array(
                 'type' => 'php',
                 'code' => $arg->getCode(),
                 'line' => $arg->getLine(),
                 'file' => $arg->getFile(),
                 'message' => $arg->getMessage(),
             );
-            $arg = $err;
+        }
+
+        if (empty($arg['code'])) {
+            $arg['code'] = 500;
         }
 
         // installer
-- 
cgit v1.2.3


From 1f910cb50dcb12e84d92db4d61dcd8dbb0f0c5b6 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Fri, 1 Feb 2013 20:04:00 +0100
Subject: Fix handling link href attribute value with (valid) newline
 characters (#1488940)

---
 program/lib/Roundcube/rcube_washtml.php |  3 ++-
 tests/Framework/Washtml.php             | 14 ++++++++++++++
 2 files changed, 16 insertions(+), 1 deletion(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_washtml.php b/program/lib/Roundcube/rcube_washtml.php
index 715c46047..2a261419f 100644
--- a/program/lib/Roundcube/rcube_washtml.php
+++ b/program/lib/Roundcube/rcube_washtml.php
@@ -240,7 +240,8 @@ class rcube_washtml
             $value = $node->getAttribute($key);
 
             if (isset($this->_html_attribs[$key]) ||
-                ($key == 'href' && !preg_match('!^(javascript|vbscript|data:text)!i', $value)
+                ($key == 'href' && ($value = trim($value))
+                    && !preg_match('!^(javascript|vbscript|data:text)!i', $value)
                     && preg_match('!^([a-z][a-z0-9.+-]+:|//|#).+!i', $value))
             ) {
                 $t .= ' ' . $key . '="' . htmlspecialchars($value, ENT_QUOTES) . '"';
diff --git a/tests/Framework/Washtml.php b/tests/Framework/Washtml.php
index 088ac4a8c..6f4aa9783 100644
--- a/tests/Framework/Washtml.php
+++ b/tests/Framework/Washtml.php
@@ -25,4 +25,18 @@ class Framework_Washtml extends PHPUnit_Framework_TestCase
         $this->assertNotRegExp('/vbscript:/', $washed, "Remove vbscript: links");
     }
 
+    /**
+     * Test fixing of invalid href (#1488940)
+     */
+    function test_href()
+    {
+        $html = "<p><a href=\"\nhttp://test.com\n\">Firefox</a>";
+
+        $washer = new rcube_washtml;
+
+        $washed = $washer->wash($html);
+
+        $this->assertRegExp('|href="http://test.com">|', $washed, "Link href with newlines (#1488940)");
+    }
+
 }
-- 
cgit v1.2.3


From 19611462279252582e8c5b384c13f89949e229e5 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Sun, 17 Feb 2013 10:25:46 +0100
Subject: Make cleanup() method public

---
 program/lib/Roundcube/rcube_vcard.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_vcard.php b/program/lib/Roundcube/rcube_vcard.php
index c2b30af59..de28767f8 100644
--- a/program/lib/Roundcube/rcube_vcard.php
+++ b/program/lib/Roundcube/rcube_vcard.php
@@ -513,7 +513,7 @@ class rcube_vcard
      *
      * @return string Cleaned vcard block
      */
-    private static function cleanup($vcard)
+    public static function cleanup($vcard)
     {
         // Convert special types (like Skype) to normal type='skype' classes with this simple regex ;)
         $vcard = preg_replace(
-- 
cgit v1.2.3


From bc2c02feec27126488005624b26c6a14df7956b7 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Sun, 17 Feb 2013 10:52:45 +0100
Subject: When connection to read-only db fails try to connect to write-master,
 but only if it is defined

---
 program/lib/Roundcube/rcube_db.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_db.php b/program/lib/Roundcube/rcube_db.php
index a3475a2fd..88cd22b0e 100644
--- a/program/lib/Roundcube/rcube_db.php
+++ b/program/lib/Roundcube/rcube_db.php
@@ -222,7 +222,7 @@ class rcube_db
         $this->db_connected = is_object($this->dbh);
 
         // use write-master when read-only fails
-        if (!$this->db_connected && $mode == 'r') {
+        if (!$this->db_connected && $mode == 'r' && $this->is_replicated()) {
             $mode = 'w';
             $this->dbh          = $this->dsn_connect($this->db_dsnw_array);
             $this->db_connected = is_object($this->dbh);
-- 
cgit v1.2.3


From e4394c95e0a39607f4fdbd427b249b1e611ca0ff Mon Sep 17 00:00:00 2001
From: Thomas Bruederli <thomas@roundcube.net>
Date: Mon, 18 Feb 2013 08:31:09 +0100
Subject: Make autoloading of the framework classes work from any location

---
 program/lib/Roundcube/bootstrap.php | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/bootstrap.php b/program/lib/Roundcube/bootstrap.php
index 8cea48122..ef221e34a 100644
--- a/program/lib/Roundcube/bootstrap.php
+++ b/program/lib/Roundcube/bootstrap.php
@@ -470,17 +470,17 @@ function rcube_autoload($classname)
             '/Mail_(.+)/',
             '/Net_(.+)/',
             '/Auth_(.+)/',
+            '/^utf8$/',
             '/^html_.+/',
             '/^rcube(.*)/',
-            '/^utf8$/',
         ),
         array(
             'Mail/\\1',
             'Net/\\1',
             'Auth/\\1',
-            'Roundcube/html',
-            'Roundcube/rcube\\1',
             'utf8.class',
+            RCUBE_LIB_DIR . '/html',
+            RCUBE_LIB_DIR . '/rcube\\1',
         ),
         $classname
     );
-- 
cgit v1.2.3


From 2187b2b7d845f6c5b50e380447c034c830454e75 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Tue, 19 Feb 2013 09:08:33 +0100
Subject: Revert "Make autoloading of the framework classes work from any
 location". Allow loading rcube_* classes from other locations (for e.g.
 managesieve plugin).

This reverts commit e4394c95e0a39607f4fdbd427b249b1e611ca0ff.
---
 program/lib/Roundcube/bootstrap.php | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/bootstrap.php b/program/lib/Roundcube/bootstrap.php
index ef221e34a..8cea48122 100644
--- a/program/lib/Roundcube/bootstrap.php
+++ b/program/lib/Roundcube/bootstrap.php
@@ -470,17 +470,17 @@ function rcube_autoload($classname)
             '/Mail_(.+)/',
             '/Net_(.+)/',
             '/Auth_(.+)/',
-            '/^utf8$/',
             '/^html_.+/',
             '/^rcube(.*)/',
+            '/^utf8$/',
         ),
         array(
             'Mail/\\1',
             'Net/\\1',
             'Auth/\\1',
+            'Roundcube/html',
+            'Roundcube/rcube\\1',
             'utf8.class',
-            RCUBE_LIB_DIR . '/html',
-            RCUBE_LIB_DIR . '/rcube\\1',
         ),
         $classname
     );
-- 
cgit v1.2.3


From 726297e5f8d84cbb434f9c4185f3cd1aaebe8e6e Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Tue, 19 Feb 2013 16:56:02 +0100
Subject: Add workaround for invalid message charset detection by IMAP servers
 (#1488968)

---
 CHANGELOG                            |  3 ++-
 program/lib/Roundcube/rcube_imap.php | 30 +++++++++++++++++++++++-------
 program/steps/mail/func.inc          |  2 +-
 3 files changed, 26 insertions(+), 9 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/CHANGELOG b/CHANGELOG
index 1ee72e953..cc83c6ab6 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
 CHANGELOG Roundcube Webmail
-===========================
+========================a===
 
+- Add workaround for invalid message charset detection by IMAP servers (#1488968)
 - Fix NUL characters in content-type of ms-tnef attachment (#1488964)
 - Fix regression in handling LDAP contact identifiers (#1488959)
 - Updated translations from Transifex
diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php
index 74c1f5324..18c6b12af 100644
--- a/program/lib/Roundcube/rcube_imap.php
+++ b/program/lib/Roundcube/rcube_imap.php
@@ -1634,9 +1634,15 @@ class rcube_imap extends rcube_storage
         // Example of structure for malformed MIME message:
         // ("text" "plain" NIL NIL NIL "7bit" 2154 70 NIL NIL NIL)
         if ($headers->ctype && !is_array($structure[0]) && $headers->ctype != 'text/plain'
-            && strtolower($structure[0].'/'.$structure[1]) == 'text/plain') {
+            && strtolower($structure[0].'/'.$structure[1]) == 'text/plain'
+        ) {
+            // A special known case "Content-type: text" (#1488968)
+            if ($headers->ctype == 'text') {
+                $structure[1]   = 'plain';
+                $headers->ctype = 'text/plain';
+            }
             // we can handle single-part messages, by simple fix in structure (#1486898)
-            if (preg_match('/^(text|application)\/(.*)/', $headers->ctype, $m)) {
+            else if (preg_match('/^(text|application)\/(.*)/', $headers->ctype, $m)) {
                 $structure[0] = $m[1];
                 $structure[1] = $m[2];
             }
@@ -1660,11 +1666,21 @@ class rcube_imap extends rcube_storage
             $struct = $this->structure_part($structure, 0, '', $headers);
         }
 
-        // don't trust given content-type
-        if (empty($struct->parts) && !empty($headers->ctype)) {
-            $struct->mime_id = '1';
-            $struct->mimetype = strtolower($headers->ctype);
-            list($struct->ctype_primary, $struct->ctype_secondary) = explode('/', $struct->mimetype);
+        // some workarounds on simple messages...
+        if (empty($struct->parts)) {
+            // ...don't trust given content-type
+            if (!empty($headers->ctype)) {
+                $struct->mime_id  = '1';
+                $struct->mimetype = strtolower($headers->ctype);
+                list($struct->ctype_primary, $struct->ctype_secondary) = explode('/', $struct->mimetype);
+            }
+
+            // ...and charset (there's a case described in #1488968 where invalid content-type
+            // results in invalid charset in BODYSTRUCTURE)
+            if (!empty($headers->charset) && $headers->charset != $struct->ctype_parameters['charset']) {
+                $struct->charset                     = $headers->charset;
+                $struct->ctype_parameters['charset'] = $headers->charset;
+            }
         }
 
         $headers->structure = $struct;
diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc
index 6b8879dcf..36ac1aa2b 100644
--- a/program/steps/mail/func.inc
+++ b/program/steps/mail/func.inc
@@ -1854,7 +1854,7 @@ function rcmail_attachment_name($attachment, $display = false)
             $filename = rcube_label('htmlmessage');
         }
         else {
-            $ext      = rcube_mime::get_mime_extensions($attachment->mimetype);
+            $ext      = (array) rcube_mime::get_mime_extensions($attachment->mimetype);
             $ext      = array_shift($ext);
             $filename = rcube_label('messagepart') . ' ' . $attachment->mime_id;
             if ($ext) {
-- 
cgit v1.2.3


From 36391cf3424b178067bed5153df915b3b3c872ac Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Mon, 25 Feb 2013 11:10:22 +0100
Subject: Fix plain text spellchecker icorrect highlighting in non-ASCII text
 (#1488973)

---
 CHANGELOG                                    | 3 ++-
 program/lib/Roundcube/rcube_spellchecker.php | 2 +-
 2 files changed, 3 insertions(+), 2 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/CHANGELOG b/CHANGELOG
index cc83c6ab6..7cb043cba 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
 CHANGELOG Roundcube Webmail
-========================a===
+===========================
 
+- Fix plain text spellchecker icorrect highlighting in non-ASCII text (#1488973)
 - Add workaround for invalid message charset detection by IMAP servers (#1488968)
 - Fix NUL characters in content-type of ms-tnef attachment (#1488964)
 - Fix regression in handling LDAP contact identifiers (#1488959)
diff --git a/program/lib/Roundcube/rcube_spellchecker.php b/program/lib/Roundcube/rcube_spellchecker.php
index 3d4d3a3d6..816bcad2f 100644
--- a/program/lib/Roundcube/rcube_spellchecker.php
+++ b/program/lib/Roundcube/rcube_spellchecker.php
@@ -31,7 +31,7 @@ class rcube_spellchecker
     private $lang;
     private $rc;
     private $error;
-    private $separator = '/[\s\r\n\t\(\)\/\[\]{}<>\\"]+|[:;?!,\.]([^\w]|$)/';
+    private $separator = '/[\s\r\n\t\(\)\/\[\]{}<>\\"]+|[:;?!,\.](?=\W|$)/';
     private $options = array();
     private $dict;
     private $have_dict;
-- 
cgit v1.2.3


From a6fd1578c3535b423e663ec910056413610a800b Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Fri, 1 Mar 2013 09:07:55 +0100
Subject: Better @package/@subpackage assignment

---
 program/lib/Roundcube/html.php                 | 27 ++++++++++++++++----------
 program/lib/Roundcube/rcube_base_replacer.php  |  2 +-
 program/lib/Roundcube/rcube_browser.php        |  2 +-
 program/lib/Roundcube/rcube_content_filter.php |  2 +-
 4 files changed, 20 insertions(+), 13 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/html.php b/program/lib/Roundcube/html.php
index a44f4d518..592720308 100644
--- a/program/lib/Roundcube/html.php
+++ b/program/lib/Roundcube/html.php
@@ -21,7 +21,7 @@
  * Class for HTML code creation
  *
  * @package    Framework
- * @subpackage HTML
+ * @subpackage View
  */
 class html
 {
@@ -340,7 +340,8 @@ class html
 /**
  * Class to create an HTML input field
  *
- * @package HTML
+ * @package    Framework
+ * @subpackage View
  */
 class html_inputfield extends html
 {
@@ -396,7 +397,8 @@ class html_inputfield extends html
 /**
  * Class to create an HTML password field
  *
- * @package HTML
+ * @package    Framework
+ * @subpackage View
  */
 class html_passwordfield extends html_inputfield
 {
@@ -406,9 +408,9 @@ class html_passwordfield extends html_inputfield
 /**
  * Class to create an hidden HTML input field
  *
- * @package HTML
+ * @package    Framework
+ * @subpackage View
  */
-
 class html_hiddenfield extends html
 {
     protected $tagname = 'input';
@@ -456,7 +458,8 @@ class html_hiddenfield extends html
 /**
  * Class to create HTML radio buttons
  *
- * @package HTML
+ * @package    Framework
+ * @subpackage View
  */
 class html_radiobutton extends html_inputfield
 {
@@ -486,7 +489,8 @@ class html_radiobutton extends html_inputfield
 /**
  * Class to create HTML checkboxes
  *
- * @package HTML
+ * @package    Framework
+ * @subpackage View
  */
 class html_checkbox extends html_inputfield
 {
@@ -516,7 +520,8 @@ class html_checkbox extends html_inputfield
 /**
  * Class to create an HTML textarea
  *
- * @package HTML
+ * @package    Framework
+ * @subpackage View
  */
 class html_textarea extends html
 {
@@ -574,7 +579,8 @@ class html_textarea extends html
  * print $select->show('CH');
  * </pre>
  *
- * @package HTML
+ * @package    Framework
+ * @subpackage View
  */
 class html_select extends html
 {
@@ -639,7 +645,8 @@ class html_select extends html
 /**
  * Class to build an HTML table
  *
- * @package HTML
+ * @package    Framework
+ * @subpackage View
  */
 class html_table extends html
 {
diff --git a/program/lib/Roundcube/rcube_base_replacer.php b/program/lib/Roundcube/rcube_base_replacer.php
index fcd85c2c8..e41ccb1d9 100644
--- a/program/lib/Roundcube/rcube_base_replacer.php
+++ b/program/lib/Roundcube/rcube_base_replacer.php
@@ -21,7 +21,7 @@
  * using a predefined base
  *
  * @package    Framework
- * @subpackage Core
+ * @subpackage Utils
  * @author     Thomas Bruederli <roundcube@gmail.com>
  */
 class rcube_base_replacer
diff --git a/program/lib/Roundcube/rcube_browser.php b/program/lib/Roundcube/rcube_browser.php
index d10fe2a2c..34128291b 100644
--- a/program/lib/Roundcube/rcube_browser.php
+++ b/program/lib/Roundcube/rcube_browser.php
@@ -20,7 +20,7 @@
  * Provide details about the client's browser based on the User-Agent header
  *
  * @package    Framework
- * @subpackage Core
+ * @subpackage Utils
  */
 class rcube_browser
 {
diff --git a/program/lib/Roundcube/rcube_content_filter.php b/program/lib/Roundcube/rcube_content_filter.php
index b814bb71d..ae6617d1b 100644
--- a/program/lib/Roundcube/rcube_content_filter.php
+++ b/program/lib/Roundcube/rcube_content_filter.php
@@ -20,7 +20,7 @@
  * PHP stream filter to detect html/javascript code in attachments
  *
  * @package    Framework
- * @subpackage Core
+ * @subpackage Utils
  */
 class rcube_content_filter extends php_user_filter
 {
-- 
cgit v1.2.3


From f0a7159c401983e7dbc9620582124f90f3e4eadc Mon Sep 17 00:00:00 2001
From: Thomas Bruederli <thomas@roundcube.net>
Date: Sat, 2 Mar 2013 00:10:54 +0100
Subject: Add methods to append certain nodes to session data in order to avoid
 session saving race conditions. Fixes #1488422

---
 program/lib/Roundcube/rcube_session.php | 58 ++++++++++++++++++++++++++++++---
 program/steps/mail/attachments.inc      | 16 ++++-----
 2 files changed, 61 insertions(+), 13 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_session.php b/program/lib/Roundcube/rcube_session.php
index 1aa5d5856..82ff8a804 100644
--- a/program/lib/Roundcube/rcube_session.php
+++ b/program/lib/Roundcube/rcube_session.php
@@ -32,6 +32,7 @@ class rcube_session
     private $ip;
     private $start;
     private $changed;
+    private $reloaded = false;
     private $unsets = array();
     private $gc_handlers = array();
     private $cookiename = 'roundcube_sessauth';
@@ -200,8 +201,13 @@ class rcube_session
         if ($oldvars !== null) {
             $a_oldvars = $this->unserialize($oldvars);
             if (is_array($a_oldvars)) {
-                foreach ((array)$this->unsets as $k)
-                    unset($a_oldvars[$k]);
+                // remove unset keys on oldvars
+                foreach ((array)$this->unsets as $var) {
+                    $path = explode('.', $var);
+                    $k = array_pop($path);
+                    $node = &$this->get_node($path, $a_oldvars);
+                    unset($node[$k]);
+                }
 
                 $newvars = $this->serialize(array_merge(
                     (array)$a_oldvars, (array)$this->unserialize($vars)));
@@ -370,10 +376,33 @@ class rcube_session
     }
 
 
+    /**
+     * Append the given value to the certain node in the session data array
+     *
+     * @param string Path denoting the session variable where to append the value
+     * @param string Key name under which to append the new value (use null for appending to an indexed list)
+     * @param mixed  Value to append to the session data array
+     */
+    public function append($path, $key, $value)
+    {
+        // re-read session data from DB because it might be outdated
+        if (!$this->reloaded && microtime(true) - $this->start > 0.5) {
+            $this->reload();
+            $this->reloaded = true;
+            $this->start = microtime(true);
+        }
+
+        $node = &$this->get_node(explode('.', $path), $_SESSION);
+
+        if ($key !== null) $node[$key] = $value;
+        else               $node[] = $value;
+    }
+
+
     /**
      * Unset a session variable
      *
-     * @param string Varibale name
+     * @param string Varibale name (can be a path denoting a certain node in the session array, e.g. compose.attachments.5)
      * @return boolean True on success
      */
     public function remove($var=null)
@@ -383,7 +412,11 @@ class rcube_session
         }
 
         $this->unsets[] = $var;
-        unset($_SESSION[$var]);
+
+        $path = explode('.', $var);
+        $key = array_pop($path);
+        $node = &$this->get_node($path, $_SESSION);
+        unset($node[$key]);
 
         return true;
     }
@@ -415,6 +448,23 @@ class rcube_session
             session_decode($data);
     }
 
+    /**
+     * Returns a reference to the node in data array referenced by the given path.
+     * e.g. ['compose','attachments'] will return $_SESSION['compose']['attachments']
+     */
+    private function &get_node($path, &$data_arr)
+    {
+        $node = &$data_arr;
+        if (!empty($path)) {
+            foreach ((array)$path as $key) {
+                if (!isset($node[$key]))
+                    $node[$key] = array();
+                $node = &$node[$key];
+            }
+        }
+
+        return $node;
+    }
 
     /**
      * Serialize session data
diff --git a/program/steps/mail/attachments.inc b/program/steps/mail/attachments.inc
index 180fc0bb9..f83f6892e 100644
--- a/program/steps/mail/attachments.inc
+++ b/program/steps/mail/attachments.inc
@@ -27,8 +27,10 @@ if (!empty($_GET['_progress'])) {
 $COMPOSE_ID = get_input_value('_id', RCUBE_INPUT_GPC);
 $COMPOSE    = null;
 
-if ($COMPOSE_ID && $_SESSION['compose_data_'.$COMPOSE_ID])
-  $COMPOSE =& $_SESSION['compose_data_'.$COMPOSE_ID];
+if ($COMPOSE_ID && $_SESSION['compose_data_' . $COMPOSE_ID]) {
+  $SESSION_KEY = 'compose_data_' . $COMPOSE_ID;
+  $COMPOSE =& $_SESSION[$SESSION_KEY];
+}
 
 if (!$COMPOSE) {
   die("Invalid session var!");
@@ -45,7 +47,7 @@ if ($RCMAIL->action=='remove-attachment')
     $attachment = $RCMAIL->plugins->exec_hook('attachment_delete', $attachment);
   if ($attachment['status']) {
     if (is_array($COMPOSE['attachments'][$id])) {
-      unset($COMPOSE['attachments'][$id]);
+      $RCMAIL->session->remove($SESSION_KEY.'.attachments.'.$id);
       $OUTPUT->command('remove_from_attachment_list', "rcmfile$id");
     }
   }
@@ -77,11 +79,7 @@ if ($RCMAIL->action=='display-attachment')
   exit;
 }
 
-// attachment upload action
-
-if (!is_array($COMPOSE['attachments'])) {
-  $COMPOSE['attachments'] = array();
-}
+/*****  attachment upload action  *****/
 
 // clear all stored output properties (like scripts and env vars)
 $OUTPUT->reset();
@@ -112,7 +110,7 @@ if (is_array($_FILES['_attachments']['tmp_name'])) {
 
       // store new attachment in session
       unset($attachment['status'], $attachment['abort']);
-      $COMPOSE['attachments'][$id] = $attachment;
+      $RCMAIL->session->append($SESSION_KEY.'.attachments', $id, $attachment);
 
       if (($icon = $COMPOSE['deleteicon']) && is_file($icon)) {
         $button = html::img(array(
-- 
cgit v1.2.3


From ee89c6dff6a309bd6d024a725eb24d79b4ac1236 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Sat, 2 Mar 2013 16:13:08 +0100
Subject: Display notice that message is encrypted also for
 application/pkcs7-mime messages (#1488526)

---
 program/lib/Roundcube/rcube_message.php | 11 +++++++++++
 program/steps/mail/func.inc             |  4 ++--
 2 files changed, 13 insertions(+), 2 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_message.php b/program/lib/Roundcube/rcube_message.php
index e0c3e3475..dc7080746 100644
--- a/program/lib/Roundcube/rcube_message.php
+++ b/program/lib/Roundcube/rcube_message.php
@@ -468,6 +468,17 @@ class rcube_message
 
             $this->parts[] = $p;
         }
+        // this is an S/MIME ecrypted message -> create a plaintext body with the according message
+        else if ($mimetype == 'application/pkcs7-mime') {
+            $p = new stdClass;
+            $p->type            = 'content';
+            $p->ctype_primary   = 'text';
+            $p->ctype_secondary = 'plain';
+            $p->mimetype        = 'text/plain';
+            $p->realtype        = 'application/pkcs7-mime';
+
+            $this->parts[] = $p;
+        }
         // message contains multiple parts
         else if (is_array($structure->parts) && !empty($structure->parts)) {
             // iterate over parts
diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc
index 36ac1aa2b..4a3476320 100644
--- a/program/steps/mail/func.inc
+++ b/program/steps/mail/func.inc
@@ -1083,9 +1083,9 @@ function rcmail_message_body($attrib)
         $out .= html::div('message-partheaders', rcmail_message_headers(sizeof($header_attrib) ? $header_attrib : null, $part->headers));
       }
       else if ($part->type == 'content') {
-        // unsapported
+        // unsupported (e.g. encrypted)
         if ($part->realtype) {
-          if ($part->realtype == 'multipart/encrypted') {
+          if ($part->realtype == 'multipart/encrypted' || $part->realtype == 'application/pkcs7-mime') {
             $out .= html::span('part-notice', rcube_label('encryptedmessage'));
           }
           continue;
-- 
cgit v1.2.3


From 879b2331e11ff90030c514aa5eb1459004ff14c3 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Mon, 4 Mar 2013 11:50:06 +0100
Subject: Generate simpler query for MSSQL when offset in limit clause is not
 set

---
 program/lib/Roundcube/rcube_db_mssql.php | 4 ++++
 1 file changed, 4 insertions(+)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_db_mssql.php b/program/lib/Roundcube/rcube_db_mssql.php
index 84fe22bbc..a1ce80a87 100644
--- a/program/lib/Roundcube/rcube_db_mssql.php
+++ b/program/lib/Roundcube/rcube_db_mssql.php
@@ -110,6 +110,10 @@ class rcube_db_mssql extends rcube_db
 
         $query = preg_replace('/^SELECT\s/i', 'SELECT TOP ' . ($limit + $offset) . ' ', $query);
 
+        if (!$offset) {
+            return $query;
+        }
+
         $query = 'SELECT * FROM (SELECT TOP ' . $limit . ' * FROM (' . $query . ') AS inner_tbl';
         if ($orderby !== false) {
             $query .= ' ORDER BY ' . $order . ' ';
-- 
cgit v1.2.3


From 139635f18985d568ad76b9f101c19542eaee2349 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Tue, 5 Mar 2013 10:25:12 +0100
Subject: Fix thumbnail size when GD extension is used for image resize
 (#1488985)

---
 CHANGELOG                             |  1 +
 program/lib/Roundcube/rcube_image.php | 23 ++++++++++++++++++-----
 program/steps/mail/get.inc            | 10 +++++-----
 3 files changed, 24 insertions(+), 10 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/CHANGELOG b/CHANGELOG
index a45a24758..c5d8c7687 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
 CHANGELOG Roundcube Webmail
 ===========================
 
+- Fix thumbnail size when GD extension is used for image resize (#1488985)
 - Display user-friendly message on IMAP "over quota" errors (#1484164)
 - Display notice that message is encrypted also for application/pkcs7-mime messages (#1488526)
 - Extended archive plugin with user-configurable options to store messages into subfolders
diff --git a/program/lib/Roundcube/rcube_image.php b/program/lib/Roundcube/rcube_image.php
index 9695022da..a55ba1600 100644
--- a/program/lib/Roundcube/rcube_image.php
+++ b/program/lib/Roundcube/rcube_image.php
@@ -77,7 +77,8 @@ class rcube_image
     }
 
     /**
-     * Resize image to a given size
+     * Resize image to a given size. Use only to shrink an image.
+     * If an image is smaller than specified size it will be not resized.
      *
      * @param int    $size      Max width/height size
      * @param string $filename  Output filename
@@ -131,19 +132,30 @@ class rcube_image
         if ($props['gd_type']) {
             if ($props['gd_type'] == IMAGETYPE_JPEG && function_exists('imagecreatefromjpeg')) {
                 $image = imagecreatefromjpeg($this->image_file);
+                $type  = 'jpg';
             }
             else if($props['gd_type'] == IMAGETYPE_GIF && function_exists('imagecreatefromgif')) {
                 $image = imagecreatefromgif($this->image_file);
+                $type  = 'gid';
             }
             else if($props['gd_type'] == IMAGETYPE_PNG && function_exists('imagecreatefrompng')) {
                 $image = imagecreatefrompng($this->image_file);
+                $type  = 'png';
             }
             else {
                 // @TODO: print error to the log?
                 return false;
             }
 
-            $scale  = $size / max($props['width'], $props['height']);
+            $scale = $size / max($props['width'], $props['height']);
+
+            // Imagemagick resize is implemented in shrinking mode (see -resize argument above)
+            // we do the same here, if an image is smaller than specified size
+            // we do nothing but copy original file to destination file
+            if ($scale > 1) {
+                return $this->image_file == $filename || copy($this->image_file, $filename) ? $type : false;
+            }
+
             $width  = $props['width']  * $scale;
             $height = $props['height'] * $scale;
 
@@ -162,15 +174,12 @@ class rcube_image
 
             if ($props['gd_type'] == IMAGETYPE_JPEG) {
                 $result = imagejpeg($image, $filename, 75);
-                $type = 'jpg';
             }
             elseif($props['gd_type'] == IMAGETYPE_GIF) {
                 $result = imagegif($image, $filename);
-                $type = 'gid';
             }
             elseif($props['gd_type'] == IMAGETYPE_PNG) {
                 $result = imagepng($image, $filename, 6, PNG_ALL_FILTERS);
-                $type = 'png';
             }
 
             if ($result) {
@@ -245,6 +254,10 @@ class rcube_image
             else if ($type == self::TYPE_PNG) {
                 $result = imagepng($image, $filename, 6, PNG_ALL_FILTERS);
             }
+
+            if ($result) {
+                return true;
+            }
         }
 
         // @TODO: print error to the log?
diff --git a/program/steps/mail/get.inc b/program/steps/mail/get.inc
index 54d1a4e77..23dc22b7c 100644
--- a/program/steps/mail/get.inc
+++ b/program/steps/mail/get.inc
@@ -60,11 +60,11 @@ else if ($_GET['_thumb']) {
   $pid = get_input_value('_part', RCUBE_INPUT_GET);
   if ($part = $MESSAGE->mime_parts[$pid]) {
     $thumbnail_size = $RCMAIL->config->get('image_thumbnail_size', 240);
-    $temp_dir = $RCMAIL->config->get('temp_dir');
-    list(,$ext) = explode('/', $part->mimetype);
+    $temp_dir       = $RCMAIL->config->get('temp_dir');
+    list(,$ext)     = explode('/', $part->mimetype);
     $cache_basename = $temp_dir . '/' . md5($MESSAGE->headers->messageID . $part->mime_id . ':' . $RCMAIL->user->ID . ':' . $thumbnail_size);
-    $cache_file = $cache_basename . '.' . $ext;
-    $mimetype = $part->mimetype;
+    $cache_file     = $cache_basename . '.' . $ext;
+    $mimetype       = $part->mimetype;
 
     // render thumbnail image if not done yet
     if (!is_file($cache_file)) {
@@ -73,7 +73,7 @@ else if ($_GET['_thumb']) {
       fclose($fp);
 
       $image = new rcube_image($orig_name);
-      if ($imgtype = $image->resize($RCMAIL->config->get('image_thumbnail_size', 240), $cache_file, true)) {
+      if ($imgtype = $image->resize($thumbnail_size, $cache_file, true)) {
         $mimetype = 'image/' . $imgtype;
         unlink($orig_name);
       }
-- 
cgit v1.2.3


From ac37746c2ae187edaf38c7a660213e216f90b035 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Wed, 6 Mar 2013 08:37:41 +0100
Subject: Add type 'ident' in quote() so we can quote identifiers (eg. column
 names) there. Using array2list() for list of identifiers is now possible.

---
 program/lib/Roundcube/rcube_db.php | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_db.php b/program/lib/Roundcube/rcube_db.php
index 88cd22b0e..b1db7ada7 100644
--- a/program/lib/Roundcube/rcube_db.php
+++ b/program/lib/Roundcube/rcube_db.php
@@ -571,7 +571,7 @@ class rcube_db
      * Formats input so it can be safely used in a query
      *
      * @param mixed  $input Value to quote
-     * @param string $type  Type of data
+     * @param string $type  Type of data (integer, bool, ident)
      *
      * @return string Quoted/converted string for use in query
      */
@@ -586,6 +586,10 @@ class rcube_db
             return 'NULL';
         }
 
+        if ($type == 'ident') {
+            return $this->quote_identifier($input);
+        }
+
         // create DB handle if not available
         if (!$this->dbh) {
             $this->db_connect('r');
@@ -635,7 +639,7 @@ class rcube_db
             $name[] = $start . $elem . $end;
         }
 
-        return  implode($name, '.');
+        return implode($name, '.');
     }
 
     /**
@@ -652,7 +656,7 @@ class rcube_db
      * Return list of elements for use with SQL's IN clause
      *
      * @param array  $arr  Input array
-     * @param string $type Type of data
+     * @param string $type Type of data (integer, bool, ident)
      *
      * @return string Comma-separated list of quoted values for use in query
      */
-- 
cgit v1.2.3


From 8cfba1bb2a5bef037316107893c4bbac801c4883 Mon Sep 17 00:00:00 2001
From: Thijs Kinkhorst <thijs@uvt.nl>
Date: Fri, 8 Mar 2013 13:41:18 +0100
Subject: Test content_id with isset instead of a true/false value.

This broke html email in which one image had Content-ID: <0>, which is a
valid value but evaulates to false in this test.
---
 program/lib/Roundcube/rcube_message.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_message.php b/program/lib/Roundcube/rcube_message.php
index dc7080746..60161a419 100644
--- a/program/lib/Roundcube/rcube_message.php
+++ b/program/lib/Roundcube/rcube_message.php
@@ -616,7 +616,7 @@ class rcube_message
 
                 foreach ($this->inline_parts as $inline_object) {
                     $part_url = $this->get_part_url($inline_object->mime_id, true);
-                    if ($inline_object->content_id)
+                    if (isset($inline_object->content_id))
                         $a_replaces['cid:'.$inline_object->content_id] = $part_url;
                     if ($inline_object->content_location) {
                         $a_replaces[$inline_object->content_location] = $part_url;
-- 
cgit v1.2.3


From d9dc320a40ef21cfc0a1f03d453784949da65f52 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Fri, 8 Mar 2013 15:35:03 +0100
Subject: Support IMAP MOVE extension [RFC 6851]

---
 CHANGELOG                                    |  1 +
 program/lib/Roundcube/rcube_imap.php         |  3 --
 program/lib/Roundcube/rcube_imap_generic.php | 47 ++++++++++++++++++++++------
 3 files changed, 38 insertions(+), 13 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/CHANGELOG b/CHANGELOG
index 062313ee1..baca86765 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
 CHANGELOG Roundcube Webmail
 ===========================
 
+- Support IMAP MOVE extension [RFC 6851]
 - Fix javascript errors when working in a page opened with taget="_blank"
 - Mention SQLite database format change in UPGRADING file (#1488983)
 - Increase maxlength to 254 chars for email input fields in addressbook (#1488987)
diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php
index 18c6b12af..8d9c37576 100644
--- a/program/lib/Roundcube/rcube_imap.php
+++ b/program/lib/Roundcube/rcube_imap.php
@@ -2333,10 +2333,7 @@ class rcube_imap extends rcube_storage
         // move messages
         $moved = $this->conn->move($uids, $from_mbox, $to_mbox);
 
-        // send expunge command in order to have the moved message
-        // really deleted from the source folder
         if ($moved) {
-            $this->expunge_message($uids, $from_mbox, false);
             $this->clear_messagecount($from_mbox);
             $this->clear_messagecount($to_mbox);
         }
diff --git a/program/lib/Roundcube/rcube_imap_generic.php b/program/lib/Roundcube/rcube_imap_generic.php
index b9a796c33..8532cf8ad 100644
--- a/program/lib/Roundcube/rcube_imap_generic.php
+++ b/program/lib/Roundcube/rcube_imap_generic.php
@@ -1065,8 +1065,8 @@ class rcube_imap_generic
     /**
      * Executes EXPUNGE command
      *
-     * @param string $mailbox  Mailbox name
-     * @param string $messages Message UIDs to expunge
+     * @param string       $mailbox  Mailbox name
+     * @param string|array $messages Message UIDs to expunge
      *
      * @return boolean True on success, False on error
      */
@@ -1084,10 +1084,13 @@ class rcube_imap_generic
         // Clear internal status cache
         unset($this->data['STATUS:'.$mailbox]);
 
-        if ($messages)
-            $result = $this->execute('UID EXPUNGE', array($messages), self::COMMAND_NORESPONSE);
-        else
+        if (!empty($messages) && $messages != '*' && $this->hasCapability('UIDPLUS')) {
+            $messages = self::compressMessageSet($messages);
+            $result   = $this->execute('UID EXPUNGE', array($messages), self::COMMAND_NORESPONSE);
+        }
+        else {
             $result = $this->execute('EXPUNGE', null, self::COMMAND_NORESPONSE);
+        }
 
         if ($result == self::ERROR_OK) {
             $this->selected = null; // state has changed, need to reselect
@@ -1980,7 +1983,6 @@ class rcube_imap_generic
 
     /**
      * Moves message(s) from one folder to another.
-     * Original message(s) will be marked as deleted.
      *
      * @param string|array  $messages  Message UID(s)
      * @param string        $from      Mailbox name
@@ -1999,14 +2001,39 @@ class rcube_imap_generic
             return false;
         }
 
-        $r = $this->copy($messages, $from, $to);
+        // use MOVE command (RFC 6851)
+        if ($this->hasCapability('MOVE')) {
+            // Clear last COPYUID data
+            unset($this->data['COPYUID']);
 
-        if ($r) {
             // Clear internal status cache
+            unset($this->data['STATUS:'.$to]);
             unset($this->data['STATUS:'.$from]);
 
-            return $this->flag($from, $messages, 'DELETED');
+            $r = $this->execute('UID MOVE', array(
+                $this->compressMessageSet($messages), $this->escape($to)),
+                self::COMMAND_NORESPONSE);
+        }
+        // use COPY + STORE +FLAGS.SILENT \Deleted + EXPUNGE
+        else {
+            $r = $this->copy($messages, $from, $to);
+
+            if ($r) {
+                // Clear internal status cache
+                unset($this->data['STATUS:'.$from]);
+
+                $r = $this->flag($from, $messages, 'DELETED');
+
+                if ($messages == '*') {
+                    // CLOSE+SELECT should be faster than EXPUNGE
+                    $this->close();
+                }
+                else {
+                    $this->expunge($from, $messages);
+                }
+            }
         }
+
         return $r;
     }
 
@@ -3502,7 +3529,7 @@ class rcube_imap_generic
             // if less than 255 bytes long, let's not bother
             if (!$force && strlen($messages)<255) {
                 return $messages;
-           }
+            }
 
             // see if it's already been compressed
             if (strpos($messages, ':') !== false) {
-- 
cgit v1.2.3


From 8b771646fadcde0abb27c2218a45942b95734838 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Sun, 10 Mar 2013 11:49:20 +0100
Subject: Fix so task name can really contain all from a-z0-9_- characters
 (#1488941)

---
 CHANGELOG                                  | 1 +
 program/include/rcmail.php                 | 2 +-
 program/js/app.js                          | 6 +++---
 program/lib/Roundcube/rcube_plugin.php     | 2 +-
 program/lib/Roundcube/rcube_plugin_api.php | 4 ++--
 5 files changed, 8 insertions(+), 7 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/CHANGELOG b/CHANGELOG
index baca86765..ef3830a42 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
 CHANGELOG Roundcube Webmail
 ===========================
 
+- Fix so task name can really contain all from a-z0-9_- characters (#1488941)
 - Support IMAP MOVE extension [RFC 6851]
 - Fix javascript errors when working in a page opened with taget="_blank"
 - Mention SQLite database format change in UPGRADING file (#1488983)
diff --git a/program/include/rcmail.php b/program/include/rcmail.php
index b2d6966d0..1bde4034f 100644
--- a/program/include/rcmail.php
+++ b/program/include/rcmail.php
@@ -123,7 +123,7 @@ class rcmail extends rcube
    */
   public function set_task($task)
   {
-    $task = asciiwords($task);
+    $task = asciiwords($task, true);
 
     if ($this->user && $this->user->ID)
       $task = !$task ? 'mail' : $task;
diff --git a/program/js/app.js b/program/js/app.js
index c2a7c1b79..9f76757a6 100644
--- a/program/js/app.js
+++ b/program/js/app.js
@@ -1235,7 +1235,7 @@ function rcube_webmail()
     if (!url)
       url = this.env.comm_path;
 
-    return url.replace(/_task=[a-z]+/, '_task='+task);
+    return url.replace(/_task=[a-z0-9_-]+/i, '_task='+task);
   };
 
   this.reload = function(delay)
@@ -5970,9 +5970,9 @@ function rcube_webmail()
     var base = this.env.comm_path, k, param = {};
 
     // overwrite task name
-    if (query._action.match(/([a-z]+)\/([a-z0-9-_.]+)/)) {
+    if (query._action.match(/([a-z0-9_-]+)\/([a-z0-9-_.]+)/)) {
       query._action = RegExp.$2;
-      base = base.replace(/\_task=[a-z]+/, '_task='+RegExp.$1);
+      base = base.replace(/\_task=[a-z0-9_-]+/, '_task='+RegExp.$1);
     }
 
     // remove undefined values
diff --git a/program/lib/Roundcube/rcube_plugin.php b/program/lib/Roundcube/rcube_plugin.php
index 66e77cce2..9ea0f73d3 100644
--- a/program/lib/Roundcube/rcube_plugin.php
+++ b/program/lib/Roundcube/rcube_plugin.php
@@ -237,7 +237,7 @@ abstract class rcube_plugin
     /**
      * Register this plugin to be responsible for a specific task
      *
-     * @param string $task Task name (only characters [a-z0-9_.-] are allowed)
+     * @param string $task Task name (only characters [a-z0-9_-] are allowed)
      */
     public function register_task($task)
     {
diff --git a/program/lib/Roundcube/rcube_plugin_api.php b/program/lib/Roundcube/rcube_plugin_api.php
index 8a4cce215..111c177d9 100644
--- a/program/lib/Roundcube/rcube_plugin_api.php
+++ b/program/lib/Roundcube/rcube_plugin_api.php
@@ -372,7 +372,7 @@ class rcube_plugin_api
     /**
      * Register this plugin to be responsible for a specific task
      *
-     * @param string $task Task name (only characters [a-z0-9_.-] are allowed)
+     * @param string $task Task name (only characters [a-z0-9_-] are allowed)
      * @param string $owner Plugin name that registers this action
      */
     public function register_task($task, $owner)
@@ -382,7 +382,7 @@ class rcube_plugin_api
             return true;
         }
 
-        if ($task != asciiwords($task)) {
+        if ($task != asciiwords($task, true)) {
             rcube::raise_error(array('code' => 526, 'type' => 'php',
                 'file' => __FILE__, 'line' => __LINE__,
                 'message' => "Invalid task name: $task."
-- 
cgit v1.2.3


From ec6a77bab27984ce05b003af07ac9f42ca410d94 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Mon, 11 Mar 2013 08:35:21 +0100
Subject: Fix LIMIT/OFFSET queries handling on MS SQL Server (#1488984) -
 require version 2005+

---
 CHANGELOG                                 |  1 +
 INSTALL                                   |  4 +---
 program/lib/Roundcube/rcube_db_mssql.php  | 30 +++++++++++++++---------------
 program/lib/Roundcube/rcube_db_sqlsrv.php | 28 ++++++++++++++++------------
 4 files changed, 33 insertions(+), 30 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/CHANGELOG b/CHANGELOG
index ef3830a42..55360ec14 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
 CHANGELOG Roundcube Webmail
 ===========================
 
+- Fix LIMIT/OFFSET queries handling on MS SQL Server (#1488984)
 - Fix so task name can really contain all from a-z0-9_- characters (#1488941)
 - Support IMAP MOVE extension [RFC 6851]
 - Fix javascript errors when working in a page opened with taget="_blank"
diff --git a/INSTALL b/INSTALL
index 326ef889f..de2944107 100644
--- a/INSTALL
+++ b/INSTALL
@@ -34,7 +34,7 @@ REQUIREMENTS
    - magic_quotes_runtime disabled
    - magic_quotes_sybase disabled
 * PHP compiled with OpenSSL to connect to IMAPS and to use the spell checker
-* A MySQL (4.0.8 or newer), PostgreSQL, MSSQL database engine
+* A MySQL (4.0.8 or newer), PostgreSQL, MS SQL Server (2005 or newer) database engine
   or SQLite support in PHP
 * One of the above databases with permission to create tables
 * An SMTP server (recommended) or PHP configured for mail delivery
@@ -232,5 +232,3 @@ $HTTP["host"] == "www.example.com" {
 
     compress.filetype = ("text/plain", "text/html", "text/javascript", "text/css", "text/xml", "image/gif", "image/png")
 }
-
-
diff --git a/program/lib/Roundcube/rcube_db_mssql.php b/program/lib/Roundcube/rcube_db_mssql.php
index a1ce80a87..37a42678a 100644
--- a/program/lib/Roundcube/rcube_db_mssql.php
+++ b/program/lib/Roundcube/rcube_db_mssql.php
@@ -100,30 +100,30 @@ class rcube_db_mssql extends rcube_db
     {
         $limit  = intval($limit);
         $offset = intval($offset);
+        $end    = $offset + $limit;
 
-        $orderby = stristr($query, 'ORDER BY');
-        if ($orderby !== false) {
-            $sort  = (stripos($orderby, ' desc') !== false) ? 'desc' : 'asc';
-            $order = str_ireplace('ORDER BY', '', $orderby);
-            $order = trim(preg_replace('/\bASC\b|\bDESC\b/i', '', $order));
-        }
-
-        $query = preg_replace('/^SELECT\s/i', 'SELECT TOP ' . ($limit + $offset) . ' ', $query);
-
+        // query without OFFSET
         if (!$offset) {
+            $query = preg_replace('/^SELECT\s/i', "SELECT TOP $limit ", $query);
             return $query;
         }
 
-        $query = 'SELECT * FROM (SELECT TOP ' . $limit . ' * FROM (' . $query . ') AS inner_tbl';
+        $orderby = stristr($query, 'ORDER BY');
+        $offset += 1;
+
         if ($orderby !== false) {
-            $query .= ' ORDER BY ' . $order . ' ';
-            $query .= (stripos($sort, 'asc') !== false) ? 'DESC' : 'ASC';
+            $query = trim(substr($query, 0, -1 * strlen($orderby)));
         }
-        $query .= ') AS outer_tbl';
-        if ($orderby !== false) {
-            $query .= ' ORDER BY ' . $order . ' ' . $sort;
+        else {
+            // it shouldn't happen, paging without sorting has not much sense
+            // @FIXME: I don't know how to build paging query without ORDER BY
+            $orderby = "ORDER BY 1";
         }
 
+        $query = preg_replace('/^SELECT\s/i', '', $query);
+        $query = "WITH paging AS (SELECT ROW_NUMBER() OVER ($orderby) AS [RowNumber], $query)"
+            . " SELECT * FROM paging WHERE [RowNumber] BETWEEN $offset AND $end ORDER BY [RowNumber]";
+
         return $query;
     }
 
diff --git a/program/lib/Roundcube/rcube_db_sqlsrv.php b/program/lib/Roundcube/rcube_db_sqlsrv.php
index e69678025..e5dfb1154 100644
--- a/program/lib/Roundcube/rcube_db_sqlsrv.php
+++ b/program/lib/Roundcube/rcube_db_sqlsrv.php
@@ -100,26 +100,30 @@ class rcube_db_sqlsrv extends rcube_db
     {
         $limit  = intval($limit);
         $offset = intval($offset);
+        $end    = $offset + $limit;
 
-        $orderby = stristr($query, 'ORDER BY');
-        if ($orderby !== false) {
-            $sort  = (stripos($orderby, ' desc') !== false) ? 'desc' : 'asc';
-            $order = str_ireplace('ORDER BY', '', $orderby);
-            $order = trim(preg_replace('/\bASC\b|\bDESC\b/i', '', $order));
+        // query without OFFSET
+        if (!$offset) {
+            $query = preg_replace('/^SELECT\s/i', "SELECT TOP $limit ", $query);
+            return $query;
         }
 
-        $query = preg_replace('/^SELECT\s/i', 'SELECT TOP ' . ($limit + $offset) . ' ', $query);
+        $orderby = stristr($query, 'ORDER BY');
+        $offset += 1;
 
-        $query = 'SELECT * FROM (SELECT TOP ' . $limit . ' * FROM (' . $query . ') AS inner_tbl';
         if ($orderby !== false) {
-            $query .= ' ORDER BY ' . $order . ' ';
-            $query .= (stripos($sort, 'asc') !== false) ? 'DESC' : 'ASC';
+            $query = trim(substr($query, 0, -1 * strlen($orderby)));
         }
-        $query .= ') AS outer_tbl';
-        if ($orderby !== false) {
-            $query .= ' ORDER BY ' . $order . ' ' . $sort;
+        else {
+            // it shouldn't happen, paging without sorting has not much sense
+            // @FIXME: I don't know how to build paging query without ORDER BY
+            $orderby = "ORDER BY 1";
         }
 
+        $query = preg_replace('/^SELECT\s/i', '', $query);
+        $query = "WITH paging AS (SELECT ROW_NUMBER() OVER ($orderby) AS [RowNumber], $query)"
+            . " SELECT * FROM paging WHERE [RowNumber] BETWEEN $offset AND $end ORDER BY [RowNumber]";
+
         return $query;
     }
 
-- 
cgit v1.2.3


From 336d2000f8b3edd788bbdd49c4c5bc11c895a94d Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Mon, 11 Mar 2013 10:26:58 +0100
Subject: Fix handling of empty $uids argument in change_flag()

---
 program/lib/Roundcube/rcube_imap_cache.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_imap_cache.php b/program/lib/Roundcube/rcube_imap_cache.php
index f33ac076c..748474af2 100644
--- a/program/lib/Roundcube/rcube_imap_cache.php
+++ b/program/lib/Roundcube/rcube_imap_cache.php
@@ -485,7 +485,7 @@ class rcube_imap_cache
             .", flags = flags ".($enabled ? "+ $idx" : "- $idx")
             ." WHERE user_id = ?"
                 ." AND mailbox = ?"
-                .($uids !== null ? " AND uid IN (".$this->db->array2list($uids, 'integer').")" : "")
+                .(!empty($uids) ? " AND uid IN (".$this->db->array2list($uids, 'integer').")" : "")
                 ." AND (flags & $idx) ".($enabled ? "= 0" : "= $idx"),
             $this->userid, $mailbox);
     }
-- 
cgit v1.2.3


From 567e45ba565b1d03d8dc981dc0dfbc49eec4a355 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Wed, 13 Mar 2013 11:03:21 +0100
Subject: Fix HTML part detection for some specific message structures
 (#1488992)

---
 CHANGELOG                               |  1 +
 program/lib/Roundcube/rcube_message.php | 16 +++++++++-------
 2 files changed, 10 insertions(+), 7 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/CHANGELOG b/CHANGELOG
index 499d5ba07..6fef2d026 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
 CHANGELOG Roundcube Webmail
 ===========================
 
+- Fix HTML part detection for some specific message structures (#1488992)
 - Don't show fake address - phishing prevention (#1488981)
 - Fix forward as attachment bug with editormode != 1 (#1488991)
 - Fix LIMIT/OFFSET queries handling on MS SQL Server (#1488984)
diff --git a/program/lib/Roundcube/rcube_message.php b/program/lib/Roundcube/rcube_message.php
index 60161a419..3f14266d4 100644
--- a/program/lib/Roundcube/rcube_message.php
+++ b/program/lib/Roundcube/rcube_message.php
@@ -210,18 +210,20 @@ class rcube_message
                 if (!$recursive) {
                     $level = explode('.', $part->mime_id);
 
-                    // Skip if level too deep or part has a file name
-                    if (count($level) > 2 || $part->filename) {
+                    // Skip if part is an attachment
+                    if ($this->is_attachment($part)) {
                         continue;
                     }
 
-                    // HTML part can be on the lower level, if not...
-                    if (count($level) > 1) {
-                        array_pop($level);
+                    // Check if the part belongs to higher-level's alternative/related
+                    while (array_pop($level) !== null) {
+                        if (!count($level)) {
+                            return true;
+                        }
+
                         $parent = $this->mime_parts[join('.', $level)];
-                        // ... parent isn't multipart/alternative or related
                         if ($parent->mimetype != 'multipart/alternative' && $parent->mimetype != 'multipart/related') {
-                            continue;
+                            continue 2;
                         }
                     }
                 }
-- 
cgit v1.2.3


From d4f8a4f28a49b2fd92c398b4df3d0a0e3059c091 Mon Sep 17 00:00:00 2001
From: Thomas Bruederli <thomas@roundcube.net>
Date: Wed, 13 Mar 2013 19:02:31 +0100
Subject: Re-implement rcube_db::num_rows() to ensure backwards compatibility

---
 program/lib/Roundcube/rcube_db.php | 23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_db.php b/program/lib/Roundcube/rcube_db.php
index b1db7ada7..49bbe5c6e 100644
--- a/program/lib/Roundcube/rcube_db.php
+++ b/program/lib/Roundcube/rcube_db.php
@@ -438,6 +438,29 @@ class rcube_db
         return 0;
     }
 
+    /**
+     * Get number of rows for a SQL query
+     * If no query handle is specified, the last query will be taken as reference
+     *
+     * @param mixed $result Optional query handle
+     * @return mixed   Number of rows or false on failure
+     */
+    public function num_rows($result = null)
+    {
+        if ($result || ($result === null && ($result = $this->last_result))) {
+            // repeat query with SELECT COUNT(*) ...
+            if (preg_match('/^SELECT\s+(?:ALL\s+|DISTINCT\s+)?(?:.*?)\s+FROM\s+(.*)$/i', $result->queryString, $m)) {
+                $query = $this->dbh->query('SELECT COUNT(*) FROM ' . $m[1], PDO::FETCH_NUM);
+                return $query ? intval($query->fetchColumn(0)) : false;
+            }
+            else {
+                return count($result->fetchAll());
+            }
+        }
+
+        return false;
+    }
+
     /**
      * Get last inserted record ID
      *
-- 
cgit v1.2.3


From 5c26bd49b10a2666df9e4853b0740038b0cc3b88 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Thu, 14 Mar 2013 12:10:40 +0100
Subject: Added rcube_message::has_text_part(), simplified has_html_part() so
 it always works in "recursive mode" - removed $recursive argument.

---
 program/lib/Roundcube/rcube_message.php | 81 +++++++++++++++++++++++++--------
 program/steps/mail/compose.inc          |  2 +-
 2 files changed, 62 insertions(+), 21 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_message.php b/program/lib/Roundcube/rcube_message.php
index 3f14266d4..7d58a8eb5 100644
--- a/program/lib/Roundcube/rcube_message.php
+++ b/program/lib/Roundcube/rcube_message.php
@@ -194,41 +194,82 @@ class rcube_message
 
 
     /**
-     * Determine if the message contains a HTML part
+     * Determine if the message contains a HTML part. This must to be
+     * a real part not an attachment (or its part)
+     * This must to be
+     * a real part not an attachment (or its part)
      *
-     * @param bool $recursive Enables checking in all levels of the structure
-     * @param bool $enriched  Enables checking for text/enriched parts too
+     * @param bool $enriched Enables checking for text/enriched parts too
      *
      * @return bool True if a HTML is available, False if not
      */
-    function has_html_part($recursive = true, $enriched = false)
+    function has_html_part($enriched = false)
     {
         // check all message parts
         foreach ($this->parts as $part) {
             if ($part->mimetype == 'text/html' || ($enriched && $part->mimetype == 'text/enriched')) {
-                // Level check, we'll skip e.g. HTML attachments
-                if (!$recursive) {
-                    $level = explode('.', $part->mime_id);
+                // Skip if part is an attachment
+                if ($this->is_attachment($part)) {
+                    continue;
+                }
 
-                    // Skip if part is an attachment
-                    if ($this->is_attachment($part)) {
-                        continue;
+                $level = explode('.', $part->mime_id);
+
+                // Check if the part belongs to higher-level's alternative/related
+                while (array_pop($level) !== null) {
+                    if (!count($level)) {
+                        return true;
                     }
 
-                    // Check if the part belongs to higher-level's alternative/related
-                    while (array_pop($level) !== null) {
-                        if (!count($level)) {
-                            return true;
-                        }
+                    $parent = $this->mime_parts[join('.', $level)];
+                    if ($parent->mimetype != 'multipart/alternative' && $parent->mimetype != 'multipart/related') {
+                        continue 2;
+                    }
+                }
 
-                        $parent = $this->mime_parts[join('.', $level)];
-                        if ($parent->mimetype != 'multipart/alternative' && $parent->mimetype != 'multipart/related') {
-                            continue 2;
-                        }
+                if ($part->size) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+
+    /**
+     * Determine if the message contains a text/plain part. This must to be
+     * a real part not an attachment (or its part)
+     *
+     * @return bool True if a plain text part is available, False if not
+     */
+    function has_text_part()
+    {
+        // check all message parts
+        foreach ($this->parts as $part) {
+            if ($part->mimetype == 'text/plain') {
+                // Skip if part is an attachment
+                if ($this->is_attachment($part)) {
+                    continue;
+                }
+
+                $level = explode('.', $part->mime_id);
+
+                // Check if the part belongs to higher-level's alternative/related
+                while (array_pop($level) !== null) {
+                    if (!count($level)) {
+                        return true;
+                    }
+
+                    $parent = $this->mime_parts[join('.', $level)];
+                    if ($parent->mimetype != 'multipart/alternative' && $parent->mimetype != 'multipart/related') {
+                        continue 2;
                     }
                 }
 
-                return true;
+                if ($part->size) {
+                    return true;
+                }
             }
         }
 
diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc
index d7cfe7ddd..dd6a1d88c 100644
--- a/program/steps/mail/compose.inc
+++ b/program/steps/mail/compose.inc
@@ -539,7 +539,7 @@ function rcmail_compose_editor_mode()
 function rcmail_message_is_html()
 {
     global $MESSAGE;
-    return ($MESSAGE instanceof rcube_message) && $MESSAGE->has_html_part(false, true);
+    return ($MESSAGE instanceof rcube_message) && $MESSAGE->has_html_part(true);
 }
 
 function rcmail_prepare_message_body()
-- 
cgit v1.2.3


From 574928200fd8da1194af9a9a1e741c77d7a50185 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Thu, 14 Mar 2013 13:20:28 +0100
Subject: Use $mime_parts not $parts in has_*_part() methods so detection is
 correct no matter if prefer_html is enabled or not.

---
 program/lib/Roundcube/rcube_message.php | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_message.php b/program/lib/Roundcube/rcube_message.php
index 7d58a8eb5..1e3d143eb 100644
--- a/program/lib/Roundcube/rcube_message.php
+++ b/program/lib/Roundcube/rcube_message.php
@@ -206,7 +206,7 @@ class rcube_message
     function has_html_part($enriched = false)
     {
         // check all message parts
-        foreach ($this->parts as $part) {
+        foreach ($this->mime_parts as $part) {
             if ($part->mimetype == 'text/html' || ($enriched && $part->mimetype == 'text/enriched')) {
                 // Skip if part is an attachment
                 if ($this->is_attachment($part)) {
@@ -246,7 +246,7 @@ class rcube_message
     function has_text_part()
     {
         // check all message parts
-        foreach ($this->parts as $part) {
+        foreach ($this->mime_parts as $part) {
             if ($part->mimetype == 'text/plain') {
                 // Skip if part is an attachment
                 if ($this->is_attachment($part)) {
-- 
cgit v1.2.3


From 0ef894ec2949100aee8624701edbf38087ea9047 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Fri, 15 Mar 2013 09:09:06 +0100
Subject: Fix has_*_part() methods so they return same result no matter what
 prefer_html option value is

---
 program/lib/Roundcube/rcube_message.php | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_message.php b/program/lib/Roundcube/rcube_message.php
index 1e3d143eb..b83334613 100644
--- a/program/lib/Roundcube/rcube_message.php
+++ b/program/lib/Roundcube/rcube_message.php
@@ -208,8 +208,8 @@ class rcube_message
         // check all message parts
         foreach ($this->mime_parts as $part) {
             if ($part->mimetype == 'text/html' || ($enriched && $part->mimetype == 'text/enriched')) {
-                // Skip if part is an attachment
-                if ($this->is_attachment($part)) {
+                // Skip if part is an attachment, don't use is_attachment() here
+                if ($part->filename) {
                     continue;
                 }
 
@@ -248,8 +248,8 @@ class rcube_message
         // check all message parts
         foreach ($this->mime_parts as $part) {
             if ($part->mimetype == 'text/plain') {
-                // Skip if part is an attachment
-                if ($this->is_attachment($part)) {
+                // Skip if part is an attachment, don't use is_attachment() here
+                if ($part->filename) {
                     continue;
                 }
 
-- 
cgit v1.2.3


From f1114237556d32bb217c5dcbb0aa7db2d081608b Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Fri, 15 Mar 2013 10:41:09 +0100
Subject: Fix storing 'safe' flag on a message. The key for session value
 should include folder name. A message with the same UID may exist in another
 folder.

---
 program/lib/Roundcube/rcube_message.php | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_message.php b/program/lib/Roundcube/rcube_message.php
index b83334613..42d7b9bbe 100644
--- a/program/lib/Roundcube/rcube_message.php
+++ b/program/lib/Roundcube/rcube_message.php
@@ -93,7 +93,7 @@ class rcube_message
         $this->subject = $this->mime->decode_mime_string($this->headers->subject);
         list(, $this->sender) = each($this->mime->decode_address_list($this->headers->from, 1));
 
-        $this->set_safe((intval($_GET['_safe']) || $_SESSION['safe_messages'][$uid]));
+        $this->set_safe((intval($_GET['_safe']) || $_SESSION['safe_messages'][$this->folder.':'.$uid]));
         $this->opt = array(
             'safe' => $this->is_safe,
             'prefer_html' => $this->app->config->get('prefer_html'),
@@ -144,8 +144,7 @@ class rcube_message
      */
     public function set_safe($safe = true)
     {
-        $this->is_safe = $safe;
-        $_SESSION['safe_messages'][$this->uid] = $this->is_safe;
+        $_SESSION['safe_messages'][$this->folder.':'.$this->uid] = $this->is_safe = $safe;
     }
 
 
-- 
cgit v1.2.3


From ea98ec0939532d6539689524414b9eeb1c6cd0fc Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Fri, 15 Mar 2013 10:50:26 +0100
Subject: Fixed MOVE command result handling

---
 program/lib/Roundcube/rcube_imap_generic.php | 31 ++++++++++++++--------------
 1 file changed, 16 insertions(+), 15 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_imap_generic.php b/program/lib/Roundcube/rcube_imap_generic.php
index 8532cf8ad..2ac1355fd 100644
--- a/program/lib/Roundcube/rcube_imap_generic.php
+++ b/program/lib/Roundcube/rcube_imap_generic.php
@@ -2010,31 +2010,32 @@ class rcube_imap_generic
             unset($this->data['STATUS:'.$to]);
             unset($this->data['STATUS:'.$from]);
 
-            $r = $this->execute('UID MOVE', array(
+            $result = $this->execute('UID MOVE', array(
                 $this->compressMessageSet($messages), $this->escape($to)),
                 self::COMMAND_NORESPONSE);
+
+            return ($result == self::ERROR_OK);
         }
+
         // use COPY + STORE +FLAGS.SILENT \Deleted + EXPUNGE
-        else {
-            $r = $this->copy($messages, $from, $to);
+        $result = $this->copy($messages, $from, $to);
 
-            if ($r) {
-                // Clear internal status cache
-                unset($this->data['STATUS:'.$from]);
+        if ($result) {
+            // Clear internal status cache
+            unset($this->data['STATUS:'.$from]);
 
-                $r = $this->flag($from, $messages, 'DELETED');
+            $result = $this->flag($from, $messages, 'DELETED');
 
-                if ($messages == '*') {
-                    // CLOSE+SELECT should be faster than EXPUNGE
-                    $this->close();
-                }
-                else {
-                    $this->expunge($from, $messages);
-                }
+            if ($messages == '*') {
+                // CLOSE+SELECT should be faster than EXPUNGE
+                $this->close();
+            }
+            else {
+                $this->expunge($from, $messages);
             }
         }
 
-        return $r;
+        return $result;
     }
 
     /**
-- 
cgit v1.2.3


From 6e8f2a7448d9bf5a87603b197816027f3dd4bb4c Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Sat, 16 Mar 2013 19:01:10 +0100
Subject: Notify about a new mail only if it's UNSEEN (#1388965)

---
 plugins/newmail_notifier/newmail_notifier.php | 63 ++++++++++++++++-----------
 plugins/newmail_notifier/package.xml          |  6 +--
 program/lib/Roundcube/rcube_imap.php          | 10 +++--
 program/lib/Roundcube/rcube_storage.php       |  5 ++-
 program/steps/mail/check_recent.inc           |  4 +-
 5 files changed, 52 insertions(+), 36 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/plugins/newmail_notifier/newmail_notifier.php b/plugins/newmail_notifier/newmail_notifier.php
index e985c8b49..912ff4f66 100644
--- a/plugins/newmail_notifier/newmail_notifier.php
+++ b/plugins/newmail_notifier/newmail_notifier.php
@@ -35,6 +35,9 @@ class newmail_notifier extends rcube_plugin
 
     private $rc;
     private $notified;
+    private $opt = array();
+    private $exceptions = array();
+
 
     /**
      * Plugin initialization
@@ -49,13 +52,34 @@ class newmail_notifier extends rcube_plugin
             $this->add_hook('preferences_save', array($this, 'prefs_save'));
         }
         else { // if ($this->rc->task == 'mail') {
-            $this->add_hook('new_messages', array($this, 'notify'));
             // add script when not in ajax and not in frame
             if ($this->rc->output->type == 'html' && empty($_REQUEST['_framed'])) {
                 $this->add_texts('localization/');
                 $this->rc->output->add_label('newmail_notifier.title', 'newmail_notifier.body');
                 $this->include_script('newmail_notifier.js');
             }
+
+            if ($this->rc->action == 'refresh') {
+                // Load configuration
+                $this->load_config();
+
+                $this->opt['basic']   = $this->rc->config->get('newmail_notifier_basic');
+                $this->opt['sound']   = $this->rc->config->get('newmail_notifier_sound');
+                $this->opt['desktop'] = $this->rc->config->get('newmail_notifier_desktop');
+
+                if (!empty($this->opt)) {
+                    // Get folders to skip checking for
+                    $exceptions = array('drafts_mbox', 'sent_mbox', 'trash_mbox');
+                    foreach ($exceptions as $folder) {
+                        $folder = $this->rc->config->get($folder);
+                        if (strlen($folder) && $folder != 'INBOX') {
+                            $this->exceptions[] = $folder;
+                        }
+                    }
+
+                    $this->add_hook('new_messages', array($this, 'notify'));
+                }
+             }
         }
     }
 
@@ -132,43 +156,30 @@ class newmail_notifier extends rcube_plugin
      */
     function notify($args)
     {
-        // Already notified or non-automatic check
-        if ($this->notified || !empty($_GET['_refresh'])) {
+        // Already notified or unexpected input
+        if ($this->notified || empty($args['diff']['new'])) {
             return $args;
         }
 
-        // Get folders to skip checking for
-        if (empty($this->exceptions)) {
-            $this->delimiter = $this->rc->storage->get_hierarchy_delimiter();
-
-            $exceptions = array('drafts_mbox', 'sent_mbox', 'trash_mbox');
-            foreach ($exceptions as $folder) {
-                $folder = $this->rc->config->get($folder);
-                if (strlen($folder) && $folder != 'INBOX') {
-                    $this->exceptions[] = $folder;
-                }
-            }
-        }
-
-        $mbox = $args['mailbox'];
+        $mbox      = $args['mailbox'];
+        $storage   = $this->rc->get_storage();
+        $delimiter = $storage->get_hierarchy_delimiter();
 
         // Skip exception (sent/drafts) folders (and their subfolders)
         foreach ($this->exceptions as $folder) {
-            if (strpos($mbox.$this->delimiter, $folder.$this->delimiter) === 0) {
+            if (strpos($mbox.$delimiter, $folder.$delimiter) === 0) {
                 return $args;
             }
         }
 
-        $this->notified = true;
-
-        // Load configuration
-        $this->load_config();
+        // Check if any of new messages is UNSEEN
+        $deleted = $this->rc->config->get('skip_deleted') ? 'UNDELETED ' : '';
+        $search  = $deleted . 'UNSEEN UID ' . $args['diff']['new'];
+        $unseen  = $storage->search_once($mbox, $search);
 
-        $basic   = $this->rc->config->get('newmail_notifier_basic');
-        $sound   = $this->rc->config->get('newmail_notifier_sound');
-        $desktop = $this->rc->config->get('newmail_notifier_desktop');
+        if ($unseen->count()) {
+            $this->notified = true;
 
-        if ($basic || $sound || $desktop) {
             $this->rc->output->command('plugin.newmail_notifier',
                 array('basic' => $basic, 'sound' => $sound, 'desktop' => $desktop));
         }
diff --git a/plugins/newmail_notifier/package.xml b/plugins/newmail_notifier/package.xml
index d3de25fb3..ea0fcd9c1 100644
--- a/plugins/newmail_notifier/package.xml
+++ b/plugins/newmail_notifier/package.xml
@@ -19,10 +19,10 @@
 		<email>alec@alec.pl</email>
 		<active>yes</active>
 	</lead>
-	<date>2012-02-07</date>
+	<date>2013-03-16</date>
 	<version>
-		<release>0.4</release>
-		<api>0.3</api>
+		<release>0.5</release>
+		<api>0.5</api>
 	</version>
 	<stability>
 		<release>stable</release>
diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php
index 8d9c37576..0aa059c26 100644
--- a/program/lib/Roundcube/rcube_imap.php
+++ b/program/lib/Roundcube/rcube_imap.php
@@ -1096,16 +1096,17 @@ class rcube_imap extends rcube_storage
 
 
     /**
-     * Returns current status of folder
+     * Returns current status of a folder (compared to the last time use)
      *
      * We compare the maximum UID to determine the number of
      * new messages because the RECENT flag is not reliable.
      *
      * @param string $folder Folder name
+     * @param array  $diff   Difference data
      *
-     * @return int   Folder status
+     * @return int Folder status
      */
-    public function folder_status($folder = null)
+    public function folder_status($folder = null, &$diff = array())
     {
         if (!strlen($folder)) {
             $folder = $this->folder;
@@ -1126,6 +1127,9 @@ class rcube_imap extends rcube_storage
         // got new messages
         if ($new['maxuid'] > $old['maxuid']) {
             $result += 1;
+            // get new message UIDs range, that can be used for example
+            // to get the data of these messages
+            $diff['new'] = ($old['maxuid'] + 1 < $new['maxuid'] ? ($old['maxuid']+1).':' : '') . $new['maxuid'];
         }
         // some messages has been deleted
         if ($new['cnt'] < $old['cnt']) {
diff --git a/program/lib/Roundcube/rcube_storage.php b/program/lib/Roundcube/rcube_storage.php
index 8a36f1f9d..700d12ffb 100644
--- a/program/lib/Roundcube/rcube_storage.php
+++ b/program/lib/Roundcube/rcube_storage.php
@@ -807,13 +807,14 @@ abstract class rcube_storage
 
 
     /**
-     * Returns current status of a folder
+     * Returns current status of a folder (compared to the last time use)
      *
      * @param string $folder Folder name
+     * @param array  $diff   Difference data
      *
      * @return int Folder status
      */
-    abstract function folder_status($folder = null);
+    abstract function folder_status($folder = null, &$diff = array());
 
 
     /**
diff --git a/program/steps/mail/check_recent.inc b/program/steps/mail/check_recent.inc
index d3c14a38d..3649d148c 100644
--- a/program/steps/mail/check_recent.inc
+++ b/program/steps/mail/check_recent.inc
@@ -52,12 +52,12 @@ foreach ($a_mailboxes as $mbox_name) {
     }
 
     // Get mailbox status
-    $status = $RCMAIL->storage->folder_status($mbox_name);
+    $status = $RCMAIL->storage->folder_status($mbox_name, $diff);
 
     if ($status & 1) {
         // trigger plugin hook
         $RCMAIL->plugins->exec_hook('new_messages',
-            array('mailbox' => $mbox_name, 'is_current' => $is_current));
+            array('mailbox' => $mbox_name, 'is_current' => $is_current, 'diff' => $diff));
     }
 
     rcmail_send_unread_count($mbox_name, true, null,
-- 
cgit v1.2.3


From d8270b66ccca4aef0db76bade89a398b1d33abe9 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Mon, 18 Mar 2013 19:51:32 +0100
Subject: Fix wrapping of text lines with the same length as specified length
 limit

---
 program/lib/Roundcube/rcube_mime.php | 7 ++++---
 tests/src/format-flowed.txt          | 4 ++--
 2 files changed, 6 insertions(+), 5 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_mime.php b/program/lib/Roundcube/rcube_mime.php
index 2f24a1bb3..d21e3b4d5 100644
--- a/program/lib/Roundcube/rcube_mime.php
+++ b/program/lib/Roundcube/rcube_mime.php
@@ -595,11 +595,12 @@ class rcube_mime
             while (count($list)) {
                 $line   = array_shift($list);
                 $l      = mb_strlen($line);
-                $newlen = $len + $l + ($len ? 1 : 0);
+                $space  = $len ? 1 : 0;
+                $newlen = $len + $l + $space;
 
                 if ($newlen <= $width) {
-                    $string .= ($len ? ' ' : '').$line;
-                    $len += (1 + $l);
+                    $string .= ($space ? ' ' : '').$line;
+                    $len += ($space + $l);
                 }
                 else {
                     if ($l > $width) {
diff --git a/tests/src/format-flowed.txt b/tests/src/format-flowed.txt
index a390ffd11..359a41aec 100644
--- a/tests/src/format-flowed.txt
+++ b/tests/src/format-flowed.txt
@@ -1,7 +1,7 @@
 I'm replying on this with a very long line which is then wrapped and 
 space-stuffed because the draft is saved as format=flowed.
- From what's specified in RFC 2646 some lines need to be space-stuffed to 
-avoid muning during transport.
+ From what's specified in RFC 2646 some lines need to be space-stuffed to avoid 
+muning during transport.
 
 X
 
-- 
cgit v1.2.3


From 1e32540839683c1309db012c4d5b9aff35ec6ae3 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Tue, 19 Mar 2013 12:47:07 +0100
Subject: Add rel="noreferrer" for links in displayed messages (#1484686)

---
 CHANGELOG                                       |  1 +
 program/lib/Roundcube/rcube_string_replacer.php | 13 ++++++++-----
 program/steps/mail/func.inc                     | 14 ++++++++++----
 tests/Framework/StringReplacer.php              | 22 +++++++++++-----------
 tests/MailFunc.php                              |  8 ++++----
 5 files changed, 34 insertions(+), 24 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/CHANGELOG b/CHANGELOG
index 1e3eb77b5..ed0ea6ef3 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
 CHANGELOG Roundcube Webmail
 ===========================
 
+- Add rel="noreferrer" for links in displayed messages (#1484686)
 - Fix so forward as attachment works if additional attachment is added by message_compose hook (#1489000)
 - Add ability to toggle between HTML and text while viewing a message (#1486939)
 - Better handling of session errors in ajax requests (#1488960)
diff --git a/program/lib/Roundcube/rcube_string_replacer.php b/program/lib/Roundcube/rcube_string_replacer.php
index 49a378166..b8768bc98 100644
--- a/program/lib/Roundcube/rcube_string_replacer.php
+++ b/program/lib/Roundcube/rcube_string_replacer.php
@@ -28,9 +28,10 @@ class rcube_string_replacer
     public $mailto_pattern;
     public $link_pattern;
     private $values = array();
+    private $options = array();
 
 
-    function __construct()
+    function __construct($options = array())
     {
         // Simplified domain expression for UTF8 characters handling
         // Support unicode/punycode in top-level domain part
@@ -44,6 +45,8 @@ class rcube_string_replacer
             ."@$utf_domain"                                                 // domain-part
             ."(\?[$url1$url2]+)?"                                           // e.g. ?subject=test...
             .")/";
+
+        $this->options = $options;
     }
 
     /**
@@ -89,10 +92,10 @@ class rcube_string_replacer
 
         if ($url) {
             $suffix = $this->parse_url_brackets($url);
-            $i = $this->add($prefix . html::a(array(
-                'href'   => $url_prefix . $url,
-                'target' => '_blank'
-            ), rcube::Q($url)) . $suffix);
+            $attrib = (array)$this->options['link_attribs'];
+            $attrib['href'] = $url_prefix . $url;
+
+            $i = $this->add($prefix . html::a($attrib, rcube::Q($url)) . $suffix);
         }
 
         // Return valid link for recognized schemes, otherwise
diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc
index 8c9743949..274c40b5c 100644
--- a/program/steps/mail/func.inc
+++ b/program/steps/mail/func.inc
@@ -760,7 +760,8 @@ function rcmail_plain_body($body, $flowed=false)
   global $RCMAIL;
 
   // make links and email-addresses clickable
-  $replacer = new rcmail_string_replacer;
+  $attribs  = array('link_attribs' => array('rel' => 'noreferrer', 'target' => '_blank'));
+  $replacer = new rcmail_string_replacer($attribs);
 
   // search for patterns like links and e-mail addresses and replace with tokens
   $body = $replacer->replace($body);
@@ -1373,7 +1374,7 @@ function rcmail_html4inline($body, $container_id, $body_id='', &$attributes=null
 
 
 /**
- * parse link attributes and set correct target
+ * parse link (a, link, area) attributes and set correct target
  */
 function rcmail_alter_html_link($matches)
 {
@@ -1382,9 +1383,9 @@ function rcmail_alter_html_link($matches)
   // Support unicode/punycode in top-level domain part
   $EMAIL_PATTERN = '([a-z0-9][a-z0-9\-\.\+\_]*@[^&@"\'.][^@&"\']*\\.([^\\x00-\\x40\\x5b-\\x60\\x7b-\\x7f]{2,}|xn--[a-z0-9]{2,}))';
 
-  $tag = $matches[1];
+  $tag    = strtolower($matches[1]);
   $attrib = parse_attrib_string($matches[2]);
-  $end = '>';
+  $end    = '>';
 
   // Remove non-printable characters in URL (#1487805)
   if ($attrib['href'])
@@ -1411,6 +1412,11 @@ function rcmail_alter_html_link($matches)
     $attrib['target'] = '_blank';
   }
 
+  // Better security by adding rel="noreferrer" (#1484686)
+  if (($tag == 'a' || $tag == 'area') && $attrib['href'] && $attrib['href'][0] != '#') {
+    $attrib['rel'] = 'noreferrer';
+  }
+
   // allowed attributes for a|link|area tags
   $allow = array('href','name','target','onclick','id','class','style','title',
     'rel','type','media','alt','coords','nohref','hreflang','shape');
diff --git a/tests/Framework/StringReplacer.php b/tests/Framework/StringReplacer.php
index e630ebac0..95c59221b 100644
--- a/tests/Framework/StringReplacer.php
+++ b/tests/Framework/StringReplacer.php
@@ -24,17 +24,17 @@ class Framework_StringReplacer extends PHPUnit_Framework_TestCase
     function data_replace()
     {
         return array(
-            array('http://domain.tld/path*path2', '<a href="http://domain.tld/path*path2" target="_blank">http://domain.tld/path*path2</a>'),
-            array("Click this link:\nhttps://mail.xn--brderli-o2a.ch/rc/ EOF", "Click this link:\n<a href=\"https://mail.xn--brderli-o2a.ch/rc/\" target=\"_blank\">https://mail.xn--brderli-o2a.ch/rc/</a> EOF"),
-            array('Start http://localhost/?foo End', 'Start <a href="http://localhost/?foo" target="_blank">http://localhost/?foo</a> End'),
-            array('www.domain.tld', '<a href="http://www.domain.tld" target="_blank">www.domain.tld</a>'),
-            array('WWW.DOMAIN.TLD', '<a href="http://WWW.DOMAIN.TLD" target="_blank">WWW.DOMAIN.TLD</a>'),
-            array('[http://link.com]', '[<a href="http://link.com" target="_blank">http://link.com</a>]'),
-            array('http://link.com?a[]=1', '<a href="http://link.com?a[]=1" target="_blank">http://link.com?a[]=1</a>'),
-            array('http://link.com?a[]', '<a href="http://link.com?a[]" target="_blank">http://link.com?a[]</a>'),
-            array('(http://link.com)', '(<a href="http://link.com" target="_blank">http://link.com</a>)'),
-            array('http://link.com?a(b)c', '<a href="http://link.com?a(b)c" target="_blank">http://link.com?a(b)c</a>'),
-            array('http://link.com?(link)', '<a href="http://link.com?(link)" target="_blank">http://link.com?(link)</a>'),
+            array('http://domain.tld/path*path2', '<a href="http://domain.tld/path*path2">http://domain.tld/path*path2</a>'),
+            array("Click this link:\nhttps://mail.xn--brderli-o2a.ch/rc/ EOF", "Click this link:\n<a href=\"https://mail.xn--brderli-o2a.ch/rc/\">https://mail.xn--brderli-o2a.ch/rc/</a> EOF"),
+            array('Start http://localhost/?foo End', 'Start <a href="http://localhost/?foo">http://localhost/?foo</a> End'),
+            array('www.domain.tld', '<a href="http://www.domain.tld">www.domain.tld</a>'),
+            array('WWW.DOMAIN.TLD', '<a href="http://WWW.DOMAIN.TLD">WWW.DOMAIN.TLD</a>'),
+            array('[http://link.com]', '[<a href="http://link.com">http://link.com</a>]'),
+            array('http://link.com?a[]=1', '<a href="http://link.com?a[]=1">http://link.com?a[]=1</a>'),
+            array('http://link.com?a[]', '<a href="http://link.com?a[]">http://link.com?a[]</a>'),
+            array('(http://link.com)', '(<a href="http://link.com">http://link.com</a>)'),
+            array('http://link.com?a(b)c', '<a href="http://link.com?a(b)c">http://link.com?a(b)c</a>'),
+            array('http://link.com?(link)', '<a href="http://link.com?(link)">http://link.com?(link)</a>'),
             array('http://<test>', 'http://<test>'),
             array('http://', 'http://'),
         );
diff --git a/tests/MailFunc.php b/tests/MailFunc.php
index 38c0bac30..319075abd 100644
--- a/tests/MailFunc.php
+++ b/tests/MailFunc.php
@@ -54,7 +54,7 @@ class MailFunc extends PHPUnit_Framework_TestCase
         $this->assertNotRegExp('/<form [^>]+>/', $html, "No form tags allowed");
         $this->assertRegExp('/Subscription form/', $html, "Include <form> contents");
         $this->assertRegExp('/<!-- link ignored -->/', $html, "No external links allowed");
-        $this->assertRegExp('/<a[^>]+ target="_blank">/', $html, "Set target to _blank");
+        $this->assertRegExp('/<a[^>]+ target="_blank"/', $html, "Set target to _blank");
         $this->assertTrue($GLOBALS['REMOTE_OBJECTS'], "Remote object detected");
 
         // render HTML in safe mode
@@ -133,8 +133,8 @@ class MailFunc extends PHPUnit_Framework_TestCase
         $html = rcmail_print_body($part, array('safe' => true));
 
         $this->assertRegExp('/<a href="mailto:nobody@roundcube.net" onclick="return rcmail.command\(\'compose\',\'nobody@roundcube.net\',this\)">nobody@roundcube.net<\/a>/', $html, "Mailto links with onclick");
-        $this->assertRegExp('#<a href="http://www.apple.com/legal/privacy" target="_blank">http://www.apple.com/legal/privacy</a>#', $html, "Links with target=_blank");
-        $this->assertRegExp('#\\[<a href="http://example.com/\\?tx\\[a\\]=5" target="_blank">http://example.com/\\?tx\\[a\\]=5</a>\\]#', $html, "Links with square brackets");
+        $this->assertRegExp('#<a rel="noreferrer" target="_blank" href="http://www.apple.com/legal/privacy">http://www.apple.com/legal/privacy</a>#', $html, "Links with target=_blank");
+        $this->assertRegExp('#\\[<a rel="noreferrer" target="_blank" href="http://example.com/\\?tx\\[a\\]=5">http://example.com/\\?tx\\[a\\]=5</a>\\]#', $html, "Links with square brackets");
     }
 
     /**
@@ -148,7 +148,7 @@ class MailFunc extends PHPUnit_Framework_TestCase
         $html = rcmail_html4inline(rcmail_print_body($part, array('safe' => false)), 'foo');
 
         $mailto = '<a href="mailto:me@me.com?subject=this is the subject&amp;body=this is the body"'
-            .' onclick="return rcmail.command(\'compose\',\'me@me.com?subject=this is the subject&amp;body=this is the body\',this)">e-mail</a>';
+            .' onclick="return rcmail.command(\'compose\',\'me@me.com?subject=this is the subject&amp;body=this is the body\',this)" rel="noreferrer">e-mail</a>';
 
         $this->assertRegExp('|'.preg_quote($mailto, '|').'|', $html, "Extended mailto links");
     }
-- 
cgit v1.2.3


From 02c9c931fe34c699ded288b449c6d2d457a41a76 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Tue, 19 Mar 2013 13:53:49 +0100
Subject: Make mime.types common locations list OS-aware

---
 program/lib/Roundcube/rcube_mime.php | 24 +++++++++++++++---------
 1 file changed, 15 insertions(+), 9 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_mime.php b/program/lib/Roundcube/rcube_mime.php
index d21e3b4d5..ac4be95c0 100644
--- a/program/lib/Roundcube/rcube_mime.php
+++ b/program/lib/Roundcube/rcube_mime.php
@@ -718,21 +718,27 @@ class rcube_mime
         // load mapping file
         $file_paths = array();
 
-        if ($mime_types = rcube::get_instance()->config->get('mime_types'))
+        if ($mime_types = rcube::get_instance()->config->get('mime_types')) {
             $file_paths[] = $mime_types;
+        }
 
         // try common locations
-        $file_paths[] = '/etc/mime.types';
-        $file_paths[] = '/etc/httpd/mime.types';
-        $file_paths[] = '/etc/httpd2/mime.types';
-        $file_paths[] = '/etc/apache/mime.types';
-        $file_paths[] = '/etc/apache2/mime.types';
-        $file_paths[] = '/usr/local/etc/httpd/conf/mime.types';
-        $file_paths[] = '/usr/local/etc/apache/conf/mime.types';
+        if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') {
+            $file_paths[] = 'C:/xampp/apache/conf/mime.types.';
+        }
+        else {
+            $file_paths[] = '/etc/mime.types';
+            $file_paths[] = '/etc/httpd/mime.types';
+            $file_paths[] = '/etc/httpd2/mime.types';
+            $file_paths[] = '/etc/apache/mime.types';
+            $file_paths[] = '/etc/apache2/mime.types';
+            $file_paths[] = '/usr/local/etc/httpd/conf/mime.types';
+            $file_paths[] = '/usr/local/etc/apache/conf/mime.types';
+        }
 
         foreach ($file_paths as $fp) {
             if (is_readable($fp)) {
-                $lines = file($fp, FILE_IGNORE_NEW_LINES);
+                $lines = file($fp, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
                 break;
             }
         }
-- 
cgit v1.2.3


From 4f693e9daa21185761d38dca9a0ba626be8c05cb Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Wed, 20 Mar 2013 10:31:37 +0100
Subject: Workaround for some versions/systems where finfo_open() with second
 argument doesn't do the same as with no 2nd argument as it should

---
 program/lib/Roundcube/rcube_mime.php | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_mime.php b/program/lib/Roundcube/rcube_mime.php
index ac4be95c0..b70d681c9 100644
--- a/program/lib/Roundcube/rcube_mime.php
+++ b/program/lib/Roundcube/rcube_mime.php
@@ -672,7 +672,16 @@ class rcube_mime
 
         // try fileinfo extension if available
         if (!$mime_type && function_exists('finfo_open')) {
-            if ($finfo = finfo_open(FILEINFO_MIME, $mime_magic)) {
+            // null as a 2nd argument should be the same as no argument
+            // this however is not true on all systems/versions
+            if ($mime_magic) {
+                $finfo = finfo_open(FILEINFO_MIME, $mime_magic);
+            }
+            else {
+                $finfo = finfo_open(FILEINFO_MIME);
+            }
+
+            if ($finfo) {
                 if ($is_stream)
                     $mime_type = finfo_buffer($finfo, $path);
                 else
-- 
cgit v1.2.3


From 1bce1420589c62176dd34bb5fc0fab206a25d41e Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Fri, 22 Mar 2013 09:35:47 +0100
Subject: Fix handling of some conditional comment tags in HTML message
 (#1489004)

---
 CHANGELOG                               | 1 +
 program/lib/Roundcube/rcube_washtml.php | 3 ++-
 2 files changed, 3 insertions(+), 1 deletion(-)

(limited to 'program/lib/Roundcube')

diff --git a/CHANGELOG b/CHANGELOG
index ed0ea6ef3..541069004 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
 CHANGELOG Roundcube Webmail
 ===========================
 
+- Fix handling of some conditional comment tags in HTML message (#1489004)
 - Add rel="noreferrer" for links in displayed messages (#1484686)
 - Fix so forward as attachment works if additional attachment is added by message_compose hook (#1489000)
 - Add ability to toggle between HTML and text while viewing a message (#1486939)
diff --git a/program/lib/Roundcube/rcube_washtml.php b/program/lib/Roundcube/rcube_washtml.php
index 2a261419f..27dff9f48 100644
--- a/program/lib/Roundcube/rcube_washtml.php
+++ b/program/lib/Roundcube/rcube_washtml.php
@@ -413,7 +413,8 @@ class rcube_washtml
 
         // Remove invalid HTML comments (#1487759)
         // Don't remove valid conditional comments
-        $html = preg_replace('/<!--[^->[\n]*>/', '', $html);
+        // Don't remove MSOutlook (<!-->) conditional comments (#1489004)
+        $html = preg_replace('/<!--[^->\[\n]+>/', '', $html);
 
         // turn relative into absolute urls
         $html = self::resolve_base($html);
-- 
cgit v1.2.3


From 3d525ffaf5b36fc74c5a9fa9ed317bcfdd32e19a Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Fri, 22 Mar 2013 11:34:24 +0100
Subject: Bump up also RCUBE_VERSION

---
 program/lib/Roundcube/bootstrap.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/bootstrap.php b/program/lib/Roundcube/bootstrap.php
index 8cea48122..0640a9448 100644
--- a/program/lib/Roundcube/bootstrap.php
+++ b/program/lib/Roundcube/bootstrap.php
@@ -45,7 +45,7 @@ foreach ($config as $optname => $optval) {
 }
 
 // framework constants
-define('RCUBE_VERSION', '0.9-git');
+define('RCUBE_VERSION', '1.0-git');
 define('RCUBE_CHARSET', 'UTF-8');
 
 if (!defined('RCUBE_LIB_DIR')) {
-- 
cgit v1.2.3


From a85d54e1e801b07a152a717fbfca08c8eadad201 Mon Sep 17 00:00:00 2001
From: Thomas Bruederli <thomas@roundcube.net>
Date: Sat, 23 Mar 2013 17:57:58 +0100
Subject: Hack to reset PDO statement iterators after counting

---
 program/lib/Roundcube/rcube_db.php | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_db.php b/program/lib/Roundcube/rcube_db.php
index 49bbe5c6e..ec61cb6b3 100644
--- a/program/lib/Roundcube/rcube_db.php
+++ b/program/lib/Roundcube/rcube_db.php
@@ -444,6 +444,7 @@ class rcube_db
      *
      * @param mixed $result Optional query handle
      * @return mixed   Number of rows or false on failure
+     * @deprecated This method shows very poor performance and should be avoided.
      */
     public function num_rows($result = null)
     {
@@ -454,7 +455,9 @@ class rcube_db
                 return $query ? intval($query->fetchColumn(0)) : false;
             }
             else {
-                return count($result->fetchAll());
+                $num = count($result->fetchAll());
+                $result->execute();  // re-execute query because there's no seek(0)
+                return $num;
             }
         }
 
-- 
cgit v1.2.3


From 99cfba2e26a6a05190d45c287e7613485bd6833d Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Sun, 24 Mar 2013 09:06:29 +0100
Subject: Add some extension/mimetype aliases to fix some quirks in attachment
 type validation (#1488891)

---
 program/lib/Roundcube/rcube_mime.php | 30 +++++++++++++++++++++++++++---
 1 file changed, 27 insertions(+), 3 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_mime.php b/program/lib/Roundcube/rcube_mime.php
index b70d681c9..7cd520752 100644
--- a/program/lib/Roundcube/rcube_mime.php
+++ b/program/lib/Roundcube/rcube_mime.php
@@ -769,11 +769,35 @@ class rcube_mime
 
         // fallback to some well-known types most important for daily emails
         if (empty($mime_types)) {
-            $mime_extensions = @include(RCUBE_CONFIG_DIR . '/mimetypes.php');
-            $mime_extensions += array('gif' => 'image/gif', 'png' => 'image/png', 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'tif' => 'image/tiff');
+            $mime_extensions = (array) @include(RCUBE_CONFIG_DIR . '/mimetypes.php');
 
-            foreach ($mime_extensions as $ext => $mime)
+            foreach ($mime_extensions as $ext => $mime) {
                 $mime_types[$mime][] = $ext;
+            }
+        }
+
+        // Add some known aliases that aren't included by some mime.types (#1488891)
+        // the order is important here so standard extensions have higher prio
+        $aliases = array(
+            'image/gif'      => array('gif'),
+            'image/png'      => array('png'),
+            'image/x-png'    => array('png'),
+            'image/jpeg'     => array('jpg', 'jpeg', 'jpe'),
+            'image/jpg'      => array('jpg', 'jpeg', 'jpe'),
+            'image/pjpeg'    => array('jpg', 'jpeg', 'jpe'),
+            'image/tiff'     => array('tif'),
+            'message/rfc822' => array('eml'),
+            'text/x-mail'    => array('eml'),
+        );
+
+        foreach ($aliases as $mime => $exts) {
+            $mime_types[$mime] = array_unique(array_merge((array) $mime_types[$mime], $exts));
+
+            foreach ($exts as $ext) {
+                if (!isset($mime_extensions[$ext])) {
+                    $mime_extensions[$ext] = $mime;
+                }
+            }
         }
 
         return $mimetype ? $mime_types[$mimetype] : $mime_extensions;
-- 
cgit v1.2.3


From 7889c57b772dbf722639894bd572c767424c8b84 Mon Sep 17 00:00:00 2001
From: Thomas Bruederli <thomas@roundcube.net>
Date: Sun, 24 Mar 2013 12:03:16 +0100
Subject: Match regex on multi-line sql statements

---
 program/lib/Roundcube/rcube_db.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_db.php b/program/lib/Roundcube/rcube_db.php
index ec61cb6b3..4e6684c51 100644
--- a/program/lib/Roundcube/rcube_db.php
+++ b/program/lib/Roundcube/rcube_db.php
@@ -450,7 +450,7 @@ class rcube_db
     {
         if ($result || ($result === null && ($result = $this->last_result))) {
             // repeat query with SELECT COUNT(*) ...
-            if (preg_match('/^SELECT\s+(?:ALL\s+|DISTINCT\s+)?(?:.*?)\s+FROM\s+(.*)$/i', $result->queryString, $m)) {
+            if (preg_match('/^SELECT\s+(?:ALL\s+|DISTINCT\s+)?(?:.*?)\s+FROM\s+(.*)$/ims', $result->queryString, $m)) {
                 $query = $this->dbh->query('SELECT COUNT(*) FROM ' . $m[1], PDO::FETCH_NUM);
                 return $query ? intval($query->fetchColumn(0)) : false;
             }
-- 
cgit v1.2.3


From 38c1951266b1fda074340be4a8eb840a60f9ac11 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Sun, 24 Mar 2013 18:00:17 +0100
Subject: Be less restrictive on vCard import, do not require FN when N exists

---
 program/lib/Roundcube/rcube_vcard.php |  4 +++-
 program/steps/addressbook/import.inc  | 11 ++++++++++-
 2 files changed, 13 insertions(+), 2 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_vcard.php b/program/lib/Roundcube/rcube_vcard.php
index de28767f8..54bb9521d 100644
--- a/program/lib/Roundcube/rcube_vcard.php
+++ b/program/lib/Roundcube/rcube_vcard.php
@@ -491,7 +491,9 @@ class rcube_vcard
             if (preg_match('/^END:VCARD$/i', $line)) {
                 // parse vcard
                 $obj = new rcube_vcard(self::cleanup($vcard_block), $charset, true, self::$fieldmap);
-                if (!empty($obj->displayname) || !empty($obj->email)) {
+                // FN and N is required by vCard format (RFC 2426)
+                // on import we can be less restrictive, let's addressbook decide
+                if (!empty($obj->displayname) || !empty($obj->surname) || !empty($obj->firstname) || !empty($obj->email)) {
                     $out[] = $obj;
                 }
 
diff --git a/program/steps/addressbook/import.inc b/program/steps/addressbook/import.inc
index df07d64bc..72da15078 100644
--- a/program/steps/addressbook/import.inc
+++ b/program/steps/addressbook/import.inc
@@ -209,6 +209,15 @@ if (is_array($_FILES['_file'])) {
         foreach ($vcards as $vcard) {
             $a_record = $vcard->get_assoc();
 
+            // Generate contact's display name (must be before validation), the same we do in save.inc
+            if (empty($a_record['name'])) {
+                $a_record['name'] = rcube_addressbook::compose_display_name($a_record, true);
+                // Reset it if equals to email address (from compose_display_name())
+                if ($a_record['name'] == $a_record['email'][0]) {
+                    $a_record['name'] = '';
+                }
+            }
+
             // skip invalid (incomplete) entries
             if (!$CONTACTS->validate($a_record, true)) {
                 $IMPORT_STATS->invalid++;
@@ -250,7 +259,7 @@ if (is_array($_FILES['_file'])) {
 
             if ($success) {
                 $IMPORT_STATS->inserted++;
-                $IMPORT_STATS->names[] = $vcard->displayname ? $vcard->displayname : $email;
+                $IMPORT_STATS->names[] = $a_record['name'] ? $a_record['name'] : $email;
             }
             else {
                 $IMPORT_STATS->errors++;
-- 
cgit v1.2.3


From 4034a79beb56756d10157635acfa0a71e75c7017 Mon Sep 17 00:00:00 2001
From: Thomas Bruederli <thomas@roundcube.net>
Date: Mon, 25 Mar 2013 09:05:26 +0100
Subject: Check for exact matching session keys before splitting into path
 segments. Adds backwards-compatibility after commit f0a7159c

---
 program/lib/Roundcube/rcube_session.php | 26 ++++++++++++++++++--------
 1 file changed, 18 insertions(+), 8 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_session.php b/program/lib/Roundcube/rcube_session.php
index 82ff8a804..4282c0cbb 100644
--- a/program/lib/Roundcube/rcube_session.php
+++ b/program/lib/Roundcube/rcube_session.php
@@ -203,10 +203,15 @@ class rcube_session
             if (is_array($a_oldvars)) {
                 // remove unset keys on oldvars
                 foreach ((array)$this->unsets as $var) {
-                    $path = explode('.', $var);
-                    $k = array_pop($path);
-                    $node = &$this->get_node($path, $a_oldvars);
-                    unset($node[$k]);
+                    if (isset($a_oldvars[$k])) {
+                        unset($a_oldvars[$k]);
+                    }
+                    else {
+                        $path = explode('.', $var);
+                        $k = array_pop($path);
+                        $node = &$this->get_node($path, $a_oldvars);
+                        unset($node[$k]);
+                    }
                 }
 
                 $newvars = $this->serialize(array_merge(
@@ -413,10 +418,15 @@ class rcube_session
 
         $this->unsets[] = $var;
 
-        $path = explode('.', $var);
-        $key = array_pop($path);
-        $node = &$this->get_node($path, $_SESSION);
-        unset($node[$key]);
+        if (isset($_SESSION[$var])) {
+            unset($_SESSION[$var])
+        }
+        else {
+            $path = explode('.', $var);
+            $key = array_pop($path);
+            $node = &$this->get_node($path, $_SESSION);
+            unset($node[$key]);
+        }
 
         return true;
     }
-- 
cgit v1.2.3


From f603883d37e778c8413347d07bb12e4180570aeb Mon Sep 17 00:00:00 2001
From: Thomas Bruederli <thomas@roundcube.net>
Date: Mon, 25 Mar 2013 09:07:29 +0100
Subject: Fix typo

---
 program/lib/Roundcube/rcube_session.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_session.php b/program/lib/Roundcube/rcube_session.php
index 4282c0cbb..059cc1112 100644
--- a/program/lib/Roundcube/rcube_session.php
+++ b/program/lib/Roundcube/rcube_session.php
@@ -419,7 +419,7 @@ class rcube_session
         $this->unsets[] = $var;
 
         if (isset($_SESSION[$var])) {
-            unset($_SESSION[$var])
+            unset($_SESSION[$var]);
         }
         else {
             $path = explode('.', $var);
-- 
cgit v1.2.3


From 39062647473c7ff105fff7e5295ee9c0ca931e32 Mon Sep 17 00:00:00 2001
From: Victor Benincasa <vbenincasa@gmail.com>
Date: Tue, 26 Mar 2013 07:08:58 -0300
Subject: Fix typos

---
 program/lib/Roundcube/rcube_session.php | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_session.php b/program/lib/Roundcube/rcube_session.php
index 059cc1112..dedde2284 100644
--- a/program/lib/Roundcube/rcube_session.php
+++ b/program/lib/Roundcube/rcube_session.php
@@ -203,8 +203,8 @@ class rcube_session
             if (is_array($a_oldvars)) {
                 // remove unset keys on oldvars
                 foreach ((array)$this->unsets as $var) {
-                    if (isset($a_oldvars[$k])) {
-                        unset($a_oldvars[$k]);
+                    if (isset($a_oldvars[$var])) {
+                        unset($a_oldvars[$var]);
                     }
                     else {
                         $path = explode('.', $var);
@@ -407,7 +407,7 @@ class rcube_session
     /**
      * Unset a session variable
      *
-     * @param string Varibale name (can be a path denoting a certain node in the session array, e.g. compose.attachments.5)
+     * @param string Variable name (can be a path denoting a certain node in the session array, e.g. compose.attachments.5)
      * @return boolean True on success
      */
     public function remove($var=null)
-- 
cgit v1.2.3


From 648fcf570964ad512d18d6df7e07d5bcec2ae830 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Wed, 27 Mar 2013 16:32:51 +0100
Subject: Whitelist configuration options (user preferences) that can be
 changed using save-pref command

---
 program/lib/Roundcube/rcube_plugin.php     |  8 ++++++++
 program/lib/Roundcube/rcube_plugin_api.php |  6 ++++++
 program/steps/utils/save_pref.inc          | 16 ++++++++++++++++
 3 files changed, 30 insertions(+)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/rcube_plugin.php b/program/lib/Roundcube/rcube_plugin.php
index 9ea0f73d3..167a9eb4f 100644
--- a/program/lib/Roundcube/rcube_plugin.php
+++ b/program/lib/Roundcube/rcube_plugin.php
@@ -60,6 +60,14 @@ abstract class rcube_plugin
      */
     public $noframe = false;
 
+    /**
+     * A list of config option names that can be modified
+     * by the user via user interface (with save-prefs command)
+     *
+     * @var array
+     */
+    public $allowed_prefs;
+
     protected $home;
     protected $urlbase;
     private $mytask;
diff --git a/program/lib/Roundcube/rcube_plugin_api.php b/program/lib/Roundcube/rcube_plugin_api.php
index 111c177d9..a89f14712 100644
--- a/program/lib/Roundcube/rcube_plugin_api.php
+++ b/program/lib/Roundcube/rcube_plugin_api.php
@@ -36,6 +36,7 @@ class rcube_plugin_api
     public $task = '';
     public $output;
     public $handlers = array();
+    public $allowed_prefs = array();
 
     protected $plugins = array();
     protected $tasks = array();
@@ -202,6 +203,11 @@ class rcube_plugin_api
                         $plugin->init();
                         $this->plugins[$plugin_name] = $plugin;
                     }
+
+                    if (!empty($plugin->allowed_prefs)) {
+                        $this->allowed_prefs = array_merge($this->allowed_prefs, $plugin->allowed_prefs);
+                    }
+
                     return true;
                 }
             }
diff --git a/program/steps/utils/save_pref.inc b/program/steps/utils/save_pref.inc
index b550ad7ef..7def8733d 100644
--- a/program/steps/utils/save_pref.inc
+++ b/program/steps/utils/save_pref.inc
@@ -21,6 +21,22 @@
 
 $name = get_input_value('_name', RCUBE_INPUT_POST);
 $value = get_input_value('_value', RCUBE_INPUT_POST);
+$whitelist = array(
+    'preview_pane',
+    'list_cols',
+    'collapsed_folders',
+    'collapsed_abooks',
+);
+
+if (!in_array($name, array_merge($whitelist, $RCMAIL->plugins->allowed_prefs))) {
+    raise_error(array('code' => 500, 'type' => 'php',
+        'file' => __FILE__, 'line' => __LINE__,
+        'message' => sprintf("Hack attempt detected (user: %s)", $RCMAIL->get_user_name())),
+        true, false);
+
+    $OUTPUT->reset();
+    $OUTPUT->send();
+}
 
 // save preference value
 $RCMAIL->user->save_prefs(array($name => $value));
-- 
cgit v1.2.3


From 589083a94c2e5d50914fba99c80c18d730af697a Mon Sep 17 00:00:00 2001
From: Thomas Bruederli <thomas@roundcube.net>
Date: Thu, 28 Mar 2013 17:35:04 +0100
Subject: Skip some irrelevant ini checks in CLI mode

---
 program/lib/Roundcube/bootstrap.php | 13 ++++++++++---
 1 file changed, 10 insertions(+), 3 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/program/lib/Roundcube/bootstrap.php b/program/lib/Roundcube/bootstrap.php
index 0640a9448..929a4ff79 100644
--- a/program/lib/Roundcube/bootstrap.php
+++ b/program/lib/Roundcube/bootstrap.php
@@ -31,12 +31,19 @@ $config = array(
     // critical PHP settings here. Only these, which doesn't provide
     // an error/warning in the logs later. See (#1486307).
     'mbstring.func_overload'  => 0,
-    'suhosin.session.encrypt' => 0,
-    'session.auto_start'      => 0,
-    'file_uploads'            => 1,
     'magic_quotes_runtime'    => 0,
     'magic_quotes_sybase'     => 0, // #1488506
 );
+
+// check these additional ini settings if not called via CLI
+if (php_sapi_name() != 'cli') {
+    $config += array(
+        'suhosin.session.encrypt' => 0,
+        'session.auto_start'      => 0,
+        'file_uploads'            => 1,
+    );
+}
+
 foreach ($config as $optname => $optval) {
     if ($optval != ini_get($optname) && @ini_set($optname, $optval) === false) {
         die("ERROR: Wrong '$optname' option value and it wasn't possible to set it to required value ($optval).\n"
-- 
cgit v1.2.3


From 8e4b49c382817723f4532b39aca06a7d41383f00 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Tue, 2 Apr 2013 12:11:33 +0200
Subject: Fix session issues with use_https=true (#1488986)

---
 CHANGELOG                       | 1 +
 program/lib/Roundcube/rcube.php | 3 ++-
 2 files changed, 3 insertions(+), 1 deletion(-)

(limited to 'program/lib/Roundcube')

diff --git a/CHANGELOG b/CHANGELOG
index fc408f199..5309676ee 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
 CHANGELOG Roundcube Webmail
 ===========================
 
+- Fix session issues with use_https=true (#1488986)
 - Fix blockquote width in sent mail (#1489031)
 - Fix keyboard events on list widgets in Internet Explorer (#1489025)
 - Call resize handler in intervals to prevent lags and double onresize calls in Chrome (#1489005)
diff --git a/program/lib/Roundcube/rcube.php b/program/lib/Roundcube/rcube.php
index 3ae511e1e..77da83d8e 100644
--- a/program/lib/Roundcube/rcube.php
+++ b/program/lib/Roundcube/rcube.php
@@ -405,6 +405,7 @@ class rcube
         $sess_domain = $this->config->get('session_domain');
         $sess_path   = $this->config->get('session_path');
         $lifetime    = $this->config->get('session_lifetime', 0) * 60;
+        $is_secure   = $this->config->get('use_https') || rcube_utils::https_check();
 
         // set session domain
         if ($sess_domain) {
@@ -419,7 +420,7 @@ class rcube
             ini_set('session.gc_maxlifetime', $lifetime * 2);
         }
 
-        ini_set('session.cookie_secure', rcube_utils::https_check());
+        ini_set('session.cookie_secure', $is_secure);
         ini_set('session.name', $sess_name ? $sess_name : 'roundcube_sessid');
         ini_set('session.use_cookies', 1);
         ini_set('session.use_only_cookies', 1);
-- 
cgit v1.2.3


From bd698341c4871bdabd763601583f0e9ff7137a97 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Wed, 3 Apr 2013 09:17:03 +0200
Subject: Remove "HTML message" from attachments list while viewing a message
 in text mode (#1486939)

---
 CHANGELOG                               |  1 +
 program/lib/Roundcube/rcube_message.php | 12 ------------
 2 files changed, 1 insertion(+), 12 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/CHANGELOG b/CHANGELOG
index 5309676ee..659336788 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -7,6 +7,7 @@ CHANGELOG Roundcube Webmail
 - Call resize handler in intervals to prevent lags and double onresize calls in Chrome (#1489005)
 - Add rel="noreferrer" for links in displayed messages (#1484686)
 - Add ability to toggle between HTML and text while viewing a message (#1486939)
+- Remove "HTML message" from attachments list while viewing a message in text mode (#1486939)
 - Support IMAP MOVE extension [RFC 6851]
 - Add attachment menu with Open and Download options (#1488975)
 - Display user-friendly message on IMAP "over quota" errors (#1484164)
diff --git a/program/lib/Roundcube/rcube_message.php b/program/lib/Roundcube/rcube_message.php
index 42d7b9bbe..41a114f7f 100644
--- a/program/lib/Roundcube/rcube_message.php
+++ b/program/lib/Roundcube/rcube_message.php
@@ -486,14 +486,6 @@ class rcube_message
                 $this->parts[] = $c;
             }
 
-            // add html part as attachment
-            if ($html_part !== null && $structure->parts[$html_part] !== $print_part) {
-                $html_part = $structure->parts[$html_part];
-                $html_part->mimetype = 'text/html';
-
-                $this->attachments[] = $html_part;
-            }
-
             // add unsupported/unrecognized parts to attachments list
             if ($attach_part) {
                 $this->attachments[] = $structure->parts[$attach_part];
@@ -578,10 +570,6 @@ class rcube_message
                     if (!empty($mail_part->filename)) {
                         $this->attachments[] = $mail_part;
                     }
-                    // list html part as attachment (here the part is most likely inside a multipart/related part)
-                    else if ($this->parse_alternative && ($secondary_type == 'html' && !$this->opt['prefer_html'])) {
-                        $this->attachments[] = $mail_part;
-                    }
                 }
                 // part message/*
                 else if ($primary_type == 'message') {
-- 
cgit v1.2.3


From 99edf8699a1b79cdb4a2398680f8f4e97292e2f3 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Wed, 3 Apr 2013 16:03:57 +0200
Subject: Fix possible header duplicates when using additional headers
 (#1489033)

---
 CHANGELOG                                    |  1 +
 program/lib/Roundcube/rcube_imap.php         |  3 +-
 program/lib/Roundcube/rcube_imap_generic.php | 45 +++++++++++++++++++++++-----
 3 files changed, 39 insertions(+), 10 deletions(-)

(limited to 'program/lib/Roundcube')

diff --git a/CHANGELOG b/CHANGELOG
index 659336788..c21743dfc 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
 CHANGELOG Roundcube Webmail
 ===========================
 
+- Fix possible header duplicates when using additional headers (#1489033)
 - Fix session issues with use_https=true (#1488986)
 - Fix blockquote width in sent mail (#1489031)
 - Fix keyboard events on list widgets in Internet Explorer (#1489025)
diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php
index 0aa059c26..c67985186 100644
--- a/program/lib/Roundcube/rcube_imap.php
+++ b/program/lib/Roundcube/rcube_imap.php
@@ -3372,7 +3372,6 @@ class rcube_imap extends rcube_storage
     {
         if (!empty($this->options['fetch_headers'])) {
             $headers = explode(' ', $this->options['fetch_headers']);
-            $headers = array_map('strtoupper', $headers);
         }
         else {
             $headers = array();
@@ -3382,7 +3381,7 @@ class rcube_imap extends rcube_storage
             $headers = array_merge($headers, $this->all_headers);
         }
 
-        return implode(' ', array_unique($headers));
+        return $headers;
     }
 
 
diff --git a/program/lib/Roundcube/rcube_imap_generic.php b/program/lib/Roundcube/rcube_imap_generic.php
index 2ac1355fd..04dc594ae 100644
--- a/program/lib/Roundcube/rcube_imap_generic.php
+++ b/program/lib/Roundcube/rcube_imap_generic.php
@@ -2265,24 +2265,53 @@ class rcube_imap_generic
         return $result;
     }
 
-    function fetchHeaders($mailbox, $message_set, $is_uid = false, $bodystr = false, $add = '')
+    /**
+     * Returns message(s) data (flags, headers, etc.)
+     *
+     * @param string $mailbox     Mailbox name
+     * @param mixed  $message_set Message(s) sequence identifier(s) or UID(s)
+     * @param bool   $is_uid      True if $message_set contains UIDs
+     * @param bool   $bodystr     Enable to add BODYSTRUCTURE data to the result
+     * @param array  $add_headers List of additional headers
+     *
+     * @return bool|array List of rcube_message_header elements, False on error
+     */
+    function fetchHeaders($mailbox, $message_set, $is_uid = false, $bodystr = false, $add_headers = array())
     {
         $query_items = array('UID', 'RFC822.SIZE', 'FLAGS', 'INTERNALDATE');
-        if ($bodystr)
+        $headers     = array('DATE', 'FROM', 'TO', 'SUBJECT', 'CONTENT-TYPE', 'CC', 'REPLY-TO',
+            'LIST-POST', 'DISPOSITION-NOTIFICATION-TO', 'X-PRIORITY');
+
+        if (!empty($add_headers)) {
+            $add_headers = array_map('strtoupper', $add_headers);
+            $headers     = array_unique(array_merge($headers, $add_headers));
+        }
+
+        if ($bodystr) {
             $query_items[] = 'BODYSTRUCTURE';
-        $query_items[] = 'BODY.PEEK[HEADER.FIELDS ('
-            . 'DATE FROM TO SUBJECT CONTENT-TYPE CC REPLY-TO LIST-POST DISPOSITION-NOTIFICATION-TO X-PRIORITY'
-            . ($add ? ' ' . trim($add) : '')
-            . ')]';
+        }
+
+        $query_items[] = 'BODY.PEEK[HEADER.FIELDS (' . implode(' ', $headers) . ')]';
 
         $result = $this->fetch($mailbox, $message_set, $is_uid, $query_items);
 
         return $result;
     }
 
-    function fetchHeader($mailbox, $id, $uidfetch=false, $bodystr=false, $add='')
+    /**
+     * Returns message data (flags, headers, etc.)
+     *
+     * @param string $mailbox     Mailbox name
+     * @param int    $id          Message sequence identifier or UID
+     * @param bool   $is_uid      True if $id is an UID
+     * @param bool   $bodystr     Enable to add BODYSTRUCTURE data to the result
+     * @param array  $add_headers List of additional headers
+     *
+     * @return bool|rcube_message_header Message data, False on error
+     */
+    function fetchHeader($mailbox, $id, $is_uid = false, $bodystr = false, $add_headers = array())
     {
-        $a = $this->fetchHeaders($mailbox, $id, $uidfetch, $bodystr, $add);
+        $a = $this->fetchHeaders($mailbox, $id, $is_uid, $bodystr, $add_headers);
         if (is_array($a)) {
             return array_shift($a);
         }
-- 
cgit v1.2.3