merge of '24faa1b3ee3108a8faa323c456c74d517b4e233d'

and 'ce654c24f2f00574570b6f4719cccc588905dc0e'

Monotone-Parent: 24faa1b3ee3108a8faa323c456c74d517b4e233d
Monotone-Parent: ce654c24f2f00574570b6f4719cccc588905dc0e
Monotone-Revision: 9091f162c671a0d32c003aa063bff71fb1e68a10

Monotone-Author: flachapelle@inverse.ca
Monotone-Date: 2009-10-05T22:10:12
Monotone-Branch: ca.inverse.sogo
This commit is contained in:
Francis Lachapelle
2009-10-05 22:10:12 +00:00
9 changed files with 495 additions and 7 deletions

View File

@@ -1,3 +1,39 @@
2009-10-05 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* Tests/test-maildav.py (DAVMailCollectionTest._testFilter): fixed
to use the "test-dav-mail" folder name as collection during the
tests.
* SoObjects/Mailer/SOGoMailFolder.m (-davMailQuery): new method
implementing the "mail-query" REPORT for Inverse's maildav
protocol.
(-parseDAVRequestedProperties:): new method making use of the
"davIMAPFieldsTable" method below for parsing fields in the
mail-query request.
(-_mailSortingFromSortElement:): new method to parse the "sort"
element of mail-query requests.
(-_fetchMessageProperties:matchingQualifier:andSorting:): new
stub for a method that will fetch the IMAP messages based on the
criteria found above.
(-_appendProperties:fromMessages:toResponse:) new stub for a
method that will add the content of the resulting set of message
properties to the DAV response.
2009-10-05 Cyril Robert <crobert@inverse.ca>
* SoObjects/Mailer/SOGoMailFolder.m (-davIMAPFieldsTable): new
method designed to match maildav properties on mail objects with
IMAP fields, for PROPFIND and REPORT methods.
* Tests/test-webdavlib.py: Added new tests for the URL parser / regexp.
* Tests/webdavlib.py: Fixed errors in the URL regexp.
* SoObjects/Mailer/EOQualifier+MailDAV.m: New category to generate IMAP
qualifiers from DAV filters. (REPORT)
Formatted dates using ([NSCalendarDate rfc822DateString]). Added
NOT and extra fields.
2009-10-04 Ludovic Marcotte <lmarcotte@inverse.ca>
* Adjusted the Welsh and Dutch templates for

View File

@@ -0,0 +1,37 @@
/* EOQualifier+MailDAV.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 EOQUALIFIER_MAILDAV_H
#define EOQUALIFIER_MAILDAV_H
#import <EOControl/EOQualifier.h>
#define XMLNS_INVERSEDAV @"urn:inverse:params:xml:ns:inverse-dav"
@class DOMElement;
@interface EOQualifier (SOGoMailDAVExtension)
+ (id) qualifierFromMailDAVMailFilters: (DOMElement *) mailFilters;
@end
#endif /* EOQUALIFIER_MAILDAV_H */

View File

