diff --git a/ChangeLog b/ChangeLog index fa7b37f58..95e83f5cf 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,16 @@ +2009-10-02 Wolfgang Sourdeau + + * Tests/webdavlib.py (MailDAVMailQuery): new class that implements + the "mail-query" DAV REPORT. + (WebDAVQuery._initProperties): new helper methods invoked by + PROPFIND and REPORT classes at initialization to format the "prop" + part of their query. + (HTTPUnparsedURL._parse): accept more valid URL characters. + + * Tests/test-maildav.py + (DAVMailCollectionTest.testREPORTMailQuery): new method with + initial tests for "mail-query" report. + 2009-10-01 Cyril Robert * SoObjects/Mailer/SOGoMailObject.m: Added methods required for PROPFIND / diff --git a/Tests/test-maildav.py b/Tests/test-maildav.py index d81f76030..82645d8f5 100755 --- a/Tests/test-maildav.py +++ b/Tests/test-maildav.py @@ -25,35 +25,149 @@ def fetchUserEmail(login): message1 = """Return-Path: Received: from cyril.dev (localhost [127.0.0.1]) by cyril.dev (Cyrus v2.3.8-Debian-2.3.8-1) with LMTPA; - Tue, 29 Sep 2009 07:42:16 -0400 + Tue, 17 Dec 2009 07:42:16 -0400 Received: from aloha.dev (localhost [127.0.0.1]) by aloha.dev (Cyrus v2.3.8-Debian-2.3.8-1) with LMTPA; Tue, 29 Sep 2009 07:42:16 -0400 -X-Virus-Scanned: Debian amavisd-new at inverse.ca -Message-ID: <4AC1F296.5060801@cyril.dev> -References: <4AC3BF1B.3010806@inverse.ca> +Message-ID: <4AC1F29sept6.5060801@cyril.dev> Date: Tue, 29 Sep 2009 07:42:14 -0400 -From: Cyril -Organization: Inverse inc. +From: Cyril User-Agent: Thunderbird 2.0.0.22 (Macintosh/20090605) MIME-Version: 1.0 -To: jacques@cyril.dev -CC: support@inverse.ca, user10@cyril.dev -Subject: Hallo -Content-Type: text/plain; charset=UTF-8; format=flowed +To: message1to@cyril.dev +CC: message1cc@cyril.dev, user10@cyril.dev +Subject: message1subject +Content-Type: text/plain; charset=us-ascii; format=flowed Content-Transfer-Encoding: 7bit -Reply-To: support@inverse.ca,Cyril +Reply-To: support@inverse.ca Hello Jacques, Can you read me? +-- +Cyril +""" + +message2 = """Return-Path: +Received: from cyril.dev (localhost [127.0.0.1]) + by cyril.dev (Cyrus v2.3.8-Debian-2.3.8-1) with LMTPA; + Tue, 09 Dec 2009 07:42:16 -0400 +Message-ID: <410sepAC1F296.5060801a@cyril.dev> +Date: Tue, 10 Sep 2009 07:42:14 -0400 +User-Agent: Thunderbird 2.0.0.22 (Macintosh/20090605) +MIME-Version: 1.0 +From: Cyril +To: message2to@cyril.dev +CC: message2cc@cyril.dev +Subject: message2subject +Content-Type: text/plain; charset=us-ascii; format=flowed +Content-Transfer-Encoding: 7bit +Reply-To: support@inverse.ca + +Hello Jacques, + +Can you read me? + +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff +-- +Cyril +""" + +message3 = """Return-Path: +Received: from cyril.dev (localhost [127.0.0.1]) + by cyril.dev (Cyrus v2.3.8-Debian-2.3.8-1) with LMTPA; + Tue, 15 Dec 2009 07:42:16 -0400 +Message-ID: <4AC1aF2dec96.5060801a@cyril.dev> +Date: Tue, 10 Dec 2009 07:42:14 -0400 +User-Agent: Thunderbird 2.0.0.22 (Macintosh/20090605) +MIME-Version: 1.0 +From: Cyril +To: message3to@cyril.dev +CC: message3cc@cyril.dev +Subject: Hallo +Content-Type: text/plain; charset=us-ascii; format=flowed +Content-Transfer-Encoding: 7bit +Reply-To: support@inverse.ca + +Hello Jacques, + +Can you read me? + +This message is just a big larger than message1 but smaller than message2 -- Cyril """ message1_received = """Received: from cyril.dev (localhost [127.0.0.1]) by cyril.dev (Cyrus v2.3.8-Debian-2.3.8-1) with LMTPA; - Tue, 29 Sep 2009 07:42:16 -0400""" + Tue, 17 Dec 2009 07:42:16 -0400""" class DAVMailCollectionTest(unittest.TestCase): resource = '/SOGo/dav/%s/Mail/' % username @@ -64,16 +178,16 @@ class DAVMailCollectionTest(unittest.TestCase): username, password) if self.user_email is None: self.user_email = fetchUserEmail(username) - if self.user_email.startswith ("mailto:"): + if self.user_email.startswith("mailto:"): self.user_email = self.user_email[7:] self.resource = '/SOGo/dav/%s/Mail/%s_A_%s/' \ % (username, - username.replace("@", "_A_").replace (".", "_D_"), - mailserver.replace (".", "_D_")) + username.replace("@", "_A_").replace(".", "_D_"), + mailserver.replace(".", "_D_")) ## helper methods - def _makeCollection (self, name, status = 201): + def _makeCollection(self, name, status = 201): url = "%s%s" % (self.resource, name) mkcol = webdavlib.WebDAVMKCOL(url) self.client.execute(mkcol) @@ -81,7 +195,7 @@ class DAVMailCollectionTest(unittest.TestCase): "failure creating collection" "(code = %d)" % mkcol.response["status"]) - def _deleteCollection (self, name, status = 204): + def _deleteCollection(self, name, status = 204): url = "%sfolder%s" % (self.resource, name) delete = webdavlib.WebDAVDELETE(url) self.client.execute(delete) @@ -89,16 +203,18 @@ class DAVMailCollectionTest(unittest.TestCase): "failure deleting collection" "(code = %d)" % delete.response["status"]) - def _putMessage(self, client, filename, + def _putMessage(self, client, folder, message, exp_status = 201): - url = "%s%s" % (self.resource, filename) + url = "%sfolder%s" % (self.resource, folder) put = webdavlib.HTTPPUT(url, message) put.content_type = "message/rfc822" client.execute(put) - self.assertEquals(put.response["status"], exp_status, - "%s: event creation/modification:" - " expected status code '%d' (received '%d')" - % (filename, exp_status, put.response["status"])) + if (exp_status is not None): + self.assertEquals(put.response["status"], exp_status, + "message creation/modification:" + " expected status code '%d' (received '%d')" + % (exp_status, put.response["status"])) + return put.response["headers"]["location"] def _testProperty (self, url, property, expected, isDate = 0): propfind = webdavlib.WebDAVPROPFIND(url, (property, ), 0) @@ -123,13 +239,13 @@ class DAVMailCollectionTest(unittest.TestCase): def testMKCOL(self): """Folder creation""" - self._makeCollection ("test-dav-mail-%40-abc") - self._deleteCollection ("test-dav-mail-%40-abc") - self._makeCollection ("test-dav-mail-@-def") - self._deleteCollection ("test-dav-mail-@-def") - self._makeCollection ("test-dav-mail-%20-ghi") - self._deleteCollection ("test-dav-mail-%20-ghi") - self._makeCollection ("test-dav-mail-%25-jkl", 500) + self._makeCollection("test-dav-mail-%40-abc") + self._deleteCollection("test-dav-mail-%40-abc") + self._makeCollection("test-dav-mail-@-def") + self._deleteCollection("test-dav-mail-@-def") + self._makeCollection("test-dav-mail-%20-ghi") + self._deleteCollection("test-dav-mail-%20-ghi") + self._makeCollection("test-dav-mail-%25-jkl", 500) # Test MOVE # self._makeCollection ("test-dav-mail-movable") @@ -144,42 +260,252 @@ class DAVMailCollectionTest(unittest.TestCase): def testPUT(self): """Message creation""" - self._deleteCollection ("test-dav-mail") - self._makeCollection ("test-dav-mail") + self._deleteCollection("test-dav-mail") + self._makeCollection("test-dav-mail") # message creation on collection url url = "%s%s" % (self.resource, "foldertest-dav-mail/") - put = webdavlib.HTTPPUT (url, message1) + put = webdavlib.HTTPPUT(url, message1) put.content_type = "message/rfc822" - self.client.execute (put) + self.client.execute(put) self.assertEquals(put.response["status"], 201, "failure putting message" "(code = %d)" % put.response["status"]) itemLocation = put.response["headers"]["location"] - get = webdavlib.WebDAVGET (itemLocation) - self.client.execute (get) + get = webdavlib.WebDAVGET(itemLocation) + self.client.execute(get) self.assertEquals(get.response["status"], 200, "failure getting item" "(code = %d)" % get.response["status"]) # message creation with explicit filename - url = "%s%s" % (self.resource, "foldertest-dav-mail/blabla.eml") - put = webdavlib.HTTPPUT (url, message1) + url = "%s%s" %(self.resource, "foldertest-dav-mail/blabla.eml") + put = webdavlib.HTTPPUT(url, message1) put.content_type = "message/rfc822" - self.client.execute (put) + self.client.execute(put) self.assertEquals(put.response["status"], 201, "failure putting message" "(code = %d)" % put.response["status"]) itemLocation = put.response["headers"]["location"] - get = webdavlib.WebDAVGET (itemLocation) - self.client.execute (get) + get = webdavlib.WebDAVGET(itemLocation) + self.client.execute(get) self.assertEquals(get.response["status"], 200, "failure getting item" "(code = %d)" % get.response["status"]) - self._deleteCollection ("test-dav-mail") + self._deleteCollection("test-dav-mail") + + def _testFilters(self, filters): + for filter in filters: + self._testFilter(filter) + + def _testFilter(self, filter): + expected_hrefs = {} + expected_count = len(filter[1]) + for href in filter[1]: + expected_hrefs[href] = True + + received_count = 0 + query = webdavlib.MailDAVMailQuery(self.resource, ["displayname"], + filter[0]) + self.client.execute(query) + self.assertEquals(query.response["status"], 207, + "filter %s:\n\tunexpected status: %d" + % (filter[0], query.response["status"])) + query.xpath_namespace = { "D": "DAV:", + "I": "urn:inverse:params:xml:ns:inverse-dav" } + response_nodes = query.xpath_evaluate("/D:multistatus/D:response") + for response_node in response_nodes: + href_node = query.xpath_evaluate("D:href", response_node)[0] + href = href_node.childNodes[0].nodeValue + received_count = received_count + 1 + self.assertTrue(expected_hrefs.has_key(href), + "filter %s:\n\tunexpected href: %s" % (filter[0], href)) + + self.assertEquals(len(filter[1]), received_count, + "filter %s:\n\tunexpected amount of refs: %d" + % (filter[0], received_count)) + + def testREPORTMailQuery(self): + """mail-query""" + self._deleteCollection("test-dav-mail") + self._makeCollection("test-dav-mail") + + msg1Loc = self._putMessage(self.client, "test-dav-mail", message1) + parsed = webdavlib.HTTPUnparsedURL(msg1Loc) + msg1Path = parsed.path + msg2Loc = self._putMessage(self.client, "test-dav-mail", message2) + parsed = webdavlib.HTTPUnparsedURL(msg2Loc) + msg2Path = parsed.path + msg3Loc = self._putMessage(self.client, "test-dav-mail", message3) + parsed = webdavlib.HTTPUnparsedURL(msg3Loc) + msg3Path = parsed.path + + properties = ["{DAV:}displayname"] + + ## 1. test filter: receive-date + # SINCE, BEFORE, ON + # q = MailDAVMailQuery(self.resource, properties, filters = None) + + filters = (({ "receive-date": { "from": "20091201T000000Z", + "to": "20091208T000000Z" } }, + []), + ({ "receive-date": { "from": "20091208T000000Z", + "to": "20091213T134300Z" } }, + [ msg2Loc ]), + ({ "receive-date": { "from": "20091208T000000Z", + "to": "20091216T134300Z" } }, + [ msg2Loc, msg3Loc ]), + ({ "receive-date": { "from": "20091216T000000Z", + "to": "20091220T134300Z" } }, + [ msg1Loc ]), + ({ "receive-date": { "from": "20091220T000000Z", + "to": "20091229T134300Z" } }, + [])) + self._testFilters(filters) + + ## 1. test filter: date + # SENTSINCE, SENTBEFORE, SENTON + + filters = (({ "date": { "from": "20090101T000000Z", + "to": "20090201T000000Z" } }, + []), + ({ "date": { "from": "20090912T000000Z", + "to": "20090929T134300Z" } }, + [ msg1Loc ]), + ({ "date": { "from": "20090929T134300Z", + "to": "20091209T000000Z" } }, + []), + ({ "date": { "from": "20090901T134300Z", + "to": "20091209T000000Z" } }, + [ msg1Loc, msg2Loc ]), + ({ "date": { "from": "20091201T000000Z", + "to": "20091211T000000Z" } }, + [ msg3Loc ]), + ({ "date": { "from": "20091211T000000Z", + "to": "20101211T000000Z" } }, + []), + ({ "date": { "from": "20090101T000000Z", + "to": "20100101T000000Z" } }, + [ msg1Loc, msg2Loc, msg3Loc ])) + self._testFilters(filters) + + ## 1. test filter: sequence + # x:y + filters = (({ "sequence": { "from": "1" }}, + [ msg1Loc, msg2Loc, msg3Loc ]), + ({ "sequence": { "from": "5" }}, + []), + ({ "sequence": { "to": "5" }}, + [ msg1Loc, msg2Loc, msg3Loc ]), + ({ "sequence": { "from": "1", + "to": "2" }}, + [ msg1Loc, msg2Loc ])) + self._testFilters(filters) + + ## 1. test filter: uid + # UID + filters = (({ "uid": { "from": "1" }}, + [ msg1Loc, msg2Loc, msg3Loc ]), + ({ "uid": { "from": "5" }}, + []), + ({ "uid": { "to": "5" }}, + [ msg1Loc, msg2Loc, msg3Loc ]), + ({ "uid": { "from": "1", + "to": "2" }}, + [ msg1Loc, msg2Loc ])) + self._testFilters(filters) + + ## 1. test filter: from + # FROM + filters = (({ "from": { "match": "message" }}, + [ msg1Loc, msg2Loc, msg3Loc ]), + ({ "from": { "match": "Cyril" }}, + [ msg1Loc, msg2Loc, msg3Loc ]), + ({ "from": { "match": "cyril.dev" }}, + [ msg1Loc, msg2Loc, msg3Loc ]), + ({ "from": { "match": "message1from" }}, + [ msg1Loc ]), + ({ "from": { "match": "message2from" }}, + [ msg2Loc ]), + ({ "from": { "match": "message3from" }}, + [ msg3Loc ])) + self._testFilters(filters) + + ## 1. test filter: to + # TO + filters = (({ "to": { "match": "message" }}, + [ msg1Loc, msg2Loc, msg3Loc ]), + ({ "to": { "match": "Cyril" }}, + [ msg1Loc, msg2Loc, msg3Loc ]), + ({ "to": { "match": "cyril.dev" }}, + [ msg1Loc, msg2Loc, msg3Loc ]), + ({ "to": { "match": "message1to" }}, + [ msg1Loc ]), + ({ "to": { "match": "message2to" }}, + [ msg2Loc ]), + ({ "to": { "match": "message3to" }}, + [ msg3Loc ])) + self._testFilters(filters) + + ## 1. test filter: cc + # CC + filters = (({ "cc": { "match": "message" }}, + [ msg1Loc, msg2Loc, msg3Loc ]), + ({ "cc": { "match": "Cyril" }}, + [ msg1Loc, msg2Loc, msg3Loc ]), + ({ "cc": { "match": "cyril.dev" }}, + [ msg1Loc, msg2Loc, msg3Loc ]), + ({ "cc": { "match": "message1cc" }}, + [ msg1Loc ]), + ({ "cc": { "match": "message2cc" }}, + [ msg2Loc ]), + ({ "cc": { "match": "message3cc" }}, + [ msg3Loc ])) + self._testFilters(filters) + + ## 1. test filter: bcc + # BCC + ## TODO + + ## 1. test filter: body + # BODY + filters = (({ "body": { "match": "Hello" }}, + [ msg1Loc, msg2Loc, msg3Loc ]), + ({ "body": { "match": "Stuff" }}, + [ msg2Loc ]), + ({ "body": { "match": "DOESNOT MATCH" }}, + [])) + self._testFilters(filters) + + ## 1. test filter: size + # LARGER, SMALLER + ## 1. test filter: answered + # ANSWERED, UNANSWERED + ## 1. test filter: draft + # DRAFT + ## 1. test filter: flagged + # FLAGGED + ## 1. test filter: recent + # RECENT + ## 1. test filter: seen + # SEEN + ## 1. test filter: deleted + # DELETED + ## 1. test filter: keywords + # KEYWORD x + + # 1. test sort: (receive-date) ARRIVAL + # 1. test sort: (date) DATE + # 1. test sort: CC + # 1. test sort: FROM + # 1. test sort: SIZE + # 1. test sort: SUBJECT + # 1. test sort: TO + + self._deleteCollection("test-dav-mail") def testPROPFIND(self): """Message properties""" @@ -201,11 +527,11 @@ class DAVMailCollectionTest(unittest.TestCase): ("{urn:schemas:httpmail:}textdescription", "" % message1, 0), ("{urn:schemas:httpmail:}unreadcount", None, 0), - ("{urn:schemas:mailheader:}cc","support@inverse.ca, user10@cyril.dev", 0), - ("{urn:schemas:mailheader:}date", 1254242534, 1), - ("{urn:schemas:mailheader:}from", "Cyril ", 0), + ("{urn:schemas:mailheader:}cc","message1cc@cyril.dev, user10@cyril.dev", 0), + ("{urn:schemas:mailheader:}date", "Tue, 29 Sep 2009 11:42:14 GMT", 0), + ("{urn:schemas:mailheader:}from", "Cyril ", 0), ("{urn:schemas:mailheader:}in-reply-to", None, 0), - ("{urn:schemas:mailheader:}message-id","<4AC1F296.5060801@cyril.dev>", 0), + ("{urn:schemas:mailheader:}message-id","<4AC1F29sept6.5060801@cyril.dev>", 0), ("{urn:schemas:mailheader:}received", message1_received, 0), ("{urn:schemas:mailheader:}references", "<4AC3BF1B.3010806@inverse.ca>", 0), diff --git a/Tests/webdavlib.py b/Tests/webdavlib.py index f86957b90..10111b565 100644 --- a/Tests/webdavlib.py +++ b/Tests/webdavlib.py @@ -22,7 +22,7 @@ class HTTPUnparsedURL: # ((proto)://((username(:(password)?)@)?hostname(:(port))))(path)? # if url_re is None: url_parts = url.split("?") - alpha_match = "[a-zA-Z0-9]+" + alpha_match = "[a-zA-Z0-9%\._-]+" num_match = "[0-9]+" pattern = ("((%s)://(((%s)(:(%s)?)@)?(%s)(:(%s))))?(/.*)" % (alpha_match, alpha_match, alpha_match, @@ -139,7 +139,15 @@ class WebDAVQuery(HTTPQuery): self.ns_mgr = _WD_XMLNS_MGR() self.top_node = None self.xml_response = None - self.xpath_namespace = { "D": "DAV:" } + self.xpath_namespace = { "D": xmlns_dav } + + # helper for PROPFIND and REPORT (only) + def _initProperties(self, properties): + props = _WD_XMLTreeElement("prop") + self.top_node.append(props) + for prop in properties: + prop_tag = self.render_tag(prop) + props.append(_WD_XMLTreeElement(prop_tag)) def render(self): if self.top_node is not None: @@ -199,11 +207,8 @@ class WebDAVPROPFIND(WebDAVQuery): def __init__(self, url, properties, depth = None): WebDAVQuery.__init__(self, url, depth) self.top_node = _WD_XMLTreeElement("propfind") - props = _WD_XMLTreeElement("prop") - self.top_node.append(props) - for prop in properties: - prop_tag = self.render_tag(prop) - props.append(_WD_XMLTreeElement(prop_tag)) + if properties is not None and len(properties) > 0: + self._initProperties(properties) class WebDAVMOVE(WebDAVQuery): method = "MOVE" @@ -262,13 +267,10 @@ class CalDAVPOST(WebDAVQuery): class CalDAVCalendarMultiget(WebDAVREPORT): def __init__(self, url, properties, hrefs): WebDAVQuery.__init__(self, url) - multiget_tag = self.ns_mgr.register("calendar-multiget", "urn:ietf:params:xml:ns:caldav") + multiget_tag = self.ns_mgr.register("calendar-multiget", xmlns_caldav) self.top_node = _WD_XMLTreeElement(multiget_tag) - props = _WD_XMLTreeElement("prop") - self.top_node.append(props) - for prop in properties: - prop_tag = self.render_tag(prop) - props.append(_WD_XMLTreeElement(prop_tag)) + if properties is not None and len(properties) > 0: + self._initProperties(properties) for href in hrefs: href_node = _WD_XMLTreeElement("href") @@ -278,19 +280,16 @@ class CalDAVCalendarMultiget(WebDAVREPORT): class CalDAVCalendarQuery(WebDAVREPORT): def __init__(self, url, properties, component = None, timerange = None): WebDAVQuery.__init__(self, url) - multiget_tag = self.ns_mgr.register("calendar-query", "urn:ietf:params:xml:ns:caldav") + multiget_tag = self.ns_mgr.register("calendar-query", xmlns_caldav) self.top_node = _WD_XMLTreeElement(multiget_tag) - props = _WD_XMLTreeElement("prop") - self.top_node.append(props) - for prop in properties: - prop_tag = self.render_tag(prop) - props.append(_WD_XMLTreeElement(prop_tag)) + if properties is not None and len(properties) > 0: + self._initProperties(properties) if component is not None: filter_tag = self.ns_mgr.register("filter", - "urn:ietf:params:xml:ns:caldav") + xmlns_caldav) compfilter_tag = self.ns_mgr.register("comp-filter", - "urn:ietf:params:xml:ns:caldav") + xmlns_caldav) filter_node = _WD_XMLTreeElement(filter_tag) cal_filter_node = _WD_XMLTreeElement(compfilter_tag, { "name": "VCALENDAR" }) @@ -312,11 +311,47 @@ class WebDAVSyncQuery(WebDAVREPORT): if token is not None: sync_token.append(_WD_XMLTreeTextNode(token)) - props = _WD_XMLTreeElement("prop") - self.top_node.append(props) - for prop in properties: - prop_tag = self.render_tag(prop) - props.append(_WD_XMLTreeElement(prop_tag)) + if properties is not None and len(properties) > 0: + self._initProperties(properties) + +class MailDAVMailQuery(WebDAVREPORT): + def __init__(self, url, properties, filters = None, sort = None): + WebDAVQuery.__init__(self, url) + mailquery_tag = self.ns_mgr.register("mail-query", + xmlns_inversedav) + self.top_node = _WD_XMLTreeElement(mailquery_tag) + if properties is not None and len(properties) > 0: + self._initProperties(properties) + + if filters is not None and len(filters) > 0: + self._initFilters(filters) + + if sort is not None and len(sort) > 0: + self._initSort(sort) + + def _initFilters(self, filters): + mailfilter_tag = self.ns_mgr.register("mail-filters", + xmlns_inversedav) + mailfilter_node = _WD_XMLTreeElement(mailfilter_tag) + self.top_node.append(mailfilter_node) + for filterk in filters.keys(): + filter_tag = self.ns_mgr.register(filterk, + xmlns_inversedav) + filter_node = _WD_XMLTreeElement(filter_tag, + filters[filterk]) + mailfilter_node.append(filter_node) + + def _initSort(self, sort): + sort_tag = self.ns_mgr.register("sort", xmlns_inversedav) + sort_node = _WD_XMLTreeElement(sort_tag) + self.top_node.append(sort_node) + sort_subtag = self.ns_mgr.register(sort[0], xmlns_inversedav) + if len(sort) > 1: + attributes = sort[1] + else: + attributes = {} + sort_subnode = _WD_XMLTreeElement(sort_subtag, attributes) + sort_node.append(sort_subnode) # private classes to handle XML stuff class _WD_XMLNS_MGR: @@ -339,7 +374,7 @@ class _WD_XMLNS_MGR: return new_nssym def register(self, tag, namespace): - if namespace != "DAV:": + if namespace != xmlns_dav: if self.xmlns.has_key(namespace): key = self.xmlns[namespace] else: