02111-1307, USA.
*/
-#include "SOGoMailObject.h"
-#include "SOGoMailFolder.h"
-#include "SOGoMailAccount.h"
-#include "SOGoMailManager.h"
-#include "SOGoMailBodyPart.h"
-#include <NGImap4/NGImap4Envelope.h>
-#include <NGImap4/NGImap4EnvelopeAddress.h>
-#include <NGMail/NGMimeMessageParser.h>
-#include "common.h"
+#import <Foundation/NSArray.h>
+#import <Foundation/NSCalendarDate.h>
+#import <Foundation/NSDictionary.h>
+#import <Foundation/NSEnumerator.h>
+#import <Foundation/NSString.h>
+#import <Foundation/NSUserDefaults.h>
+#import <Foundation/NSValue.h>
+
+#import <NGObjWeb/WOContext.h>
+#import <NGObjWeb/WOContext+SoObjects.h>
+#import <NGObjWeb/WOResponse.h>
+#import <NGObjWeb/NSException+HTTP.h>
+#import <NGExtensions/NSNull+misc.h>
+#import <NGExtensions/NSObject+Logs.h>
+#import <NGExtensions/NSString+Encoding.h>
+#import <NGExtensions/NSString+misc.h>
+#import <NGImap4/NGImap4Connection.h>
+#import <NGImap4/NGImap4Envelope.h>
+#import <NGImap4/NGImap4EnvelopeAddress.h>
+#import <NGMail/NGMimeMessageParser.h>
+
+#import <SoObjects/SOGo/NSArray+Utilities.h>
+#import <SoObjects/SOGo/SOGoPermissions.h>
+#import <SoObjects/SOGo/SOGoUser.h>
+
+#import "NSData+Mail.h"
+#import "SOGoMailFolder.h"
+#import "SOGoMailAccount.h"
+#import "SOGoMailManager.h"
+#import "SOGoMailBodyPart.h"
+
+#import "SOGoMailObject.h"
@implementation SOGoMailObject
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'");
/* 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",
}
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];
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 */
/* 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";
}
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)
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;
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 */
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;
return nil;
if ((info = [self bodyStructure]) == nil) {
- [self errorWithFormat:@"got no body part structure!"];
+ [self errorWithFormat: @"got no body part structure!"];
return nil;
}
if ([_path length] == 0)
return info;
- _path = [_path componentsSeparatedByString:@"."];
+ _path = [_path componentsSeparatedByString: @"."];
}
/*
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];
}
/* 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;
/* 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
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];
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];
}
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.
/* 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 */
-- (BOOL)isDeletionAllowed {
- return [[self container] isDeleteAndExpungeAllowed];
+- (BOOL) isDeletionAllowed
+{
+ NSArray *parentAcl;
+ NSString *login;
+
+ login = [[context activeUser] login];
+ parentAcl = [[self container] aclsForUser: login];
+
+ 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 */
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
/* actions */
-- (id)GETAction:(id)_ctx {
+- (id) GETAction: (id) _ctx
+{
NSException *error;
WOResponse *r;
NSData *content;
/* 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 */
}
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
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
[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
error = [[self imap4Connection] markURLDeleted:[self imap4URL]];
return error;
}
-- (id)DELETEAction:(id)_ctx {
+
+- (id) DELETEAction: (id) _ctx
+{
NSException *error;
// TODO: ensure safe HTTP method
/* 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
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];
}
/* 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)];
return _value;
}
-- (NSString *)mailingListArchiveURL {
+- (NSString *) mailingListArchiveURL
+{
return [self scanListHeaderValue:
- [[self mailHeaders] objectForKey:@"list-archive"]
- forFieldWithPrefix:@"<http://"];
+ [[self mailHeaders] objectForKey: @"list-archive"]
+ forFieldWithPrefix: @"<http://"];
}
-- (NSString *)mailingListSubscribeURL {
+
+- (NSString *) mailingListSubscribeURL
+{
return [self scanListHeaderValue:
- [[self mailHeaders] objectForKey:@"list-subscribe"]
- forFieldWithPrefix:@"<http://"];
+ [[self mailHeaders] objectForKey: @"list-subscribe"]
+ forFieldWithPrefix: @"<http://"];
}
-- (NSString *)mailingListUnsubscribeURL {
+
+- (NSString *) mailingListUnsubscribeURL
+{
return [self scanListHeaderValue:
- [[self mailHeaders] objectForKey:@"list-unsubscribe"]
- forFieldWithPrefix:@"<http://"];
+ [[self mailHeaders] objectForKey: @"list-unsubscribe"]
+ forFieldWithPrefix: @"<http://"];
}
/* etag support */
-- (id)davEntityTag {
+- (id) davEntityTag
+{
/*
Note: There is one thing which *can* change for an existing message,
those are the IMAP4 flags (and annotations, which we do not use).
*/
return mailETag;
}
-- (int)zlGenerationCount {
+
+- (int) zlGenerationCount
+{
return 0; /* mails never change */
}
/* Outlook mail tagging */
-- (NSString *)outlookMessageClass {
+- (NSString *) outlookMessageClass
+{
NSString *type;
- if ((type = [[self mailHeaders] objectForKey:@"x-kolab-type"]) != nil) {
- if ([type isEqualToString:@"application/x-vnd.kolab.contact"])
+ if ((type = [[self mailHeaders] objectForKey: @"x-kolab-type"]) != nil) {
+ if ([type isEqualToString: @"application/x-vnd.kolab.contact"])
return @"IPM.Contact";
- if ([type isEqualToString:@"application/x-vnd.kolab.task"])
+ if ([type isEqualToString: @"application/x-vnd.kolab.task"])
return @"IPM.Task";
- if ([type isEqualToString:@"application/x-vnd.kolab.event"])
+ if ([type isEqualToString: @"application/x-vnd.kolab.event"])
return @"IPM.Appointment";
- if ([type isEqualToString:@"application/x-vnd.kolab.note"])
+ if ([type isEqualToString: @"application/x-vnd.kolab.note"])
return @"IPM.Note";
- if ([type isEqualToString:@"application/x-vnd.kolab.journal"])
+ if ([type isEqualToString: @"application/x-vnd.kolab.journal"])
return @"IPM.Journal";
}
return @"IPM.Message"; /* email, default class */
}
+- (NSArray *) aclsForUser: (NSString *) uid
+{
+ return [container aclsForUser: uid];
+}
+
/* debugging */
-- (BOOL)isDebuggingEnabled {
+- (BOOL) isDebuggingEnabled
+{
return debugOn;
}