Index: sope-mime/NGImap4/NGImap4Client.h =================================================================== --- sope-mime/NGImap4/NGImap4Client.h (revision 1626) +++ sope-mime/NGImap4/NGImap4Client.h (working copy) @@ -62,6 +62,8 @@ NGImap4ResponseNormalizer *normer; NSMutableArray *responseReceiver; + BOOL loggedIn; + BOOL isLogin; unsigned tagId; @@ -120,6 +122,7 @@ - (NSDictionary *)list:(NSString *)_folder pattern:(NSString *)_pattern; - (NSDictionary *)lsub:(NSString *)_folder pattern:(NSString *)_pattern; - (NSDictionary *)select:(NSString *)_folder; +- (NSDictionary *)unselect; - (NSDictionary *)status:(NSString *)_folder flags:(NSArray *)_flags; - (NSDictionary *)rename:(NSString *)_folder to:(NSString *)_newName; - (NSDictionary *)delete:(NSString *)_folder; @@ -138,7 +141,7 @@ flags:(NSArray *)_flags; - (NSDictionary *)storeFrom:(unsigned)_from to:(unsigned)_to add:(NSNumber *)_add flags:(NSArray *)_flags; -- (NSDictionary *)storeFlags:(NSArray *)_flags forMSNs:(id)_msns +- (NSDictionary *)storeFlags:(NSArray *)_flags forUIDs:(id)_uids addOrRemove:(BOOL)_flag; - (NSDictionary *)copyUid:(unsigned)_uid toFolder:(NSString *)_folder; Index: sope-mime/NGImap4/NGImap4Client.m =================================================================== --- sope-mime/NGImap4/NGImap4Client.m (revision 1626) +++ sope-mime/NGImap4/NGImap4Client.m (working copy) @@ -24,6 +24,8 @@ #include "NGImap4Client.h" #include "NGImap4Context.h" #include "NGImap4Support.h" +#include "NGImap4Envelope.h" +#include "NGImap4EnvelopeAddress.h" #include "NGImap4Functions.h" #include "NGImap4ResponseParser.h" #include "NGImap4ResponseNormalizer.h" @@ -53,17 +55,17 @@ @end /* NGImap4Client(ConnectionRegistration); */ -#if GNUSTEP_BASE_LIBRARY -/* FIXME: TODO: move someplace better (hh: NGExtensions...) */ -@implementation NSException(setUserInfo) +// #if GNUSTEP_BASE_LIBRARY +// /* FIXME: TODO: move someplace better (hh: NGExtensions...) */ +// @implementation NSException(setUserInfo) -- (id)setUserInfo:(NSDictionary *)_userInfo { - ASSIGN(self->_e_info, _userInfo); - return self; -} +// - (id)setUserInfo:(NSDictionary *)_userInfo { +// ASSIGN(self->_e_info, _userInfo); +// return self; +// } -@end /* NSException(setUserInfo) */ -#endif +// @end /* NSException(setUserInfo) */ +// #endif @interface NGImap4Client(Private) @@ -84,6 +86,8 @@ - (NSDictionary *)login; +- (NSDictionary *) _sopeSORT: (id)_sortSpec qualifier:(EOQualifier *)_qual encoding:(NSString *)_encoding; + @end /* @@ -195,11 +199,14 @@ self->debug = ImapDebugEnabled; self->responseReceiver = [[NSMutableArray alloc] initWithCapacity:128]; self->normer = [[NGImap4ResponseNormalizer alloc] initWithClient:self]; + self->loggedIn = NO; + self->context = nil; } return self; } - (void)dealloc { + if (self->loggedIn) [self logout]; [self removeFromConnectionRegister]; [self->normer release]; [self->text release]; @@ -457,8 +464,8 @@ - (void)reconnect { if ([self->context lastException] != nil) return; - - [self closeConnection]; + + [self closeConnection]; self->tagId = 0; [self openConnection]; @@ -481,6 +488,7 @@ */ NGHashMap *map; NSString *s, *log; + NSDictionary *response; if (self->isLogin ) return nil; @@ -499,7 +507,11 @@ self->isLogin = NO; - return [self->normer normalizeResponse:map]; + response = [self->normer normalizeResponse:map]; + + self->loggedIn = [[response valueForKey:@"result"] boolValue]; + + return response; } - (NSDictionary *)logout { @@ -508,6 +520,8 @@ map = [self processCommand:@"logout"]; [self closeConnection]; + [self->selectedFolder release]; self->selectedFolder = nil; + self->loggedIn = NO; return [self->normer normalizeResponse:map]; } @@ -618,23 +632,24 @@ 'RawResponse' - the raw IMAP4 response */ NSString *s; - id tmp; - - tmp = self->selectedFolder; // remember ptr to old folder name - + if (![_folder isNotEmpty]) return nil; if ((_folder = [self _folder2ImapFolder:_folder]) == nil) return nil; + [self->selectedFolder release]; self->selectedFolder = [_folder copy]; - - [tmp release]; tmp = nil; // release old folder name s = [NSString stringWithFormat:@"select \"%@\"", self->selectedFolder]; return [self->normer normalizeSelectResponse:[self processCommand:s]]; } +- (NSDictionary *)unselect { + [self->selectedFolder release]; self->selectedFolder = nil; + return [self->normer normalizeResponse:[self processCommand:@"unselect"]]; +} + - (NSDictionary *)status:(NSString *)_folder flags:(NSArray *)_flags { NSString *cmd; @@ -820,23 +835,23 @@ return [self->normer normalizeResponse:[self processCommand:cmd]]; } -- (NSDictionary *)storeFlags:(NSArray *)_flags forMSNs:(id)_msns +- (NSDictionary *)storeFlags:(NSArray *)_flags forUIDs:(id)_uids addOrRemove:(BOOL)_flag { NSString *cmd; NSString *flagstr; NSString *seqstr; - if ([_msns isKindOfClass:[NSArray class]]) { + if ([_uids isKindOfClass:[NSArray class]]) { // TODO: improve by using ranges, eg 1:5 instead of 1,2,3,4,5 - _msns = [_msns valueForKey:@"stringValue"]; - seqstr = [_msns componentsJoinedByString:@","]; + _uids = [_uids valueForKey:@"stringValue"]; + seqstr = [_uids componentsJoinedByString:@","]; } else - seqstr = [_msns stringValue]; + seqstr = [_uids stringValue]; flagstr = [_flags2ImapFlags(self, _flags) componentsJoinedByString:@" "]; - cmd = [NSString stringWithFormat:@"store %@ %cFLAGS (%@)", + cmd = [NSString stringWithFormat:@"UID STORE %@ %cFLAGS (%@)", seqstr, _flag ? '+' : '-', flagstr]; return [self->normer normalizeResponse:[self processCommand:cmd]]; @@ -967,11 +982,12 @@ descr = @"Could not process qualifier for imap search "; descr = [descr stringByAppendingString:reason]; - exception = [[NGImap4SearchException alloc] initWithFormat:@"%@", descr]; ui = [NSDictionary dictionaryWithObject:_q forKey:@"qualifier"]; - [exception setUserInfo:ui]; + exception + = [NGImap4SearchException exceptionWithName: @"NGImap4SearchException" + reason: descr + userInfo: ui]; [self->context setLastException:exception]; - [exception release]; } - (NSString *)_searchExprForQual:(EOQualifier *)_qualifier { @@ -1093,7 +1109,18 @@ Eg: UID SORT ( DATE REVERSE SUBJECT ) UTF-8 TODO */ NSString *tmp; + NSArray *capa; + // We first check to see if our server supports IMAP SORT. If not + // we'll sort ourself the results. + capa = [[self capability] objectForKey: @"capability"]; + + if ([capa indexOfObject: @"sort"] == NSNotFound) + { + return [self _sopeSORT: _sortSpec qualifier: _qual encoding: _encoding]; + } + + if ([_sortSpec isKindOfClass:[NSArray class]]) tmp = [self _generateIMAP4SortOrderings:_sortSpec]; else if ([_sortSpec isKindOfClass:[EOSortOrdering class]]) @@ -1107,9 +1134,10 @@ tmp = @"DATE"; } + return [self primarySort:tmp - qualifierString:[self _searchExprForQual:_qual] - encoding:_encoding]; + qualifierString:[self _searchExprForQual:_qual] + encoding:_encoding]; } - (NSDictionary *)sort:(NSArray *)_sortOrderings qualifier:(EOQualifier *)_qual @@ -1130,7 +1158,7 @@ return nil; } - s = [@"search" stringByAppendingString:s]; + s = [@"UID SEARCH" stringByAppendingString:s]; return [self->normer normalizeSearchResponse:[self processCommand:s]]; } @@ -1193,6 +1221,79 @@ /* Private Methods */ +- (NSDictionary *) _sopeSORT: (id)_sortSpec qualifier:(EOQualifier *)_qual encoding:(NSString *)_encoding { + + NSMutableDictionary *result; + NSDictionary *d; + + result = [[[NSMutableDictionary alloc] init] autorelease]; + [result setObject: [NSNumber numberWithBool: NO] forKey: @"result"]; + + // _sortSpec: [REVERSE] {DATE,FROM,SUBJECT} + d = [self searchWithQualifier: _qual]; + + if ((d = [d objectForKey: @"RawResponse"])) + { + NSMutableDictionary *dict; + NSArray *a, *s_a; + BOOL b; + int i; + + a = [d objectForKey: @"search"]; + d = [self fetchUids: a parts: [NSArray arrayWithObject: @"ENVELOPE"]]; + a = [d objectForKey: @"fetch"]; + + + dict = [[[NSMutableDictionary alloc] init] autorelease]; + b = YES; + + for (i = 0; i < [a count]; i++) + { + NGImap4Envelope *env; + id o, uid, s; + + o = [a objectAtIndex: i]; + env = [o objectForKey: @"envelope"]; + uid = [o objectForKey: @"uid"]; + + if ([_sortSpec rangeOfString: @"SUBJECT"].length) + { + s = [env subject]; + if ([s isKindOfClass: [NSData class]]) + s = [[[NSString alloc] initWithData: s encoding: NSUTF8StringEncoding] autorelease]; + + [dict setObject: (s != nil ? s : (id)@"") forKey: uid]; + } + else if ([_sortSpec rangeOfString: @"FROM"].length) + { + s = [[[env from] lastObject] email]; + [dict setObject: (s != nil ? s : (id)@"") forKey: uid]; + } + else + { + [dict setObject: [env date] forKey: uid]; + b = NO; + } + } + + if (b) + s_a = [dict keysSortedByValueUsingSelector: @selector(caseInsensitiveCompare:)]; + else + s_a = [dict keysSortedByValueUsingSelector: @selector(compare:)]; + + if ([_sortSpec rangeOfString: @"REVERSE"].length) + { + s_a = [[s_a reverseObjectEnumerator] allObjects]; + } + + [result setObject: [NSNumber numberWithBool: YES] forKey: @"result"]; + [result setObject: s_a forKey: @"sort"]; + } + + return result; +} + + - (NSException *)_processCommandParserException:(NSException *)_exception { [self logWithFormat:@"ERROR(%s): catched IMAP4 parser exception %@: %@", __PRETTY_FUNCTION__, [_exception name], [_exception reason]]; Index: sope-mime/NGImap4/NGSieveClient.m =================================================================== --- sope-mime/NGImap4/NGSieveClient.m (revision 1626) +++ sope-mime/NGImap4/NGSieveClient.m (working copy) @@ -294,8 +294,8 @@ return con; } - logLen = [self->login cStringLength]; - bufLen = (logLen * 2) + [self->password cStringLength] +2; + logLen = [self->login lengthOfBytesUsingEncoding: NSUTF8StringEncoding]; + bufLen = (logLen * 2) + [self->password lengthOfBytesUsingEncoding: NSUTF8StringEncoding] +2; buf = calloc(bufLen + 2, sizeof(char)); @@ -306,8 +306,9 @@ password */ sprintf(buf, "%s %s %s", - [self->login cString], [self->login cString], - [self->password cString]); + [self->login cStringUsingEncoding:NSUTF8StringEncoding], + [self->login cStringUsingEncoding:NSUTF8StringEncoding], + [self->password cStringUsingEncoding:NSUTF8StringEncoding]); buf[logLen] = '\0'; buf[logLen * 2 + 1] = '\0'; @@ -656,7 +657,7 @@ fputc('\n', stderr); } else - fprintf(stderr, "C: %s\n", [_txt cString]); + fprintf(stderr, "C: %s\n", [_txt cStringUsingEncoding:NSUTF8StringEncoding]); } /* write */ Index: sope-mime/NGImap4/NGImap4Connection.h =================================================================== --- sope-mime/NGImap4/NGImap4Connection.h (revision 1626) +++ sope-mime/NGImap4/NGImap4Connection.h (working copy) @@ -89,6 +89,7 @@ - (NSArray *)subfoldersForURL:(NSURL *)_url; - (NSArray *)allFoldersForURL:(NSURL *)_url; +- (BOOL)selectFolder:(id)_url; /* message operations */ Index: sope-mime/NGImap4/NGImap4Connection.m =================================================================== --- sope-mime/NGImap4/NGImap4Connection.m (revision 1626) +++ sope-mime/NGImap4/NGImap4Connection.m (working copy) @@ -381,7 +381,7 @@ if (debugCache) [self logWithFormat:@" no folders cached yet .."]; - result = [[self client] list:(onlyFetchInbox ? @"INBOX" : @"*") + result = [[self client] list:(onlyFetchInbox ? @"INBOX" : @"") pattern:@"*"]; if (![[result valueForKey:@"result"] boolValue]) { [self errorWithFormat:@"Could not list mailbox hierarchy!"]; @@ -646,7 +646,7 @@ /* store flags */ - result = [[self client] storeFlags:_f forMSNs:result addOrRemove:YES]; + result = [[self client] storeFlags:_f forUIDs:result addOrRemove:YES]; if (![[result valueForKey:@"result"] boolValue]) { return [self errorForResult:result text:@"Failed to change flags of IMAP4 message"]; @@ -736,7 +736,7 @@ /* managing folders */ - (BOOL)doesMailboxExistAtURL:(NSURL *)_url { - NSString *folderName; + NSString *folderName, *previousFolderName; id result; /* check in hierarchy cache */ @@ -760,9 +760,16 @@ // TODO: we should probably just fetch the whole hierarchy? folderName = [self imap4FolderNameForURL:_url]; - result = [[self client] select:folderName]; - if (![[result valueForKey:@"result"] boolValue]) - return NO; + previousFolderName = [[self client] selectedFolderName]; + if (![previousFolderName isEqualToString: folderName]) { + result = [[self client] select:folderName]; + if (previousFolderName) + [[self client] select:previousFolderName]; + else + [[self client] unselect]; + if (![[result valueForKey:@"result"] boolValue]) + return NO; + } return YES; } Index: sope-mime/NGImap4/NGImap4ResponseNormalizer.m =================================================================== --- sope-mime/NGImap4/NGImap4ResponseNormalizer.m (revision 1626) +++ sope-mime/NGImap4/NGImap4ResponseNormalizer.m (working copy) @@ -292,7 +292,7 @@ /* filter for fetch response fetch : NSArray (fetch responses) - 'header' - RFC822.HEADER + 'header' - RFC822.HEADER and BODY[HEADER.FIELDS (...)] 'text' - RFC822.TEXT 'size' - SIZE 'flags' - FLAGS @@ -336,7 +336,12 @@ switch (c) { case 'b': /* Note: we check for _prefix_! eg body[1] is valid too */ - if (klen > 3 && [key hasPrefix:@"body"]) { + if (klen > 17 && [key hasPrefix:@"body[header.fields"]) { + keys[count] = @"header"; + values[count] = objForKey(obj, @selector(objectForKey:), key); + count++; + } + else if (klen > 3 && [key hasPrefix:@"body"]) { keys[count] = @"body"; values[count] = objForKey(obj, @selector(objectForKey:), key); count++; @@ -648,14 +653,13 @@ enumerator = [_flags objectEnumerator]; cnt = 0; while ((obj = [enumerator nextObject])) { - if (![obj isNotEmpty]) - continue; - - if (![[obj substringToIndex:1] isEqualToString:@"\\"]) - continue; - - objs[cnt] = [obj substringFromIndex:1]; - cnt++; + if ([obj isNotEmpty]) { + if ([obj hasPrefix:@"\\"]) + objs[cnt] = [obj substringFromIndex:1]; + else + objs[cnt] = obj; + cnt++; + } } result = [NSArray arrayWithObjects:objs count:cnt]; if (objs) free(objs); Index: sope-mime/NGImap4/NGImap4ResponseParser.m =================================================================== --- sope-mime/NGImap4/NGImap4ResponseParser.m (revision 1626) +++ sope-mime/NGImap4/NGImap4ResponseParser.m (working copy) @@ -31,6 +31,7 @@ @interface NGImap4ResponseParser(ParsingPrivates) - (BOOL)_parseNumberUntaggedResponse:(NGMutableHashMap *)result_; - (NSDictionary *)_parseBodyContent; +- (NSData *) _parseBodyHeaderFields; - (NSData *)_parseData; @@ -84,6 +85,8 @@ static NSDictionary *_parseMultipartBody(NGImap4ResponseParser *self, BOOL isBodyStructure); +static NSArray *_parseLanguages(); + static NSString *_parseBodyString(NGImap4ResponseParser *self, BOOL _convertString); static NSString *_parseBodyDecodeString(NGImap4ResponseParser *self, @@ -111,6 +114,7 @@ static NSNumber *_parseUnsigned(NGImap4ResponseParser *self); static NSString *_parseUntil(NGImap4ResponseParser *self, char _c); static NSString *_parseUntil2(NGImap4ResponseParser *self, char _c1, char _c2); +static BOOL _endsWithCQuote(NSString *_string); static __inline__ NSException *_consumeIfMatch (NGImap4ResponseParser *self, unsigned char _m); @@ -488,6 +492,50 @@ return [self _parseDataIntoRAM:size]; } +/* + Similair to _parseData but used to parse something like this : + + BODY[HEADER.FIELDS (X-PRIORITY)] {17} + X-Priority: 1 + + ) + + Headers are returned as data, as is. +*/ +- (NSData *) _parseBodyHeaderFields +{ + NSData *result; + unsigned size; + NSNumber *sizeNum; + + /* we skip until we're ready to parse {length} */ + _parseUntil(self, '{'); + + result = nil; + + if ((sizeNum = _parseUnsigned(self)) == nil) { + NSException *e; + + e = [[NGImap4ParserException alloc] + initWithFormat:@"expect a number between {}"]; + [self setLastException:[e autorelease]]; + return nil; + } + _consumeIfMatch(self, '}'); + _consumeIfMatch(self, '\n'); + + if ((size = [sizeNum intValue]) == 0) { + [self logWithFormat:@"ERROR(%s): got content size '0'!", + __PRETTY_FUNCTION__]; + return nil; + } + + if (UseMemoryMappedData && (size > Imap4MMDataBoundary)) + return [self _parseDataToFile:size]; + + return [self _parseDataIntoRAM:size]; +} + static int _parseTaggedResponse(NGImap4ResponseParser *self, NGMutableHashMap *result_) { @@ -648,13 +696,148 @@ [result_ addObject:_parseUntil(self, '\n') forKey:@"description"]; } +static inline BOOL +_isReallyQuoted(const unichar *startC, const unichar *endC) { + const unichar *currentChar; + unsigned int questionMarks; + BOOL result; + + result = YES; + + if (startC < (endC - 4) + && (*startC == '=' + && *(startC + 1) == '?') + && (*(endC - 2) == '?' + && *(endC - 1) == '=')) { + currentChar = startC; + while (result && (currentChar < endC)) { + if (*currentChar == ' ' + || *currentChar == '\t') + result = NO; + else { + if (*currentChar == '?') { + questionMarks++; + if (questionMarks == 2) { + result = ((currentChar + 1) < endC + && (*(currentChar + 1) == 'q' + || *(currentChar + 1) == 'Q' + || *(currentChar + 1) == 'b' + || *(currentChar + 1) == 'B')); + if (result) + currentChar++; + } + } + currentChar++; + } + } + + result = (result && questionMarks == 4); + } + else + result = NO; + + return result; +} + +static inline void +_purifyQuotedString(NSMutableString *quotedString) { + unichar *currentChar, *qString, *maxC, *startC; + unsigned int max; + BOOL possiblyQuoted, skipSpaces; + NSMutableString *newString; + + newString = [NSMutableString string]; + + max = [quotedString length]; + qString = malloc (sizeof (unichar) * max); + [quotedString getCharacters: qString]; + currentChar = qString; + startC = qString; + maxC = qString + max; + + possiblyQuoted = NO; + skipSpaces = NO; + + while (currentChar < maxC) { + if (skipSpaces) { + while (currentChar < maxC + && (*currentChar == ' ' + || *currentChar == '\t')) + currentChar++; + startC = currentChar; + skipSpaces = NO; + } + else if (possiblyQuoted) { + while (currentChar < maxC + && *currentChar != ' ' + && *currentChar != '\t') + currentChar++; + if (startC < currentChar) { + [newString appendString: [NSString stringWithCharacters: startC + length: (currentChar - startC)]]; + skipSpaces = _isReallyQuoted(startC, currentChar); + } + possiblyQuoted = NO; + startC = currentChar; + } + else { + while (currentChar < maxC + && *currentChar != '=') + currentChar++; + if ((currentChar + 1 < maxC) + && *(currentChar + 1) == '?') { + if (startC < currentChar) { + [newString appendString: [NSString stringWithCharacters: startC + length: (currentChar - startC)]]; + } + startC = currentChar; + currentChar += 2; + possiblyQuoted = YES; + } + else + currentChar++; + } + } + + if (startC + && startC < (currentChar - 1)) { + [newString appendString: [NSString stringWithCharacters: startC + length: (currentChar - startC - 1)]]; + } + + [quotedString setString: newString]; + free (qString); +} + - (NSString *)_parseQuotedString { + NSMutableString *quotedString; + NSString *tmpString; + BOOL stop; + /* parse a quoted string, eg '"' */ if (_la(self, 0) == '"') { _consume(self, 1); - return _parseUntil(self, '"'); + quotedString = [NSMutableString string]; + stop = NO; + while (!stop) { + tmpString = _parseUntil(self, '"'); + [quotedString appendString: tmpString]; + if(_endsWithCQuote(tmpString)) { + [quotedString deleteSuffix: @"\\"]; + [quotedString appendString: @"\""]; + } + else { + stop = YES; + } + } } - return nil; + else { + quotedString = nil; + } + + _purifyQuotedString(quotedString); + + return quotedString; } - (void)_consumeOptionalSpace { if (_la(self, 0) == ' ') _consume(self, 1); @@ -1090,6 +1273,8 @@ return @""; s = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + if (s == nil) + s = [[NSString alloc] initWithData:data encoding:NSISOLatin1StringEncoding]; if (s == nil) { [self logWithFormat: @"ERROR(%s): could not convert data (%d bytes) into string.", @@ -1185,7 +1370,7 @@ route = [self _parseQuotedStringOrNIL]; [self _consumeOptionalSpace]; mailbox = [self _parseQuotedStringOrNIL]; [self _consumeOptionalSpace]; host = [self _parseQuotedStringOrNIL]; [self _consumeOptionalSpace]; - + if (_la(self, 0) != ')') { [self logWithFormat:@"WARNING: IMAP4 envelope " @"address not properly closed (c0=%c,c1=%c): %@", @@ -1197,6 +1382,7 @@ address = [[NGImap4EnvelopeAddress alloc] initWithPersonalName:pname sourceRoute:route mailbox:mailbox host:host]; + return address; } @@ -1382,7 +1568,15 @@ #if 0 [self logWithFormat:@"PARSE KEY: %@", key]; #endif - if ([key hasPrefix:@"body["]) { + if ([key hasPrefix:@"body[header.fields"]) { + NSData *content; + + if ((content = [self _parseBodyHeaderFields]) != nil) + [fetch setObject:content forKey:key]; + else + [self logWithFormat:@"ERROR: got no body content for key: '%@'",key]; + } + else if ([key hasPrefix:@"body["]) { NSDictionary *content; if ((content = [self _parseBodyContent]) != nil) @@ -1594,8 +1788,11 @@ if (_decode) data = [data decodeQuotedPrintableValueOfMIMEHeaderField:nil]; - return [[[StrClass alloc] initWithData:data encoding:encoding] - autorelease]; + if ([data isKindOfClass: [NSString class]]) + return (NSString *) data; + else + return [[[StrClass alloc] initWithData:data encoding:encoding] + autorelease]; } else { str = _parseUntil2(self, ' ', ')'); @@ -1620,13 +1817,35 @@ return str; } - static NSString *_parseBodyString(NGImap4ResponseParser *self, BOOL _convertString) { return _parseBodyDecodeString(self, _convertString, NO /* no decode */); } +static NSArray *_parseLanguages(NGImap4ResponseParser *self) { + NSMutableArray *languages; + NSString *language; + + languages = [NSMutableArray array]; + if (_la(self, 0) == '(') { + while (_la(self, 0) != ')') { + _consume(self,1); + language = _parseBodyString(self, YES); + if ([language length]) + [languages addObject: language]; + } + _consume(self,1); + } + else { + language = _parseBodyString(self, YES); + if ([language length]) + [languages addObject: language]; + } + + return languages; +} + static NSDictionary *_parseBodyParameterList(NGImap4ResponseParser *self) { NSMutableDictionary *list; @@ -1646,7 +1865,7 @@ _consumeIfMatch(self, ' '); value = _parseBodyDecodeString(self, YES, YES); - [list setObject:value forKey:[key lowercaseString]]; + if (value) [list setObject:value forKey:[key lowercaseString]]; } _consumeIfMatch(self, ')'); } @@ -1731,13 +1950,14 @@ static NSDictionary *_parseSingleBody(NGImap4ResponseParser *self, BOOL isBodyStructure) { NSString *type, *subtype, *bodyId, *description, - *encoding, *bodysize; + *result, *encoding, *bodysize; NSDictionary *parameterList; NSMutableDictionary *dict; + NSArray *languages; type = [_parseBodyString(self, YES) lowercaseString]; _consumeIfMatch(self, ' '); - subtype = _parseBodyString(self, YES); + subtype = [_parseBodyString(self, YES) lowercaseString]; _consumeIfMatch(self, ' '); parameterList = _parseBodyParameterList(self); _consumeIfMatch(self, ' '); @@ -1762,13 +1982,18 @@ _consumeIfMatch(self, ' '); [dict setObject:_parseBodyString(self, YES) forKey:@"lines"]; } - else if ([type isEqualToString:@"message"]) { + else if ([type isEqualToString:@"message"] + && [subtype isEqualToString:@"rfc822"]) { if (_la(self, 0) != ')') { _consumeIfMatch(self, ' '); _consumeIfMatch(self, '('); - [dict setObject:_parseBodyString(self, YES) forKey:@"date"]; + result = _parseBodyString(self, YES); + if (result == nil) result = @""; + [dict setObject:result forKey:@"date"]; _consumeIfMatch(self, ' '); - [dict setObject:_parseBodyString(self, YES) forKey:@"subject"]; + result = _parseBodyString(self, YES); + if (result == nil) result = @""; + [dict setObject:result forKey:@"subject"]; _consumeIfMatch(self, ' '); [dict setObject:_parseParenthesizedAddressList(self) forKey:@"from"]; _consumeIfMatch(self, ' '); @@ -1783,14 +2008,20 @@ _consumeIfMatch(self, ' '); [dict setObject:_parseParenthesizedAddressList(self) forKey:@"bcc"]; _consumeIfMatch(self, ' '); - [dict setObject:_parseBodyString(self, YES) forKey:@"in-reply-to"]; + result = _parseBodyString(self, YES); + if (result == nil) result = @""; + [dict setObject:result forKey:@"in-reply-to"]; _consumeIfMatch(self, ' '); - [dict setObject:_parseBodyString(self, YES) forKey:@"messageId"]; + result = _parseBodyString(self, YES); + if (result == nil) result = @""; + [dict setObject:result forKey:@"messageId"]; _consumeIfMatch(self, ')'); _consumeIfMatch(self, ' '); [dict setObject:_parseBody(self, isBodyStructure) forKey:@"body"]; _consumeIfMatch(self, ' '); - [dict setObject:_parseBodyString(self, YES) forKey:@"bodyLines"]; + result = _parseBodyString(self, YES); + if (result == nil) result = @""; + [dict setObject:result forKey:@"bodyLines"]; } } @@ -1805,14 +2036,9 @@ forKey: @"disposition"]; if (_la(self, 0) != ')') { _consume(self,1); - if (_la(self, 0) == '(') { - [dict setObject: _parseBodyParameterList(self) - forKey: @"language"]; - } - else { - [dict setObject: _parseBodyString(self, YES) - forKey: @"language"]; - } + languages = _parseLanguages(self); + if ([languages count]) + [dict setObject: languages forKey: @"languages"]; if (_la(self, 0) != ')') { _consume(self,1); [dict setObject: _parseBodyString(self, YES) @@ -1829,6 +2055,7 @@ static NSDictionary *_parseMultipartBody(NGImap4ResponseParser *self, BOOL isBodyStructure) { NSMutableArray *parts; + NSArray *languages; NSString *kind; NSMutableDictionary *dict; @@ -1854,14 +2081,9 @@ forKey: @"disposition"]; if (_la(self, 0) != ')') { _consume(self,1); - if (_la(self, 0) == '(') { - [dict setObject: _parseBodyParameterList(self) - forKey: @"language"]; - } - else { - [dict setObject: _parseBodyString(self, YES) - forKey: @"language"]; - } + languages = _parseLanguages(self); + if ([languages count]) + [dict setObject: languages forKey: @"languages"]; if (_la(self, 0) != ')') { _consume(self,1); [dict setObject: _parseBodyString(self, YES) @@ -2170,6 +2392,21 @@ } } +static BOOL _endsWithCQuote(NSString *_string){ + unsigned int quoteSlashes; + int pos; + + quoteSlashes = 0; + pos = [_string length] - 1; + while (pos > -1 + && [_string characterAtIndex: pos] == '\\') { + quoteSlashes++; + pos--; + } + + return ((quoteSlashes % 2) == 1); +} + - (NSException *)exceptionForFailedMatch:(unsigned char)_match got:(unsigned char)_avail { @@ -2225,9 +2462,9 @@ [s release]; if (c == '\n') { - if ([self->serverResponseDebug cStringLength] > 2) { + if ([self->serverResponseDebug lengthOfBytesUsingEncoding:NSISOLatin1StringEncoding] > 2) { fprintf(stderr, "S[%p]: %s", self, - [self->serverResponseDebug cString]); + [self->serverResponseDebug cStringUsingEncoding:NSISOLatin1StringEncoding]); } [self->serverResponseDebug release]; self->serverResponseDebug = Index: sope-mime/NGImap4/ChangeLog =================================================================== --- sope-mime/NGImap4/ChangeLog (revision 1626) +++ sope-mime/NGImap4/ChangeLog (working copy) @@ -1,3 +1,23 @@ +2008-09-22 Wolfgang Sourdeau + + * NGImap4Connection.m ([NGImap -doesMailboxExistAtURL:]): restore + the previously selected folder state. + +2008-09-19 Wolfgang Sourdeau + + * NGImap4Client.m ([NGImap -select:]): simplified method by + removing the need for storing the previous folder before releasing + it. This strangely seems to fix a crash with gnustep 1.14. + +2008-09-01 Ludovic Marcotte + + * NGImap4ConnectionManager.m: implemented _garbageCollect. + +2008-08-28 Wolfgang Sourdeau + + * NGImap4Client.m ([NGImap -unselect]): new method to send + "UNSELECT" to the imap server. + 2007-08-24 Wolfgang Sourdeau * NGImap4Connection.m: some fix for folders ending with a slash (OGo Index: sope-mime/NGImap4/NGImap4ConnectionManager.m =================================================================== --- sope-mime/NGImap4/NGImap4ConnectionManager.m (revision 1626) +++ sope-mime/NGImap4/NGImap4ConnectionManager.m (working copy) @@ -38,6 +38,9 @@ debugCache = [ud boolForKey:@"NGImap4EnableIMAP4CacheDebug"]; poolingOff = [ud boolForKey:@"NGImap4DisableIMAP4Pooling"]; + if ([ud objectForKey:@"NGImap4PoolingCleanupInterval"]) + PoolScanInterval = [[ud objectForKey:@"NGImap4PoolingCleanupInterval"] doubleValue]; + if (debugOn) NSLog(@"Note: NGImap4EnableIMAP4Debug is enabled!"); if (poolingOff) NSLog(@"WARNING: IMAP4 connection pooling is disabled!"); } @@ -53,18 +56,17 @@ if ((self = [super init])) { if (!poolingOff) { self->urlToEntry = [[NSMutableDictionary alloc] initWithCapacity:256]; + self->gcTimer = [[NSTimer scheduledTimerWithTimeInterval: + PoolScanInterval + target:self selector:@selector(_garbageCollect:) + userInfo:nil repeats:YES] retain]; } - - self->gcTimer = [[NSTimer scheduledTimerWithTimeInterval: - PoolScanInterval - target:self selector:@selector(_garbageCollect:) - userInfo:nil repeats:YES] retain]; } return self; } - (void)dealloc { - if (self->gcTimer) [self->gcTimer invalidate]; + [self->gcTimer invalidate]; [self->urlToEntry release]; [self->gcTimer release]; [super dealloc]; @@ -91,6 +93,25 @@ - (void)_garbageCollect:(NSTimer *)_timer { // TODO: scan for old IMAP4 channels + NGImap4Connection *entry; + NSDate *now; + NSArray *a; + int i; + + a = [self->urlToEntry allKeys]; + now = [NSDate date]; + + for (i = 0; i < [a count]; i++) + { + entry = [self->urlToEntry objectForKey: [a objectAtIndex: i]]; + + if ([now timeIntervalSinceDate: [entry creationTime]] > PoolScanInterval) + { + [[entry client] logout]; + [self->urlToEntry removeObjectForKey: [a objectAtIndex: i]]; + } + } + [self debugWithFormat:@"should collect IMAP4 channels (%d active)", [self->urlToEntry count]]; } @@ -105,34 +126,42 @@ NGImap4Connection *entry; NGImap4Client *client; + if (poolingOff) { + client = [self imap4ClientForURL:_url password:_p]; + entry = [[NGImap4Connection alloc] initWithClient:client + password:_p]; + return [entry autorelease]; + } + else { /* check cache */ - if ((entry = [self entryForURL:_url]) != nil) { - if ([entry isValidPassword:_p]) { + if ((entry = [self entryForURL:_url]) != nil) { + if ([entry isValidPassword:_p]) { + if (debugCache) + [self logWithFormat:@"valid password, reusing cache entry ..."]; + return entry; + } + + /* different password, password could have changed! */ if (debugCache) - [self logWithFormat:@"valid password, reusing cache entry ..."]; - return entry; + [self logWithFormat:@"different password than cached entry: %@", _url]; + entry = nil; } - - /* different password, password could have changed! */ - if (debugCache) - [self logWithFormat:@"different password than cached entry: %@", _url]; - entry = nil; - } - else - [self debugWithFormat:@"no connection cached yet for url: %@", _url]; + else + [self debugWithFormat:@"no connection cached yet for url: %@", _url]; - /* try to login */ + /* try to login */ - client = [entry isValidPassword:_p] - ? [entry client] - : [self imap4ClientForURL:_url password:_p]; + client = [entry isValidPassword:_p] + ? [entry client] + : [self imap4ClientForURL:_url password:_p]; + + if (client == nil) + return nil; - if (client == nil) - return nil; - /* sideeffect of -imap4ClientForURL:password: is to create a cache entry */ - return [self entryForURL:_url]; + return [self entryForURL:_url]; + } } /* client object */ Index: sope-mime/NGImap4/NSString+Imap4.m =================================================================== --- sope-mime/NGImap4/NSString+Imap4.m (revision 1626) +++ sope-mime/NGImap4/NSString+Imap4.m (working copy) @@ -20,11 +20,56 @@ 02111-1307, USA. */ +#import + #include #include "imCommon.h" /* TODO: NOT UNICODE SAFE (uses cString) */ +#ifndef __APPLE__ + +@interface NSMutableData (PantomimeExtensions) + +- (void) appendCFormat: (NSString *) theFormat, ...; +- (void) appendCString: (const char *) theCString; + +@end + +@implementation NSMutableData (PantomimeExtensions) + +- (void) appendCFormat: (NSString *) theFormat, ... +{ + NSString *aString; + va_list args; + + va_start(args, theFormat); + aString = [[NSString alloc] initWithFormat: theFormat arguments: args]; + va_end(args); + + // We allow lossy conversion to not lose any information / raise an exception + [self appendData: [aString dataUsingEncoding: NSASCIIStringEncoding allowLossyConversion: YES]]; + + RELEASE(aString); +} + + +// +// +// +- (void) appendCString: (const char *) theCString +{ + [self appendBytes: theCString length: strlen(theCString)]; +} + +@end + +#endif + +@implementation NSString(Imap4) + +#if __APPLE__ + static void _encodeToModifiedUTF7(unsigned char *_buf, int encLen, unsigned char **result_, unsigned int *cntRes_); @@ -33,8 +78,6 @@ unsigned char **buffer_, int *bufLen_, int maxBuf); -@implementation NSString(Imap4) - - (NSString *)stringByEncodingImap4FolderName { // TBD: this is restricted to Latin1, should be fixed to UTF-8 /* dude.d& --> dude.d&- */ @@ -46,12 +89,15 @@ NSString *result = nil; NSData *data; - len = [self cStringLength]; - buf = calloc(len + 3, sizeof(char)); - res = calloc((len * 6) + 3, sizeof(char)); - buf[len] = '\0'; - res[len * 6] = '\0'; - [self getCString:(char *)buf]; + len = [self lengthOfBytesUsingEncoding: NSISOLatin1StringEncoding] + 1; + buf = calloc(len + 3, sizeof(char)); + res = calloc((len * 6) + 3, sizeof(char)); + [self getCString:(char *)buf maxLength: len + encoding: NSISOLatin1StringEncoding]; + buf[len-1] = 0; + buf[len] = 0; + buf[len+1] = 0; + buf[len+2] = 0; while (cnt < len) { int c = buf[cnt]; @@ -185,70 +231,6 @@ return [result autorelease]; } -- (NSString *)stringByEscapingImap4Password { - // TODO: perf - unichar *buffer; - unichar *chars; - unsigned len, i, j; - NSString *s; - - len = [self length]; - chars = calloc(len + 2, sizeof(unichar)); - [self getCharacters:chars]; - - buffer = calloc(len * 2 + 2, sizeof(unichar)); - buffer[len * 2] = '\0'; - - for (i = 0, j = 0; i < len; i++, j++) { - BOOL conv = NO; - - if (chars[i] <= 0x1F || chars[i] > 0x7F) { - conv = YES; - } - else switch (chars[i]) { - case '(': - case ')': - case '{': - case ' ': - case '%': - case '*': - case '"': - case '\\': - conv = YES; - break; - } - - if (conv) { - buffer[j] = '\\'; - j++; - } - buffer[j] = chars[i]; - } - if (chars != NULL) free(chars); chars = NULL; - - s = [NSString stringWithCharacters:buffer length:j]; - if (buffer != NULL) free(buffer); buffer = NULL; - return s; -} - -@end /* NSString(Imap4) */ - -static void writeChunk(int _c1, int _c2, int _c3, int _pads, - unsigned char **result_, - unsigned int *cntRes_); - -static int getChar(int _cnt, int *cnt_, unsigned char *_buf) { - int result; - - if ((_cnt % 2)) { - result = _buf[*cnt_]; - (*cnt_)++; - } - else { - result = 0; - } - return result; -} static void _encodeToModifiedUTF7(unsigned char *_buf, int encLen, unsigned char **result_, unsigned int *cntRes_) { @@ -276,58 +258,6 @@ } } -/* check metamail output for correctness */ - -static unsigned char basis_64[] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - -static void writeChunk(int c1, int c2, int c3, int pads, unsigned char **result_, - unsigned int *cntRes_) { - unsigned char c; - - c = basis_64[c1>>2]; - (*result_)[*cntRes_] = c; - (*cntRes_)++; - - c = basis_64[((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4)]; - - (*result_)[*cntRes_] = c; - (*cntRes_)++; - - - if (pads == 2) { - ; - } - else if (pads) { - c = basis_64[((c2 & 0xF) << 2) | ((c3 & 0xC0) >>6)]; - (*result_)[*cntRes_] = c; - (*cntRes_)++; - } - else { - c = basis_64[((c2 & 0xF) << 2) | ((c3 & 0xC0) >>6)]; - - (*result_)[*cntRes_] = c; - (*cntRes_)++; - - c = basis_64[c3 & 0x3F]; - (*result_)[*cntRes_] = c; - (*cntRes_)++; - } -} - -static char index_64[128] = { - -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, - -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, - -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63, - 52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1,-1,-1,-1, - -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14, - 15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1, - -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40, - 41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1 -}; - -#define char64(c) (((c) < 0 || (c) > 127) ? -1 : index_64[(c)]) - static int _decodeOfModifiedUTF7(unsigned char *_target, unsigned _targetLen, unsigned *usedBytes_ , unsigned char **buffer_, int *bufLen_, int maxBuf) @@ -430,3 +360,299 @@ } return 0; } + +static void writeChunk(int _c1, int _c2, int _c3, int _pads, + unsigned char **result_, + unsigned int *cntRes_); + +static int getChar(int _cnt, int *cnt_, unsigned char *_buf) { + int result; + + if ((_cnt % 2)) { + result = _buf[*cnt_]; + (*cnt_)++; + } + else { + result = 0; + } + return result; +} + +/* check metamail output for correctness */ + +static unsigned char basis_64[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +static void writeChunk(int c1, int c2, int c3, int pads, unsigned char **result_, + unsigned int *cntRes_) { + unsigned char c; + + c = basis_64[c1>>2]; + (*result_)[*cntRes_] = c; + (*cntRes_)++; + + c = basis_64[((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4)]; + + (*result_)[*cntRes_] = c; + (*cntRes_)++; + + + if (pads == 2) { + ; + } + else if (pads) { + c = basis_64[((c2 & 0xF) << 2) | ((c3 & 0xC0) >>6)]; + (*result_)[*cntRes_] = c; + (*cntRes_)++; + } + else { + c = basis_64[((c2 & 0xF) << 2) | ((c3 & 0xC0) >>6)]; + + (*result_)[*cntRes_] = c; + (*cntRes_)++; + + c = basis_64[c3 & 0x3F]; + (*result_)[*cntRes_] = c; + (*cntRes_)++; + } +} + +static char index_64[128] = { + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63, + 52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1,-1,-1,-1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14, + 15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1, + -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40, + 41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1 +}; + +#define char64(c) (((c) < 0 || (c) > 127) ? -1 : index_64[(c)]) + +#else // __APPLE__ + +#define IS_PRINTABLE(c) (isascii(c) && isprint(c)) + +- (NSString *)stringByEncodingImap4FolderName { + NSMutableData *aMutableData, *modifiedData; + NSString *aString; + + const char *b; + BOOL escaped; + unichar ch; + int i, len; + + // + // We UTF-7 encode _only_ the non-ASCII parts. + // + aMutableData = [[NSMutableData alloc] init]; + AUTORELEASE(aMutableData); + len = [self length]; + + for (i = 0; i < len; i++) + { + ch = [self characterAtIndex: i]; + + if (IS_PRINTABLE(ch)) + { + [aMutableData appendCFormat: @"%c", ch]; + } + else + { + int j; + + j = i+1; + // We got a non-ASCII character, let's get the substring and encode it using UTF-7. + while (j < len && !IS_PRINTABLE([self characterAtIndex: j])) + { + j++; + } + + // Get the substring. + [aMutableData appendData: [[self substringWithRange: NSMakeRange(i,j-i)] dataUsingEncoding: NSUTF7StringEncoding]]; + i = j-1; + } + } + + b = [aMutableData bytes]; + len = [aMutableData length]; + escaped = NO; + + // + // We replace: + // + // & -> &- + // + -> & + // +- -> + + // / -> , + // + // in order to produce our modified UTF-7 string. + // + modifiedData = [[NSMutableData alloc] init]; + AUTORELEASE(modifiedData); + + for (i = 0; i < len; i++, b++) + { + if (!escaped && *b == '&') + { + [modifiedData appendCString: "&-"]; + } + else if (!escaped && *b == '+') + { + if (*(b+1) == '-') + { + [modifiedData appendCString: "+"]; + } + else + { + [modifiedData appendCString: "&"]; + + // We enter the escaped mode. + escaped = YES; + } + } + else if (escaped && *b == '/') + { + [modifiedData appendCString: ","]; + } + else if (escaped && *b == '-') + { + [modifiedData appendCString: "-"]; + + // We leave the escaped mode. + escaped = NO; + } + else + { + [modifiedData appendCFormat: @"%c", *b]; + } + } + + // If we're still in the escaped mode we haven't added our trailing -, + // let's add it right now. + if (escaped) + { + [modifiedData appendCString: "-"]; + } + + aString = AUTORELEASE([[NSString alloc] initWithData: modifiedData encoding: NSASCIIStringEncoding]); + + return (aString != nil ? aString : self); +} + +// +// +// +- (NSString *)stringByDecodingImap4FolderName { + NSMutableData *aMutableData; + + BOOL escaped; + unichar ch; + int i, len; + + aMutableData = [[NSMutableData alloc] init]; + AUTORELEASE(aMutableData); + + len = [self length]; + escaped = NO; + + // + // We replace: + // + // & -> + + // &- -> & + // , -> / + // + // If we are in escaped mode. That is, between a &....- + // + for (i = 0; i < len; i++) + { + ch = [self characterAtIndex: i]; + + if (!escaped && ch == '&') + { + if ( (i+1) < len && [self characterAtIndex: (i+1)] != '-' ) + { + [aMutableData appendCString: "+"]; + + // We enter the escaped mode. + escaped = YES; + } + else + { + // We replace &- by & + [aMutableData appendCString: "&"]; + i++; + } + } + else if (escaped && ch == ',') + { + [aMutableData appendCString: "/"]; + } + else if (escaped && ch == '-') + { + [aMutableData appendCString: "-"]; + + // We leave the escaped mode. + escaped = NO; + } + else + { + [aMutableData appendCFormat: @"%c", ch]; + } + } + + return AUTORELEASE([[NSString alloc] initWithData: aMutableData encoding: NSUTF7StringEncoding]); +} + + +#endif // __APPLE__ + +- (NSString *)stringByEscapingImap4Password { + // TODO: perf + unichar *buffer; + unichar *chars; + unsigned len, i, j; + NSString *s; + + len = [self length]; + chars = calloc(len + 2, sizeof(unichar)); + [self getCharacters:chars]; + + buffer = calloc(len * 2 + 2, sizeof(unichar)); + buffer[len * 2] = '\0'; + + for (i = 0, j = 0; i < len; i++, j++) { + BOOL conv = NO; + + if (chars[i] <= 0x1F || chars[i] > 0x7F) { + conv = YES; + } + else switch (chars[i]) { + case '(': + case ')': + case '{': + case ' ': + case '%': + case '*': + case '"': + case '\\': + conv = YES; + break; + } + + if (conv) { + buffer[j] = '\\'; + j++; + } + buffer[j] = chars[i]; + } + if (chars != NULL) free(chars); chars = NULL; + + s = [NSString stringWithCharacters:buffer length:j]; + if (buffer != NULL) free(buffer); buffer = NULL; + return s; +} + +@end /* NSString(Imap4) */ Index: sope-mime/NGMail/NGSmtpClient.m =================================================================== --- sope-mime/NGMail/NGSmtpClient.m (revision 1626) +++ sope-mime/NGMail/NGSmtpClient.m (working copy) @@ -24,6 +24,82 @@ #include "NGSmtpReplyCodes.h" #include "common.h" +// +// Useful extension that comes from Pantomime which is also +// released under the LGPL. +// +@interface NSMutableData (DataCleanupExtension) + +- (NSRange) rangeOfCString: (const char *) theCString; +- (NSRange) rangeOfCString: (const char *) theCString + options: (unsigned int) theOptions + range: (NSRange) theRange; +@end + +@implementation NSMutableData (DataCleanupExtension) + +- (NSRange) rangeOfCString: (const char *) theCString +{ + return [self rangeOfCString: theCString + options: 0 + range: NSMakeRange(0,[self length])]; +} + +-(NSRange) rangeOfCString: (const char *) theCString + options: (unsigned int) theOptions + range: (NSRange) theRange +{ + const char *b, *bytes; + int i, len, slen; + + if (!theCString) + { + return NSMakeRange(NSNotFound,0); + } + + bytes = [self bytes]; + len = [self length]; + slen = strlen(theCString); + + b = bytes; + + if (len > theRange.location + theRange.length) + { + len = theRange.location + theRange.length; + } + + if (theOptions == NSCaseInsensitiveSearch) + { + i = theRange.location; + b += i; + + for (; i <= len-slen; i++, b++) + { + if (!strncasecmp(theCString,b,slen)) + { + return NSMakeRange(i,slen); + } + } + } + else + { + i = theRange.location; + b += i; + + for (; i <= len-slen; i++, b++) + { + if (!memcmp(theCString,b,slen)) + { + return NSMakeRange(i,slen); + } + } + } + + return NSMakeRange(NSNotFound,0); +} + +@end + @interface NGSmtpClient(PrivateMethods) - (void)_fetchExtensionInfo; @end @@ -429,7 +505,9 @@ - (BOOL)sendData:(NSData *)_data { NGSmtpResponse *reply = nil; - + NSMutableData *cleaned_data; + NSRange r1, r2; + [self requireState:NGSmtpState_TRANSACTION]; reply = [self sendCommand:@"DATA"]; @@ -441,11 +519,54 @@ } [self->text flush]; + cleaned_data = [NSMutableData dataWithData: _data]; + + // + // According to RFC 2821 section 4.5.2, we must check for the character + // sequence "."; any occurrence have its period duplicated + // to avoid data transparency. + // + // The following code was copied from Pantomime (and also the one + // that strips Bcc: headers from the mail's content) + // + r1 = [cleaned_data rangeOfCString: "\r\n."]; + + while (r1.location != NSNotFound) + { + [cleaned_data replaceBytesInRange: r1 withBytes: "\r\n.." length: 4]; + + r1 = [cleaned_data rangeOfCString: "\r\n." + options: 0 + range: NSMakeRange(NSMaxRange(r1)+1, [cleaned_data length]-NSMaxRange(r1)-1)]; + } + + // + // We now look for the Bcc: header. If it is present, we remove it. + // Some servers, like qmail, do not remove it automatically. + // + r1 = [cleaned_data rangeOfCString: "\r\n\r\n"]; + r1 = [cleaned_data rangeOfCString: "\r\nBcc: " + options: 0 + range: NSMakeRange(0,r1.location-1)]; + + if (r1.location != NSNotFound) + { + // We search for the first \r\n AFTER the Bcc: header and + // replace the whole thing with \r\n. + r2 = [cleaned_data rangeOfCString: "\r\n" + options: 0 + range: NSMakeRange(NSMaxRange(r1)+1,[cleaned_data length]-NSMaxRange(r1)-1)]; + [cleaned_data replaceBytesInRange: NSMakeRange(r1.location, NSMaxRange(r2)-r1.location) + withBytes: "\r\n" + length: 2]; + } + + if (self->isDebuggingEnabled) - [NGTextErr writeFormat:@"C: data(%i bytes) ..\n", [_data bytes]]; + [NGTextErr writeFormat:@"C: data(%i bytes) ..\n", [cleaned_data length]]; - [self->connection safeWriteBytes:[_data bytes] count:[_data length]]; - [self->connection safeWriteBytes:".\r\n" count:3]; + [self->connection safeWriteBytes:[cleaned_data bytes] count:[cleaned_data length]]; + [self->connection safeWriteBytes:"\r\n.\r\n" count:5]; [self->connection flush]; reply = [self receiveReply]; Index: sope-mime/NGMail/NGMailAddressParser.h =================================================================== --- sope-mime/NGMail/NGMailAddressParser.h (revision 1626) +++ sope-mime/NGMail/NGMailAddressParser.h (working copy) @@ -24,7 +24,9 @@ #import -@class NSData, NSString, NSArray; +#import + +@class NSData, NSArray; @class NGMailAddressList; /* @@ -34,16 +36,16 @@ @interface NGMailAddressParser : NSObject { @private - unsigned char *data; - int dataPos; - int errorPos; - int maxLength; + unichar *data; + int dataPos; + int errorPos; + int maxLength; } + (id)mailAddressParserWithString:(NSString *)_string; + (id)mailAddressParserWithData:(NSData *)_data; -+ (id)mailAddressParserWithCString:(char *)_cString; -- (id)initWithCString:(const unsigned char *)_cstr length:(int unsigned)_len; ++ (id)mailAddressParserWithCString:(const char *)_cString; +- (id)initWithString:(NSString *)_str; /* parsing */ Index: sope-mime/NGMail/NGMimeMessageGenerator.m =================================================================== --- sope-mime/NGMail/NGMimeMessageGenerator.m (revision 1626) +++ sope-mime/NGMail/NGMimeMessageGenerator.m (working copy) @@ -86,37 +86,40 @@ char *des = NULL; unsigned int cnt; BOOL doEnc; - NSString *str; +// NSString *str; // TODO: this s***s big time! +// NSLog (@"class: '%@'", NSStringFromClass ([_data class])); +// #if APPLE_Foundation_LIBRARY || NeXT_Foundation_LIBRARY +// str = [[NSString alloc] initWithData:_data +// encoding:NSISOLatin1StringEncoding]; +// str = [str autorelease]; + +// #else +// str = [[NSString alloc] initWithData:_data +// encoding:NSISOLatin9StringEncoding]; +// #endif +// bytes = [str cString]; +// length = [str cStringLength]; -#if APPLE_Foundation_LIBRARY || NeXT_Foundation_LIBRARY - str = [[NSString alloc] initWithData:_data - encoding:NSISOLatin1StringEncoding]; -#else - str = [[NSString alloc] initWithData:_data - encoding:NSISOLatin9StringEncoding]; -#endif - str = [str autorelease]; - - bytes = [str cString]; - length = [str cStringLength]; - + bytes = [_data bytes]; + length = [_data length]; + /* check whether we need to encode */ - - for (cnt = 0, doEnc = NO; cnt < length; cnt++) { - if ((unsigned char)bytes[cnt] > 127) { + cnt = 0; + doEnc = NO; + while (!doEnc && cnt < length) + if ((unsigned char)bytes[cnt] > 127) doEnc = YES; - break; - } - } - + else + cnt++; + if (!doEnc) return _data; /* encode quoted printable */ { - char iso[] = "=?iso-8859-15?q?"; + char iso[] = "=?utf-8?q?"; unsigned isoLen = 16; char isoEnd[] = "?="; unsigned isoEndLen = 2; Index: sope-mime/NGMail/NGMailAddressParser.m =================================================================== --- sope-mime/NGMail/NGMailAddressParser.m (revision 1626) +++ sope-mime/NGMail/NGMailAddressParser.m (working copy) @@ -52,9 +52,9 @@ StrClass = [NSString class]; } -static inline NSString *mkStrObj(const unsigned char *s, unsigned int l) { +static inline NSString *mkStrObj(const unichar *s, unsigned int l) { // TODO: unicode - return [(NSString *)[StrClass alloc] initWithCString:(char *)s length:l]; + return [(NSString *)[StrClass alloc] initWithCharacters:s length:l]; } static inline id parseWhiteSpaces(NGMailAddressParser *self, BOOL _guessMode) { @@ -84,7 +84,7 @@ int keepPos = self->dataPos; // keep reference for backtracking id returnValue = nil; BOOL isAtom = YES; - unsigned char text[self->maxLength + 2]; // token text + unichar text[self->maxLength + 2]; // token text int length = 0; // token text length BOOL done = NO; @@ -94,7 +94,7 @@ done = YES; } else { - register unsigned char c = self->data[self->dataPos]; + register unichar c = self->data[self->dataPos]; switch (c) { case '(' : case ')': case '<': case '>': @@ -162,7 +162,7 @@ int keepPos = self->dataPos; // keep reference for backtracking id returnValue = nil; BOOL isQText = YES; - unsigned char text[self->maxLength + 4]; // token text + unichar text[self->maxLength + 4]; // token text int length = 0; // token text length BOOL done = YES; @@ -172,9 +172,9 @@ done = YES; } else { - register char c = self->data[self->dataPos]; + register unichar c = self->data[self->dataPos]; - switch ((int)c) { + switch (c) { case '"' : case '\\': case 13 : @@ -215,7 +215,7 @@ int keepPos = self->dataPos; // keep reference for backtracking id returnValue = nil; BOOL isDText = YES; - unsigned char text[self->maxLength]; // token text + unichar text[self->maxLength]; // token text int length = 0; // token text length BOOL done = YES; @@ -225,9 +225,9 @@ done = YES; } else { - register char c = self->data[self->dataPos]; + register unichar c = self->data[self->dataPos]; - switch ((int)c) { + switch (c) { case '[': case ']': case '\\': case 13: isDText = (length > 0); @@ -320,42 +320,47 @@ /* constructors */ + (id)mailAddressParserWithData:(NSData *)_data { - return [[(NGMailAddressParser *)[self alloc] - initWithCString:[_data bytes] - length:[_data length]] autorelease]; + NSString *uniString; + + uniString = [NSString stringWithCharacters:(unichar *)[_data bytes] + length:([_data length] / sizeof(unichar))]; + + return [(NGMailAddressParser *)self mailAddressParserWithString:uniString]; } + + (id)mailAddressParserWithCString:(char *)_cString { - return [[(NGMailAddressParser *)[self alloc] - initWithCString:(unsigned char *)_cString - length:strlen(_cString)] autorelease]; + NSString *nsCString; + + nsCString = [NSString stringWithCString:_cString]; + + return [(NGMailAddressParser *)self mailAddressParserWithString:nsCString]; } -- (id)initWithCString:(const unsigned char *)_cstr length:(int unsigned)_len { + ++ (id)mailAddressParserWithString:(NSString *)_string { + return [[(NGMailAddressParser *)[self alloc] initWithString:_string] + autorelease]; +} + +- (id)initWithString:(NSString *)_str { if ((self = [super init])) { // TODO: remember some string encoding? - self->data = (unsigned char *)_cstr; - self->maxLength = _len; + self->maxLength = [_str length]; + self->data = malloc(self->maxLength*sizeof(unichar)); + [_str getCharacters:self->data]; self->dataPos = 0; self->errorPos = -1; } return self; } -- (id)initWithString:(NSString *)_str { - // TODO: unicode - return [self initWithCString:(unsigned char *)[_str cString] - length:[_str cStringLength]]; -} - - (id)init { - return [self initWithCString:NULL length:0]; + return [self initWithString:nil]; } -+ (id)mailAddressParserWithString:(NSString *)_string { - return [[(NGMailAddressParser *)[self alloc] initWithString:_string] - autorelease]; -} - - (void)dealloc { + if (self->data != NULL) { + free(self->data); + } self->data = NULL; self->maxLength = 0; self->dataPos = 0; Index: sope-mime/NGMime/NGMimeRFC822DateHeaderFieldParser.m =================================================================== --- sope-mime/NGMime/NGMimeRFC822DateHeaderFieldParser.m (revision 1626) +++ sope-mime/NGMime/NGMimeRFC822DateHeaderFieldParser.m (working copy) @@ -19,88 +19,45 @@ 02111-1307, USA. */ +#ifdef HAVE_STRNDUP +#define _GNU_SOURCE 1 +#endif + +#include + #include "NGMimeHeaderFieldParser.h" #include "NGMimeHeaderFields.h" #include "NGMimeUtilities.h" #include "common.h" -#include +#ifndef HAVE_STRNDUP +char *strndup(const char *str, size_t len) +{ + char *dup = (char *)malloc(len+1); + if (dup) { + strncpy(dup,str,len); + dup[len]= '\0'; + } + return dup; +} +#endif + @implementation NGMimeRFC822DateHeaderFieldParser -static Class CalDateClass = Nil; -static NSTimeZone *gmt = nil; -static NSTimeZone *gmt01 = nil; -static NSTimeZone *gmt02 = nil; -static NSTimeZone *gmt03 = nil; -static NSTimeZone *gmt04 = nil; -static NSTimeZone *gmt05 = nil; -static NSTimeZone *gmt06 = nil; -static NSTimeZone *gmt07 = nil; -static NSTimeZone *gmt08 = nil; -static NSTimeZone *gmt09 = nil; -static NSTimeZone *gmt10 = nil; -static NSTimeZone *gmt11 = nil; -static NSTimeZone *gmt12 = nil; -static NSTimeZone *gmt0530 = nil; -static NSTimeZone *gmtM01 = nil; -static NSTimeZone *gmtM02 = nil; -static NSTimeZone *gmtM03 = nil; -static NSTimeZone *gmtM04 = nil; -static NSTimeZone *gmtM05 = nil; -static NSTimeZone *gmtM06 = nil; -static NSTimeZone *gmtM07 = nil; -static NSTimeZone *gmtM08 = nil; -static NSTimeZone *gmtM09 = nil; -static NSTimeZone *gmtM10 = nil; -static NSTimeZone *gmtM11 = nil; -static NSTimeZone *gmtM12 = nil; -static NSTimeZone *gmtM13 = nil; -static NSTimeZone *gmtM14 = nil; -static NSTimeZone *met = nil; +static NSTimeZone *gmt = nil; +static NSTimeZone *met = nil; + (int)version { return 2; } + + (void)initialize { static BOOL didInit = NO; - Class TzClass; if (didInit) return; didInit = YES; - CalDateClass = [NSCalendarDate class]; - - /* timezones which were actually used in a maillist mailbox */ - TzClass = [NSTimeZone class]; - gmt = [[TzClass timeZoneWithName:@"GMT"] retain]; - met = [[TzClass timeZoneWithName:@"MET"] retain]; - gmt01 = [[TzClass timeZoneForSecondsFromGMT: 1 * (60 * 60)] retain]; - gmt02 = [[TzClass timeZoneForSecondsFromGMT: 2 * (60 * 60)] retain]; - gmt03 = [[TzClass timeZoneForSecondsFromGMT: 3 * (60 * 60)] retain]; - gmt04 = [[TzClass timeZoneForSecondsFromGMT: 4 * (60 * 60)] retain]; - gmt05 = [[TzClass timeZoneForSecondsFromGMT: 5 * (60 * 60)] retain]; - gmt06 = [[TzClass timeZoneForSecondsFromGMT: 6 * (60 * 60)] retain]; - gmt07 = [[TzClass timeZoneForSecondsFromGMT: 7 * (60 * 60)] retain]; - gmt08 = [[TzClass timeZoneForSecondsFromGMT: 8 * (60 * 60)] retain]; - gmt09 = [[TzClass timeZoneForSecondsFromGMT: 9 * (60 * 60)] retain]; - gmt10 = [[TzClass timeZoneForSecondsFromGMT: 10 * (60 * 60)] retain]; - gmt11 = [[TzClass timeZoneForSecondsFromGMT: 11 * (60 * 60)] retain]; - gmt12 = [[TzClass timeZoneForSecondsFromGMT: 12 * (60 * 60)] retain]; - gmtM01 = [[TzClass timeZoneForSecondsFromGMT: -1 * (60 * 60)] retain]; - gmtM02 = [[TzClass timeZoneForSecondsFromGMT: -2 * (60 * 60)] retain]; - gmtM03 = [[TzClass timeZoneForSecondsFromGMT: -3 * (60 * 60)] retain]; - gmtM04 = [[TzClass timeZoneForSecondsFromGMT: -4 * (60 * 60)] retain]; - gmtM05 = [[TzClass timeZoneForSecondsFromGMT: -5 * (60 * 60)] retain]; - gmtM06 = [[TzClass timeZoneForSecondsFromGMT: -6 * (60 * 60)] retain]; - gmtM07 = [[TzClass timeZoneForSecondsFromGMT: -7 * (60 * 60)] retain]; - gmtM08 = [[TzClass timeZoneForSecondsFromGMT: -8 * (60 * 60)] retain]; - gmtM09 = [[TzClass timeZoneForSecondsFromGMT: -9 * (60 * 60)] retain]; - gmtM10 = [[TzClass timeZoneForSecondsFromGMT:-10 * (60 * 60)] retain]; - gmtM11 = [[TzClass timeZoneForSecondsFromGMT:-11 * (60 * 60)] retain]; - gmtM12 = [[TzClass timeZoneForSecondsFromGMT:-12 * (60 * 60)] retain]; - gmtM13 = [[TzClass timeZoneForSecondsFromGMT:-13 * (60 * 60)] retain]; - gmtM14 = [[TzClass timeZoneForSecondsFromGMT:-14 * (60 * 60)] retain]; - - gmt0530 = [[TzClass timeZoneForSecondsFromGMT:5 * (60*60) + (30*60)] retain]; + gmt = [[NSTimeZone timeZoneWithName:@"GMT"] retain]; + met = [[NSTimeZone timeZoneWithName:@"MET"] retain]; } /* @@ -111,7 +68,7 @@ TODO: use an own parser for that. */ -static int parseMonthOfYear(unsigned char *s, unsigned int len) { +static int parseMonthOfYear(char *s, unsigned int len) { /* This one is *extremely* forgiving, it only checks what is necessary for the set below. This should work for both, English @@ -147,162 +104,110 @@ } } -static NSTimeZone *parseTimeZone(unsigned char *s, unsigned int len) { +static int offsetFromTZAbbreviation(const char **p) { + NSString *abbreviation; + NSTimeZone *offsetTZ; + unsigned int length; + + length = 0; + while (isalpha(*(*p+length))) + length++; + abbreviation = [[NSString alloc] initWithBytes: *p + length: length - 1 + encoding: NSISOLatin1StringEncoding]; + offsetTZ = [NSTimeZone timeZoneWithAbbreviation: abbreviation]; + [abbreviation release]; + *p += length; + + return [offsetTZ secondsFromGMT]; +} + +static inline char *digitsString(const char *string) { + const char *p; + unsigned int len; + + p = string; + while (!isdigit(*p)) + p++; + len = 0; + while (isdigit(*(p + len))) + len++; + + return strndup(p, len); +} + +static NSTimeZone *parseTimeZone(const char *s, unsigned int len) { /* WARNING: failed to parse RFC822 timezone: '+0530' \ (value='Tue, 13 Jul 2004 21:39:28 +0530') TODO: this is because libFoundation doesn't accept 'GMT+0530' as input. */ - char *p = (char *)s; + char *newString, *digits; + const char *p; NSTimeZone *tz; - NSString *ts; - - if (len == 0) - return nil; - - if (*s == '+' || *s == '-') { - if (len == 3) { - if (p[1] == '0' && p[2] == '0') // '+00' or '-00' - return gmt; - if (*s == '+') { - if (p[1] == '0' && p[2] == '1') // '+01' - return gmt01; - if (p[1] == '0' && p[2] == '2') // '+02' - return gmt02; - } - } - else if (len == 5) { - if (p[3] == '0' && p[4] == '0' && p[1] == '0') { // '?0x00' - if (p[2] == '0') // '+0000' - return gmt; - - if (*s == '+') { - if (p[2] == '1') return gmt01; // '+0100' - if (p[2] == '2') return gmt02; // '+0200' - if (p[2] == '3') return gmt03; // '+0300' - if (p[2] == '4') return gmt04; // '+0400' - if (p[2] == '5') return gmt05; // '+0500' - if (p[2] == '6') return gmt06; // '+0600' - if (p[2] == '7') return gmt07; // '+0700' - if (p[2] == '8') return gmt08; // '+0800' - if (p[2] == '9') return gmt09; // '+0900' - } - else if (*s == '-') { - if (p[2] == '1') return gmtM01; // '-0100' - if (p[2] == '2') return gmtM02; // '-0200' - if (p[2] == '3') return gmtM03; // '-0300' - if (p[2] == '4') return gmtM04; // '-0400' - if (p[2] == '5') return gmtM05; // '-0500' - if (p[2] == '6') return gmtM06; // '-0600' - if (p[2] == '7') return gmtM07; // '-0700' - if (p[2] == '8') return gmtM08; // '-0800' - if (p[2] == '9') return gmtM09; // '-0900' - } - } - else if (p[3] == '0' && p[4] == '0' && p[1] == '1') { // "?1x00" - if (*s == '+') { - if (p[2] == '0') return gmt10; // '+1000' - if (p[2] == '1') return gmt11; // '+1100' - if (p[2] == '2') return gmt12; // '+1200' - } - else if (*s == '-') { - if (p[2] == '0') return gmtM10; // '-1000' - if (p[2] == '1') return gmtM11; // '-1100' - if (p[2] == '2') return gmtM12; // '-1200' - if (p[2] == '3') return gmtM13; // '-1300' - if (p[2] == '4') return gmtM14; // '-1400' - } - } - - /* special case for GMT+0530 */ - if (strncmp((char *)s, "+0530", 5) == 0) - return gmt0530; - } - else if (len == 7) { - /* - "MultiMail" submits timezones like this: - "Tue, 9 Mar 2004 9:43:00 -05-500", - don't know what the "-500" trailer is supposed to mean? Apparently - Thunderbird just uses the "-05", so do we. - */ - - if (isdigit(p[1]) && isdigit(p[2]) && (p[3] == '-'||p[3] == '+')) { - unsigned char tmp[8]; - - strncpy((char *)tmp, p, 3); - tmp[3] = '0'; - tmp[4] = '0'; - tmp[5] = '\0'; - return parseTimeZone(tmp, 5); - } - } + unsigned int hours, minutes, seconds, remaining; + int sign; + + sign = 1; + hours = 0; + minutes = 0; + seconds = 0; + + newString = strndup(s, len); + p = newString; + + if (isalpha(*p)) + seconds = offsetFromTZAbbreviation(&p); + while (isspace(*p)) + p++; + while (*p == '+' || *p == '-') { + if (*p == '-') + sign = -sign; + p++; } - else if (*s == '0') { - if (len == 2) { // '00' - if (p[1] == '0') return gmt; - if (p[1] == '1') return gmt01; - if (p[1] == '2') return gmt02; - } - else if (len == 4) { - if (p[2] == '0' && p[3] == '0') { // '0x00' - if (p[1] == '0') return gmt; - if (p[1] == '1') return gmt01; - if (p[1] == '2') return gmt02; - } - } + digits = digitsString(p); + p = digits; + remaining = strlen(p); + switch(remaining) { + case 6: /* hhmmss */ + seconds += (10 * (*(p + remaining - 2) - 48) + + *(p + remaining - 1) - 48); + case 4: /* hhmm */ + hours += 10 * (*p - 48); + p++; + case 3: /* hmm */ + hours += (*p - 48); + p++; + minutes += 10 * (*p - 48) + *(p + 1) - 48; + break; + case 2: /* hh */ + hours += 10 * (*p - 48) + *(p + 1) - 48; + break; + default: + NSLog (@"parseTimeZone: cannot parse time notation '%s'", newString); } - else if (len == 3) { - if (strcasecmp((char *)s, "GMT") == 0) return gmt; - if (strcasecmp((char *)s, "UTC") == 0) return gmt; - if (strcasecmp((char *)s, "MET") == 0) return met; - if (strcasecmp((char *)s, "CET") == 0) return met; - } - - if (isalpha(*s)) { - ts = [[NSString alloc] initWithCString:(char *)s length:len]; - } - else { - char buf[len + 5]; - - buf[0] = 'G'; buf[1] = 'M'; buf[2] = 'T'; - if (*s == '+' || *s == '-') { - strcpy(&(buf[3]), (char *)s); - } - else { - buf[3] = '+'; - strcpy(&(buf[4]), (char *)s); - } - ts = [[NSString alloc] initWithCString:buf]; - } -#if 1 - NSLog(@"%s: RFC822 TZ Parser: expensive: '%@'", __PRETTY_FUNCTION__, ts); -#endif - tz = [NSTimeZone timeZoneWithAbbreviation:ts]; - [ts release]; + free(digits); + + seconds += sign * (3600 * hours + 60 * minutes); + tz = [NSTimeZone timeZoneForSecondsFromGMT: seconds]; + free(newString); + return tz; } - (id)parseValue:(id)_data ofHeaderField:(NSString *)_field { // TODO: use UNICODE NSCalendarDate *date = nil; - unsigned char buf[256]; - unsigned char *bytes = buf, *pe; + char *bytes, *pe; unsigned length = 0; NSTimeZone *tz = nil; char dayOfMonth, monthOfYear, hour, minute, second; short year; BOOL flag; - - if ((length = [_data cStringLength]) > 254) { - [self logWithFormat: - @"header field value to large for date parsing: '%@'(%i)", - _data, length]; - length = 254; - } - - [_data getCString:(char *)buf maxLength:length]; - buf[length] = '\0'; - + + length = [_data lengthOfBytesUsingEncoding: NSUTF8StringEncoding]; + bytes = [_data cStringUsingEncoding: NSUTF8StringEncoding]; + /* remove leading chars (skip to first digit, the day of the month) */ while (length > 0 && (!isdigit(*bytes))) { bytes++; @@ -312,7 +217,7 @@ if (length == 0) { NSLog(@"WARNING(%s): empty value for header field %@ ..", __PRETTY_FUNCTION__, _field); - return [CalDateClass date]; + return [NSCalendarDate date]; } // TODO: should be a category on NSCalendarDate @@ -435,7 +340,8 @@ for (pe = bytes; isalnum(*pe) || *pe == '-' || *pe == '+'; pe++) ; *pe = '\0'; - if ((tz = parseTimeZone(bytes, (pe - bytes))) == nil) { + if (pe == bytes + || (tz = parseTimeZone((const char *) bytes, (pe - bytes))) == nil) { [self logWithFormat: @"WARNING: failed to parse RFC822 timezone: '%s' (value='%@')", bytes, _data]; @@ -444,9 +350,9 @@ /* construct and return */ finished: - date = [CalDateClass dateWithYear:year month:monthOfYear day:dayOfMonth - hour:hour minute:minute second:second - timeZone:tz]; + date = [NSCalendarDate dateWithYear:year month:monthOfYear day:dayOfMonth + hour:hour minute:minute second:second + timeZone:tz]; if (date == nil) goto failed; #if 0 Index: sope-mime/NGMime/NGMimeMultipartBodyParser.m =================================================================== --- sope-mime/NGMime/NGMimeMultipartBodyParser.m (revision 1626) +++ sope-mime/NGMime/NGMimeMultipartBodyParser.m (working copy) @@ -428,6 +428,7 @@ NSString *boundary = nil; NSArray *rawBodyParts = nil; BOOL foundError = NO; + NSData *boundaryBytes; contentType = [_part contentType]; boundary = [contentType valueOfParameter:@"boundary"]; @@ -437,9 +438,10 @@ *(&foundError) = NO; + boundaryBytes = [boundary dataUsingEncoding:NSISOLatin1StringEncoding]; *(&rawBodyParts) = [self _parseBody:_body part:_part data:_data - boundary:[boundary cString] - length:[boundary cStringLength] + boundary:[boundaryBytes bytes] + length:[boundary length] delegate:_d]; if (rawBodyParts) { Index: sope-mime/NGMime/NGMimeHeaderFieldGeneratorSet.m =================================================================== --- sope-mime/NGMime/NGMimeHeaderFieldGeneratorSet.m (revision 1626) +++ sope-mime/NGMime/NGMimeHeaderFieldGeneratorSet.m (working copy) @@ -77,6 +77,7 @@ [rfc822Set setGenerator:gen forField:@"bcc"]; [rfc822Set setGenerator:gen forField:Fields->from]; [rfc822Set setGenerator:gen forField:@"reply-to"]; + [rfc822Set setGenerator:gen forField:@"in-reply-to"]; [rfc822Set setGenerator:gen forField:@"Disposition-Notification-To"]; } Index: sope-mime/NGMime/NGMimeType.m =================================================================== --- sope-mime/NGMime/NGMimeType.m (revision 1626) +++ sope-mime/NGMime/NGMimeType.m (working copy) @@ -120,28 +120,23 @@ /* some unsupported, but known encoding */ else if ([charset isEqualToString:@"ks_c_5601-1987"]) { - encoding = [NSString defaultCStringEncoding]; + encoding = NSISOLatin1StringEncoding; foundUnsupported = YES; } else if ([charset isEqualToString:@"euc-kr"]) { - encoding = [NSString defaultCStringEncoding]; - foundUnsupported = YES; + encoding = NSKoreanEUCStringEncoding; } else if ([charset isEqualToString:@"big5"]) { - encoding = [NSString defaultCStringEncoding]; - foundUnsupported = YES; + encoding = NSBIG5StringEncoding; } else if ([charset isEqualToString:@"iso-2022-jp"]) { - encoding = [NSString defaultCStringEncoding]; - foundUnsupported = YES; + encoding = NSISO2022JPStringEncoding; } else if ([charset isEqualToString:@"gb2312"]) { - encoding = [NSString defaultCStringEncoding]; - foundUnsupported = YES; + encoding = NSGB2312StringEncoding; } else if ([charset isEqualToString:@"koi8-r"]) { - encoding = [NSString defaultCStringEncoding]; - foundUnsupported = YES; + encoding = NSKOI8RStringEncoding; } else if ([charset isEqualToString:@"windows-1252"]) { @@ -152,7 +147,7 @@ } else if ([charset isEqualToString:@"x-unknown"] || [charset isEqualToString:@"unknown"]) { - encoding = NSASCIIStringEncoding; + encoding = NSISOLatin1StringEncoding; } /* ISO Latin 9 */ #if !(NeXT_Foundation_LIBRARY || APPLE_Foundation_LIBRARY) @@ -166,7 +161,7 @@ else { [self logWithFormat:@"%s: unknown charset '%@'", __PRETTY_FUNCTION__, _s]; - encoding = [NSString defaultCStringEncoding]; + encoding = NSISOLatin1StringEncoding; } return encoding; } @@ -385,23 +380,26 @@ } - (BOOL)valueNeedsQuotes:(NSString *)_parameterValue { - unsigned len = [_parameterValue cStringLength]; - char buf[len + 15]; - char *cstr; + NSData *stringData; + const char *cstr; + unsigned int count, max; + BOOL needsQuote; - cstr = &(buf[0]); + needsQuote = NO; - [_parameterValue getCString:cstr]; cstr[len] = '\0'; - while (*cstr) { - if (isMime_SpecialByte(*cstr)) - return YES; - - if (*cstr == 32) - return YES; - - cstr++; + stringData = [_parameterValue dataUsingEncoding:NSUTF8StringEncoding]; + cstr = [stringData bytes]; + max = [stringData length]; + count = 0; + while (!needsQuote && count < max) { + if (isMime_SpecialByte(*(cstr + count)) + || *(cstr + count) == 32) + needsQuote = YES; + else + count++; } - return NO; + + return needsQuote; } - (NSString *)stringValue { Index: sope-mime/NGMime/NGMimeBodyPart.m =================================================================== --- sope-mime/NGMime/NGMimeBodyPart.m (revision 1626) +++ sope-mime/NGMime/NGMimeBodyPart.m (working copy) @@ -31,18 +31,6 @@ return 2; } -static NGMimeType *defaultType = nil; - -+ (void)initialize { - static BOOL isInitialized = NO; - if (!isInitialized) { - isInitialized = YES; - - defaultType = - [[NGMimeType mimeType:@"text/plain; charset=us-ascii"] retain]; - } -} - + (id)bodyPartWithHeader:(NGHashMap *)_header { return [[[self alloc] initWithHeader:_header] autorelease]; } @@ -156,13 +144,12 @@ if (!Fields) Fields = (NGMimeHeaderNames *)[NGMimePartParser headerFieldNames]; - type = [self->header objectForKey:Fields->contentType]; if (![type isKindOfClass:[NGMimeType class]]) type = [NGMimeType mimeType:[type stringValue]]; - return (type != nil ? type : (id)defaultType); + return type; } - (NSString *)contentId { Index: sope-mime/NGMime/ChangeLog =================================================================== --- sope-mime/NGMime/ChangeLog (revision 1626) +++ sope-mime/NGMime/ChangeLog (working copy) @@ -1,3 +1,25 @@ +2008-09-08 Wolfgang Sourdeau + + * NGMimeRFC822DateHeaderFieldParser.m ([NGMimeRFC + -parseValue:ofHeaderField:]): don't parse timezone with a length + of 0. + +2008-09-01 Wolfgang Sourdeau + + * NGMimeRFC822DateHeaderFieldParser.m ([NGMimeRFC + -parseValue:ofHeaderField:]): use an 8-bit safe encoding when + parsing dates. Since we only consider 7-bits characters, we ensure + that bad user-agents can be handled more properly. + + * NGMimeType.m ([NGMimeType +stringEncodingForCharset:]): + x-unknown encoding is now translated to an 8-bit safe encoding + (NSISOLatin1StringEncoding). + + * NGMimeAddressHeaderFieldGenerator.m + ([NGMimeAddressHeaderFieldGenerator + -generateDataForHeaderFieldNamed:value:]): encode resulting string + in an 8-bit safe encoding (NSISOLatin1StringEncoding). + 2008-01-29 Albrecht Dress * fixes for OGo bug #789 (reply-to QP encoding) Index: sope-mime/NGMime/NGMimeContentTypeHeaderFieldGenerator.m =================================================================== --- sope-mime/NGMime/NGMimeContentTypeHeaderFieldGenerator.m (revision 1626) +++ sope-mime/NGMime/NGMimeContentTypeHeaderFieldGenerator.m (working copy) @@ -36,8 +36,7 @@ NGMimeType *type = nil; // only one content-type field NSString *tmp = nil; NSMutableData *data = nil; - unsigned char *ctmp = NULL; - unsigned len = 0; + NSData *valueData; type = _value; @@ -59,21 +58,15 @@ tmp = [type type]; NSAssert(tmp, @"type should not be nil"); - len = [tmp length]; - ctmp = malloc(len + 4); - [tmp getCString:(char *)ctmp]; ctmp[len] = '\0'; - [data appendBytes:ctmp length:len]; - free(ctmp); + valueData = [tmp dataUsingEncoding: NSISOLatin1StringEncoding]; + [data appendData: valueData]; + + [data appendBytes:"/" length:1]; - [data appendBytes:"//" length:1]; - tmp = [type subType]; if (tmp != nil) { - len = [tmp length]; - ctmp = malloc(len + 4); - [tmp getCString:(char *)ctmp]; ctmp[len] = '\0'; - [data appendBytes:ctmp length:len]; - free(ctmp); + valueData = [tmp dataUsingEncoding: NSISOLatin1StringEncoding]; + [data appendData:valueData]; } else [data appendBytes:"*" length:1]; @@ -91,12 +84,9 @@ continue; } [data appendBytes:"; " length:2]; - - len = [name cStringLength]; - ctmp = malloc(len + 1); - [name getCString:(char *)ctmp]; ctmp[len] = '\0'; - [data appendBytes:ctmp length:len]; - free(ctmp); + + valueData = [name dataUsingEncoding: NSUTF8StringEncoding]; + [data appendData: valueData]; /* this confuses GroupWise: "= \"" (a space) @@ -105,66 +95,30 @@ /* check for encoding */ { - unsigned cnt; + unsigned cnt, max; + const char *dataBytes; BOOL doEnc; - len = [value cStringLength]; - ctmp = malloc(len + 4); - [value getCString:(char *)ctmp]; ctmp[len] = '\0'; - cnt = 0; + valueData = [value dataUsingEncoding:NSUTF8StringEncoding]; + dataBytes = [valueData bytes]; + max = [valueData length]; + doEnc = NO; - while (cnt < len) { - if ((unsigned char)ctmp[cnt] > 127) { + cnt = 0; + while (!doEnc && cnt < max) { + if ((unsigned char)dataBytes[cnt] > 127) doEnc = YES; - break; - } - cnt++; + else + cnt++; } if (doEnc) { - unsigned char iso[] = "=?iso-8859-15?q?"; - unsigned isoLen = 16; - unsigned char isoEnd[] = "?="; - unsigned isoEndLen = 2; - unsigned desLen; - unsigned char *des; - - if (ctmp) free(ctmp); - { - NSData *data; - -#if APPLE_Foundation_LIBRARY || NeXT_Foundation_LIBRARY - data = [value dataUsingEncoding:NSISOLatin1StringEncoding]; -#else - data = [value dataUsingEncoding:NSISOLatin9StringEncoding]; -#endif - - len = [data length]; - ctmp = malloc(len + 10); - [data getBytes:ctmp]; ctmp[len] = '\0'; - } - - desLen = len * 3 + 20; - des = calloc(desLen + 10, sizeof(char)); - - memcpy(des, ctmp, cnt); - memcpy(des + cnt, iso, isoLen); - desLen = - NGEncodeQuotedPrintableMime(ctmp + cnt, len - cnt, - des + cnt + isoLen, - desLen - cnt - isoLen); - if ((int)desLen != -1) { - memcpy(des + cnt + isoLen + desLen, isoEnd, isoEndLen); - [data appendBytes:des length:(cnt + isoLen + desLen + isoEndLen)]; - } - else { - NSLog(@"WARNING: An error occour during quoted-printable decoding"); - } - if (des) free(des); + [data appendBytes:"=?utf-8?q?" length:10]; + [data appendData: [valueData dataByEncodingQuotedPrintable]]; + [data appendBytes:"?=" length:2]; } else { - [data appendBytes:ctmp length:len]; + [data appendData: valueData]; } - free(ctmp); } [data appendBytes:"\"" length:1]; } Index: sope-mime/NGMime/NGMimePartGenerator.m =================================================================== --- sope-mime/NGMime/NGMimePartGenerator.m (revision 1626) +++ sope-mime/NGMime/NGMimePartGenerator.m (working copy) @@ -155,8 +155,9 @@ BOOL isMultiValue, isFirst; /* get field name and strip leading spaces */ - fcname = (const unsigned char *)[_field cString]; - for (len = [_field cStringLength]; len > 0; fcname++, len--) { + fcname = (const unsigned char *)[_field cStringUsingEncoding:NSISOLatin1StringEncoding]; + for (len = [_field lengthOfBytesUsingEncoding:NSISOLatin1StringEncoding]; + len > 0; fcname++, len--) { if (*fcname != ' ') break; } @@ -328,7 +329,7 @@ if ([body isKindOfClass:[NSData class]]) data = body; else if ([body isKindOfClass:[NSString class]]) - data = [body dataUsingEncoding:[NSString defaultCStringEncoding]]; + data = [body dataUsingEncoding: NSISOLatin1StringEncoding]; else data = nil; Index: sope-mime/NGMime/NGMimeBodyParser.m =================================================================== --- sope-mime/NGMime/NGMimeBodyParser.m (revision 1626) +++ sope-mime/NGMime/NGMimeBodyParser.m (working copy) @@ -67,7 +67,10 @@ if (_data == nil) return nil; ctype = [_part contentType]; - + if (!ctype + && [_d respondsToSelector: @selector(parser:contentTypeOfPart:)]) + ctype = [_d parser: self contentTypeOfPart: _part]; + if (![ctype isKindOfClass:[NGMimeType class]]) ctype = [NGMimeType mimeType:[ctype stringValue]]; Index: sope-mime/NGMime/NGMimePartParser.h =================================================================== --- sope-mime/NGMime/NGMimePartParser.h (revision 1626) +++ sope-mime/NGMime/NGMimePartParser.h (working copy) @@ -117,6 +117,7 @@ BOOL parserParseRawBodyDataOfPart:1; BOOL parserBodyParserForPart:1; BOOL parserDecodeBodyOfPart:1; + BOOL parserContentTypeOfPart:1; } delegateRespondsTo; @@ -275,6 +276,9 @@ - (id)parser:(NGMimePartParser *)_parser bodyParserForPart:(id)_part; +- (NGMimeType *)parser:(id)_parser + contentTypeOfPart:(id)_part; + @end /* NSObject(NGMimePartParserDelegate) */ @interface NSObject(NGMimePartParser) Index: sope-mime/NGMime/NGMimePartParser.m =================================================================== --- sope-mime/NGMime/NGMimePartParser.m (revision 1626) +++ sope-mime/NGMime/NGMimePartParser.m (working copy) @@ -227,7 +227,7 @@ } + (NSStringEncoding)defaultHeaderFieldEncoding { - return NSISOLatin1StringEncoding; + return NSUTF8StringEncoding; } - (id)valueOfHeaderField:(NSString *)_name data:(id)_data { @@ -1091,7 +1091,10 @@ id bodyParser = nil; ctype = [_p contentType]; - + if (!ctype + && self->delegateRespondsTo.parserContentTypeOfPart) + ctype = [self->delegate parser: self contentTypeOfPart: _p]; + contentType = ([ctype isKindOfClass:[NGMimeType class]]) ? ctype : [NGMimeType mimeType:[ctype stringValue]]; Index: sope-mime/NGMime/NGMimeAddressHeaderFieldGenerator.m =================================================================== --- sope-mime/NGMime/NGMimeAddressHeaderFieldGenerator.m (revision 1626) +++ sope-mime/NGMime/NGMimeAddressHeaderFieldGenerator.m (working copy) @@ -105,10 +105,10 @@ } tmp = [obj displayName]; - bufLen = [tmp cStringLength]; + bufLen = [tmp lengthOfBytesUsingEncoding: NSUTF8StringEncoding]; - buffer = calloc(bufLen + 10, sizeof(char)); - [tmp getCString:buffer]; + buffer = calloc(bufLen, sizeof(char)); + [tmp getCString: buffer maxLength: bufLen encoding: NSUTF8StringEncoding]; cnt = 0; doEnc = NO; @@ -117,11 +117,11 @@ /* must encode chars outside ASCII 33..60, 62..126 ranges [RFC 2045, Sect. 6.7] * RFC 2047, Sect. 4.2 also requires chars 63 and 95 to be encoded * For spaces, quotation is fine */ - if ((unsigned char)buffer[cnt] < 32 || - (unsigned char)buffer[cnt] == 61 || - (unsigned char)buffer[cnt] == 63 || - (unsigned char)buffer[cnt] == 95 || - (unsigned char)buffer[cnt] > 126) { + if ((unichar)buffer[cnt] < 32 || + (unichar)buffer[cnt] == 61 || + (unichar)buffer[cnt] == 63 || + (unichar)buffer[cnt] == 95 || + (unichar)buffer[cnt] > 126) { doEnc = YES; break; } @@ -130,8 +130,13 @@ if (doEnc) { /* FIXME - better use UTF8 encoding! */ +#if NeXT_Foundation_LIBRARY unsigned char iso[] = "=?iso-8859-15?q?"; unsigned isoLen = 16; +#else + unsigned char iso[] = "=?utf-8?q?"; + unsigned isoLen = 10; +#endif unsigned char isoEnd[] = "?="; unsigned isoEndLen = 2; unsigned desLen; @@ -141,10 +146,10 @@ { NSData *data; -#if APPLE_Foundation_LIBRARY || NeXT_Foundation_LIBRARY +#if NeXT_Foundation_LIBRARY data = [tmp dataUsingEncoding:NSISOLatin1StringEncoding]; #else - data = [tmp dataUsingEncoding:NSISOLatin9StringEncoding]; + data = [tmp dataUsingEncoding:NSUTF8StringEncoding]; #endif bufLen = [data length]; @@ -162,8 +167,9 @@ des + isoLen, desLen - isoLen); if ((int)desLen != -1) { memcpy(des + isoLen + desLen, isoEnd, isoEndLen); - tmp = [NSString stringWithCString:(char *)des - length:(isoLen + desLen + isoEndLen)]; + tmp = [[NSString alloc] initWithData: [NSData dataWithBytes:(char *)des length:(isoLen + desLen + isoEndLen)] + encoding: NSISOLatin1StringEncoding]; + [tmp autorelease]; } else { [self warnWithFormat: @@ -190,11 +196,7 @@ } } -#if APPLE_Foundation_LIBRARY || NeXT_Foundation_LIBRARY data = [result dataUsingEncoding:NSISOLatin1StringEncoding]; -#else - data = [result dataUsingEncoding:NSISOLatin9StringEncoding]; -#endif [result release]; return data; Index: sope-mime/NGMime/NGMimeContentDispositionHeaderFieldGenerator.m =================================================================== --- sope-mime/NGMime/NGMimeContentDispositionHeaderFieldGenerator.m (revision 1626) +++ sope-mime/NGMime/NGMimeContentDispositionHeaderFieldGenerator.m (working copy) @@ -49,80 +49,70 @@ // TODO: move the stuff below to some NSString or NSData category? - data = [NSMutableData dataWithCapacity:64]; + data = [NSMutableData dataWithCapacity: 64]; tmp = [field type]; [data appendBytes:[tmp cString] length:[tmp length]]; tmp = [field filename]; if (tmp != nil) { [data appendBytes:"; " length:2]; [data appendBytes:"filename=\"" length:10]; - { - unsigned char *ctmp; - int cnt, len; - BOOL doEnc; - - // TODO: unicode? - len = [tmp cStringLength]; - ctmp = malloc(len + 3); - [tmp getCString:(char *)ctmp]; ctmp[len] = '\0'; - cnt = 0; - doEnc = NO; - while (cnt < len) { - if ((unsigned char)ctmp[cnt] > 127) { - doEnc = YES; - break; - } - cnt++; + + NSData *d; + unsigned char* bytes; + unsigned length; + int cnt; + BOOL doEnc; + + //d = [tmp dataUsingEncoding: NSUTF8StringEncoding]; + //bytes = [d bytes]; + //length = [d length]; + bytes = [tmp cStringUsingEncoding: NSUTF8StringEncoding]; + length = strlen(bytes); + + cnt = 0; + doEnc = NO; + while (cnt < length) { + if ((unsigned char)bytes[cnt] > 127) { + doEnc = YES; + break; } - if (doEnc) { - char iso[] = "=?iso-8859-15?q?"; - unsigned isoLen = 16; - char isoEnd[] = "?="; - unsigned isoEndLen = 2; - unsigned desLen; - char *des; - - if (ctmp) free(ctmp); - { - NSData *data; + cnt++; + } -#if APPLE_Foundation_LIBRARY || NeXT_Foundation_LIBRARY - data = [tmp dataUsingEncoding:NSISOLatin1StringEncoding]; -#else - data = [tmp dataUsingEncoding:NSISOLatin9StringEncoding]; -#endif - - len = [data length]; - ctmp = malloc(len+1); - [data getBytes:ctmp]; ctmp[len] = '\0'; - } - - desLen = len * 3 + 20; - des = calloc(desLen + 10, sizeof(char)); - - memcpy(des, ctmp, cnt); - memcpy(des + cnt, iso, isoLen); - desLen = - NGEncodeQuotedPrintableMime((unsigned char *)ctmp + cnt, len - cnt, - (unsigned char *)des + cnt + isoLen, - desLen - cnt - isoLen); - if ((int)desLen != -1) { - memcpy(des + cnt + isoLen + desLen, isoEnd, isoEndLen); - [data appendBytes:des length:(cnt + isoLen + desLen + isoEndLen)]; - } - else { + if (doEnc) + { + char iso[] = "=?utf-8?q?"; + unsigned isoLen = 10; + char isoEnd[] = "?="; + unsigned isoEndLen = 2; + int desLen; + char *des; + + desLen = length * 3 + 20; + + des = calloc(desLen + 2, sizeof(char)); + + memcpy(des, iso, isoLen); + desLen = NGEncodeQuotedPrintableMime((unsigned char *)bytes, length, + (unsigned char *)(des + isoLen), + desLen - isoLen); + if (desLen != -1) { + memcpy(des + isoLen + desLen, isoEnd, isoEndLen); + [data appendBytes:des length:(isoLen + desLen + isoEndLen)]; + } + else { [self logWithFormat:@"WARNING(%s:%i): An error occour during " @"quoted-printable decoding", __PRETTY_FUNCTION__, __LINE__]; - } - if (des) free(des); + if (des != NULL) free(des); + } } - else { - [data appendBytes:ctmp length:len]; + else + { + [data appendBytes:[tmp cString] length:[tmp length]]; } - } - // [data appendBytes:[tmp cString] length:[tmp length]]; - [data appendBytes:"\"" length:1]; + + [data appendBytes:"\"" length:1]; } return data; } Index: sope-gdl1/PostgreSQL/PostgreSQL72Channel.m =================================================================== --- sope-gdl1/PostgreSQL/PostgreSQL72Channel.m (revision 1626) +++ sope-gdl1/PostgreSQL/PostgreSQL72Channel.m (working copy) @@ -713,6 +713,39 @@ return ms; } +/* GCSEOAdaptorChannel protocol */ +static NSString *sqlFolderFormat = (@"CREATE TABLE %@ (\n" \ + @" c_name VARCHAR (256) NOT NULL PRIMARY KEY,\n" + @" c_content VARCHAR (100000) NOT NULL,\n" + @" c_creationdate INT4 NOT NULL,\n" + @" c_lastmodified INT4 NOT NULL,\n" + @" c_version INT4 NOT NULL,\n" + @" c_deleted INT4 NULL\n" + @")"); +static NSString *sqlFolderACLFormat = (@"CREATE TABLE %@ (\n" \ + @" c_uid VARCHAR (256) NOT NULL,\n" + @" c_object VARCHAR (256) NOT NULL,\n" + @" c_role VARCHAR (80) NOT NULL\n" + @")"); + +- (NSException *) createGCSFolderTableWithName: (NSString *) tableName +{ + NSString *sql; + + sql = [NSString stringWithFormat: sqlFolderFormat, tableName]; + + return [self evaluateExpressionX: sql]; +} + +- (NSException *) createGCSFolderACLTableWithName: (NSString *) tableName +{ + NSString *sql; + + sql = [NSString stringWithFormat: sqlFolderACLFormat, tableName]; + + return [self evaluateExpressionX: sql]; +} + @end /* PostgreSQL72Channel */ @implementation PostgreSQL72Channel(PrimaryKeyGeneration) Index: sope-gdl1/Oracle8/OracleAdaptorChannel.m =================================================================== --- sope-gdl1/Oracle8/OracleAdaptorChannel.m (revision 1626) +++ sope-gdl1/Oracle8/OracleAdaptorChannel.m (working copy) @@ -30,6 +30,7 @@ #import +static BOOL debugOn = NO; // // // @@ -41,10 +42,19 @@ @implementation OracleAdaptorChannel (Private) -- (void) _cleanup ++ (void) initialize { + NSUserDefaults *ud; + + ud = [NSUserDefaults standardUserDefaults]; + debugOn = [ud boolForKey: @"OracleAdaptorDebug"]; +} + +- (void) _cleanup +{ column_info *info; int c; + sword result; [_resultSetProperties removeAllObjects]; @@ -58,11 +68,29 @@ // so we just free the value instead. if (info->value) { - if (OCIDescriptorFree((dvoid *)info->value, (ub4)OCI_DTYPE_LOB) != OCI_SUCCESS) + if (info->type == SQLT_CLOB + || info->type == SQLT_BLOB + || info->type == SQLT_BFILEE + || info->type == SQLT_CFILEE) + { + result = OCIDescriptorFree((dvoid *)info->value, (ub4) OCI_DTYPE_LOB); + if (result != OCI_SUCCESS) + { + NSLog (@"value was not a LOB descriptor"); + abort(); + } + } + else free(info->value); info->value = NULL; } - free(info); + else + { + NSLog (@"trying to free an already freed value!"); + abort(); + } + free(info); + [_row_buffer removeObjectAtIndex: c]; } @@ -231,6 +259,9 @@ [self _cleanup]; + if (debugOn) + [self logWithFormat: @"expression: %@", theExpression]; + if (!theExpression || ![theExpression length]) { [NSException raise: @"OracleInvalidExpressionException" @@ -302,7 +333,9 @@ // We read the maximum width of a column info->max_width = 0; status = OCIAttrGet((dvoid*)param, (ub4)OCI_DTYPE_PARAM, (dvoid*)&(info->max_width), (ub4 *)0, (ub4)OCI_ATTR_DATA_SIZE, (OCIError *)_oci_err); - + + if (debugOn) + NSLog(@"name: %s, type: %d", cname, info->type); attribute = [EOAttribute attributeWithOracleType: info->type name: cname length: clen width: info->max_width]; [_resultSetProperties addObject: attribute]; @@ -609,7 +642,7 @@ /* GCSEOAdaptorChannel protocol */ static NSString *sqlFolderFormat = (@"CREATE TABLE %@ (\n" \ - @" c_name VARCHAR2 (256) NOT NULL,\n" + @" c_name VARCHAR2 (256) NOT NULL PRIMARY KEY,\n" @" c_content CLOB NOT NULL,\n" @" c_creationdate INTEGER NOT NULL,\n" @" c_lastmodified INTEGER NOT NULL,\n" Index: sope-gdl1/Oracle8/OracleAdaptorChannelController.m =================================================================== --- sope-gdl1/Oracle8/OracleAdaptorChannelController.m (revision 1626) +++ sope-gdl1/Oracle8/OracleAdaptorChannelController.m (working copy) @@ -31,6 +31,8 @@ #import #import +static BOOL debugOn = NO; + // // // @@ -48,6 +50,14 @@ // @implementation OracleAdaptorChannelController ++ (void) initialize +{ + NSUserDefaults *ud; + + ud = [NSUserDefaults standardUserDefaults]; + debugOn = [ud boolForKey: @"OracleAdaptorDebug"]; +} + - (EODelegateResponse) adaptorChannel: (id) theChannel willInsertRow: (NSMutableDictionary *) theRow forEntity: (EOEntity *) theEntity @@ -56,7 +66,8 @@ NSArray *keys; int i, c; - NSLog(@"willInsertRow: %@ %@", [theRow description], [theEntity description]); + if (debugOn) + NSLog(@"willInsertRow: %@ %@", [theRow description], [theEntity description]); s = AUTORELEASE([[NSMutableString alloc] init]); @@ -101,7 +112,8 @@ NSArray *keys; int i, c; - NSLog(@"willUpdatetRow: %@ %@", [theRow description], [theQualifier description]); + if (debugOn) + NSLog(@"willUpdateRow: %@ %@", [theRow description], [theQualifier description]); s = AUTORELEASE([[NSMutableString alloc] init]); Index: sope-core/NGExtensions/NGExtensions/NSString+Ext.h =================================================================== --- sope-core/NGExtensions/NGExtensions/NSString+Ext.h (revision 1626) +++ sope-core/NGExtensions/NGExtensions/NSString+Ext.h (working copy) @@ -30,6 +30,7 @@ @interface NSString(GSAdditions) +#if !GNUSTEP - (NSString *)stringWithoutPrefix:(NSString *)_prefix; - (NSString *)stringWithoutSuffix:(NSString *)_suffix; @@ -39,6 +40,7 @@ - (NSString *)stringByTrimmingLeadSpaces; - (NSString *)stringByTrimmingTailSpaces; - (NSString *)stringByTrimmingSpaces; +#endif /* !GNUSTEP */ /* the following are not available in gstep-base 1.6 ? */ - (NSString *)stringByTrimmingLeadWhiteSpaces; @@ -47,6 +49,8 @@ @end /* NSString(GSAdditions) */ +#if !GNUSTEP + @interface NSMutableString(GNUstepCompatibility) - (void)trimLeadSpaces; @@ -55,6 +59,8 @@ @end /* NSMutableString(GNUstepCompatibility) */ +#endif /* !GNUSTEP */ + #endif /* specific to libFoundation */ Index: sope-core/NGExtensions/FdExt.subproj/NSString+Ext.m =================================================================== --- sope-core/NGExtensions/FdExt.subproj/NSString+Ext.m (revision 1626) +++ sope-core/NGExtensions/FdExt.subproj/NSString+Ext.m (working copy) @@ -39,18 +39,6 @@ : (NSString *)[[self copy] autorelease]; } -- (NSString *)stringByReplacingString:(NSString *)_orignal - withString:(NSString *)_replacement -{ - /* very slow solution .. */ - - if ([self rangeOfString:_orignal].length == 0) - return [[self copy] autorelease]; - - return [[self componentsSeparatedByString:_orignal] - componentsJoinedByString:_replacement]; -} - - (NSString *)stringByTrimmingLeadWhiteSpaces { // should check 'whitespaceAndNewlineCharacterSet' .. @@ -96,6 +84,25 @@ return [[self copy] autorelease]; } +- (NSString *)stringByTrimmingWhiteSpaces +{ + return [[self stringByTrimmingTailWhiteSpaces] + stringByTrimmingLeadWhiteSpaces]; +} + +#ifndef GNUSTEP +- (NSString *)stringByReplacingString:(NSString *)_orignal + withString:(NSString *)_replacement +{ + /* very slow solution .. */ + + if ([self rangeOfString:_orignal].length == 0) + return [[self copy] autorelease]; + + return [[self componentsSeparatedByString:_orignal] + componentsJoinedByString:_replacement]; +} + - (NSString *)stringByTrimmingLeadSpaces { unsigned len; @@ -117,6 +124,7 @@ else return [[self copy] autorelease]; } + - (NSString *)stringByTrimmingTailSpaces { unsigned len; @@ -139,19 +147,17 @@ return [[self copy] autorelease]; } -- (NSString *)stringByTrimmingWhiteSpaces -{ - return [[self stringByTrimmingTailWhiteSpaces] - stringByTrimmingLeadWhiteSpaces]; -} - (NSString *)stringByTrimmingSpaces { return [[self stringByTrimmingTailSpaces] stringByTrimmingLeadSpaces]; } +#endif @end /* NSString(GSAdditions) */ +#if !GNUSTEP + @implementation NSMutableString(GNUstepCompatibility) - (void)trimLeadSpaces @@ -169,6 +175,8 @@ @end /* NSMutableString(GNUstepCompatibility) */ +#endif /* !GNUSTEP */ + @implementation NSString(lfNSURLUtilities) - (BOOL)isAbsoluteURL Index: sope-core/NGExtensions/FdExt.subproj/NSString+Encoding.m =================================================================== --- sope-core/NGExtensions/FdExt.subproj/NSString+Encoding.m (revision 1626) +++ sope-core/NGExtensions/FdExt.subproj/NSString+Encoding.m (working copy) @@ -140,8 +140,12 @@ #ifdef __linux__ +#if __BYTE_ORDER == __LITTLE_ENDIAN static NSString *unicharEncoding = @"UCS-2LE"; #else +static NSString *unicharEncoding = @"UCS-2BE"; +#endif /* __BYTE_ORDER */ +#else static NSString *unicharEncoding = @"UCS-2-INTERNAL"; #endif static int IconvLogEnabled = -1; @@ -149,21 +153,12 @@ static void checkDefaults(void) { NSUserDefaults *ud; - if (IconvLogEnabled != -1) - return; - ud = [NSUserDefaults standardUserDefaults]; - IconvLogEnabled = [ud boolForKey:@"IconvLogEnabled"]?1:0; + if (IconvLogEnabled == -1) { + ud = [NSUserDefaults standardUserDefaults]; + IconvLogEnabled = [ud boolForKey:@"IconvLogEnabled"]?1:0; -#ifdef __linux__ - if (NSHostByteOrder() == NS_BigEndian) { - NSLog(@"Note: using UCS-2 big endian on Linux."); - unicharEncoding = @"UCS-2BE"; + NSLog(@"Note: using '%@' on Linux.", unicharEncoding); } - else { - NSLog(@"Note: using UCS-2 little endian on Linux."); - unicharEncoding = @"UCS-2LE"; - } -#endif } static char *iconv_wrapper(id self, char *_src, unsigned _srcLen, Index: sope-core/NGExtensions/NGQuotedPrintableCoding.m =================================================================== --- sope-core/NGExtensions/NGQuotedPrintableCoding.m (revision 1626) +++ sope-core/NGExtensions/NGQuotedPrintableCoding.m (working copy) @@ -278,7 +278,12 @@ for (cnt = 0; (cnt < _srcLen) && (destCnt < _destLen); cnt++) { char c = _src[cnt]; - if ((c == 9) || + if (c == 95) { // we encode the _, otherwise we'll always decode it as a space! + _dest[destCnt++] = '='; + _dest[destCnt++] = '5'; + _dest[destCnt++] = 'F'; + } + else if ((c == 9) || (c == 10) || (c == 13) || ((c > 31) && (c < 61)) || Index: sope-core/NGExtensions/EOExt.subproj/EOGlobalID+Ext.m =================================================================== --- sope-core/NGExtensions/EOExt.subproj/EOGlobalID+Ext.m (revision 1626) +++ sope-core/NGExtensions/EOExt.subproj/EOGlobalID+Ext.m (working copy) @@ -19,6 +19,7 @@ 02111-1307, USA. */ +#import #import #import Index: sope-core/NGStreams/GNUmakefile.preamble =================================================================== --- sope-core/NGStreams/GNUmakefile.preamble (revision 1626) +++ sope-core/NGStreams/GNUmakefile.preamble (working copy) @@ -1,7 +1,10 @@ # compilation settings +MACHCPU = $(shell echo $$MACHTYPE | cut -f 1 -d '-') + libNGStreams_INCLUDE_DIRS += \ -I$(GNUSTEP_TARGET_CPU)/$(GNUSTEP_TARGET_OS) \ + -I./$(MACHCPU)/$(GNUSTEP_TARGET_OS) \ -INGStreams \ -I../NGExtensions \ -I.. Index: sope-xml/libxmlSAXDriver/libxmlHTMLSAXDriver.h =================================================================== --- sope-xml/libxmlSAXDriver/libxmlHTMLSAXDriver.h (revision 1626) +++ sope-xml/libxmlSAXDriver/libxmlHTMLSAXDriver.h (working copy) @@ -19,6 +19,8 @@ 02111-1307, USA. */ +#include + #include #include #include @@ -34,7 +36,7 @@ @interface libxmlHTMLSAXDriver : NSObject < SaxXMLReader > { - id contentHandler; + NSObject *contentHandler; id dtdHandler; id errorHandler; id entityResolver; Index: sope-xml/libxmlSAXDriver/libxmlHTMLSAXDriver.m =================================================================== --- sope-xml/libxmlSAXDriver/libxmlHTMLSAXDriver.m (revision 1626) +++ sope-xml/libxmlSAXDriver/libxmlHTMLSAXDriver.m (working copy) @@ -30,6 +30,12 @@ #include #include +@interface NSObject (contentHandlerExtensions) + +- (xmlCharEncoding) contentEncoding; + +@end + @interface libxmlHTMLSAXDriver(PrivateMethods) - (void)tearDownParser; @@ -194,10 +200,10 @@ return self->entityResolver; } -- (void)setContentHandler:(id)_handler { +- (void)setContentHandler:(NSObject *)_handler { ASSIGN(self->contentHandler, _handler); } -- (id)contentHandler { +- (NSObject *)contentHandler { return self->contentHandler; } @@ -205,6 +211,7 @@ - (void)setupParserWithDocumentPath:(NSString *)_path { xmlSAXHandler sax; + xmlCharEncoding charEncoding; if (self->ctxt != NULL) { NSLog(@"WARNING(%s): HTML parser context already setup !", @@ -223,14 +230,18 @@ __PRETTY_FUNCTION__, self, activeDriver); } activeDriver = self; - + + if ([self->contentHandler respondsToSelector: @selector (contentEncoding)]) + charEncoding = [self->contentHandler contentEncoding]; + else + charEncoding = XML_CHAR_ENCODING_8859_1; + self->ctxt = htmlCreatePushParserCtxt(&sax /* sax */, NULL /*self*/ /* userdata */, NULL /* chunk */, 0 /* chunklen */, [_path cString] /* filename */, - XML_CHAR_ENCODING_8859_1 - /* encoding */); + charEncoding /* encoding */); self->doc = NULL; } - (void)tearDownParser { Index: sope-xml/libxmlSAXDriver/libxmlSAXDriver.m =================================================================== --- sope-xml/libxmlSAXDriver/libxmlSAXDriver.m (revision 1626) +++ sope-xml/libxmlSAXDriver/libxmlSAXDriver.m (working copy) @@ -614,7 +614,7 @@ xmlParseDocument(ctxt); if (!(((xmlParserCtxtPtr)self->ctxt)->wellFormed)) - NSLog(@"%@: not well formed", _sysId); + NSLog(@"%@: not well formed 1", _sysId); if (((xmlParserCtxtPtr)self->ctxt)->input != NULL && [_sysId length] > 0) { ((xmlParserInputPtr)((xmlParserCtxtPtr)self->ctxt)->input)->filename @@ -737,7 +737,7 @@ } if (!(((xmlParserCtxtPtr)self->ctxt)->wellFormed)) - NSLog(@"%@: not well formed", _sysId); + NSLog(@"%@: not well formed 2", _sysId); ((xmlParserCtxtPtr)self->ctxt)->sax = NULL; xmlFreeParserCtxt(self->ctxt); Index: sope-appserver/mod_ngobjweb/config.c =================================================================== --- sope-appserver/mod_ngobjweb/config.c (revision 1626) +++ sope-appserver/mod_ngobjweb/config.c (working copy) @@ -21,7 +21,7 @@ #include "common.h" -//#define LOG_CONFIG 1 +#define LOG_CONFIG 0 static char *_makeString(char *buf, char *str, int max) { if (buf == NULL) Index: sope-appserver/mod_ngobjweb/NGBufferedDescriptor.c =================================================================== --- sope-appserver/mod_ngobjweb/NGBufferedDescriptor.c (revision 1626) +++ sope-appserver/mod_ngobjweb/NGBufferedDescriptor.c (working copy) @@ -23,6 +23,7 @@ #include #include #include +#include "common.h" #include "NGBufferedDescriptor.h" // returns the number of bytes which where read from the buffer Index: sope-appserver/mod_ngobjweb/GNUmakefile =================================================================== --- sope-appserver/mod_ngobjweb/GNUmakefile (revision 1626) +++ sope-appserver/mod_ngobjweb/GNUmakefile (working copy) @@ -82,7 +82,7 @@ CFLAGS = -Wall -I. -fPIC \ $(APXS_CFLAGS) $(APR_CFLAGS) \ - $(APXS_INCLUDE_DIRS) $(APR_INCLUDE_DIRS) + $(APXS_INCLUDE_DIRS) $(APR_INCLUDE_DIRS) -O0 -ggdb LDFLAGS = $(APXS_LDFLAGS) $(APR_LDFLAGS) -shared -fPIC LDLIBS = $(APXS_LIBS) $(APR_LIBS) @@ -111,8 +111,7 @@ apache-dir : $(MKDIRS) $(GNUSTEP_INSTALLATION_DIR) -install :: apache-dir all - $(INSTALL_PROGRAM) $(product) $(GNUSTEP_INSTALLATION_DIR) +install :: install-usr-libexec :: all $(INSTALL_PROGRAM) $(product) /usr/libexec/httpd/ Index: sope-appserver/NGObjWeb/GNUmakefile.postamble =================================================================== --- sope-appserver/NGObjWeb/GNUmakefile.postamble (revision 1626) +++ sope-appserver/NGObjWeb/GNUmakefile.postamble (working copy) @@ -23,14 +23,20 @@ # install makefiles -after-install :: - $(MKDIRS) $(INSTALL_ROOT_DIR)/$(GNUSTEP_MAKEFILES)/Additional/ - $(INSTALL_DATA) ngobjweb.make $(INSTALL_ROOT_DIR)/$(GNUSTEP_MAKEFILES)/Additional/ngobjweb.make +after-install :: $(DESTDIR)/$(GNUSTEP_MAKEFILES)/Additional/ngobjweb.make ifneq ($(GNUSTEP_MAKE_VERSION),1.3.0) -after-install :: +after-install :: $(DESTDIR)/$(GNUSTEP_MAKEFILES)/woapp.make $(DESTDIR)/$(GNUSTEP_MAKEFILES)/wobundle.make +endif + +$(DESTDIR)/$(GNUSTEP_MAKEFILES)/Additional/ngobjweb.make: ngobjweb.make + $(MKDIRS) $(DESTDIR)/$(GNUSTEP_MAKEFILES)/Additional/ + $(INSTALL_DATA) ngobjweb.make $(DESTDIR)/$(GNUSTEP_MAKEFILES)/Additional/ngobjweb.make + +$(DESTDIR)/$(GNUSTEP_MAKEFILES)/woapp.make: woapp-gs.make $(INSTALL_DATA) woapp-gs.make \ - $(INSTALL_ROOT_DIR)/$(GNUSTEP_MAKEFILES)/woapp.make + $(DESTDIR)/$(GNUSTEP_MAKEFILES)/woapp.make + +$(DESTDIR)/$(GNUSTEP_MAKEFILES)/wobundle.make: wobundle-gs.make $(INSTALL_DATA) wobundle-gs.make \ - $(INSTALL_ROOT_DIR)/$(GNUSTEP_MAKEFILES)/wobundle.make -endif + $(DESTDIR)/$(GNUSTEP_MAKEFILES)/wobundle.make Index: sope-appserver/NGObjWeb/WOContext.m =================================================================== --- sope-appserver/NGObjWeb/WOContext.m (revision 1626) +++ sope-appserver/NGObjWeb/WOContext.m (working copy) @@ -64,11 +64,13 @@ static BOOL testNSURLs = NO; static BOOL newCURLStyle = NO; static NSString *WOApplicationSuffix = nil; +static NSURL *redirectURL = nil; + (void)initialize { static BOOL didInit = NO; NSUserDefaults *ud; NSString *cn; + NSString *url; if (didInit) return; @@ -91,6 +93,9 @@ debugCursor = [ud boolForKey:@"WODebugCursor"] ? 1 : 0; debugComponentAwake = [ud boolForKey:@"WODebugComponentAwake"]; WOApplicationSuffix = [[ud stringForKey:@"WOApplicationSuffix"] copy]; + url = [ud stringForKey:@"WOApplicationRedirectURL"]; + if (url != nil) + redirectURL = [NSURL URLWithString: url]; } + (id)contextWithRequest:(WORequest *)_r { @@ -503,6 +508,11 @@ return nil; } + if (redirectURL) { + // Use URL from user defaults (WOApplicationRedirectURL) + return redirectURL; + } + if ((serverURL = [rq headerForKey:@"x-webobjects-server-url"]) == nil) { if ((host = [rq headerForKey:@"host"])) serverURL = [@"http://" stringByAppendingString:host]; Index: sope-appserver/NGObjWeb/ChangeLog =================================================================== --- sope-appserver/NGObjWeb/ChangeLog (revision 1626) +++ sope-appserver/NGObjWeb/ChangeLog (working copy) @@ -1,3 +1,8 @@ +2008-09-01 Ludovic Marcotte + + * WORequest.m ([WORequest -browserLanguages]): we ensure + "language" never is an empty string, otherwise we ignore it. + 2008-05-21 Sebastian Reitenbach * WOHTTPURLHandle.m: add 'query' component of URL to request path Index: sope-appserver/NGObjWeb/DAVPropMap.plist =================================================================== --- sope-appserver/NGObjWeb/DAVPropMap.plist (revision 1626) +++ sope-appserver/NGObjWeb/DAVPropMap.plist (working copy) @@ -24,13 +24,19 @@ "{DAV:}status" = "davStatus"; "{http://apache.org/dav/props/}executable" = "davIsExecutable"; + /* RFC 3253 - Versioning Extensions to WebDAV (DeltaV) */ + "{DAV:}comment" = "davComment"; + "{DAV:}creator-displayname" = "davCreatorDisplayName"; + "{DAV:}supported-method-set" = "davSupportedMethodSet"; + "{DAV:}supported-live-property-set" = "davSupportedLivePropertySet"; + "{DAV:}supported-report-set" = "davSupportedReportSet"; + /* used with Apple WebDAV */ "{DAV:}quota" = davQuota; "{DAV:}quotaused" = davQuotaUsed; "{http://www.apple.com/webdav_fs/props/}appledoubleheader"=appleDoubleHeader; /* Novell NetDrive */ - "{DAV:}owner" = davOwner; "{DAV:}locktoken" = davLockToken; "{DAV:}activelock" = davActiveLock; // TODO: non-standard?, also used by WebDrive @@ -120,12 +126,31 @@ "{http://ucb.openoffice.org/dav/props/}IsRemoveable" = isOOoRemoveable; "{http://ucb.openoffice.org/dav/props/}IsVolume" = isOOoVolume; "{http://ucb.openoffice.org/dav/props/}TargetURL" = davOOoTargetURL; - + /* WebDAV ACL */ + "{DAV:}owner" = davOwner; + "{DAV:}group" = davGroup; + "{DAV:}supported-privilege-set" = davSupportedPrivilegeSet; + "{DAV:}principal-collection-set" = davPrincipalCollectionSet; + "{DAV:}acl" = davAcl; + "{DAV:}acl-restrictions" = davAclRestrictions; "{DAV:}current-user-privilege-set" = davCurrentUserPrivilegeSet; + "{DAV:}inherited-acl-set" = davInheritedAclSet; + "{DAV:}principal-URL" = davPrincipalURL; + "{DAV:}alternate-URI-set" = davAlternateURISet; + "{DAV:}group-member-set" = davGroupMemberSet; + "{DAV:}group-membership" = davGroupMembership; /* CalDAV */ + "{urn:ietf:params:xml:ns:caldav}calendar-data" = davCalendarData; + "{urn:ietf:params:xml:ns:caldav}calendar-description" = davDescription; "{urn:ietf:params:xml:ns:caldav}calendar-home-set" = davCalendarHomeSet; + "{urn:ietf:params:xml:ns:caldav}calendar-user-address-set" = + davCalendarUserAddressSet; + "{urn:ietf:params:xml:ns:caldav}calendar-free-busy-set" = + davCalendarFreeBusySet; + "{urn:ietf:params:xml:ns:caldav}schedule-inbox-URL" = davCalendarScheduleInboxURL; + "{urn:ietf:params:xml:ns:caldav}schedule-outbox-URL" = davCalendarScheduleOutboxURL; "{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set" = davCalendarComponentSet; "{urn:ietf:params:xml:ns:caldav}supported-calendar-data" = @@ -138,13 +163,14 @@ "{urn:ietf:params:xml:ns:carddav}addressbook-description" = davDescription; /* Apple CalServer */ - "{http://apple.com/ns/calendarserver/}dropbox-home-URL" = - davDropboxHomeURL; - "{http://apple.com/ns/calendarserver/}notifications-URL" = - davNotificationsURL; - "{com.apple.ical:}calendarcolor" = davCalendarColor; + "{http://calendarserver.org/ns/}dropbox-home-URL" = davDropboxHomeURL; + "{http://calendarserver.org/ns/}notifications-URL" = davNotificationsURL; "{http://calendarserver.org/ns/}getctag" = davCollectionTag; + /* Apple extensions */ + "{http://apple.com/ns/ical/}calendar-color" = davCalendarColor; + "{http://apple.com/ns/ical/}calendar-order" = davCalendarOrder; + /* GroupDAV */ "{http://www.groupdav.org/}component-set" = gdavComponentSet; "{http://groupdav.org/}component-set" = gdavComponentSet; Index: sope-appserver/NGObjWeb/WebDAV/SaxDAVHandler.m =================================================================== --- sope-appserver/NGObjWeb/WebDAV/SaxDAVHandler.m (revision 1626) +++ sope-appserver/NGObjWeb/WebDAV/SaxDAVHandler.m (working copy) @@ -655,6 +655,7 @@ if (self->responses == nil) self->responses = [[NSMutableArray alloc] initWithCapacity:64]; } + break; case 'n': Index: sope-appserver/NGObjWeb/WebDAV/SoObjectWebDAVDispatcher.m =================================================================== --- sope-appserver/NGObjWeb/WebDAV/SoObjectWebDAVDispatcher.m (revision 1626) +++ sope-appserver/NGObjWeb/WebDAV/SoObjectWebDAVDispatcher.m (working copy) @@ -1523,16 +1523,16 @@ - (id)doREPORT:(WOContext *)_ctx { id domDocument; WORequest *rq; - NSString *mname; + NSString *mname, *ctype; id method, resultObject; rq = [_ctx request]; /* ensure XML */ - if ((![[rq headerForKey:@"content-type"] hasPrefix:@"text/xml"]) && - (![[rq headerForKey:@"content-type"] hasPrefix:@"application/xml"])) - { + ctype = [rq headerForKey:@"content-type"]; + if (!([ctype hasPrefix:@"text/xml"] + || [ctype hasPrefix:@"application/xml"])) { return [self httpException:400 /* invalid request */ reason:@"XML entity expected for WebDAV REPORT."]; } @@ -1601,8 +1601,60 @@ /* CalDAV */ - (id)doMKCALENDAR:(WOContext *)_ctx { - return [self httpException:405 /* method not allowed */ - reason:@"CalDAV calendar creation not yet implemented."]; + SoSecurityManager *sm; + NSException *e; + NSString *pathInfo; + + pathInfo = [_ctx pathInfo]; + if (![pathInfo isNotEmpty]) { + /* MKCALENDAR target already exists ... */ + WOResponse *r; + + [self logWithFormat:@"MKCALENDAR target exists !"]; + + r = [_ctx response]; + [r setStatus:405 /* method not allowed */]; + [r appendContentString:@"calendar collection already exists !"]; + return r; + } + + /* check permissions */ + + sm = [_ctx soSecurityManager]; + e = [sm validatePermission:SoPerm_AddFolders + onObject:self->object + inContext:_ctx]; + if (e != nil) return e; + + /* check whether all the parent collections are available */ + if ([pathInfo rangeOfString:@"/"].length > 0) { + return [self httpException:409 /* Conflict */ + reason: + @"invalid WebDAV MKCALENDAR request, first create all " + @"parent collections !"]; + } + + /* check whether the object supports creating collections */ + + if (![self->object respondsToSelector: + @selector(davCreateCalendarCollection:inContext:)]) { + /* Note: this should never happen, as this is implemented on NSObject */ + + [self logWithFormat:@"MKCALENDAR: object '%@' path-info '%@'", + self->object, pathInfo]; + return [self httpException:405 /* not allowed */ + reason: + @"this object cannot create a new calendar collection with MKCALENDAR"]; + } + + if ((e = [self->object davCreateCalendarCollection:pathInfo inContext:_ctx])) { + [self debugWithFormat:@"creation of calendar collection '%@' failed: %@", + pathInfo, e]; + return e; + } + + [self debugWithFormat:@"created calendar collection."]; + return [NSNumber numberWithBool:YES]; } /* DAV access control lists */ Index: sope-appserver/NGObjWeb/WebDAV/SoWebDAVRenderer.m =================================================================== --- sope-appserver/NGObjWeb/WebDAV/SoWebDAVRenderer.m (revision 1626) +++ sope-appserver/NGObjWeb/WebDAV/SoWebDAVRenderer.m (working copy) @@ -277,7 +277,8 @@ ok = [self renderLockToken:_object inContext:_ctx]; break; case 'M': - if ([m isEqualToString:@"MKCOL"]) + if ([m isEqualToString:@"MKCOL"] + || [m isEqualToString:@"MKCALENDAR"]) ok = [self renderMkColResult:_object inContext:_ctx]; else if ([m isEqualToString:@"MOVE"]) { ok = [self renderStatusResult:_object Index: sope-appserver/NGObjWeb/WebDAV/SoObject+SoDAV.h =================================================================== --- sope-appserver/NGObjWeb/WebDAV/SoObject+SoDAV.h (revision 1626) +++ sope-appserver/NGObjWeb/WebDAV/SoObject+SoDAV.h (working copy) @@ -62,6 +62,7 @@ properties:(NSDictionary *)_props inContext:(id)_ctx; - (NSException *)davCreateCollection:(NSString *)_name inContext:(id)_ctx; +- (NSException *)davCreateCalendarCollection:(NSString *)_name inContext:(id)_ctx; - (NSException *)davMoveToTargetObject:(id)_target newName:(NSString *)_name inContext:(id)_ctx; Index: sope-appserver/NGObjWeb/WODirectAction.m =================================================================== --- sope-appserver/NGObjWeb/WODirectAction.m (revision 1626) +++ sope-appserver/NGObjWeb/WODirectAction.m (working copy) @@ -46,7 +46,7 @@ } - (id)initWithContext:(WOContext *)_ctx { if ((self = [self initWithRequest:[_ctx request]])) { - self->context = [_ctx retain]; + self->context = _ctx; } return self; } @@ -54,16 +54,16 @@ return [self initWithRequest:nil]; } -- (void)dealloc { - [self->context release]; - [super dealloc]; -} +// - (void)dealloc { +// [self->context release]; +// [super dealloc]; +// } /* accessors */ - (WOContext *)context { if (self->context == nil) - self->context = [[[WOApplication application] context] retain]; + self->context = [[WOApplication application] context]; return self->context; } Index: sope-appserver/NGObjWeb/DynamicElements/WOHyperlinkInfo.m =================================================================== --- sope-appserver/NGObjWeb/DynamicElements/WOHyperlinkInfo.m (revision 1626) +++ sope-appserver/NGObjWeb/DynamicElements/WOHyperlinkInfo.m (working copy) @@ -216,6 +216,12 @@ assocCount++; } } + if (count > 0) { + if ((self->isAbsolute = OWGetProperty(_config, @"absolute"))) { + count--; + assocCount++; + } + } self->rest = _config; Index: sope-appserver/NGObjWeb/DynamicElements/_WOComplexHyperlink.m =================================================================== --- sope-appserver/NGObjWeb/DynamicElements/_WOComplexHyperlink.m (revision 1626) +++ sope-appserver/NGObjWeb/DynamicElements/_WOComplexHyperlink.m (working copy) @@ -41,6 +41,7 @@ WOAssociation *string; WOAssociation *target; WOAssociation *disabled; + WOAssociation *isAbsolute; WOElement *template; /* new in WO4: */ @@ -360,6 +361,7 @@ { if ((self = [super initWithName:_name hyperlinkInfo:_info template:_t])) { self->href = _info->href; + self->isAbsolute = _info->isAbsolute; } return self; } @@ -375,8 +377,11 @@ // TODO: we need a binding to disable rewriting! NSRange r; + if ([[self->isAbsolute valueInContext:_ctx] boolValue] == YES) + return NO; + r.length = [_s length]; - + /* do not rewrite pure fragment URLs */ if (r.length > 0 && [_s characterAtIndex:0] == '#') return NO; Index: sope-appserver/NGObjWeb/DynamicElements/WOHyperlinkInfo.h =================================================================== --- sope-appserver/NGObjWeb/DynamicElements/WOHyperlinkInfo.h (revision 1626) +++ sope-appserver/NGObjWeb/DynamicElements/WOHyperlinkInfo.h (working copy) @@ -41,7 +41,8 @@ WOAssociation *pageName; WOAssociation *actionClass; WOAssociation *directActionName; - + WOAssociation *isAbsolute; + BOOL sidInUrl; /* 'ivar' associations */ Index: sope-appserver/NGObjWeb/SoObjects/SoObject.m =================================================================== --- sope-appserver/NGObjWeb/SoObjects/SoObject.m (revision 1626) +++ sope-appserver/NGObjWeb/SoObjects/SoObject.m (working copy) @@ -39,22 +39,34 @@ static int debugLookup = -1; static int debugBaseURL = -1; static int useRelativeURLs = -1; +static int redirectInitted = -1; +static NSURL *redirectURL = nil; + static void _initialize(void) { + NSString *url; + NSUserDefaults *ud; + + ud = [NSUserDefaults standardUserDefaults]; + if (debugLookup == -1) { - debugLookup = [[NSUserDefaults standardUserDefaults] - boolForKey:@"SoDebugKeyLookup"] ? 1 : 0; + debugLookup = [ud boolForKey:@"SoDebugKeyLookup"] ? 1 : 0; NSLog(@"Note(SoObject): SoDebugKeyLookup is enabled!"); } if (debugBaseURL == -1) { - debugBaseURL = [[NSUserDefaults standardUserDefaults] - boolForKey:@"SoDebugBaseURL"] ? 1 : 0; + debugBaseURL = [ud boolForKey:@"SoDebugBaseURL"] ? 1 : 0; NSLog(@"Note(SoObject): SoDebugBaseURL is enabled!"); } if (useRelativeURLs == -1) { - useRelativeURLs = [[NSUserDefaults standardUserDefaults] - boolForKey:@"WOUseRelativeURLs"] ?1:0; + useRelativeURLs = [ud boolForKey:@"WOUseRelativeURLs"] ?1:0; NSLog(@"Note(SoObject): relative base URLs are enabled."); } + if (redirectInitted == -1) { + url = [ud stringForKey:@"WOApplicationRedirectURL"]; + if ([url length]) { + redirectURL = [[NSURL alloc] initWithString: url]; + } + redirectInitted = 1; + } } /* classes */ @@ -318,56 +330,61 @@ rq = [_ctx request]; ms = [[NSMutableString alloc] initWithCapacity:128]; + + if (redirectURL) { + [ms appendString: [redirectURL absoluteString]]; + } + else { + if (!useRelativeURLs) { + port = [[rq headerForKey:@"x-webobjects-server-port"] intValue]; - if (!useRelativeURLs) { - port = [[rq headerForKey:@"x-webobjects-server-port"] intValue]; - - /* this is actually a bug in Apache */ - if (port == 0) { - static BOOL didWarn = NO; - if (!didWarn) { - [self warnWithFormat:@"(%s:%i): got an empty port from Apache!", - __PRETTY_FUNCTION__, __LINE__]; - didWarn = YES; + /* this is actually a bug in Apache */ + if (port == 0) { + static BOOL didWarn = NO; + if (!didWarn) { + [self warnWithFormat:@"(%s:%i): got an empty port from Apache!", + __PRETTY_FUNCTION__, __LINE__]; + didWarn = YES; + } + port = 80; } - port = 80; - } - if ((tmp = [rq headerForKey:@"host"]) != nil) { - /* check whether we have a host header with port */ - if ([tmp rangeOfString:@":"].length == 0) - tmp = nil; - } - if (tmp != nil) { /* we have a host header with port */ - isHTTPS = - [[rq headerForKey:@"x-webobjects-server-url"] hasPrefix:@"https"]; - [ms appendString:isHTTPS ? @"https://" : @"http://"]; - [ms appendString:tmp]; - } - else if ((tmp = [rq headerForKey:@"x-webobjects-server-url"]) != nil) { - /* sometimes the URL is just wrong! (suggests port 80) */ - if ([tmp hasSuffix:@":0"] && [tmp length] > 2) { // TODO: bad bad bad - [self warnWithFormat:@"%s: got incorrect URL from Apache: '%@'", - __PRETTY_FUNCTION__, tmp]; - tmp = [tmp substringToIndex:([tmp length] - 2)]; + if ((tmp = [rq headerForKey:@"host"]) != nil) { + /* check whether we have a host header with port */ + if ([tmp rangeOfString:@":"].length == 0) + tmp = nil; } - else if ([tmp hasSuffix:@":443"] && [tmp hasPrefix:@"http://"]) { - /* see OGo bug #1435, Debian Apache hack */ - [self warnWithFormat:@"%s: got 'http' protocol but 443 port, " - @"assuming Debian/Apache bug (OGo #1435): '%@'", - __PRETTY_FUNCTION__, tmp]; - tmp = [tmp substringWithRange:NSMakeRange(4, [tmp length] - 4 - 4)]; - tmp = [@"https" stringByAppendingString:tmp]; + if (tmp != nil) { /* we have a host header with port */ + isHTTPS = + [[rq headerForKey:@"x-webobjects-server-url"] hasPrefix:@"https"]; + [ms appendString:isHTTPS ? @"https://" : @"http://"]; + [ms appendString:tmp]; } - [ms appendString:tmp]; - } - else { - // TODO: isHTTPS always no in this case? - [ms appendString:isHTTPS ? @"https://" : @"http://"]; + else if ((tmp = [rq headerForKey:@"x-webobjects-server-url"]) != nil) { + /* sometimes the URL is just wrong! (suggests port 80) */ + if ([tmp hasSuffix:@":0"] && [tmp length] > 2) { // TODO: bad bad bad + [self warnWithFormat:@"%s: got incorrect URL from Apache: '%@'", + __PRETTY_FUNCTION__, tmp]; + tmp = [tmp substringToIndex:([tmp length] - 2)]; + } + else if ([tmp hasSuffix:@":443"] && [tmp hasPrefix:@"http://"]) { + /* see OGo bug #1435, Debian Apache hack */ + [self warnWithFormat:@"%s: got 'http' protocol but 443 port, " + @"assuming Debian/Apache bug (OGo #1435): '%@'", + __PRETTY_FUNCTION__, tmp]; + tmp = [tmp substringWithRange:NSMakeRange(4, [tmp length] - 4 - 4)]; + tmp = [@"https" stringByAppendingString:tmp]; + } + [ms appendString:tmp]; + } + else { + // TODO: isHTTPS always no in this case? + [ms appendString:isHTTPS ? @"https://" : @"http://"]; - [ms appendString:[rq headerForKey:@"x-webobjects-server-name"]]; - if ((isHTTPS ? (port != 443) : (port != 80)) && port != 0) - [ms appendFormat:@":%i", port]; + [ms appendString:[rq headerForKey:@"x-webobjects-server-name"]]; + if ((isHTTPS ? (port != 443) : (port != 80)) && port != 0) + [ms appendFormat:@":%i", port]; + } } } Index: sope-appserver/NGObjWeb/SoObjects/SoObject+Traversal.m =================================================================== --- sope-appserver/NGObjWeb/SoObjects/SoObject+Traversal.m (revision 1626) +++ sope-appserver/NGObjWeb/SoObjects/SoObject+Traversal.m (working copy) @@ -195,7 +195,8 @@ isCreateIfMissingMethod = YES; else if ([m isEqualToString:@"PROPPATCH"]) isCreateIfMissingMethod = YES; - else if ([m isEqualToString:@"MKCOL"]) + else if ([m isEqualToString:@"MKCOL"] + || [m isEqualToString:@"MKCALENDAR"]) /* this one is strictly creating */ isCreateMethod = YES; // TODO: the following are only create-if-missing on the target! Index: sope-appserver/NGObjWeb/WOHttpAdaptor/WOHttpTransaction.m =================================================================== --- sope-appserver/NGObjWeb/WOHttpAdaptor/WOHttpTransaction.m (revision 1626) +++ sope-appserver/NGObjWeb/WOHttpAdaptor/WOHttpTransaction.m (working copy) @@ -32,6 +32,7 @@ #include #include #include +#include #include "common.h" #include @@ -1042,6 +1043,12 @@ - (void)parser:(NGMimePartParser *)_parser didParseHeader:(NGHashMap *)_header { } +- (NGMimeType *)parser:(id)_parser + contentTypeOfPart:(id)_part +{ + return [NGMimeType mimeType: @"text/plain; charset=utf-8"]; +} + @end /* WOHttpAdaptor */ @implementation WOCoreApplication(SimpleParserSelection) Index: sope-appserver/NGObjWeb/Defaults.plist =================================================================== --- sope-appserver/NGObjWeb/Defaults.plist (revision 1626) +++ sope-appserver/NGObjWeb/Defaults.plist (working copy) @@ -216,7 +216,7 @@ SoWebDAVDisableCrossHostMoveCheck = NO; SoWebDAVDefaultAllowMethods = ( - GET, HEAD, POST, OPTIONS, MKCOL, DELETE, PUT, + GET, HEAD, POST, OPTIONS, MKCOL, MKCALENDAR, DELETE, PUT, LOCK, UNLOCK, COPY, MOVE /* , NOTIFY, POLL, SUBSCRIBE, UNSUBSCRIBE */ ); @@ -224,6 +224,7 @@ SoWebDAVDetectionMethods = ( OPTIONS, MKCOL, + MKCALENDAR, PROPFIND, PROPPATCH, DELETE, Index: sope-appserver/NGObjWeb/WORequest.m =================================================================== --- sope-appserver/NGObjWeb/WORequest.m (revision 1626) +++ sope-appserver/NGObjWeb/WORequest.m (working copy) @@ -597,6 +597,8 @@ if (r.length > 0) language = [language substringToIndex:r.location]; language = [language stringByTrimmingSpaces]; + + if (![language length]) continue; /* check in map */ if ((tmp = [self languageForBrowserLanguageCode:language])) Index: sope-appserver/NGObjWeb/NGHttp/NGHttpRequest.h =================================================================== --- sope-appserver/NGObjWeb/NGHttp/NGHttpRequest.h (revision 1626) +++ sope-appserver/NGObjWeb/NGHttp/NGHttpRequest.h (working copy) @@ -62,6 +62,10 @@ /* RFC 3253 (DeltaV) */ NGHttpMethod_REPORT, NGHttpMethod_VERSION_CONTROL, + /* RFC 4791 (CalDAV) */ + NGHttpMethod_MKCALENDAR, + /* http://ietfreport.isoc.org/idref/draft-daboo-carddav/ (CardDAV) */ + NGHttpMethod_MKADDRESSBOOK, NGHttpMethod_last } NGHttpMethod; Index: sope-appserver/NGObjWeb/NGHttp/NGHttpRequest.m =================================================================== --- sope-appserver/NGObjWeb/NGHttp/NGHttpRequest.m (revision 1626) +++ sope-appserver/NGObjWeb/NGHttp/NGHttpRequest.m (working copy) @@ -59,6 +59,10 @@ /* RFC 3253 (DeltaV) */ @"REPORT", @"VERSION-CONTROL", + /* RFC 4791 (CalDAV) */ + @"MKCALENDAR", + /* http://ietfreport.isoc.org/idref/draft-daboo-carddav/ (CardDAV) */ + @"MKADDRESSBOOK", nil };