merge of 'a0d0b11a9d09b6e2a4c1105454c00cca7c853ac5'

and 'd35569adb5f95d077a2e359cae7af87c6ba720d1'

Monotone-Parent: a0d0b11a9d09b6e2a4c1105454c00cca7c853ac5
Monotone-Parent: d35569adb5f95d077a2e359cae7af87c6ba720d1
Monotone-Revision: 2ae443d1e9e3ef66ebb42cbba16fc86deb005615

Monotone-Author: flachapelle@inverse.ca
Monotone-Date: 2009-08-06T14:37:53
Monotone-Branch: ca.inverse.sogo
This commit is contained in:
Francis Lachapelle
2009-08-06 14:37:53 +00:00
19 changed files with 716 additions and 21 deletions
+15
View File
@@ -1,3 +1,18 @@
2009-08-05 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* 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 <lmarcotte@inverse.ca>
* Added missing files for Hungarian translation
* Fixed the reply / forward ops for many translations
2009-08-03 Ludovic Marcotte <lmarcotte@inverse.ca>
* Dropped the current caching subsystem based on
Binary file not shown.
+1
View File
@@ -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)
--------------------
+12
View File
@@ -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 */
+12
View File
@@ -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
@@ -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;
}
@@ -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;
}
+12
View File
@@ -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 */
+12
View File
@@ -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
@@ -5,7 +5,7 @@
Testun: <#subject/><#newLine/>
Dyddiad: <#date/><#newLine/>
Oddi wrth: <#from/><#newLine/>
<#hasReplyTo>Ymateb i: <#replyTo/><#hasOrganization>Sefydliad: <#organization/></#hasOrganization>I: <#to/><#newLine/>
<#hasReplyTo>Ymateb i: <#replyTo/></#hasReplyTo><#hasOrganization>Sefydliad: <#organization/></#hasOrganization>I: <#to/><#newLine/>
<#hasCc>CC: <#cc/></#hasCc><#hasNewsGroups>Grwpiau newyddion: <#newsgroups/></#hasNewsGroups><#hasReferences>Cyfeirnodau: <#references/></#hasReferences><#newLine/>
<#newLine/>
<#messageBody/><#newLine/>
+2
View File
@@ -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 = \
+26 -20
View File
@@ -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:@"<?xml version=\"1.0\" encoding=\"utf-8\"?>\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: @"<?xml version=\"1.0\""
@" encoding=\"utf-8\"?>\r\n"];
[self _appendComponentProperties: [properties allKeys]
fromRecords: records
matchingSyncToken: [syncToken intValue]
toResponse: r];
}
else
[r appendDAVError: davElement (@"valid-sync-token", XMLNS_WEBDAV)];
return r;
}
+36
View File
@@ -0,0 +1,36 @@
/* WOResponse+SOGo.h - this file is part of SOGo
*
* Copyright (C) 2009 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
*
* 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 <NGObjWeb/WOResponse.h>
@class NSDictionary;
@interface WOResponse (SOGoSOPEUtilities)
- (void) appendDAVError: (NSDictionary *) errorCondition;
@end
#endif /* WORESPONSE_SOGo_H */
+46
View File
@@ -0,0 +1,46 @@
/* WOResponse+SOGo.m - this file is part of SOGo
*
* Copyright (C) 2009 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
*
* 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 <Foundation/NSString.h>
#import <SaxObjC/XMLNamespaces.h>
#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: @"<?xml version=\"1.0\""
@" encoding=\"utf-8\"?>\r\n"];
[self appendContentString: [error asWebDavStringWithNamespaces: nil]];
}
@end
+19
View File
@@ -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
+39
View File
@@ -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)
+4
View File
@@ -0,0 +1,4 @@
hostname = "localhost"
port = "80"
username = "myuser"
password = "mypass"
+71
View File
@@ -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()
+223
View File
@@ -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 = ("<?xml version=\"1.0\" encoding=\"utf-8\"?>\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 + "</" + self.tag + ">"
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