diff options
-rw-r--r-- | program/include/rcube_vcard.php | 34 | ||||
-rw-r--r-- | program/steps/addressbook/func.inc | 8 | ||||
-rw-r--r-- | program/steps/addressbook/import.inc | 2 | ||||
-rwxr-xr-x | tests/src/utf-16_sample.vcf | bin | 0 -> 460 bytes | |||
-rw-r--r-- | tests/vcards.php | 8 |
5 files changed, 47 insertions, 5 deletions
diff --git a/program/include/rcube_vcard.php b/program/include/rcube_vcard.php index 40544be90..81d4d11ab 100644 --- a/program/include/rcube_vcard.php +++ b/program/include/rcube_vcard.php @@ -40,6 +40,7 @@ class rcube_vcard 'notes' => 'NOTE', 'email' => 'EMAIL', 'address' => 'ADR', + 'jobtitle' => 'TITLE', 'gender' => 'X-GENDER', 'maidenname' => 'X-MAIDENNAME', 'anniversary' => 'X-ANNIVERSARY', @@ -165,6 +166,10 @@ class rcube_vcard } } } + + // force subtype if none set + if (preg_match('/^(email|phone|address|website)/', $key) && !$subtype) + $subtype = 'other'; if ($subtype) $key .= ':' . $subtype; @@ -277,8 +282,14 @@ class rcube_vcard break; case 'photo': - $encoded = !preg_match('![^a-z0-9/=+-]!i', $value); - $this->raw['PHOTO'][0] = array(0 => $encoded ? $value : base64_encode($value), 'BASE64' => true); + if (strpos($value, 'http:') === 0) { + // TODO: fetch file from URL and save it locally? + $this->raw['PHOTO'][0] = array(0 => $value, 'URL' => true); + } + else { + $encoded = !preg_match('![^a-z0-9/=+-]!i', $value); + $this->raw['PHOTO'][0] = array(0 => $encoded ? $value : base64_encode($value), 'BASE64' => true); + } break; case 'email': @@ -422,10 +433,16 @@ class rcube_vcard { // Convert special types (like Skype) to normal type='skype' classes with this simple regex ;) $vcard = preg_replace( - '/item(\d+)\.(TEL|URL)([^:]*?):(.*?)item\1.X-ABLabel:(?:_\$!<)?([\w-() ]*)(?:>!\$_)?./s', + '/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); @@ -440,6 +457,11 @@ class rcube_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) { @@ -631,6 +653,12 @@ class rcube_vcard if (substr($string, 0, 2) == "\xFF\xFE") return 'UTF-16LE'; // Little Endian if (substr($string, 0, 3) == "\xEF\xBB\xBF") return 'UTF-8'; + // heuristics + if ($string[0] == "\0" && $string[1] == "\0" && $string[2] == "\0" && $string[3] != "\0") return 'UTF-32BE'; + if ($string[0] != "\0" && $string[1] == "\0" && $string[2] == "\0" && $string[3] == "\0") return 'UTF-32LE'; + if ($string[0] == "\0" && $string[1] != "\0" && $string[2] == "\0" && $string[3] != "\0") return 'UTF-16BE'; + if ($string[0] != "\0" && $string[1] == "\0" && $string[2] != "\0" && $string[3] == "\0") return 'UTF-16LE'; + // use mb_detect_encoding() $encodings = array('UTF-8', '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', diff --git a/program/steps/addressbook/func.inc b/program/steps/addressbook/func.inc index 336dd44ff..ed87cb150 100644 --- a/program/steps/addressbook/func.inc +++ b/program/steps/addressbook/func.inc @@ -365,6 +365,10 @@ function rcmail_contact_form($form, $record, $attrib = null) // skip cols unknown to the backend if (!$coltypes[$col]) continue; + + // only string values are expected here + if (is_array($record[$col])) + $record[$col] = join(' ', $record[$col]); if ($RCMAIL->action == 'show') { if (!empty($record[$col])) @@ -563,7 +567,9 @@ function rcmail_contact_photo($attrib) if ($CONTACT_COLTYPES['photo']) { $RCMAIL->output->set_env('photo_placeholder', $photo_img); - if ($record['photo']) + if (strpos($record['photo'], 'http:') === 0) + $photo_img = $record['photo']; + else if ($record['photo']) $photo_img = $RCMAIL->url(array('_action' => 'photo', '_cid' => $record['ID'], '_source' => $_REQUEST['_source'])); $img = html::img(array('src' => $photo_img, 'border' => 1, 'alt' => '')); $content = html::div($attrib, $img); diff --git a/program/steps/addressbook/import.inc b/program/steps/addressbook/import.inc index af6f67dd8..7300e3370 100644 --- a/program/steps/addressbook/import.inc +++ b/program/steps/addressbook/import.inc @@ -138,7 +138,7 @@ if ($_FILES['_file']['tmp_name'] && is_uploaded_file($_FILES['_file']['tmp_name' // We're using UTF8 internally $email = rcube_idn_to_utf8($email); - if (!$replace) { + if (!$replace && $email) { // compare e-mail address $existing = $CONTACTS->search('email', $email, false, false); if (!$existing->count) { // compare display name diff --git a/tests/src/utf-16_sample.vcf b/tests/src/utf-16_sample.vcf Binary files differnew file mode 100755 index 000000000..22f54618a --- /dev/null +++ b/tests/src/utf-16_sample.vcf diff --git a/tests/vcards.php b/tests/vcards.php index 3b8f260c4..ba5ce2bef 100644 --- a/tests/vcards.php +++ b/tests/vcards.php @@ -54,4 +54,12 @@ class rcube_test_vcards extends UnitTestCase $this->assertEqual("Iksiñski", $vcards2[0]->surname, "Detect charset in encoded values"); } + function test_encodings() + { + $input = file_get_contents($this->_srcpath('utf-16_sample.vcf')); + + $vcards = rcube_vcard::import($input); + $this->assertEqual("Ǽgean ĽdaMonté", $vcards[0]->displayname, "Decoded from UTF-16"); + } + } |