diff --git a/ChangeLog b/ChangeLog index f335bb8ad..f901159d8 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,18 @@ +2009-08-05 Wolfgang Sourdeau + + * SoObjects/SOGo/SOGoGCSFolder.m (-davSyncCollection): enhanced to + return a DAV error with the "valid-sync-token" condition tag when + a token is provided while the collection is empty. + + * SoObjects/SOGo/WOResponse+SOGo.m: new class module. + (-appendDAVError:): utility method that adds and formats the XML + body of a DAV error, with the error condition passed as argument. + +2009-08-05 Ludovic Marcotte + + * Added missing files for Hungarian translation + * Fixed the reply / forward ops for many translations + 2009-08-03 Ludovic Marcotte * Dropped the current caching subsystem based on diff --git a/Documentation/SOGo Installation Guide.odt b/Documentation/SOGo Installation Guide.odt index b610a6563..5c24a0c71 100644 Binary files a/Documentation/SOGo Installation Guide.odt and b/Documentation/SOGo Installation Guide.odt differ diff --git a/NEWS b/NEWS index 9d8d44edb..5881208c2 100644 --- a/NEWS +++ b/NEWS @@ -7,6 +7,7 @@ - added the possibility to disable alarms or tasks from Web calendars - added support for printing cards - the event/task summary dialog is now similar to Lightning +- replaced GNUstep's DO subsystem with memcached 1.0-20090714 (1.0.3) -------------------- diff --git a/SoObjects/Mailer/SOGoMailForward.h b/SoObjects/Mailer/SOGoMailForward.h index 6c1b94f06..bcfa41d22 100644 --- a/SoObjects/Mailer/SOGoMailForward.h +++ b/SoObjects/Mailer/SOGoMailForward.h @@ -39,6 +39,12 @@ @end +@interface SOGoMailBrazilianPortugueseForward : SOGoMailForward +@end + +@interface SOGoMailCzechForward : SOGoMailForward +@end + @interface SOGoMailDutchForward : SOGoMailForward @end @@ -51,6 +57,9 @@ @interface SOGoMailGermanForward : SOGoMailForward @end +@interface SOGoMailHungarianForward : SOGoMailForward +@end + @interface SOGoMailItalianForward : SOGoMailForward @end @@ -60,4 +69,7 @@ @interface SOGoMailRussianForward : SOGoMailForward @end +@interface SOGoMailWelshForward : SOGoMailForward +@end + #endif /* SOGOMAILFORWARD_H */ diff --git a/SoObjects/Mailer/SOGoMailForward.m b/SoObjects/Mailer/SOGoMailForward.m index e2c07716e..be2450393 100644 --- a/SoObjects/Mailer/SOGoMailForward.m +++ b/SoObjects/Mailer/SOGoMailForward.m @@ -225,6 +225,12 @@ @end +@implementation SOGoMailBrazilianPortugueseForward +@end + +@implementation SOGoMailCzechForward +@end + @implementation SOGoMailDutchForward @end @@ -237,6 +243,9 @@ @implementation SOGoMailGermanForward @end +@implementation SOGoMailHungarianForward +@end + @implementation SOGoMailItalianForward @end @@ -245,3 +254,6 @@ @implementation SOGoMailRussianForward @end + +@implementation SOGoMailWelshForward +@end diff --git a/SoObjects/Mailer/SOGoMailHungarianForward.wo/SOGoMailHungarianForward.wod b/SoObjects/Mailer/SOGoMailHungarianForward.wo/SOGoMailHungarianForward.wod new file mode 100644 index 000000000..7787fa18e --- /dev/null +++ b/SoObjects/Mailer/SOGoMailHungarianForward.wo/SOGoMailHungarianForward.wod @@ -0,0 +1,79 @@ +subject: WOString { + value = subject; + escapeHTML = NO; +} + +date: WOString { + value = date; + escapeHTML = NO; +} + +from: WOString { + value = from; + escapeHTML = NO; +} + +newLine: WOString { + value = newLine; + escapeHTML = NO; +} + +hasReplyTo: WOConditional { + condition = hasReplyTo; +} + +replyTo: WOString { + value = replyTo; + escapeHTML = NO; +} + +hasOrganization: WOConditional { + condition = hasOrganization; +} + +organization: WOString { + value = organization; + escapeHTML = NO; +} + +to: WOString { + value = to; + escapeHTML = NO; +} + +hasCc: WOConditional { + condition = hasCc; +} + +cc: WOString { + value = cc; + escapeHTML = NO; +} + +hasNewsGroups: WOConditional { + condition = hasNewsGroups; +} + +newsgroups: WOString { + value = newsgroups; + escapeHTML = NO; +} + +hasReferences: WOConditional { + condition = hasReferences; +} + +references: WOString { + value = references; + escapeHTML = NO; +} + +messageBody: WOString { + value = messageBody; + escapeHTML = NO; +} + +signature: WOString { + value = signature; + escapeHTML = NO; +} diff --git a/SoObjects/Mailer/SOGoMailHungarianReply.wo/SOGoMailHungarianReply.wod b/SoObjects/Mailer/SOGoMailHungarianReply.wo/SOGoMailHungarianReply.wod new file mode 100644 index 000000000..3fbed6d61 --- /dev/null +++ b/SoObjects/Mailer/SOGoMailHungarianReply.wo/SOGoMailHungarianReply.wod @@ -0,0 +1,106 @@ +outlookMode: WOConditional { + condition = outlookMode; +} + +standardMode: WOConditional { + condition = outlookMode; + negate = YES; +} + +subject: WOString { + value = subject; + escapeHTML = NO; +} + +date: WOString { + value = date; + escapeHTML = NO; +} + +from: WOString { + value = from; + escapeHTML = NO; +} + +newLine: WOString { + value = newLine; + escapeHTML = NO; +} + +hasReplyTo: WOConditional { + condition = hasReplyTo; +} + +replyTo: WOString { + value = replyTo; + escapeHTML = NO; +} + +hasOrganization: WOConditional { + condition = hasOrganization; +} + +organization: WOString { + value = organization; + escapeHTML = NO; +} + +to: WOString { + value = to; + escapeHTML = NO; +} + +hasCc: WOConditional { + condition = hasCc; +} + +cc: WOString { + value = cc; + escapeHTML = NO; +} + +hasNewsGroups: WOConditional { + condition = hasNewsGroups; +} + +newsgroups: WOString { + value = newsgroups; + escapeHTML = NO; +} + +hasReferences: WOConditional { + condition = hasReferences; +} + +references: WOString { + value = references; + escapeHTML = NO; +} + +messageBody: WOString { + value = messageBody; + escapeHTML = NO; +} + +signature: WOString { + value = signature; + escapeHTML = NO; +} + +replyPlacementOnTop: WOConditional { + condition = replyPlacementOnTop; +} + +replyPlacementOnBottom: WOConditional { + condition = replyPlacementOnTop; + negate = YES; +} + +signaturePlacementOnTop: WOConditional { + condition = signaturePlacementOnTop; +} + +signaturePlacementOnBottom: WOConditional { + condition = signaturePlacementOnTop; + negate = YES; +} diff --git a/SoObjects/Mailer/SOGoMailReply.h b/SoObjects/Mailer/SOGoMailReply.h index 9f320c27b..31fb41779 100644 --- a/SoObjects/Mailer/SOGoMailReply.h +++ b/SoObjects/Mailer/SOGoMailReply.h @@ -44,6 +44,12 @@ @end +@interface SOGoMailBrazilianPortugueseReply : SOGoMailReply +@end + +@interface SOGoMailCzechReply : SOGoMailReply +@end + @interface SOGoMailDutchReply : SOGoMailReply @end @@ -56,6 +62,9 @@ @interface SOGoMailGermanReply : SOGoMailReply @end +@interface SOGoMailHungarianReply : SOGoMailReply +@end + @interface SOGoMailItalianReply : SOGoMailReply @end @@ -65,4 +74,7 @@ @interface SOGoMailRussianReply : SOGoMailReply @end +@interface SOGoMailWelshReply : SOGoMailReply +@end + #endif /* SOGOMAILREPLY_H */ diff --git a/SoObjects/Mailer/SOGoMailReply.m b/SoObjects/Mailer/SOGoMailReply.m index 0e7638260..57dca3c22 100644 --- a/SoObjects/Mailer/SOGoMailReply.m +++ b/SoObjects/Mailer/SOGoMailReply.m @@ -108,6 +108,12 @@ @end +@implementation SOGoMailBrazilianPortugueseReply +@end + +@implementation SOGoMailCzechReply +@end + @implementation SOGoMailDutchReply @end @@ -120,6 +126,9 @@ @implementation SOGoMailGermanReply @end +@implementation SOGoMailHungarianReply +@end + @implementation SOGoMailItalianReply @end @@ -128,3 +137,6 @@ @implementation SOGoMailRussianReply @end + +@implementation SOGoMailWelshReply +@end diff --git a/SoObjects/Mailer/SOGoMailWelshForward.wo/SOGoMailWelshForward.html b/SoObjects/Mailer/SOGoMailWelshForward.wo/SOGoMailWelshForward.html index a5db1d073..0e90a3f5a 100644 --- a/SoObjects/Mailer/SOGoMailWelshForward.wo/SOGoMailWelshForward.html +++ b/SoObjects/Mailer/SOGoMailWelshForward.wo/SOGoMailWelshForward.html @@ -5,7 +5,7 @@ Testun: <#subject/><#newLine/> Dyddiad: <#date/><#newLine/> Oddi wrth: <#from/><#newLine/> -<#hasReplyTo>Ymateb i: <#replyTo/><#hasOrganization>Sefydliad: <#organization/>I: <#to/><#newLine/> +<#hasReplyTo>Ymateb i: <#replyTo/><#hasOrganization>Sefydliad: <#organization/>I: <#to/><#newLine/> <#hasCc>CC: <#cc/><#hasNewsGroups>Grwpiau newyddion: <#newsgroups/><#hasReferences>Cyfeirnodau: <#references/><#newLine/> <#newLine/> <#messageBody/><#newLine/> diff --git a/SoObjects/SOGo/GNUmakefile b/SoObjects/SOGo/GNUmakefile index 6db8b586a..335778d72 100644 --- a/SoObjects/SOGo/GNUmakefile +++ b/SoObjects/SOGo/GNUmakefile @@ -58,6 +58,7 @@ SOGo_HEADER_FILES = \ DOMNode+SOGo.h \ \ WORequest+SOGo.h \ + WOResponse+SOGo.h \ WOContext+SOGo.h # SOGoCustomGroupFolder.m \ @@ -106,6 +107,7 @@ SOGo_OBJC_FILES = \ DOMNode+SOGo.m \ \ WORequest+SOGo.m \ + WOResponse+SOGo.m \ WOContext+SOGo.m SOGo_RESOURCE_FILES = \ diff --git a/SoObjects/SOGo/SOGoGCSFolder.m b/SoObjects/SOGo/SOGoGCSFolder.m index a9d3e8d15..edcc5c0fd 100644 --- a/SoObjects/SOGo/SOGoGCSFolder.m +++ b/SoObjects/SOGo/SOGoGCSFolder.m @@ -69,6 +69,7 @@ #import "SOGoUser.h" #import "SOGoWebDAVAclManager.h" #import "WORequest+SOGo.h" +#import "WOResponse+SOGo.h" #import "SOGoGCSFolder.h" @@ -896,7 +897,7 @@ static NSArray *childRecordFields = nil; } - (NSArray *) _fetchSyncTokenFields: (NSDictionary *) properties - matchingSyncToken: (int) syncToken + matchingSyncToken: (NSString *) syncToken { /* TODO: - validation: @@ -908,6 +909,7 @@ static NSArray *childRecordFields = nil; EOQualifier *qualifier; NSEnumerator *addFields; NSString *currentField, *filter; + int syncTokenInt; fields = [NSMutableArray arrayWithObjects: @"c_name", @"c_component", @"c_creationdate", @"c_lastmodified", nil]; @@ -916,16 +918,17 @@ static NSArray *childRecordFields = nil; if ([currentField length]) [fields addObjectUniquely: currentField]; - if (syncToken) + if ([syncToken length]) { + syncTokenInt = [syncToken intValue]; qualifier = [EOQualifier qualifierWithQualifierFormat: - @"c_lastmodified > %d", syncToken]; + @"c_lastmodified > %d", syncTokenInt]; mRecords = [NSMutableArray arrayWithArray: [self _fetchFields: fields - withQualifier: qualifier - ignoreDeleted: YES]]; + withQualifier: qualifier + ignoreDeleted: YES]]; qualifier = [EOQualifier qualifierWithQualifierFormat: @"c_lastmodified > %d and c_deleted == 1", - syncToken]; + syncTokenInt]; fields = [NSMutableArray arrayWithObjects: @"c_name", @"c_deleted", nil]; [mRecords addObjectsFromArray: [self _fetchFields: fields withQualifier: qualifier @@ -1090,10 +1093,10 @@ static NSArray *childRecordFields = nil; if (newToken < currentLM) newToken = currentLM; [syncResponses addObject: [self _syncResponseWithProperties: properties - andMethodSelectors: selectors - fromRecord: record - withToken: syncToken - andBaseURL: baseURL]]; + andMethodSelectors: selectors + fromRecord: record + withToken: syncToken + andBaseURL: baseURL]]; } NSZoneFree (NULL, selectors); @@ -1122,15 +1125,12 @@ static NSArray *childRecordFields = nil; NSString *syncToken; NSDictionary *properties; NSArray *records; - int syncTokenInt; r = [context response]; - [r setStatus: 207]; [r setContentEncoding: NSUTF8StringEncoding]; [r setHeader: @"text/xml; charset=\"utf-8\"" forKey: @"content-type"]; [r setHeader: @"no-cache" forKey: @"pragma"]; [r setHeader: @"no-cache" forKey: @"cache-control"]; - [r appendContentString:@"\r\n"]; document = [[context request] contentAsDOMDocument]; documentElement = (DOMElement *) [document documentElement]; @@ -1139,15 +1139,21 @@ static NSArray *childRecordFields = nil; propElement = [documentElement firstElementWithTag: @"prop" inNamespace: XMLNS_WEBDAV]; - - syncTokenInt = [syncToken intValue]; properties = [self parseDAVRequestedProperties: propElement]; records = [self _fetchSyncTokenFields: properties - matchingSyncToken: syncTokenInt]; - [self _appendComponentProperties: [properties allKeys] - fromRecords: records - matchingSyncToken: syncTokenInt - toResponse: r]; + matchingSyncToken: syncToken]; + if (![syncToken length] || [records count]) + { + [r setStatus: 207]; + [r appendContentString: @"\r\n"]; + [self _appendComponentProperties: [properties allKeys] + fromRecords: records + matchingSyncToken: [syncToken intValue] + toResponse: r]; + } + else + [r appendDAVError: davElement (@"valid-sync-token", XMLNS_WEBDAV)]; return r; } diff --git a/SoObjects/SOGo/WOResponse+SOGo.h b/SoObjects/SOGo/WOResponse+SOGo.h new file mode 100644 index 000000000..cc9c56a9e --- /dev/null +++ b/SoObjects/SOGo/WOResponse+SOGo.h @@ -0,0 +1,36 @@ +/* WOResponse+SOGo.h - this file is part of SOGo + * + * Copyright (C) 2009 Inverse inc. + * + * Author: Wolfgang Sourdeau + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef WORESPONSE_SOGo_H +#define WORESPONSE_SOGo_H + +#import + +@class NSDictionary; + +@interface WOResponse (SOGoSOPEUtilities) + +- (void) appendDAVError: (NSDictionary *) errorCondition; + +@end + +#endif /* WORESPONSE_SOGo_H */ diff --git a/SoObjects/SOGo/WOResponse+SOGo.m b/SoObjects/SOGo/WOResponse+SOGo.m new file mode 100644 index 000000000..737577769 --- /dev/null +++ b/SoObjects/SOGo/WOResponse+SOGo.m @@ -0,0 +1,46 @@ +/* WOResponse+SOGo.m - this file is part of SOGo + * + * Copyright (C) 2009 Inverse inc. + * + * Author: Wolfgang Sourdeau + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#import + +#import + +#import "NSDictionary+DAV.h" +#import "NSObject+DAV.h" + +#import "WOResponse+SOGo.h" + +@implementation WOResponse (SOGoSOPEUtilities) + +- (void) appendDAVError: (NSDictionary *) errorCondition +{ + NSDictionary *error; + + error = davElementWithContent (@"error", XMLNS_WEBDAV, errorCondition); + + [self setStatus: 403]; + [self appendContentString: @"\r\n"]; + [self appendContentString: [error asWebDavStringWithNamespaces: nil]]; +} + +@end diff --git a/Tests/README b/Tests/README new file mode 100644 index 000000000..fefa3f773 --- /dev/null +++ b/Tests/README @@ -0,0 +1,19 @@ +setup +----- + +(you need "python-xml" and "python-m2crypto" in order to run the scripts) + +1) copy testconfig.py.in to testconfig.py (make sure to never EVER add it to monotone) +2) edit testconfig.py to suit your environment +3) run the test scripts + +runnable scripts +---------------- + +webdav-sync.py + +other +----- + +propfind.py - a sample implementation of a PROPFIND request using webdavlib + diff --git a/Tests/propfind.py b/Tests/propfind.py new file mode 100755 index 000000000..28594e251 --- /dev/null +++ b/Tests/propfind.py @@ -0,0 +1,39 @@ +#!/usr/bin/python + +from testconfig import hostname, port, username, password + +import webdavlib + +import sys +import getopt + +def parseArguments(): + arguments = {} + +depth = "0" +quiet = False +(opts, args) = getopt.getopt(sys.argv[1:], "d:q", ["depth=", "quiet"]) + +for pair in opts: + print pair + if (pair[0] == "-d" or pair[0] == "--depth"): + depth = pair[1] + elif (pair[0] == "-q" or pair[0] == "--quiet"): + quiet = True + +# print "depth: " + depth + +nargs = len(args) +if (nargs > 0): + resource = args[0] + if (nargs > 1): + properties = args[1:] + else: + properties = [ "allprop" ] +else: + print "resource required" + sys.exit(-1) + +client = webdavlib.WebDAVClient(hostname, port, username, password) +propfind = webdavlib.WebDAVPROPFIND(resource, properties, depth) +client.execute(propfind) diff --git a/Tests/testconfig.py.in b/Tests/testconfig.py.in new file mode 100644 index 000000000..fbbaf2925 --- /dev/null +++ b/Tests/testconfig.py.in @@ -0,0 +1,4 @@ +hostname = "localhost" +port = "80" +username = "myuser" +password = "mypass" diff --git a/Tests/webdav-sync.py b/Tests/webdav-sync.py new file mode 100755 index 000000000..969a181fd --- /dev/null +++ b/Tests/webdav-sync.py @@ -0,0 +1,71 @@ +#!/usr/bin/python + +from testconfig import hostname, port, username, password + +import sys +import unittest +import webdavlib +import xml.xpath +import time + +resource = '/SOGo/dav/%s/Calendar/test-webdavsync/' % username + +class WebdavSyncTest(unittest.TestCase): + def setUp(self): + self.client = webdavlib.WebDAVClient(hostname, port, + username, password) + + def tearDown(self): + 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 + # non-empty collections: token validity, status codes for added, + # modified and removed elements + + # preparation + mkcol = webdavlib.WebDAVMKCOL(resource) + self.client.execute(mkcol) + self.assertEquals(mkcol.response["status"], 201, + "preparation: failure creating collection (code != 201)") + + # test queries: + # empty collection: + # without a token (query1) + # with a token (query2) + # non-empty collection: + # without a token (query3) + # with a token (query4) + + query1 = webdavlib.WebDAVSyncQuery(resource, None, [ "getetag" ]) + self.client.execute(query1) + 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] + # 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) + + self.assertTrue(token > 0) + self.assertTrue(token < int(query1.start)) + + # we make sure that any token is invalid when the collection is empty + 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"]) + self.assertTrue(len(cond_nodes) > 0, + "expected 'valid-sync-token' condition error") + +if __name__ == "__main__": + unittest.main() diff --git a/Tests/webdavlib.py b/Tests/webdavlib.py new file mode 100644 index 000000000..3498e9706 --- /dev/null +++ b/Tests/webdavlib.py @@ -0,0 +1,223 @@ +import httplib +import M2Crypto.httpslib +import time +import xml.sax.saxutils +import xml.dom.ext.reader.Sax2 +import sys + +class WebDAVClient: + def __init__(self, hostname, port, username, password, forcessl = False): + if port == "443" or forcessl: + self.conn = M2Crypto.httpslib.HTTPSConnection(hostname, int(port), + True) + else: + self.conn = httplib.HTTPConnection(hostname, port, True) + + self.simpleauth_hash = (("%s:%s" % (username, password)) + .encode('base64')[:-1]) + + def _prepare_headers(self, query, body): + headers = { "User-Agent": "Mozilla/5.0", + "content-length": len(body), + "authorization": "Basic %s" % self.simpleauth_hash } + if query.__dict__.has_key("query") and query.depth is not None: + headers["depth"] = query.depth + if query.__dict__.has_key("content_type"): + headers["content-type"] = query.content_type + + return headers + + def execute(self, query): + body = query.render() + + query.start = time.time() + self.conn.request(query.method, query.url, + body, self._prepare_headers(query, body)) + query.set_response(self.conn.getresponse()); + query.duration = time.time() - query.start + +class HTTPSimpleQuery: + method = None + + def __init__(self, url): + self.url = url + self.response = None + self.start = -1 + self.duration = -1 + + def render(): + return None + +class HTTPGET(HTTPSimpleQuery): + method = "GET" + +class HTTPQuery(HTTPSimpleQuery): + def __init__(self, url, content_type): + HTTPSimpleQuery.__init__(self, url) + self.content_type = content_type + + def set_response(self, http_response): + headers = {} + for rk, rv in http_response.getheaders(): + k = rk.lower() + headers[k] = rv + self.response = { "headers": headers, + "status": http_response.status, + "version": http_response.version, + "body": http_response.read() } + +class HTTPPUT(HTTPQuery): + method = "PUT" + + def __init__(self, url, content, content_type = "application/octet-stream"): + HTTPQuery.__init__(self, url, content_type) + self.content = content + + def render(self): + return self.content + +class WebDAVQuery(HTTPQuery): + method = None + + def __init__(self, url, depth = None): + HTTPQuery.__init__(self, url, "application/xml; charset=\"utf-8\"") + self.depth = depth + self.ns_mgr = _WD_XMLNS_MGR() + self.top_node = None + self.xml_response = None + + def render(self): + if self.top_node is not None: + text = ("\n%s" + % self.top_node.render(self.ns_mgr.render())) + else: + text = "" + + return text + + def render_tag(self, tag): + cb = tag.find("}") + if cb > -1: + ns = tag[1:cb] + real_tag = tag[cb+1:] + new_tag = self.ns_mgr.register(real_tag, ns) + else: + new_tag = tag + + return new_tag + + def set_response(self, http_response): + HTTPQuery.set_response(self, http_response) + headers = self.response["headers"] + if (headers.has_key("content-type") + and headers.has_key("content-length") + 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"]) + self.response["document"] = dom_response.documentElement + +class WebDAVMKCOL(WebDAVQuery): + method = "MKCOL" + +class WebDAVDELETE(WebDAVQuery): + method = "DELETE" + +class WebDAVREPORT(WebDAVQuery): + method = "REPORT" + +class WebDAVPROPFIND(WebDAVQuery): + method = "PROPFIND" + + 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)) + +class WebDAVSyncQuery(WebDAVREPORT): + def __init__(self, url, token, properties): + WebDAVQuery.__init__(self, url) + self.top_node = _WD_XMLTreeElement("sync-collection") + + sync_token = _WD_XMLTreeElement("sync-token") + self.top_node.append(sync_token) + 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)) + +# private classes to handle XML stuff +class _WD_XMLNS_MGR: + def __init__(self): + self.xmlns = {} + self.counter = 0 + + def render(self): + text = " xmlns=\"DAV:\"" + for k in self.xmlns: + text = text + " xmlns:%s=\"%s\"" % (self.xmlns[k], k) + + return text + + def create_key(self, namespace): + new_nssym = "n%d" % self.counter + self.counter = self.counter + 1 + self.xmlns[namespace] = new_nssym + + return new_nssym + + def register(self, tag, namespace): + if namespace != "DAV:": + if self.xmlns.has_key(namespace): + key = self.xmlns[namespace] + else: + key = self.create_key(namespace) + else: + key = None + + if key is not None: + newTag = "%s:%s" % (key, tag) + else: + newTag = tag + + return newTag + +class _WD_XMLTreeElement: + def __init__(self, tag): + self.tag = tag + self.children = [] + + def append(self, child): + self.children.append(child) + + def render(self, ns_text = None): + text = "<" + self.tag + + if ns_text is not None: + text = text + ns_text + + count = len(self.children) + if count > 0: + text = text + ">" + for x in range(0, count): + text = text + self.children[x].render() + text = text + "" + else: + text = text + "/>" + + return text + +class _WD_XMLTreeTextNode: + def __init__(self, text): + self.text = xml.sax.saxutils.escape(text) + + def render(self): + return self.text