From ed1d212ae2daea5e4bd043417610177093e99f19 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Sat, 16 Jan 2016 03:03:51 -0500
Subject: [PATCH] Improved SVG cleanup code
---
program/lib/Roundcube/rcube_vcard.php | 157 +++++++++++++++++++++++++++++++++++-----------------
1 files changed, 105 insertions(+), 52 deletions(-)
diff --git a/program/lib/Roundcube/rcube_vcard.php b/program/lib/Roundcube/rcube_vcard.php
index 54bb952..cea61bd 100644
--- a/program/lib/Roundcube/rcube_vcard.php
+++ b/program/lib/Roundcube/rcube_vcard.php
@@ -1,6 +1,6 @@
<?php
-/*
+/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2008-2012, The Roundcube Dev Team |
@@ -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);
}
@@ -109,7 +110,7 @@
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) {
@@ -121,11 +122,6 @@
&& $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
@@ -147,6 +143,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;
}
}
@@ -195,7 +196,7 @@
}
while ($k < count($raw['type']) && ($subtype == 'internet' || $subtype == 'pref')) {
- $subtype = $typemap[$raw['type'][++$k]] ? $typemap[$raw['type'][$k]] : strtolower($raw['type'][$k]);
+ $subtype = $typemap[$raw['type'][++$k]] ?: strtolower($raw['type'][$k]);
}
}
@@ -206,7 +207,7 @@
&& !in_array($k, array('pref','internet','voice','base64'))
) {
$k_uc = strtoupper($k);
- $subtype = $typemap[$k_uc] ? $typemap[$k_uc] : $k;
+ $subtype = $typemap[$k_uc] ?: $k;
break;
}
}
@@ -357,8 +358,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,16 +378,20 @@
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));
+ $this->raw[$tag][$index]['type'] = explode(',', $typemap[$type_uc] ?: $type);
}
}
+ else {
+ unset($this->raw[$tag]);
+ }
+
break;
}
}
@@ -408,9 +413,10 @@
* 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')
+ private function get_type_index($field)
{
$result = 0;
if ($this->raw[$field]) {
@@ -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";
}
@@ -490,7 +496,7 @@
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)) {
@@ -517,29 +523,34 @@
*/
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-() ]*)(?:>!\$_)?./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
+ ),
+ 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;
}
@@ -583,45 +594,50 @@
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;
+ }
- if (preg_match('/^(BEGIN|END)$/i', $line[1]))
+ $prefix = substr($lines[$i], 0, $pos);
+ $data = substr($lines[$i], $pos+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;
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
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);
@@ -641,20 +657,30 @@
// 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 ?: '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;
}
/**
@@ -714,9 +740,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 {
@@ -750,7 +782,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) {
@@ -759,7 +791,7 @@
return(implode($sep, (array)$r));
}
- return strtr($s, array('\\' => '\\\\', "\r" => '', "\n" => '\n', ',' => '\,', ';' => '\;'));
+ return strtr($s, array('\\' => '\\\\', "\r" => '', "\n" => '\n', $sep => '\\'.$sep));
}
/**
@@ -784,9 +816,30 @@
}
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