Added support for MailDAV PUT

Monotone-Parent: 90509c89f14ff1a8b0d7884244366bd33783ccc1
Monotone-Revision: 8515028ade639f5e3e38f20b4eba28b16a1250b1

Monotone-Author: crobert@inverse.ca
Monotone-Date: 2009-09-29T20:13:36
Monotone-Branch: ca.inverse.sogo
maint-2.0.2
C Robert 2009-09-29 20:13:36 +00:00
parent 0aaaf59814
commit 9493a33729
17 changed files with 344 additions and 52 deletions

View File

@ -1,3 +1,22 @@
2009-09-29 Cyril Robert <crobert@inverse.ca>
* SoObjects/Mailer/SOGoMailFolder.m (PUTAction:, _appendMessageData:usingId:):
Added new methods to enable MailDAV PUT on a collection.
* SoObjects/Mailer/SOGoMailBaseObject.m (IMAP4IDFromAppendResult:): Moved
here, since SOGoDraftObject and SOGoMailFolder need it.
* SoObjects/Mailer/SOGoDraftObject.m (_IMAP4IDFromAppendResult): Removed,
replaced by [SOGoMailBaseObject IMAP4IDFromAppendResult:].
* SoObjects/SOGo/SOGoObject.m: Changed file extension from "mail" to "eml".
* SoObjects/Mailer/SOGoMailAccount.m: Added accountName to fix errors in MailDAV.
* SoObjects/Mailer/SOGoMailAccounts.m: Added management for account names
to fix errors in MailDAV.
* SoObjects/Mailer/SOGoMailFolder.m (appendMessage:): Added this method to
facilitate the PUT from a SOGoMailObject.
* SoObjects/Mailer/SOGoMailObject.m (PUTAction:): Added to enable MailDAV
PUT on a non-existing object.
* UI/MailerUI/UIxMailMainFrame.m (mailAccounts): Changed the return value to
include both the account name and display name.
2009-09-29 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* SoObjects/SOGo/SOGoObject.m (SOGoSelectorForPropertyGetter)

View File

