]> err.no Git - scalable-opengroupware.org/blob - SoObjects/Mailer/SOGoMailAccount.m
git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1188 d1b88da0-ebda-0310...
[scalable-opengroupware.org] / SoObjects / Mailer / SOGoMailAccount.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 #import <Foundation/NSArray.h>
23 #import <Foundation/NSURL.h>
24 #import <Foundation/NSString.h>
25 #import <Foundation/NSUserDefaults.h>
26
27 #import <NGObjWeb/NSException+HTTP.h>
28 #import <NGObjWeb/SoHTTPAuthenticator.h>
29 #import <NGObjWeb/WORequest.h>
30 #import <NGObjWeb/WOContext+SoObjects.h>
31 #import <NGExtensions/NSObject+Logs.h>
32 #import <NGExtensions/NSNull+misc.h>
33 #import <NGImap4/NGImap4Connection.h>
34
35 #import <SoObjects/SOGo/SOGoUser.h>
36
37 #import "SOGoMailFolder.h"
38 #import "SOGoMailManager.h"
39 #import "SOGoDraftsFolder.h"
40
41 #import "SOGoMailAccount.h"
42
43 @implementation SOGoMailAccount
44
45 static NSArray *rootFolderNames = nil;
46 static NSString *inboxFolderName = @"INBOX";
47 static NSString *draftsFolderName = @"Drafts";
48 static NSString *sieveFolderName = @"Filters";
49 static NSString *sentFolderName = nil;
50 static NSString *trashFolderName = nil;
51 static NSString *sharedFolderName = @""; // TODO: add English default
52 static NSString *otherUsersFolderName = @""; // TODO: add English default
53 static BOOL useAltNamespace = NO;
54
55 + (void) initialize
56 {
57   NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
58   NSString *cfgDraftsFolderName;
59
60   useAltNamespace = [ud boolForKey:@"SOGoSpecialFoldersInRoot"];
61   
62   sharedFolderName = [ud stringForKey:@"SOGoSharedFolderName"];
63   otherUsersFolderName = [ud stringForKey:@"SOGoOtherUsersFolderName"];
64   cfgDraftsFolderName = [ud stringForKey:@"SOGoDraftsFolderName"];
65   if (!sentFolderName)
66     {
67       sentFolderName = [ud stringForKey: @"SOGoSentFolderName"];
68       if (!sentFolderName)
69         sentFolderName = @"Sent";
70       [sentFolderName retain];
71     }
72   if (!trashFolderName)
73     {
74       trashFolderName = [ud stringForKey: @"SOGoTrashFolderName"];
75       if (!trashFolderName)
76         trashFolderName = @"Trash";
77       [trashFolderName retain];
78     }
79   if ([cfgDraftsFolderName length] > 0)
80     {
81       ASSIGN (draftsFolderName, cfgDraftsFolderName);
82       NSLog(@"Note: using drafts folder named:      '%@'", draftsFolderName);
83     }
84
85   NSLog(@"Note: using shared-folders name:      '%@'", sharedFolderName);
86   NSLog(@"Note: using other-users-folders name: '%@'", otherUsersFolderName);
87   if ([ud boolForKey: @"SOGoEnableSieveFolder"])
88     rootFolderNames = [[NSArray alloc] initWithObjects:
89                                         draftsFolderName, 
90                                         sieveFolderName, 
91                                       nil];
92   else
93     rootFolderNames = [[NSArray alloc] initWithObjects:
94                                         draftsFolderName, 
95                                       nil];
96 }
97
98 - (id) init
99 {
100   if ((self = [super init]))
101     {
102       inboxFolder = nil;
103       draftsFolder = nil;
104       sentFolder = nil;
105       trashFolder = nil;
106     }
107
108   return self;
109 }
110
111 - (void) dealloc
112 {
113   [inboxFolder release];
114   [draftsFolder release];
115   [sentFolder release];
116   [trashFolder release];
117   [super dealloc];  
118 }
119
120 /* shared accounts */
121
122 - (BOOL) isSharedAccount
123 {
124   NSString *s;
125   NSRange  r;
126   
127   s = [self nameInContainer];
128   r = [s rangeOfString:@"@"];
129   if (r.length == 0) /* regular HTTP logins are never a shared mailbox */
130     return NO;
131   
132   s = [s substringToIndex:r.location];
133   return [s rangeOfString:@".-."].length > 0 ? YES : NO;
134 }
135
136 - (NSString *) sharedAccountName
137 {
138   return nil;
139 }
140
141 /* listing the available folders */
142
143 - (NSArray *) additionalRootFolderNames
144 {
145   return rootFolderNames;
146 }
147
148 - (BOOL) isInDraftsFolder
149 {
150   return NO;
151 }
152
153 - (NSArray *) toManyRelationshipKeys
154 {
155   NSMutableArray *folders;
156   NSArray *imapFolders, *additionalFolders;
157
158   folders = [NSMutableArray array];
159
160   imapFolders = [[self imap4Connection] subfoldersForURL: [self imap4URL]];
161   additionalFolders = [self additionalRootFolderNames];
162   if ([imapFolders count] > 0)
163     [folders addObjectsFromArray: imapFolders];
164   if ([additionalFolders count] > 0)
165     {
166       [folders removeObjectsInArray: additionalFolders];
167       [folders addObjectsFromArray: additionalFolders];
168     }
169
170   return folders;
171 }
172
173 /* hierarchy */
174
175 - (SOGoMailAccount *) mailAccountFolder
176 {
177   return self;
178 }
179
180 - (NSArray *) allFolderPaths
181 {
182   NSMutableArray *newFolders;
183   NSArray *rawFolders, *mainFolders;
184
185   rawFolders = [[self imap4Connection]
186                  allFoldersForURL: [self imap4URL]];
187
188   mainFolders = [NSArray arrayWithObjects: inboxFolderName, draftsFolderName,
189                          sentFolder, trashFolder, nil];
190   newFolders = [NSMutableArray arrayWithArray: rawFolders];
191   [newFolders removeObjectsInArray: mainFolders];
192   [newFolders sortUsingSelector: @selector (caseInsensitiveCompare:)];
193   [newFolders replaceObjectsInRange: NSMakeRange (0, 0)
194               withObjectsFromArray: mainFolders];
195
196   return newFolders;
197 }
198
199 /* IMAP4 */
200
201 - (BOOL) useSSL
202 {
203   return NO;
204 }
205
206 - (NSString *) imap4LoginFromHTTP
207 {
208   WORequest *rq;
209   NSString  *s;
210   NSArray   *creds;
211   
212   rq = [context request];
213   
214   s = [rq headerForKey:@"x-webobjects-remote-user"];
215   if ([s length] > 0)
216     return s;
217   
218   if ((s = [rq headerForKey:@"authorization"]) == nil) {
219     /* no basic auth */
220     return nil;
221   }
222   
223   creds = [SoHTTPAuthenticator parseCredentials:s];
224   if ([creds count] < 2)
225     /* somehow invalid */
226     return nil;
227   
228   return [creds objectAtIndex:0]; /* the user */
229 }
230
231 - (NSMutableString *) imap4URLString
232 {
233   /* private, overridden by SOGoSharedMailAccount */
234   NSMutableString *urlString;
235   NSString *host;
236
237   urlString = [NSMutableString string];
238
239   if ([self useSSL])
240     [urlString appendString: @"imaps://"];
241   else
242     [urlString appendString: @"imap://"];
243
244   host = [self nameInContainer];
245   if (![host rangeOfString: @"@"].length)
246     [urlString appendFormat: @"%@@", [self imap4LoginFromHTTP]];
247   [urlString appendFormat: @"%@/", host];
248
249   return urlString;
250 }
251
252 - (NSString *) imap4Login
253 {
254   return [[self imap4URL] user];
255 }
256
257 /* name lookup */
258
259 - (id) lookupFolder: (NSString *) _key
260        ofClassNamed: (NSString *) _cn
261           inContext: (id) _cx
262 {
263   Class clazz;
264   SOGoMailFolder *folder;
265
266   if ((clazz = NSClassFromString(_cn)) == Nil)
267     {
268       [self logWithFormat:@"ERROR: did not find class '%@' for key: '%@'", 
269             _cn, _key];
270       return [NSException exceptionWithHTTPStatus:500 /* server error */
271                           reason:@"did not find mail folder class!"];
272     }
273
274   folder = [clazz objectWithName: _key inContainer: self];
275
276   return folder;
277 }
278
279 - (id) lookupImap4Folder: (NSString *) _key
280                inContext: (id) _cx
281 {
282   NSString *s;
283
284   s = [_key isEqualToString: [self trashFolderNameInContext:_cx]]
285     ? @"SOGoTrashFolder" : @"SOGoMailFolder";
286   
287   return [self lookupFolder:_key ofClassNamed:s inContext:_cx];
288 }
289
290 - (id) lookupDraftsFolder: (NSString *) _key
291                 inContext: (id) _ctx
292 {
293   return [self lookupFolder: _key ofClassNamed: @"SOGoDraftsFolder" 
294                inContext: _ctx];
295 }
296
297 - (id) lookupFiltersFolder: (NSString *) _key inContext: (id) _ctx
298 {
299   return [self lookupFolder:_key ofClassNamed:@"SOGoSieveScriptsFolder" 
300                inContext:_ctx];
301 }
302
303 - (id) lookupName: (NSString *) _key
304         inContext: (id)_ctx
305           acquire: (BOOL) _flag
306 {
307   id obj;
308
309   if ([_key hasPrefix: @"folder"])
310     {
311   // TODO: those should be product.plist bindings? (can't be class bindings
312   //       though because they are 'per-account')
313       if ([_key isEqualToString: [self draftsFolderNameInContext: _ctx]])
314         obj = [self lookupDraftsFolder: _key inContext: _ctx];
315       else if ([_key isEqualToString: [self sieveFolderNameInContext: _ctx]])
316         obj = [self lookupFiltersFolder: _key inContext: _ctx];
317       else
318         obj = [self lookupImap4Folder: _key inContext: _ctx];
319     }
320   else
321     obj = [super lookupName: _key inContext: _ctx acquire: NO];
322   
323   /* return 404 to stop acquisition */
324   if (!obj)
325     obj = [NSException exceptionWithHTTPStatus: 404 /* Not Found */];
326
327   return obj;
328 }
329
330 /* special folders */
331
332 - (NSString *) inboxFolderNameInContext: (id)_ctx
333 {
334   /* cannot be changed in Cyrus ? */
335   return [NSString stringWithFormat: @"folder%@", inboxFolderName];
336 }
337
338 - (NSString *) _userFolderNameWithPurpose: (NSString *) purpose
339 {
340   NSUserDefaults *ud;
341   NSMutableDictionary *mailSettings;
342   NSString *folderName;
343
344   folderName = nil;
345   ud = [[context activeUser] userSettings];
346   mailSettings = [ud objectForKey: @"Mail"];
347   if (mailSettings)
348     folderName
349       = [mailSettings objectForKey: [NSString stringWithFormat: @"%@Folder",
350                                               purpose]];
351
352   return folderName;
353 }
354
355 - (NSString *) draftsFolderNameInContext: (id) _ctx
356 {
357   NSString *folderName;
358
359   folderName = [self _userFolderNameWithPurpose: @"Drafts"];
360   if (!folderName)
361     folderName = draftsFolderName;
362
363   return [NSString stringWithFormat: @"folder%@", folderName];
364 }
365
366 - (NSString *) sieveFolderNameInContext: (id) _ctx
367 {
368   return [NSString stringWithFormat: @"folder%@", sieveFolderName];
369 }
370
371 - (NSString *) sentFolderNameInContext: (id)_ctx
372 {
373   NSString *folderName;
374
375   folderName = [self _userFolderNameWithPurpose: @"Sent"];
376   if (!folderName)
377     folderName = sentFolderName;
378
379   return [NSString stringWithFormat: @"folder%@", folderName];
380 }
381
382 - (NSString *) trashFolderNameInContext: (id)_ctx
383 {
384   NSString *folderName;
385
386   folderName = [self _userFolderNameWithPurpose: @"Trash"];
387   if (!folderName)
388     folderName = trashFolderName;
389
390   return [NSString stringWithFormat: @"folder%@", folderName];
391 }
392
393 - (SOGoMailFolder *) inboxFolderInContext: (id) _ctx
394 {
395   // TODO: use some profile to determine real location, use a -traverse lookup
396   if (!inboxFolder)
397     {
398       inboxFolder = [self lookupName: [self inboxFolderNameInContext: _ctx]
399                           inContext: _ctx acquire: NO];
400       [inboxFolder retain];
401     }
402
403   return inboxFolder;
404 }
405
406 - (SOGoDraftsFolder *) draftsFolderInContext: (id) _ctx
407 {
408   SOGoMailFolder *lookupFolder;
409   // TODO: use some profile to determine real location, use a -traverse lookup
410
411   if (!draftsFolder)
412     {
413       lookupFolder = (useAltNamespace
414                       ? (id) self
415                       : [self inboxFolderInContext:_ctx]);
416       if (![lookupFolder isKindOfClass: [NSException class]])
417         draftsFolder
418           = [lookupFolder lookupName: [self draftsFolderNameInContext:_ctx]
419                           inContext: _ctx acquire: NO];
420       if (![draftsFolder isNotNull])
421         draftsFolder = [NSException exceptionWithHTTPStatus: 404 /* not found */
422                                     reason: @"did not find Drafts folder!"];
423       [draftsFolder retain];
424     }
425
426   return draftsFolder;
427 }
428
429 - (SOGoMailFolder *) sentFolderInContext: (id) _ctx
430 {
431   SOGoMailFolder *lookupFolder;
432   // TODO: use some profile to determine real location, use a -traverse lookup
433
434   if (!sentFolder)
435     {
436       lookupFolder = (useAltNamespace
437                       ? (id) self
438                       : [self inboxFolderInContext:_ctx]);
439       if (![lookupFolder isKindOfClass: [NSException class]])
440         sentFolder = [lookupFolder lookupName: [self sentFolderNameInContext:_ctx]
441                                    inContext: _ctx acquire: NO];
442       if (![sentFolder isNotNull])
443         sentFolder = [NSException exceptionWithHTTPStatus: 404 /* not found */
444                                   reason: @"did not find Sent folder!"];
445       [sentFolder retain];
446     }
447
448   return sentFolder;
449 }
450
451 - (SOGoMailFolder *) trashFolderInContext: (id) _ctx
452 {
453   SOGoMailFolder *lookupFolder;
454   // TODO: use some profile to determine real location, use a -traverse lookup
455
456   if (!trashFolder)
457     {
458       lookupFolder = (useAltNamespace
459                       ? (id) self
460                       : [self inboxFolderInContext:_ctx]);
461       if (![lookupFolder isKindOfClass: [NSException class]])
462         trashFolder = [lookupFolder lookupName: [self trashFolderNameInContext: _ctx]
463                                     inContext: _ctx acquire: NO];
464       if (![trashFolder isNotNull])
465         trashFolder = [NSException exceptionWithHTTPStatus: 404 /* not found */
466                                   reason: @"did not find Trash folder!"];
467       [trashFolder retain];
468     }
469
470   return trashFolder;
471 }
472
473 /* WebDAV */
474
475 - (NSString *) davContentType
476 {
477   return @"httpd/unix-directory";
478 }
479
480 - (BOOL) davIsCollection
481 {
482   return YES;
483 }
484
485 - (NSException *) davCreateCollection: (NSString *) _name
486                             inContext: (id) _ctx
487 {
488   return [[self imap4Connection] createMailbox:_name atURL:[self imap4URL]];
489 }
490
491 - (NSString *) shortTitle
492 {
493   NSString *s, *login, *host;
494   NSRange r;
495
496   s = [self nameInContainer];
497   
498   r = [s rangeOfString:@"@"];
499   if (r.length > 0) {
500     login = [s substringToIndex:r.location];
501     host  = [s substringFromIndex:(r.location + r.length)];
502   }
503   else {
504     login = nil;
505     host  = s;
506   }
507   
508   r = [host rangeOfString:@"."];
509   if (r.length > 0)
510     host = [host substringToIndex:r.location];
511   
512   if ([login length] == 0)
513     return host;
514   
515   r = [login rangeOfString:@"."];
516   if (r.length > 0)
517     login = [login substringToIndex:r.location];
518   
519   return [NSString stringWithFormat:@"%@@%@", login, host];
520 }
521
522 - (NSString *) davDisplayName
523 {
524   return [self shortTitle];
525 }
526
527 - (NSString *) sharedFolderName
528 {
529   return sharedFolderName;
530 }
531
532 - (NSString *) otherUsersFolderName
533 {
534   return otherUsersFolderName;
535 }
536
537 @end /* SOGoMailAccount */