@@ -0,0 +1,233 @@
/* EOQualifier+MailDAV.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 <Foundation/NSSet.h>
#import <DOM/DOMElement.h>
#import <DOM/DOMProtocols.h>
#import <SaxObjC/XMLNamespaces.h>
#import <SOGo/DOMNode+SOGo.h>
#import <NGCards/NSString+NGCards.h>
#import <SoObjects/SOGo/NSCalendarDate+SOGo.h>
#import "EOQualifier+MailDAV.h"
@implementation EOQualifier (SOGoMailDAVExtension)
+ (NSString *) buildQualifierFromFilters: (DOMElement *) mailFilters
{
NSMutableArray *qualifiers;
NSString *qual, *buffer;
id <DOMNodeList> list;
DOMElement *current;
NSCalendarDate *startDate, *endDate;
int count, max, intValue;
NSString *negate;
qualifiers = [NSMutableArray array];
qual = nil;
#warning Qualifiers may be invalid, need to be tested
list = [mailFilters childNodes];
if (list)
{
max = [list length];
for (count = 0; count < max; count++)
{
current = [list objectAtIndex: count];
if ([current nodeType] == DOM_ELEMENT_NODE)
{
// Negate condition
if ([current attribute: @"not"])
negate = @"NOT ";
else
negate = @"";
// Received date
if ([[current tagName] isEqualToString: @"receive-date"])
{
startDate = [[current attribute: @"from"] asCalendarDate];
endDate = [[current attribute: @"to"] asCalendarDate];
if (startDate && [startDate isEqual: endDate])
[qualifiers addObject:
[NSString stringWithFormat: @"%@(on = '%@')",
negate, [startDate rfc822DateString]]];
else if (startDate)
[qualifiers addObject:
[NSString stringWithFormat: @"%@(since > '%@')",
negate, [startDate rfc822DateString]]];
if (endDate)
[qualifiers addObject:
[NSString stringWithFormat: @"%@(before < '%@')",
negate, [endDate rfc822DateString]]];
}
// Sent date
else if ([[current tagName] isEqualToString: @"date"])
{
startDate = [[current attribute: @"from"] asCalendarDate];
endDate = [[current attribute: @"to"] asCalendarDate];
if (startDate && [startDate isEqual: endDate])
[qualifiers addObject:
[NSString stringWithFormat: @"%@(senton = '%@')",
negate, [startDate rfc822DateString]]];
else if (startDate)
[qualifiers addObject:
[NSString stringWithFormat: @"%@(sentsince > '%@')",
negate, [startDate rfc822DateString]]];
if (endDate)
[qualifiers addObject:
[NSString stringWithFormat: @"%@(sentbefore < '%@')",
negate, [endDate rfc822DateString]]];
}
// Sequence
else if ([[current tagName] isEqualToString: @"sequence"])
{
//TODO
}
// UID
else if ([[current tagName] isEqualToString: @"uid"])
{
buffer = [current attribute: @"uid"];
if (buffer)
[qualifiers addObject:
[NSString stringWithFormat: @"%@(uid = '%@')",
negate, buffer]];
}
// From
else if ([[current tagName] isEqualToString: @"from"])
{
buffer = [current attribute: @"from"];
if (buffer)
[qualifiers addObject:
[NSString stringWithFormat: @"%@(from doesContain: '%@')",
negate, buffer]];
}
// To
else if ([[current tagName] isEqualToString: @"to"])
{
buffer = [current attribute: @"to"];
if (buffer)
[qualifiers addObject:
[NSString stringWithFormat: @"%@(to doesContain: '%@')",
negate, buffer]];
}
// Size
else if ([[current tagName] isEqualToString: @"size"])
{
intValue = [[current attribute: @"min"] intValue];
[qualifiers addObject:
[NSString stringWithFormat: @"%@(larger > '%d')",
negate, intValue]];
intValue = [[current attribute: @"max"] intValue];
if (intValue)
[qualifiers addObject:
[NSString stringWithFormat: @"%@(smaller < '%d')",
negate, intValue]];
}
// Answered
else if ([[current tagName] isEqualToString: @"answered"])
{
intValue = [[current attribute: @"answered"] intValue];
if (intValue)
[qualifiers addObject: [NSString stringWithFormat:
@"%@(answered)", negate]];
intValue = [[current attribute: @"unanswered"] intValue];
if (intValue)
[qualifiers addObject: [NSString stringWithFormat:
@"%@(unanswered)", negate]];
}
// Draft
else if ([[current tagName] isEqualToString: @"draft"])
{
intValue = [[current attribute: @"draft"] intValue];
if (intValue)
[qualifiers addObject: [NSString stringWithFormat:
@"%@(draft)", negate]];
}
// Flagged
else if ([[current tagName] isEqualToString: @"flagged"])
{
intValue = [[current attribute: @"flagged"] intValue];
if (intValue)
[qualifiers addObject: [NSString stringWithFormat:
@"%@(flagged)", negate]];
}
// Recent
else if ([[current tagName] isEqualToString: @"recent"])
{
intValue = [[current attribute: @"recent"] intValue];
if (intValue)
[qualifiers addObject: [NSString stringWithFormat:
@"%@(recent)", negate]];
}
// Seen
else if ([[current tagName] isEqualToString: @"seen"])
{
intValue = [[current attribute: @"seen"] intValue];
if (intValue)
[qualifiers addObject: [NSString stringWithFormat:
@"%@(seen)", negate]];
}
// Deleted
else if ([[current tagName] isEqualToString: @"deleted"])
{
intValue = [[current attribute: @"deleted"] intValue];
if (intValue)
[qualifiers addObject: [NSString stringWithFormat:
@"%@(deleted)", negate]];
}
// Keywords
else if ([[current tagName] isEqualToString: @"keywords"])
{
buffer = [current attribute: @"keywords"];
if (buffer)
[qualifiers addObject:
[NSString stringWithFormat: @"%@(keywords doesContain: '%@')",
negate, buffer]];
}
}
}
}
if ([qualifiers count])
qual = [qualifiers componentsJoinedByString: @" AND "];
return qual;
}
+ (id) qualifierFromMailDAVMailFilters: (DOMElement *) mailFilters
{
EOQualifier *newQualifier;
NSString *qual;
qual = [EOQualifier buildQualifierFromFilters: mailFilters];
newQualifier = [EOQualifier qualifierWithQualifierFormat: qual];
return newQualifier;
}
@end

View File

@@ -32,6 +32,7 @@ Mailer_OBJC_FILES += \
SOGoMailForward.m \
SOGoMailReply.m \
\
EOQualifier+MailDAV.m \
NSData+Mail.m \
NSString+Mail.m

View File

@@ -34,18 +34,26 @@
#import <NGExtensions/NSString+misc.h>
#import <NGExtensions/NSFileManager+Extensions.h>
#import <DOM/DOMElement.h>
#import <DOM/DOMProtocols.h>
#import <SaxObjC/XMLNamespaces.h>
#import <NGImap4/NGImap4Connection.h>
#import <NGImap4/NGImap4Client.h>
#import <SoObjects/SOGo/SOGoPermissions.h>
#import <SoObjects/SOGo/SOGoUser.h>
#import <SoObjects/SOGo/NSArray+Utilities.h>
#import <SOGo/DOMNode+SOGo.h>
#import <SOGo/NSArray+Utilities.h>
#import <SOGo/SOGoPermissions.h>
#import <SOGo/SOGoUser.h>
#import "EOQualifier+MailDAV.h"
#import "SOGoMailObject.h"
#import "SOGoMailAccount.h"
#import "SOGoMailManager.h"
#import "SOGoMailFolder.h"
#define XMLNS_INVERSEDAV @"urn:inverse:params:xml:ns:inverse-dav"
static NSString *defaultUserID = @"anyone";
#warning this could be detected from the capabilities
@@ -1102,7 +1110,159 @@ static NSString *spoolFolder = nil;
return [self nameInContainer];
}
// For DAV PUT
- (NSDictionary *) davIMAPFieldsTable
{
static NSMutableDictionary *davIMAPFieldsTable = nil;
if (!davIMAPFieldsTable)
{
davIMAPFieldsTable = [NSMutableDictionary new];
[davIMAPFieldsTable setObject: @"BODY[HEADER.FIELDS (DATE)]"
forKey: @"{urn:schemas:httpmail:}date"];
[davIMAPFieldsTable setObject: @""
forKey: @"{urn:schemas:httpmail:}hasattachment"];
[davIMAPFieldsTable setObject: @""
forKey: @"{urn:schemas:httpmail:}read"];
[davIMAPFieldsTable setObject: @"BODY"
forKey: @"{urn:schemas:httpmail:}textdescription"];
[davIMAPFieldsTable setObject: @"BODY[HEADER.FIELDS (CC)]"
forKey: @"{urn:schemas:mailheader:}cc"];
[davIMAPFieldsTable setObject: @"BODY[HEADER.FIELDS (DATE)]"
forKey: @"{urn:schemas:mailheader:}date"];
[davIMAPFieldsTable setObject: @"BODY[HEADER.FIELDS (FROM)]"
forKey: @"{urn:schemas:mailheader:}from"];
[davIMAPFieldsTable setObject: @"BODY[HEADER.FIELDS (INREPLYTO)]"
forKey: @"{urn:schemas:mailheader:}in-reply-to"];
[davIMAPFieldsTable setObject: @"BODY[HEADER.FIELDS (MESSAGEID)]"
forKey: @"{urn:schemas:mailheader:}message-id"];
[davIMAPFieldsTable setObject: @"BODY[HEADER.FIELDS (RECEIVED)]"
forKey: @"{urn:schemas:mailheader:}received"];
[davIMAPFieldsTable setObject: @"BODY[HEADER.FIELDS (REFERENCES)]"
forKey: @"{urn:schemas:mailheader:}references"];
[davIMAPFieldsTable setObject: @"BODY[HEADER.FIELDS (SUBJECT)]"
forKey: @"{urn:schemas:mailheader:}displayname"];
[davIMAPFieldsTable setObject: @"BODY[HEADER.FIELDS (TO)]"
forKey: @"{urn:schemas:mailheader:}to"];
}
return davIMAPFieldsTable;
}
- (NSDictionary *) _davIMAPFieldsForProperties: (NSArray *) properties
{
NSMutableDictionary *davIMAPFields;
NSDictionary *davIMAPFieldsTable;
NSString *imapField, *property;
unsigned int count, max;
davIMAPFieldsTable = [self davIMAPFieldsTable];
max = [properties count];
davIMAPFields = [NSMutableDictionary dictionaryWithCapacity: max];
for (count = 0; count < max; count++)
{
property = [properties objectAtIndex: count];
imapField = [davIMAPFieldsTable objectForKey: property];
if (imapField)
[davIMAPFields setObject: imapField forKey: property];
else
[self errorWithFormat: @"DAV property '%@' has no matching IMAP field,"
@" response could be incomplete", property];
}
return davIMAPFields;
}
- (NSDictionary *) parseDAVRequestedProperties: (DOMElement *) propElement
{
NSArray *properties;
NSDictionary *imapFieldsTable;
properties = [propElement flatPropertyNameOfSubElements];
imapFieldsTable = [self _davIMAPFieldsForProperties: properties];
return imapFieldsTable;
}
- (NSString *) _mailSortingFromSortElement: (DOMElement *) sortElement
{
NSArray *imapFields;
NSString *davReverseAttr;
NSMutableString *imapSortCriteria;
imapSortCriteria = [NSMutableString string];
imapFields = [[self parseDAVRequestedProperties: sortElement] allValues];
davReverseAttr = [[sortElement attribute: @"order"] uppercaseString];
if ([davReverseAttr isEqualToString: @"descending"])
[imapSortCriteria appendString: @"REVERSE "];
else if ([davReverseAttr length]
&& ![davReverseAttr isEqualToString: @"ascending"])
[self errorWithFormat: @"unrecognized sort order: '%@'",
davReverseAttr];
[imapSortCriteria
appendString: [imapFields componentsJoinedByString: @" "]];
return imapSortCriteria;
}
- (NSArray *) _fetchMessageProperties: (NSDictionary *) properties
matchingQualifier: (EOQualifier *) searchQualifier
andSorting: (NSString *) sorting
{
#warning not implemented
return nil;
}
- (void) _appendProperties: (NSArray *) keys
fromMessages: (NSArray *) messages
toResponse: (WOResponse *) response
{
#warning not implemented
}
- (id) davMailQuery: (id) queryContext
{
WOResponse *r;
id <DOMDocument> document;
DOMElement *documentElement, *propElement, *filterElement, *sortElement;
NSDictionary *properties;
NSArray *messages;
EOQualifier *searchQualifier;
NSString *sorting;
r = [context response];
[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"];
document = [[context request] contentAsDOMDocument];
documentElement = (DOMElement *) [document documentElement];
propElement = [documentElement firstElementWithTag: @"prop"
inNamespace: XMLNS_WEBDAV];
properties = [self parseDAVRequestedProperties: propElement];
filterElement = [documentElement firstElementWithTag: @"mail-filters"
inNamespace: XMLNS_INVERSEDAV];
searchQualifier = [EOQualifier
qualifierFromMailDAVMailFilters: filterElement];
sortElement = [documentElement firstElementWithTag: @"sort"
inNamespace: XMLNS_INVERSEDAV];
sorting = [self _mailSortingFromSortElement: sortElement];
messages = [self _fetchMessageProperties: properties
matchingQualifier: searchQualifier
andSorting: sorting];
[r setStatus: 207];
[r appendContentString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"];
[self _appendProperties: [properties allKeys]
fromMessages: messages
toResponse: r];
return r;
}
- (NSException *) _appendMessageData: (NSData *) data
usingId: (int *) imap4id;
{

View File

@@ -22,4 +22,7 @@
= davCollectionQuery;
"{urn:inverse:params:xml:ns:inverse-dav}acl-query" = davAclQuery;
"{urn:inverse:params:xml:ns:inverse-dav}user-query" = davUserQuery;
/* Inverse MailDAV */
"{urn:inverse:params:xml:ns:inverse-dav}mail-query" = davMailQuery;
}