@ -365,19 +365,6 @@ static BOOL showTextAttachmentsInline = NO;
return IMAP4ID;
}
- (int) _IMAP4IDFromAppendResult: (NSDictionary *) result
{
NSDictionary *results;
NSString *flag, *newIdString;
results = [[result objectForKey: @"RawResponse"]
objectForKey: @"ResponseResult"];
flag = [results objectForKey: @"flag"];
newIdString = [[flag componentsSeparatedByString: @" "] objectAtIndex: 2];
return [newIdString intValue];
}
- (NSException *) save
{
NGImap4Client *client;
@ -405,7 +392,7 @@ static BOOL showTextAttachmentsInline = NO;
{
if (IMAP4ID > -1)
error = [imap4 markURLDeleted: [self imap4URL]];
IMAP4ID = [self _IMAP4IDFromAppendResult: result];
IMAP4ID = [self IMAP4IDFromAppendResult: result];
[self storeInfo];
}
else
@ -695,7 +682,7 @@ static BOOL showTextAttachmentsInline = NO;
else
{
// TODO: use subject for filename?
// error = [newDraft saveAttachment:content withName:@"forward.mail"];
// error = [newDraft saveAttachment:content withName:@"forward.eml"];
signature = [currentUser signature];
if ([signature length])
[self setText: [NSString stringWithFormat: @"\n-- \n%@", signature]];
@ -936,7 +923,7 @@ static BOOL showTextAttachmentsInline = NO;
if ([_ext isEqualToString: @"gif"]) return @"image/gif";
if ([_ext isEqualToString: @"jpg"]) return @"image/jpeg";
if ([_ext isEqualToString: @"jpeg"]) return @"image/jpeg";
if ([_ext isEqualToString: @"mail"]) return @"message/rfc822";
if ([_ext isEqualToString: @"eml"]) return @"message/rfc822";
return @"application/octet-stream";
}

View File

@ -42,12 +42,15 @@
@interface SOGoMailAccount : SOGoMailBaseObject
{
NSString *accountName;
SOGoMailFolder *inboxFolder;
SOGoDraftsFolder *draftsFolder;
SOGoSentFolder *sentFolder;
SOGoTrashFolder *trashFolder;
}
- (void) setAccountName: (NSString *) newAccountName;
- (BOOL) supportsQuotas;
/* folder pathes */

View File

@ -121,6 +121,7 @@ static NSString *fallbackIMAP4Server = nil;
draftsFolder = nil;
sentFolder = nil;
trashFolder = nil;
accountName = nil;
}
return self;
@ -132,9 +133,15 @@ static NSString *fallbackIMAP4Server = nil;
[draftsFolder release];
[sentFolder release];
[trashFolder release];
[accountName release];
[super dealloc];
}
- (void) setAccountName: (NSString *) newAccountName
{
ASSIGN (accountName, newAccountName);
}
/* shared accounts */
- (BOOL) isSharedAccount
@ -142,7 +149,7 @@ static NSString *fallbackIMAP4Server = nil;
NSString *s;
NSRange r;
s = [self nameInContainer];
s = accountName;
r = [s rangeOfString:@"@"];
if (r.length == 0) /* regular HTTP logins are never a shared mailbox */
return NO;
@ -275,7 +282,7 @@ static NSString *fallbackIMAP4Server = nil;
NSDictionary *mailAccount;
NSString *username, *escUsername, *hostString;
mailAccount = [[context activeUser] accountWithName: nameInContainer];
mailAccount = [[context activeUser] accountWithName: accountName];
if (mailAccount)
{
username = [mailAccount objectForKey: @"userName"];
@ -536,21 +543,19 @@ static NSString *fallbackIMAP4Server = nil;
- (NSString *) shortTitle
{
NSString *s, *login, *host;
NSString *login, *host;
NSRange r;
s = [self nameInContainer];
r = [s rangeOfString:@"@"];
r = [accountName rangeOfString:@"@"];
if (r.length > 0)
{
login = [s substringToIndex:r.location];
host = [s substringFromIndex:(r.location + r.length)];
login = [accountName substringToIndex:r.location];
host = [accountName substringFromIndex:(r.location + r.length)];
}
else
{
login = nil;
host = s;
host = accountName;
}
r = [host rangeOfString:@"."];

View File

@ -38,10 +38,15 @@
*/
@class NSArray;
@class NSMutableDictionary;
@interface SOGoMailAccounts : SOGoFolder
{
NSMutableDictionary *accountKeys;
}
- (NSArray *) toManyRelationshipKeys;
- (NSDictionary *) accountKeys;
@end

View File

@ -19,6 +19,7 @@
02111-1307, USA.
*/
#import <Foundation/NSDictionary.h>
#import <Foundation/NSString.h>
#import <NGObjWeb/NSException+HTTP.h>
@ -27,6 +28,7 @@
#import <NGExtensions/NSObject+Logs.h>
#import "../SOGo/NSArray+Utilities.h"
#import "../SOGo/NSString+Utilities.h"
#import "../SOGo/SOGoUser.h"
#import "SOGoMailAccount.h"
@ -41,22 +43,59 @@
// return [[container nameInContainer] isEqualToString: userLogin];
// }
- (NSArray *) toManyRelationshipKeys
- (id) init
{
if ((self = [super init]))
{
accountKeys = nil;
}
return self;
}
- (void) dealloc
{
[accountKeys release];
[super dealloc];
}
- (void) _initAccountKeys
{
NSArray *accounts;
NSString *currentName;
int count, max;
accounts = [[context activeUser] mailAccounts];
if (!accountKeys)
{
accountKeys = [NSMutableDictionary new];
return [accounts objectsForKey: @"name" notFoundMarker: nil];
accounts = [[context activeUser] mailAccounts];
max = [accounts count];
for (count = 0; count < max; count++)
{
currentName = [[accounts objectAtIndex: count] objectForKey: @"name"];
[accountKeys setObject: currentName
forKey: [currentName asCSSIdentifier]];
}
}
}
- (NSDictionary *) accountKeys
{
[self _initAccountKeys];
return accountKeys;
}
- (NSArray *) toManyRelationshipKeys
{
[self _initAccountKeys];
return [accountKeys allKeys];
}
/* name lookup */
- (BOOL) isValidMailAccountName: (NSString *) _key
{
return [[self toManyRelationshipKeys] containsObject: _key];
}
// - (id) mailAccountWithName: (NSString *) _key
// inContext: (id) _ctx
// {
@ -76,10 +115,11 @@
// }
- (id) lookupName: (NSString *) _key
inContext: (id) _ctx
acquire: (BOOL) _flag
inContext: (id) _ctx
acquire: (BOOL) _flag
{
id obj;
NSString *accountName;
// NSString *userLogin;
// userLogin = [[context activeUser] login];
@ -96,10 +136,15 @@
obj = [super lookupName:_key inContext:_ctx acquire:NO];
if (!obj)
{
if ([self isValidMailAccountName: _key])
obj = [SOGoMailAccount objectWithName: _key inContainer: self];
[self _initAccountKeys];
accountName = [accountKeys objectForKey: _key];
if ([accountName length])
{
obj = [SOGoMailAccount objectWithName: _key inContainer: self];
[obj setAccountName: accountName];
}
else
obj = [NSException exceptionWithHTTPStatus: 404 /* Not Found */];
obj = [NSException exceptionWithHTTPStatus: 404 /* Not Found */];
}
return obj;

View File

@ -78,6 +78,8 @@
- (BOOL) isBodyPartKey: (NSString *) key;
- (int) IMAP4IDFromAppendResult: (NSDictionary *) result;
@end
#endif /* __Mailer_SOGoMailBaseObject_H__ */

View File

@ -21,6 +21,7 @@
#import <Foundation/NSCharacterSet.h>
#import <Foundation/NSString.h>
#import <Foundation/NSDictionary.h>
#import <NGObjWeb/SoObject+SoDAV.h>
#import <NGExtensions/NSNull+misc.h>
@ -230,4 +231,17 @@ static BOOL debugOn = YES;
return nil;
}
- (int) IMAP4IDFromAppendResult: (NSDictionary *) result
{
NSDictionary *results;
NSString *flag, *newIdString;
results = [[result objectForKey: @"RawResponse"]
objectForKey: @"ResponseResult"];
flag = [results objectForKey: @"flag"];
newIdString = [[flag componentsSeparatedByString: @" "] objectAtIndex: 2];
return [newIdString intValue];
}
@end /* SOGoMailBaseObject */

View File

@ -446,7 +446,7 @@ static BOOL debugOn = NO;
return NSClassFromString(@"SOGoVCardMailBodyPart");
break;
case 4:
if ([pe isEqualToString:@"mail"])
if ([pe isEqualToString:@"eml"])
return NSClassFromString(@"SOGoMessageMailBodyPart");
break;
default:

View File

@ -94,6 +94,10 @@ typedef enum {
- (NSString *) userSpoolFolderPath;
- (BOOL) ensureSpoolFolderPath;
- (id) appendMessage: (NSData *) message
inContext: (WOContext *) _ctx
usingId: (int *) imap4id;
@end
@interface SOGoSpecialMailFolder : SOGoMailFolder

View File

@ -259,7 +259,7 @@ static NSString *spoolFolder = nil;
max = [uids count];
for (count = 0; count < max; count++)
{
filename = [NSString stringWithFormat: @"%@.mail",
filename = [NSString stringWithFormat: @"%@.eml",
[uids objectAtIndex: count]];
[filenames addObject: filename];
}
@ -1102,6 +1102,83 @@ static NSString *spoolFolder = nil;
return [self nameInContainer];
}
// For DAV PUT
- (NSException *) _appendMessageData: (NSData *) data
usingId: (int *) imap4id;
{
NGImap4Client *client;
NSString *folderName;
NSException *error;
id result;
error = nil;
client = [imap4 client];
folderName = [imap4 imap4FolderNameForURL: [self imap4URL]];
result = [client append: data toFolder: folderName withFlags: nil];
if ([[result objectForKey: @"result"] boolValue])
*imap4id = [self IMAP4IDFromAppendResult: result];
else
error = [NSException exceptionWithHTTPStatus: 500 /* Server Error */
reason: @"Failed to store message"];
return error;
}
- (id) appendMessage: (NSData *) message
inContext: (WOContext *) _ctx
usingId: (int *) imap4id
{
NSException *error;
WOResponse *response;
NSString *location;
id msg;
error = [self _appendMessageData: message
usingId: imap4id];
if (error)
response = (WOResponse *) error;
else
{
response = [_ctx response];
[response setStatus: 201];
msg = [SOGoMailObject objectWithName:
[NSString stringWithFormat: @"%d", imap4id]
inContainer: self];
if (msg)
{
location = [NSString stringWithFormat: @"%@%d.eml",
[self davURL], *imap4id];
[response setHeader: location forKey: @"location"];
}
}
return response;
}
- (id) PUTAction: (WOContext *) _ctx
{
WORequest *rq;
NSException *error;
WOResponse *response;
int imap4id;
error = [self matchesRequestConditionInContext: _ctx];
if (error)
response = (WOResponse *) error;
else
{
rq = [_ctx request];
response = [self appendMessage: [rq content]
inContext: _ctx
usingId: &imap4id];
}
return response;
}
@end /* SOGoMailFolder */
@implementation SOGoSpecialMailFolder

View File

@ -1239,4 +1239,34 @@ static BOOL debugSoParts = NO;
return debugOn;
}
// For DAV PUT
- (id) PUTAction: (WOContext *) _ctx
{
WORequest *rq;
NSException *error;
WOResponse *response;
SOGoMailFolder *folder;
int imap4id;
error = [self matchesRequestConditionInContext: _ctx];
if (error)
response = (WOResponse *) error;
else
{
rq = [_ctx request];
folder = [self container];
if ([self doesMailExist])
response = [NSException exceptionWithHTTPStatus: 403
reason: @"Can't overwrite messages"];
else
response = [folder appendMessage: [rq content]
inContext: _ctx
usingId: &imap4id];
}
return response;
}
@end /* SOGoMailObject */

View File

@ -44,6 +44,32 @@ class DAVMailTest(unittest.TestCase):
#self.client.execute(delete)
message1 = """Return-Path: <cyril@cyril.dev>
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
X-Virus-Scanned: Debian amavisd-new at inverse.ca
Message-ID: <4AC1F296.5060801@cyril.dev>
Date: Tue, 29 Sep 2009 07:42:14 -0400
From: Cyril <cyril@cyril.dev>
Organization: Inverse inc.
User-Agent: Thunderbird 2.0.0.22 (Macintosh/20090605)
MIME-Version: 1.0
To: jacques@cyril.dev
CC: support@inverse.ca
Subject: Hallo
Content-Type: text/plain; charset=UTF-8; format=flowed
Content-Transfer-Encoding: 7bit
Reply-To: support@inverse.ca,Cyril <cyril@cyril.dev>
Hello Jacques,
Can you read me?
--
Cyril <cyril@cyril.dev>
"""
class DAVMailCollectionTest(DAVMailTest):
resource = '/SOGo/dav/%s/Mail/' % username
user_email = None
@ -56,6 +82,8 @@ class DAVMailCollectionTest(DAVMailTest):
self.resource = '/SOGo/dav/%s/Mail/%s/' \
% (username, self.user_email)
self.resource = self.resource.replace ("@", "_A_")
self.resource = self.resource.replace (".", "_D_")
DAVMailTest.setUp(self)
@ -69,6 +97,7 @@ class DAVMailCollectionTest(DAVMailTest):
self._deleteCollection ("test-dav-mail-%20-ghi")
self._makeCollection ("test-dav-mail-%25-jkl", 500)
# Test MOVE
# self._makeCollection ("test-dav-mail-movable")
# url = "%sfolder%s" % (self.resource, "test-dav-mail-movable")
# move = webdavlib.WebDAVMOVE (url)
@ -79,6 +108,38 @@ class DAVMailCollectionTest(DAVMailTest):
# "failure creating collection"
# "(code = %d)" % move.response["status"])
#Test PUT
self._makeCollection ("test-dav-mail")
url = "%s%s" % (self.resource, "foldertest-dav-mail/")
put = webdavlib.WebDAVPUT (url, message1)
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)
self.assertEquals(get.response["status"], 200,
"failure getting item"
"(code = %d)" % get.response["status"])
url = "%s%s" % (self.resource, "foldertest-dav-mail/blabla.eml")
put = webdavlib.WebDAVPUT (url, message1)
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)
self.assertEquals(get.response["status"], 200,
"failure getting item"
"(code = %d)" % get.response["status"])
self._deleteCollection ("test-dav-mail")
def _makeCollection (self, name, status = 201):
url = "%s%s" % (self.resource, name)
mkcol = webdavlib.WebDAVMKCOL(url)

