diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 9e152e9..29c7ed0 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -18,6 +18,9 @@ Version 0.10.0 file. See :gh:`409`. - The ``collections`` parameter can now be used to synchronize differently-named collections with each other. +- **Packagers:** The ``lxml`` dependency has been dropped. +- XML parsing is now a lot stricter. Malfunctioning servers that used to work + with vdirsyncer may stop working. Version 0.9.3 ============= diff --git a/setup.py b/setup.py index de1b219..c4536ac 100644 --- a/setup.py +++ b/setup.py @@ -10,8 +10,6 @@ Vdirsyncer is a synchronization tool for vdir. See the README for more details. # detectable for setuptools-scm. Rather use the PyPI ones. -import platform - from setuptools import Command, find_packages, setup @@ -28,15 +26,6 @@ requirements = [ # replicate vdirsyncer's current behavior (verifying fingerprints without # verifying against CAs) with older versions of urllib3. 'requests >=2.4.1, !=2.9.0', - 'lxml >=3.1' + ( - # See https://github.com/pimutils/vdirsyncer/issues/298 - # We pin some LXML version that is known to work with PyPy - # I assume nobody actually uses PyPy with vdirsyncer, so this is - # moot - ', <=3.4.4' - if platform.python_implementation() == 'PyPy' - else '' - ), # https://github.com/sigmavirus24/requests-toolbelt/pull/28 # And https://github.com/sigmavirus24/requests-toolbelt/issues/54 'requests_toolbelt >=0.4.0', diff --git a/tests/storage/dav/test_utils.py b/tests/storage/dav/test_utils.py deleted file mode 100644 index b9b3ed9..0000000 --- a/tests/storage/dav/test_utils.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- - -from vdirsyncer.storage.dav import _parse_xml - - -def test_broken_xml(capsys): - rv = _parse_xml(b'

\x10haha

') - assert rv.text == 'haha' - warnings = capsys.readouterr()[1] - assert 'partially invalid xml' in warnings.lower() diff --git a/vdirsyncer/storage/dav.py b/vdirsyncer/storage/dav.py index d8c9fe5..77af2a2 100644 --- a/vdirsyncer/storage/dav.py +++ b/vdirsyncer/storage/dav.py @@ -3,7 +3,7 @@ import datetime import logging -from lxml import etree +import xml.etree.ElementTree as etree import requests from requests.exceptions import HTTPError @@ -69,18 +69,12 @@ class InvalidXMLResponse(exceptions.InvalidResponse): def _parse_xml(content): - p = etree.XMLParser(recover=True) - rv = etree.XML(content, parser=p) - if rv is None: + try: + return etree.XML(content) + except etree.ParseError as e: raise InvalidXMLResponse('Invalid XML encountered: {}\n' 'Double-check the URLs in your config.' - .format(p.error_log)) - if p.error_log: - dav_logger.warning('Partially invalid XML response, some of your ' - 'items may be corrupted. Check the debug log and ' - 'consider switching servers. ({})' - .format(p.error_log)) - return rv + .format(e)) def _merge_xml(items): @@ -177,7 +171,7 @@ class Discover(object): root = etree.fromstring(response.content) # Better don't do string formatting here, because of XML namespaces - rv = root.find('.//' + self._homeset_tag + '/{*}href') + rv = root.find('.//' + self._homeset_tag + '/{DAV:}href') if rv is None: raise InvalidXMLResponse() return utils.compat.urlparse.urljoin(response.url, rv.text) @@ -192,11 +186,11 @@ class Discover(object): root = _parse_xml(r.content) done = set() for response in root.findall('{DAV:}response'): - props = _merge_xml(response.findall('{*}propstat/{*}prop')) - if props.find('{*}resourcetype/{*}' + self._resourcetype) is None: + props = _merge_xml(response.findall('{DAV:}propstat/{DAV:}prop')) + if props.find('{DAV:}resourcetype/' + self._resourcetype) is None: continue - href = response.find('{*}href') + href = response.find('{DAV:}href') if href is None: raise InvalidXMLResponse() href = utils.compat.urlparse.urljoin(r.url, href.text) @@ -261,7 +255,7 @@ class Discover(object): class CalDiscover(Discover): _namespace = 'urn:ietf:params:xml:ns:caldav' - _resourcetype = 'calendar' + _resourcetype = '{%s}calendar' % _namespace _homeset_xml = """ @@ -269,13 +263,13 @@ class CalDiscover(Discover): """ - _homeset_tag = '{*}calendar-home-set' + _homeset_tag = '{%s}calendar-home-set' % _namespace _well_known_uri = '/.well-known/caldav/' class CardDiscover(Discover): _namespace = 'urn:ietf:params:xml:ns:carddav' - _resourcetype = 'addressbook' + _resourcetype = '{%s}addressbook' % _namespace _homeset_xml = """ @@ -283,7 +277,7 @@ class CardDiscover(Discover): """ - _homeset_tag = '{*}addressbook-home-set' + _homeset_tag = '{%s}addressbook-home-set' % _namespace _well_known_uri = '/.well-known/carddav/' @@ -581,7 +575,7 @@ class DavStorage(Storage): except KeyError: raise exceptions.UnsupportedMetadataError() - lxml_selector = '{%s}%s' % (namespace, tagname) + xpath = '{%s}%s' % (namespace, tagname) data = ''' @@ -589,7 +583,7 @@ class DavStorage(Storage): '''.format( - to_native(etree.tostring(etree.Element(lxml_selector))) + to_native(etree.tostring(etree.Element(xpath))) ) headers = self.session.get_default_headers() @@ -602,7 +596,7 @@ class DavStorage(Storage): root = _parse_xml(response.content) - for prop in root.findall('.//' + lxml_selector): + for prop in root.findall('.//' + xpath): text = normalize_meta_value(getattr(prop, 'text', None)) if text: return text