X-Git-Url: https://err.no/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=SoObjects%2FMailer%2FSOGoMailObject.m;h=15d531375cae2514eab9b34b07fb3cd52bdd8bf5;hb=5c9ec4619b7f19cb1db6bf19aa081710dbdb0a59;hp=68ec6326ad6925dc6f9e9cddffbb6bf55424ff7f;hpb=80ee2e9ecd3b30b84952bb7a4d8aee074770a9a7;p=scalable-opengroupware.org diff --git a/SoObjects/Mailer/SOGoMailObject.m b/SoObjects/Mailer/SOGoMailObject.m index 68ec6326..15d53137 100644 --- a/SoObjects/Mailer/SOGoMailObject.m +++ b/SoObjects/Mailer/SOGoMailObject.m @@ -20,6 +20,7 @@ */ #import +#import #import #import #import @@ -30,18 +31,20 @@ #import #import #import -#import #import #import -#import #import +#import #import #import #import #import +#import #import #import + +#import "NSData+Mail.h" #import "SOGoMailFolder.h" #import "SOGoMailAccount.h" #import "SOGoMailManager.h" @@ -59,18 +62,11 @@ static BOOL debugOn = NO; static BOOL debugBodyStructure = NO; static BOOL debugSoParts = NO; -+ (int)version { - return [super version] + 0 /* v1 */; -} - -+ (void)initialize { ++ (void) initialize +{ NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; - NSAssert2([super version] == 1, - @"invalid superclass (%@) version %i !", - NSStringFromClass([self superclass]), [super version]); - - if ((fetchHeader = ([ud boolForKey:@"SOGoDoNotFetchMailHeader"] ? NO : YES))) + if ((fetchHeader = ([ud boolForKey: @"SOGoDoNotFetchMailHeader"] ? NO : YES))) NSLog(@"Note: fetching full mail header."); else NSLog(@"Note: not fetching full mail header: 'SOGoDoNotFetchMailHeader'"); @@ -79,7 +75,7 @@ static BOOL debugSoParts = NO; /* Note: "BODY" actually returns the structure! */ if (fetchHeader) { coreInfoKeys = [[NSArray alloc] initWithObjects: - @"FLAGS", @"ENVELOPE", @"BODY", + @"FLAGS", @"ENVELOPE", @"BODYSTRUCTURE", @"RFC822.SIZE", @"RFC822.HEADER", // not yet supported: @"INTERNALDATE", @@ -87,14 +83,14 @@ static BOOL debugSoParts = NO; } else { coreInfoKeys = [[NSArray alloc] initWithObjects: - @"FLAGS", @"ENVELOPE", @"BODY", + @"FLAGS", @"ENVELOPE", @"BODYSTRUCTURE", @"RFC822.SIZE", // not yet supported: @"INTERNALDATE", nil]; } - if (![[ud objectForKey:@"SOGoMailDisableETag"] boolValue]) { - mailETag = [[NSString alloc] initWithFormat:@"\"imap4url_%d_%d_%03d\"", + if (![[ud objectForKey: @"SOGoMailDisableETag"] boolValue]) { + mailETag = [[NSString alloc] initWithFormat: @"\"imap4url_%d_%d_%03d\"", UIX_MAILER_MAJOR_VERSION, UIX_MAILER_MINOR_VERSION, UIX_MAILER_SUBMINOR_VERSION]; @@ -105,17 +101,19 @@ static BOOL debugSoParts = NO; NSLog(@"Note(SOGoMailObject): etag caching disabled!"); } -- (void)dealloc { - [self->headers release]; - [self->headerPart release]; - [self->coreInfos release]; +- (void) dealloc +{ + [headers release]; + [headerPart release]; + [coreInfos release]; [super dealloc]; } /* IMAP4 */ -- (NSString *)relativeImap4Name { - return [[self nameInContainer] stringByDeletingPathExtension]; +- (NSString *) relativeImap4Name +{ + return [nameInContainer stringByDeletingPathExtension]; } /* hierarchy */ @@ -126,24 +124,25 @@ static BOOL debugSoParts = NO; /* part hierarchy */ -- (NSString *)keyExtensionForPart:(id)_partInfo { +- (NSString *) keyExtensionForPart: (id) _partInfo +{ NSString *mt, *st; if (_partInfo == nil) return nil; - mt = [_partInfo valueForKey:@"type"]; - st = [[_partInfo valueForKey:@"subtype"] lowercaseString]; - if ([mt isEqualToString:@"text"]) { - if ([st isEqualToString:@"plain"]) return @".txt"; - if ([st isEqualToString:@"html"]) return @".html"; - if ([st isEqualToString:@"calendar"]) return @".ics"; - if ([st isEqualToString:@"x-vcard"]) return @".vcf"; + mt = [_partInfo valueForKey: @"type"]; + st = [[_partInfo valueForKey: @"subtype"] lowercaseString]; + if ([mt isEqualToString: @"text"]) { + if ([st isEqualToString: @"plain"]) return @".txt"; + if ([st isEqualToString: @"html"]) return @".html"; + if ([st isEqualToString: @"calendar"]) return @".ics"; + if ([st isEqualToString: @"x-vcard"]) return @".vcf"; } - else if ([mt isEqualToString:@"image"]) + else if ([mt isEqualToString: @"image"]) return [@"." stringByAppendingString:st]; - else if ([mt isEqualToString:@"application"]) { - if ([st isEqualToString:@"pgp-signature"]) + else if ([mt isEqualToString: @"application"]) { + if ([st isEqualToString: @"pgp-signature"]) return @".asc"; } @@ -156,7 +155,7 @@ static BOOL debugSoParts = NO; NSArray *parts; unsigned i, count; - parts = [[self bodyStructure] valueForKey:@"parts"]; + parts = [[self bodyStructure] valueForKey: @"parts"]; if (![parts isNotNull]) return nil; if ((count = [parts count]) == 0) @@ -168,7 +167,7 @@ static BOOL debugSoParts = NO; BOOL hasParts; part = [parts objectAtIndex:i]; - hasParts = [part valueForKey:@"parts"] != nil ? YES:NO; + hasParts = [part valueForKey: @"parts"] != nil ? YES:NO; if ((hasParts && !_withParts) || (_withParts && !hasParts)) continue; @@ -176,28 +175,32 @@ static BOOL debugSoParts = NO; ma = [NSMutableArray arrayWithCapacity:count - i]; ext = [self keyExtensionForPart:part]; - key = [[NSString alloc] initWithFormat:@"%d%@", i + 1, ext?ext:@""]; + key = [[NSString alloc] initWithFormat: @"%d%@", i + 1, ext?ext: @""]; [ma addObject:key]; [key release]; } return ma; } -- (NSArray *)toOneRelationshipKeys { +- (NSArray *) toOneRelationshipKeys +{ return [self relationshipKeysWithParts:NO]; } -- (NSArray *)toManyRelationshipKeys { + +- (NSArray *) toManyRelationshipKeys +{ return [self relationshipKeysWithParts:YES]; } /* message */ -- (id)fetchParts:(NSArray *)_parts { +- (id) fetchParts: (NSArray *) _parts +{ // TODO: explain what it does /* Called by -fetchPlainTextParts: */ - return [[self imap4Connection] fetchURL:[self imap4URL] parts:_parts]; + return [[self imap4Connection] fetchURL: [self imap4URL] parts:_parts]; } /* core infos */ @@ -206,102 +209,128 @@ static BOOL debugSoParts = NO; static NSArray *existsKey = nil; id msgs; - if (self->coreInfos != nil) /* if we have coreinfos, we can use them */ - return [self->coreInfos isNotNull]; + if (coreInfos != nil) /* if we have coreinfos, we can use them */ + return [coreInfos isNotNull]; /* otherwise fetch something really simple */ if (existsKey == nil) /* we use size, other suggestions? */ - existsKey = [[NSArray alloc] initWithObjects:@"RFC822.SIZE", nil]; + existsKey = [[NSArray alloc] initWithObjects: @"RFC822.SIZE", nil]; msgs = [self fetchParts:existsKey]; // returns dict - msgs = [msgs valueForKey:@"fetch"]; + msgs = [msgs valueForKey: @"fetch"]; return [msgs count] > 0 ? YES : NO; } -- (id)fetchCoreInfos { +- (id) fetchCoreInfos +{ id msgs; - - if (self->coreInfos != nil) - return [self->coreInfos isNotNull] ? self->coreInfos : nil; - -#if 0 // TODO: old code, why was it using clientObject?? - msgs = [[self clientObject] fetchParts:coreInfoKeys]; // returns dict -#else - msgs = [self fetchParts:coreInfoKeys]; // returns dict -#endif - if (heavyDebug) [self logWithFormat:@"M: %@", msgs]; - msgs = [msgs valueForKey:@"fetch"]; - if ([msgs count] == 0) - return nil; - - self->coreInfos = [[msgs objectAtIndex:0] retain]; - return self->coreInfos; + + if (!coreInfos) + { + msgs = [self fetchParts: coreInfoKeys]; // returns dict + if (heavyDebug) + [self logWithFormat: @"M: %@", msgs]; + msgs = [msgs valueForKey: @"fetch"]; + if ([msgs count] > 0) + coreInfos = [msgs objectAtIndex: 0]; + [coreInfos retain]; + } + + return coreInfos; } -- (id)bodyStructure { +- (id) bodyStructure +{ id body; - body = [[self fetchCoreInfos] valueForKey:@"body"]; + body = [[self fetchCoreInfos] valueForKey: @"body"]; if (debugBodyStructure) - [self logWithFormat:@"BODY: %@", body]; + [self logWithFormat: @"BODY: %@", body]; + return body; } -- (NGImap4Envelope *)envelope { - return [[self fetchCoreInfos] valueForKey:@"envelope"]; +- (NGImap4Envelope *) envelope +{ + return [[self fetchCoreInfos] valueForKey: @"envelope"]; } -- (NSString *)subject { + +- (NSString *) subject +{ return [[self envelope] subject]; } -- (NSCalendarDate *)date { - return [[self envelope] date]; + +- (NSCalendarDate *) date +{ + NSTimeZone *userTZ; + NSCalendarDate *date; + + userTZ = [[context activeUser] timeZone]; + date = [[self envelope] date]; + [date setTimeZone: userTZ]; + + return date; } -- (NSArray *)fromEnvelopeAddresses { + +- (NSArray *) fromEnvelopeAddresses +{ return [[self envelope] from]; } -- (NSArray *)toEnvelopeAddresses { + +- (NSArray *) toEnvelopeAddresses +{ return [[self envelope] to]; } -- (NSArray *)ccEnvelopeAddresses { + +- (NSArray *) ccEnvelopeAddresses +{ return [[self envelope] cc]; } -- (NSData *)mailHeaderData { - return [[self fetchCoreInfos] valueForKey:@"header"]; +- (NSData *) mailHeaderData +{ + return [[self fetchCoreInfos] valueForKey: @"header"]; } -- (BOOL)hasMailHeaderInCoreInfos { + +- (BOOL) hasMailHeaderInCoreInfos +{ return [[self mailHeaderData] length] > 0 ? YES : NO; } -- (id)mailHeaderPart { +- (id) mailHeaderPart +{ NGMimeMessageParser *parser; NSData *data; - if (self->headerPart != nil) - return [self->headerPart isNotNull] ? self->headerPart : nil; + if (headerPart != nil) + return [headerPart isNotNull] ? headerPart : nil; if ([(data = [self mailHeaderData]) length] == 0) return nil; // TODO: do we need to set some delegate method which stops parsing the body? parser = [[NGMimeMessageParser alloc] init]; - self->headerPart = [[parser parsePartFromData:data] retain]; + headerPart = [[parser parsePartFromData:data] retain]; [parser release]; parser = nil; - if (self->headerPart == nil) { - self->headerPart = [[NSNull null] retain]; + if (headerPart == nil) { + headerPart = [[NSNull null] retain]; return nil; } - return self->headerPart; + return headerPart; } -- (NSDictionary *)mailHeaders { - if (self->headers == nil) - self->headers = [[[self mailHeaderPart] headers] copy]; - return self->headers; + +- (NSDictionary *) mailHeaders +{ + if (!headers) + headers = [[[self mailHeaderPart] headers] copy]; + + return headers; } -- (id)lookupInfoForBodyPart:(id)_path { +- (id) lookupInfoForBodyPart: (id) _path +{ NSEnumerator *pe; NSString *p; id info; @@ -310,7 +339,7 @@ static BOOL debugSoParts = NO; return nil; if ((info = [self bodyStructure]) == nil) { - [self errorWithFormat:@"got no body part structure!"]; + [self errorWithFormat: @"got no body part structure!"]; return nil; } @@ -320,7 +349,7 @@ static BOOL debugSoParts = NO; if ([_path length] == 0) return info; - _path = [_path componentsSeparatedByString:@"."]; + _path = [_path componentsSeparatedByString: @"."]; } /* @@ -337,19 +366,19 @@ static BOOL debugSoParts = NO; NSArray *parts; NSString *mt; - [self debugWithFormat:@"check PATH: %@", p]; + [self debugWithFormat: @"check PATH: %@", p]; idx = [p intValue] - 1; - parts = [info valueForKey:@"parts"]; - mt = [[info valueForKey:@"type"] lowercaseString]; - if ([mt isEqualToString:@"message"]) { + parts = [info valueForKey: @"parts"]; + mt = [[info valueForKey: @"type"] lowercaseString]; + if ([mt isEqualToString: @"message"]) { /* we have special behaviour for message types */ id body; - if ((body = [info valueForKey:@"body"]) != nil) { - mt = [body valueForKey:@"type"]; - if ([mt isEqualToString:@"multipart"]) - parts = [body valueForKey:@"parts"]; + if ((body = [info valueForKey: @"body"]) != nil) { + mt = [body valueForKey: @"type"]; + if ([mt isEqualToString: @"multipart"]) + parts = [body valueForKey: @"parts"]; else parts = [NSArray arrayWithObject:body]; } @@ -368,26 +397,27 @@ static BOOL debugSoParts = NO; /* content */ -- (NSData *)content { +- (NSData *) content +{ NSData *content; id result, fullResult; - fullResult = [self fetchParts:[NSArray arrayWithObject:@"RFC822"]]; + fullResult = [self fetchParts: [NSArray arrayWithObject: @"RFC822"]]; if (fullResult == nil) return nil; - if ([fullResult isKindOfClass:[NSException class]]) + if ([fullResult isKindOfClass: [NSException class]]) return fullResult; /* extract fetch result */ - result = [fullResult valueForKey:@"fetch"]; + result = [fullResult valueForKey: @"fetch"]; if (![result isKindOfClass:[NSArray class]]) { [self logWithFormat: @"ERROR: unexpected IMAP4 result (missing 'fetch'): %@", fullResult]; return [NSException exceptionWithHTTPStatus:500 /* server error */ - reason:@"unexpected IMAP4 result"]; + reason: @"unexpected IMAP4 result"]; } if ([result count] == 0) return nil; @@ -396,59 +426,75 @@ static BOOL debugSoParts = NO; /* extract message */ - if ((content = [result valueForKey:@"message"]) == nil) { + if ((content = [result valueForKey: @"message"]) == nil) { [self logWithFormat: @"ERROR: unexpected IMAP4 result (missing 'message'): %@", result]; return [NSException exceptionWithHTTPStatus:500 /* server error */ - reason:@"unexpected IMAP4 result"]; + reason: @"unexpected IMAP4 result"]; } return [[content copy] autorelease]; } -- (NSString *)contentAsString { - NSString *s; - NSData *content; - - if ((content = [self content]) == nil) - return nil; - if ([content isKindOfClass:[NSException class]]) - return (id)content; - - s = [[NSString alloc] initWithData:content - encoding:NSISOLatin1StringEncoding]; - if (s == nil) { - [self logWithFormat: - @"ERROR: could not convert data of length %d to string", - [content length]]; - return nil; - } - return [s autorelease]; +- (NSString *) davContentType +{ + return @"message/rfc822"; } -/* bulk fetching of plain/text content */ +- (NSString *) contentAsString +{ + id s; + NSData *content; -- (BOOL)shouldFetchPartOfType:(NSString *)_type subtype:(NSString *)_subtype { - /* - This method decides which parts are 'prefetched' for display. Those are - usually text parts (the set is currently hardcoded in this method ...). - */ - _type = [_type lowercaseString]; - _subtype = [_subtype lowercaseString]; - - return (([_type isEqualToString:@"text"] - && ([_subtype isEqualToString:@"plain"] - || [_subtype isEqualToString:@"html"] - || [_subtype isEqualToString:@"calendar"])) - || ([_type isEqualToString:@"application"] - && ([_subtype isEqualToString:@"pgp-signature"] - || [_subtype hasPrefix:@"x-vnd.kolab."]))); + content = [self content]; + if (content) + { + if ([content isKindOfClass: [NSData class]]) + { + s = [[NSString alloc] initWithData: content + encoding: NSISOLatin1StringEncoding]; + if (s) + [s autorelease]; + else + [self logWithFormat: + @"ERROR: could not convert data of length %d to string", + [content length]]; + } + else + s = content; + } + else + s = nil; + + return s; } -- (void)addRequiredKeysOfStructure:(id)_info path:(NSString *)_p - toArray:(NSMutableArray *)_keys - recurse:(BOOL)_recurse +/* bulk fetching of plain/text content */ + +// - (BOOL) shouldFetchPartOfType: (NSString *) _type +// subtype: (NSString *) _subtype +// { +// /* +// This method decides which parts are 'prefetched' for display. Those are +// usually text parts (the set is currently hardcoded in this method ...). +// */ +// _type = [_type lowercaseString]; +// _subtype = [_subtype lowercaseString]; + +// return (([_type isEqualToString: @"text"] +// && ([_subtype isEqualToString: @"plain"] +// || [_subtype isEqualToString: @"html"] +// || [_subtype isEqualToString: @"calendar"])) +// || ([_type isEqualToString: @"application"] +// && ([_subtype isEqualToString: @"pgp-signature"] +// || [_subtype hasPrefix: @"x-vnd.kolab."]))); +// } + +- (void) addRequiredKeysOfStructure: (NSDictionary *) info + path: (NSString *) p + toArray: (NSMutableArray *) keys + acceptedTypes: (NSArray *) types { /* This is used to collect the set of IMAP4 fetch-keys required to fetch @@ -457,92 +503,96 @@ static BOOL debugSoParts = NO; The method calls itself recursively to walk the body structure. */ - NSArray *parts; + NSArray *parts; unsigned i, count; - BOOL fetchPart; + NSString *k; id body; - - /* Note: if the part itself doesn't qualify, we still check subparts */ - fetchPart = [self shouldFetchPartOfType:[_info valueForKey:@"type"] - subtype:[_info valueForKey:@"subtype"]]; - if (fetchPart) { - NSString *k; - - if ([_p length] > 0) { - k = [[@"body[" stringByAppendingString:_p] stringByAppendingString:@"]"]; + NSString *sp, *mimeType; + id childInfo; + + mimeType = [[NSString stringWithFormat: @"%@/%@", + [info valueForKey: @"type"], + [info valueForKey: @"subtype"]] + lowercaseString]; + if ([types containsObject: mimeType]) + { + if ([p length] > 0) + k = [NSString stringWithFormat: @"body[%@]", p]; + else + { + /* + for some reason we need to add ".TEXT" for plain text stuff on root + entities? + TODO: check with HTML + */ + k = @"body[text]"; + } + [keys addObject: [NSDictionary dictionaryWithObjectsAndKeys: k, @"key", + mimeType, @"mimeType", nil]]; } - else { - /* - for some reason we need to add ".TEXT" for plain text stuff on root - entities? - TODO: check with HTML - */ - k = @"body[text]"; + + parts = [info objectForKey: @"parts"]; + count = [parts count]; + for (i = 0; i < count; i++) + { + sp = (([p length] > 0) + ? [p stringByAppendingFormat: @".%d", i + 1] + : [NSString stringWithFormat: @"%d", i + 1]); + + childInfo = [parts objectAtIndex: i]; + + [self addRequiredKeysOfStructure: childInfo + path: sp toArray: keys + acceptedTypes: types]; } - [_keys addObject:k]; - } - - if (!_recurse) - return; - - /* recurse */ - - parts = [(NSDictionary *)_info objectForKey:@"parts"]; - for (i = 0, count = [parts count]; i < count; i++) { - NSString *sp; - id childInfo; - - sp = ([_p length] > 0) - ? [_p stringByAppendingFormat:@".%d", i + 1] - : [NSString stringWithFormat:@"%d", i + 1]; - - childInfo = [parts objectAtIndex:i]; - - [self addRequiredKeysOfStructure:childInfo path:sp toArray:_keys - recurse:YES]; - } - + /* check body */ - - if ((body = [(NSDictionary *)_info objectForKey:@"body"]) != nil) { - NSString *sp; - - sp = [[body valueForKey:@"type"] lowercaseString]; - if ([sp isEqualToString:@"multipart"]) - sp = _p; - else - sp = [_p length] > 0 ? [_p stringByAppendingString:@".1"] : @"1"; - [self addRequiredKeysOfStructure:body path:sp toArray:_keys - recurse:YES]; - } + body = [info objectForKey: @"body"]; + if (body) + { + sp = [[body valueForKey: @"type"] lowercaseString]; + if ([sp isEqualToString: @"multipart"]) + sp = p; + else + sp = [p length] > 0 ? [p stringByAppendingString: @".1"] : @"1"; + [self addRequiredKeysOfStructure: body + path: sp toArray: keys + acceptedTypes: types]; + } } -- (NSArray *)plainTextContentFetchKeys { +- (NSArray *) plainTextContentFetchKeys +{ /* The name is not 100% correct. The method returns all body structure fetch keys which are marked by the -shouldFetchPartOfType:subtype: method. */ NSMutableArray *ma; - - ma = [NSMutableArray arrayWithCapacity:4]; - [self addRequiredKeysOfStructure:[[self clientObject] bodyStructure] - path:@"" toArray:ma recurse:YES]; + NSArray *types; + + types = [NSArray arrayWithObjects: @"text/plain", @"text/html", + @"text/calendar", @"application/pgp-signature", nil]; + ma = [NSMutableArray arrayWithCapacity: 4]; + [self addRequiredKeysOfStructure: [self bodyStructure] + path: @"" toArray: ma acceptedTypes: types]; + return ma; } -- (NSDictionary *)fetchPlainTextParts:(NSArray *)_fetchKeys { +- (NSDictionary *) fetchPlainTextParts: (NSArray *) _fetchKeys +{ // TODO: is the name correct or does it also fetch other parts? NSMutableDictionary *flatContents; unsigned i, count; id result; - [self debugWithFormat:@"fetch keys: %@", _fetchKeys]; + [self debugWithFormat: @"fetch keys: %@", _fetchKeys]; - result = [self fetchParts:_fetchKeys]; - result = [result valueForKey:@"RawResponse"]; // hackish + result = [self fetchParts: [_fetchKeys objectsForKey: @"key"]]; + result = [result valueForKey: @"RawResponse"]; // hackish // Note: -valueForKey: doesn't work! - result = [(NSDictionary *)result objectForKey:@"fetch"]; + result = [(NSDictionary *)result objectForKey: @"fetch"]; count = [_fetchKeys count]; flatContents = [NSMutableDictionary dictionaryWithCapacity:count]; @@ -550,22 +600,22 @@ static BOOL debugSoParts = NO; NSString *key; NSData *data; - key = [_fetchKeys objectAtIndex:i]; + key = [[_fetchKeys objectAtIndex:i] objectForKey: @"key"]; data = [(NSDictionary *)[(NSDictionary *)result objectForKey:key] - objectForKey:@"data"]; + objectForKey: @"data"]; if (![data isNotNull]) { - [self errorWithFormat:@"got no data for key: %@", key]; + [self errorWithFormat: @"got no data for key: %@", key]; continue; } - if ([key isEqualToString:@"body[text]"]) + if ([key isEqualToString: @"body[text]"]) key = @""; // see key collector for explanation (TODO: where?) - else if ([key hasPrefix:@"body["]) { + else if ([key hasPrefix: @"body["]) { NSRange r; key = [key substringFromIndex:5]; - r = [key rangeOfString:@"]"]; + r = [key rangeOfString: @"]"]; if (r.length > 0) key = [key substringToIndex:r.location]; } @@ -574,63 +624,61 @@ static BOOL debugSoParts = NO; return flatContents; } -- (NSDictionary *)fetchPlainTextParts { - return [self fetchPlainTextParts:[self plainTextContentFetchKeys]]; +- (NSDictionary *) fetchPlainTextParts +{ + return [self fetchPlainTextParts: [self plainTextContentFetchKeys]]; } /* convert parts to strings */ - -- (NSString *)stringForData:(NSData *)_data partInfo:(NSDictionary *)_info +- (NSString *) stringForData: (NSData *) _data + partInfo: (NSDictionary *) _info { - NSString *charset, *encoding, *s; + NSString *charset, *s; NSData *mailData; - if (![_data isNotNull]) - return nil; - - s = nil; - - encoding = [[_info objectForKey:@"encoding"] lowercaseString]; - - if ([encoding isEqualToString: @"7bit"] - || [encoding isEqualToString: @"8bit"]) - mailData = _data; - else if ([encoding isEqualToString: @"base64"]) - mailData = [_data dataByDecodingBase64]; - else if ([encoding isEqualToString: @"quoted-printable"]) - mailData = [_data dataByDecodingQuotedPrintable]; - - charset = [[_info valueForKey:@"parameterList"] valueForKey: @"charset"]; - if (![charset length]) + if ([_data isNotNull]) { - s = [[NSString alloc] initWithData:mailData encoding:NSUTF8StringEncoding]; - [s autorelease]; + mailData + = [_data bodyDataFromEncoding: [_info objectForKey: @"encoding"]]; + + charset = [[_info valueForKey: @"parameterList"] valueForKey: @"charset"]; + if (![charset length]) + { + s = [[NSString alloc] initWithData: mailData encoding: NSUTF8StringEncoding]; + [s autorelease]; + } + else + s = [NSString stringWithData: mailData + usingEncodingNamed: charset]; } else - s = [NSString stringWithData: mailData - usingEncodingNamed: charset]; + s = nil; return s; } -- (NSDictionary *)stringifyTextParts:(NSDictionary *)_datas { +- (NSDictionary *) stringifyTextParts: (NSDictionary *) _datas +{ NSMutableDictionary *md; + NSDictionary *info; NSEnumerator *keys; - NSString *key; - - md = [NSMutableDictionary dictionaryWithCapacity:4]; + NSString *key, *s; + + md = [NSMutableDictionary dictionaryWithCapacity:4]; keys = [_datas keyEnumerator]; - while ((key = [keys nextObject]) != nil) { - NSDictionary *info; - NSString *s; - - info = [self lookupInfoForBodyPart:key]; - if ((s = [self stringForData:[_datas objectForKey:key] partInfo:info])) - [md setObject:s forKey:key]; - } + while ((key = [keys nextObject])) + { + info = [self lookupInfoForBodyPart: key]; + s = [self stringForData: [_datas objectForKey:key] partInfo: info]; + if (s) + [md setObject: s forKey: key]; + } + return md; } -- (NSDictionary *)fetchPlainTextStrings:(NSArray *)_fetchKeys { + +- (NSDictionary *) fetchPlainTextStrings: (NSArray *) _fetchKeys +{ /* The fetched parts are NSData objects, this method converts them into NSString objects based on the information inside the bodystructure. @@ -650,11 +698,14 @@ static BOOL debugSoParts = NO; /* flags */ -- (NSException *)addFlags:(id)_flags { - return [[self imap4Connection] addFlags:_flags toURL:[self imap4URL]]; +- (NSException *) addFlags: (id) _flags +{ + return [[self imap4Connection] addFlags:_flags toURL: [self imap4URL]]; } -- (NSException *)removeFlags:(id)_flags { - return [[self imap4Connection] removeFlags:_flags toURL:[self imap4URL]]; + +- (NSException *) removeFlags: (id) _flags +{ + return [[self imap4Connection] removeFlags:_flags toURL: [self imap4URL]]; } /* permissions */ @@ -667,20 +718,26 @@ static BOOL debugSoParts = NO; login = [[context activeUser] login]; parentAcl = [[self container] aclsForUser: login]; - return [parentAcl containsObject: SOGoMailRole_MessageEraser]; + return [parentAcl containsObject: SOGoRole_ObjectEraser]; } /* name lookup */ -- (id)lookupImap4BodyPartKey:(NSString *)_key inContext:(id)_ctx { +- (id) lookupImap4BodyPartKey: (NSString *) _key + inContext: (id) _ctx +{ // TODO: we might want to check for existence prior controller creation Class clazz; clazz = [SOGoMailBodyPart bodyPartClassForKey:_key inContext:_ctx]; - return [[[clazz alloc] initWithName:_key inContainer:self] autorelease]; + + return [clazz objectWithName:_key inContainer: self]; } -- (id)lookupName:(NSString *)_key inContext:(id)_ctx acquire:(BOOL)_flag { +- (id) lookupName: (NSString *) _key + inContext: (id) _ctx + acquire: (BOOL) _flag +{ id obj; /* first check attributes directly bound to the application */ @@ -692,46 +749,53 @@ static BOOL debugSoParts = NO; if ([self isBodyPartKey:_key inContext:_ctx]) { if ((obj = [self lookupImap4BodyPartKey:_key inContext:_ctx]) != nil) { if (debugSoParts) - [self logWithFormat:@"mail looked up part %@: %@", _key, obj]; + [self logWithFormat: @"mail looked up part %@: %@", _key, obj]; return obj; } } /* return 404 to stop acquisition */ return [NSException exceptionWithHTTPStatus:404 /* Not Found */ - reason:@"Did not find mail method or part-reference!"]; + reason: @"Did not find mail method or part-reference!"]; } /* WebDAV */ -- (BOOL)davIsCollection { +- (BOOL) davIsCollection +{ /* while a mail has child objects, it should appear as a file in WebDAV */ return NO; } -- (id)davContentLength { - return [[self fetchCoreInfos] valueForKey:@"size"]; +- (id) davContentLength +{ + return [[self fetchCoreInfos] valueForKey: @"size"]; } -- (NSDate *)davCreationDate { +- (NSDate *) davCreationDate +{ // TODO: use INTERNALDATE once NGImap4 supports that return nil; } -- (NSDate *)davLastModified { + +- (NSDate *) davLastModified +{ return [self davCreationDate]; } -- (NSException *)davMoveToTargetObject:(id)_target newName:(NSString *)_name - inContext:(id)_ctx +- (NSException *) davMoveToTargetObject: (id) _target + newName: (NSString *) _name + inContext: (id)_ctx { - [self logWithFormat:@"TODO: should move mail as '%@' to: %@", + [self logWithFormat: @"TODO: should move mail as '%@' to: %@", _name, _target]; - return [NSException exceptionWithHTTPStatus:501 /* Not Implemented */ - reason:@"not implemented"]; + return [NSException exceptionWithHTTPStatus: 501 /* Not Implemented */ + reason: @"not implemented"]; } -- (NSException *)davCopyToTargetObject:(id)_target newName:(NSString *)_name - inContext:(id)_ctx +- (NSException *) davCopyToTargetObject: (id) _target + newName: (NSString *) _name + inContext: (id)_ctx { /* Note: this is special because we create SOGoMailObject's even if they do @@ -755,7 +819,8 @@ static BOOL debugSoParts = NO; /* actions */ -- (id)GETAction:(id)_ctx { +- (id) GETAction: (id) _ctx +{ NSException *error; WOResponse *r; NSData *content; @@ -764,7 +829,7 @@ static BOOL debugSoParts = NO; /* check whether the mail still exists */ if (![self doesMailExist]) { return [NSException exceptionWithHTTPStatus:404 /* Not Found */ - reason:@"mail was deleted"]; + reason: @"mail was deleted"]; } return error; /* return 304 or 416 */ } @@ -774,18 +839,19 @@ static BOOL debugSoParts = NO; return content; if (content == nil) { return [NSException exceptionWithHTTPStatus:404 /* Not Found */ - reason:@"did not find IMAP4 message"]; + reason: @"did not find IMAP4 message"]; } r = [(WOContext *)_ctx response]; - [r setHeader:@"message/rfc822" forKey:@"content-type"]; + [r setHeader: @"message/rfc822" forKey: @"content-type"]; [r setContent:content]; return r; } /* operations */ -- (NSException *)trashInContext:(id)_ctx { +- (NSException *) trashInContext: (id) _ctx +{ /* Trashing is three actions: a) copy to trash folder @@ -805,47 +871,35 @@ static BOOL debugSoParts = NO; return (NSException *)trashFolder; if (![trashFolder isNotNull]) { return [NSException exceptionWithHTTPStatus:500 /* Server Error */ - reason:@"Did not find Trash folder!"]; + reason: @"Did not find Trash folder!"]; } [trashFolder flushMailCaches]; /* a) copy */ error = [self davCopyToTargetObject:trashFolder - newName:@"fakeNewUnusedByIMAP4" /* autoassigned */ + newName: @"fakeNewUnusedByIMAP4" /* autoassigned */ inContext:_ctx]; if (error != nil) return error; /* b) mark deleted */ - error = [[self imap4Connection] markURLDeleted:[self imap4URL]]; + error = [[self imap4Connection] markURLDeleted: [self imap4URL]]; if (error != nil) return error; - - /* c) expunge */ - error = [[self imap4Connection] expungeAtURL:[[self container] imap4URL]]; - if (error != nil) return error; // TODO: unflag as deleted? + [container markForExpunge]; + [self flushMailCaches]; return nil; } -- (NSException *) moveToFolderNamed: (NSString *) folderName +- (NSException *) copyToFolderNamed: (NSString *) folderName inContext: (id)_ctx { - /* - Trashing is three actions: - a) copy to trash folder - b) mark mail as deleted - c) expunge folder - - In case b) or c) fails, we can't do anything because IMAP4 doesn't tell us - the ID used in the trash folder. - */ SOGoMailAccounts *destFolder; NSEnumerator *folders; NSString *currentFolderName, *reason; - NSException *error; // TODO: check for safe HTTP method @@ -875,27 +929,33 @@ static BOOL debugSoParts = NO; [destFolder flushMailCaches]; /* a) copy */ - - error = [self davCopyToTargetObject: destFolder - newName:@"fakeNewUnusedByIMAP4" /* autoassigned */ - inContext:_ctx]; - if (error != nil) return error; - /* b) mark deleted */ - - error = [[self imap4Connection] markURLDeleted: [self imap4URL]]; - if (error != nil) return error; + return [self davCopyToTargetObject: destFolder + newName: @"fakeNewUnusedByIMAP4" /* autoassigned */ + inContext:_ctx]; +} + +- (NSException *) moveToFolderNamed: (NSString *) folderName + inContext: (id)_ctx +{ + NSException *error; + + if (![self copyToFolderNamed: folderName + inContext: _ctx]) + { + /* b) mark deleted */ - /* c) expunge */ + error = [[self imap4Connection] markURLDeleted: [self imap4URL]]; + if (error != nil) return error; - error = [[self imap4Connection] expungeAtURL:[[self container] imap4URL]]; - if (error != nil) return error; // TODO: unflag as deleted? - [self flushMailCaches]; + [self flushMailCaches]; + } return nil; } -- (NSException *)delete { +- (NSException *) delete +{ /* Note: delete is different to DELETEAction: for mails! The 'delete' runs either flags a message as deleted or moves it to the Trash while @@ -910,7 +970,9 @@ static BOOL debugSoParts = NO; error = [[self imap4Connection] markURLDeleted:[self imap4URL]]; return error; } -- (id)DELETEAction:(id)_ctx { + +- (id) DELETEAction: (id) _ctx +{ NSException *error; // TODO: ensure safe HTTP method @@ -926,39 +988,42 @@ static BOOL debugSoParts = NO; /* some mail classification */ -- (BOOL)isKolabObject { +- (BOOL) isKolabObject +{ NSDictionary *h; if ((h = [self mailHeaders]) != nil) - return [[h objectForKey:@"x-kolab-type"] isNotEmpty]; + return [[h objectForKey: @"x-kolab-type"] isNotEmpty]; // TODO: we could check the body structure? return NO; } -- (BOOL)isMailingListMail { +- (BOOL) isMailingListMail +{ NSDictionary *h; if ((h = [self mailHeaders]) == nil) return NO; - return [[h objectForKey:@"list-id"] isNotEmpty]; + return [[h objectForKey: @"list-id"] isNotEmpty]; } -- (BOOL)isVirusScanned { +- (BOOL) isVirusScanned +{ NSDictionary *h; if ((h = [self mailHeaders]) == nil) return NO; - if (![[h objectForKey:@"x-virus-status"] isNotEmpty]) return NO; - if (![[h objectForKey:@"x-virus-scanned"] isNotEmpty]) return NO; + if (![[h objectForKey: @"x-virus-status"] isNotEmpty]) return NO; + if (![[h objectForKey: @"x-virus-scanned"] isNotEmpty]) return NO; return YES; } -- (NSString *)scanListHeaderValue:(id)_value - forFieldWithPrefix:(NSString *)_prefix +- (NSString *) scanListHeaderValue: (id) _value + forFieldWithPrefix: (NSString *) _prefix { /* Note: not very tolerant on embedded commands and <> */ // TODO: does not really belong here, should be a header-field-parser @@ -983,9 +1048,9 @@ static BOOL debugSoParts = NO; return nil; /* check for commas in string values */ - r = [_value rangeOfString:@","]; + r = [_value rangeOfString: @","]; if (r.length > 0) { - return [self scanListHeaderValue:[_value componentsSeparatedByString:@","] + return [self scanListHeaderValue:[_value componentsSeparatedByString: @","] forFieldWithPrefix:_prefix]; } @@ -995,7 +1060,7 @@ static BOOL debugSoParts = NO; /* unquote */ if ([_value characterAtIndex:0] == '<') { - r = [_value rangeOfString:@">"]; + r = [_value rangeOfString: @">"]; _value = (r.length == 0) ? [_value substringFromIndex:1] : [_value substringWithRange:NSMakeRange(1, r.location - 2)]; @@ -1004,25 +1069,31 @@ static BOOL debugSoParts = NO; return _value; } -- (NSString *)mailingListArchiveURL { +- (NSString *) mailingListArchiveURL +{ return [self scanListHeaderValue: - [[self mailHeaders] objectForKey:@"list-archive"] - forFieldWithPrefix:@"