View File

@ -155,6 +155,9 @@ class WebDAVDELETE(WebDAVQuery):
class WebDAVREPORT(WebDAVQuery):
method = "REPORT"
class WebDAVGET(WebDAVQuery):
method = "GET"
class WebDAVPROPFIND(WebDAVQuery):
method = "PROPFIND"
@ -181,6 +184,20 @@ class WebDAVMOVE(WebDAVQuery):
headers["Host"] = self.host
return headers
class WebDAVPUT(WebDAVQuery):
method = "PUT"
def __init__(self, url, content):
WebDAVQuery.__init__(self, url)
self.content_type = "text/plain; charset=utf-8"
self.content = content
def prepare_headers(self):
return WebDAVQuery.prepare_headers(self)
def render(self):
return self.content
class CalDAVPOST(WebDAVQuery):
method = "POST"

View File

@ -43,6 +43,7 @@
#import <SoObjects/Mailer/SOGoMailAccounts.h>
#import <SoObjects/SOGo/NSDictionary+URL.h>
#import <SoObjects/SOGo/NSArray+Utilities.h>
#import <SoObjects/SOGo/NSDictionary+Utilities.h>
#import <SoObjects/SOGo/SOGoUser.h>
#import <SoObjects/SOGo/SOGoUserFolder.h>
#import <SOGoUI/UIxComponent.h>
@ -84,15 +85,29 @@
/* accessors */
#warning this method is a duplication of SOGoMailAccounts:toManyRelationShipKeys
- (NSString *) mailAccounts
{
NSArray *accounts, *accountNames;
SOGoMailAccounts *accounts;
NSDictionary *accountKeys;
NSArray *keys, *entry;
NSMutableArray *values;
NSString *key;
int i, max;
accounts = [[context activeUser] mailAccounts];
accountNames = [accounts objectsForKey: @"name" notFoundMarker: nil];
accounts = [self clientObject];
accountKeys = [accounts accountKeys];
keys = [accountKeys allKeys];
values = [NSMutableArray array];
return [accountNames jsonRepresentation];
max = [keys count];
for (i = 0; i < max; i++)
{
key = [keys objectAtIndex: i];
entry = [NSArray arrayWithObjects: key, [accountKeys objectForKey: key], nil];
[values addObject: entry];
}
return [values jsonRepresentation];
}
- (NSString *) defaultColumnsOrder

