From 6bdb6102c06c605d18f5d2cfba8ac806875ae13a Mon Sep 17 00:00:00 2001
From: thomascube <thomas@roundcube.net>
Date: Tue, 04 Jan 2011 17:00:35 -0500
Subject: [PATCH] Do charset detection in vcards with encoded values (#1485542)

---
 CHANGELOG                       |    1 +
 program/include/rcube_vcard.php |   32 ++++++++++++++++++++++----------
 tests/vcards.php                |    4 ++++
 tests/src/thebat.vcf            |    8 ++++++++
 4 files changed, 35 insertions(+), 10 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG
index 2e20b3e..17a6563 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
 CHANGELOG Roundcube Webmail
 ===========================
 
+- Fix charset detection in vcards with encoded values (#1485542)
 - Better CSS cursors for splitters (#1486874)
 - Show the same message only once (#1487641)
 - Fix namespaces handling (#1487649)
diff --git a/program/include/rcube_vcard.php b/program/include/rcube_vcard.php
index 9bbc32b..641d61e 100644
--- a/program/include/rcube_vcard.php
+++ b/program/include/rcube_vcard.php
@@ -28,6 +28,7 @@
  */
 class rcube_vcard
 {
+  private static $values_decoded = false;
   private $raw = array(
     'FN' => array(),
     'N' => array(array('','','','','')),
@@ -47,10 +48,10 @@
   /**
    * Constructor
    */
-  public function __construct($vcard = null, $charset = RCMAIL_CHARSET)
+  public function __construct($vcard = null, $charset = RCMAIL_CHARSET, $detect = false)
   {
     if (!empty($vcard))
-      $this->load($vcard, $charset);
+      $this->load($vcard, $charset, $detect);
   }
 
 
@@ -58,14 +59,23 @@
    * 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 = RCMAIL_CHARSET)
+  public function load($vcard, $charset = RCMAIL_CHARSET, $detect = false)
   {
+    self::$values_decoded = false;
     $this->raw = self::vcard_decode($vcard);
-    
+
     // resolve charset parameters
-    if ($charset == null)
-      $this->raw = $this->charset_convert($this->raw);
+    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 != RCMAIL_CHARSET) {
+        $this->raw = self::charset_convert($this->raw, $detected_charset);
+    }
 
     // find well-known address fields
     $this->displayname = $this->raw['FN'][0][0];
@@ -171,13 +181,13 @@
   
   /**
    * Convert a whole vcard (array) to UTF-8.
-   * Each member value that has a charset parameter will be converted.
+   * If $force_charset is null, each member value that has a charset parameter will be converted
    */
-  private function charset_convert($card)
+  private static function charset_convert($card, $force_charset = null)
   {
     foreach ($card as $key => $node) {
       foreach ($node as $i => $subnode) {
-        if (is_array($subnode) && $subnode['charset'] && ($charset = $subnode['charset'][0])) {
+        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);
@@ -222,7 +232,7 @@
 
       if (preg_match('/^END:VCARD$/i', $line)) {
         // parse vcard
-        $obj = new rcube_vcard(self::cleanup($vcard_block), $charset);
+        $obj = new rcube_vcard(self::cleanup($vcard_block), $charset, true);
         if (!empty($obj->displayname))
           $out[] = $obj;
 
@@ -363,9 +373,11 @@
   {
     switch (strtolower($encoding)) {
       case 'quoted-printable':
+        self::$values_decoded = true;
         return quoted_printable_decode($value);
 
       case 'base64':
+        self::$values_decoded = true;
         return base64_decode($value);
 
       default:
diff --git a/tests/src/thebat.vcf b/tests/src/thebat.vcf
new file mode 100644
index 0000000..8179f78
--- /dev/null
+++ b/tests/src/thebat.vcf
@@ -0,0 +1,8 @@
+BEGIN:VCARD
+VERSION:2.1
+N;ENCODING=QUOTED-PRINTABLE:Iksi=F1ski;Piotr
+FN;ENCODING=QUOTED-PRINTABLE:Piotr Iksi=F1ski
+EMAIL;PREF;INTERNET:piotr.iksinski@somedomain.com
+X-GENDER:Male
+REV:20080716T203548Z
+END:VCARD
diff --git a/tests/vcards.php b/tests/vcards.php
index 72c6874..3b8f260 100644
--- a/tests/vcards.php
+++ b/tests/vcards.php
@@ -48,6 +48,10 @@
     $this->assertEqual(2, count($vcards), "Detected 2 vcards");
     $this->assertEqual("Apple Computer AG", $vcards[0]->displayname, "FN => displayname");
     $this->assertEqual("John Doë", $vcards[1]->displayname, "Displayname with correct charset");
+    
+    // http://trac.roundcube.net/ticket/1485542
+    $vcards2 = rcube_vcard::import(file_get_contents($this->_srcpath('thebat.vcf')));
+    $this->assertEqual("Iksiñski", $vcards2[0]->surname, "Detect charset in encoded values");
   }
   
 }

--
Gitblit v1.9.1