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 "SOGoMailObject.h"
38 #import "SOGoMailManager.h"
40 #import "SOGoMailBodyPart.h"
42 @implementation SOGoMailBodyPart
44 static NSString *mailETag = nil;
45 static BOOL debugOn = NO;
49 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
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: '%@'",
60 NSLog(@"Note(SOGoMailBodyPart): etag caching disabled!");
64 [self->partInfo release];
65 [self->identifier release];
66 [self->pathToPart release];
72 - (SOGoMailObject *)mailObject {
73 return [[self container] mailObject];
78 - (NSString *)bodyPartName {
82 s = [self nameInContainer];
83 r = [s rangeOfString:@"."]; /* strip extensions */
86 return [s substringToIndex:r.location];
89 - (NSArray *)bodyPartPath {
93 if (self->pathToPart != nil)
94 return [self->pathToPart isNotNull] ? self->pathToPart : nil;
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];
102 self->pathToPart = [p copy];
104 return self->pathToPart;
107 - (NSString *)bodyPartIdentifier {
108 if (self->identifier != nil)
109 return [self->identifier isNotNull] ? self->identifier : nil;
112 [[[self bodyPartPath] componentsJoinedByString:@"."] copy];
113 return self->identifier;
116 - (NSURL *)imap4URL {
117 /* reuse URL of message */
118 return [[self mailObject] imap4URL];
124 if (self->partInfo != nil)
125 return [self->partInfo isNotNull] ? self->partInfo : nil;
128 [[[self mailObject] lookupInfoForBodyPart:[self bodyPartPath]] retain];
129 return self->partInfo;
134 - (id) lookupImap4BodyPartKey: (NSString *) _key
137 // TODO: we might want to check for existence prior controller creation
140 clazz = [SOGoMailBodyPart bodyPartClassForKey:_key inContext:_ctx];
142 return [clazz objectWithName: _key inContainer: self];
145 - (id) lookupName: (NSString *) _key
147 acquire: (BOOL) _flag
151 /* first check attributes directly bound to the application */
152 obj = [super lookupName:_key inContext:_ctx acquire:NO];
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 */
168 - (NSData *)fetchBLOB {
169 // HEADER, HEADER.FIELDS, HEADER.FIELDS.NOT, MIME, TEXT
173 data = [[self imap4Connection] fetchContentOfBodyPart:
174 [self bodyPartIdentifier]
175 atURL:[self imap4URL]];
176 if (data == nil) return nil;
178 /* check for content encodings */
180 if ((enc = [[self partInfo] valueForKey:@"encoding"]) != nil) {
181 enc = [enc uppercaseString];
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?
188 [self errorWithFormat:@"unsupported encoding: %@", enc];
196 - (NSString *)contentTypeForBodyPartInfo:(id)_info {
197 NSMutableString *type;
199 NSDictionary *parameters;
203 if (![_info isNotNull])
206 mt = [_info valueForKey:@"type"]; if (![mt isNotNull]) return nil;
207 st = [_info valueForKey:@"subtype"]; if (![st isNotNull]) return nil;
209 type = [NSMutableString stringWithCapacity:16];
210 [type appendString:[mt lowercaseString]];
211 [type appendString:@"/"];
212 [type appendString:[st lowercaseString]];
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:@"\""];
226 - (NSString *)contentTypeForPathExtension:(NSString *)pe {
227 if ([pe length] == 0)
228 return @"application/octet-stream";
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";
236 return @"application/octet-stream";
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!)
244 /* try type from body structure info */
246 if ((pe = [self contentTypeForBodyPartInfo:[self partInfo]]) != nil)
251 pe = [[self nameInContainer] pathExtension];
252 return [self contentTypeForPathExtension:pe];
257 - (id)GETAction:(id)_ctx {
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"];
270 return error; /* return 304 or 416 */
273 [self debugWithFormat:@"should fetch body part: %@",
274 [self bodyPartIdentifier]];
276 if ((data = [self fetchBLOB]) == nil) {
277 return [NSException exceptionWithHTTPStatus:404 /* not found */
278 reason:@"did not find body part"];
281 [self debugWithFormat:@" fetched %d bytes: %@", [data length],
284 // TODO: wrong, could be encoded
285 r = [(WOContext *)_ctx response];
286 [r setHeader:[self davContentType] forKey:@"content-type"];
287 [r setHeader:[NSString stringWithFormat:@"%d", [data length]]
288 forKey:@"content-length"];
290 if ((etag = [self davEntityTag]) != nil)
291 [r setHeader:etag forKey:@"etag"];
299 + (Class)bodyPartClassForKey:(NSString *)_key inContext:(id)_ctx {
302 pe = [_key pathExtension];
303 if (![pe isNotNull] || [pe length] == 0)
306 /* hard coded for now */
308 switch ([pe length]) {
310 if ([pe isEqualToString:@"gif"] ||
311 [pe isEqualToString:@"png"] ||
312 [pe isEqualToString:@"jpg"])
313 return NSClassFromString(@"SOGoImageMailBodyPart");
314 if ([pe isEqualToString:@"ics"])
315 return NSClassFromString(@"SOGoCalendarMailBodyPart");
316 if ([pe isEqualToString:@"vcf"])
317 return NSClassFromString(@"SOGoVCardMailBodyPart");
320 if ([pe isEqualToString:@"mail"])
321 return NSClassFromString(@"SOGoMessageMailBodyPart");
337 - (BOOL)isDebuggingEnabled {
341 @end /* SOGoMailBodyPart */