View File

@ -1656,7 +1656,7 @@ function initMailboxTree() {
mailboxTree.pendingRequests = mailAccounts.length;
activeAjaxRequests += mailAccounts.length;
for (var i = 0; i < mailAccounts.length; i++) {
var url = ApplicationBaseURL + encodeURI(mailAccounts[i]) + "/mailboxes";
var url = ApplicationBaseURL + encodeURI(mailAccounts[i][0]) + "/mailboxes";
triggerAjaxRequest(url, onLoadMailboxesCallback, mailAccounts[i]);
}
}
@ -1875,9 +1875,9 @@ function onLoadMailboxesCallback(http) {
// }
}
function buildMailboxes(accountName, encoded) {
var account = new Mailbox("account", accountName);
var accountIndex = mailAccounts.indexOf(accountName);
function buildMailboxes(accountKeys, encoded) {
var account = new Mailbox("account", accountKeys);
var accountIndex = mailAccounts.indexOf(accountKeys);
var data = encoded.evalJSON(true);
var mailboxes = data.mailboxes;
var unseen = (data.status? data.status.unseen : 0);
@ -2290,7 +2290,14 @@ document.observe("dom:loaded", initMailer);
function Mailbox(type, name, unseen) {
this.type = type;
this.name = name;
if (typeof (name) == "object" && name.length == 2) {
this.name = name[0];
this.displayName = name[1];
}
else {
this.name = name;
this.displayName = name;
}
this.unseen = unseen;
this.parentFolder = null;
this.children = new Array();
@ -2322,7 +2329,8 @@ Mailbox.prototype = {
var i = 0;
while (!mailbox && i < this.children.length)
if (this.children[i].name == name)
if (this.children[i].name == name
|| this.children[i].displayName == name)
mailbox = this.children[i];
else
i++;

View File

@ -31,7 +31,7 @@ var MailerUIdTreeExtension = {
},
_addFolder: function (parent, folder) {
var thisCounter = this.elementCounter;
this._addFolderNode(parent, folder.name, folder.fullName(), folder.type, folder.unseen);
this._addFolderNode(parent, folder.displayName, folder.fullName(), folder.type, folder.unseen);
for (var i = 0; i < folder.children.length; i++)
this._addFolder(thisCounter, folder.children[i]);
},