]> err.no Git - scalable-opengroupware.org/blob - SoObjects/Mailer/SOGoMailFolder.m
d6bf24cd30d700cea863092afb89ee228220e0bb
[scalable-opengroupware.org] / SoObjects / Mailer / SOGoMailFolder.m
1 /*
2   Copyright (C) 2004-2005 SKYRIX Software AG
3
4   This file is part of OpenGroupware.org.
5
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
9   later version.
10
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.
15
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
19   02111-1307, USA.
20 */
21
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"
28 #include "common.h"
29
30 @implementation SOGoMailFolder
31
32 static BOOL useAltNamespace = NO;
33
34 + (int)version {
35   return [super version] + 0 /* v1 */;
36 }
37
38 + (void)initialize {
39   NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
40
41   NSAssert2([super version] == 1,
42             @"invalid superclass (%@) version %i !",
43             NSStringFromClass([self superclass]), [super version]);
44   
45   useAltNamespace = [ud boolForKey:@"SOGoSpecialFoldersInRoot"];
46 }
47
48 - (void)dealloc {
49   [self->selectInfo release];
50   [self->filenames  release];
51   [self->folderType release];
52   [super dealloc];
53 }
54
55 /* IMAP4 */
56
57 - (NSString *)relativeImap4Name {
58   return [self nameInContainer];
59 }
60
61 /* listing the available folders */
62
63 - (NSArray *)toManyRelationshipKeys {
64   return [[self imap4Connection] subfoldersForURL:[self imap4URL]];
65 }
66
67 - (NSArray *)toOneRelationshipKeys {
68   NSArray  *uids;
69   unsigned count;
70   
71   if (self->filenames != nil)
72     return [self->filenames isNotNull] ? self->filenames : nil;
73
74   uids = [self fetchUIDsMatchingQualifier:nil sortOrdering:@"DATE"];
75   if ([uids isKindOfClass:[NSException class]])
76     return nil;
77   
78   if ((count = [uids count]) == 0) {
79     self->filenames = [[NSArray alloc] init];
80   }
81   else {
82     NSMutableArray *keys;
83     unsigned i;
84     
85     keys = [[NSMutableArray alloc] initWithCapacity:count];
86     for (i = 0; i < count; i++) {
87       NSString *k;
88       
89       k = [[uids objectAtIndex:i] stringValue];
90       k = [k stringByAppendingString:@".mail"];
91       [keys addObject:k];
92     }
93     self->filenames = [keys copy];
94     [keys release];
95   }
96   return self->filenames;
97 }
98
99 - (EODataSource *)contentDataSourceInContext:(id)_ctx {
100   SOGoMailFolderDataSource *ds;
101   
102   ds = [[SOGoMailFolderDataSource alloc] initWithImap4URL:[self imap4URL]
103                                          imap4Password:[self imap4Password]];
104   return [ds autorelease];
105 }
106
107 /* mailbox raw ops */
108
109 - (NSException *)primaryFetchMailboxInfo {
110   /* returns nil if fetch was successful */
111   id info;
112   
113   if (self->selectInfo != nil)
114     return nil; /* select info exists, => no error */
115   
116   info = [[self imap4Connection] infoForMailboxAtURL:[self imap4URL]];
117   if ([info isKindOfClass:[NSException class]])
118     return info;
119   
120   self->selectInfo = [info retain];
121   return nil; /* no error */
122 }
123
124 /* permissions */
125
126 - (void)_loadACLPermissionFlags {
127   NSString *rights;
128   unsigned i, len;
129   
130   if (self->somfFlags.didCheckMyRights)
131     return;
132
133   rights = [[self imap4Connection] myRightsForMailboxAtURL:[self imap4URL]];
134   if ([rights isKindOfClass:[NSException class]]) {
135     [self logWithFormat:@"ERROR: could not retrieve ACL: %@", rights];
136     return;
137   }
138   
139   // [self logWithFormat:@"GOT PERM: %@", rights];
140   
141   self->somfFlags.didCheckMyRights = 1;
142   
143   /* reset flags */
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;
151   
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;
161     }
162   }
163 }
164
165 - (BOOL)isDeleteAndExpungeAllowed {
166   [self _loadACLPermissionFlags];
167   return self->somfFlags.isDeleteAndExpungeAllowed ? YES : NO;
168 }
169 - (BOOL)isReadAllowed {
170   [self _loadACLPermissionFlags];
171   return self->somfFlags.isReadAllowed ? YES : NO;
172 }
173 - (BOOL)isWriteAllowed {
174   [self _loadACLPermissionFlags];
175   return self->somfFlags.isWriteAllowed ? YES : NO;
176 }
177 - (BOOL)isInsertAllowed {
178   [self _loadACLPermissionFlags];
179   return self->somfFlags.isInsertAllowed ? YES : NO;
180 }
181 - (BOOL)isPostAllowed {
182   [self _loadACLPermissionFlags];
183   return self->somfFlags.isPostAllowed ? YES : NO;
184 }
185
186 - (BOOL)isCreateAllowedInACL {
187   /* we call this directly from UIxMailAccountView */
188   [self _loadACLPermissionFlags];
189   return self->somfFlags.isCreateAllowed ? YES : NO;
190 }
191 - (BOOL)isCreateAllowed {
192   if (useAltNamespace) {
193     /* with altnamespace, Cyrus doesn't allow mailboxes under INBOX */
194     if ([[self outlookFolderClass] isEqualToString:@"IPF.Inbox"])
195       return NO;
196   }
197   return [self isCreateAllowedInACL];
198 }
199
200 - (BOOL)hasAdminAccess {
201   [self _loadACLPermissionFlags];
202   return self->somfFlags.hasAdminAccess ? YES : NO;
203 }
204
205 /* messages */
206
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];
211 }
212
213 - (NSArray *)fetchUIDs:(NSArray *)_uids parts:(NSArray *)_parts {
214   return [[self imap4Connection] fetchUIDs:_uids inURL:[self imap4URL]
215                                  parts:_parts];
216 }
217
218 - (NSException *)postData:(NSData *)_data flags:(id)_flags {
219   return [[self imap4Connection] postData:_data flags:_flags
220                                  toFolderURL:[self imap4URL]];
221 }
222
223 - (NSException *)expunge {
224   return [[self imap4Connection] expungeAtURL:[self imap4URL]];
225 }
226
227 /* flags */
228
229 - (NSException *)addFlagsToAllMessages:(id)_f {
230   return [[self imap4Connection] addFlags:_f 
231                                  toAllMessagesInURL:[self imap4URL]];
232 }
233
234 /* name lookup */
235
236 - (BOOL)isMessageKey:(NSString *)_key inContext:(id)_ctx {
237   /*
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.
240     
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
243     slower.
244     TODO: support this mode using a default.
245   */
246   if ([_key length] == 0)
247     return NO;
248   
249   if (isdigit([_key characterAtIndex:0]))
250     return YES;
251   
252   return NO;
253 }
254
255 - (id)lookupImap4Folder:(NSString *)_key inContext:(id)_ctx {
256   // TODO: we might want to check for existence prior controller creation
257   NSURL *sf;
258
259   /* check whether URL exists */
260   
261   sf = [self imap4URL];
262   sf = [NSURL URLWithString:[[sf path] stringByAppendingPathComponent:_key]
263               relativeToURL:sf];
264   
265   if (![[self imap4Connection] doesMailboxExistAtURL:sf]) {
266     /* 
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).
270     */
271     return nil;
272   }
273   
274   /* create object */
275   
276   return [[[SOGoMailFolder alloc] initWithName:_key 
277                                   inContainer:self] autorelease];
278 }
279
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];
284 }
285
286 - (id)lookupName:(NSString *)_key inContext:(id)_ctx acquire:(BOOL)_acquire {
287   id obj;
288   
289   if ([self isMessageKey:_key inContext:_ctx]) {
290     /* 
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.
294     */
295     return [self lookupImap4Message:_key inContext:_ctx];
296   }
297   
298   /* check attributes directly bound to the app */
299   if ((obj = [super lookupName:_key inContext:_ctx acquire:NO]))
300     return obj;
301   
302   obj = [self lookupImap4Folder:_key  inContext:_ctx];
303   if (obj != nil)
304     return obj;
305   
306   /* return 404 to stop acquisition */
307   return _acquire
308     ? [NSException exceptionWithHTTPStatus:404 /* Not Found */]
309     : nil; /* hack to work with WebDAV move */
310 }
311
312 /* WebDAV */
313
314 - (BOOL)davIsCollection {
315   return YES;
316 }
317
318 - (NSException *)davCreateCollection:(NSString *)_name inContext:(id)_ctx {
319   return [[self imap4Connection] createMailbox:_name atURL:[self imap4URL]];
320 }
321
322 - (NSException *)delete {
323   /* Note: overrides SOGoObject -delete */
324   return [[self imap4Connection] deleteMailboxAtURL:[self imap4URL]];
325 }
326
327 - (NSException *)davMoveToTargetObject:(id)_target newName:(NSString *)_name
328   inContext:(id)_ctx
329 {
330   NSURL *destImapURL;
331   
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"];
336   }
337   if (![_target respondsToSelector:@selector(imap4URL)]) {
338     return [NSException exceptionWithHTTPStatus:502 /* Bad Gateway */
339                         reason:@"target is not an IMAP4 folder"];
340   }
341   
342   /* build IMAP4 URL for target */
343   
344   destImapURL = [_target imap4URL];
345   destImapURL = [NSURL URLWithString:[[destImapURL path] 
346                                        stringByAppendingPathComponent:_name]
347                        relativeToURL:destImapURL];
348   
349   [self logWithFormat:@"TODO: should move collection as '%@' to: %@",
350         [[self imap4URL] absoluteString], 
351         [destImapURL absoluteString]];
352   
353   return [[self imap4Connection] moveMailboxAtURL:[self imap4URL] 
354                                  toURL:destImapURL];
355 }
356 - (NSException *)davCopyToTargetObject:(id)_target newName:(NSString *)_name
357   inContext:(id)_ctx
358 {
359   [self logWithFormat:@"TODO: should copy collection as '%@' to: %@",
360         _name, _target];
361   return [NSException exceptionWithHTTPStatus:501 /* Not Implemented */
362                       reason:@"not implemented"];
363 }
364
365 /* folder type */
366
367 - (NSString *)outlookFolderClass {
368   // TODO: detect Trash/Sent/Drafts folders
369   SOGoMailAccount *account;
370   NSString *n;
371
372   if (self->folderType != nil)
373     return self->folderType;
374   
375   account = [self mailAccountFolder];
376   n       = [self nameInContainer];
377   
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";
384   else
385     self->folderType = @"IPF.Folder";
386   
387   return self->folderType;
388 }
389
390 @end /* SOGoMailFolder */