From f59c8d1fdf2715a966bedec2672f9f2da331ac85 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Fri, 30 May 2014 16:53:22 +0200 Subject: [PATCH 1/2] Add testcase for #70 --- tests/__init__.py | 3 ++- tests/utils/test_vobject.py | 20 ++++++++------------ vdirsyncer/utils/vobject.py | 4 +++- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/tests/__init__.py b/tests/__init__.py index 4896364..fc9fca9 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -18,7 +18,8 @@ vdirsyncer.log.set_level(vdirsyncer.log.logging.DEBUG) def normalize_item(item): if not isinstance(item, text_type): item = item.raw - return tuple(sorted(_normalize_item(item).splitlines())) + return tuple(sorted(_normalize_item( + item, use_icalendar=False).splitlines())) def assert_item_equals(a, b): diff --git a/tests/utils/test_vobject.py b/tests/utils/test_vobject.py index 506f0d5..227c65d 100644 --- a/tests/utils/test_vobject.py +++ b/tests/utils/test_vobject.py @@ -10,22 +10,21 @@ from vdirsyncer.utils.vobject import split_collection, join_collection, \ hash_item -from .. import normalize_item, SIMPLE_TEMPLATE, BARE_EVENT_TEMPLATE, \ +from .. import normalize_item, VCARD_TEMPLATE, BARE_EVENT_TEMPLATE, \ EVENT_TEMPLATE - _simple_joined = u'\r\n'.join(( u'BEGIN:VADDRESSBOOK', - SIMPLE_TEMPLATE.format(r=123), - SIMPLE_TEMPLATE.format(r=345), - SIMPLE_TEMPLATE.format(r=678), + VCARD_TEMPLATE.format(r=123), + VCARD_TEMPLATE.format(r=345), + VCARD_TEMPLATE.format(r=678), u'END:VADDRESSBOOK' )) _simple_split = [ - SIMPLE_TEMPLATE.format(r=123), - SIMPLE_TEMPLATE.format(r=345), - SIMPLE_TEMPLATE.format(r=678) + VCARD_TEMPLATE.format(r=123), + VCARD_TEMPLATE.format(r=345), + VCARD_TEMPLATE.format(r=678) ] @@ -36,10 +35,7 @@ def test_split_collection_simple(): def test_join_collection_simple(): - item_type = _simple_split[0].splitlines()[0][len(u'BEGIN:'):] - given = join_collection(_simple_split, wrappers={ - item_type: (u'VADDRESSBOOK', ()) - }) + given = join_collection(_simple_split) print(given) print(_simple_joined) assert normalize_item(given) == normalize_item(_simple_joined) diff --git a/vdirsyncer/utils/vobject.py b/vdirsyncer/utils/vobject.py index 19bc7b4..f0b1006 100644 --- a/vdirsyncer/utils/vobject.py +++ b/vdirsyncer/utils/vobject.py @@ -27,8 +27,10 @@ IGNORE_PROPS = frozenset(( )) -def normalize_item(text, ignore_props=IGNORE_PROPS): +def normalize_item(text, ignore_props=IGNORE_PROPS, use_icalendar=True): try: + if not use_icalendar: + raise Exception() lines = to_unicode_lines(icalendar.cal.Component.from_ical(text)) except Exception: lines = sorted(text.splitlines()) From 96d55c523ce16f9e809803feaa9e9147096e6f5d Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Fri, 30 May 2014 17:06:06 +0200 Subject: [PATCH 2/2] Fix semicolon bug Fix #70 Also remove multiple parameters from vcard template. Android JB for example converts this: Email: lol@lol (work) Email: lol@lol (home) to this: EMAIL;TYPE=WORK:lol@lol which is exactly what icalendar (our parser) does too. ownCloud on the other hand converts this: EMAIL;TYPE=HOME,WORK:lol@lol to this: EMAIL;TYPE=HOME;TYPE=WORK:lol@lol So at this point it does not really matter anymore whether our behavior is RFC-conform or not. --- tests/__init__.py | 4 ++-- vdirsyncer/utils/vobject.py | 17 ++++++++++++++--- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/tests/__init__.py b/tests/__init__.py index fc9fca9..da81e00 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -31,11 +31,11 @@ VERSION:3.0 FN:Cyrus Daboo N:Daboo;Cyrus ADR;TYPE=POSTAL:;2822 Email HQ;Suite 2821;RFCVille;PA;15213;USA -EMAIL;TYPE=INTERNET;TYPE=PREF:cyrus@example.com +EMAIL;TYPE=PREF:cyrus@example.com NICKNAME:me NOTE:Example VCard. ORG:Self Employed -TEL;TYPE=WORK;TYPE=VOICE:412 605 0499 +TEL;TYPE=VOICE:412 605 0499 TEL;TYPE=FAX:412 605 0705 URL:http://www.example.com X-SOMETHING:{r} diff --git a/vdirsyncer/utils/vobject.py b/vdirsyncer/utils/vobject.py index f0b1006..51ea61f 100644 --- a/vdirsyncer/utils/vobject.py +++ b/vdirsyncer/utils/vobject.py @@ -13,7 +13,17 @@ import icalendar.parser from . import text_type, itervalues -IGNORE_PROPS = frozenset(( + +def _process_properties(*s): + rv = set() + for key in s: + rv.add(key + ':') + rv.add(key + ';') + + return frozenset(rv) + + +IGNORE_PROPS = _process_properties( # PRODID is changed by radicale for some reason after upload 'PRODID', # VERSION can get lost in singlefile storage @@ -24,7 +34,7 @@ IGNORE_PROPS = frozenset(( # REV is from the VCARD specification and is supposed to change when the # item does -- however, we can determine that ourselves 'REV' -)) +) def normalize_item(text, ignore_props=IGNORE_PROPS, use_icalendar=True): @@ -38,7 +48,7 @@ def normalize_item(text, ignore_props=IGNORE_PROPS, use_icalendar=True): return u'\r\n'.join(line.strip() for line in lines if line.strip() and - not any(line.startswith(p + ':') + not any(line.startswith(p) for p in IGNORE_PROPS)) @@ -83,6 +93,7 @@ def to_unicode_lines(item): for content_line in item.content_lines(): if content_line: + content_line = content_line.replace(u'\\;', u';') yield icalendar.parser.foldline(content_line)