diff --git a/ChangeLog b/ChangeLog index d31e25f59..ad5ec27ee 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,14 @@ +2009-08-21 Cyril Robert + + * UI/Contacts/UIxListView.m: Implementation, allows VLISTs to be displayed. + * UI/Contacts/UIxListView.m (checkListReferences): Added list integrity + verification (make sure all references are valid). + +2009-08-21 Wolfgang Sourdeau + + * Tests/webdavlib.py (WebDAVQuery.xpath_evaluate): new method to + facilitate xpath evaluation from tests. + 2009-08-20 Cyril Robert * UI/Common/UIxParentFolderActions.m (createFolderAction): Mantis 2040: diff --git a/Tests/test-davacl.py b/Tests/test-davacl.py index d2068816a..eabf3edf3 100755 --- a/Tests/test-davacl.py +++ b/Tests/test-davacl.py @@ -5,7 +5,6 @@ from config import hostname, port, username, password, subscriber_username, subs import sys import unittest import webdavlib -import xml.xpath import time # TODO: @@ -27,11 +26,11 @@ def fetchUserEmail(login): propfind = webdavlib.WebDAVPROPFIND(resource, ["{urn:ietf:params:xml:ns:caldav}calendar-user-address-set"], 0) + propfind.xpath_namespace = { "D": "DAV:", + "C": "urn:ietf:params:xml:ns:caldav" } client.execute(propfind) - xpath_context = xml.xpath.CreateContext(propfind.response["document"]) - xpath_context.setNamespaces({ "D": "DAV:", - "C": "urn:ietf:params:xml:ns:caldav" }) - nodes = xml.xpath.Evaluate('/D:multistatus/D:response/D:propstat/D:prop/C:calendar-user-address-set/D:href', None, xpath_context) + nodes = propfind.xpath_evaluate('/D:multistatus/D:response/D:propstat/D:prop/C:calendar-user-address-set/D:href', + None) return nodes[0].childNodes[0].nodeValue @@ -59,12 +58,6 @@ class DAVAclTest(unittest.TestCase): def rightsToSOGoRights(self, rights): self.fail("subclass must implement this method") - def xpath_query(self, query, top_node): - xpath_context = xml.xpath.CreateContext(top_node) - xpath_context.setNamespaces({ "D": "DAV:", - "C": "urn:ietf:params:xml:ns:caldav" }) - return xml.xpath.Evaluate(query, None, xpath_context) - def setupRights(self, rights): rights_str = "".join(["<%s/>" % x for x in self.rightsToSOGoRights(rights) ]) aclQuery = """ @@ -284,23 +277,24 @@ class DAVCalendarAclTest(DAVAclTest): return event - def _calendarDataInMultistatus(self, top_node, filename, + def _calendarDataInMultistatus(self, query, filename, response_tag = "D:response"): event = None - response_nodes = self.xpath_query("/D:multistatus/%s" % response_tag, - top_node) + query.xpath_namespace = { "D": "DAV:", + "C": "urn:ietf:params:xml:ns:caldav" } + response_nodes = query.xpath_evaluate("/D:multistatus/%s" % response_tag) for response_node in response_nodes: - href_node = self.xpath_query("D:href", response_node)[0] + href_node = query.xpath_evaluate("D:href", response_node)[0] href = href_node.childNodes[0].nodeValue if href.endswith(filename): - propstat_nodes = self.xpath_query("D:propstat", response_node) + propstat_nodes = query.xpath_evaluate("D:propstat", response_node) for propstat_node in propstat_nodes: - status_node = self.xpath_query("D:status", - propstat_node)[0] + status_node = query.xpath_evaluate("D:status", + propstat_node)[0] status = status_node.childNodes[0].nodeValue - data_nodes = self.xpath_query("D:prop/C:calendar-data", - propstat_node) + data_nodes = query.xpath_evaluate("D:prop/C:calendar-data", + propstat_node) if status.endswith("200 OK"): if (len(data_nodes) > 0 and len(data_nodes[0].childNodes) > 0): @@ -323,8 +317,7 @@ class DAVCalendarAclTest(DAVAclTest): 1) self.subscriber_client.execute(propfind) if propfind.response["status"] != 403: - event = self._calendarDataInMultistatus(propfind.response["document"], - filename) + event = self._calendarDataInMultistatus(propfind, filename) return event @@ -338,8 +331,7 @@ class DAVCalendarAclTest(DAVAclTest): [ url ]) self.subscriber_client.execute(multiget) if multiget.response["status"] != 403: - event = self._calendarDataInMultistatus(multiget.response["document"], - url) + event = self._calendarDataInMultistatus(multiget, url) return event @@ -352,8 +344,8 @@ class DAVCalendarAclTest(DAVAclTest): ["{urn:ietf:params:xml:ns:caldav}calendar-data"]) self.subscriber_client.execute(sync_query) if sync_query.response["status"] != 403: - event = self._calendarDataInMultistatus(sync_query.response["document"], - url, "D:sync-response") + event = self._calendarDataInMultistatus(sync_query, url, + "D:sync-response") return event diff --git a/Tests/test-wedavsync.py b/Tests/test-wedavsync.py index f0ccf7200..46b96559d 100755 --- a/Tests/test-wedavsync.py +++ b/Tests/test-wedavsync.py @@ -5,7 +5,6 @@ from config import hostname, port, username, password import sys import unittest import webdavlib -import xml.xpath import time resource = '/SOGo/dav/%s/Calendar/test-webdavsync/' % username @@ -19,11 +18,6 @@ class WebdavSyncTest(unittest.TestCase): delete = webdavlib.WebDAVDELETE(resource) self.client.execute(delete) - def _xpath_query(self, query, top_node): - xpath_context = xml.xpath.CreateContext(top_node) - xpath_context.setNamespaces({ "D": "DAV:" }) - return xml.xpath.Evaluate(query, None, xpath_context) - def test(self): # missing tests: # invalid tokens: negative, non-numeric, > current timestamp @@ -49,8 +43,7 @@ class WebdavSyncTest(unittest.TestCase): self.assertEquals(query1.response["status"], 207, ("query1: invalid status code: %d (!= 207)" % query1.response["status"])) - token_node = self._xpath_query("/D:multistatus/D:sync-token", - query1.response["document"])[0] + token_node = query1.xpath_evaluate("/D:multistatus/D:sync-token")[0] # Implicit "assertion": we expect SOGo to return a token node, with a # non-empty numerical value. Anything else will trigger an exception token = int(token_node.childNodes[0].nodeValue) @@ -62,8 +55,7 @@ class WebdavSyncTest(unittest.TestCase): query2 = webdavlib.WebDAVSyncQuery(resource, "1234", [ "getetag" ]) self.client.execute(query2) self.assertEquals(query2.response["status"], 403) - cond_nodes = self._xpath_query("/D:error/D:valid-sync-token", - query2.response["document"]) + cond_nodes = query2.xpath_evaluate("/D:error/D:valid-sync-token") self.assertTrue(len(cond_nodes) > 0, "expected 'valid-sync-token' condition error") diff --git a/Tests/webdavlib.py b/Tests/webdavlib.py index 1a837c1e7..963fe8358 100644 --- a/Tests/webdavlib.py +++ b/Tests/webdavlib.py @@ -1,11 +1,15 @@ +import cStringIO import httplib import M2Crypto.httpslib import time import xml.sax.saxutils import xml.dom.ext.reader.Sax2 +import xml.xpath import sys class WebDAVClient: + user_agent = "Mozilla/5.0" + def __init__(self, hostname, port, username, password, forcessl = False): if port == "443" or forcessl: self.conn = M2Crypto.httpslib.HTTPSConnection(hostname, int(port), @@ -17,7 +21,7 @@ class WebDAVClient: .encode('base64')[:-1]) def _prepare_headers(self, query, body): - headers = { "User-Agent": "Mozilla/5.0", + headers = { "User-Agent": self.user_agent, "authorization": "Basic %s" % self.simpleauth_hash } if body is not None: headers["content-length"] = len(body) @@ -89,6 +93,7 @@ class WebDAVQuery(HTTPQuery): self.ns_mgr = _WD_XMLNS_MGR() self.top_node = None self.xml_response = None + self.xpath_namespace = { "D": "DAV:" } def render(self): if self.top_node is not None: @@ -118,9 +123,18 @@ class WebDAVQuery(HTTPQuery): and (headers["content-type"].startswith("application/xml") or headers["content-type"].startswith("text/xml")) and int(headers["content-length"]) > 0): - dom_response = xml.dom.ext.reader.Sax2.FromXml(self.response["body"]) + reader = xml.dom.ext.reader.Sax2.Reader() + stream = cStringIO.StringIO(self.response["body"]) + dom_response = reader.fromStream(stream) self.response["document"] = dom_response.documentElement + def xpath_evaluate(self, query, top_node = None): + if top_node is None: + top_node = self.response["document"] + xpath_context = xml.xpath.CreateContext(top_node) + xpath_context.setNamespaces(self.xpath_namespace) + return xml.xpath.Evaluate(query, None, xpath_context) + class WebDAVMKCOL(WebDAVQuery): method = "MKCOL" diff --git a/UI/Contacts/UIxListView.h b/UI/Contacts/UIxListView.h index efac5bced..09d89b000 100644 --- a/UI/Contacts/UIxListView.h +++ b/UI/Contacts/UIxListView.h @@ -24,8 +24,14 @@ #define UIXLISTVIEW_H #import +#import @interface UIxListView : UIxComponent +{ + NGVList *list; + SOGoContactGCSList *co; + id item; +} @end diff --git a/UI/Contacts/UIxListView.m b/UI/Contacts/UIxListView.m index 5998705e8..b075c80c7 100644 --- a/UI/Contacts/UIxListView.m +++ b/UI/Contacts/UIxListView.m @@ -20,8 +20,103 @@ * Boston, MA 02111-1307, USA. */ +#import +#import +#import +#import +#import + #import "UIxListView.h" @implementation UIxListView + +- (NSString *) listName +{ + return [list fn]; +} + +- (BOOL) hasNickname +{ + return [list nickname] != nil; +} +- (NSString *) listNickname +{ + return [list nickname]; +} + +- (BOOL) hasDescription +{ + return [list description] != nil; +} +- (NSString *) listDescription +{ + return [list description]; +} + +- (NSArray *) components +{ + return [list cardReferences]; +} +- (NSString *) itemText +{ + NSString *rc; + + rc = [NSString stringWithFormat: @"%@ <%@>", + [item fn], [item email]]; + + return rc; +} + +- (void) checkListReferences +{ + NSMutableArray *invalid; + NGVCardReference *card; + int i, count; + id test; + + invalid = [NSMutableArray array]; + + count = [[list cardReferences] count]; + for (i = 0; i < count; i++) + { + card = [[list cardReferences] objectAtIndex: i]; + test = [[co container] lookupName: [card reference] + inContext: context + acquire: NO]; + if ([test isKindOfClass: [NSException class]]) + { + NSLog (@"%@ not found", [card reference]); + [invalid addObject: [card copy]]; + } + } + + count = [invalid count]; + if (count > 0) + { + for (i = 0; i < count; i++) + [list deleteCardReference: [invalid objectAtIndex: i]]; + [co save]; + } +} + +- (id ) defaultAction +{ + id rc; + + co = [self clientObject]; + list = [co vList]; + + if (list) + { + [self checkListReferences]; + rc = self; + } + else + rc = [NSException exceptionWithHTTPStatus: 404 /* Not Found */ + reason: @"could not locate contact"]; + + return rc; +} + @end diff --git a/UI/Templates/ContactsUI/UIxListView.wox b/UI/Templates/ContactsUI/UIxListView.wox index 02bb02116..a3b7c1a28 100644 --- a/UI/Templates/ContactsUI/UIxListView.wox +++ b/UI/Templates/ContactsUI/UIxListView.wox @@ -9,4 +9,17 @@ className="UIxPageFrame" title="name" const:popup="YES" - >Unimplemented + > +

+ +

+
+ +
+
+
    + +
  • +
    +
+