View File

@@ -310,8 +310,8 @@ class DAVMailCollectionTest(unittest.TestCase):
expected_hrefs[href] = True
received_count = 0
query = webdavlib.MailDAVMailQuery(self.resource, ["displayname"],
filter[0])
url = "%sfolder%s" % (self.resource, "test-dav-mail")
query = webdavlib.MailDAVMailQuery(url, ["displayname"], filter[0])
self.client.execute(query)
self.assertEquals(query.response["status"], 207,
"filter %s:\n\tunexpected status: %d"

View File

@@ -28,5 +28,23 @@ class HTTPUnparsedURLTest(unittest.TestCase):
self.assertEquals(testURL.port, None)
self.assertEquals(testURL.path, "/folder/folder/simplereference")
pathURL = "http://user:secret@bla.com/hooray"
testURL = HTTPUnparsedURL(pathURL)
self.assertEquals(testURL.protocol, "http")
self.assertEquals(testURL.username, "user")
self.assertEquals(testURL.password, "secret")
self.assertEquals(testURL.hostname, "bla.com")
self.assertEquals(testURL.port, None)
self.assertEquals(testURL.path, "/hooray")
pathURL = "http://user@bla.com:80/hooray"
testURL = HTTPUnparsedURL(pathURL)
self.assertEquals(testURL.protocol, "http")
self.assertEquals(testURL.username, "user")
self.assertEquals(testURL.password, None)
self.assertEquals(testURL.hostname, "bla.com")
self.assertEquals(testURL.port, "80")
self.assertEquals(testURL.path, "/hooray")
if __name__ == "__main__":
unittest.main()

View File

@@ -24,7 +24,7 @@ class HTTPUnparsedURL:
url_parts = url.split("?")
alpha_match = "[a-zA-Z0-9%\._-]+"
num_match = "[0-9]+"
pattern = ("((%s)://(((%s)(:(%s)?)@)?(%s)(:(%s))))?(/.*)"
pattern = ("((%s)://(((%s)(:(%s))?@)?(%s)(:(%s))?))?(/.*)"
% (alpha_match, alpha_match, alpha_match,
alpha_match, num_match))
url_re = re.compile(pattern)