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 #include "SOGoMailBodyPart.h"
23 #include "SOGoMailObject.h"
24 #include "SOGoMailManager.h"
27 @implementation SOGoMailBodyPart
29 static NSString *mailETag = nil;
30 static BOOL debugOn = NO;
33 return [super version] + 0 /* v1 */;
37 NSAssert2([super version] == 1,
38 @"invalid superclass (%@) version %i !",
39 NSStringFromClass([self superclass]), [super version]);
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: '%@'",
50 [self->partInfo release];
51 [self->identifier release];
52 [self->pathToPart release];
58 - (SOGoMailObject *)mailObject {
59 return [[self container] mailObject];
64 - (NSString *)bodyPartName {
68 s = [self nameInContainer];
69 r = [s rangeOfString:@"."]; /* strip extensions */
72 return [s substringToIndex:r.location];
75 - (NSArray *)bodyPartPath {
79 if (self->pathToPart != nil)
80 return [self->pathToPart isNotNull] ? self->pathToPart : nil;
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];
88 self->pathToPart = [p copy];
90 return self->pathToPart;
93 - (NSString *)bodyPartIdentifier {
94 if (self->identifier != nil)
95 return [self->identifier isNotNull] ? self->identifier : nil;
98 [[[self bodyPartPath] componentsJoinedByString:@"."] copy];
99 return self->identifier;
102 - (NSURL *)imap4URL {
103 /* reuse URL of message */
104 return [[self mailObject] imap4URL];
110 if (self->partInfo != nil)
111 return [self->partInfo isNotNull] ? self->partInfo : nil;
114 [[[self mailObject] lookupInfoForBodyPart:[self bodyPartPath]] retain];
115 return self->partInfo;
120 - (id)lookupImap4BodyPartKey:(NSString *)_key inContext:(id)_ctx {
121 // TODO: we might want to check for existence prior controller creation
124 clazz = [SOGoMailBodyPart bodyPartClassForKey:_key inContext:_ctx];
125 return [[[clazz alloc] initWithName:_key inContainer:self] autorelease];
128 - (id)lookupName:(NSString *)_key inContext:(id)_ctx acquire:(BOOL)_flag {
131 /* first check attributes directly bound to the application */
132 if ((obj = [super lookupName:_key inContext:_ctx acquire:NO]) != nil)
135 /* lookup body part */
137 if ([self isBodyPartKey:_key inContext:_ctx]) {
138 if ((obj = [self lookupImap4BodyPartKey:_key inContext:_ctx]) != nil)
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
147 if ([[_key pathExtension] length] > 0)
150 /* return 404 to stop acquisition */
151 return [NSException exceptionWithHTTPStatus:404 /* Not Found */
152 reason:@"Did not find a subpart for the given name!"];
157 - (NSData *)fetchBLOB {
158 // HEADER, HEADER.FIELDS, HEADER.FIELDS.NOT, MIME, TEXT
162 data = [[self imap4Connection] fetchContentOfBodyPart:
163 [self bodyPartIdentifier]
164 atURL:[self imap4URL]];
165 if (data == nil) return nil;
167 /* check for content encodings */
169 if ((enc = [[self partInfo] valueForKey:@"encoding"]) != nil) {
170 enc = [enc uppercaseString];
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?
177 [self errorWithFormat:@"unsupported encoding: %@", enc];
185 - (NSString *)contentTypeForBodyPartInfo:(id)_info {
186 NSMutableString *type;
188 NSDictionary *parameters;
192 if (![_info isNotNull])
195 mt = [_info valueForKey:@"type"]; if (![mt isNotNull]) return nil;
196 st = [_info valueForKey:@"subtype"]; if (![st isNotNull]) return nil;
198 type = [NSMutableString stringWithCapacity:16];
199 [type appendString:[mt lowercaseString]];
200 [type appendString:@"/"];
201 [type appendString:[st lowercaseString]];
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:@"\""];
215 - (NSString *)contentTypeForPathExtension:(NSString *)pe {
216 if ([pe length] == 0)
217 return @"application/octet-stream";
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";
225 return @"application/octet-stream";
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!)
233 /* try type from body structure info */
235 if ((pe = [self contentTypeForBodyPartInfo:[self partInfo]]) != nil)
240 pe = [[self nameInContainer] pathExtension];
241 return [self contentTypeForPathExtension:pe];
246 - (id)GETAction:(id)_ctx {
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"];
259 return error; /* return 304 or 416 */
262 [self debugWithFormat:@"should fetch body part: %@",
263 [self bodyPartIdentifier]];
265 if ((data = [self fetchBLOB]) == nil) {
266 return [NSException exceptionWithHTTPStatus:404 /* not found */
267 reason:@"did not find body part"];
270 [self debugWithFormat:@" fetched %d bytes: %@", [data length],
273 // TODO: wrong, could be encoded
275 [r setHeader:[self davContentType] forKey:@"content-type"];
276 [r setHeader:[NSString stringWithFormat:@"%d", [data length]]
277 forKey:@"content-length"];
279 if ((etag = [self davEntityTag]) != nil)
280 [r setHeader:etag forKey:@"etag"];
288 + (Class)bodyPartClassForKey:(NSString *)_key inContext:(id)_ctx {
291 pe = [_key pathExtension];
292 if (![pe isNotNull] || [pe length] == 0)
295 /* hard coded for now */
297 switch ([pe length]) {
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");
309 if ([pe isEqualToString:@"mail"])
310 return NSClassFromString(@"SOGoMessageMailBodyPart");
326 - (BOOL)isDebuggingEnabled {
330 @end /* SOGoMailBodyPart */