2007-10-23 23:14:03 +02:00
|
|
|
#! /usr/bin/env perl
|
2006-12-17 17:36:17 +01:00
|
|
|
#
|
2009-03-25 12:25:08 +01:00
|
|
|
# Copyright (C) 2008 Funambol, Inc.
|
2009-03-25 15:21:04 +01:00
|
|
|
# Copyright (C) 2008-2009 Patrick Ohly <patrick.ohly@gmx.de>
|
|
|
|
# Copyright (C) 2009 Intel Corporation
|
2006-12-17 17:36:17 +01:00
|
|
|
#
|
2007-11-08 21:56:37 +01:00
|
|
|
# Usage: <file>
|
|
|
|
# <left file> <right file>
|
|
|
|
# Either normalizes a file or compares two of them in a side-by-side
|
|
|
|
# diff.
|
|
|
|
#
|
|
|
|
# Checks environment variables:
|
|
|
|
#
|
|
|
|
# CLIENT_TEST_SERVER=funambol|scheduleworld|egroupware|synthesis
|
|
|
|
# Enables code which simplifies the text files just like
|
|
|
|
# certain well-known servers do. This is useful for testing
|
|
|
|
# to ignore the data loss introduced by these servers or (for
|
|
|
|
# users) to simulate the effect of these servers on their data.
|
|
|
|
#
|
|
|
|
# CLIENT_TEST_CLIENT=evolution|addressbook (Mac OS X/iPhone)
|
|
|
|
# Same as for servers this replicates the effect of storing
|
|
|
|
# data in the clients.
|
|
|
|
#
|
|
|
|
# CLIENT_TEST_LEFT_NAME="before sync"
|
|
|
|
# CLIENT_TEST_RIGHT_NAME="after sync"
|
|
|
|
# CLIENT_TEST_REMOVED="removed during sync"
|
|
|
|
# CLIENT_TEST_ADDED="added during sync"
|
|
|
|
# Setting these variables changes the default legend
|
|
|
|
# print above the left and right file during a
|
|
|
|
# comparison.
|
|
|
|
#
|
|
|
|
# CLIENT_TEST_COMPARISON_FAILED=1
|
|
|
|
# Overrides the default error code when changes are found.
|
2009-04-30 18:26:27 +02:00
|
|
|
#
|
|
|
|
# This program is free software; you can redistribute it and/or
|
|
|
|
# modify it under the terms of the GNU Lesser General Public
|
|
|
|
# License as published by the Free Software Foundation; either
|
|
|
|
# version 2.1 of the License, or (at your option) version 3.
|
|
|
|
#
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
# Lesser General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU Lesser General Public
|
|
|
|
# License along with this library; if not, write to the Free Software
|
|
|
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
|
|
# 02110-1301 USA
|
2007-11-08 21:56:37 +01:00
|
|
|
|
2006-12-17 17:36:17 +01:00
|
|
|
|
|
|
|
use strict;
|
2011-05-05 14:08:32 +02:00
|
|
|
|
|
|
|
# Various crashes have been encountered in the Perl interpreter
|
|
|
|
# executable when enabling UTF-8. It is only needed for nicer
|
|
|
|
# side-by-side comparison of changes (correct column width),
|
|
|
|
# so not much functionality is lost by disabling this.
|
|
|
|
# use encoding 'utf8';
|
|
|
|
|
|
|
|
# Instead enable writing the result as UTF-8. Input
|
|
|
|
# files are read as UTF-8 via PerlIO parameters in open().
|
|
|
|
binmode(STDOUT, ":utf8");
|
|
|
|
|
2007-03-29 22:39:54 +02:00
|
|
|
use Algorithm::Diff;
|
2012-03-02 14:11:03 +01:00
|
|
|
use MIME::Base64;
|
|
|
|
use Digest::MD5 qw(md5 md5_hex md5_base64);
|
2006-12-17 17:36:17 +01:00
|
|
|
|
2007-10-23 23:14:03 +02:00
|
|
|
# ignore differences caused by specific servers or local backends?
|
2009-03-11 16:30:23 +01:00
|
|
|
my $server = $ENV{CLIENT_TEST_SERVER};
|
2007-10-23 23:14:03 +02:00
|
|
|
my $client = $ENV{CLIENT_TEST_CLIENT} || "evolution";
|
2006-12-17 17:36:17 +01:00
|
|
|
my $scheduleworld = $server =~ /scheduleworld/;
|
|
|
|
my $synthesis = $server =~ /synthesis/;
|
ZYB: Ignore some properties comparison(MB#2424)
Some properties in vcard21 are dismissed by ZYB server.
So we ignore them in synccompare:
CALURI, CATEGORIES, FBURL, NICKNAME, X-MOZILLA-HTML,
PHOTO, X-EVOLUTION-FILE-AS, X-ANNIVERSARY,
X-ASSISTANT, X-EVOLUTION-BLOG-URL, X-EVOLUTION-VIDEO-URL,
X-GROUPWISE, X-ICQ, X-MANAGER, X-SPOUSE, X-YAHOO, X-AIM
2009-07-13 09:35:21 +02:00
|
|
|
my $zyb = $server =~ /zyb/;
|
2009-08-11 03:53:01 +02:00
|
|
|
my $mobical = $server =~ /mobical/;
|
2009-09-04 03:07:05 +02:00
|
|
|
my $memotoo = $server =~ /memotoo/;
|
2010-01-07 06:35:17 +01:00
|
|
|
my $nokia_7210c = $server =~ /nokia_7210c/;
|
2010-03-08 07:43:16 +01:00
|
|
|
my $ovi = $server =~ /Ovi/;
|
2010-11-26 14:02:46 +01:00
|
|
|
my $unique_uid = $ENV{CLIENT_TEST_UNIQUE_UID};
|
2011-05-05 15:32:02 +02:00
|
|
|
my $full_timezones = $ENV{CLIENT_TEST_FULL_TIMEZONES}; # do not simplify VTIMEZONE definitions
|
2014-05-02 16:35:23 +02:00
|
|
|
my $no_timezones = $ENV{CLIENT_TEST_NO_TIMEZONES};
|
2009-02-24 12:20:54 +01:00
|
|
|
|
|
|
|
# TODO: this hack ensures that any synchronization is limited to
|
|
|
|
# properties supported by Synthesis. Remove this again.
|
2009-03-18 12:11:32 +01:00
|
|
|
# $synthesis = 1;
|
2009-02-24 12:20:54 +01:00
|
|
|
|
2011-08-12 10:27:31 +02:00
|
|
|
my $exchange = $server =~ /exchange/; # Exchange via ActiveSync
|
2006-12-17 17:36:17 +01:00
|
|
|
my $egroupware = $server =~ /egroupware/;
|
|
|
|
my $funambol = $server =~ /funambol/;
|
2012-06-04 10:17:18 +02:00
|
|
|
my $googlesyncml = $server eq "google";
|
|
|
|
my $googlecaldav = $server eq "googlecalendar";
|
2012-10-03 10:55:29 +02:00
|
|
|
my $googlecarddav = $server eq "googlecontacts";
|
2012-06-04 10:17:18 +02:00
|
|
|
my $googleeas = $server eq "googleeas";
|
2010-12-09 12:20:15 +01:00
|
|
|
my $google_valarm = $ENV{CLIENT_TEST_GOOGLE_VALARM};
|
2010-12-10 14:46:28 +01:00
|
|
|
my $yahoo = $server =~ /yahoo/;
|
2011-08-10 14:08:37 +02:00
|
|
|
my $davical = $server =~ /davical/;
|
2011-04-12 22:48:28 +02:00
|
|
|
my $apple = $server =~ /apple/;
|
2011-10-05 11:38:54 +02:00
|
|
|
my $oracle = $server =~ /oracle/;
|
2012-02-20 16:51:37 +01:00
|
|
|
my $radicale = $server =~ /radicale/;
|
2012-10-03 10:55:29 +02:00
|
|
|
my $zimbra = $server =~ /zimbra/;
|
2007-10-23 23:14:03 +02:00
|
|
|
my $evolution = $client =~ /evolution/;
|
|
|
|
my $addressbook = $client =~ /addressbook/;
|
2014-10-10 12:09:11 +02:00
|
|
|
my $akonadi = $server =~ /kde/;
|
2006-12-17 17:36:17 +01:00
|
|
|
|
|
|
|
sub Usage {
|
|
|
|
print "$0 <vcards.vcf\n";
|
|
|
|
print " normalizes one file (stdin or single argument), prints to stdout\n";
|
|
|
|
print "$0 vcards1.vcf vcards2.vcf\n";
|
|
|
|
print " compares the two files\n";
|
|
|
|
print "Also works for iCalendar files.\n";
|
|
|
|
}
|
|
|
|
|
2007-11-30 23:44:51 +01:00
|
|
|
sub uppercase {
|
|
|
|
my $text = shift;
|
|
|
|
$text =~ tr/a-z/A-Z/;
|
|
|
|
return $text;
|
|
|
|
}
|
|
|
|
|
2008-02-25 21:43:11 +01:00
|
|
|
sub sortlist {
|
|
|
|
my $list = shift;
|
|
|
|
return join(",", sort(split(/,/, $list)));
|
|
|
|
}
|
|
|
|
|
2009-03-24 19:16:59 +01:00
|
|
|
sub splitvalue {
|
|
|
|
my $prop = shift;
|
|
|
|
my $values = shift;
|
|
|
|
my $eol = shift;
|
|
|
|
|
|
|
|
my @res = ();
|
|
|
|
foreach my $val (split (/;/, $values)) {
|
|
|
|
push(@res, $prop, ":", $val, $eol);
|
|
|
|
}
|
|
|
|
return join("", @res);
|
|
|
|
}
|
|
|
|
|
2011-05-06 17:26:25 +02:00
|
|
|
# normalize the DATE-TIME duration unless the VALUE isn't a duration
|
|
|
|
sub NormalizeTrigger {
|
|
|
|
my $value = shift;
|
2011-08-12 10:29:45 +02:00
|
|
|
$value =~ /([+-]?)P(?:(\d*)D)?T(?:(\d*)H)?(?:(\d*)M)?(?:(\d*)S)?/;
|
|
|
|
my ($sign, $days, $hours, $minutes, $seconds) = ($1, int($2), int($3), int($4), int($5));
|
|
|
|
while ($seconds >= 60) {
|
|
|
|
$minutes++;
|
|
|
|
$seconds -= 60;
|
|
|
|
}
|
|
|
|
while ($minutes >= 60) {
|
|
|
|
$hours++;
|
|
|
|
$minutes -= 60;
|
|
|
|
}
|
|
|
|
while ($hours >= 24) {
|
|
|
|
$days++;
|
|
|
|
$hours -= 24;
|
|
|
|
}
|
|
|
|
$value = $sign;
|
|
|
|
$value .= ($days . "D") if $days;
|
|
|
|
$value .= ($hours . "H") if $hours;
|
|
|
|
$value .= ($minutes . "M") if $minutes;
|
|
|
|
$value .= ($seconds . "S") if $seconds;
|
2011-05-06 17:26:25 +02:00
|
|
|
return $value;
|
|
|
|
}
|
|
|
|
|
2012-03-02 14:11:03 +01:00
|
|
|
# decode base64 string, return size and hash
|
|
|
|
sub describeBase64 {
|
|
|
|
my $data = decode_base64($1);
|
|
|
|
return sprintf("%d b64 characters = %d bytes, %s md5sum", length($1), length($data), md5_hex($data));
|
|
|
|
}
|
|
|
|
|
2010-11-26 14:04:40 +01:00
|
|
|
# called for one VCALENDAR (with single VEVENT/VTODO/VJOURNAL) or VCARD,
|
|
|
|
# returns normalized one
|
|
|
|
sub NormalizeItem {
|
|
|
|
my $width = shift;
|
|
|
|
$_ = shift;
|
2006-12-17 17:36:17 +01:00
|
|
|
|
2012-10-03 10:55:29 +02:00
|
|
|
# Reduce \N to \n (both are allowed in vCard 3.0).
|
|
|
|
# Using a regular expression is a bit too broad
|
|
|
|
# because it also matches \\N, which must not be
|
|
|
|
# changed.
|
|
|
|
s/\\N/\\n/g;
|
|
|
|
|
2014-03-19 14:35:15 +01:00
|
|
|
# Ignore blank lines. Akonadi inserts them.
|
|
|
|
s/\n{2,}/\n/s;
|
|
|
|
|
2006-12-17 17:36:17 +01:00
|
|
|
# undo line continuation
|
|
|
|
s/\n\s//gs;
|
|
|
|
# ignore charset specifications, assume UTF-8
|
|
|
|
s/;CHARSET="?UTF-8"?//g;
|
|
|
|
|
2007-04-15 15:16:17 +02:00
|
|
|
# UID may differ, but only in vCards and journal entries:
|
|
|
|
# in calendar events the UID needs to be preserved to handle
|
|
|
|
# meeting invitations/replies correctly
|
|
|
|
s/((VCARD|VJOURNAL).*)^UID:[^\n]*\n/$1/msg;
|
2006-12-17 17:36:17 +01:00
|
|
|
|
2010-11-26 14:02:46 +01:00
|
|
|
# intentional changes to UID are acceptable when running with CLIENT_TEST_UNIQUE_UID
|
|
|
|
if ($unique_uid) {
|
|
|
|
s/UID:UNIQUE-UID-\d+-/UID:/g;
|
|
|
|
}
|
|
|
|
|
2011-07-07 07:35:22 +02:00
|
|
|
# merge all CATEGORIES properties into one comma-separated one
|
|
|
|
while ( s/^CATEGORIES:([^\n]*)\n(.*)^CATEGORIES:([^\n]*)\n/CATEGORIES:$1,$3\n$2/ms ) {}
|
|
|
|
|
2008-02-25 21:43:11 +01:00
|
|
|
# exact order of categories is irrelevant
|
|
|
|
s/^CATEGORIES:(\S+)/"CATEGORIES:" . sortlist($1)/mge;
|
|
|
|
|
2007-02-11 12:11:34 +01:00
|
|
|
# expand <foo> shortcuts to TYPE=<foo>
|
|
|
|
while (s/^(ADR|EMAIL|TEL)([^:\n]*);(HOME|OTHER|WORK|PARCEL|INTERNET|CAR|VOICE|CELL|PAGER)/$1;TYPE=$3/mg) {}
|
|
|
|
|
2006-12-17 17:36:17 +01:00
|
|
|
# the distinction between an empty and a missing property
|
|
|
|
# is vague and handled differently, so ignore empty properties
|
|
|
|
s/^[^:\n]*:;*\n//mg;
|
|
|
|
|
|
|
|
# use separate TYPE= fields
|
2007-12-08 18:42:58 +01:00
|
|
|
while( s/^(\w*[^:\n]*);TYPE=(\w*),(\w*)/$1;TYPE=$2;TYPE=$3/mg ) {}
|
2006-12-17 17:36:17 +01:00
|
|
|
|
2007-12-08 18:42:58 +01:00
|
|
|
# make TYPE uppercase (in vCard 3.0 at least those parameters are case-insensitive)
|
|
|
|
while( s/^(\w*[^:\n]*);TYPE=(\w*?[a-z]\w*?)([;:])/ $1 . ";TYPE=" . uppercase($2) . $3 /mge ) {}
|
|
|
|
|
2014-05-06 17:05:34 +02:00
|
|
|
# Replace parameters with a sorted parameter list. Cannot be done with
|
|
|
|
# a regular expression because of quoted strings. While we know exact
|
|
|
|
# parameter values, normalize them to use quoted strings if and only
|
|
|
|
# if the content is more complex than alphanumeric plus underscore and
|
|
|
|
# hyphen.
|
|
|
|
my @lines;
|
|
|
|
my ($propname, $sep, $rest);
|
|
|
|
foreach (split /\n/) {
|
|
|
|
($propname, $sep, $rest) = /^([^;:]+)([:;])(.*)/;
|
|
|
|
if ($sep eq ";") {
|
|
|
|
my @params;
|
|
|
|
my $c;
|
|
|
|
my $i = 0;
|
|
|
|
my $n = length($rest);
|
|
|
|
my $quoted = 0;
|
|
|
|
my $start = 0;
|
|
|
|
while ($i < $n) {
|
|
|
|
$c = substr($rest, $i, 1);
|
|
|
|
$i++;
|
|
|
|
if ($quoted) {
|
|
|
|
if ($c eq '"') {
|
|
|
|
$quoted = 0;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if ($c eq '"') {
|
|
|
|
$quoted = 1;
|
|
|
|
} elsif ($c eq ';' || $c eq ':') {
|
|
|
|
my $param = substr($rest, $start, $i - $start - 1);
|
|
|
|
my ($name, $value) = $param =~ /^([^=]*)="?([^"]*)"?$/;
|
|
|
|
if ($value =~ /^[a-zA-Z0-9_-]*$/) {
|
|
|
|
$param = $name . '=' . $value;
|
|
|
|
} else {
|
|
|
|
$param = $name . '="' . $value . '"';
|
|
|
|
}
|
|
|
|
push @params, $param;
|
|
|
|
$start = $i;
|
|
|
|
if ($c eq ':') {
|
|
|
|
last;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$_ = $propname . ';' . join(";", sort(@params)) . ':' . substr($rest, $start);
|
|
|
|
}
|
|
|
|
push @lines, $_;
|
|
|
|
}
|
|
|
|
$_ = join("\n", @lines);
|
2006-12-17 17:36:17 +01:00
|
|
|
|
2012-10-03 10:55:29 +02:00
|
|
|
# VALUE=DATE is the default, no need to show it
|
|
|
|
s/^(EXDATE|BDAY);VALUE=DATE:/\1:/mg;
|
2009-03-23 17:52:45 +01:00
|
|
|
|
2010-10-08 14:50:33 +02:00
|
|
|
# default opacity is OPAQUE
|
|
|
|
s/^TRANSP:OPAQUE\r?\n?//gm;
|
|
|
|
|
2009-03-24 19:16:59 +01:00
|
|
|
# multiple EXDATEs may be joined into one, use separate properties as normal form
|
|
|
|
s/^(EXDATE[^:]*):(.*)(\r?\n)/splitvalue($1, $2, $3)/mge;
|
2009-03-23 17:52:45 +01:00
|
|
|
|
2009-03-23 17:15:45 +01:00
|
|
|
# sort value lists of specific properties
|
|
|
|
s!^(RRULE.*):(.*)!$1 . ":" . join(';',sort(split(/;/, $2)))!meg;
|
|
|
|
|
2009-05-11 15:51:08 +02:00
|
|
|
# INTERVAL=1 is the default and thus can be removed
|
|
|
|
s/^RRULE(.*?);INTERVAL=1(;|$)/RRULE$1$2/mg;
|
|
|
|
|
2006-12-17 17:36:17 +01:00
|
|
|
# Ignore remaining "other" email, address and telephone type - this is
|
|
|
|
# an Evolution specific extension which might not be preserved.
|
|
|
|
s/^(ADR|EMAIL|TEL)([^:\n]*);TYPE=OTHER/$1$2/mg;
|
|
|
|
# TYPE=PREF on the other hand is not used by Evolution, but
|
|
|
|
# might be sent back.
|
|
|
|
s/^(ADR|EMAIL)([^:\n]*);TYPE=PREF/$1$2/mg;
|
|
|
|
# Evolution does not need TYPE=INTERNET for email
|
|
|
|
s/^(EMAIL)([^:\n]*);TYPE=INTERNET/$1$2/mg;
|
|
|
|
# ignore TYPE=PREF in address, does not matter in Evolution
|
|
|
|
s/^((ADR|LABEL)[^:\n]*);TYPE=PREF/$1/mg;
|
|
|
|
# ignore extra separators in multi-value fields
|
|
|
|
s/^((ORG|N|(ADR[^:\n]*?)):.*?);*$/$1/mg;
|
|
|
|
# the type of certain fields is ignore by Evolution
|
|
|
|
s/^X-(AIM|GROUPWISE|ICQ|YAHOO);TYPE=HOME/X-$1/gm;
|
|
|
|
# Evolution ignores an additional pager type
|
|
|
|
s/^TEL;TYPE=PAGER;TYPE=WORK/TEL;TYPE=PAGER/gm;
|
|
|
|
# PAGER property is sent by Evolution, but otherwise ignored
|
|
|
|
s/^LABEL[;:].*\n//mg;
|
|
|
|
# TYPE=VOICE is the default in Evolution and may or may not appear in the vcard;
|
|
|
|
# this simplification is a bit too agressive and hides the problematic
|
|
|
|
# TYPE=PREF,VOICE combination which Evolution does not handle :-/
|
|
|
|
s/^TEL([^:\n]*);TYPE=VOICE,([^:\n]*):/TEL$1;TYPE=$2:/mg;
|
|
|
|
s/^TEL([^:\n]*);TYPE=([^;:\n]*),VOICE([^:\n]*):/TEL$1;TYPE=$2$3:/mg;
|
|
|
|
s/^TEL([^:\n]*);TYPE=VOICE([^:\n]*):/TEL$1$2:/mg;
|
|
|
|
# don't care about the TYPE property of PHOTOs
|
|
|
|
s/^PHOTO;(.*)TYPE=[A-Z]*/PHOTO;$1/mg;
|
|
|
|
# encoding is not case sensitive, skip white space in the middle of binary data
|
|
|
|
if (s/^PHOTO;.*?ENCODING=(b|B|BASE64).*?:\s*/PHOTO;ENCODING=B: /mgi) {
|
2011-07-21 11:10:31 +02:00
|
|
|
if ($memotoo) {
|
|
|
|
# transcodes image data, can't compare it
|
|
|
|
s/(^PHOTO.*:).*/$1<stripped by synccompare>/mg;
|
|
|
|
} else {
|
|
|
|
while (s/^PHOTO(.*?): (\S+)[\t ]+(\S+)/PHOTO$1: $2$3/mg) {}
|
|
|
|
}
|
2006-12-17 17:36:17 +01:00
|
|
|
}
|
2012-03-02 14:11:03 +01:00
|
|
|
# Don't show base64 encoded PHOTO data (makes diff very long). Instead
|
|
|
|
# decode and show size + hash.
|
|
|
|
s/^PHOTO;ENCODING=B: (.*)$/"PHOTO: " . describeBase64($1)/mge;
|
2011-07-22 09:25:33 +02:00
|
|
|
# special case for the inlining of the local test case PHOTO
|
|
|
|
s!^PHOTO;;VALUE=uri:file://testcases/local.png$!PHOTO;;VALUE=uri:<local.png>!m;
|
|
|
|
s!^PHOTO;ENCODING=B: iVBORw0KGgoAAAANSUh.*UQOVkeH/aKBSLM04QlMqAAFNBTl\+CjN9AAAAAElFTkSuQmCC$!PHOTO;;VALUE=uri:<local.png>!m;
|
2006-12-17 17:36:17 +01:00
|
|
|
# ignore extra day factor in front of weekday
|
|
|
|
s/^RRULE:(.*)BYDAY=\+?1(\D)/RRULE:$1BYDAY=$2/mg;
|
|
|
|
# remove default VALUE=DATE-TIME
|
|
|
|
s/^(DTSTART|DTEND)([^:\n]*);VALUE=DATE-TIME/$1$2/mg;
|
|
|
|
|
2011-08-04 11:50:56 +02:00
|
|
|
# remove default LANGUAGE=en-US
|
|
|
|
s/^([^:\n]*);LANGUAGE=en-US/$1/mg;
|
|
|
|
|
2009-03-18 12:11:32 +01:00
|
|
|
# normalize values which look like a date to YYYYMMDD because the hyphen is optional
|
|
|
|
s/:(\d{4})-(\d{2})-(\d{2})/:$1$2$3/g;
|
|
|
|
|
2009-03-19 13:00:31 +01:00
|
|
|
# mailto is case insensitive
|
|
|
|
s/^((ATTENDEE|ORGANIZER).*):[Mm][Aa][Ii][Ll][Tt][Oo]:/$1:mailto:/mg;
|
|
|
|
|
2006-12-17 17:36:17 +01:00
|
|
|
# remove fields which may differ
|
2011-04-12 22:48:28 +02:00
|
|
|
s/^(PRODID|CREATED|DTSTAMP|LAST-MODIFIED|REV)(;X-VOBJ-FLOATINGTIME-ALLOWED=(TRUE|FALSE))?:.*\r?\n?//gm;
|
2014-03-19 14:35:15 +01:00
|
|
|
# remove optional properties and parameters
|
|
|
|
s/^(METHOD|X-WSS-[A-Z]*|X-WR-[A-Z]*|CALSCALE|X-KDE-ICAL-IMPLEMENTATION-VERSION|X-KDE-KCALCORE-ENABLED):.*\r?\n?//gm;
|
|
|
|
s/^(ATTENDEE[^:]*);X-UID=[^;:]*/$1/mg;
|
2006-12-17 17:36:17 +01:00
|
|
|
|
2007-12-09 18:12:53 +01:00
|
|
|
# trailing line break(s) in a DESCRIPTION may or may not be
|
|
|
|
# removed or added by servers
|
|
|
|
s/^DESCRIPTION:(.*?)(\\n)+$/DESCRIPTION:$1/gm;
|
|
|
|
|
2009-07-01 12:45:46 +02:00
|
|
|
# use the shorter property name when there are alternatives,
|
|
|
|
# but avoid duplicates
|
|
|
|
foreach my $i ("SPOUSE", "MANAGER", "ASSISTANT", "ANNIVERSARY") {
|
|
|
|
if (/^X-\Q$i\E:(.*?)$/m) {
|
|
|
|
s/^X-EVOLUTION-\Q$i\E:\Q$1\E\n//m;
|
|
|
|
}
|
|
|
|
}
|
2009-06-24 16:06:58 +02:00
|
|
|
s/^X-EVOLUTION-(SPOUSE|MANAGER|ASSISTANT|ANNIVERSARY)/X-$1/gm;
|
|
|
|
|
2011-06-15 17:49:12 +02:00
|
|
|
# some properties are always lost because we don't transmit them
|
|
|
|
if ($ENV{CLIENT_TEST_SERVER}) {
|
|
|
|
s/^(X-FOOBAR-EXTENSION|X-TEST)(;[^:;\n]*)*:.*\r?\n?//gm;
|
|
|
|
}
|
|
|
|
|
2009-05-05 11:37:51 +02:00
|
|
|
# if there is no DESCRIPTION in a VJOURNAL, then use the
|
|
|
|
# summary: that's what is done when exchanging such a
|
|
|
|
# VJOURNAL as plain text
|
|
|
|
if (/^BEGIN:VJOURNAL$/m && !/^DESCRIPTION/m) {
|
|
|
|
s/^SUMMARY:(.*)$/SUMMARY:$1\nDESCRIPTION:$1/m;
|
|
|
|
}
|
|
|
|
|
2011-06-20 14:21:16 +02:00
|
|
|
# strip configurable X- parameters or properties
|
|
|
|
my $strip = $ENV{CLIENT_TEST_STRIP_PROPERTIES};
|
|
|
|
if ($strip) {
|
|
|
|
s/^$strip(;[^:;\n]*)*:.*\r?\n?//gm;
|
|
|
|
}
|
|
|
|
$strip = $ENV{CLIENT_TEST_STRIP_PARAMETERS};
|
|
|
|
if ($strip) {
|
|
|
|
while (s/^(\w+)([^:\n]*);$strip=\d+/$1$2/mg) {}
|
|
|
|
}
|
|
|
|
|
2011-11-02 12:11:48 +01:00
|
|
|
# strip redundant VTIMEZONE definitions (happen to be
|
|
|
|
# added by Google CalDAV server when storing an all-day event
|
|
|
|
# which doesn't need any time zone definition)
|
|
|
|
# http://code.google.com/p/google-caldav-issues/issues/detail?id=63
|
2014-05-02 16:35:23 +02:00
|
|
|
#
|
|
|
|
# Also strip all definitions if requested.
|
2011-11-02 12:11:48 +01:00
|
|
|
while (m/(BEGIN:VTIMEZONE.*?TZID:([^\n]*)\n.*?END:VTIMEZONE\n)/gs) {
|
|
|
|
my $def = $1;
|
|
|
|
my $tzid = $2;
|
2014-05-02 16:35:23 +02:00
|
|
|
# Strip all, or not used as parameter?
|
|
|
|
if ($no_timezones || ! m/;TZID="?\Q$tzid\E"?/) {
|
2011-11-02 12:11:48 +01:00
|
|
|
# no, remove definition
|
2011-12-06 12:18:10 +01:00
|
|
|
s!\Q$def\E!!s;
|
2011-11-02 12:11:48 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-05-05 15:32:02 +02:00
|
|
|
if (!$full_timezones) {
|
|
|
|
# Strip trailing digits from TZID. They are appended by
|
|
|
|
# Evolution and SyncEvolution to distinguish VTIMEZONE
|
|
|
|
# definitions which have the same TZID, but different rules.
|
2014-02-13 16:26:07 +01:00
|
|
|
# This appending of digits may even get repeated, leading to:
|
|
|
|
# TZID=EST/EDT 1 1
|
|
|
|
s/(^TZID:|;TZID=)([^;:]*?)( \d+)+/$1$2/gm;
|
2011-05-05 15:32:02 +02:00
|
|
|
|
|
|
|
# Strip trailing -(Standard) from TZID. Evolution 2.24.5 adds
|
|
|
|
# that (not sure exactly where that comes from).
|
|
|
|
s/(^TZID:|;TZID=)([^;:]*?)-\(Standard\)/$1$2/gm;
|
|
|
|
|
|
|
|
# VTIMEZONE and TZID do not have to be preserved verbatim as long
|
|
|
|
# as the replacement is still representing the same timezone.
|
|
|
|
# Reduce TZIDs which specify a proper location
|
|
|
|
# to their location part and strip the VTIMEZONE - makes the
|
|
|
|
# diff shorter, too.
|
|
|
|
my $location = "[^\n]*((?:Africa|America|Antarctica|Arctic|Asia|Atlantic|Australia|Brazil|Canada|Chile|Egypt|Eire|Europe|Hongkong|Iceland|India|Iran|Israel|Jamaica|Japan|Kwajalein|Libya|Mexico|Mideast|Navajo|Pacific|Poland|Portugal|Singapore|Turkey|Zulu)[-a-zA-Z0-9_/]*)";
|
|
|
|
s;^BEGIN:VTIMEZONE.*?^TZID:$location.*^END:VTIMEZONE;BEGIN:VTIMEZONE\n TZID:$1 [...]\nEND:VTIMEZONE;gms;
|
|
|
|
s;TZID="?$location"?;TZID=$1;gm;
|
|
|
|
}
|
2008-04-19 10:41:51 +02:00
|
|
|
|
2010-09-02 14:15:16 +02:00
|
|
|
# normalize iCalendar 2.0
|
|
|
|
if (/^BEGIN:(VEVENT|VTODO|VJOURNAL)$/m) {
|
|
|
|
# CLASS=PUBLIC is the default, no need to show it
|
|
|
|
s/^CLASS:PUBLIC\r?\n//m;
|
2010-12-13 14:17:39 +01:00
|
|
|
# RELATED=START is the default behavior
|
|
|
|
s/^TRIGGER([^\n:]*);RELATED=START/TRIGGER$1/mg;
|
2011-04-12 22:48:28 +02:00
|
|
|
# VALUE=DURATION is the default behavior
|
|
|
|
s/^TRIGGER([^\n:]*);VALUE=DURATION/TRIGGER$1/mg;
|
2011-05-06 17:26:25 +02:00
|
|
|
s/^(TRIGGER.*):(\S*)/$1 . ":" . NormalizeTrigger($2)/mge;
|
2014-03-19 14:35:15 +01:00
|
|
|
# INDIVIDUAL is default for CUTYPE.
|
|
|
|
s/;CUTYPE=INDIVIDUAL([;:])/$1/mg;
|
|
|
|
# Print without quotation marks (probably not save in general, but okay for our test data).
|
|
|
|
s/;CN="([^;]*)"/;CN=$1/g;
|
2010-09-02 14:15:16 +02:00
|
|
|
}
|
|
|
|
|
2011-05-05 14:08:32 +02:00
|
|
|
# Added by EDS >= 2.32, presumably to cache some internal computation.
|
|
|
|
# Because it can be recreated, it doesn't have to be preserved during
|
|
|
|
# sync and such changes can be ignored:
|
|
|
|
#
|
|
|
|
# RRULE:BYDAY=SU;COUNT=10;FREQ=WEEKLY | RRULE;X-EVOLUTION-ENDDATE=20080608T
|
|
|
|
# > 070000Z:BYDAY=SU;COUNT=10;FREQ=WEEK
|
|
|
|
# > LY
|
|
|
|
s/^(\w+)([^:\n]*);X-EVOLUTION-ENDDATE=[0-9TZ]*/$1$2/mg;
|
|
|
|
|
2012-10-03 10:55:29 +02:00
|
|
|
if ($scheduleworld || $egroupware || $synthesis || $addressbook || $funambol ||$googlesyncml || $googleeas || $googlecarddav || $mobical || $memotoo || $zimbra) {
|
2006-12-17 17:36:17 +01:00
|
|
|
# does not preserve X-EVOLUTION-UI-SLOT=
|
|
|
|
s/^(\w+)([^:\n]*);X-EVOLUTION-UI-SLOT=\d+/$1$2/mg;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($scheduleworld) {
|
|
|
|
# cannot distinguish EMAIL types
|
|
|
|
s/^EMAIL;TYPE=\w*/EMAIL/mg;
|
|
|
|
# replaces certain TZIDs with more up-to-date ones
|
|
|
|
s;TZID(=|:)/(scheduleworld.com|softwarestudio.org)/Olson_\d+_\d+/;TZID$1/foo.com/Olson_20000101_1/;mg;
|
|
|
|
}
|
|
|
|
|
2009-08-11 03:53:01 +02:00
|
|
|
if ($synthesis || $mobical) {
|
2006-12-17 17:36:17 +01:00
|
|
|
# only preserves ORG "Company", but loses "Department" and "Office"
|
|
|
|
s/^ORG:([^;:\n]+)(;[^\n]*)/ORG:$1/mg;
|
|
|
|
}
|
|
|
|
|
2007-02-11 12:11:34 +01:00
|
|
|
if ($funambol) {
|
|
|
|
# only preserves ORG "Company";"Department", but loses "Office"
|
|
|
|
s/^ORG:([^;:\n]+)(;[^;:\n]*)(;[^\n]*)/ORG:$1$2/mg;
|
2007-11-07 22:30:36 +01:00
|
|
|
# drops the second address line
|
|
|
|
s/^ADR(.*?):([^;]*?);[^;]*?;/ADR$1:$2;;/mg;
|
2008-02-24 18:47:02 +01:00
|
|
|
# has no concept of "preferred" phone number
|
|
|
|
s/^(TEL.*);TYPE=PREF/$1/mg;
|
2007-11-07 22:30:36 +01:00
|
|
|
}
|
|
|
|
|
2012-10-03 10:55:29 +02:00
|
|
|
if($googlesyncml || $googleeas || $googlecarddav) {
|
2012-07-18 15:35:46 +02:00
|
|
|
# ignore the PHOTO encoding data
|
|
|
|
s/^PHOTO(.*?): .*\n/PHOTO$1: [...]\n/mg;
|
|
|
|
}
|
|
|
|
|
2012-10-03 10:55:29 +02:00
|
|
|
if($googlesyncml || $googlecarddav) {
|
|
|
|
# FN property gets synthesized by Google.
|
2009-07-17 11:04:24 +02:00
|
|
|
s/^FN:.*\n/FN$1: [...]\n/mg;
|
2012-10-03 10:55:29 +02:00
|
|
|
}
|
|
|
|
|
CardDAV: use Apple/Google/CardDAV vCard flavor
In principle, CardDAV servers support arbitrary vCard 3.0
data. Extensions can be different and need to be preserved. However,
when multiple different clients or the server's Web UI interpret the
vCards, they need to agree on the semantic of these vCard extensions.
In practice, CardDAV was pushed by Apple and Apple clients are
probably the most common clients of CardDAV services. When the Google
Contacts Web UI creates or edits a contact, Google CardDAV will
send that data using the vCard flavor used by Apple.
Therefore it makes sense to exchange contacts with *all* CardDAV
servers using that format. This format could be made configurable in
SyncEvolution on a case-by-case basis; at the moment, it is
hard-coded.
During syncing, SyncEvolution takes care to translate between the
vCard flavor used internally (based on Evolution) and the CardDAV
vCard flavor. This mapping includes:
X-AIM/JABBER/... <-> IMPP + X-SERVICE-TYPE
Any IMPP property declared as X-SERVICE-TYPE=AIM will get
mapped to X-AIM. Same for others. Some IMPP service types
have no known X- property extension; they are stored in
EDS as IMPP. X- property extensions without a known X-SERVICE-TYPE
(for example, GaduGadu and Groupwise) are stored with
X-SERVICE-TYPE values chosen by SyncEvolution so that
Google CardDAV preserves them (GroupWise with mixed case
got translated by Google into Groupwise, so the latter is used).
Google always sends an X-ABLabel:Other for IMPP. This is ignored
because the service type overrides it.
The value itself also gets transformed during the mapping. IMPP uses
an URI as value, with a chat protocol (like "aim" or "xmpp") and
some protocol specific identifier. For each X- extension the
protocol is determined by the property name and the value is the
protocol specific identifier without URL encoding.
X-SPOUSE/MANAGER/ASSISTANT <-> X-ABRELATEDNAMES + X-ABLabel
The mapping is based on the X-ABLabel property attached to
the X-ABRELATEDNAMES property. This depends on the English
words "Spouse", "Manager", "Assistant" that Google CardDAV
and Apple devices seem to use regardless of the configured
language.
As with IMPP, only the subset of related names which have
a corresponding X- property extension get mapped. The rest
is stored in EDS using the X-ABRELATEDNAMES property.
X-ANNIVERSARY <-> X-ABDATE
Same here, with X-ABLabel:Anniversary as the special case
which gets mapped.
X-ABLabel parameter <-> property
CardDAV vCards have labels attached to arbitrary other properties
(TEL, ADR, X-ABDATE, X-ABRELATEDNAMES, ...) via vCard group tags:
item1.X-ABDATE:2010-01-01
item1.X-ABLabel:Anniversary
The advantage is that property values can contain arbitrary
characters, including line breaks and double quotation marks,
which is not possible in property parameters.
Neither EDS nor KDE (judging from the lack of responses on the
KDE-PIM mailing list) support custom labels. SyncEvolution could
have used grouping as it is done in CardDAV, but grouping is not
used much (not at all?) by the UIs working with the vCards in EDS
and KDE. It seemed easier to use a new X-ABLabel parameter.
Characters which cannot be stored in a parameter get converted
(double space to single space, line break to space, etc.) during
syncing. In practice, these characters don't appear in X-ABLabel
properties anyway because neither Apple nor Google UIs allow entering
them for custom labels.
The "Other" label is used by Google even in case where it adds no
information. For example, all XMPP properties have an associated
X-ABLabel=Other although the Web UI does not provide a means to edit
or show such a label. Editing the text before the value in the UI
changes the X-SERVICE-TYPE parameter value, not the X-ABLabel as for
other fields.
Therefore the "Other" label is ignored by removing it during syncing.
X-EVOLUTION-UI-SLOT (the parameter used in Evolution to determine the
order of properties in the UI) gets stored in CardDAV. The only exception
is Google CardDAV which got confused when an IMPP property had both
X-SERVICE-TYPE and X-EVOLUTION-UI-SLOT parameters set. For Google,
X-EVOLUTION-UI-SLOT is only sent on other properties and thus ordering
of chat information can get lost when syncing with Google.
CardDAV needs to use test data with the new CardDAV vCard flavor.
Most CardDAV servers can store EDS vCards and thus
Client::Source::*::testImport passed (with minor tweaks in
synccompare) when using the default eds_contact.vcf, but
Client::Sync::*::testItems fails when comparing synced data with test
cases when the synced data uses the native format and the test cases
are still the ones from EDS.
A libsynthesis with URLENCODE/DECODE() and sharedfield parameter for
<property> is needed.
2014-05-16 11:25:00 +02:00
|
|
|
# Properties and parameters are case-insensitive. ownCloud uses
|
|
|
|
# X-ABLABEL while everyone else uses X-ABLabel.
|
|
|
|
s/X-ABLABEL/X-ABLabel/g;
|
2012-10-03 10:55:29 +02:00
|
|
|
|
|
|
|
if ($googlesyncml) {
|
2009-07-17 11:04:24 +02:00
|
|
|
# Not support car type in telephone
|
|
|
|
s!^TEL\;TYPE=CAR(.*)\n!TEL$1\n!mg;
|
|
|
|
# some properties are lost
|
2013-02-08 15:40:22 +01:00
|
|
|
s/^(X-EVOLUTION-FILE-AS|NICKNAME|BDAY|CATEGORIES|CALURI|FBURL|GEO|ROLE|URL|X-AIM|X-EVOLUTION-UI-SLOT|X-ANNIVERSARY|X-ASSISTANT|X-EVOLUTION-BLOG-URL|X-EVOLUTION-VIDEO-URL|X-GROUPWISE|X-ICQ|X-GADUGADU|X-JABBER|X-MSN|X-SIP|X-SKYPE|X-MANAGER|X-SPOUSE|X-MOZILLA-HTML|X-YAHOO)(;[^:;\n]*)*:.*\r?\n?//gm;
|
2012-06-04 10:17:18 +02:00
|
|
|
}
|
2010-10-08 14:52:20 +02:00
|
|
|
|
2012-06-04 10:17:18 +02:00
|
|
|
if ($googlecaldav) {
|
2010-10-08 14:52:20 +02:00
|
|
|
#several properties are not preserved by Google in icalendar2.0 format
|
2013-04-22 12:55:08 +02:00
|
|
|
s/^(SEQUENCE|X-EVOLUTION-ALARM-UID|TRANSP)(;[^:;\n]*)*:.*\r?\n?//gm;
|
2011-05-06 17:26:25 +02:00
|
|
|
|
|
|
|
# Google adds calendar owner as attendee of meetings, regardless
|
|
|
|
# whether it was on the original attendee list. Ignore this
|
|
|
|
# during testing by removing all attendees with @googlemail.com
|
|
|
|
# email address.
|
|
|
|
s/^ATTENDEE.*googlemail.com\r?\n//gm;
|
2010-12-10 14:46:28 +01:00
|
|
|
}
|
2010-10-08 14:52:20 +02:00
|
|
|
|
2011-04-12 22:48:28 +02:00
|
|
|
if ($apple) {
|
|
|
|
# remove some parameters added by Apple Calendar server in CalDAV
|
|
|
|
s/^(ORGANIZER[^:]*);SCHEDULE-AGENT=NONE/$1/gm;
|
|
|
|
s/^(ORGANIZER[^:]*);SCHEDULE-STATUS=5.3/$1/gm;
|
|
|
|
# seems to require a fixed number of recurrences; hmm, okay...
|
|
|
|
s/^RRULE:COUNT=400;FREQ=DAILY/RRULE:FREQ=DAILY/gm;
|
|
|
|
}
|
|
|
|
|
2011-10-05 11:38:54 +02:00
|
|
|
if ($oracle) {
|
|
|
|
# remove extensions added by server
|
|
|
|
s/^(X-S1CS-RECURRENCE-COUNT)(;[^:;\n]*)*:.*\r?\n?//gm;
|
2012-02-21 13:16:42 +01:00
|
|
|
# ignore loss of LANGUAGE=xxx property in ATTENDEE
|
|
|
|
s/^ATTENDEE([^\n:]*);LANGUAGE=([^\n;:]*)/ATTENDEE$1/mg;
|
2011-10-05 11:38:54 +02:00
|
|
|
}
|
|
|
|
|
2012-02-20 16:51:37 +01:00
|
|
|
if ($radicale) {
|
|
|
|
# remove extensions added by server
|
|
|
|
s/^(X-RADICALE-NAME)(;[^:;\n]*)*:.*\r?\n?//gm;
|
|
|
|
}
|
|
|
|
|
2012-06-04 10:17:18 +02:00
|
|
|
if ($googlecaldav || $yahoo) {
|
2010-10-08 14:52:20 +02:00
|
|
|
# default status is CONFIRMED
|
|
|
|
s/^STATUS:CONFIRMED\r?\n?//gm;
|
2009-07-17 11:04:24 +02:00
|
|
|
}
|
|
|
|
|
2014-07-14 13:46:29 +02:00
|
|
|
# Ignore VALARM ACTION:NONE. This has to be added to avoid default alarms in Google CalDAV.
|
|
|
|
s/^BEGIN:VALARM\r?\n.*?^ACTION:NONE\r?\n.*?^END:VALARM\r?\n//ms;
|
2010-12-09 12:20:15 +01:00
|
|
|
|
2010-12-10 14:46:28 +01:00
|
|
|
if ($yahoo) {
|
|
|
|
s/^(X-MICROSOFT-[-A-Z0-9]*)(;[^:;\n]*)*:.*\r?\n?//gm;
|
2011-05-10 15:06:41 +02:00
|
|
|
# some properties cannot be stored
|
|
|
|
s/^(FN)(;[^:;\n]*)*:.*\r?\n?//gm;
|
2010-12-10 14:46:28 +01:00
|
|
|
}
|
|
|
|
|
2007-10-23 23:14:03 +02:00
|
|
|
if ($addressbook) {
|
|
|
|
# some properties cannot be stored
|
|
|
|
s/^(X-MOZILLA-HTML|X-EVOLUTION-FILE-AS|X-EVOLUTION-ANNIVERSARY|X-EVOLUTION-BLOG-URL|X-EVOLUTION-VIDEO-URL|X-GROUPWISE|ROLE|CATEGORIES|FBURL|CALURI|FN)(;[^:;\n]*)*:.*\r?\n?//gm;
|
|
|
|
# only some parts of ADR are preserved
|
|
|
|
my $type;
|
|
|
|
s/^ADR(.*?)\:(.*)/$type=($1 || ""); @_ = split(\/(?<!\\);\/, $2); "ADR:;;" . ($_[2] || "") . ";" . ($_[3] || "") . ";" . ($_[4] || "") . ";" . ($_[5] || "") . ";" . ($_[6] || "")/gme;
|
|
|
|
# TYPE=CAR not supported
|
|
|
|
s/;TYPE=CAR//g;
|
|
|
|
}
|
|
|
|
|
2006-12-17 17:36:17 +01:00
|
|
|
if ($synthesis) {
|
|
|
|
# does not preserve certain properties
|
2013-02-08 15:40:22 +01:00
|
|
|
s/^(FN|GEO|BDAY|X-MOZILLA-HTML|X-EVOLUTION-FILE-AS|X-AIM|NICKNAME|UID|PHOTO|CALURI|SEQUENCE|TRANSP|ORGANIZER|ROLE|FBURL|X-ANNIVERSARY|X-ASSISTANT|X-EVOLUTION-BLOG-URL|X-EVOLUTION-VIDEO-URL|X-GADUGADU|X-GROUPWISE|X-ICQ|X-JABBER|X-MANAGER|X-MSN|X-SIP|X-SKYPE|X-SPOUSE|X-YAHOO)(;[^:;\n]*)*:.*\r?\n?//gm;
|
2006-12-17 17:36:17 +01:00
|
|
|
# default ADR is HOME
|
|
|
|
s/^ADR;TYPE=HOME/ADR/gm;
|
|
|
|
# only some parts of N are preserved
|
2008-02-03 11:59:11 +01:00
|
|
|
s/^N((?:;[^;:]*)*)\:(.*)/@_ = split(\/(?<!\\);\/, $2); "N$1:$_[0];" . ($_[1] || "") . ";;" . ($_[3] || "")/gme;
|
2007-06-14 22:40:45 +02:00
|
|
|
# breaks lines at semicolons, which adds white space
|
|
|
|
while( s/^ADR:(.*); +/ADR:$1;/gm ) {}
|
2008-02-10 17:02:51 +01:00
|
|
|
# no attributes stored for ATTENDEEs
|
|
|
|
s/^ATTENDEE;.*?:/ATTENDEE:/msg;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($synthesis) {
|
|
|
|
# VALARM not supported
|
|
|
|
s/^BEGIN:VALARM.*?END:VALARM\r?\n?//msg;
|
2006-12-17 17:36:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if ($egroupware) {
|
|
|
|
# CLASS:PUBLIC is added if none exists (as in our test cases),
|
|
|
|
# several properties not preserved
|
|
|
|
s/^(BDAY|CATEGORIES|FBURL|PHOTO|FN|X-[A-Z-]*|CALURI|CLASS|NICKNAME|UID|TRANSP|PRIORITY|SEQUENCE)(;[^:;\n]*)*:.*\r?\n?//gm;
|
|
|
|
# org gets truncated
|
|
|
|
s/^ORG:([^;:\n]*);.*/ORG:$1/gm;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($funambol) {
|
|
|
|
# several properties are not preserved
|
2013-02-08 15:40:22 +01:00
|
|
|
s/^(CALURI|FBURL|GEO|X-MOZILLA-HTML|X-EVOLUTION-FILE-AS|X-AIM|X-EVOLUTION-BLOG-URL|X-EVOLUTION-VIDEO-URL|X-GROUPWISE|X-ICQ|X-YAHOO|X-GADUGADU|X-JABBER|X-MSN|X-SIP|X-SKYPE|X-ASSISTANT)(;[^:;\n]*)*:.*\r?\n?//gm;
|
2007-02-11 12:11:34 +01:00
|
|
|
|
|
|
|
# quoted-printable line breaks are =0D=0A, not just single =0A
|
|
|
|
s/(?<!=0D)=0A/=0D=0A/g;
|
2007-11-07 22:30:36 +01:00
|
|
|
# only three email addresses, fourth one from test case gets lost
|
|
|
|
s/^EMAIL:john.doe\@yet.another.world\n\r?//mg;
|
|
|
|
# this particular type is not preserved
|
|
|
|
s/ADR;TYPE=PARCEL:Test Box #3/ADR;TYPE=HOME:Test Box #3/;
|
2006-12-17 17:36:17 +01:00
|
|
|
}
|
2009-06-25 04:05:40 +02:00
|
|
|
if ($funambol) {
|
|
|
|
#several properties are not preserved by funambol server in icalendar2.0 format
|
|
|
|
s/^(UID|SEQUENCE|TRANSP|LAST-MODIFIED|X-EVOLUTION-ALARM-UID)(;[^:;\n]*)*:.*\r?\n?//gm;
|
2009-07-02 08:32:50 +02:00
|
|
|
if (/^BEGIN:VEVENT/m ) {
|
|
|
|
#several properties are not preserved by funambol server in itodo2.0 format and
|
|
|
|
s/^(RECURRENCE-ID|ATTENDEE)(;[^:;\n]*)*:.*\r?\n?//gm;
|
|
|
|
#REPEAT:0 is added by funambol server so ignore it
|
|
|
|
s/^(REPEAT:0).*\r?\n?//gm;
|
|
|
|
#CN parameter is lost by funambol server
|
|
|
|
s/^ORGANIZER([^:\n]*);CN=([^:\n]*)(;[^:\n])*:(.*\r?\n?)/ORGANIZER$1$3:$4/mg;
|
|
|
|
}
|
2009-07-01 04:48:13 +02:00
|
|
|
|
|
|
|
if (/^BEGIN:VTODO/m ) {
|
|
|
|
#several properties are not preserved by funambol server in itodo2.0 format and
|
2009-07-02 08:32:50 +02:00
|
|
|
s/^(STATUS|URL)(;[^:;\n]*)*:.*\r?\n?//gm;
|
2009-07-01 04:48:13 +02:00
|
|
|
|
|
|
|
#some new properties are added by funambol server
|
|
|
|
s/^(CLASS:PUBLIC|PERCENT-COMPLETE:0).*\r?\n?//gm;
|
|
|
|
}
|
2009-06-25 04:05:40 +02:00
|
|
|
}
|
2006-12-17 17:36:17 +01:00
|
|
|
|
2009-12-11 10:52:22 +01:00
|
|
|
if($nokia_7210c) {
|
|
|
|
if (/BEGIN:VCARD/m) {
|
|
|
|
#ignore PREF, as it will added by default
|
|
|
|
s/^TEL([^:\n]*);TYPE=PREF/TEL$1/mg;
|
|
|
|
#remove non-digit prefix in TEL
|
|
|
|
s/^TEL([^:\n]*):(\D*)/TEL$1:/mg;
|
|
|
|
#properties N mismatch, sometimes lost part of components
|
|
|
|
s/^(N|X-EVOLUTION-FILE-AS):.*\r?\n?/$1:[...]\n/gm;
|
|
|
|
#strip spaces in 'NOTE'
|
|
|
|
while (s/^(NOTE|DESCRIPTION):(\S+)[\t ]+(\S+)/$1:$2$3/mg) {}
|
|
|
|
#preserve 80 chars in NOTE
|
|
|
|
s/^NOTE:(.{70}).*\r?\n?/NOTE:$1\n/mg;
|
|
|
|
#preserve one ADDR
|
|
|
|
|
|
|
|
# ignore the PHOTO encoding data, sometimes it add a default photo
|
|
|
|
s/^PHOTO(.*?): .*\n//mg;
|
|
|
|
#s/^(ADR)([^:;\n]*)(;TYPE=[^:\n]*)?:.*\r?\n?/$1:$4\n/mg;
|
|
|
|
|
|
|
|
#lost properties
|
|
|
|
s/^(NICKNAME|CATEGORIES|CALURI|FBURL|ROLE|X-AIM|X-ANNIVERSARY|X-ASSISTANT|X-EVOLUTION-BLOG-URL|X-EVOLUTION-VIDEO-URL|X-GROUPWISE|X-ICQ|X-MANAGER|X-SPOUSE|X-MOZILLA-HTML|X-YAHOO)(;[^:;\n]*)*:.*\r?\n?//gm;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (/^BEGIN:VEVENT/m ) {
|
|
|
|
#The properties phones add by default
|
|
|
|
s/^(PRIORITY|CATEGORIES)(;[^:;\n]*)*:.*\r?\n?//gm;
|
|
|
|
#strip spaces in 'DESCRIPTION'
|
|
|
|
while (s/^DESCRIPTION:(\S+)[\t ]+(\S+)/DESCRIPTION:$1$2/mg) {}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (/^BEGIN:VTODO/m) {
|
|
|
|
#mismatch properties
|
|
|
|
s/^(PRIORITY)(;[^:;\n]*)*:.*\r?\n?/$1:[...]\n/gm;
|
|
|
|
#lost properties
|
|
|
|
s/^(STATUS|DTSTART|CATEGORIES)(;[^:;\n]*)*:.*\r?\n?//gm;
|
|
|
|
}
|
|
|
|
|
|
|
|
#Testing with phones using vcalendar, do not support UID
|
|
|
|
s/^(UID|CLASS|SEQUENCE|TRANSP)(;[^:;\n]*)*:.*\r?\n?//gm;
|
|
|
|
}
|
|
|
|
|
2010-03-08 07:43:16 +01:00
|
|
|
if ($ovi) {
|
|
|
|
if (/^BEGIN:VCARD/m) {
|
|
|
|
#lost properties
|
|
|
|
s/^(X-AIM|CALURI|URL|FBURL|PHOTO|EMAIL)(;[^:;\n]*)*:.*\r?\n?//gm;
|
|
|
|
#FN value mismatch (reordring and adding , by the server)
|
|
|
|
s/^FN:.*\r?\n?/FN:[...]\n/gm;
|
|
|
|
#X-EVOLUTION-FILE-AS adding '\' by the server
|
|
|
|
while (s/^X-EVOLUTION-FILE-AS:(.*)\\(.*)/X-EVOLUTION-FILE-AS:$1$2/gm) {}
|
|
|
|
|
|
|
|
# does not preserve X-EVOLUTION-UI-SLOT=
|
|
|
|
s/^(\w+)([^:\n]*);X-EVOLUTION-UI-SLOT=\d+/$1$2/mg;
|
|
|
|
|
|
|
|
# does not preserve third ADR
|
|
|
|
s/^ADR:Test Box #3.*\n\r?//mg;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (/^BEGIN:VEVENT/m) {
|
|
|
|
#Testing with vcalendar, do not support UID
|
|
|
|
s/^(UID|SEQUENCE|TRANSP)(;[^:;\n]*)*:.*\r?\n?//gm;
|
|
|
|
#Add PRORITY by default
|
|
|
|
s/^(PRIORITY)(;[^:;\n]*)*:.*\r?\n?//gm;
|
|
|
|
# VALARM not supported
|
|
|
|
s/^BEGIN:VALARM.*?END:VALARM\r?\n?//msg;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (/^BEGIN:VTODO/m) {
|
|
|
|
#Testing with vcalendar, do not support UID
|
|
|
|
s/^(UID|SEQUENCE|PERCENT-COMPLETE)(;[^:;\n]*)*:.*\r?\n?//gm;
|
|
|
|
#Mismatch DTSTART, COMPLETED
|
|
|
|
s/^(DTSTART|COMPLETED)(;[^:;\n]*)*:.*\r?\n?/$1:[...]\n/gm;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-12-11 10:52:22 +01:00
|
|
|
if ($funambol || $egroupware || $nokia_7210c) {
|
2006-12-17 17:36:17 +01:00
|
|
|
# NOTE may be truncated due to length resistrictions
|
|
|
|
s/^(NOTE(;[^:;\n]*)*:.{0,160}).*(\r?\n?)/$1$3/gm;
|
|
|
|
}
|
2009-09-04 03:07:05 +02:00
|
|
|
if ($memotoo) {
|
|
|
|
if (/^BEGIN:VCARD/m ) {
|
2013-02-08 15:40:22 +01:00
|
|
|
s/^(FN|FBURL|GEO|CALURI|ROLE|X-MOZILLA-HTML|X-EVOLUTION-BLOG-URL|X-EVOLUTION-VIDEO-URL|X-GADUGADU|X-JABBER|X-MSN|X-SIP|X-SKYPE|X-GROUPWISE)(;[^:;\n]*)*:.*\r?\n?//gm;
|
2011-10-27 18:29:33 +02:00
|
|
|
# s/^(FN|FBURL|CALURI|CATEGORIES|ROLE|X-MOZILLA-HTML|X-EVOLUTION-FILE-AS|X-EVOLUTION-BLOG-URL|X-EVOLUTION-VIDEO-URL|X-GADUGADU|X-JABBER|X-MSN|X-SIP|X-SKYPE|X-GROUPWISE)(;[^:;\n]*)*:.*\r?\n?//gm;
|
2009-09-04 03:07:05 +02:00
|
|
|
# strip 'TYPE=HOME'
|
|
|
|
s/^URL([^\n:]*);TYPE=HOME/URL$1/mg;
|
|
|
|
s/^EMAIL([^\n:]*);TYPE=HOME/EMAIL$1/mg;
|
|
|
|
}
|
|
|
|
if (/^BEGIN:VEVENT/m ) {
|
|
|
|
s/^(UID|SEQUENCE|TRANSP|RECURRENCE-ID|X-EVOLUTION-ALARM-UID|ORGANIZER)(;[^:;\n]*)*:.*\r?\n?//gm;
|
|
|
|
# some parameters of 'ATTENDEE' will be lost by server
|
|
|
|
s/^ATTENDEE([^\n:]*);CUTYPE=([^\n;:]*)/ATTENDEE$1/mg;
|
|
|
|
s/^ATTENDEE([^\n:]*);LANGUAGE=([^\n;:]*)/ATTENDEE$1/mg;
|
|
|
|
s/^ATTENDEE([^\n:]*);ROLE=([^\n;:]*)/ATTENDEE$1/mg;
|
|
|
|
s/^ATTENDEE([^\n:]*);RSVP=([^\n;:]*)/ATTENDEE$1/mg;
|
|
|
|
s/^ATTENDEE([^\n:]*);CN=([^\n;:]*)/ATTENDEE$1/mg;
|
|
|
|
s/^ATTENDEE([^\n:]*);PARTSTAT=([^\n;:]*)/ATTENDEE$1/mg;
|
|
|
|
if (/^BEGIN:VALARM/m ) {
|
|
|
|
s/^(DESCRIPTION)(;[^:;\n]*)*:.*\r?\n?//mg;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (/^BEGIN:VTODO/m ) {
|
|
|
|
s/^(UID|SEQUENCE|URL|CLASS|PRIORITY)(;[^:;\n]*)*:.*\r?\n?//gm;
|
|
|
|
s/^PERCENT-COMPLETE:0\r?\n?//gm;
|
|
|
|
}
|
|
|
|
}
|
2009-08-11 03:53:01 +02:00
|
|
|
if ($mobical) {
|
2013-02-08 15:40:22 +01:00
|
|
|
s/^(CALURI|CATEGORIES|FBURL|GEO|NICKNAME|X-MOZILLA-HTML|X-EVOLUTION-FILE-AS|X-ANNIVERSARY|X-ASSISTANT|X-EVOLUTION-BLOG-URL|X-EVOLUTION-VIDEO-URL|X-GROUPWISE|X-ICQ|X-GADUGADU|X-JABBER|X-MSN|X-SIP|X-SKYPE|X-MANAGER|X-SPOUSE|X-YAHOO|X-AIM)(;[^:;\n]*)*:.*\r?\n?//gm;
|
2009-08-11 03:53:01 +02:00
|
|
|
|
|
|
|
# some workrounds here for mobical's bug
|
|
|
|
s/^(FN|BDAY)(;[^:;\n]*)*:.*\r?\n?//gm;
|
|
|
|
|
|
|
|
if (/^BEGIN:VEVENT/m ) {
|
|
|
|
s/^(UID|SEQUENCE|CLASS|TRANSP|RECURRENCE-ID|ATTENDEE|ORGANIZER|AALARM|DALARM)(;[^:;\n]*)*:.*\r?\n?//gm;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (/^BEGIN:VTODO/m ) {
|
|
|
|
s/^(UID|SEQUENCE|DTSTART|URL|PERCENT-COMPLETE|CLASS)(;[^:;\n]*)*:.*\r?\n?//gm;
|
|
|
|
s/^PRIORITY:0\r?\n?//gm;
|
|
|
|
}
|
|
|
|
}
|
2006-12-17 17:36:17 +01:00
|
|
|
|
ZYB: Ignore some properties comparison(MB#2424)
Some properties in vcard21 are dismissed by ZYB server.
So we ignore them in synccompare:
CALURI, CATEGORIES, FBURL, NICKNAME, X-MOZILLA-HTML,
PHOTO, X-EVOLUTION-FILE-AS, X-ANNIVERSARY,
X-ASSISTANT, X-EVOLUTION-BLOG-URL, X-EVOLUTION-VIDEO-URL,
X-GROUPWISE, X-ICQ, X-MANAGER, X-SPOUSE, X-YAHOO, X-AIM
2009-07-13 09:35:21 +02:00
|
|
|
if ($zyb) {
|
|
|
|
s/^(CALURI|CATEGORIES|FBURL|NICKNAME|X-MOZILLA-HTML|PHOTO|X-EVOLUTION-FILE-AS|X-ANNIVERSARY|X-ASSISTANT|X-EVOLUTION-BLOG-URL|X-EVOLUTION-VIDEO-URL|X-GROUPWISE|X-ICQ|X-MANAGER|X-SPOUSE|X-YAHOO|X-AIM)(;[^:;\n]*)*:.*\r?\n?//gm;
|
|
|
|
}
|
|
|
|
|
2011-08-12 10:27:31 +02:00
|
|
|
if ($exchange) {
|
|
|
|
# unsupported properties
|
2011-08-12 10:47:51 +02:00
|
|
|
s/^(SEQUENCE|X-EVOLUTION-ALARM-UID)(;[^:;\n]*)*:.*\r?\n?//gm;
|
2011-08-12 10:27:31 +02:00
|
|
|
# added properties which can be ignored (?)
|
|
|
|
s/^(X-MEEGO-ACTIVESYNCD-[a-zA-Z]*)(;[^:;\n]*)*:.*\r?\n?//gm;
|
|
|
|
# ORGANIZER added - remove and thus ignore if we have no ATTENDEEs
|
|
|
|
if (!/^ATTENDEE/m) {
|
|
|
|
s/^(ORGANIZER)(;[^:;\n]*)*:.*\r?\n?//gm;
|
|
|
|
}
|
2011-08-12 10:47:51 +02:00
|
|
|
# ignore added VALARM DESCRIPTION
|
|
|
|
s/^DESCRIPTION:Reminder\n//m;
|
2011-08-12 10:27:31 +02:00
|
|
|
}
|
|
|
|
|
2012-06-04 10:17:18 +02:00
|
|
|
if ($googleeas) {
|
2012-08-31 21:14:31 +02:00
|
|
|
# properties not supported by Google
|
|
|
|
s/^(X-EVOLUTION-FILE-AS|CATEGORIES)(;[^:;\n]*)*:.*\r?\n?//gm;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($googleeas || $exchange) {
|
|
|
|
# properties not supported by ActiveSync
|
|
|
|
s/^(FN)(;[^:;\n]*)*:.*\r?\n?//gm;
|
2012-06-04 10:17:18 +02:00
|
|
|
}
|
|
|
|
|
2013-02-08 15:40:22 +01:00
|
|
|
if ($googleeas || $exchange) {
|
|
|
|
# properties not supported by ActiveSync and/or activesyncd
|
|
|
|
s/^(GEO)(;[^:;\n]*)*:.*\r?\n?//gm;
|
|
|
|
}
|
|
|
|
|
2014-10-10 12:09:11 +02:00
|
|
|
if ($akonadi) {
|
|
|
|
# Akonadi adds empty GEO propery....
|
|
|
|
s/^(GEO)(;[^:;\n]*)*:0+\.0+;0+\.0+\r?\n?//gm;
|
|
|
|
# ... and rounds other values.
|
|
|
|
s/^(GEO(?:;[^:;\n]*)*):([-+]?\d+)\.\d+;([-+]?\d+)\.\d+/$1:$2;$3/gm;
|
|
|
|
# does not preserve X-EVOLUTION-UI-SLOT=
|
|
|
|
s/^(\w+)([^:\n]*);X-EVOLUTION-UI-SLOT=\d+/$1$2/mg;
|
|
|
|
}
|
|
|
|
|
2012-08-02 08:36:25 +02:00
|
|
|
if ($googleeas || $exchange) {
|
2012-07-18 15:38:38 +02:00
|
|
|
# temporarily ignore modified properties
|
2012-07-20 14:55:06 +02:00
|
|
|
s/^(BDAY|X-ANNIVERSARY)(;[^:;\n]*)*:.*\r?\n?//gm;
|
2012-07-18 15:38:38 +02:00
|
|
|
}
|
|
|
|
|
2007-05-08 21:32:16 +02:00
|
|
|
# treat X-MOZILLA-HTML=FALSE as if the property didn't exist
|
|
|
|
s/^X-MOZILLA-HTML:FALSE\r?\n?//gm;
|
|
|
|
|
2006-12-17 17:36:17 +01:00
|
|
|
my @formatted = ();
|
|
|
|
|
|
|
|
# Modify lines to cover not more than
|
|
|
|
# $width characters by folding lines (as done for the N or SUMMARY above),
|
|
|
|
# but also indent each inner BEGIN/END block by 2 spaces
|
|
|
|
# and finally sort the lines.
|
|
|
|
# We need to keep a stack of open blocks in @formatted:
|
|
|
|
# - BEGIN creates another open block
|
|
|
|
# - END closes it, sorts it, and adds as single string to the parent block
|
|
|
|
push @formatted, [];
|
|
|
|
foreach $_ (split /\n/, $_) {
|
|
|
|
if (/^BEGIN:/) {
|
|
|
|
# start a new block
|
|
|
|
push @formatted, [];
|
|
|
|
}
|
|
|
|
|
|
|
|
my $spaces = " " x ($#formatted - 1);
|
2014-05-06 16:55:14 +02:00
|
|
|
|
|
|
|
# Ignore group tags during folding, add back before indenting.
|
|
|
|
/^([^.:;]+\.)?(.*)/s;
|
|
|
|
my $tag = $1;
|
|
|
|
$_ = $2;
|
2006-12-17 17:36:17 +01:00
|
|
|
my $thiswidth = $width -1 - length($spaces);
|
|
|
|
$thiswidth = 1 if $thiswidth <= 0;
|
|
|
|
s/(.{$thiswidth})(?!$)/$1\n /g;
|
2014-05-06 16:55:14 +02:00
|
|
|
$_ = $tag . $_;
|
2006-12-17 17:36:17 +01:00
|
|
|
s/^(.*)$/$spaces$1/mg;
|
|
|
|
push @{$formatted[$#formatted]}, $_;
|
|
|
|
|
|
|
|
if (/^\s*END:/) {
|
|
|
|
my $block = pop @formatted;
|
|
|
|
my $begin = shift @{$block};
|
|
|
|
my $end = pop @{$block};
|
|
|
|
|
|
|
|
# Keep begin/end as first/last line,
|
|
|
|
# inbetween sort, but so that N or SUMMARY are
|
|
|
|
# at the top. This ensures that the order of items
|
|
|
|
# is the same, even if individual properties differ.
|
|
|
|
# Also put indented blocks at the end, not the top.
|
|
|
|
sub numspaces {
|
|
|
|
my $str = shift;
|
|
|
|
$str =~ /^(\s*)/;
|
|
|
|
return length($1);
|
|
|
|
}
|
2014-05-06 16:55:14 +02:00
|
|
|
# Sort lines without group tag before lines without group tag.
|
|
|
|
# When both lines have group tags, sort based on line without
|
|
|
|
# group tag, then regroup related items after sorting.
|
|
|
|
sub cmplines {
|
|
|
|
my $a = shift;
|
|
|
|
my $b = shift;
|
|
|
|
$a =~ s/^[^.:;]+\.//;
|
|
|
|
$b =~ s/^[^.:;]+\.//;
|
|
|
|
return $a cmp $b;
|
|
|
|
}
|
|
|
|
my @body;
|
|
|
|
my $isimportant;
|
|
|
|
foreach $_ (@{$block}) {
|
|
|
|
$isimportant = ($_ =~ /^\s*(N|SUMMARY):/);
|
|
|
|
/^(\s*)([^.:;]+\.)?(.*)/s;
|
|
|
|
push @body, [$isimportant, length($1), $2, $3];
|
|
|
|
}
|
|
|
|
my @sorted = sort( { ($a->[1] - $b->[1]) || # Compare indention, more indented last.
|
|
|
|
($b->[0] - $a->[0]) || # Compare importance, less important last.
|
|
|
|
$a->[3] cmp $b->[3] } # Compare property name, parameters and value without group tag.
|
|
|
|
@body );
|
|
|
|
|
|
|
|
# Combine lines with the same group tag.
|
|
|
|
my %tags;
|
|
|
|
my @tagged;
|
|
|
|
my $tag;
|
|
|
|
my $entry;
|
|
|
|
my $index;
|
|
|
|
foreach (@sorted) {
|
|
|
|
$tag = $_->[2];
|
|
|
|
# Has a line a group tag?
|
|
|
|
if ($tag) {
|
|
|
|
# Same as one found before?
|
|
|
|
$index = $tags{$tag};
|
|
|
|
if (defined($index)) {
|
|
|
|
# Append to previous instance of the tag, keeping tag indices the same.
|
|
|
|
push @{$tagged[$index]}, $_;
|
|
|
|
} else {
|
|
|
|
# Add at end, remember index for next line with the same tag.
|
|
|
|
push @tagged, $_;
|
|
|
|
$tags{$tag} = $#tagged;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
push @tagged, $_;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# Convert back into individual, indented text lines.
|
|
|
|
my @expanded;
|
|
|
|
foreach (@tagged) {
|
|
|
|
if ($_->[2]) {
|
2014-05-16 10:15:41 +02:00
|
|
|
if ($#{$_} == 4) {
|
|
|
|
# Simplify IMPP + X-ABLabel:Other to just IMPP without group tag.
|
|
|
|
# For the sake of simplicity we only do that if the number of
|
|
|
|
# grouped properties is exactly two. Otherwise we would have
|
|
|
|
# to search in the list of extra properties.
|
|
|
|
if ($_->[3] =~ /^IMPP[;:]/ &&
|
|
|
|
$_->[4][3] =~ /^X-ABLabel:Other$/) {
|
|
|
|
splice(@{$_}, 4);
|
|
|
|
}
|
|
|
|
}
|
2014-05-06 16:55:14 +02:00
|
|
|
if ($#{$_} == 3) {
|
2014-05-19 11:48:55 +02:00
|
|
|
# If the last remaining property is X-ABLabel, then ignore it.
|
|
|
|
# We ignore empty properties, which can cause their labels to
|
|
|
|
# be left as redundant information (happens with Google CardDAV
|
|
|
|
# when sending an empty URL).
|
|
|
|
if ($_->[3] =~ "X-ABLabel:") {
|
|
|
|
next;
|
|
|
|
}
|
|
|
|
# Remove redundant group tag from other properties.
|
2014-05-06 16:55:14 +02:00
|
|
|
$_->[2] = "";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
push @expanded, (" " x $_->[1]) . ($_->[2] ? "- " : "") . $_->[3];
|
|
|
|
if ($#{$_} > 3) {
|
|
|
|
foreach ($_->[4,-1]) {
|
|
|
|
push @expanded, (" " x $_->[1]) . " " . $_->[3];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# Create one BEGIN/END block.
|
2006-12-17 17:36:17 +01:00
|
|
|
$_ = join("\n",
|
|
|
|
$begin,
|
2014-05-06 16:55:14 +02:00
|
|
|
@expanded,
|
2006-12-17 17:36:17 +01:00
|
|
|
$end);
|
2014-05-06 16:55:14 +02:00
|
|
|
|
2006-12-17 17:36:17 +01:00
|
|
|
push @{$formatted[$#formatted]}, $_;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-11-26 14:04:40 +01:00
|
|
|
return ${$formatted[0]}[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
# parameters: text, width to use for reformatted lines
|
|
|
|
# returns list of lines without line breaks
|
|
|
|
sub Normalize {
|
|
|
|
$_ = shift;
|
|
|
|
my $width = shift;
|
|
|
|
|
|
|
|
s/\r//g;
|
|
|
|
|
|
|
|
my @items = ();
|
|
|
|
|
|
|
|
# split into individual items
|
|
|
|
foreach $_ ( split( /(?:(?<=\nEND:VCARD)|(?<=\nEND:VCALENDAR))\n*/ ) ) {
|
|
|
|
if (/END:VEVENT\s+BEGIN:VEVENT/s) {
|
|
|
|
# remove multiple events from calendar item
|
|
|
|
s/(BEGIN:VEVENT.*END:VEVENT\n)//s;
|
|
|
|
my $events = $1;
|
|
|
|
my $calendar = $_;
|
|
|
|
my $event;
|
|
|
|
# inject every single one back into the calendar and process the result
|
|
|
|
foreach $event ( split ( /(?:(?<=\nEND:VEVENT))\n*/, $events ) ) {
|
|
|
|
$_ = $calendar;
|
|
|
|
s/\nEND:VCALENDAR/\n$event\nEND:VCALENDAR/;
|
|
|
|
push @items, NormalizeItem($width, $_);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
# already a single item
|
|
|
|
push @items, NormalizeItem($width, $_);
|
|
|
|
}
|
|
|
|
}
|
2006-12-17 17:36:17 +01:00
|
|
|
|
2010-11-26 14:04:40 +01:00
|
|
|
return split( /\n/, join( "\n\n", sort @items ));
|
2006-12-17 17:36:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
# number of columns available for output:
|
|
|
|
# try tput without printing the shells error if not found,
|
|
|
|
# default to 80
|
2007-04-02 20:57:19 +02:00
|
|
|
my $columns = `which tput >/dev/null 2>/dev/null && tput 2>/dev/null && tput cols`;
|
2006-12-17 17:36:17 +01:00
|
|
|
if ($? || !$columns) {
|
|
|
|
$columns = 80;
|
|
|
|
}
|
|
|
|
|
|
|
|
if($#ARGV > 1) {
|
|
|
|
# error
|
|
|
|
Usage();
|
|
|
|
exit 1;
|
|
|
|
} elsif($#ARGV == 1) {
|
|
|
|
# comparison
|
|
|
|
|
|
|
|
my ($file1, $file2) = ($ARGV[0], $ARGV[1]);
|
|
|
|
|
|
|
|
my $singlewidth = int(($columns - 3) / 2);
|
|
|
|
$columns = $singlewidth * 2 + 3;
|
2010-02-07 19:10:34 +01:00
|
|
|
my @normal1;
|
|
|
|
my @normal2;
|
|
|
|
|
|
|
|
if (-d $file1 && -d $file2) {
|
|
|
|
# Both "files" are really directories of individual files.
|
|
|
|
# Don't include files in the comparison which are known
|
|
|
|
# to be identical because the refer to the same inode.
|
2012-05-29 10:54:06 +02:00
|
|
|
# - build map from inode to filename(s) (each inode might be used more than once!)
|
2010-02-07 19:10:34 +01:00
|
|
|
my %files1;
|
|
|
|
my %files2;
|
|
|
|
my @content1;
|
|
|
|
my @content2;
|
|
|
|
my $inode;
|
|
|
|
my $fullname;
|
|
|
|
my $entry;
|
|
|
|
opendir(my $dh, $file1) || die "cannot read $file1: $!";
|
|
|
|
foreach $entry (grep { -f "$file1/$_" } readdir($dh)) {
|
|
|
|
$fullname = "$file1/$entry";
|
|
|
|
$inode = (stat($fullname))[1];
|
2012-05-29 10:54:06 +02:00
|
|
|
if (!$files1{$inode}) {
|
|
|
|
$files1{$inode} = [];
|
|
|
|
}
|
|
|
|
push(@{$files1{$inode}}, $entry);
|
2010-02-07 19:10:34 +01:00
|
|
|
}
|
|
|
|
closedir($dh);
|
|
|
|
# - remove common files, read others
|
|
|
|
opendir(my $dh, $file2) || die "cannot read $file2: $!";
|
|
|
|
foreach $entry (grep { -f "$file2/$_" } readdir($dh)) {
|
|
|
|
$fullname = "$file2/$entry";
|
|
|
|
$inode = (stat($fullname))[1];
|
2012-05-29 10:54:06 +02:00
|
|
|
if (@{$files1{$inode}}) {
|
|
|
|
# randomly match against the last file
|
|
|
|
pop @{$files1{$inode}};
|
2010-02-07 19:10:34 +01:00
|
|
|
} else {
|
|
|
|
open(IN, "<:utf8", "$fullname") || die "$fullname: $!";
|
|
|
|
push @content2, <IN>;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
# - read remaining entries from first dir
|
2012-05-29 10:54:06 +02:00
|
|
|
foreach my $array (values %files1) {
|
|
|
|
foreach $entry (@{$array}) {
|
|
|
|
$fullname = "$file1/$entry";
|
|
|
|
open(IN, "<:utf8", "$fullname") || die "$fullname: $!";
|
|
|
|
push @content1, <IN>;
|
|
|
|
}
|
2010-02-07 19:10:34 +01:00
|
|
|
}
|
2010-02-26 13:18:42 +01:00
|
|
|
my $content1 = join("", @content1);
|
|
|
|
my $content2 = join("", @content2);
|
|
|
|
@normal1 = Normalize($content1, $singlewidth);
|
|
|
|
@normal2 = Normalize($content2, $singlewidth);
|
2010-02-07 19:10:34 +01:00
|
|
|
} else {
|
|
|
|
if (-d $file1) {
|
|
|
|
open(IN1, "-|:utf8", "find $file1 -type f -print0 | xargs -0 cat") || die "$file1: $!";
|
|
|
|
} else {
|
|
|
|
open(IN1, "<:utf8", $file1) || die "$file1: $!";
|
|
|
|
}
|
|
|
|
if (-d $file2) {
|
|
|
|
open(IN2, "-|:utf8", "find $file2 -type f -print0 | xargs -0 cat") || die "$file2: $!";
|
|
|
|
} else {
|
|
|
|
open(IN2, "<:utf8", $file2) || die "$file2: $!";
|
|
|
|
}
|
2010-12-01 15:00:37 +01:00
|
|
|
my $buf1 = join("", <IN1>);
|
|
|
|
my $buf2 = join("", <IN2>);
|
|
|
|
@normal1 = Normalize($buf1, $singlewidth);
|
|
|
|
@normal2 = Normalize($buf2, $singlewidth);
|
2010-02-07 19:10:34 +01:00
|
|
|
close(IN1);
|
|
|
|
close(IN2);
|
|
|
|
}
|
2006-12-17 17:36:17 +01:00
|
|
|
|
|
|
|
# Produce output where each line is marked as old (aka remove) with o,
|
|
|
|
# as new (aka added) with n, and as unchanged with u at the beginning.
|
|
|
|
# This allows simpler processing below.
|
2007-03-29 22:39:54 +02:00
|
|
|
my $res = 0;
|
|
|
|
if (0) {
|
|
|
|
# $_ = `diff "--old-line-format=o %L" "--new-line-format=n %L" "--unchanged-line-format=u %L" "$normal1" "$normal2"`;
|
|
|
|
# $res = $?;
|
|
|
|
} else {
|
|
|
|
# convert into same format as diff above - this allows reusing the
|
|
|
|
# existing output formatting code
|
|
|
|
my $diffs_ref = Algorithm::Diff::sdiff(\@normal1, \@normal2);
|
|
|
|
@_ = ();
|
|
|
|
my $hunk;
|
|
|
|
foreach $hunk ( @{$diffs_ref} ) {
|
|
|
|
my ($type, $left, $right) = @{$hunk};
|
|
|
|
if ($type eq "-") {
|
|
|
|
push @_, "o $left";
|
|
|
|
$res = 1;
|
|
|
|
} elsif ($type eq "+") {
|
|
|
|
push @_, "n $right";
|
|
|
|
$res = 1;
|
|
|
|
} elsif ($type eq "c") {
|
|
|
|
push @_, "o $left";
|
|
|
|
push @_, "n $right";
|
|
|
|
$res = 1;
|
|
|
|
} else {
|
|
|
|
push @_, "u $left";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$_ = join("\n", @_);
|
|
|
|
}
|
2006-12-17 17:36:17 +01:00
|
|
|
|
|
|
|
if ($res) {
|
2009-03-11 16:30:23 +01:00
|
|
|
print $ENV{CLIENT_TEST_HEADER};
|
2007-11-08 21:56:37 +01:00
|
|
|
printf "%*s | %s\n", $singlewidth,
|
|
|
|
($ENV{CLIENT_TEST_LEFT_NAME} || "before sync"),
|
|
|
|
($ENV{CLIENT_TEST_RIGHT_NAME} || "after sync");
|
|
|
|
printf "%*s <\n", $singlewidth,
|
|
|
|
($ENV{CLIENT_TEST_REMOVED} || "removed during sync");
|
|
|
|
printf "%*s > %s\n", $singlewidth, "",
|
|
|
|
($ENV{CLIENT_TEST_ADDED} || "added during sync");
|
2007-03-29 22:39:54 +02:00
|
|
|
print "-" x $columns, "\n";
|
|
|
|
|
2006-12-17 17:36:17 +01:00
|
|
|
# fix confusing output like:
|
|
|
|
# BEGIN:VCARD BEGIN:VCARD
|
|
|
|
# > N:new;entry
|
|
|
|
# > FN:new
|
|
|
|
# > END:VCARD
|
|
|
|
# >
|
|
|
|
# > BEGIN:VCARD
|
|
|
|
# and replace it with:
|
|
|
|
# > BEGIN:VCARD
|
|
|
|
# > N:new;entry
|
|
|
|
# > FN:new
|
|
|
|
# > END:VCARD
|
|
|
|
#
|
|
|
|
# BEGIN:VCARD BEGIN:VCARD
|
|
|
|
#
|
|
|
|
# With the o/n/u markup this presents itself as:
|
|
|
|
# u BEGIN:VCARD
|
|
|
|
# n N:new;entry
|
|
|
|
# n FN:new
|
|
|
|
# n END:VCARD
|
|
|
|
# n
|
|
|
|
# n BEGIN:VCARD
|
|
|
|
#
|
2007-03-29 22:39:54 +02:00
|
|
|
# The alternative case is also possible:
|
|
|
|
# o END:VCARD
|
|
|
|
# o
|
|
|
|
# o BEGIN:VCARD
|
|
|
|
# o N:old;entry
|
|
|
|
# u END:VCARD
|
|
|
|
|
|
|
|
# case one above
|
2006-12-17 17:36:17 +01:00
|
|
|
while( s/^u BEGIN:(VCARD|VCALENDAR)\n((?:^n .*\n)+?)^n BEGIN:/n BEGIN:$1\n$2u BEGIN:/m) {}
|
2007-03-29 22:39:54 +02:00
|
|
|
# same for the other direction
|
2006-12-17 17:36:17 +01:00
|
|
|
while( s/^u BEGIN:(VCARD|VCALENDAR)\n((?:^o .*\n)+?)^o BEGIN:/o BEGIN:$1\n$2u BEGIN:/m) {}
|
|
|
|
|
2007-03-29 22:39:54 +02:00
|
|
|
# case two
|
|
|
|
while( s/^o END:(VCARD|VCALENDAR)\n((?:^o .*\n)+?)^u END:/u END:$1\n$2o END:/m) {}
|
|
|
|
while( s/^n END:(VCARD|VCALENDAR)\n((?:^n .*\n)+?)^u END:/u END:$1\n$2n END:/m) {}
|
|
|
|
|
2006-12-17 17:36:17 +01:00
|
|
|
# split at end of each record
|
|
|
|
my $spaces = " " x $singlewidth;
|
|
|
|
foreach $_ (split /(?:(?<=. END:VCARD\n)|(?<=. END:VCALENDAR\n))(?:^. \n)*/m, $_) {
|
|
|
|
# ignore unchanged records
|
|
|
|
if (!length($_) || /^((u [^\n]*\n)*(u [^\n]*?))$/s) {
|
|
|
|
next;
|
|
|
|
}
|
|
|
|
|
|
|
|
# make all lines equally long in terms of printable characters
|
|
|
|
s/^(.*)$/$1 . (" " x ($singlewidth + 2 - length($1)))/gme;
|
|
|
|
|
|
|
|
# convert into side-by-side output
|
|
|
|
my @buffer = ();
|
|
|
|
foreach $_ (split /\n/, $_) {
|
|
|
|
if (/^u (.*)/) {
|
|
|
|
print join(" <\n", @buffer), " <\n" if $#buffer >= 0;
|
|
|
|
@buffer = ();
|
|
|
|
print $1, " ", $1, "\n";
|
|
|
|
} elsif (/^o (.*)/) {
|
|
|
|
# preserve in buffer for potential merging with "n "
|
|
|
|
push @buffer, $1;
|
|
|
|
} else {
|
|
|
|
/^n (.*)/;
|
|
|
|
# have line to be merged with?
|
|
|
|
if ($#buffer >= 0) {
|
|
|
|
print shift @buffer, " | ", $1, "\n";
|
|
|
|
} else {
|
|
|
|
print join(" <\n", @buffer), " <\n" if $#buffer >= 0;
|
|
|
|
print $spaces, " > ", $1, "\n";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
print join(" <\n", @buffer), " <\n" if $#buffer >= 0;
|
|
|
|
@buffer = ();
|
|
|
|
|
|
|
|
print "-" x $columns, "\n";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-03-29 22:39:54 +02:00
|
|
|
# unlink($normal1);
|
|
|
|
# unlink($normal2);
|
2007-11-08 21:56:37 +01:00
|
|
|
exit($res ? ((defined $ENV{CLIENT_TEST_COMPARISON_FAILED}) ? int($ENV{CLIENT_TEST_COMPARISON_FAILED}) : 1) : 0);
|
2006-12-17 17:36:17 +01:00
|
|
|
} else {
|
|
|
|
# normalize
|
|
|
|
my $in;
|
|
|
|
if( $#ARGV >= 0 ) {
|
2010-11-26 14:07:04 +01:00
|
|
|
my $file1 = $ARGV[0];
|
|
|
|
if (-d $file1) {
|
|
|
|
open(IN, "-|:utf8", "find $file1 -type f -print0 | xargs -0 cat") || die "$file1: $!";
|
|
|
|
} else {
|
|
|
|
open(IN, "<:utf8", $file1) || die "$file1: $!";
|
|
|
|
}
|
2006-12-17 17:36:17 +01:00
|
|
|
$in = *IN{IO};
|
|
|
|
} else {
|
|
|
|
$in = *STDIN{IO};
|
|
|
|
}
|
|
|
|
|
2010-12-01 15:00:37 +01:00
|
|
|
my $buf = join("", <$in>);
|
|
|
|
print STDOUT join("\n", Normalize($buf, $columns)), "\n";
|
2006-12-17 17:36:17 +01:00
|
|
|
}
|