]> err.no Git - scalable-opengroupware.org/blob - SOGo/SoObjects/Mailer/SOGoMailBodyPart.m
do not show Drafts folder in shared mailboxes
[scalable-opengroupware.org] / SOGo / SoObjects / Mailer / SOGoMailBodyPart.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 "SOGoMailBodyPart.h"
23 #include "SOGoMailObject.h"
24 #include "SOGoMailManager.h"
25 #include "common.h"
26
27 @implementation SOGoMailBodyPart
28
29 static NSString *mailETag = nil;
30 static BOOL debugOn = NO;
31
32 + (int)version {
33   return [super version] + 0 /* v1 */;
34 }
35
36 + (void)initialize {
37   NSAssert2([super version] == 1,
38             @"invalid superclass (%@) version %i !",
39             NSStringFromClass([self superclass]), [super version]);
40   
41   mailETag = [[NSString alloc] initWithFormat:@"\"imap4url_%d_%d_%03d\"",
42                                  UIX_MAILER_MAJOR_VERSION,
43                                  UIX_MAILER_MINOR_VERSION,
44                                  UIX_MAILER_SUBMINOR_VERSION];
45   NSLog(@"Note(SOGoMailBodyPart): using constant etag for mail parts: '%@'", 
46         mailETag);
47 }
48
49 - (void)dealloc {
50   [self->partInfo   release];
51   [self->identifier release];
52   [self->pathToPart release];
53   [super dealloc];
54 }
55
56 /* hierarchy */
57
58 - (SOGoMailObject *)mailObject {
59   return [[self container] mailObject];
60 }
61
62 /* IMAP4 */
63
64 - (NSString *)bodyPartName {
65   NSString *s;
66   NSRange  r;
67
68   s = [self nameInContainer];
69   r = [s rangeOfString:@"."]; /* strip extensions */
70   if (r.length == 0)
71     return s;
72   return [s substringToIndex:r.location];
73 }
74
75 - (NSArray *)bodyPartPath {
76   NSMutableArray *p;
77   id obj;
78   
79   if (self->pathToPart != nil)
80     return [self->pathToPart isNotNull] ? self->pathToPart : nil;
81   
82   p = [[NSMutableArray alloc] initWithCapacity:8];
83   for (obj = self; [obj isKindOfClass:[SOGoMailBodyPart class]]; 
84        obj = [obj container]) {
85     [p insertObject:[obj bodyPartName] atIndex:0];
86   }
87   
88   self->pathToPart = [p copy];
89   [p release];
90   return self->pathToPart;
91 }
92
93 - (NSString *)bodyPartIdentifier {
94   if (self->identifier != nil)
95     return [self->identifier isNotNull] ? self->identifier : nil;
96   
97   self->identifier =
98     [[[self bodyPartPath] componentsJoinedByString:@"."] copy];
99   return self->identifier;
100 }
101
102 - (NSURL *)imap4URL {
103   /* reuse URL of message */
104   return [[self mailObject] imap4URL];
105 }
106
107 /* part info */
108
109 - (id)partInfo {
110   if (self->partInfo != nil)
111     return [self->partInfo isNotNull] ? self->partInfo : nil;
112
113   self->partInfo =
114     [[[self mailObject] lookupInfoForBodyPart:[self bodyPartPath]] retain];
115   return self->partInfo;
116 }
117
118 /* name lookup */
119
120 - (id)lookupImap4BodyPartKey:(NSString *)_key inContext:(id)_ctx {
121   // TODO: we might want to check for existence prior controller creation
122   Class clazz;
123   
124   clazz = [SOGoMailBodyPart bodyPartClassForKey:_key inContext:_ctx];
125   return [[[clazz alloc] initWithName:_key inContainer:self] autorelease];
126 }
127
128 - (id)lookupName:(NSString *)_key inContext:(id)_ctx acquire:(BOOL)_flag {
129   id obj;
130   
131   /* first check attributes directly bound to the application */
132   if ((obj = [super lookupName:_key inContext:_ctx acquire:NO]) != nil)
133     return obj;
134   
135   /* lookup body part */
136   
137   if ([self isBodyPartKey:_key inContext:_ctx]) {
138     if ((obj = [self lookupImap4BodyPartKey:_key inContext:_ctx]) != nil)
139       return obj;
140   }
141   
142   /* 
143      Treat other keys which have a path-extension as 'virtual' noops to allow
144      addition of path names to the attachment path, eg:
145        http://.../login@server/INBOX/1/2/3/MyDocument.pdf
146   */
147   if ([[_key pathExtension] length] > 0)
148     return self;
149   
150   /* return 404 to stop acquisition */
151   return [NSException exceptionWithHTTPStatus:404 /* Not Found */
152                       reason:@"Did not find a subpart for the given name!"];
153 }
154
155 /* fetch */
156
157 - (NSData *)fetchBLOB {
158   // HEADER, HEADER.FIELDS, HEADER.FIELDS.NOT, MIME, TEXT
159   NSString *enc;
160   NSData *data;
161   
162   data = [[self imap4Connection] fetchContentOfBodyPart:
163                                    [self bodyPartIdentifier]
164                                  atURL:[self imap4URL]];
165   if (data == nil) return nil;
166
167   /* check for content encodings */
168   
169   if ((enc = [[self partInfo] valueForKey:@"encoding"]) != nil) {
170     enc = [enc uppercaseString];
171     
172     if ([enc isEqualToString:@"BASE64"])
173       data = [data dataByDecodingBase64];
174     else if ([enc isEqualToString:@"7BIT"])
175       ; /* keep data as is */ // TODO: do we need to change encodings?
176     else
177       [self errorWithFormat:@"unsupported encoding: %@", enc];
178   }
179   
180   return data;
181 }
182
183 /* WebDAV */
184
185 - (NSString *)contentTypeForBodyPartInfo:(id)_info {
186   NSMutableString *type;
187   NSString     *mt, *st;
188   NSDictionary *parameters;
189   NSEnumerator *ke;
190   NSString     *pn;
191     
192   if (![_info isNotNull])
193     return nil;
194   
195   mt = [_info valueForKey:@"type"];    if (![mt isNotNull]) return nil;
196   st = [_info valueForKey:@"subtype"]; if (![st isNotNull]) return nil;
197   
198   type = [NSMutableString stringWithCapacity:16];
199   [type appendString:[mt lowercaseString]];
200   [type appendString:@"/"];
201   [type appendString:[st lowercaseString]];
202   
203   parameters = [_info valueForKey:@"parameterList"];
204   ke = [parameters keyEnumerator];
205   while ((pn = [ke nextObject]) != nil) {
206     [type appendString:@"; "];
207     [type appendString:pn];
208     [type appendString:@"=\""];
209     [type appendString:[[parameters objectForKey:pn] stringValue]];
210     [type appendString:@"\""];
211   }
212   return type;
213 }
214
215 - (NSString *)contentTypeForPathExtension:(NSString *)pe {
216   if ([pe length] == 0)
217     return @"application/octet-stream";
218   
219   /* TODO: add some map */
220   if ([pe isEqualToString:@"gif"]) return @"image/gif";
221   if ([pe isEqualToString:@"png"]) return @"image/png";
222   if ([pe isEqualToString:@"jpg"]) return @"image/jpeg";
223   if ([pe isEqualToString:@"txt"]) return @"text/plain";
224   
225   return @"application/octet-stream";
226 }
227
228 - (NSString *)davContentType {
229   // TODO: what about the content-type and other headers?
230   //       => we could pass them in as the extension? (eg generate 1.gif!)
231   NSString *pe;
232   
233   /* try type from body structure info */
234   
235   if ((pe = [self contentTypeForBodyPartInfo:[self partInfo]]) != nil)
236     return pe;
237   
238   /* construct type */
239   
240   pe = [[self nameInContainer] pathExtension];
241   return [self contentTypeForPathExtension:pe];
242 }
243
244 /* actions */
245
246 - (id)GETAction:(id)_ctx {
247   NSException *error;
248   WOResponse *r;
249   NSData     *data;
250   NSString   *etag;
251   
252   if ((error = [self matchesRequestConditionInContext:_ctx]) != nil) {
253     // TODO: currently we fetch the body structure to get here - check this!
254     /* check whether the mail still exists */
255     if (![[self mailObject] doesMailExist]) {
256       return [NSException exceptionWithHTTPStatus:404 /* Not Found */
257                           reason:@"mail was deleted"];
258     }
259     return error; /* return 304 or 416 */
260   }
261
262   [self debugWithFormat:@"should fetch body part: %@", 
263           [self bodyPartIdentifier]];
264   
265   if ((data = [self fetchBLOB]) == nil) {
266     return [NSException exceptionWithHTTPStatus:404 /* not found */
267                         reason:@"did not find body part"];
268   }
269   
270   [self debugWithFormat:@"  fetched %d bytes: %@", [data length],
271         [self partInfo]];
272   
273   // TODO: wrong, could be encoded
274   r = [_ctx response];
275   [r setHeader:[self davContentType] forKey:@"content-type"];
276   [r setHeader:[NSString stringWithFormat:@"%d", [data length]]
277      forKey:@"content-length"];
278   
279   if ((etag = [self davEntityTag]) != nil)
280     [r setHeader:etag forKey:@"etag"];
281
282   [r setContent:data];
283   return r;
284 }
285
286 /* factory */
287
288 + (Class)bodyPartClassForKey:(NSString *)_key inContext:(id)_ctx {
289   NSString *pe;
290   
291   pe = [_key pathExtension];
292   if (![pe isNotNull] || [pe length] == 0)
293     return self;
294   
295   /* hard coded for now */
296   
297   switch ([pe length]) {
298   case 3:
299     if ([pe isEqualToString:@"gif"] ||
300         [pe isEqualToString:@"png"] ||
301         [pe isEqualToString:@"jpg"])
302       return NSClassFromString(@"SOGoImageMailBodyPart");
303     if ([pe isEqualToString:@"ics"])
304       return NSClassFromString(@"SOGoCalendarMailBodyPart");
305     if ([pe isEqualToString:@"vcf"])
306       return NSClassFromString(@"SOGoVCardMailBodyPart");
307     break;
308   case 4:
309     if ([pe isEqualToString:@"mail"])
310       return NSClassFromString(@"SOGoMessageMailBodyPart");
311     break;
312   default:
313     return self;
314   }
315   return self;
316 }
317
318 /* etag support */
319
320 - (id)davEntityTag {
321   return mailETag;
322 }
323
324 /* debugging */
325
326 - (BOOL)isDebuggingEnabled {
327   return debugOn;
328 }
329
330 @end /* SOGoMailBodyPart */