From 3412e50b54e3daac8745234e21ab6e72be0ed165 Mon Sep 17 00:00:00 2001
From: Thomas Bruederli <thomas@roundcube.net>
Date: Wed, 04 Jun 2014 11:20:33 -0400
Subject: [PATCH] Fix attachment menu structure and aria-attributes

---
 program/lib/Roundcube/rcube_vcard.php |  104 +++++++++++++++++++++++++++++++++++++--------------
 1 files changed, 75 insertions(+), 29 deletions(-)

diff --git a/program/lib/Roundcube/rcube_vcard.php b/program/lib/Roundcube/rcube_vcard.php
index e6fa5b2..fb8fdd5 100644
--- a/program/lib/Roundcube/rcube_vcard.php
+++ b/program/lib/Roundcube/rcube_vcard.php
@@ -47,6 +47,7 @@
         'manager'     => 'X-MANAGER',
         'spouse'      => 'X-SPOUSE',
         'edit'        => 'X-AB-EDIT',
+        'groups'      => 'CATEGORIES',
     );
     private $typemap = array(
         'IPHONE'   => 'mobile',
@@ -90,7 +91,7 @@
      */
     public function __construct($vcard = null, $charset = RCUBE_CHARSET, $detect = false, $fieldmap = array())
     {
-        if (!empty($fielmap)) {
+        if (!empty($fieldmap)) {
             $this->extend_fieldmap($fieldmap);
         }
 
@@ -147,6 +148,11 @@
             $tmp = $this->email[0];
             $this->email[0] = $this->email[$pref_index];
             $this->email[$pref_index] = $tmp;
+        }
+
+        // fix broken vcards from Outlook that only supply ORG but not the required N or FN properties
+        if (!strlen(trim($this->displayname . $this->surname . $this->firstname)) && strlen($this->organization)) {
+            $this->displayname = $this->organization;
         }
     }
 
@@ -357,8 +363,8 @@
 
         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'));
+            if (($val = rcube_utils::anytodatetime($value)) && ($fn = self::$fieldmap[$field])) {
+                $this->raw[$fn][] = array(0 => $val->format('Y-m-d'), 'value' => array('date'));
             }
             break;
 
@@ -377,7 +383,7 @@
         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]);
@@ -481,7 +487,7 @@
         $vcard_block    = '';
         $in_vcard_block = false;
 
-        foreach (preg_split("/[\r\n]+/", $data) as $i => $line) {
+        foreach (preg_split("/[\r\n]+/", $data) as $line) {
             if ($in_vcard_block && !empty($line)) {
                 $vcard_block .= $line . "\n";
             }
@@ -491,7 +497,9 @@
             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;
                 }
 
@@ -513,31 +521,36 @@
      *
      * @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(
-            '/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);
+        // Cleanup
+        $vcard = preg_replace(array(
+                // convert special types (like Skype) to normal type='skype' classes with this simple regex ;)
+                '/item(\d+)\.(TEL|EMAIL|URL)([^:]*?):(.*?)item\1.X-ABLabel:(?:_\$!<)?([\w-() ]*)(?:>!\$_)?./s',
+                '/^item\d*\.X-AB.*$/m',  // remove cruft like item1.X-AB*
+                '/^item\d*\./m',         // remove item1.ADR instead of ADR
+                '/\n+/',                 // remove empty lines
+                '/^(N:[^;\R]*)$/m',      // if N doesn't have any semicolons, add some
+            ),
+            array(
+                '\2;type=\5\3:\4',
+                '',
+                '',
+                "\n",
+                '\1;;;;',
+            ), $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;
     }
@@ -609,8 +622,8 @@
                 $enc   = null;
 
                 foreach($regs2[1] as $attrid => $attr) {
+                    $attr = preg_replace('/[\s\t\n\r\0\x0B]/', '', $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
@@ -712,9 +725,15 @@
                             $value[] = $attrvalues;
                         }
                         else if (is_bool($attrvalues)) {
-                            // true means just tag, not tag=value, as in PHOTO;BASE64:...
+                            // true means just a tag, not tag=value, as in PHOTO;BASE64:...
                             if ($attrvalues) {
-                                $attr .= strtoupper(";$attrname");
+                                // vCard v3 uses ENCODING=B (#1489183)
+                                if ($attrname == 'base64') {
+                                    $attr .= ";ENCODING=B";
+                                }
+                                else {
+                                    $attr .= strtoupper(";$attrname");
+                                }
                             }
                         }
                         else {
@@ -748,7 +767,7 @@
      *
      * @return string Joined and quoted string
      */
-    private static function vcard_quote($s, $sep = ';')
+    public static function vcard_quote($s, $sep = ';')
     {
         if (is_array($s)) {
             foreach($s as $part) {
@@ -757,7 +776,7 @@
             return(implode($sep, (array)$r));
         }
 
-        return strtr($s, array('\\' => '\\\\', "\r" => '', "\n" => '\n', ',' => '\,', ';' => '\;'));
+        return strtr($s, array('\\' => '\\\\', "\r" => '', "\n" => '\n', $sep => '\\'.$sep));
     }
 
     /**
@@ -770,15 +789,42 @@
      */
     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;
+
+            $s = trim(strtr($s, $rep2));
         }
 
-        return strtr($s, array("\r" => '', '\\\\' => '\\', '\n' => "\n", '\N' => "\n", '\,' => ',', '\;' => ';', '\:' => ':'));
+        // some implementations (GMail) use non-standard backslash before colon (#1489085)
+        // we will handle properly any backslashed character - removing dummy backslahes
+        // return strtr($s, array("\r" => '', '\\\\' => '\\', '\n' => "\n", '\N' => "\n", '\,' => ',', '\;' => ';'));
+
+        $s   = str_replace("\r", '', $s);
+        $pos = 0;
+
+        while (($pos = strpos($s, '\\', $pos)) !== false) {
+            $next = substr($s, $pos + 1, 1);
+            if ($next == 'n' || $next == 'N') {
+                $s = substr_replace($s, "\n", $pos, 2);
+            }
+            else {
+                $s = substr_replace($s, '', $pos, 1);
+            }
+
+            $pos += 1;
+        }
+
+        return $s;
     }
 
     /**

--
Gitblit v1.9.1