]> err.no Git - scalable-opengroupware.org/blob - SOGo/SoObjects/Mailer/SOGoMailObject.m
4194cda3d8a5928a618194e90f24dc0608be8972
[scalable-opengroupware.org] / SOGo / SoObjects / Mailer / SOGoMailObject.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 "SOGoMailObject.h"
23 #include "SOGoMailManager.h"
24 #include "SOGoMailBodyPart.h"
25 #include <NGImap4/NGImap4Envelope.h>
26 #include <NGImap4/NGImap4EnvelopeAddress.h>
27 #include "common.h"
28
29 @implementation SOGoMailObject
30
31 static NSArray *coreInfoKeys = nil;
32 static BOOL heavyDebug = NO;
33
34 + (void)initialize {
35   /* Note: see SOGoMailManager.m for allowed IMAP4 keys */
36   /* Note: "BODY" actually returns the structure! */
37   coreInfoKeys = [[NSArray alloc] initWithObjects:
38                                     @"FLAGS", @"ENVELOPE", @"BODY",
39                                     @"RFC822.SIZE",
40                                     // not yet supported: @"INTERNALDATE",
41                                   nil];
42 }
43
44 - (void)dealloc {
45   [self->coreInfos release];
46   [super dealloc];
47 }
48
49 /* IMAP4 */
50
51 - (NSString *)relativeImap4Name {
52   return [[self nameInContainer] stringByDeletingPathExtension];
53 }
54
55 /* hierarchy */
56
57 - (SOGoMailObject *)mailObject {
58   return self;
59 }
60
61 /* part hierarchy */
62
63 - (NSString *)keyExtensionForPart:(id)_partInfo {
64   NSString *mt, *st;
65   
66   if (_partInfo == nil)
67     return nil;
68   
69   mt = [_partInfo valueForKey:@"type"];
70   st = [[_partInfo valueForKey:@"subtype"] lowercaseString];
71   if ([mt isEqualToString:@"text"]) {
72     if ([st isEqualToString:@"plain"])
73       return @".txt";
74     if ([st isEqualToString:@"html"])
75       return @".html";
76   }
77   else if ([mt isEqualToString:@"image"])
78     return [@"." stringByAppendingString:st];
79   
80   return nil;
81 }
82
83 - (NSArray *)relationshipKeysWithParts:(BOOL)_withParts {
84   /* should return non-multipart children */
85   NSMutableArray *ma;
86   NSArray *parts;
87   unsigned i, count;
88   
89   parts = [[self bodyStructure] valueForKey:@"parts"];
90   if (![parts isNotNull]) 
91     return nil;
92   if ((count = [parts count]) == 0)
93     return nil;
94   
95   for (i = 0, ma = nil; i < count; i++) {
96     NSString *key, *ext;
97     id   part;
98     BOOL hasParts;
99     
100     part     = [parts objectAtIndex:i];
101     hasParts = [part valueForKey:@"parts"] != nil ? YES:NO;
102     if ((hasParts && !_withParts) || (_withParts && !hasParts))
103       continue;
104
105     if (ma == nil)
106       ma = [NSMutableArray arrayWithCapacity:count - i];
107     
108     ext = [self keyExtensionForPart:part];
109     key = [[NSString alloc] initWithFormat:@"%d%@", i + 1, ext?ext:@""];
110     [ma addObject:key];
111     [key release];
112   }
113   return ma;
114 }
115
116 - (NSArray *)toOneRelationshipKeys {
117   return [self relationshipKeysWithParts:NO];
118 }
119 - (NSArray *)toManyRelationshipKeys {
120   return [self relationshipKeysWithParts:YES];
121 }
122
123 /* message */
124
125 - (id)fetchParts:(NSArray *)_parts {
126   return [[self mailManager] fetchURL:[self imap4URL] parts:_parts
127                              password:[self imap4Password]];
128 }
129
130 /* core infos */
131
132 - (id)fetchCoreInfos {
133   id msgs;
134   
135   if (self->coreInfos != nil)
136     return [self->coreInfos isNotNull] ? self->coreInfos : nil;
137
138   msgs = [[self clientObject] fetchParts:coreInfoKeys]; // returns dict
139   if (heavyDebug) [self logWithFormat:@"M: %@", msgs];
140   msgs = [msgs valueForKey:@"fetch"];
141   if ([msgs count] == 0)
142     return nil;
143   
144   self->coreInfos = [[msgs objectAtIndex:0] retain];
145   return self->coreInfos;
146 }
147
148 - (id)bodyStructure {
149   return [[self fetchCoreInfos] valueForKey:@"body"];
150 }
151
152 - (NGImap4Envelope *)envelope {
153   return [[self fetchCoreInfos] valueForKey:@"envelope"];
154 }
155 - (NSString *)subject {
156   return [[self envelope] subject];
157 }
158 - (NSCalendarDate *)date {
159   return [[self envelope] date];
160 }
161 - (NGImap4EnvelopeAddress *)fromEnvelopeAddress {
162   return [[self envelope] from];
163 }
164 - (NSArray *)toEnvelopeAddresses {
165   return [[self envelope] to];
166 }
167 - (NSArray *)ccEnvelopeAddresses {
168   return [[self envelope] cc];
169 }
170
171 - (id)lookupInfoForBodyPart:(NSArray *)_path {
172   NSEnumerator *pe;
173   NSString *p;
174   id info;
175   
176   if ((info = [self bodyStructure]) == nil) {
177     [self errorWithFormat:@"got no body part structure!"];
178     return nil;
179   }
180   
181   /* for each path component, eg 1,1,3 */
182   pe = [_path objectEnumerator];
183   while ((p = [pe nextObject]) != nil && [info isNotNull]) {
184     unsigned idx;
185     NSArray  *parts;
186     
187     [self logWithFormat:@"check PATH: %@", p];
188     idx = [p intValue] - 1;
189     
190     parts = [info valueForKey:@"parts"];
191     if (idx >= [parts count]) {
192       [self errorWithFormat:@"body part index out of bounds(%d vs %d): %@", 
193               (idx + 1), [parts count], info];
194       return nil;
195     }
196     info = [parts objectAtIndex:idx];
197   }
198   return [info isNotNull] ? info : nil;
199 }
200
201 /* content */
202
203 - (NSData *)content {
204   NSData *content;
205   id     result, fullResult;
206   
207   fullResult = [self fetchParts:[NSArray arrayWithObject:@"RFC822"]];
208   if (fullResult == nil)
209     return nil;
210   
211   if ([fullResult isKindOfClass:[NSException class]])
212     return fullResult;
213   
214   /* extract fetch result */
215   
216   result = [fullResult valueForKey:@"fetch"];
217   if (![result isKindOfClass:[NSArray class]]) {
218     [self logWithFormat:
219             @"ERROR: unexpected IMAP4 result (missing 'fetch'): %@", 
220             fullResult];
221     return [NSException exceptionWithHTTPStatus:500 /* server error */
222                         reason:@"unexpected IMAP4 result"];
223   }
224   if ([result count] == 0)
225     return nil;
226   
227   result = [result objectAtIndex:0];
228   
229   /* extract message */
230   
231   if ((content = [result valueForKey:@"message"]) == nil) {
232     [self logWithFormat:
233             @"ERROR: unexpected IMAP4 result (missing 'message'): %@", 
234             result];
235     return [NSException exceptionWithHTTPStatus:500 /* server error */
236                         reason:@"unexpected IMAP4 result"];
237   }
238   
239   return [[content copy] autorelease];
240 }
241
242 - (NSString *)contentAsString {
243   NSString *s;
244   NSData *content;
245   
246   if ((content = [self content]) == nil)
247     return nil;
248   if ([content isKindOfClass:[NSException class]])
249     return (id)content;
250   
251   s = [[NSString alloc] initWithData:content 
252                         encoding:NSISOLatin1StringEncoding];
253   if (s == nil) {
254     [self logWithFormat:
255             @"ERROR: could not convert data of length %d to string", 
256             [content length]];
257     return nil;
258   }
259   return [s autorelease];
260 }
261
262 /* flags */
263
264 - (NSException *)addFlags:(id)_flags {
265   return [[self mailManager] addFlags:_flags toURL:[self imap4URL] 
266                              password:[self imap4Password]];
267 }
268 - (NSException *)removeFlags:(id)_flags {
269   return [[self mailManager] removeFlags:_flags toURL:[self imap4URL] 
270                              password:[self imap4Password]];
271 }
272
273 /* name lookup */
274
275 - (id)lookupImap4BodyPartKey:(NSString *)_key inContext:(id)_ctx {
276   // TODO: we might want to check for existence prior controller creation
277   return [[[SOGoMailBodyPart alloc] initWithName:_key 
278                                     inContainer:self] autorelease];
279 }
280
281 - (id)lookupName:(NSString *)_key inContext:(id)_ctx acquire:(BOOL)_flag {
282   id obj;
283   
284   /* first check attributes directly bound to the application */
285   if ((obj = [super lookupName:_key inContext:_ctx acquire:NO]) != nil)
286     return obj;
287   
288   /* lookup body part */
289   
290   if ([self isBodyPartKey:_key inContext:_ctx]) {
291     if ((obj = [self lookupImap4BodyPartKey:_key inContext:_ctx]) != nil)
292       return obj;
293   }
294   
295   /* return 404 to stop acquisition */
296   return [NSException exceptionWithHTTPStatus:404 /* Not Found */];
297 }
298
299 /* WebDAV */
300
301 - (BOOL)davIsCollection {
302   /* while a mail has child objects, it should appear as a file in WebDAV */
303   return NO;
304 }
305
306 - (id)davContentLength {
307   return [[self fetchCoreInfos] valueForKey:@"size"];
308 }
309
310 - (NSDate *)davCreationDate {
311   // TODO: use INTERNALDATE once NGImap4 supports that
312   return nil;
313 }
314 - (NSDate *)davLastModified {
315   return [self davCreationDate];
316 }
317
318 /* actions */
319
320 - (id)GETAction:(WOContext *)_ctx {
321   WOResponse *r;
322   NSData     *content;
323   
324   content = [self content];
325   if ([content isKindOfClass:[NSException class]])
326     return content;
327   if (content == nil) {
328     return [NSException exceptionWithHTTPStatus:404 /* Not Found */
329                         reason:@"did not find IMAP4 message"];
330   }
331   
332   r = [_ctx response];
333   [r setHeader:@"message/rfc822" forKey:@"content-type"];
334   [r setContent:content];
335   return r;
336 }
337
338 @end /* SOGoMailObject */