From b231c8f6accb7c04461ab8364016d0abbe81f82e Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Mon, 7 Jul 2014 19:06:10 +0200 Subject: Fix images import from various vCard formats (#1489977) --- program/lib/Roundcube/rcube_vcard.php | 51 ++++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 18 deletions(-) (limited to 'program/lib/Roundcube/rcube_vcard.php') diff --git a/program/lib/Roundcube/rcube_vcard.php b/program/lib/Roundcube/rcube_vcard.php index fb8fdd525..4a2684f10 100644 --- a/program/lib/Roundcube/rcube_vcard.php +++ b/program/lib/Roundcube/rcube_vcard.php @@ -594,29 +594,34 @@ class rcube_vcard 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(); + $vcard = preg_replace(array("/\r/", "/\n\s+/"), '', $vcard); + $lines = explode("\n", $vcard); + $result = array(); for ($i=0; $i < count($lines); $i++) { - if (!preg_match('/^([^:]+):(.+)$/', $lines[$i], $line)) + if (!($pos = strpos($lines[$i], ':'))) { continue; + } + + $prefix = substr($lines[$i], 0, $pos); + $data = substr($lines[$i], $pos+1); - if (preg_match('/^(BEGIN|END)$/i', $line[1])) + if (preg_match('/^(BEGIN|END)$/i', $prefix)) { 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) + if ($result['VERSION'][0] == "2.1" + && preg_match('/^([^;]+);([^:]+)/', $prefix, $regs2) && !preg_match('/^TYPE=/i', $regs2[2]) ) { - $line[1] = $regs2[1]; + $prefix = $regs2[1]; foreach (explode(';', $regs2[2]) as $prop) { - $line[1] .= ';' . (strpos($prop, '=') ? $prop : 'TYPE='.$prop); + $prefix .= ';' . (strpos($prop, '=') ? $prop : 'TYPE='.$prop); } } - if (preg_match_all('/([^\\;]+);?/', $line[1], $regs2)) { + if (preg_match_all('/([^\\;]+);?/', $prefix, $regs2)) { $entry = array(); $field = strtoupper($regs2[1][0]); $enc = null; @@ -629,10 +634,10 @@ class rcube_vcard // 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]; + $data .= "\n" . $lines[++$i]; } } - $enc = $value; + $enc = $value == 'BASE64' ? 'B' : $value; } else { $lc_key = strtolower($key); @@ -652,20 +657,30 @@ class rcube_vcard // should we use vCard 3.0 instead? // $entry['base64'] = true; } - $line[2] = self::decode_value($line[2], $enc ? $enc : 'base64'); + + $data = self::decode_value($data, $enc ? $enc : 'base64'); + } + else if ($field == 'PHOTO') { + // vCard 4.0 data URI, "PHOTO:data:image/jpeg;base64,..." + if (preg_match('/^data:[a-z\/_-]+;base64,/i', $data, $m)) { + $entry['encoding'] = $enc = 'B'; + $data = substr($data, strlen($m[0])); + $data = self::decode_value($data, 'base64'); + } } if ($enc != 'B' && empty($entry['base64'])) { - $line[2] = self::vcard_unquote($line[2]); + $data = self::vcard_unquote($data); } - $entry = array_merge($entry, (array) $line[2]); - $data[$field][] = $entry; + $entry = array_merge($entry, (array) $data); + $result[$field][] = $entry; } } - unset($data['VERSION']); - return $data; + unset($result['VERSION']); + + return $result; } /** -- cgit v1.2.3 From fcb7d4fc034335d960917abd37254bd3997cf2f3 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Fri, 1 Aug 2014 12:49:37 +0200 Subject: Fix various iCloud vCard issues, added fallback for external photos (#1489993) --- CHANGELOG | 1 + program/lib/Roundcube/html.php | 2 +- program/lib/Roundcube/rcube_vcard.php | 10 +++++----- program/steps/addressbook/func.inc | 10 +++++++--- program/steps/addressbook/photo.inc | 8 ++++++-- program/steps/mail/show.inc | 4 +++- tests/Framework/VCard.php | 19 +++++++++++++++++++ 7 files changed, 42 insertions(+), 12 deletions(-) (limited to 'program/lib/Roundcube/rcube_vcard.php') diff --git a/CHANGELOG b/CHANGELOG index a6ade5bfc..a2de91fa6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -41,6 +41,7 @@ CHANGELOG Roundcube Webmail - Fix some mime-type to extension mapping checks in Installer (#1489983) - Fix errors when using localStorage in Safari's private browsing mode (#1489996) - Fix bug where $Forwarded flag was being set even if server didn't support it (#1490000) +- Fix various iCloud vCard issues, added fallback for external photos (#1489993) RELEASE 1.0.2 ------------- diff --git a/program/lib/Roundcube/html.php b/program/lib/Roundcube/html.php index bcf89d7df..f18cad0bf 100644 --- a/program/lib/Roundcube/html.php +++ b/program/lib/Roundcube/html.php @@ -153,7 +153,7 @@ class html $attr = array('src' => $attr); } return self::tag('img', $attr + array('alt' => ''), null, array_merge(self::$common_attrib, - array('src','alt','width','height','border','usemap','onclick'))); + array('src','alt','width','height','border','usemap','onclick','onerror'))); } /** diff --git a/program/lib/Roundcube/rcube_vcard.php b/program/lib/Roundcube/rcube_vcard.php index 4a2684f10..96add110f 100644 --- a/program/lib/Roundcube/rcube_vcard.php +++ b/program/lib/Roundcube/rcube_vcard.php @@ -110,7 +110,7 @@ class rcube_vcard public function load($vcard, $charset = RCUBE_CHARSET, $detect = false) { self::$values_decoded = false; - $this->raw = self::vcard_decode($vcard); + $this->raw = self::vcard_decode(self::cleanup($vcard)); // resolve charset parameters if ($charset == null) { @@ -496,7 +496,7 @@ class rcube_vcard if (preg_match('/^END:VCARD$/i', $line)) { // parse vcard - $obj = new rcube_vcard(self::cleanup($vcard_block), $charset, true, self::$fieldmap); + $obj = new rcube_vcard($vcard_block, $charset, true, self::$fieldmap); // 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)) { @@ -532,9 +532,9 @@ class rcube_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 + '/item(\d+)\.(TEL|EMAIL|URL)([^:]*?):(.*?)item\1.X-ABLabel:(?:_\$!<)?([\w-() ]*)(?:>!\$_)?./si', + '/^item\d*\.X-AB.*$/mi', // remove cruft like item1.X-AB* + '/^item\d*\./mi', // remove item1.ADR instead of ADR '/\n+/', // remove empty lines '/^(N:[^;\R]*)$/m', // if N doesn't have any semicolons, add some ), diff --git a/program/steps/addressbook/func.inc b/program/steps/addressbook/func.inc index d4c57cc9d..38de93d05 100644 --- a/program/steps/addressbook/func.inc +++ b/program/steps/addressbook/func.inc @@ -816,11 +816,15 @@ function rcmail_contact_photo($attrib) } $photo_img = $RCMAIL->url($url); } - else + else { $ff_value = '-del-'; // will disable delete-photo action + } - $img = html::img(array('src' => $photo_img, 'border' => 1, 'alt' => $RCMAIL->gettext('contactphoto'))); - $content = html::div($attrib, $img); + $content = html::div($attrib, html::img(array( + 'src' => $photo_img, + 'alt' => $RCMAIL->gettext('contactphoto'), + 'onerror' => 'this.src = rcmail.env.photo_placeholder', + ))); if ($CONTACT_COLTYPES['photo'] && ($RCMAIL->action == 'edit' || $RCMAIL->action == 'add')) { $RCMAIL->output->add_gui_object('contactphoto', $attrib['id']); diff --git a/program/steps/addressbook/photo.inc b/program/steps/addressbook/photo.inc index 482185735..30d09ffcc 100644 --- a/program/steps/addressbook/photo.inc +++ b/program/steps/addressbook/photo.inc @@ -72,8 +72,12 @@ $plugin = $RCMAIL->plugins->exec_hook('contact_photo', if ($plugin['url']) { $RCMAIL->output->redirect($plugin['url']); } -else { - $data = $plugin['data']; + +$data = $plugin['data']; + +// detect if photo data is an URL +if (strlen($data) < 1024 && filter_var($data, FILTER_VALIDATE_URL)) { + $RCMAIL->output->redirect($data); } // deliver alt image diff --git a/program/steps/mail/show.inc b/program/steps/mail/show.inc index 4b2d78d31..d4121fdd8 100644 --- a/program/steps/mail/show.inc +++ b/program/steps/mail/show.inc @@ -349,8 +349,10 @@ function rcmail_message_contactphoto($attrib) '_task' => 'addressbook', '_action' => 'photo', '_email' => $MESSAGE->sender['mailto'], - '_alt' => $placeholder + '_alt' => $placeholder, )); + + $attrib['onerror'] = "this.src = '" . ($placeholder ? $placeholder : 'program/resources/blank.gif') . "'"; } else { $photo_img = $placeholder ? $placeholder : 'program/resources/blank.gif'; diff --git a/tests/Framework/VCard.php b/tests/Framework/VCard.php index 0a34fc51f..c23dba844 100644 --- a/tests/Framework/VCard.php +++ b/tests/Framework/VCard.php @@ -79,6 +79,25 @@ class Framework_VCard extends PHPUnit_Framework_TestCase $this->assertEquals("http://domain.tld", $vcard['website:other'][0], "Decode dummy backslash character"); } + /** + * Some Apple vCard quirks (#1489993) + */ + function test_parse_six() + { + $vcard = new rcube_vcard("BEGIN:VCARD\n" + . "VERSION:3.0\n" + . "N:;;;;\n" + . "FN:Apple Computer AG\n" + . "ITEM1.ADR;type=WORK;type=pref:;;Birgistrasse 4a;Wallisellen-Zürich;;8304;Switzerland\n" + . "PHOTO;ENCODING=B:aHR0cDovL3Rlc3QuY29t\n" + . "END:VCARD" + ); + + $result = $vcard->get_assoc(); + + $this->assertCount(1, $result['address:work'], "ITEM1.-prefixed entry"); + } + function test_import() { $input = file_get_contents($this->_srcpath('apple.vcf')); -- cgit v1.2.3