2 Copyright (C) 2004-2005 SKYRIX Software AG
4 This file is part of OpenGroupware.org.
6 OGo is free software; you can redistribute it and/or modify it under
7 the terms of the GNU Lesser General Public License as published by the
8 Free Software Foundation; either version 2, or (at your option) any
11 OGo is distributed in the hope that it will be useful, but WITHOUT ANY
12 WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
14 License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with OGo; see the file COPYING. If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
22 #include "SOGoMailFolder.h"
23 #include "SOGoMailObject.h"
24 #include "SOGoMailAccount.h"
25 #include "SOGoMailManager.h"
26 #include <NGImap4/NGImap4MailboxInfo.h>
27 #include "SOGoMailFolderDataSource.h"
30 @implementation SOGoMailFolder
32 static BOOL useAltNamespace = NO;
35 return [super version] + 0 /* v1 */;
39 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
41 NSAssert2([super version] == 1,
42 @"invalid superclass (%@) version %i !",
43 NSStringFromClass([self superclass]), [super version]);
45 useAltNamespace = [ud boolForKey:@"SOGoSpecialFoldersInRoot"];
49 [self->selectInfo release];
50 [self->filenames release];
51 [self->folderType release];
57 - (NSString *)relativeImap4Name {
58 return [self nameInContainer];
61 /* listing the available folders */
63 - (NSArray *)toManyRelationshipKeys {
64 return [[self imap4Connection] subfoldersForURL:[self imap4URL]];
67 - (NSArray *)toOneRelationshipKeys {
71 if (self->filenames != nil)
72 return [self->filenames isNotNull] ? self->filenames : nil;
74 uids = [self fetchUIDsMatchingQualifier:nil sortOrdering:@"DATE"];
75 if ([uids isKindOfClass:[NSException class]])
78 if ((count = [uids count]) == 0) {
79 self->filenames = [[NSArray alloc] init];
85 keys = [[NSMutableArray alloc] initWithCapacity:count];
86 for (i = 0; i < count; i++) {
89 k = [[uids objectAtIndex:i] stringValue];
90 k = [k stringByAppendingString:@".mail"];
93 self->filenames = [keys copy];
96 return self->filenames;
99 - (EODataSource *)contentDataSourceInContext:(id)_ctx {
100 SOGoMailFolderDataSource *ds;
102 ds = [[SOGoMailFolderDataSource alloc] initWithImap4URL:[self imap4URL]
103 imap4Password:[self imap4Password]];
104 return [ds autorelease];
107 /* mailbox raw ops */
109 - (NSException *)primaryFetchMailboxInfo {
110 /* returns nil if fetch was successful */
113 if (self->selectInfo != nil)
114 return nil; /* select info exists, => no error */
116 info = [[self imap4Connection] infoForMailboxAtURL:[self imap4URL]];
117 if ([info isKindOfClass:[NSException class]])
120 self->selectInfo = [info retain];
121 return nil; /* no error */
126 - (void)_loadACLPermissionFlags {
130 if (self->somfFlags.didCheckMyRights)
133 rights = [[self imap4Connection] myRightsForMailboxAtURL:[self imap4URL]];
134 if ([rights isKindOfClass:[NSException class]]) {
135 [self logWithFormat:@"ERROR: could not retrieve ACL: %@", rights];
139 // [self logWithFormat:@"GOT PERM: %@", rights];
141 self->somfFlags.didCheckMyRights = 1;
144 self->somfFlags.isDeleteAndExpungeAllowed = 0;
145 self->somfFlags.isReadAllowed = 0;
146 self->somfFlags.isWriteAllowed = 0;
147 self->somfFlags.isInsertAllowed = 0;
148 self->somfFlags.isPostAllowed = 0;
149 self->somfFlags.isCreateAllowed = 0;
150 self->somfFlags.hasAdminAccess = 0;
152 for (i = 0, len = [rights length]; i < len; i++) {
153 switch ([rights characterAtIndex:i]) {
154 case 'd': self->somfFlags.isDeleteAndExpungeAllowed = 1; break;
155 case 'r': self->somfFlags.isReadAllowed = 1; break;
156 case 'w': self->somfFlags.isWriteAllowed = 1; break;
157 case 'i': self->somfFlags.isInsertAllowed = 1; break;
158 case 'p': self->somfFlags.isPostAllowed = 1; break;
159 case 'c': self->somfFlags.isCreateAllowed = 1; break;
160 case 'a': self->somfFlags.hasAdminAccess = 1; break;
165 - (BOOL)isDeleteAndExpungeAllowed {
166 [self _loadACLPermissionFlags];
167 return self->somfFlags.isDeleteAndExpungeAllowed ? YES : NO;
169 - (BOOL)isReadAllowed {
170 [self _loadACLPermissionFlags];
171 return self->somfFlags.isReadAllowed ? YES : NO;
173 - (BOOL)isWriteAllowed {
174 [self _loadACLPermissionFlags];
175 return self->somfFlags.isWriteAllowed ? YES : NO;
177 - (BOOL)isInsertAllowed {
178 [self _loadACLPermissionFlags];
179 return self->somfFlags.isInsertAllowed ? YES : NO;
181 - (BOOL)isPostAllowed {
182 [self _loadACLPermissionFlags];
183 return self->somfFlags.isPostAllowed ? YES : NO;
186 - (BOOL)isCreateAllowedInACL {
187 /* we call this directly from UIxMailAccountView */
188 [self _loadACLPermissionFlags];
189 return self->somfFlags.isCreateAllowed ? YES : NO;
191 - (BOOL)isCreateAllowed {
192 if (useAltNamespace) {
193 /* with altnamespace, Cyrus doesn't allow mailboxes under INBOX */
194 if ([[self outlookFolderClass] isEqualToString:@"IPF.Inbox"])
197 return [self isCreateAllowedInACL];
200 - (BOOL)hasAdminAccess {
201 [self _loadACLPermissionFlags];
202 return self->somfFlags.hasAdminAccess ? YES : NO;
207 - (NSArray *)fetchUIDsMatchingQualifier:(id)_q sortOrdering:(id)_so {
208 /* seems to return an NSArray of NSNumber's */
209 return [[self imap4Connection] fetchUIDsInURL:[self imap4URL]
210 qualifier:_q sortOrdering:_so];
213 - (NSArray *)fetchUIDs:(NSArray *)_uids parts:(NSArray *)_parts {
214 return [[self imap4Connection] fetchUIDs:_uids inURL:[self imap4URL]
218 - (NSException *)postData:(NSData *)_data flags:(id)_flags {
219 return [[self imap4Connection] postData:_data flags:_flags
220 toFolderURL:[self imap4URL]];
223 - (NSException *)expunge {
224 return [[self imap4Connection] expungeAtURL:[self imap4URL]];
229 - (NSException *)addFlagsToAllMessages:(id)_f {
230 return [[self imap4Connection] addFlags:_f
231 toAllMessagesInURL:[self imap4URL]];
236 - (BOOL)isMessageKey:(NSString *)_key inContext:(id)_ctx {
238 Every key starting with a digit is consider an IMAP4 message key. This is
239 not entirely correct since folders could also start with a number.
241 If we want to support folders beginning with numbers, we would need to
242 scan the folder list for the _key, which would make everything quite a bit
244 TODO: support this mode using a default.
246 if ([_key length] == 0)
249 if (isdigit([_key characterAtIndex:0]))
255 - (id)lookupImap4Folder:(NSString *)_key inContext:(id)_ctx {
256 // TODO: we might want to check for existence prior controller creation
259 /* check whether URL exists */
261 sf = [self imap4URL];
262 sf = [NSURL URLWithString:[[sf path] stringByAppendingPathComponent:_key]
265 if (![[self imap4Connection] doesMailboxExistAtURL:sf]) {
267 We may not return 404, confuses path traversal - but we still do in the
268 calling method. Probably the traversal process should be fixed to
269 support 404 exceptions (as stop traversal _and_ acquisition).
276 return [[[SOGoMailFolder alloc] initWithName:_key
277 inContainer:self] autorelease];
280 - (id)lookupImap4Message:(NSString *)_key inContext:(id)_ctx {
281 // TODO: we might want to check for existence prior controller creation
282 return [[[SOGoMailObject alloc] initWithName:_key
283 inContainer:self] autorelease];
286 - (id)lookupName:(NSString *)_key inContext:(id)_ctx acquire:(BOOL)_acquire {
289 if ([self isMessageKey:_key inContext:_ctx]) {
291 We assume here that _key is a number and methods are not and this is
292 moved above the super lookup since the super checks the
293 -toOneRelationshipKeys which in turn loads the message ids.
295 return [self lookupImap4Message:_key inContext:_ctx];
298 obj = [self lookupImap4Folder:_key inContext:_ctx];
302 /* check attributes directly bound to the app */
303 if ((obj = [super lookupName:_key inContext:_ctx acquire:NO]))
306 /* return 404 to stop acquisition */
308 ? [NSException exceptionWithHTTPStatus:404 /* Not Found */]
309 : nil; /* hack to work with WebDAV move */
314 - (BOOL)davIsCollection {
318 - (NSException *)davCreateCollection:(NSString *)_name inContext:(id)_ctx {
319 return [[self imap4Connection] createMailbox:_name atURL:[self imap4URL]];
322 - (NSException *)delete {
323 /* Note: overrides SOGoObject -delete */o
324 return [[self imap4Connection] deleteMailboxAtURL:[self imap4URL]];
327 - (NSException *)davMoveToTargetObject:(id)_target newName:(NSString *)_name
332 if ([_name length] == 0) { /* target already exists! */
333 // TODO: check the overwrite request field (should be done by dispatcher)
334 return [NSException exceptionWithHTTPStatus:412 /* Precondition Failed */
335 reason:@"target already exists"];
337 if (![_target respondsToSelector:@selector(imap4URL)]) {
338 return [NSException exceptionWithHTTPStatus:502 /* Bad Gateway */
339 reason:@"target is not an IMAP4 folder"];
342 /* build IMAP4 URL for target */
344 destImapURL = [_target imap4URL];
345 destImapURL = [NSURL URLWithString:[[destImapURL path]
346 stringByAppendingPathComponent:_name]
347 relativeToURL:destImapURL];
349 [self logWithFormat:@"TODO: should move collection as '%@' to: %@",
350 [[self imap4URL] absoluteString],
351 [destImapURL absoluteString]];
353 return [[self imap4Connection] moveMailboxAtURL:[self imap4URL]
356 - (NSException *)davCopyToTargetObject:(id)_target newName:(NSString *)_name
359 [self logWithFormat:@"TODO: should copy collection as '%@' to: %@",
361 return [NSException exceptionWithHTTPStatus:501 /* Not Implemented */
362 reason:@"not implemented"];
367 - (NSString *)outlookFolderClass {
368 // TODO: detect Trash/Sent/Drafts folders
369 SOGoMailAccount *account;
372 if (self->folderType != nil)
373 return self->folderType;
375 account = [self mailAccountFolder];
376 n = [self nameInContainer];
378 if ([n isEqualToString:[account trashFolderNameInContext:nil]])
379 self->folderType = @"IPF.Trash";
380 else if ([n isEqualToString:[account inboxFolderNameInContext:nil]])
381 self->folderType = @"IPF.Inbox";
382 else if ([n isEqualToString:[account sentFolderNameInContext:nil]])
383 self->folderType = @"IPF.Sent";
385 self->folderType = @"IPF.Folder";
387 return self->folderType;
390 @end /* SOGoMailFolder */