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