2 Copyright (C) 2004-2005 SKYRIX Software AG
4 This file is part of OpenGroupware.org.
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
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.
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
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>
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>
37 #import <SoObjects/SOGo/NSDictionary+Utilities.h>
39 #import "SOGoMailObject.h"
40 #import "SOGoMailManager.h"
42 #import "SOGoMailBodyPart.h"
44 @implementation SOGoMailBodyPart
46 static NSString *mailETag = nil;
47 static BOOL debugOn = NO;
51 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
53 if (![[ud objectForKey:@"SOGoMailDisableETag"] boolValue]) {
54 mailETag = [[NSString alloc] initWithFormat:@"\"imap4url_%d_%d_%03d\"",
55 UIX_MAILER_MAJOR_VERSION,
56 UIX_MAILER_MINOR_VERSION,
57 UIX_MAILER_SUBMINOR_VERSION];
58 NSLog(@"Note(SOGoMailBodyPart): using constant etag for mail parts: '%@'",
62 NSLog(@"Note(SOGoMailBodyPart): etag caching disabled!");
66 [self->partInfo release];
67 [self->identifier release];
68 [self->pathToPart release];
74 - (SOGoMailObject *)mailObject {
75 return [[self container] mailObject];
80 - (NSString *)bodyPartName {
84 s = [self nameInContainer];
85 r = [s rangeOfString:@"."]; /* strip extensions */
88 return [s substringToIndex:r.location];
91 - (NSArray *)bodyPartPath {
95 if (self->pathToPart != nil)
96 return [self->pathToPart isNotNull] ? self->pathToPart : nil;
98 p = [[NSMutableArray alloc] initWithCapacity:8];
99 for (obj = self; [obj isKindOfClass:[SOGoMailBodyPart class]];
100 obj = [obj container]) {
101 [p insertObject:[obj bodyPartName] atIndex:0];
104 self->pathToPart = [p copy];
106 return self->pathToPart;
109 - (NSString *)bodyPartIdentifier {
110 if (self->identifier != nil)
111 return [self->identifier isNotNull] ? self->identifier : nil;
114 [[[self bodyPartPath] componentsJoinedByString:@"."] copy];
115 return self->identifier;
118 - (NSURL *)imap4URL {
119 /* reuse URL of message */
120 return [[self mailObject] imap4URL];
126 if (self->partInfo != nil)
127 return [self->partInfo isNotNull] ? self->partInfo : nil;
130 [[[self mailObject] lookupInfoForBodyPart:[self bodyPartPath]] retain];
131 return self->partInfo;
136 - (id) lookupImap4BodyPartKey: (NSString *) _key
139 // TODO: we might want to check for existence prior controller creation
142 clazz = [SOGoMailBodyPart bodyPartClassForKey:_key inContext:_ctx];
144 return [clazz objectWithName: _key inContainer: self];
147 /* We overwrite the super's class method in order to make sure
148 we aren't dealing with our actual filename as the _key. That
149 could lead to problems if we weren't doing this as our filename
150 could start with a digit, leading to a wrong assumption in
153 - (BOOL)isBodyPartKey:(NSString *)_key inContext:(id)_ctx
157 s = [[[self partInfo] objectForKey: @"parameterList"] objectForKey: @"name"];
160 s = [[[[self partInfo] objectForKey: @"disposition"] objectForKey: @"parameterList"] objectForKey: @"filename"];
162 if (s && [s isEqualToString: _key]) return NO;
164 return [super isBodyPartKey: _key inContext: _ctx];
167 - (id) lookupName: (NSString *) _key
169 acquire: (BOOL) _flag
173 /* first check attributes directly bound to the application */
174 obj = [super lookupName:_key inContext:_ctx acquire:NO];
177 /* lookup body part */
178 if ([self isBodyPartKey:_key inContext:_ctx])
179 obj = [self lookupImap4BodyPartKey:_key inContext:_ctx];
180 /* should check whether such a filename exist in the attached names */
190 - (NSData *) fetchBLOB
192 // HEADER, HEADER.FIELDS, HEADER.FIELDS.NOT, MIME, TEXT
196 data = [[self imap4Connection] fetchContentOfBodyPart:
197 [self bodyPartIdentifier]
198 atURL:[self imap4URL]];
199 if (data == nil) return nil;
201 /* check for content encodings */
202 enc = [[self partInfo] valueForKey: @"encoding"];
204 /* if we haven't found one, check out the main message's encoding
205 as we could be trying to fetch the message's content as a part */
207 enc = [[[[self mailObject] fetchCoreInfos] valueForKey: @"body"]
208 valueForKey: @"encoding"];
212 enc = [enc uppercaseString];
214 if ([enc isEqualToString:@"BASE64"])
215 data = [data dataByDecodingBase64];
216 else if ([enc isEqualToString:@"7BIT"])
217 ; /* keep data as is */ // TODO: do we need to change encodings?
219 [self errorWithFormat:@"unsupported encoding: %@", enc];
227 - (NSString *)contentTypeForBodyPartInfo:(id)_info {
228 NSMutableString *type;
230 NSDictionary *parameters;
234 if (![_info isNotNull])
237 mt = [_info valueForKey:@"type"]; if (![mt isNotNull]) return nil;
238 st = [_info valueForKey:@"subtype"]; if (![st isNotNull]) return nil;
240 type = [NSMutableString stringWithCapacity:16];
241 [type appendString:[mt lowercaseString]];
242 [type appendString:@"/"];
243 [type appendString:[st lowercaseString]];
245 parameters = [_info valueForKey:@"parameterList"];
246 ke = [parameters keyEnumerator];
247 while ((pn = [ke nextObject]) != nil) {
248 [type appendString:@"; "];
249 [type appendString:pn];
250 [type appendString:@"=\""];
251 [type appendString:[[parameters objectForKey:pn] stringValue]];
252 [type appendString:@"\""];
257 - (NSString *) contentTypeForPathExtension: (NSString *) pe
259 if ([pe length] == 0)
260 return @"application/octet-stream";
262 /* TODO: add some map */
263 if ([pe isEqualToString:@"gif"]) return @"image/gif";
264 if ([pe isEqualToString:@"png"]) return @"image/png";
265 if ([pe isEqualToString:@"jpg"]) return @"image/jpeg";
266 if ([pe isEqualToString:@"txt"]) return @"text/plain";
268 return @"application/octet-stream";
271 - (NSString *) davContentType
273 // TODO: what about the content-type and other headers?
274 // => we could pass them in as the extension? (eg generate 1.gif!)
275 NSString *parts, *contentType, *extension;
277 /* try type from body structure info */
279 parts = [self contentTypeForBodyPartInfo: [self partInfo]];
280 contentType = [[parts componentsSeparatedByString: @";"] objectAtIndex: 0];
282 if (![contentType length])
284 extension = [[self nameInContainer] pathExtension];
285 contentType = [self contentTypeForPathExtension: extension];
293 - (id)GETAction:(id)_ctx {
297 NSString *etag, *mimeType;
299 if ((error = [self matchesRequestConditionInContext:_ctx]) != nil) {
300 // TODO: currently we fetch the body structure to get here - check this!
301 /* check whether the mail still exists */
302 if (![[self mailObject] doesMailExist]) {
303 return [NSException exceptionWithHTTPStatus:404 /* Not Found */
304 reason:@"mail was deleted"];
306 return error; /* return 304 or 416 */
309 [self debugWithFormat:@"should fetch body part: %@",
310 [self bodyPartIdentifier]];
312 if ((data = [self fetchBLOB]) == nil) {
313 return [NSException exceptionWithHTTPStatus:404 /* not found */
314 reason:@"did not find body part"];
317 [self debugWithFormat:@" fetched %d bytes: %@", [data length],
320 // TODO: wrong, could be encoded
321 r = [(WOContext *)_ctx response];
322 mimeType = [self davContentType];
323 if ([mimeType isEqualToString: @"application/x-xpinstall"])
324 mimeType = @"application/octet-stream";
326 [r setHeader: mimeType forKey:@"content-type"];
327 [r setHeader: [NSString stringWithFormat:@"%d", [data length]]
328 forKey: @"content-length"];
330 if ((etag = [self davEntityTag]) != nil)
331 [r setHeader:etag forKey:@"etag"];
340 + (Class) bodyPartClassForKey: (NSString *) _key
345 pe = [_key pathExtension];
346 if (![pe isNotNull] || [pe length] == 0)
349 /* hard coded for now */
351 switch ([pe length]) {
353 if ([pe isEqualToString:@"gif"] ||
354 [pe isEqualToString:@"png"] ||
355 [pe isEqualToString:@"jpg"])
356 return NSClassFromString(@"SOGoImageMailBodyPart");
357 if ([pe isEqualToString:@"ics"])
358 return NSClassFromString(@"SOGoCalendarMailBodyPart");
359 if ([pe isEqualToString:@"vcf"])
360 return NSClassFromString(@"SOGoVCardMailBodyPart");
363 if ([pe isEqualToString:@"mail"])
364 return NSClassFromString(@"SOGoMessageMailBodyPart");
372 + (Class) bodyPartClassForMimeType: (NSString *) mimeType
375 NSString *classString;
378 if ([mimeType isEqualToString: @"image/gif"]
379 || [mimeType isEqualToString: @"image/png"]
380 || [mimeType isEqualToString: @"image/jpg"]
381 || [mimeType isEqualToString: @"image/jpeg"])
382 classString = @"SOGoImageMailBodyPart";
383 else if ([mimeType isEqualToString: @"text/calendar"]
384 || [mimeType isEqualToString: @"application/ics"])
385 classString = @"SOGoCalendarMailBodyPart";
386 else if ([mimeType isEqualToString: @"text/x-vcard"])
387 classString = @"SOGoVCardMailBodyPart";
388 else if ([mimeType isEqualToString: @"message/rfc822"])
389 classString = @"SOGoMessageMailBodyPart";
392 classString = @"SOGoMailBodyPart";
393 // NSLog (@"unhandled mime type: '%@'", mimeType);
396 klazz = NSClassFromString (classString);
409 - (BOOL)isDebuggingEnabled {
413 @end /* SOGoMailBodyPart */