Skip to content

Commit

Permalink
Fix python-caldav#300 : Add option to enable huge_tree in XMLParser
Browse files Browse the repository at this point in the history
  • Loading branch information
Arnaud Aujon Chevallier authored and tobixen committed Mar 14, 2023
1 parent fcbbccd commit ae585d8
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 8 deletions.
26 changes: 20 additions & 6 deletions caldav/davclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,17 @@ class DAVResponse:
tree = None
headers = {}
status = 0
davclient = None
huge_tree = False

def __init__(self, response):
def __init__(self, response, davclient=None):
self.headers = response.headers
log.debug("response headers: " + str(self.headers))
log.debug("response status: " + str(self.status))

self._raw = response.content
if davclient:
self.huge_tree = davclient.huge_tree

## TODO: this if/else/elif could possibly be refactored, or we should
## consider to do streaming into the xmltree library as originally
Expand All @@ -65,7 +69,10 @@ def __init__(self, response):
# self.tree = etree.parse(response.raw, parser=etree.XMLParser(remove_blank_text=True))
try:
self.tree = etree.XML(
self._raw, parser=etree.XMLParser(remove_blank_text=True)
self._raw,
parser=etree.XMLParser(
remove_blank_text=True, huge_tree=self.huge_tree
),
)
except:
logging.critical(
Expand All @@ -92,7 +99,10 @@ def __init__(self, response):
## data be parsed through this code.
try:
self.tree = etree.XML(
self._raw, parser=etree.XMLParser(remove_blank_text=True)
self._raw,
parser=etree.XMLParser(
remove_blank_text=True, huge_tree=self.huge_tree
),
)
except:
pass
Expand Down Expand Up @@ -310,6 +320,7 @@ class DAVClient:

proxy = None
url = None
huge_tree = False

def __init__(
self,
Expand All @@ -322,6 +333,7 @@ def __init__(
ssl_verify_cert=True,
ssl_cert=None,
headers={},
huge_tree=False,
):
"""
Sets up a HTTPConnection object towards the server in the url.
Expand All @@ -330,7 +342,9 @@ def __init__(
* proxy: A string defining a proxy server: `hostname:port`
* username and password should be passed as arguments or in the URL
* auth, timeout and ssl_verify_cert are passed to requests.request.
** ssl_verify_cert can be the path of a CA-bundle or False.
* ssl_verify_cert can be the path of a CA-bundle or False.
* huge_tree: boolean, enable XMLParser huge_tree to handle big events, beware
of security issues, see : https://lxml.de/api/lxml.etree.XMLParser-class.html
The requests library will honor a .netrc-file, if such a file exists
username and password may be omitted. Known bug: .netrc is honored
Expand All @@ -341,7 +355,7 @@ def __init__(

log.debug("url: " + str(url))
self.url = URL.objectify(url)

self.huge_tree = huge_tree
# Prepare proxy info
if proxy is not None:
self.proxy = proxy
Expand Down Expand Up @@ -591,7 +605,7 @@ def request(self, url, method="GET", body="", headers={}):
cert=self.ssl_cert,
)
log.debug("server responded with %i %s" % (r.status_code, r.reason))
response = DAVResponse(r)
response = DAVResponse(r, self)
except:
## this is a workaround needed due to some weird server
## that would just abort the connection rather than send a
Expand Down
85 changes: 83 additions & 2 deletions tests/test_caldav_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@
END:VCALENDAR"""


def MockedDAVResponse(text):
def MockedDAVResponse(text, davclient=None):
"""
For unit testing - a mocked DAVResponse with some specific content
"""
Expand All @@ -156,7 +156,7 @@ def MockedDAVResponse(text):
resp.reason = "multistatus"
resp.headers = {}
resp.content = text
return DAVResponse(resp)
return DAVResponse(resp, davclient)


def MockedDAVClient(xml_returned):
Expand Down Expand Up @@ -870,6 +870,87 @@ def test_xml_parsing(self):
},
}

def testHugeTreeParam(self):
"""
With dealing with a huge XML response, such as event containing attachments, XMLParser will throw an exception
huge_tree parameters allows to handle this kind of events.
"""

xml = """
<multistatus xmlns="DAV:">
<response>
<href>/17149682/calendars/testcalendar-84439d0b-ce46-4416-b978-7b4009122c64/</href>
<propstat>
<prop>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
<propstat>
<prop>
<calendar-data xmlns="urn:ietf:params:xml:ns:caldav"/>
</prop>
<status>HTTP/1.1 404 Not Found</status>
</propstat>
</response>
<response>
<href>/17149682/calendars/testcalendar-84439d0b-ce46-4416-b978-7b4009122c64/20010712T182145Z-123401%40example.com.ics</href>
<propstat>
<prop>
<calendar-data xmlns="urn:ietf:params:xml:ns:caldav">
BEGIN:VCALENDAR
PRODID:-//MDaemon Technologies Ltd//MDaemon 21.5.2
VERSION:2.0
METHOD:PUBLISH
BEGIN:VEVENT
UID:
040000008200E0007000B7101A82E0080000000050BF99B19D31
SEQUENCE:0
DTSTAMP:20230213T142930Z
SUMMARY:This a summary of a very bug event
DESCRIPTION:Description of this very big event
LOCATION:Somewhere
ORGANIZER:MAILTO:noreply@test.com
PRIORITY:5
ATTACH;VALUE=BINARY;ENCODING=BASE64;FMTTYPE=image/jpeg;
X-FILENAME=image001.jpg;X-ORACLE-FILENAME=image001.jpg:
"""
xml += (
"gIyIoLTkwKCo2KyIjM4444449QEBAJjBGS0U+Sjk/QD3/2wBDAQsLCw8NDx0QEB09KSMpPT09\n"
* 153490
)
xml += """
/Z
DTSTART;TZID="Europe/Paris":20230310T140000
DTEND;TZID="Europe/Paris":20230310T150000
END:VEVENT
END:VCALENDAR
</calendar-data>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
</multistatus>
"""
davclient = MockedDAVClient(xml)
resp = mock.MagicMock()
resp.headers = {"Content-Type": "text/xml"}
resp.content = xml

davclient.huge_tree = False
try:
DAVResponse(resp, davclient=davclient)
assert False
except Exception as e:
assert type(e) == lxml.etree.XMLSyntaxError

davclient.huge_tree = True
try:
DAVResponse(resp, davclient=davclient)
assert True
except:
assert False

def testFailedQuery(self):
"""
ref https://github.com/python-caldav/caldav/issues/54
Expand Down

0 comments on commit ae585d8

Please sign in to comment.