Index: sope-ldap/NGLdap/NGLdapEntry.m =================================================================== --- sope-ldap/NGLdap/NGLdapEntry.m (revision 1664) +++ sope-ldap/NGLdap/NGLdapEntry.m (working copy) @@ -105,14 +105,16 @@ - (NGLdapAttribute *)attributeWithName:(NSString *)_name { NSEnumerator *e; NGLdapAttribute *a; - + NSString *upperName; + if (_name == nil) return nil; + upperName = [_name uppercaseString]; e = [self->attributes objectEnumerator]; while ((a = [e nextObject])) { - if ([[a attributeName] isEqualToString:_name]) + if ([[[a attributeName] uppercaseString] isEqualToString:upperName]) return a; } return nil; Index: sope-ldap/NGLdap/ChangeLog =================================================================== --- sope-ldap/NGLdap/ChangeLog (revision 1664) +++ sope-ldap/NGLdap/ChangeLog (working copy) @@ -1,3 +1,8 @@ +2009-08-13 Wolfgang Sourdeau + + * NGLdapEntry.m (-attributeWithName:): attribute names are now + accessed in a case-insensitive way. + 2009-04-02 Wolfgang Sourdeau * NGLdapConnection.m (useSSL,startTLS): new method enabling Index: sope-gdl1/PostgreSQL/PostgreSQL72Channel.m =================================================================== --- sope-gdl1/PostgreSQL/PostgreSQL72Channel.m (revision 1664) +++ 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/MySQL/MySQL4Channel.m =================================================================== --- sope-gdl1/MySQL/MySQL4Channel.m (revision 1664) +++ sope-gdl1/MySQL/MySQL4Channel.m (working copy) @@ -755,6 +755,39 @@ return pkey; } +/* 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 INT NOT NULL,\n" + @" c_lastmodified INT NOT NULL,\n" + @" c_version INT NOT NULL,\n" + @" c_deleted INT 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 /* MySQL4Channel */ void __link_MySQL4Channel() { Index: sope-gdl1/Oracle8/OracleAdaptorChannel.m =================================================================== --- sope-gdl1/Oracle8/OracleAdaptorChannel.m (revision 1664) +++ sope-gdl1/Oracle8/OracleAdaptorChannel.m (working copy) @@ -1,7 +1,7 @@ /* ** OracleAdaptorChannel.m ** -** Copyright (c) 2007 Inverse groupe conseil inc. and Ludovic Marcotte +** Copyright (c) 2007-2009 Inverse inc. and Ludovic Marcotte ** ** Author: Ludovic Marcotte ** @@ -30,6 +30,11 @@ #import +#include + +static BOOL debugOn = NO; +static int maxTry = 3; +static int maxSleep = 500; // // // @@ -41,10 +46,11 @@ @implementation OracleAdaptorChannel (Private) -- (void) _cleanup +- (void) _cleanup { column_info *info; int c; + sword result; [_resultSetProperties removeAllObjects]; @@ -58,11 +64,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]; } @@ -78,8 +102,7 @@ // @implementation OracleAdaptorChannel -static void -DBTerminate() +static void DBTerminate() { if (OCITerminate(OCI_DEFAULT)) NSLog(@"FAILED: OCITerminate()"); @@ -89,6 +112,11 @@ + (void) initialize { + NSUserDefaults *ud; + + ud = [NSUserDefaults standardUserDefaults]; + debugOn = [ud boolForKey: @"OracleAdaptorDebug"]; + // We Initialize the OCI process environment. if (OCIInitialize((ub4)OCI_DEFAULT, (dvoid *)0, (dvoid * (*)(dvoid *, size_t)) 0, @@ -156,14 +184,17 @@ [super closeChannel]; // We logoff from the database. - if (OCILogoff(_oci_ctx, _oci_err)) + if (!_oci_ctx || !_oci_err || OCILogoff(_oci_ctx, _oci_err)) { NSLog(@"FAILED: OCILogoff()"); } + if (_oci_ctx) + OCIHandleFree(_oci_ctx, OCI_HTYPE_SVCCTX); - OCIHandleFree(_oci_ctx, OCI_HTYPE_SVCCTX); - OCIHandleFree(_oci_err, OCI_HTYPE_ERROR); + if (_oci_err) + OCIHandleFree(_oci_err, OCI_HTYPE_ERROR); + // OCIHandleFree(_oci_env, OCI_HTYPE_ENV); _oci_ctx = (OCISvcCtx *)0; @@ -177,7 +208,8 @@ // - (void) dealloc { - //NSLog(@"OracleAdaptorChannel: -dealloc"); + if (debugOn) + NSLog(@"OracleAdaptorChannel: -dealloc"); [self _cleanup]; @@ -222,7 +254,7 @@ { EOAttribute *attribute; OCIParam *param; - + int rCount; column_info *info; ub4 i, clen, count; text *sql, *cname; @@ -231,6 +263,9 @@ [self _cleanup]; + if (debugOn) + [self logWithFormat: @"expression: %@", theExpression]; + if (!theExpression || ![theExpression length]) { [NSException raise: @"OracleInvalidExpressionException" @@ -244,7 +279,9 @@ } sql = (text *)[theExpression UTF8String]; - + + rCount = 0; + retry: // We alloc our statement handle if ((status = OCIHandleAlloc((dvoid *)_oci_env, (dvoid **)&_current_stm, (ub4)OCI_HTYPE_STMT, (CONST size_t) 0, (dvoid **) 0))) { @@ -264,13 +301,39 @@ // We check if we're doing a SELECT and if so, we're fetching data! OCIAttrGet(_current_stm, OCI_HTYPE_STMT, &type, 0, OCI_ATTR_STMT_TYPE, _oci_err); self->isFetchInProgress = (type == OCI_STMT_SELECT ? YES : NO); - + // We execute our statement. Not that we _MUST_ set iter to 0 for non-SELECT statements. if ((status = OCIStmtExecute(_oci_ctx, _current_stm, _oci_err, (self->isFetchInProgress ? (ub4)0 : (ub4)1), (ub4)0, (CONST OCISnapshot *)NULL, (OCISnapshot *)NULL, ([(OracleAdaptorContext *)[self adaptorContext] autoCommit] ? OCI_COMMIT_ON_SUCCESS : OCI_DEFAULT)))) { + ub4 serverStatus; + checkerr(_oci_err, status); NSLog(@"Statement execute failed (OCI_ERROR): %@", theExpression); + + // We check to see if we lost connection and need to reconnect. + serverStatus = 0; + OCIAttrGet((dvoid *)_oci_env, OCI_HTYPE_SERVER, (dvoid *)&serverStatus, (ub4 *)0, OCI_ATTR_SERVER_STATUS, _oci_err); + + if (serverStatus == OCI_SERVER_NOT_CONNECTED) + { + // We cleanup our previous handles + [self cancelFetch]; + [self closeChannel]; + + // We try to reconnect a couple of times before giving up... + while (rCount < maxTry) + { + usleep(maxSleep); + rCount++; + + if ([self openChannel]) + { + NSLog(@"Connection re-established to Oracle - retrying to process the statement."); + goto retry; + } + } + } return NO; } @@ -302,7 +365,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]; @@ -394,16 +459,17 @@ return NO; } - if (OCIEnvInit((OCIEnv **)&_oci_env, (ub4)OCI_DEFAULT, (size_t)0, (dvoid **)0)) { NSLog(@"FAILED: OCIEnvInit()"); + [self closeChannel]; return NO; } if (OCIHandleAlloc((dvoid *)_oci_env, (dvoid *)&_oci_err, (ub4)OCI_HTYPE_ERROR, (size_t)0, (dvoid **)0)) { NSLog(@"FAILED: OCIHandleAlloc() on errhp"); + [self closeChannel]; return NO; } @@ -414,7 +480,10 @@ // Under Oracle 10g, the third parameter of OCILogon() has the form: [//]host[:port][/service_name] // See http://download-west.oracle.com/docs/cd/B12037_01/network.101/b10775/naming.htm#i498306 for // all juicy details. - database = [[NSString stringWithFormat:@"%@:%@", [o serverName], [o port]] UTF8String]; + if ([o serverName] && [o port]) + database = [[NSString stringWithFormat:@"%@:%@/%@", [o serverName], [o port], [o databaseName]] UTF8String]; + else + database = [[o databaseName] UTF8String]; // We logon to the database. if (OCILogon(_oci_env, _oci_err, &_oci_ctx, (const OraText*)username, strlen(username), @@ -422,6 +491,7 @@ { NSLog(@"FAILED: OCILogon(). username = %s password = %s" @" database = %s", username, password, database); + [self closeChannel]; return NO; } @@ -438,6 +508,11 @@ { sword status; + // We check if our connection is open prior to trying to fetch any data. OCIStmtFetch2() returns + // NO error code if the OCI environment is set up but the OCILogon() has failed. + if (![self isOpen]) + return nil; + status = OCIStmtFetch2(_current_stm, _oci_err, (ub4)1, (ub4)OCI_FETCH_NEXT, (sb4)0, (ub4)OCI_DEFAULT); if (status == OCI_NO_DATA) @@ -609,7 +684,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 1664) +++ 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-mime/NGImap4/NGImap4Functions.m =================================================================== --- sope-mime/NGImap4/NGImap4Functions.m (revision 1664) +++ sope-mime/NGImap4/NGImap4Functions.m (working copy) @@ -367,3 +367,16 @@ } @end /* NGImap4FolderHandler */ + +NSString * +SaneFolderName(NSString *folderName) +{ + NSString *saneFName; + + saneFName = [[folderName stringByReplacingString: @"\\" + withString: @"\\\\"] + stringByReplacingString: @"\"" + withString: @"\\\""]; + + return saneFName; +} Index: sope-mime/NGImap4/NGImap4Client.h =================================================================== --- sope-mime/NGImap4/NGImap4Client.h (revision 1664) +++ sope-mime/NGImap4/NGImap4Client.h (working copy) @@ -62,6 +62,8 @@ NGImap4ResponseNormalizer *normer; NSMutableArray *responseReceiver; + BOOL loggedIn; + BOOL isLogin; unsigned tagId; @@ -117,9 +119,11 @@ - (NSDictionary *)noop; - (NSDictionary *)capability; +- (NSDictionary *)namespace; - (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 +142,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 1664) +++ 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 /* @@ -110,6 +114,9 @@ static BOOL ImapDebugEnabled = NO; static NSArray *Imap4SystemFlags = nil; +static NSMutableDictionary *capabilities; +static NSMutableDictionary *namespaces; + - (BOOL)useSSL { return self->useSSL; } @@ -140,6 +147,9 @@ Imap4SystemFlags = [[NSArray alloc] initWithObjects: @"seen", @"answered", @"deleted", @"draft", nil]; + + capabilities = [[NSMutableDictionary alloc] init]; + namespaces = [[NSMutableDictionary alloc] init]; } /* constructors */ @@ -195,11 +205,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 +470,8 @@ - (void)reconnect { if ([self->context lastException] != nil) return; - - [self closeConnection]; + + [self closeConnection]; self->tagId = 0; [self openConnection]; @@ -481,6 +494,7 @@ */ NGHashMap *map; NSString *s, *log; + NSDictionary *response; if (self->isLogin ) return nil; @@ -499,7 +513,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 +526,8 @@ map = [self processCommand:@"logout"]; [self closeConnection]; + [self->selectedFolder release]; self->selectedFolder = nil; + self->loggedIn = NO; return [self->normer normalizeResponse:map]; } @@ -530,7 +550,7 @@ NSAutoreleasePool *pool; NGHashMap *map; NSDictionary *result; - NSString *s; + NSString *s, *prefix; pool = [[NSAutoreleasePool alloc] init]; @@ -547,7 +567,11 @@ if (!(_pattern = [self _folder2ImapFolder:_pattern])) return nil; - s = [NSString stringWithFormat:@"list \"%@\" \"%@\"", _folder, _pattern]; + if ([_folder length] > 0) + prefix = [NSString stringWithFormat: @"%@%@", SaneFolderName(_folder), self->delimiter]; + else + prefix = @""; + s = [NSString stringWithFormat:@"list \"%@\" \"%@\"", prefix, _pattern]; map = [self processCommand:s]; if (self->delimiter == nil) { @@ -563,18 +587,49 @@ } - (NSDictionary *)capability { + NSDictionary *result; id capres; - capres = [self processCommand:@"capability"]; - return [self->normer normalizeCapabilityRespone:capres]; + + result = [capabilities objectForKey: [self->address description]]; + + if (!result) + { + capres = [self processCommand:@"capability"]; + result = [self->normer normalizeCapabilityResponse:capres]; + + if (result) + [capabilities setObject: result forKey: [self->address description]]; + } + return result; } +- (NSDictionary *)namespace { + NSArray *capabilities; + NGHashMap *namesres; + id namespace; + + namespace = [namespaces objectForKey: [self->address description]]; + if (!namespace) { + capabilities = [[self capability] objectForKey: @"capability"]; + if ([capabilities containsObject: @"namespace"]) { + namesres = [self processCommand: @"namespace"]; + namespace = [self->normer normalizeNamespaceResponse:namesres]; + } + else + namespace = [NSNull null]; + [namespaces setObject: namespace forKey: [self->address description]]; + } + + return ([namespace isKindOfClass: [NSNull class]] ? nil : namespace); +} + - (NSDictionary *)lsub:(NSString *)_folder pattern:(NSString *)_pattern { /* The method build statements like 'LSUB "_folder" "_pattern"'. The returnvalue is the same like the list:pattern: method */ NGHashMap *map; - NSString *s; + NSString *s, *prefix; if (_folder == nil) _folder = @""; @@ -591,7 +646,11 @@ return nil; } - s = [NSString stringWithFormat:@"lsub \"%@\" \"%@\"", _folder, _pattern]; + if ([_folder length] > 0) + prefix = [NSString stringWithFormat: @"%@%@", SaneFolderName(_folder), self->delimiter]; + else + prefix = @""; + s = [NSString stringWithFormat:@"lsub \"%@\" \"%@\"", prefix, _pattern]; map = [self processCommand:s]; if (self->delimiter == nil) { @@ -617,24 +676,25 @@ 'flags' - array of strings (eg (answered,flagged,draft,seen); 'RawResponse' - the raw IMAP4 response */ - NSString *s; - id tmp; - - tmp = self->selectedFolder; // remember ptr to old folder name - + NSString *s, *newFolder; + if (![_folder isNotEmpty]) return nil; if ((_folder = [self _folder2ImapFolder:_folder]) == nil) return nil; - self->selectedFolder = [_folder copy]; - - [tmp release]; tmp = nil; // release old folder name + newFolder = [NSString stringWithString: _folder]; + ASSIGN (self->selectedFolder, newFolder); - s = [NSString stringWithFormat:@"select \"%@\"", self->selectedFolder]; + s = [NSString stringWithFormat:@"select \"%@\"", SaneFolderName(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; @@ -646,7 +706,7 @@ return nil; cmd = [NSString stringWithFormat:@"status \"%@\" (%@)", - _folder, [_flags componentsJoinedByString:@" "]]; + SaneFolderName(_folder), [_flags componentsJoinedByString:@" "]]; return [self->normer normalizeStatusResponse:[self processCommand:cmd]]; } @@ -663,24 +723,28 @@ if ((_newName = [self _folder2ImapFolder:_newName]) == nil) return nil; - cmd = [NSString stringWithFormat:@"rename \"%@\" \"%@\"", _folder, _newName]; + cmd = [NSString stringWithFormat:@"rename \"%@\" \"%@\"", + SaneFolderName(_folder), SaneFolderName(_newName)]; return [self->normer normalizeResponse:[self processCommand:cmd]]; } - (NSDictionary *)_performCommand:(NSString *)_op onFolder:(NSString *)_fname { NSString *command; - + if ((_fname = [self _folder2ImapFolder:_fname]) == nil) return nil; - + // eg: 'delete "blah"' - command = [NSString stringWithFormat:@"%@ \"%@\"", _op, _fname]; - + command = [NSString stringWithFormat:@"%@ \"%@\"", _op, SaneFolderName(_fname)]; + return [self->normer normalizeResponse:[self processCommand:command]]; } - (NSDictionary *)delete:(NSString *)_name { + if ([self->selectedFolder isEqualToString:_name]) { + [self unselect]; + } return [self _performCommand:@"delete" onFolder:_name]; } - (NSDictionary *)create:(NSString *)_name { @@ -820,23 +884,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 +1031,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 +1158,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 +1183,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 +1207,7 @@ return nil; } - s = [@"search" stringByAppendingString:s]; + s = [@"UID SEARCH" stringByAppendingString:s]; return [self->normer normalizeSearchResponse:[self processCommand:s]]; } @@ -1142,7 +1219,7 @@ if ((_folder = [self _folder2ImapFolder:_folder]) == nil) return nil; - cmd = [NSString stringWithFormat:@"getacl \"%@\"", _folder]; + cmd = [NSString stringWithFormat:@"getacl \"%@\"", SaneFolderName(_folder)]; return [self->normer normalizeGetACLResponse:[self processCommand:cmd]]; } @@ -1155,7 +1232,7 @@ return nil; cmd = [NSString stringWithFormat:@"setacl \"%@\" \"%@\" \"%@\"", - _folder, _uid, _r]; + SaneFolderName(_folder), _uid, _r]; return [self->normer normalizeResponse:[self processCommand:cmd]]; } @@ -1166,7 +1243,7 @@ return nil; cmd = [NSString stringWithFormat:@"deleteacl \"%@\" \"%@\"", - _folder, _uid]; + SaneFolderName(_folder), _uid]; return [self->normer normalizeResponse:[self processCommand:cmd]]; } @@ -1177,7 +1254,7 @@ return nil; cmd = [NSString stringWithFormat:@"listrights \"%@\" \"%@\"", - _folder, _uid]; + SaneFolderName(_folder), _uid]; return [self->normer normalizeListRightsResponse:[self processCommand:cmd]]; } @@ -1187,12 +1264,94 @@ if ((_folder = [self _folder2ImapFolder:_folder]) == nil) return nil; - cmd = [NSString stringWithFormat:@"myrights \"%@\"", _folder]; + cmd = [NSString stringWithFormat:@"myrights \"%@\"", SaneFolderName(_folder)]; return [self->normer normalizeMyRightsResponse:[self processCommand:cmd]]; } /* Private Methods */ +- (NSDictionary *) _sopeSORT: (id)_sortSpec qualifier:(EOQualifier *)_qual encoding:(NSString *)_encoding { + NSMutableDictionary *result; + NSDictionary *d; + NSCalendarDate *envDate; + + result = [NSMutableDictionary dictionary]; + [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"]; + if ([a isNotEmpty]) { + d = [self fetchUids: a + parts: [NSArray arrayWithObjects: @"ENVELOPE", + @"RFC822.SIZE", nil]]; + a = [d objectForKey: @"fetch"]; + + dict = [NSMutableDictionary dictionary]; + 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 if ([_sortSpec rangeOfString: @"SIZE"].length) { + s = [o objectForKey: @"size"]; + [dict setObject: (s != nil ? s : [NSNumber numberWithInt: 0]) + forKey: uid]; + b = NO; + } + else { + envDate = [env date]; + if (!envDate) + envDate = [NSCalendarDate date]; + [dict setObject: envDate 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]; + } + + } + else { + s_a = [NSArray array]; + } + [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]]; @@ -1412,21 +1571,24 @@ return nil; } - array = [_folder pathComponents]; +// array = [_folder pathComponents]; + array = [_folder componentsSeparatedByString:@"/"]; - if ([array isNotEmpty]) { + if ([array count]) { NSString *o; o = [array objectAtIndex:0]; - if (([o isEqualToString:@"/"]) || ([o length] == 0)) + if ([o length] == 0) array = [array subarrayWithRange:NSMakeRange(1, [array count] - 1)]; - - o = [array lastObject]; - if (([o length] == 0) || ([o isEqualToString:@"/"])) - array = [array subarrayWithRange:NSMakeRange(0, [array count] - 1)]; + + if ([array count]) { + o = [array lastObject]; + if ([o length] == 0) + array = [array subarrayWithRange:NSMakeRange(0, [array count] - 1)]; + } } return [[array componentsJoinedByString:self->delimiter] - stringByEncodingImap4FolderName]; + stringByEncodingImap4FolderName]; } - (NSString *)_imapFolder2Folder:(NSString *)_folder { @@ -1442,10 +1604,16 @@ return nil; } + if ([_folder hasPrefix: self->delimiter]) + _folder = [_folder substringFromIndex: 1]; + if ([_folder hasSuffix: self->delimiter]) + _folder = [_folder substringToIndex: [_folder length] - 1]; + array = [array arrayByAddingObjectsFromArray: [_folder componentsSeparatedByString:[self delimiter]]]; - - return [[NSString pathWithComponents:array] stringByDecodingImap4FolderName]; + + return [[array componentsJoinedByString: @"/"] + stringByDecodingImap4FolderName]; } - (void)setContext:(NGImap4Context *)_ctx { Index: sope-mime/NGImap4/NGSieveClient.m =================================================================== --- sope-mime/NGImap4/NGSieveClient.m (revision 1664) +++ 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 1664) +++ sope-mime/NGImap4/NGImap4Connection.h (working copy) @@ -52,7 +52,7 @@ NSString *separator; /* hierarchy cache */ - NSDictionary *subfolders; + NSMutableDictionary *subfolders; /* permission cache */ NSMutableDictionary *urlToRights; @@ -72,8 +72,9 @@ - (NSDate *)creationTime; -- (void)cacheHierarchyResults:(NSDictionary *)_hierarchy; -- (NSDictionary *)cachedHierarchyResults; +- (void)cacheHierarchyResults:(NSDictionary *)_hierarchy + forURL:(NSURL *)_url; +- (NSDictionary *)cachedHierarchyResultsForURL:(NSURL *)_url; - (void)flushFolderHierarchyCache; - (id)cachedUIDsForURL:(NSURL *)_url qualifier:(id)_q sortOrdering:(id)_so; @@ -88,7 +89,12 @@ /* folder operations */ - (NSArray *)subfoldersForURL:(NSURL *)_url; +- (NSArray *)subfoldersForURL:(NSURL *)_url + onlySubscribedFolders: (BOOL) subscribedFoldersOnly; - (NSArray *)allFoldersForURL:(NSURL *)_url; +- (NSArray *)allFoldersForURL:(NSURL *)_url + onlySubscribedFolders: (BOOL) subscribedFoldersOnly; +- (BOOL)selectFolder:(id)_url; /* message operations */ Index: sope-mime/NGImap4/NGImap4ResponseNormalizer.h =================================================================== --- sope-mime/NGImap4/NGImap4ResponseNormalizer.h (revision 1664) +++ sope-mime/NGImap4/NGImap4ResponseNormalizer.h (working copy) @@ -49,7 +49,8 @@ - (NSDictionary *)normalizeSearchResponse:(NGHashMap *)_map; - (NSDictionary *)normalizeSortResponse:(NGHashMap *)_map; - (NSDictionary *)normalizeThreadResponse:(NGHashMap *)_map; -- (NSDictionary *)normalizeCapabilityRespone:(NGHashMap *)_map; +- (NSDictionary *)normalizeCapabilityResponse:(NGHashMap *)_map; +- (NSDictionary *)normalizeNamespaceResponse:(NGHashMap *)_map; - (NSDictionary *)normalizeQuotaResponse:(NGHashMap *)_map; /* ACL */ Index: sope-mime/NGImap4/NGImap4Connection.m =================================================================== --- sope-mime/NGImap4/NGImap4Connection.m (revision 1664) +++ sope-mime/NGImap4/NGImap4Connection.m (working copy) @@ -22,6 +22,7 @@ #include "NGImap4Connection.h" #include "NGImap4MailboxInfo.h" #include "NGImap4Client.h" +#include "NGImap4Functions.h" #include "imCommon.h" @implementation NGImap4Connection @@ -66,7 +67,8 @@ self->creationTime = [[NSDate alloc] init]; // TODO: retrieve from IMAP4 instead of using a default - self->separator = imap4Separator; + self->separator = [imap4Separator copy]; + self->subfolders = [NSMutableDictionary new]; } return self; } @@ -100,14 +102,16 @@ return self->creationTime; } -- (void)cacheHierarchyResults:(NSDictionary *)_hierarchy { - ASSIGNCOPY(self->subfolders, _hierarchy); +- (void)cacheHierarchyResults:(NSDictionary *)_hierarchy + forURL:(NSURL *)_url +{ + [self->subfolders setObject:_hierarchy forKey:[_url absoluteString]]; } -- (NSDictionary *)cachedHierarchyResults { - return self->subfolders; +- (NSDictionary *)cachedHierarchyResultsForURL:(NSURL *)_url { + return [self->subfolders objectForKey:[_url absoluteString]]; } - (void)flushFolderHierarchyCache { - [self->subfolders release]; self->subfolders = nil; + [self->subfolders release]; self->subfolders = [NSMutableDictionary new]; [self->urlToRights release]; self->urlToRights = nil; } @@ -152,7 +156,6 @@ ASSIGN(self->cachedUIDs, nil); } - /* errors */ - (NSException *)errorCouldNotSelectURL:(NSURL *)_url { @@ -215,18 +218,13 @@ NSMutableArray *ma; unsigned i, count, prefixlen; - if ((count = [_array count]) < 2) { - /* one entry is the folder itself, so we need at least two */ - return [NSArray array]; - } + count = [_array count]; // TODO: somehow results are different on OSX // we should investigate and test all Foundation libraries and document the // differences #if __APPLE__ prefixlen = [_fn isEqualToString:@""] ? 0 : [_fn length] + 1; -#elif GNUSTEP_BASE_LIBRARY - prefixlen = [_fn isEqualToString:@"/"] ? 1 : [_fn length]; #else prefixlen = [_fn isEqualToString:@"/"] ? 1 : [_fn length] + 1; #endif @@ -321,13 +319,15 @@ return nil; if ([folderName characterAtIndex:0] == '/') folderName = [folderName substringFromIndex:1]; + if ([folderName hasSuffix: @"/"]) + folderName = [folderName substringToIndex:[folderName length] - 1]; if (_delfn) folderName = [folderName stringByDeletingLastPathComponent]; if ([[self imap4Separator] isEqualToString:@"/"]) return folderName; - names = [folderName pathComponents]; + names = [folderName componentsSeparatedByString: @"/"]; return [names componentsJoinedByString:[self imap4Separator]]; } - (NSString *)imap4FolderNameForURL:(NSURL *)_url { @@ -373,16 +373,26 @@ /* folder operations */ -- (NSDictionary *)primaryFetchMailboxHierarchyForURL:(NSURL *)_url { +- (NSDictionary *)primaryFetchMailboxHierarchyForURL:(NSURL *)_url + onlySubscribedFolders:(BOOL) subscribedFoldersOnly +{ NSDictionary *result; + NSString *prefix; - if ((result = [self cachedHierarchyResults]) != nil) + if ((result = [self cachedHierarchyResultsForURL:_url]) != nil) return [result isNotNull] ? result : (NSDictionary *)nil; if (debugCache) [self logWithFormat:@" no folders cached yet .."]; - - result = [[self client] list:(onlyFetchInbox ? @"INBOX" : @"*") - pattern:@"*"]; + + prefix = [_url path]; + if ([prefix hasPrefix: @"/"]) + prefix = [prefix substringFromIndex:1]; + if (subscribedFoldersOnly) + result = [[self client] lsub:(onlyFetchInbox ? @"INBOX" : prefix) + pattern:@"*"]; + else + result = [[self client] list:(onlyFetchInbox ? @"INBOX" : prefix) + pattern:@"*"]; if (![[result valueForKey:@"result"] boolValue]) { [self errorWithFormat:@"Could not list mailbox hierarchy!"]; return nil; @@ -391,7 +401,7 @@ /* cache results */ if ([result isNotNull]) { - [self cacheHierarchyResults:result]; + [self cacheHierarchyResults:result forURL:_url]; if (debugCache) { [self logWithFormat:@"cached results: 0x%p(%d)", result, [result count]]; @@ -400,32 +410,55 @@ return result; } -- (NSArray *)subfoldersForURL:(NSURL *)_url { +- (NSDictionary *)primaryFetchMailboxHierarchyForURL:(NSURL *)_url +{ + return [self primaryFetchMailboxHierarchyForURL: _url onlySubscribedFolders: NO]; +} + +- (NSArray *)allFoldersForURL:(NSURL *)_url + onlySubscribedFolders:(BOOL)_subscribedFoldersOnly +{ NSDictionary *result; - if ((result = [self primaryFetchMailboxHierarchyForURL:_url]) == nil) + if ((result = [self primaryFetchMailboxHierarchyForURL:_url + onlySubscribedFolders:_subscribedFoldersOnly]) == nil) return nil; if ([result isKindOfClass:[NSException class]]) { [self errorWithFormat:@"failed to retrieve hierarchy: %@", result]; return nil; } - return [self extractSubfoldersForURL:_url fromResultSet:result]; + return [self extractFoldersFromResultSet:result]; } -- (NSArray *)allFoldersForURL:(NSURL *)_url { +- (NSArray *)allFoldersForURL:(NSURL *)_url +{ + return [self allFoldersForURL: _url onlySubscribedFolders: NO]; +} + +- (NSArray *)subfoldersForURL:(NSURL *)_url + onlySubscribedFolders:(BOOL)_subscribedFoldersOnly +{ NSDictionary *result; + NSString *baseFolder; - if ((result = [self primaryFetchMailboxHierarchyForURL:_url]) == nil) + baseFolder = [self imap4FolderNameForURL:_url removeFileName:NO]; + if (_subscribedFoldersOnly) + result = [[self client] lsub:baseFolder pattern:@"%"]; + else + result = [[self client] list:baseFolder pattern:@"%"]; + if (![[result valueForKey:@"result"] boolValue]) { + [self errorWithFormat:@"Could not list mailbox hierarchy!"]; return nil; - if ([result isKindOfClass:[NSException class]]) { - [self errorWithFormat:@"failed to retrieve hierarchy: %@", result]; - return nil; } - - return [self extractFoldersFromResultSet:result]; + + return [self extractSubfoldersForURL:_url fromResultSet: result]; } +- (NSArray *)subfoldersForURL:(NSURL *)_url { + return [self subfoldersForURL:_url onlySubscribedFolders: NO]; +} + /* message operations */ - (NSArray *)fetchUIDsInURL:(NSURL *)_url qualifier:(id)_qualifier @@ -646,7 +679,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"]; @@ -737,34 +770,42 @@ - (BOOL)doesMailboxExistAtURL:(NSURL *)_url { NSString *folderName; + NSArray *caches; id result; + int count, max; + BOOL found; + found = NO; + /* check in hierarchy cache */ - - if ((result = [self cachedHierarchyResults]) != nil) { + caches = [self->subfolders allValues]; + max = [caches count]; + for (count = 0; !found && count < max; count++) { NSString *p; - - result = [(NSDictionary *)result objectForKey:@"list"]; + + result = [[caches objectAtIndex: count] objectForKey:@"list"]; p = [_url path]; -#if __APPLE__ /* normalized results already have the / in front on libFoundation?! */ if ([p hasPrefix:@"/"]) p = [p substringFromIndex:1]; -#endif if ([p hasSuffix:@"/"]) p = [p substringToIndex:[p length]-1]; - return ([(NSDictionary *)result objectForKey:p] != nil) ? YES : NO; + found = ([(NSDictionary *)result objectForKey:p] != nil); } + + if (!found) { + /* check using IMAP4 select */ + // TODO: we should probably just fetch the whole hierarchy? - /* check using IMAP4 select */ - // 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; - - return YES; + folderName = [self imap4FolderNameForURL:_url]; + + result = [self->client status: folderName + flags: [NSArray arrayWithObject: @"UIDVALIDITY"]]; + + found =([[result valueForKey: @"result"] boolValue]); + } + + return found; } - (id)infoForMailboxAtURL:(NSURL *)_url { @@ -789,7 +830,8 @@ /* construct path */ newPath = [self imap4FolderNameForURL:_url]; - newPath = [newPath stringByAppendingString:[self imap4Separator]]; + if ([newPath length]) + newPath = [newPath stringByAppendingString:[self imap4Separator]]; newPath = [newPath stringByAppendingString:_mailbox]; /* create */ Index: sope-mime/NGImap4/NGImap4ResponseNormalizer.m =================================================================== --- sope-mime/NGImap4/NGImap4ResponseNormalizer.m (revision 1664) +++ sope-mime/NGImap4/NGImap4ResponseNormalizer.m (working copy) @@ -76,22 +76,6 @@ return self; } -/* client callbacks */ - -- (void)closeConnection { - [(id)self->client closeConnection]; -} - -- (NSString *)delimiter { - return [self->client delimiter]; -} - -/* folder handling */ - -- (NSString *)_imapFolder2Folder:(NSString *)_folder { - return [self->client _imapFolder2Folder:_folder]; -} - /* primary */ - (NSMutableDictionary *)normalizeResponse:(NGHashMap *)_map { @@ -117,7 +101,7 @@ if ((obj = [_map objectForKey:@"bye"])) { [result setObject:NoNumber forKey:@"result"]; [result setObject:obj forKey:@"reason"]; - [self closeConnection]; + [self->client closeConnection]; return result; } @@ -157,7 +141,7 @@ return result; } -- (NSDictionary *)normalizeCapabilityRespone:(NGHashMap *)_map { +- (NSDictionary *)normalizeCapabilityResponse:(NGHashMap *)_map { /* filter for capability response: capability : NSArray */ id obj; NSMutableDictionary *result; @@ -170,6 +154,51 @@ return result; } +- (NSArray *)_normalizeNamespace:(NSArray *)_namespace { + NSMutableArray *result; + NSDictionary *currentNS; + NSMutableDictionary *newNS; + NSString *newPrefix; + int count, max; + + max = [_namespace count]; + result = [NSMutableArray arrayWithCapacity: max]; + for (count = 0; count < max; count++) { + currentNS = [_namespace objectAtIndex: count]; + newNS = [currentNS mutableCopy]; + newPrefix = [self->client + _imapFolder2Folder: [currentNS objectForKey: @"prefix"]]; + [newNS setObject: newPrefix forKey: @"prefix"]; + [result addObject: newNS]; + [newNS release]; + } + + return result; +} + +- (NSDictionary *)normalizeNamespaceResponse:(NGHashMap *)_map { + NSMutableDictionary *result; + NSDictionary *rawResponse; + NSArray *namespace; + + result = [self normalizeResponse:_map]; + rawResponse = [result objectForKey: @"RawResponse"]; + namespace = [rawResponse objectForKey: @"personal"]; + if (namespace) + [result setObject: [self _normalizeNamespace: namespace] + forKey: @"personal"]; + namespace = [rawResponse objectForKey: @"other users"]; + if (namespace) + [result setObject: [self _normalizeNamespace: namespace] + forKey: @"other users"]; + namespace = [rawResponse objectForKey: @"shared"]; + if (namespace) + [result setObject: [self _normalizeNamespace: namespace] + forKey: @"shared"]; + + return result; +} + - (NSDictionary *)normalizeThreadResponse:(NGHashMap *)_map { /* filter for thread response: thread : NSArray (msn) */ id obj; @@ -292,7 +321,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 +365,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++; @@ -516,7 +550,7 @@ } continue; } - [tmp setObject:qDesc forKey:[self _imapFolder2Folder:obj]]; + [tmp setObject:qDesc forKey:[self->client _imapFolder2Folder:obj]]; } [result setObject:tmp forKey:@"quotas"]; return [[result copy] autorelease]; @@ -615,7 +649,7 @@ while ((o = [enumerator nextObject])) { [folder setObject:_imapFlags2Flags(self, [o objectForKey:@"flags"]) - forKey:[self _imapFolder2Folder:[o objectForKey:@"folderName"]]]; + forKey:[self->client _imapFolder2Folder:[o objectForKey:@"folderName"]]]; } { @@ -648,14 +682,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/EOQualifier+IMAPAdditions.m =================================================================== --- sope-mime/NGImap4/EOQualifier+IMAPAdditions.m (revision 1664) +++ sope-mime/NGImap4/EOQualifier+IMAPAdditions.m (working copy) @@ -53,13 +53,13 @@ if (FlagKeyWords) return; ud = [NSUserDefaults standardUserDefaults]; - FlagKeyWords = [[NSArray alloc] initWithObjects: @"answered", @"deleted", - @"draft", @"flagged", @"new", @"old", @"recent", - @"seen", @"unanswered", @"undeleted", @"undraft", - @"unflagged", @"unseen", nil]; - OtherKeyWords = [[NSArray alloc] initWithObjects: - @"bcc", @"body", @"cc", @"from", @"subject", - @"text", @"to", @"keyword", @"unkeyword", nil]; + FlagKeyWords = [[NSArray alloc] initWithObjects: @"ANSWERED", @"DELETED", + @"DRAFT", @"FLAGGED", @"NEW", @"OLD", @"RECENT", + @"SEEN", @"UNANSWERED", @"UNDELETED", @"UNDRAFT", + @"UNFLAGGED", @"UNSEEN", nil]; + OtherKeyWords = [[NSArray alloc] initWithObjects: @"ALL", @"BCC", @"BODY", + @"CC", @"FROM", @"SUBJECT", @"TEXT", @"TO", + @"KEYWORD", @"UID", @"UNKEYWORD", nil]; debugOn = [ud boolForKey:@"ImapDebugQualifierGeneration"]; } @@ -266,10 +266,10 @@ enumerator = [lvalue objectEnumerator]; while ((lvalue = [enumerator nextObject]) != nil) { - lvalue = [lvalue lowercaseString]; + lvalue = [lvalue uppercaseString]; if ([FlagKeyWords containsObject:lvalue]) { - if (insertNot) [search appendString:@"not "]; + if (insertNot) [search appendString:@"NOT "]; [search appendString:lvalue]; } else { @@ -280,15 +280,31 @@ return nil; } -- (NSString *)imap4OperatorForDateComparisonSelector:(SEL)lselector { +- (NSString *)imap4OperatorForDateKeyword:(NSString *)dkey +andComparisonSelector:(SEL)lselector { + NSString *operatorPrefix, *dateOperator, *imap4Operator; + if (sel_eq(lselector, EOQualifierOperatorEqual)) - return @" senton "; - if (sel_eq(lselector, EOQualifierOperatorGreaterThan)) - return @" sentsince "; - if (sel_eq(lselector, EOQualifierOperatorLessThan)) - return @" sentbefore "; - - return nil; + dateOperator = @"ON"; + else if (sel_eq(lselector, EOQualifierOperatorGreaterThan)) + dateOperator = @"SINCE"; + else if (sel_eq(lselector, EOQualifierOperatorLessThan)) + dateOperator = @"BEFORE"; + else + dateOperator = nil; + + if (dateOperator) { + if ([dkey isEqualToString: @"DATE"]) + operatorPrefix = @"SENT"; + else + operatorPrefix = @""; + imap4Operator = [NSString stringWithFormat: @"%@%@ ", + operatorPrefix, dateOperator]; + } + else + imap4Operator = nil; + + return imap4Operator; } - (NSException *)appendToImap4SearchString:(NSMutableString *)search @@ -300,11 +316,11 @@ id lvalue; SEL lselector; - lkey = [[self key] lowercaseString]; + lkey = [[self key] uppercaseString]; lvalue = [self value]; lselector = [self selector]; - if ([lkey isEqualToString:@"flags"]) { + if ([lkey isEqualToString:@"FLAGS"]) { /* NOTE: special "not" processing! */ return [self appendFlagsCheckToImap4SearchString:search insertNot:insertNot]; @@ -312,9 +328,9 @@ /* not a flag */ if (insertNot) - [search appendString:@"not "]; + [search appendString:@"NOT "]; - if ([lkey isEqualToString:@"date"]) { + if ([lkey isEqualToString:@"DATE"] || [lkey isEqualToString:@"RECEIVE-DATE"]) { NSString *s; if (![lvalue isKindOfClass:[NSCalendarDate class]]) { @@ -322,35 +338,38 @@ @"expected a NSDate as value"]; } - if ((s = [self imap4OperatorForDateComparisonSelector:lselector]) == nil) + if ((s = [self imap4OperatorForDateKeyword:lkey + andComparisonSelector:lselector]) == nil) return [self invalidImap4SearchQualifier:@"unexpected selector"]; - // TODO: operator created but NOT added? + [search appendString:s]; // TODO: much faster without descriptionWithCalendarFormat:?! - s = [lvalue descriptionWithCalendarFormat:@"%d-%b-%Y"]; + s = [lvalue descriptionWithCalendarFormat:@"\"%d-%b-%Y\""]; [search appendString:s]; return nil; } - if ([lkey isEqualToString:@"uid"]) { - if (!sel_eq(lselector, EOQualifierOperatorEqual)) + if ([lkey isEqualToString:@"UID"]) { + if (!sel_eq(lselector, EOQualifierOperatorEqual)) { return [self invalidImap4SearchQualifier:@"unexpected qualifier 2"]; + } - [search appendString:@"uid "]; + [search appendString:@"UID "]; [search appendString:[lvalue stringValue]]; return nil; } - if ([lkey isEqualToString:@"size"]) { + if ([lkey isEqualToString:@"SIZE"]) { if (sel_eq(lselector, EOQualifierOperatorGreaterThan)) - [search appendString:@"larger "]; + [search appendString:@"LARGER "]; else if (sel_eq(lselector, EOQualifierOperatorLessThan)) - [search appendString:@"smaller "]; + [search appendString:@"SMALLER "]; else return [self invalidImap4SearchQualifier:@"unexpected qualifier 3"]; [search appendString:[lvalue stringValue]]; + return nil; } @@ -386,7 +405,7 @@ if (!sel_eq(lselector, EOQualifierOperatorEqual)) return [self invalidImap4SearchQualifier:@"unexpected qualifier 5"]; - [search appendString:@"header "]; + [search appendString:@"HEADER "]; [search appendString:lkey]; [search appendString:@" \""]; [search appendString:[lvalue stringValue]]; Index: sope-mime/NGImap4/NGImap4ResponseParser.m =================================================================== --- sope-mime/NGImap4/NGImap4ResponseParser.m (revision 1664) +++ sope-mime/NGImap4/NGImap4ResponseParser.m (working copy) @@ -31,6 +31,7 @@ @interface NGImap4ResponseParser(ParsingPrivates) - (BOOL)_parseNumberUntaggedResponse:(NGMutableHashMap *)result_; - (NSDictionary *)_parseBodyContent; +- (NSData *) _parseBodyHeaderFields; - (NSData *)_parseData; @@ -38,6 +39,7 @@ - (void)_parseContinuationResponseIntoHashMap:(NGMutableHashMap *)result_; - (BOOL)_parseListOrLSubResponseIntoHashMap:(NGMutableHashMap *)result_; - (BOOL)_parseCapabilityResponseIntoHashMap:(NGMutableHashMap *)result_; +- (BOOL)_parseNamespaceResponseIntoHashMap:(NGMutableHashMap *)result_; - (BOOL)_parseSearchResponseIntoHashMap:(NGMutableHashMap *)result_; - (BOOL)_parseSortResponseIntoHashMap:(NGMutableHashMap *)result_; - (BOOL)_parseQuotaRootResponseIntoHashMap:(NGMutableHashMap *)result_; @@ -84,6 +86,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 +115,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); @@ -300,6 +305,16 @@ /* those starting with a number '24 ', eg '24 OK Completed' */ endOfCommand = (_parseTaggedResponse(self, result) == _tag); } + else if (l0 == -1) { + *ex_ = [self->buffer lastException]; + if (!*ex_) + *ex_ + = [NSException exceptionWithName:@"UnexpectedEndOfStream" + reason:(@"the parsed stream ended" + @" unexpectedly") + userInfo:nil]; + endOfCommand = YES; + } } return result; } @@ -488,6 +503,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_) { @@ -584,6 +643,10 @@ break; case 'N': + if (_matchesString(self, "NAMESPACE")) { + if ([self _parseNamespaceResponseIntoHashMap:result_]) + return; + } if (_parseNoUntaggedResponse(self, result_)) // la: 2 return; break; @@ -648,14 +711,171 @@ [result_ addObject:_parseUntil(self, '\n') forKey:@"description"]; } +static inline void +_purifyQuotedString(NSMutableString *quotedString) { + unichar *currentChar, *qString, *maxC, *startC; + unsigned int max, questionMarks; + 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; + + questionMarks = 0; + + while (currentChar < maxC) { + if (possiblyQuoted) { + if (questionMarks == 2) { + if ((*currentChar == 'Q' || *currentChar == 'q' + || *currentChar == 'B' || *currentChar == 'b') + && ((currentChar + 1) < maxC + && (*(currentChar + 1) == '?'))) { + currentChar++; + questionMarks = 3; + } + else { + possiblyQuoted = NO; + } + } + else if (questionMarks == 4) { + if (*currentChar == '=') { + skipSpaces = YES; + possiblyQuoted = NO; + currentChar++; + [newString appendString: [NSString stringWithCharacters: startC + length: (currentChar - startC)]]; + startC = currentChar; + } + else { + possiblyQuoted = NO; + } + } + else { + if (*currentChar == '?') { + questionMarks++; + } + else if (*currentChar == ' ' && questionMarks != 3) { + possiblyQuoted = NO; + } + } + } + else if (*currentChar == '=' + && ((currentChar + 1) < maxC + && (*(currentChar + 1) == '?'))) { + [newString appendString: [NSString stringWithCharacters: startC + length: (currentChar - startC)]]; + startC = currentChar; + possiblyQuoted = YES; + skipSpaces = NO; + currentChar++; + questionMarks = 1; + } + + currentChar++; + + if (skipSpaces) { + while (currentChar < maxC + && (*currentChar == ' ' + || *currentChar == '\t')) + currentChar++; + skipSpaces = NO; + startC = currentChar; + } + } + + if (startC < maxC) + [newString appendString: [NSString stringWithCharacters: startC + length: (currentChar - startC)]]; + + [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; + } + } } + else { + quotedString = nil; + } + + _purifyQuotedString(quotedString); + + return quotedString; +} +- (NSString *)_parseQuotedStringOrNIL { + unsigned char c0; + + if ((c0 = _la(self, 0)) == '"') + return [self _parseQuotedString]; + + if (c0 == '{') { + /* a size indicator, eg '{112}\nkasdjfkja sdj fhj hasdfj hjasdf' */ + NSData *data; + NSString *s; + + if ((data = [self _parseData]) == nil) + return nil; + if (![data isNotEmpty]) + 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.", + __PRETTY_FUNCTION__, [data length]]; + return @"[ERROR: NGImap4 could not parse IMAP4 data string]"; + } + return [s autorelease]; + } + + if (c0 == 'N' && _matchesString(self, "NIL")) { + _consume(self, 3); + return (id)null; + } return nil; } +- (id)_parseQuotedStringOrDataOrNIL { + if (_la(self, 0) == '"') + return [self _parseQuotedString]; + if (_la(self, 0) == '{') + return [self _parseData]; + + if (_matchesString(self, "NIL")) { + _consume(self, 3); + return null; + } + return nil; +} - (void)_consumeOptionalSpace { if (_la(self, 0) == ' ') _consume(self, 1); } @@ -685,6 +905,10 @@ name = [self _parseQuotedString]; _parseUntil(self, '\n'); } + else if (_la(self, 0) == '{') { + name = [self _parseQuotedStringOrNIL]; + _parseUntil(self, '\n'); + } else name = _parseUntil(self, '\n'); @@ -723,6 +947,85 @@ return YES; } +/* support for NAMESPACE extension - RFC2342 */ + +- (NSDictionary *)_parseNamespacePart { + NSDictionary *namespacePart; + NSString *prefix, *key, *delimiter; + NSMutableDictionary *parameters; + NSMutableArray *values; + + _consume(self, 1); /* ( */ + prefix = [self _parseQuotedStringOrNIL]; /* "prefix" */ + _consume(self, 1); /* */ + delimiter = [self _parseQuotedStringOrNIL]; /* "delimiter" */ + parameters = [NSMutableDictionary dictionary]; + while (_la(self, 0) == ' ') { + _consume(self, 1); /* */ + key = [self _parseQuotedString]; + _consume(self, 1); /* */ + values = [NSMutableArray new]; + while (_la(self, 0) != ')') { + _consume(self, 1); /* ( or */ + [values addObject: [self _parseQuotedString]]; + } + _consume(self, 1); /* ) */ + [parameters setObject: values forKey: key]; + [values release]; + } + _consume(self, 1); /* ) */ + + namespacePart = [NSDictionary dictionaryWithObjectsAndKeys: + prefix, @"prefix", + delimiter, @"delimiter", + parameters, @"parameters", + nil]; + + return namespacePart; +} + +- (NSArray *)_parseNamespace { + NSMutableArray *namespace; + + namespace = [[NSMutableArray alloc] initWithCapacity: 3]; + if (_la(self, 0) == 'N') { + namespace = nil; + _consume(self, 3); + } else { + _consume(self, 1); /* ( */ + while (_la(self, 0) == '(') { + [namespace addObject: [self _parseNamespacePart]]; + } + _consume(self, 1); /* ) */ + } + + return namespace; +} + +- (BOOL)_parseNamespaceResponseIntoHashMap:(NGMutableHashMap *)result_ { + NSArray *namespace; + + if (!_matchesString(self, "NAMESPACE ")) + return NO; + + _parseUntil(self, ' '); + + namespace = [self _parseNamespace]; + if (namespace) + [result_ addObject:namespace forKey:@"personal"]; + _consume(self, 1); + namespace = [self _parseNamespace]; + if (namespace) + [result_ addObject:namespace forKey:@"other users"]; + _consume(self, 1); + namespace = [self _parseNamespace]; + if (namespace) + [result_ addObject:namespace forKey:@"shared"]; + _consume(self, 1); /* \n */ + + return YES; +} + - (BOOL)_parseACLResponseIntoHashMap:(NGMutableHashMap *)result_ { /* 21 GETACL INBOX @@ -1030,10 +1333,15 @@ _consume(self, 7); if (_la(self, 0) == '"') { - _consume(self, 1); - name = _parseUntil(self, '"'); + name = [self _parseQuotedString]; +// _consume(self, 1); +// name = _parseUntil(self, '"'); _consumeIfMatch(self, ' '); } + else if (_la(self, 0) == '{') { + name = [self _parseQuotedStringOrNIL]; + _consumeIfMatch(self, ' '); + } else { name = _parseUntil(self, ' '); } @@ -1073,51 +1381,6 @@ return YES; } -- (NSString *)_parseQuotedStringOrNIL { - unsigned char c0; - - if ((c0 = _la(self, 0)) == '"') - return [self _parseQuotedString]; - - if (c0 == '{') { - /* a size indicator, eg '{112}\nkasdjfkja sdj fhj hasdfj hjasdf' */ - NSData *data; - NSString *s; - - if ((data = [self _parseData]) == nil) - return nil; - if (![data isNotEmpty]) - return @""; - - s = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; - if (s == nil) { - [self logWithFormat: - @"ERROR(%s): could not convert data (%d bytes) into string.", - __PRETTY_FUNCTION__, [data length]]; - return @"[ERROR: NGImap4 could not parse IMAP4 data string]"; - } - return [s autorelease]; - } - - if (c0 == 'N' && _matchesString(self, "NIL")) { - _consume(self, 3); - return (id)null; - } - return nil; -} -- (id)_parseQuotedStringOrDataOrNIL { - if (_la(self, 0) == '"') - return [self _parseQuotedString]; - if (_la(self, 0) == '{') - return [self _parseData]; - - if (_matchesString(self, "NIL")) { - _consume(self, 3); - return null; - } - return nil; -} - - (id)_decodeQP:(id)_string headerField:(NSString *)_field { if (![_string isNotNull]) return _string; @@ -1185,7 +1448,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 +1460,7 @@ address = [[NGImap4EnvelopeAddress alloc] initWithPersonalName:pname sourceRoute:route mailbox:mailbox host:host]; + return address; } @@ -1382,7 +1646,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 +1866,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 +1895,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 +1943,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 +2028,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 +2060,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 +2086,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 +2114,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 +2133,7 @@ static NSDictionary *_parseMultipartBody(NGImap4ResponseParser *self, BOOL isBodyStructure) { NSMutableArray *parts; + NSArray *languages; NSString *kind; NSMutableDictionary *dict; @@ -1854,14 +2159,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 +2470,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 +2540,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 1664) +++ sope-mime/NGImap4/ChangeLog (working copy) @@ -1,3 +1,113 @@ +2010-01-25 Wolfgang Sourdeau + + * NGImap4ResponseParser.m (-parseResponseForTagId:exception:): + detect "-1" return code from _la and leave the loop with a proper + execption when it occurs. + +2010-01-15 Wolfgang Sourdeau + + * NGImap4Connection.m (-doesMailboxExistAtURL:): sometimes an + entry is present in the subfolders cache. When it is the case, the + method was returning at the end of the first iteration even though + the mailbox was never found. We need to continue and then proceed + to the "status" check whether the cache is populated or not. + +2010-01-14 Wolfgang Sourdeau + + * NGImap4Connection.m (-doesMailboxExistAtURL:): mailbox paths can + start with '/' on non-Apple platforms. + (-flushFolderHierarchyCache): reassign a new dictionary to + self->subfolders to avoid disappearing folders. + +2010-01-05 Wolfgang Sourdeau + + * NGImap4ResponseParser.m (_parseUntaggedResponse): now accepts + the "NAMESPACE" response and parse accordingly by making use of + the appropriate new method. + + * NGImap4ResponseNormalizer.m (-normalizeNamespaceResponse): + self-explicit new method. + + * NGImap4Connection.m (-subfolderForURL:, -allFoldersForURL:): + differenciate both methods by having "subfolder..." use the "%" + wild card and "allFolders" the "*" wildcard. + (-cachedHierarchyResultsForURL): now accepts a url parameter so + in order to maintain multiple caches depending on the queried + namespace. + (SOGoMailGetDirectChildren): if the array count is < 2, we must + not return since certain implementations may not return the + current folder. + + * NGImap4Client.m (-namespace): new method implementing the + "NAMESPACE" command. + (-lsub:pattern:): we now sanitize the "prefix" particle of the + LSUB command. + +2009-11-25 Wolfgang Sourdeau + + * NSString+Imap4.m (_encodeToModifiedUTF7): handle the case where + the leftOver is 0 by "chance" after the first 2 cycles. This + can happen when coding characters having a bitmask with 6 zeroes + in a row. + +2009-10-06 Wolfgang Sourdeau + + * NGImap4Client.m (-delete:): if the folder we want to delete is + the same as self->selectedFolder, we unselect it first to ensure a + "SELECT" happens if a new folder with the same name is created. + + * EOQualifier+IMAPAdditions.m + (-imap4OperatorForDateKeyword:andComparisonSelector:): modified + operator handler to handle "receive-date" search key as well as + "date", prefixing the real filter with "sent" or not. + +2009-09-22 Wolfgang Sourdeau + + * NGImap4Client.m (_sopeSORT:qualifier:encoding:): added support + for sorting by message size. + +2009-07-01 Wolfgang Sourdeau + + * NGImap4Connection.m (-initWithClient:password:): we need to copy + the imap4Separator, otherwise it will be released when the connection + is deallocated. + +2009-06-15 Wolfgang Sourdeau + + * NSString+Imap4.m (-stringByEncodingImap4FolderName, + -stringByDecodingImap4FolderName): reimplemented the original + methods in a unicode-safe way, thereby simplifying the code at the + same time. + + * NGImap4Functions.m (SaneFolderName): new function designed to + sanitize folder names prior to using them in IMAP commands. + +2008-10-23 Wolfgang Sourdeau + + * NGImap4Client.m ([NGImap -sort:qualifier:encoding:]): message + without date that are sorted on servers which do not have the SORT + capability are now given the current date as a work-around. + +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 1664) +++ 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 1664) +++ sope-mime/NGImap4/NSString+Imap4.m (working copy) @@ -20,117 +20,86 @@ 02111-1307, USA. */ +#import + #include #include "imCommon.h" -/* TODO: NOT UNICODE SAFE (uses cString) */ - -static void _encodeToModifiedUTF7(unsigned char *_buf, int encLen, - unsigned char **result_, - unsigned int *cntRes_); -static int _decodeOfModifiedUTF7(unsigned char *_target, unsigned _targetLen, - unsigned *usedBytes_ , - unsigned char **buffer_, - int *bufLen_, int maxBuf); - @implementation NSString(Imap4) +static unsigned int _encodeToModifiedUTF7(unichar *_char, unsigned char *result_, + unsigned int *cntRes_); +static unsigned int _decodeOfModifiedUTF7(unsigned char *_source, unichar *result_, + unsigned int *cntRes_ ); + - (NSString *)stringByEncodingImap4FolderName { - // TBD: this is restricted to Latin1, should be fixed to UTF-8 - /* dude.d& --> dude.d&- */ - unsigned char *buf = NULL; + unichar *buf = NULL; unsigned char *res = NULL; unsigned int len = 0; unsigned int cnt = 0; unsigned int cntRes = 0; 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 length]; + buf = NSZoneMalloc(NULL, (len + 1) * sizeof(unichar)); + [self getCharacters: buf]; + buf[len] = 0; + /* 1 * '&', 3 for the max bytes / char, 1 * '-' */ + res = NSZoneMalloc(NULL, ((len * 5) + 1) * sizeof(char)); + while (cnt < len) { - int c = buf[cnt]; + unichar c = buf[cnt]; if (((c > 31) && (c < 38)) || ((c > 38) && (c < 127))) { res[cntRes++] = c; - cnt++; } else { if (c == '&') { res[cntRes++] = '&'; res[cntRes++] = '-'; - cnt++; } else { - int start; - - start = cnt; - - while (cnt < (len - 1)) { - int c = buf[cnt + 1]; - if (((c > 31) && (c < 38)) || - ((c > 38) && (c < 127)) || - (c == '&')) { - break; - } - else { - cnt++; - } - } - { - unsigned length; - - res[cntRes++] = '&'; - - length = cnt - start + 1; - - _encodeToModifiedUTF7(buf + start, length, &res, &cntRes); - - res[cntRes] = '-'; - cntRes++; - cnt++; - } + res[cntRes++] = '&'; + cnt += _encodeToModifiedUTF7(buf + cnt, res + cntRes, &cntRes); + res[cntRes++] = '-'; } } + cnt++; } - if (buf != NULL) free(buf); buf = NULL; + if (buf != NULL) NSZoneFree(NULL, buf); - data = [[NSData alloc] initWithBytesNoCopy:res length:cntRes - freeWhenDone:YES]; - result = [[NSString alloc] initWithData:data - encoding:NSISOLatin1StringEncoding]; - [data release]; data = nil; - - return [result autorelease]; + res[cntRes] = 0; + result = [NSString stringWithCString: (char *) res + encoding: NSISOLatin1StringEncoding]; + + return result; } - (NSString *)stringByDecodingImap4FolderName { - // TBD: this is restricted to Latin1, should be fixed to UTF-8 - /* dude/d&- --> dude/d& */ unsigned char *buf; - unsigned char *res; + unichar *res; unsigned int len; unsigned int cnt = 0; unsigned int cntRes = 0; NSString *result = nil; - NSData *data; +// NSData *data; - if ((len = [self cStringLength]) == 0) + if ((len = [self lengthOfBytesUsingEncoding: NSISOLatin1StringEncoding]) == 0) return @""; - - buf = calloc(len + 3, sizeof(unsigned char)); - res = calloc(len + 3, sizeof(unsigned char)); + + buf = NSZoneMalloc(NULL, (len + 1) * sizeof(unsigned char)); + + if ([self getCString:(char *)buf maxLength: len + 1 + encoding: NSISOLatin1StringEncoding] == NO) { + NSZoneFree(NULL, buf); + return @""; + } buf[len] = '\0'; - res[len] = '\0'; - - [self getCString:(char *)buf]; - - while (cnt < (len - 1)) { /* &- */ + + res = NSZoneMalloc(NULL, (len + 1) * sizeof(unichar)); + + while (cnt < len) { /* &- */ unsigned char c; c = buf[cnt]; @@ -141,29 +110,7 @@ cnt += 2; } else { - unsigned usedBytes = 0; - unsigned char *buffer; - int maxBuf, bufLen; - - cnt++; - maxBuf = 511; - bufLen = 0; - buffer = calloc(maxBuf + 3, sizeof(char)); - - if (_decodeOfModifiedUTF7(buf + cnt, len - cnt, &usedBytes , &buffer, - &bufLen, maxBuf) == 0) { - int cnt1; - - cnt1 = 0; - while (cnt1 < bufLen) { - res[cntRes++] = buffer[cnt1++]; - } - cnt += usedBytes; - } - else { - NSCAssert(NO, @"couldn't decode UTF-7 .."); - } - free(buffer); buffer = NULL; + cnt += _decodeOfModifiedUTF7(buf + cnt + 1, res + cntRes, &cntRes) + 1; } } else { @@ -171,20 +118,133 @@ cnt++; } } - if (cnt < len) - res[cntRes++] = buf[cnt++]; - - if (buf != NULL) free(buf); buf = NULL; - data = [[NSData alloc] initWithBytesNoCopy:res length:cntRes - freeWhenDone:YES]; - result = [[NSString alloc] initWithData:data - encoding:NSISOLatin1StringEncoding]; - [data release]; data = nil; - - return [result autorelease]; + if (buf != NULL) NSZoneFree(NULL, buf); + + res[cntRes] = 0; + result = [NSString stringWithCharacters: res length: cntRes]; + + return result; } +/* check metamail output for correctness */ + +static unsigned char basis_64[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +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 unsigned int _encodeToModifiedUTF7(unichar *_char, unsigned char *result_, + unsigned int *cntRes_) +{ + unsigned int processedSrc, processedDest, cycle; + unichar c; + char leftover; + BOOL hasLeftOver; + + processedSrc = 0; + processedDest = 0; + cycle = 0; + leftover = 0; + + c = *_char; + while (c > 126 || (c > 0 && c < 32)) { + if (cycle == 0) { + *(result_ + processedDest) = basis_64[(c >> 10) & 0x3f]; + *(result_ + processedDest + 1) = basis_64[(c >> 4) & 0x3f]; + leftover = (c << 2); + hasLeftOver = YES; + processedDest += 2; + cycle = 1; + } + else if (cycle == 1) { + *(result_ + processedDest) = basis_64[(leftover | (c >> 14)) & 0x3f]; + *(result_ + processedDest + 1) = basis_64[(c >> 8) & 0x3f]; + *(result_ + processedDest + 2) = basis_64[(c >> 2) & 0x3f]; + leftover = (c << 4); + hasLeftOver = YES; + processedDest += 3; + cycle = 2; + } + else if (cycle == 2) { + *(result_ + processedDest) = basis_64[(leftover | (c >> 12)) & 0x3f]; + *(result_ + processedDest + 1) = basis_64[(c >> 6) & 0x3f]; + *(result_ + processedDest + 2) = basis_64[c & 0x3f]; + leftover = 0; + hasLeftOver = NO; + processedDest += 3; + cycle = 0; + } + processedSrc++; + c = *(_char + processedSrc); + } + if (hasLeftOver) { + *(result_ + processedDest) = basis_64[leftover & 0x3f]; + processedDest++; + } + processedSrc--; + *cntRes_ += processedDest; + + return processedSrc; +} + +static unsigned int _decodeOfModifiedUTF7(unsigned char *_source, unichar *result_, + unsigned int *cntRes_) +{ + unsigned int processedSrc, processedDest; + unsigned char c, decoded; + unichar currentRes; + int shift; + + processedSrc = 0; + processedDest = 0; + shift = 10; + currentRes = 0; + + c = *_source; + while (c != 0 && c != '-') { + decoded = index_64[c]; + if (shift < 0) { + currentRes |= (decoded >> (shift * -1)); + *(result_ + processedDest) = currentRes; + processedDest++; + shift += 16; + currentRes = (decoded << shift); + } else { + currentRes |= (decoded << shift); + if (shift == 0) { + *(result_ + processedDest) = currentRes; + processedDest++; + currentRes = 0; + shift = 16; + } + } + shift -= 6; + processedSrc++; + c = *(_source + processedSrc); + } + if (shift != 10) { + *(result_ + processedDest) = currentRes; + } + if (c == '-') + processedSrc++; + + *cntRes_ += processedDest; + + return processedSrc; +} + - (NSString *)stringByEscapingImap4Password { // TODO: perf unichar *buffer; @@ -193,12 +253,12 @@ NSString *s; len = [self length]; - chars = calloc(len + 2, sizeof(unichar)); + chars = NSZoneCalloc(NULL, len + 2, sizeof(unichar)); [self getCharacters:chars]; - - buffer = calloc(len * 2 + 2, sizeof(unichar)); + + buffer = NSZoneCalloc(NULL, len * 2 + 2, sizeof(unichar)); buffer[len * 2] = '\0'; - + for (i = 0, j = 0; i < len; i++, j++) { BOOL conv = NO; @@ -224,209 +284,11 @@ } buffer[j] = chars[i]; } - if (chars != NULL) free(chars); chars = NULL; + if (chars != NULL) NSZoneFree(NULL, chars); 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_) -{ - int c1, c2, c3; - int cnt, cntAll; - - cnt = 0; - cntAll = 0; - - while (cnt < encLen) { - c1 = getChar(cntAll++, &cnt, _buf); - if (cnt == encLen) { - writeChunk(c1, 0, 0, 2, result_, cntRes_); - } - else { - c2 = getChar(cntAll++, &cnt, _buf); - if (cnt == encLen) { - writeChunk(c1, c2, 0, 1, result_, cntRes_); - } - else { - c3 = getChar(cntAll++, &cnt, _buf); - writeChunk(c1, c2, c3, 0, result_, cntRes_); - } - } - } -} - -/* 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) -{ - int c1, c2, c3, c4; - unsigned int cnt; - - for (cnt = 0; cnt < _targetLen; ) { - c1 = '='; - c2 = '='; - c3 = '='; - c4 = '='; - - c1 = _target[cnt++]; - - if (c1 == '-') { - (*usedBytes_)++; - return 0; - } - if (cnt < _targetLen) - c2 = _target[cnt++]; - - if (c2 == '-') { - (*usedBytes_)+=2; - return 0; - } - - (*usedBytes_) += 2; - - if (cnt < _targetLen) { - c3 = _target[cnt++]; - (*usedBytes_)++; - } - - if (cnt < _targetLen) { - c4 = _target[cnt++]; - if (c3 != '-') - (*usedBytes_)++; - } - - if (c2 == -1 || c3 == -1 || c4 == -1) { - fprintf(stderr, "Warning: base64 decoder saw premature EOF!\n"); - return 0; - } - - if (c1 == '=' || c2 == '=') { - continue; - } - - c1 = char64(c1); - c2 = char64(c2); - - if (*bufLen_ < maxBuf) { - unsigned char c; - - c = ((c1<<2) | ((c2&0x30)>>4)); - - if (c) { - (*buffer_)[*bufLen_] = c; - *bufLen_ = *bufLen_ + 1; - } - } - if (c3 == '-') { - return 0; - } - else if (c3 == '=') { - continue; - } else { - - c3 = char64(c3); - - if (*bufLen_ < maxBuf) { - unsigned char c; - c = (((c2&0XF) << 4) | ((c3&0x3C) >> 2)); - if (c) { - (*buffer_)[*bufLen_] = c; - *bufLen_ = *bufLen_ + 1; - } - } - - if (c4 == '-') { - return 0; - } - else if (c4 == '=') { - continue; - } else { - c4 = char64(c4); - - if (*bufLen_ < maxBuf) { - unsigned char c; - - c = (((c3&0x03) <<6) | c4); - if (c) { - (*buffer_)[*bufLen_] = c; - (*bufLen_) = (*bufLen_) + 1; - } - } - } - } - } - return 0; -} Index: sope-mime/NGImap4/NGImap4Functions.h =================================================================== --- sope-mime/NGImap4/NGImap4Functions.h (revision 1664) +++ sope-mime/NGImap4/NGImap4Functions.h (working copy) @@ -58,4 +58,6 @@ id_folder); BOOL _createSubFolderWithName(id self, NSString *_name, BOOL _app); +NSString *SaneFolderName(NSString *folderName); + #endif /* __NGMime_NGImap4_NGImap4Functions_H__ */ Index: sope-mime/NGMail/NGMailAddressParser.h =================================================================== --- sope-mime/NGMail/NGMailAddressParser.h (revision 1664) +++ 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 1664) +++ 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 1664) +++ 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) { @@ -79,12 +79,29 @@ return returnValue; } +static void dumpBadString(unichar *text, int length) { + char *bytes; + NSMutableString *logString; + int count, max; + max = length * sizeof (unichar); + logString = [NSMutableString stringWithCapacity: max]; + [logString appendString: @"dumping buggy atom string: "]; + bytes = (char *) text; + for (count = 0; count < max; count++) { + [logString appendFormat: @"0x%X", bytes[count]]; + if (count < (max - 1)) + [logString appendString: @", "]; + } + + NSLog (@"%@", logString); +} + static inline id parseAtom(NGMailAddressParser *self, BOOL _guessMode) { 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 +111,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 '>': @@ -128,6 +145,9 @@ else { NSCAssert(length > 0, @"no atom with length=0"); returnValue = [mkStrObj(text, length) autorelease]; + if (!returnValue) { + dumpBadString(text, length); + } NSCAssert([returnValue isKindOfClass:StrClass], @"got no string .."); } } @@ -162,7 +182,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 +192,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 +235,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 +245,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 +340,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 1664) +++ 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 1664) +++ 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 1664) +++ 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 1664) +++ sope-mime/NGMime/NGMimeType.m (working copy) @@ -120,30 +120,30 @@ /* 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-1250"]) { + encoding = NSWindowsCP1250StringEncoding; + } + else if ([charset isEqualToString:@"windows-1251"]) { + encoding = NSWindowsCP1251StringEncoding; + } else if ([charset isEqualToString:@"windows-1252"]) { encoding = NSWindowsCP1252StringEncoding; } @@ -152,7 +152,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 +166,7 @@ else { [self logWithFormat:@"%s: unknown charset '%@'", __PRETTY_FUNCTION__, _s]; - encoding = [NSString defaultCStringEncoding]; + encoding = NSISOLatin1StringEncoding; } return encoding; } @@ -385,23 +385,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; + 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++; + } - if (*cstr == 32) - return YES; - - cstr++; - } - return NO; + return needsQuote; } - (NSString *)stringValue { Index: sope-mime/NGMime/NGMimeBodyPart.m =================================================================== --- sope-mime/NGMime/NGMimeBodyPart.m (revision 1664) +++ 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 1664) +++ 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 1664) +++ 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 1664) +++ 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 1664) +++ 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]]; @@ -88,10 +91,20 @@ NSStringEncoding encoding; encoding = [NGMimeType stringEncodingForCharset:charset]; - + + // If we nave no encoding here, let's not simply return nil. + // We SHOULD try at least UTF-8 and after, Latin1. + if (!encoding) + encoding = NSUTF8StringEncoding; + body = [[[NSString alloc] - initWithData:_data + initWithData:_data encoding:encoding] autorelease]; + + if (!body) + body = [[[NSString alloc] initWithData:_data + encoding:NSISOLatin1StringEncoding] + autorelease]; } return body; } Index: sope-mime/NGMime/NGMimePartParser.h =================================================================== --- sope-mime/NGMime/NGMimePartParser.h (revision 1664) +++ 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 1664) +++ 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 1664) +++ 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 1664) +++ 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-core/NGExtensions/NGExtensions/NSString+Ext.h =================================================================== --- sope-core/NGExtensions/NGExtensions/NSString+Ext.h (revision 1664) +++ 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 1664) +++ 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 1664) +++ 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/EOExt.subproj/EOGlobalID+Ext.m =================================================================== --- sope-core/NGExtensions/EOExt.subproj/EOGlobalID+Ext.m (revision 1664) +++ sope-core/NGExtensions/EOExt.subproj/EOGlobalID+Ext.m (working copy) @@ -19,6 +19,7 @@ 02111-1307, USA. */ +#import #import #import Index: sope-core/NGStreams/NGStream+serialization.m =================================================================== --- sope-core/NGStreams/NGStream+serialization.m (revision 1664) +++ sope-core/NGStreams/NGStream+serialization.m (working copy) @@ -282,10 +282,10 @@ else { char *result = NULL; -#if NeXT_Foundation_LIBRARY +#if LIB_FOUNDATION_LIBRARY + result = NSZoneMallocAtomic(NULL, len + 1); +#else result = NSZoneMalloc(NULL, len + 1); -#else - result = NSZoneMallocAtomic(NULL, len + 1); #endif result[len] = '\0'; Index: sope-core/NGStreams/NGStreams/NGDatagramSocket.h =================================================================== --- sope-core/NGStreams/NGStreams/NGDatagramSocket.h (revision 1664) +++ sope-core/NGStreams/NGStreams/NGDatagramSocket.h (working copy) @@ -69,7 +69,7 @@ + (id)socketBoundToAddress:(id)_address; #if !defined(WIN32) -+ (BOOL)socketPair:(id[2])_pair inDomain:(id)_domain; ++ (BOOL)socketPair:(id[2])_pair; #endif // accessors Index: sope-core/NGStreams/NGStreams/NGActiveSocket.h =================================================================== --- sope-core/NGStreams/NGStreams/NGActiveSocket.h (revision 1664) +++ sope-core/NGStreams/NGStreams/NGActiveSocket.h (working copy) @@ -60,7 +60,7 @@ - (id)initWithDomain:(id)_domain; // designated initializer #if !defined(WIN32) -+ (BOOL)socketPair:(id[2])_pair inDomain:(id)_domain; ++ (BOOL)socketPair:(id[2])_pair; #endif // ******************** operations ******************** Index: sope-core/NGStreams/NGGZipStream.m =================================================================== --- sope-core/NGStreams/NGGZipStream.m (revision 1664) +++ sope-core/NGStreams/NGGZipStream.m (working copy) @@ -52,7 +52,7 @@ @"invalid compression level %i (0-9)", _level); self->outBufLen = 2048; -#if GNU_RUNTIME +#if LIB_FOUNDATION_LIBRARY self->outBuf = NSZoneMallocAtomic([self zone], self->outBufLen); self->outp = NSZoneMallocAtomic([self zone], sizeof(z_stream)); #else Index: sope-core/NGStreams/NGDatagramSocket.m =================================================================== --- sope-core/NGStreams/NGDatagramSocket.m (revision 1664) +++ sope-core/NGStreams/NGDatagramSocket.m (working copy) @@ -25,6 +25,8 @@ #endif #include +#include +#include #include "NGDatagramSocket.h" #include "NGDatagramPacket.h" #include "NGSocketExceptions.h" @@ -55,19 +57,21 @@ #if !defined(WIN32) || defined(__CYGWIN32__) -+ (BOOL)socketPair:(id[2])_pair inDomain:(id)_domain { ++ (BOOL)socketPair:(id[2])_pair { int fds[2]; + NGLocalSocketDomain *domain; _pair[0] = nil; _pair[1] = nil; - if (socketpair([_domain socketDomain], SOCK_DGRAM, [_domain protocol], + domain = [NGLocalSocketDomain domain]; + if (socketpair([domain socketDomain], SOCK_DGRAM, [domain protocol], fds) == 0) { NGDatagramSocket *s1 = nil; NGDatagramSocket *s2 = nil; - s1 = [[self alloc] _initWithDomain:_domain descriptor:fds[0]]; - s2 = [[self alloc] _initWithDomain:_domain descriptor:fds[1]]; + s1 = [[self alloc] _initWithDomain:domain descriptor:fds[0]]; + s2 = [[self alloc] _initWithDomain:domain descriptor:fds[1]]; s1 = AUTORELEASE(s1); s2 = AUTORELEASE(s2); @@ -112,7 +116,7 @@ break; } [[[NGCouldNotCreateSocketException alloc] - initWithReason:reason domain:_domain] raise]; + initWithReason:reason domain:domain] raise]; return NO; } } Index: sope-core/NGStreams/GNUmakefile.preamble =================================================================== --- sope-core/NGStreams/GNUmakefile.preamble (revision 1664) +++ sope-core/NGStreams/GNUmakefile.preamble (working copy) @@ -1,6 +1,7 @@ # compilation settings -MACHCPU = $(shell echo $$MACHTYPE | cut -f 1 -d '-') +# MACHCPU = $(shell echo $$MACHTYPE | cut -f 1 -d '-') +MACHCPU = $(shell uname -m) libNGStreams_INCLUDE_DIRS += \ -I$(GNUSTEP_TARGET_CPU)/$(GNUSTEP_TARGET_OS) \ Index: sope-core/NGStreams/ChangeLog =================================================================== --- sope-core/NGStreams/ChangeLog (revision 1664) +++ sope-core/NGStreams/ChangeLog (working copy) @@ -1,3 +1,14 @@ +2009-11-11 Wolfgang Sourdeau + + * NGActiveSocket.m (+socketPair): removed the domain: parameter as + only AF_LOCAL is permitted. Assign a default instance of + NGLocalSocketAddress to the sockets to "declare" the sockets as + connected. + + * NGActiveSocket.m (-setSendTimeout, -setReceiveTimeout): make use + of those methods to actually configure the timeouts on the socket + objects (via setsockopt and SO_RCVTIMEO and SO_SNDTIMEO). + 2009-03-24 Wolfgang Sourdeau * GNUmakefile.preamble: add machine-type to include path (eg i386) (v4.7.57) Index: sope-core/NGStreams/NGCharBuffer.m =================================================================== --- sope-core/NGStreams/NGCharBuffer.m (revision 1664) +++ sope-core/NGStreams/NGCharBuffer.m (working copy) @@ -46,11 +46,11 @@ // Find first power of 2 >= to requested size for (size = 2; size < _la; size *=2); -#if NeXT_Foundation_LIBRARY - self->la = NSZoneMalloc([self zone], sizeof(LA_NGCharBuffer) * size); -#else +#if LIB_FOUNDATION_LIBRARY self->la = NSZoneMallocAtomic([self zone], sizeof(LA_NGCharBuffer) * size); +#else + self->la = NSZoneMalloc([self zone], sizeof(LA_NGCharBuffer) * size); #endif memset(self->la, 0, sizeof(LA_NGCharBuffer) * size); Index: sope-core/NGStreams/NGActiveSocket.m =================================================================== --- sope-core/NGStreams/NGActiveSocket.m (revision 1664) +++ sope-core/NGStreams/NGActiveSocket.m (working copy) @@ -62,6 +62,8 @@ #include "common.h" #include +#include +#include #include "NGActiveSocket.h" #include "NGSocketExceptions.h" #include "NGSocket+private.h" @@ -83,29 +85,35 @@ #if !defined(WIN32) || defined(__CYGWIN32__) -+ (BOOL)socketPair:(id[2])_pair inDomain:(id)_domain { ++ (BOOL)socketPair:(id[2])_pair { int fds[2]; + NGLocalSocketDomain *domain; _pair[0] = nil; _pair[1] = nil; - if (socketpair([_domain socketDomain], SOCK_STREAM, [_domain protocol], + domain = [NGLocalSocketDomain domain]; + if (socketpair([domain socketDomain], SOCK_STREAM, [domain protocol], fds) == 0) { NGActiveSocket *s1 = nil; NGActiveSocket *s2 = nil; + NGLocalSocketAddress *address; - s1 = [[self alloc] _initWithDomain:_domain descriptor:fds[0]]; - s2 = [[self alloc] _initWithDomain:_domain descriptor:fds[1]]; + s1 = [[self alloc] _initWithDomain:domain descriptor:fds[0]]; + s2 = [[self alloc] _initWithDomain:domain descriptor:fds[1]]; s1 = [s1 autorelease]; s2 = [s2 autorelease]; + address = [NGLocalSocketAddress address]; if ((s1 != nil) && (s2 != nil)) { s1->mode = NGStreamMode_readWrite; s1->receiveTimeout = 0.0; s1->sendTimeout = 0.0; + ASSIGN(s1->remoteAddress, address); s2->mode = NGStreamMode_readWrite; s2->receiveTimeout = 0.0; s2->sendTimeout = 0.0; + ASSIGN(s2->remoteAddress, address); _pair[0] = s1; _pair[1] = s2; @@ -152,7 +160,7 @@ break; } [[[NGCouldNotCreateSocketException alloc] - initWithReason:reason domain:_domain] raise]; + initWithReason:reason domain:domain] raise]; return NO; } } @@ -507,6 +515,13 @@ } - (void)setSendTimeout:(NSTimeInterval)_timeout { + struct timeval tv; + + if ([self isConnected]) { + tv.tv_sec = (int) _timeout; + tv.tv_usec = 0; + setsockopt(self->fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof (struct timeval)); + } self->sendTimeout = _timeout; } - (NSTimeInterval)sendTimeout { @@ -514,6 +529,13 @@ } - (void)setReceiveTimeout:(NSTimeInterval)_timeout { + struct timeval tv; + + if ([self isConnected]) { + tv.tv_sec = (int) _timeout; + tv.tv_usec = 0; + setsockopt(self->fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof (struct timeval)); + } self->receiveTimeout = _timeout; } - (NSTimeInterval)receiveTimeout { Index: sope-xml/libxmlSAXDriver/libxmlHTMLSAXDriver.h =================================================================== --- sope-xml/libxmlSAXDriver/libxmlHTMLSAXDriver.h (revision 1664) +++ 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 1664) +++ sope-xml/libxmlSAXDriver/libxmlHTMLSAXDriver.m (working copy) @@ -200,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; } Index: sope-xml/SaxObjC/XMLNamespaces.h =================================================================== --- sope-xml/SaxObjC/XMLNamespaces.h (revision 1664) +++ sope-xml/SaxObjC/XMLNamespaces.h (working copy) @@ -292,6 +292,9 @@ #ifndef XMLNS_AppleCalServer # define XMLNS_AppleCalServer @"http://apple.com/ns/calendarserver/" #endif +#ifndef XMLNS_CalendarServerOrg +# define XMLNS_CalendarServerOrg @"http://calendarserver.org/ns/" +#endif #ifndef XMLNS_AppleCalApp # define XMLNS_AppleCalApp @"com.apple.ical:" #endif Index: sope-appserver/mod_ngobjweb/GNUmakefile =================================================================== --- sope-appserver/mod_ngobjweb/GNUmakefile (revision 1664) +++ 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/WOWatchDogApplicationMain.m =================================================================== --- sope-appserver/NGObjWeb/WOWatchDogApplicationMain.m (revision 1664) +++ sope-appserver/NGObjWeb/WOWatchDogApplicationMain.m (working copy) @@ -1,5 +1,6 @@ /* Copyright (C) 2000-2005 SKYRIX Software AG + Copyright (C) 2009 Inverse inc. This file is part of SOPE. @@ -19,9 +20,29 @@ 02111-1307, USA. */ -#import -#include +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +#import "UnixSignalHandler.h" + #if defined(__CYGWIN32__) || defined(__MINGW32__) int WOWatchDogApplicationMain @@ -39,201 +60,911 @@ #include #include -static pid_t child = -1; -static NSString *pidFile = nil; -static time_t lastFailExit = 0; -static unsigned failExitCount = 0; -static BOOL killedChild = NO; +static NSTimeInterval respawnDelay; /* seconds */ +static const char *pidFile = NULL; -static void killChild(void) { - if (child > 0) { - int status; - - fprintf(stderr, "watchdog[%i]: terminating child %i ..\n", getpid(), child); - - if (kill(child, SIGTERM) == 0) { - waitpid(child, &status, 0); - killedChild = YES; - - fprintf(stderr, " terminated child %i", child); - - if (WIFEXITED(status)) - fprintf(stderr, " exit=%i", WEXITSTATUS(status)); - if (WIFSIGNALED(status)) - fprintf(stderr, " signal=%i", WTERMSIG(status)); - - fprintf(stderr, ".\n"); - fflush(stderr); - - child = -1; - return; +typedef enum { + WOChildStatusDown = 0, + WOChildStatusSpawning, + WOChildStatusReady, + WOChildStatusBusy, + WOChildStatusExcessive, + WOChildStatusTerminating, + WOChildStatusMax +} WOChildStatus; + +@class WOWatchDog; + +@interface WOWatchDogChild : NSObject +{ + int pid; + int SIGCHLDStatus; + int counter; + NGActiveSocket *controlSocket; + WOChildStatus status; + WOWatchDog *watchDog; + NSCalendarDate *lastSpawn; + BOOL loggedNotRespawn; +} + +- (void) setWatchDog: (WOWatchDog *) newWatchDog; + +- (void) setPid: (int) newPid; +- (int) pid; + +- (void) setSIGCHLDStatus: (int) newSIGCHLDStatus; +- (int) SIGCHLDStatus; +- (void) handleSIGCHLDStatus; + +- (void) setControlSocket: (NGActiveSocket *) newSocket; +- (NGActiveSocket *) controlSocket; + +- (void) setStatus: (WOChildStatus) newStatus; +- (WOChildStatus) status; + +- (void) setLastSpawn: (NSCalendarDate *) newLastSpawn; +- (NSCalendarDate *) lastSpawn; +- (NSCalendarDate *) nextSpawn; +- (void) logNotRespawn; + +- (BOOL) readMessage; + +- (void) notify; +- (void) terminate; + +@end + +@interface WOWatchDog : NSObject +{ + NSString *appName; + int argc; + const char **argv; + + BOOL terminate; + BOOL willTerminate; + NSNumber *terminationSignal; + int pendingSIGCHLD; + int pendingSIGHUP; + + NGPassiveSocket *listeningSocket; + + int numberOfChildren; + NSMutableArray *children; + NSMutableArray *readyChildren; + NSMutableArray *downChildren; +} + ++ (id) sharedWatchDog; + +- (void) declareChildReady: (WOWatchDogChild *) readyChild; +- (void) declareChildDown: (WOWatchDogChild *) readyChild; + +- (int) run: (NSString *) appName + argc: (int) argc + argv: (const char **) argv; + +@end + +@implementation WOWatchDogChild + ++ (WOWatchDogChild *) watchDogChild +{ + WOWatchDogChild *newChild; + + newChild = [self new]; + [newChild autorelease]; + + return newChild; +} + +- (id) init +{ + if ((self = [super init])) + { + pid = -1; + controlSocket = nil; + SIGCHLDStatus = -1; + status = WOChildStatusDown; + counter = 0; + lastSpawn = nil; + loggedNotRespawn = NO; } - else if (kill(child, SIGKILL)) { - waitpid(child, &status, 0); - killedChild = YES; - - fprintf(stderr, " killed child %i", child); - - if (WIFEXITED(status)) - fprintf(stderr, " exit=%i", WEXITSTATUS(status)); - if (WIFSIGNALED(status)) - fprintf(stderr, " signal=%i", WTERMSIG(status)); - - fprintf(stderr, ".\n"); - fflush(stderr); - - child = -1; - return; + + return self; +} + +- (void) dealloc +{ + [self setControlSocket: nil]; + [lastSpawn release]; + [super dealloc]; +} + +- (void) setWatchDog: (WOWatchDog *) newWatchDog +{ + watchDog = newWatchDog; +} + +- (void) setPid: (int) newPid +{ + pid = newPid; +} + +- (int) pid +{ + return pid; +} + +- (void) setSIGCHLDStatus: (int) newSIGCHLDStatus +{ + SIGCHLDStatus = newSIGCHLDStatus; +} + +- (int) SIGCHLDStatus +{ + return SIGCHLDStatus; +} + +- (void) handleSIGCHLDStatus +{ + int code; + + [self logWithFormat: @"received SIGCHLD from pid %d", pid]; + code = WEXITSTATUS (SIGCHLDStatus); + if (code != 0) + [self logWithFormat: @"child %d exited with code %i", pid, code]; + if (WIFSIGNALED (SIGCHLDStatus)) + [self logWithFormat: @" (terminated due to signal %i%@)", + WTERMSIG (SIGCHLDStatus), + WCOREDUMP (SIGCHLDStatus) ? @", coredump" : @""]; + if (WIFSTOPPED (SIGCHLDStatus)) + [self logWithFormat: @" (stopped due to signal %i)", WSTOPSIG (SIGCHLDStatus)]; + SIGCHLDStatus = -1; + [self setStatus: WOChildStatusDown]; + [self setControlSocket: nil]; +} + +- (void) setControlSocket: (NGActiveSocket *) newSocket +{ + NSRunLoop *runLoop; + + runLoop = [NSRunLoop currentRunLoop]; + if (controlSocket) + [runLoop removeEvent: (void *) [controlSocket fileDescriptor] + type: ET_RDESC + forMode: NSDefaultRunLoopMode + all: YES]; + [controlSocket close]; + ASSIGN (controlSocket, newSocket); + if (controlSocket) + [runLoop addEvent: (void *) [controlSocket fileDescriptor] + type: ET_RDESC + watcher: self + forMode: NSDefaultRunLoopMode]; +} + +- (NGActiveSocket *) controlSocket +{ + return controlSocket; +} + +- (void) setStatus: (WOChildStatus) newStatus +{ + status = newStatus; +} + +- (WOChildStatus) status +{ + return status; +} + +- (void) setLastSpawn: (NSCalendarDate *) newLastSpawn +{ + ASSIGN (lastSpawn, newLastSpawn); + loggedNotRespawn = NO; +} + +- (NSCalendarDate *) lastSpawn +{ + return lastSpawn; +} + +- (NSCalendarDate *) nextSpawn +{ + return [lastSpawn addYear: 0 month: 0 day: 0 + hour: 0 minute: 0 + second: respawnDelay]; +} + +- (void) logNotRespawn +{ + if (!loggedNotRespawn) + { + [self logWithFormat: + @"avoiding to respawn child before %@", [self nextSpawn]]; + loggedNotRespawn = YES; } +} + +- (BOOL) readMessage +{ + WOChildMessage message; + BOOL rc; + + if ([controlSocket readBytes: &message + count: sizeof (WOChildMessage)] == NGStreamError) { + rc = NO; + [self errorWithFormat: @"FAILURE receiving status for child %d", pid]; + [self errorWithFormat: @" socket: %@", controlSocket]; } + else { + rc = YES; + if (message == WOChildMessageAccept) { + status = WOChildStatusBusy; + } + else if (message == WOChildMessageReady) { + status = WOChildStatusReady; + [watchDog declareChildReady: self]; + } + // else if (message == WOChildMessageShutdown) { + // status = WOChildStatusDown; + // [watchDog declareChildDown: self]; + // } + // [self logStatus]; + } + + return rc; } -static void _writePid(NSString *pidFile) { - if ([pidFile length] > 0) { - FILE *pf; - - if ((pf = fopen([pidFile cString], "w"))) { - fprintf(pf, "%i\n", getpid()); - fflush(pf); - fclose(pf); +- (BOOL) _sendMessage: (WOChildMessage) message +{ + return ([controlSocket writeBytes: &message + count: sizeof (WOChildMessage)] != NGStreamError + && [self readMessage]); +} + +- (void) _killKill +{ + if (status != WOChildStatusDown) { + [self warnWithFormat: @"sending KILL signal to pid %d", pid]; + kill (pid, SIGKILL); + } +} + +- (void) _kill +{ + if (status != WOChildStatusDown) { + [self logWithFormat: @"sending terminate signal to pid %d", pid]; + status = WOChildStatusTerminating; + kill (pid, SIGTERM); + /* We hardcode a 5 minutes delay before ensuring that all children are + terminated. This enables long requests to finish properly while + avoiding 100% CPU usage for deadlocked children. */ + [NSTimer scheduledTimerWithTimeInterval: 5.0 * 60 + target: self + selector: @selector (_killKill) + userInfo: nil + repeats: NO]; + } +} + +- (void) notify +{ + WOChildMessage message; + + counter++; + message = WOChildMessageAccept; + if (![self _sendMessage: message]) { + [self errorWithFormat: @"FAILURE notifying child %d", pid]; + [self _kill]; + } +} + +- (void) terminate +{ + if (status == WOChildStatusDown) { + [self logWithFormat: @"child is already down"]; + } else { + [self setControlSocket: nil]; + [self _kill]; + } +} + +- (void) receivedEvent: (void*)data + type: (RunLoopEventType)type + extra: (void*)extra + forMode: (NSString*)mode +{ + if ([controlSocket isAlive]) + [self readMessage]; + else { + /* This happens when a socket has been closed by the child but the child + has not terminated yet. */ + [[NSRunLoop currentRunLoop] removeEvent: data + type: ET_RDESC + forMode: NSDefaultRunLoopMode + all: YES]; + [self setControlSocket: nil]; + } +} + +@end + +@implementation WOWatchDog + ++ (id) sharedWatchDog +{ + static WOWatchDog *sharedWatchDog = nil; + + if (!sharedWatchDog) + sharedWatchDog = [self new]; + + return sharedWatchDog; +} + +- (id) init +{ + if ((self = [super init])) + { + listeningSocket = nil; + terminate = NO; + willTerminate = NO; + terminationSignal = nil; + pendingSIGCHLD = 0; + pendingSIGHUP = 0; + + numberOfChildren = 0; + children = [[NSMutableArray alloc] initWithCapacity: 10]; + readyChildren = [[NSMutableArray alloc] initWithCapacity: 10]; + downChildren = [[NSMutableArray alloc] initWithCapacity: 10]; } + + return self; +} + +- (void) _releaseListeningSocket +{ + if (listeningSocket) { + [[NSRunLoop currentRunLoop] removeEvent: (void *) [listeningSocket fileDescriptor] + type: ET_RDESC + forMode: NSDefaultRunLoopMode + all: YES]; + [listeningSocket close]; + [listeningSocket release]; + listeningSocket = nil; } } -static void _delPid(void) { - if ([pidFile length] > 0) { - if (unlink([pidFile cString]) == 0) - pidFile = nil; + +- (void) dealloc +{ + [self _releaseListeningSocket]; + [terminationSignal release]; + [appName release]; + [children release]; + [super dealloc]; +} + +- (void) _runChildWithControlSocket: (NGActiveSocket *) controlSocket +{ + WOApplication *app; + extern char **environ; + + [NSProcessInfo initializeWithArguments: (char **) argv + count: argc + environment: environ]; + NGInitTextStdio(); + app = [NSClassFromString(appName) new]; + [app autorelease]; + [app setListeningSocket: listeningSocket]; + [app setControlSocket: controlSocket]; + [app run]; +} + +- (void) receivedEvent: (void*)data + type: (RunLoopEventType)type + extra: (void*)extra + forMode: (NSString*)mode +{ + int nextId; + WOWatchDogChild *child; + + // NSLog (@"have a child accept the connection"); + nextId = [readyChildren count] - 1; + if (nextId > -1) + { + child = [readyChildren objectAtIndex: nextId]; + [readyChildren removeObjectAtIndex: nextId]; + [child notify]; + } + // else + // NSLog (@"all children busy"); +} + +- (void) _cleanupSignalAndEventHandlers +{ + int count; + NSRunLoop *runLoop; + + [[UnixSignalHandler sharedHandler] removeObserver: self]; + + runLoop = [NSRunLoop currentRunLoop]; + [runLoop removeEvent: (void *) [listeningSocket fileDescriptor] + type: ET_RDESC + forMode: NSDefaultRunLoopMode + all: YES]; + + for (count = 0; count < numberOfChildren; count++) { + [[children objectAtIndex: count] setControlSocket: nil]; + // controlSocket = [[children objectAtIndex: count] controlSocket]; + // if (controlSocket) + // [runLoop removeEvent: (void *) [controlSocket fileDescriptor] + // type: ET_RDESC + // forMode: NSDefaultRunLoopMode + // all: YES]; } } -static void exitWatchdog(void) { - killChild(); - _delPid(); +- (BOOL) _spawnChild: (WOWatchDogChild *) child +{ + NGActiveSocket *pair[2]; + BOOL isChild; + int childPid; + extern char **environ; + + isChild = NO; + + if ([NGActiveSocket socketPair: pair]) { + childPid = fork (); + if (childPid == 0) { + setsid (); + isChild = YES; + [self _cleanupSignalAndEventHandlers]; + [self _runChildWithControlSocket: pair[0]]; + } else if (childPid > 0) { + [self logWithFormat: @"child spawned with pid %d", childPid]; + [child setPid: childPid]; + [child setStatus: WOChildStatusSpawning]; + [child setControlSocket: pair[1]]; + [child setLastSpawn: [NSCalendarDate date]]; + // [self logWithFormat: @"parent ready for child: %d", childPid]; + } else { + perror ("fork"); + } + } + + return isChild; } -static void wsignalHandler(int _signal) { - switch (_signal) { - case SIGINT: - /* Control-C */ - fprintf(stderr, "[%i]: watchdog handling signal ctrl-c ..\n", getpid()); - killChild(); - exit(0); - /* shouldn't get here */ - abort(); +- (void) _ensureNumberOfChildren +{ + int currentNumber, delta, count, min, max; + WOWatchDogChild *child; - case SIGSEGV: - /* Coredump ! */ - fprintf(stderr, - "[%i]: watchdog handling segmentation fault " - "(SERIOUS PROBLEM) ..\n", - getpid()); - killChild(); - exit(123); - /* shouldn't get here */ - abort(); + currentNumber = [children count]; + if (currentNumber < numberOfChildren) { + delta = numberOfChildren - currentNumber; + for (count = 0; count < delta; count++) { + child = [WOWatchDogChild watchDogChild]; + [child setWatchDog: self]; + [children addObject: child]; + [downChildren addObject: child]; + } + [self logWithFormat: @"preparing %d children", delta]; + } + else if (currentNumber > numberOfChildren) { + delta = currentNumber - numberOfChildren; + max = [downChildren count]; + if (max > delta) + min = max - delta; + else + min = 0; + for (count = max - 1; count >= min; count--) { + child = [downChildren objectAtIndex: count]; + [downChildren removeObjectAtIndex: count]; + [children removeObject: child]; + delta--; + [self logWithFormat: @"%d processes purged from pool", delta]; + } - case SIGTERM: - /* TERM signal (kill 'pid') */ - fprintf(stderr, "[%i]: watchdog handling SIGTERM ..\n", getpid()); - killChild(); - exit(0); - /* shouldn't get here */ - abort(); - - case SIGHUP: - /* HUP signal (restart children) */ - fprintf(stderr, "[%i]: watchdog handling SIGHUP ..\n", getpid()); - killChild(); - killedChild = YES; - signal(_signal, wsignalHandler); - return; - - case SIGCHLD: - break; - - default: - fprintf(stderr, "[%i]: watchdog handling signal %i ..\n", - getpid(), _signal); - break; + max = [readyChildren count]; + if (max > delta) + max -= delta; + for (count = max - 1; count > -1; count--) { + child = [readyChildren objectAtIndex: count]; + [readyChildren removeObjectAtIndex: count]; + [child terminate]; + [child setStatus: WOChildStatusExcessive]; + delta--; + } + [self logWithFormat: @"%d processes left to terminate", delta]; } - fflush(stderr); - - switch (_signal) { - case SIGTERM: - case SIGINT: - case SIGKILL: - case SIGILL: - killChild(); - exit(0); - break; - - case SIGHUP: - killChild(); - break; - - case SIGCHLD: { - int returnStatus; - pid_t result; - - // NSLog(@"SIGNAL: SIGCHLD"); - // fetch return state - - do { - result = waitpid(-1, &returnStatus, WNOHANG); - if (result > 0) { - fprintf(stderr, "[%i]: process %i exited with code %i", - getpid(), (int)result, WEXITSTATUS(returnStatus)); +} - if (WIFSIGNALED(returnStatus)) { - fprintf(stderr, " (terminated due to signal %i%s)", - WTERMSIG(returnStatus), - WCOREDUMP(returnStatus) ? ", coredump" : ""); - } - if (WIFSTOPPED(returnStatus)) { - fprintf(stderr, " (stopped due to signal %i)", - WSTOPSIG(returnStatus)); - } - - fprintf(stderr, "\n"); - fflush(stderr); +- (void) _noop +{ +} + +- (BOOL) _ensureChildren +{ + int count, max; + WOWatchDogChild *child; + BOOL isChild, delayed; + NSCalendarDate *now, *nextSpawn; + + isChild = NO; + + if (!willTerminate) { + [self _ensureNumberOfChildren]; + max = [downChildren count]; + for (count = max - 1; !isChild && count > -1; count--) { + delayed = NO; + child = [downChildren objectAtIndex: count]; + + if ([child status] == WOChildStatusExcessive) + [children removeObject: child]; + else { + now = [NSCalendarDate date]; + nextSpawn = [child nextSpawn]; + if ([nextSpawn earlierDate: now] == nextSpawn) + isChild = [self _spawnChild: child]; + else { + delayed = YES; + [child logNotRespawn]; } } - while (result > 0); - - break; + if (!delayed) + [downChildren removeObjectAtIndex: count]; } - - default: - fprintf(stderr, "watchdog[%i]: caught signal %i\n", getpid(), _signal); - break; } - signal(_signal, wsignalHandler); + + return isChild; } -static void signalHandler(int _signal) { - fprintf(stderr, "[%i]: handling signal %i ..\n", - getpid(), _signal); - fflush(stderr); - - switch (_signal) { - case SIGPIPE: - fprintf(stderr, "[%i]: caught signal SIGPIPE\n", getpid()); - break; - - default: - fprintf(stderr, "[%i]: caught signal %i\n", getpid(), _signal); - break; +/* SOPE on GNUstep does not need to parse the argument line, since the + arguments will be put in the NSArgumentDomain. I don't know about + libFoundation but OSX is supposed to act the same way. */ +- (NGInternetSocketAddress *) _listeningAddress +{ + NGInternetSocketAddress *listeningAddress; + NSUserDefaults *ud; + id port, allow; + + listeningAddress = nil; + + ud = [NSUserDefaults standardUserDefaults]; + port = [ud objectForKey:@"p"]; + if (!port) { + port = [ud objectForKey:@"WOPort"]; + if (!port) + port = @"auto"; } - signal(_signal, signalHandler); + allow = [ud objectForKey:@"WOHttpAllowHost"]; + if (allow) + [self warnWithFormat: @"'WOHttpAllowHost' is ignored in watchdog mode, use a real firewall instead"]; + if ([port isKindOfClass: [NSString class]]) { + if ([port isEqualToString: @"auto"]) { + listeningAddress + = [[NGInternetSocketAddress alloc] initWithPort:0 onHost:@"127.0.0.1"]; + [listeningAddress autorelease]; + } else if ([port rangeOfString: @":"].location == NSNotFound) { + if (allow) + listeningAddress = + [NGInternetSocketAddress wildcardAddressWithPort:[port intValue]]; + else + port = [NSString stringWithFormat: @"127.0.0.1:%d", [port intValue]]; + } + } + else { + if (allow) + listeningAddress = + [NGInternetSocketAddress wildcardAddressWithPort:[port intValue]]; + else { + port = [NSString stringWithFormat: @"127.0.0.1:%@", port]; + } + } + + if (!listeningAddress) + listeningAddress = (NGInternetSocketAddress *) NGSocketAddressFromString(port); + + return listeningAddress; } +- (BOOL) _prepareListeningSocket +{ + NGInternetSocketAddress *addr; + NSString *address; + BOOL rc; + int backlog; + + addr = [self _listeningAddress]; + NS_DURING { + [listeningSocket release]; + listeningSocket = [[NGPassiveSocket alloc] initWithDomain: [addr domain]]; + [listeningSocket bindToAddress: addr]; + backlog = [[NSUserDefaults standardUserDefaults] + integerForKey: @"WOListenQueueSize"]; + if (!backlog) + backlog = 5; + [listeningSocket listenWithBacklog: backlog]; + address = [addr address]; + if (!address) + address = @"*"; + [self logWithFormat: @"listening on %@:%d", address, [addr port]]; + [[NSRunLoop currentRunLoop] addEvent: (void *) [listeningSocket fileDescriptor] + type: ET_RDESC + watcher: self + forMode: NSDefaultRunLoopMode]; + rc = YES; + } + NS_HANDLER { + // [self logWithFormat:@"failure listening on address 127.0.0.1:%d", port]; + rc = NO; + } + NS_ENDHANDLER; + + return rc; +} + +- (WOWatchDogChild *) _childWithPID: (pid_t) childPid +{ + WOWatchDogChild *currentChild, *child; + int count; + + child = nil; + for (count = 0; !child && count < numberOfChildren; count++) { + currentChild = [children objectAtIndex: count]; + if ([currentChild pid] == childPid) + child = currentChild; + } + + return child; +} + +- (void) _handleSIGPIPE:(NSNumber *)_signal { + [self logWithFormat: @"received SIGPIPE (unhandled)"]; +} + +- (void) _handleSIGCHLD:(NSNumber *)_signal { + WOWatchDogChild *child; + pid_t childPid; + int status; + + childPid = wait (&status); + if (childPid > -1) { + pendingSIGCHLD++; + child = [self _childWithPID: childPid]; + [child setSIGCHLDStatus: status]; + } +} + +- (void) _handleTermination:(NSNumber *)_signal { + if (!terminationSignal) { + ASSIGN (terminationSignal, _signal); + if (pidFile) + unlink (pidFile); + } +} + +- (void) _handleSIGHUP:(NSNumber *)_signal { + pendingSIGHUP++; +} + +- (void) _setupSignals +{ +#if !defined(__MINGW32__) && !defined(NeXT_Foundation_LIBRARY) + UnixSignalHandler *us; + + us = [UnixSignalHandler sharedHandler]; + [us addObserver:self selector:@selector(_handleSIGPIPE:) + forSignal:SIGPIPE immediatelyNotifyOnSignal:YES]; + [us addObserver:self selector:@selector(_handleSIGCHLD:) + forSignal:SIGCHLD immediatelyNotifyOnSignal:YES]; + [us addObserver:self selector:@selector(_handleTermination:) + forSignal:SIGINT immediatelyNotifyOnSignal:YES]; + [us addObserver:self selector:@selector(_handleTermination:) + forSignal:SIGTERM immediatelyNotifyOnSignal:YES]; + // [us addObserver:self selector:@selector(_handleSIGKILL:) + // forSignal:SIGKILL immediatelyNotifyOnSignal:YES]; + [us addObserver:self selector:@selector(_handleSIGHUP:) + forSignal:SIGHUP immediatelyNotifyOnSignal:YES]; +#endif +} + +- (void) declareChildReady: (WOWatchDogChild *) readyChild +{ + [readyChildren addObject: readyChild]; +} + +- (void) declareChildDown: (WOWatchDogChild *) downChild +{ + if (![downChildren containsObject: downChild]) + [downChildren addObject: downChild]; +} + +- (void) _ensureWorkersCount +{ + int newNumberOfChildren; + NSUserDefaults *ud; + + ud = [NSUserDefaults standardUserDefaults]; + [ud synchronize]; + newNumberOfChildren = [ud integerForKey: @"WOHttpAdaptorForkCount"]; + if (newNumberOfChildren) + [self logWithFormat: @"user default 'WOHttpAdaptorForkCount' has been" + " replaced with 'WOWorkersCount'"]; + else + newNumberOfChildren = [ud integerForKey: @"WOWorkersCount"]; + if (newNumberOfChildren < 1) + newNumberOfChildren = 1; + numberOfChildren = newNumberOfChildren; +} + +- (void) _handlePostTerminationSignal +{ + WOWatchDogChild *child; + int count; + + [self logWithFormat: @"Terminating with signal %@", terminationSignal]; + [self _releaseListeningSocket]; + for (count = 0; count < numberOfChildren; count++) { + child = [children objectAtIndex: count]; + if ([child status] != WOChildStatusDown + && [child status] != WOChildStatusTerminating) + [child terminate]; + } + [terminationSignal release]; + terminationSignal = nil; + if ([downChildren count] == numberOfChildren) { + [self logWithFormat: @"all children exited. We now terminate."]; + terminate = YES; + } + else + willTerminate = YES; +} + +- (void) _handlePostSIGCHLDStatus +{ + int status, count; + WOWatchDogChild *child; + + for (count = 0; pendingSIGCHLD && count < numberOfChildren; count++) { + child = [children objectAtIndex: count]; + status = [child SIGCHLDStatus]; + if (status != -1) { + [child handleSIGCHLDStatus]; + pendingSIGCHLD--; + [self declareChildDown: child]; + if (willTerminate && [downChildren count] == numberOfChildren) { + [self logWithFormat: @"all children exited. We now terminate."]; + terminate = YES; + } + } + } +} + +- (void) _setupProcessName +{ + NSProcessInfo *processInfo; + NSString *name; + + /* this does not seem to work */ + processInfo = [NSProcessInfo processInfo]; + name = [processInfo processName]; + if (!name) + name = @""; + [processInfo setProcessName: [NSString stringWithFormat: @"%@: %@ watchdog", + name, appName]]; +} + +- (int) run: (NSString *) newAppName + argc: (int) newArgC argv: (const char **) newArgV +{ + NSAutoreleasePool *pool; + NSRunLoop *runLoop; + NSDate *limitDate; + BOOL listening; + int retries; + + willTerminate = NO; + + ASSIGN (appName, newAppName); + [self _setupProcessName]; + + argc = newArgC; + argv = newArgV; + + listening = NO; + retries = 0; + while (!listening && retries < 5) { + listening = [self _prepareListeningSocket]; + retries++; + if (!listening) { + [self warnWithFormat: @"listening socket: attempt %d failed", retries]; + [NSThread sleepForTimeInterval: 1.0]; + } + } + if (listening) { + [self logWithFormat: @"watchdog process pid: %d", getpid ()]; + [self _setupSignals]; + [self _ensureWorkersCount]; + + // NSLog (@"ready to process requests"); + runLoop = [NSRunLoop currentRunLoop]; + + /* This timer ensures the looping of the runloop at reasonable intervals + for correct processing of signal handlers. */ + [NSTimer scheduledTimerWithTimeInterval: 0.5 + target: self + selector: @selector (_noop) + userInfo: nil + repeats: YES]; + terminate = NO; + while (!terminate) { + pool = [NSAutoreleasePool new]; + + while (pendingSIGHUP) { + [self logWithFormat: @"received SIGHUP"]; + [self _ensureWorkersCount]; + pendingSIGHUP--; + } + + // [self logWithFormat: @"watchdog loop"]; + NS_DURING { + terminate = [self _ensureChildren]; + if (!terminate) { + limitDate = [runLoop limitDateForMode:NSDefaultRunLoopMode]; + [runLoop runMode: NSDefaultRunLoopMode beforeDate: limitDate]; + } + } + NS_HANDLER { + terminate = YES; + [self errorWithFormat: + @"an exception occured in runloop %@", localException]; + } + NS_ENDHANDLER; + + if (!terminate) { + if (terminationSignal) + [self _handlePostTerminationSignal]; + while (pendingSIGCHLD) + [self _handlePostSIGCHLDStatus]; + } + [pool release]; + } + + [[UnixSignalHandler sharedHandler] removeObserver: self]; + } + else + [self errorWithFormat: @"unable to listen on specified port," + @" check that no other process is already using it"]; + + return 0; +} + +@end + +static BOOL _writePid(NSString *nsPidFile) { + NSString *pid; + BOOL rc; + + pid = [NSString stringWithFormat: @"%d", getpid()]; + rc = [pid writeToFile: nsPidFile atomically: NO]; + + return rc; +} + int WOWatchDogApplicationMain (NSString *appName, int argc, const char *argv[]) { NSAutoreleasePool *pool; NSUserDefaults *ud; + NSString *logFile, *nsPidFile; + int rc, stdErrNo; + pid_t childPid; + NSProcessInfo *processInfo; pool = [[NSAutoreleasePool alloc] init]; + #if LIB_FOUNDATION_LIBRARY || defined(GS_PASS_ARGUMENTS) { extern char **environ; @@ -241,179 +972,68 @@ environment:(void*)environ]; } #endif - + + /* This invocation forces the class initialization of WOCoreApplication, + which causes the NSUserDefaults to be initialized as well with + Defaults.plist. */ + [NSClassFromString (appName) class]; + ud = [NSUserDefaults standardUserDefaults]; - - /* default is to use the watch dog! */ - /* Note: the Defaults.plist is not yet loaded at this stage! */ - if ([ud objectForKey:@"WOUseWatchDog"] != nil) { - if (![ud boolForKey:@"WOUseWatchDog"]) - return WOApplicationMain(appName, argc, argv); + processInfo = [NSProcessInfo processInfo]; + + logFile = [ud objectForKey: @"WOLogFile"]; + if (!logFile) + logFile = [NSString stringWithFormat: @"/var/log/%@/%@.log", + [processInfo processName], + [processInfo processName]]; + if (![logFile isEqualToString: @"-"]) { + stdErrNo = dup(fileno(stderr)); + stdout = freopen([logFile cString], "a", stdout); + stderr = freopen([logFile cString], "a", stderr); } - - /* watch dog */ - { - int failCount = 0; - int forkCount = 0; - BOOL repeat = YES; - BOOL isVerbose = NO; - - isVerbose = [[ud objectForKey:@"watchdog_verbose"] boolValue]; - pidFile = [[[ud objectForKey:@"watchdog_pidfile"] stringValue] copy]; - - /* write current pid to pidfile */ - _writePid(pidFile); - - /* register exit handler */ - atexit(exitWatchdog); - - /* register signal handlers of watch dog */ - signal(SIGPIPE, wsignalHandler); - signal(SIGCHLD, wsignalHandler); - signal(SIGINT, wsignalHandler); - signal(SIGTERM, wsignalHandler); - signal(SIGKILL, wsignalHandler); - signal(SIGHUP, wsignalHandler); - - /* loop */ - - while (repeat) { - time_t clientStartTime; - - clientStartTime = time(NULL); - killedChild = NO; - - if ((child = fork()) == -1) { - fprintf(stderr, "[%i]: fork failed: %s\n", getpid(), strerror(errno)); - failCount++; - - if (failCount > 5) { - fprintf(stderr, " fork failed %i times, sleeping 60 seconds ..\n", - failCount); - sleep(60); - } - else { - sleep(1); - } + if (stdout && stderr) { + if ([ud boolForKey: @"WONoDetach"]) + childPid = 0; + else + childPid = fork(); + + if (childPid) { + rc = 0; + } + else { + nsPidFile = [ud objectForKey: @"WOPidFile"]; + if (!nsPidFile) + nsPidFile = [NSString stringWithFormat: @"/var/run/%@/%@.pid", + [processInfo processName], + [processInfo processName]]; + pidFile = [nsPidFile UTF8String]; + if (_writePid(nsPidFile)) { + respawnDelay = [ud integerForKey: @"WORespawnDelay"]; + if (!respawnDelay) + respawnDelay = 5; + /* default is to use the watch dog! */ + if ([ud objectForKey:@"WOUseWatchDog"] != nil + && ![ud boolForKey:@"WOUseWatchDog"]) + rc = WOApplicationMain(appName, argc, argv); + else + rc = [[WOWatchDog sharedWatchDog] run: appName argc: argc argv: argv]; } else { - if (child == 0) { - /* child process */ - signal(SIGPIPE, SIG_DFL); - signal(SIGCHLD, SIG_DFL); - signal(SIGINT, SIG_DFL); - signal(SIGTERM, SIG_DFL); - signal(SIGKILL, SIG_DFL); - - if (isVerbose) - fprintf(stderr, "starting child %i ..\n", getpid()); - - pidFile = [pidFile stringByAppendingPathExtension:@"child"]; - _writePid(pidFile); - - atexit(_delPid); - - exit(WOApplicationMain(appName, argc, argv)); - - /* shouldn't even get here ! */ - fprintf(stderr, "internal server error !\n"); - abort(); - } - else { - /* parent (watch dog) */ - int status = 0; - pid_t result = 0; - time_t clientStopTime; - unsigned uptime; - - forkCount++; - - if (isVerbose) { - fprintf(stderr, "forked child process %i (#%i) ..\n", - child, forkCount); - } - - failCount = 0; - status = 0; - - if ((result = waitpid(child, &status, 0)) == -1) { - if (killedChild) { - killedChild = NO; - continue; - } - - fprintf(stderr, - "### waiting for child %i (#%i) failed: %s\n", - child, forkCount, strerror(errno)); - continue; - } - - clientStopTime = time(NULL); - uptime = clientStopTime - clientStartTime; - - if (WIFSIGNALED(status)) { - fprintf(stderr, - "### child %i (#%i) was terminated by signal %i " - "(uptime=%ds).\n", - child, forkCount, WTERMSIG(status), uptime); - - lastFailExit = time(NULL); - failExitCount++; - } - else if (WIFEXITED(status)) { - unsigned exitCode; - - if ((exitCode = WEXITSTATUS(status)) != 0) { - time_t now; - - now = time(NULL); - - if (uptime < 3) { - if (failExitCount > 0) { - unsigned secsSinceLastFail; - - secsSinceLastFail = (now - lastFailExit); - - if (secsSinceLastFail > 120) { - /* reset fail count */ - failExitCount = 0; - } - else if (failExitCount > 20) { - printf("### child %i (#%i) already failed %i times " - "in the last %i seconds, stopping watchdog !\n", - child, forkCount, failExitCount, secsSinceLastFail); - repeat = NO; - } - } - } - failExitCount++; - lastFailExit = now; - - fprintf(stderr, - "### child %i (#%i) exited with status %i " - "(#fails=%i, uptime=%ds).\n", - child, forkCount, exitCode, failExitCount, uptime); - } - else { - fprintf(stderr, - "### child %i (#%i) exited successfully (uptime=%ds).\n", - child, forkCount, uptime); - } - - if (exitCode == 123) // ??? - repeat = NO; - } - else { - fprintf(stderr, - "### abnormal termination of child %i (#%i) status=%i" - "(was not signaled nor exited).", - child, forkCount, status); - } - } + [ud errorWithFormat: @"unable to open pid file: %@", pidFile]; + rc = -1; } } - return 0; } + else { + stdout = fdopen(stdErrNo, "a"); + stderr = fdopen(stdErrNo, "a"); + fprintf(stderr, "failed to redirect output channels to log file '%s'\n", + [logFile cString]); + } + + [pool release]; + + return rc; } #endif @@ -421,8 +1041,8 @@ @interface NSUserDefaults(ServerDefaults) + (id)hackInServerDefaults:(NSUserDefaults *)_ud - withAppDomainPath:(NSString *)_appDomainPath - globalDomainPath:(NSString *)_globalDomainPath; + withAppDomainPath:(NSString *)_appDomainPath + globalDomainPath:(NSString *)_globalDomainPath; @end int WOWatchDogApplicationMainWithServerDefaults @@ -437,7 +1057,7 @@ { extern char **environ; [NSProcessInfo initializeWithArguments:(void*)argv count:argc - environment:(void*)environ]; + environment:(void*)environ]; } #endif @@ -446,8 +1066,8 @@ ud = [NSUserDefaults standardUserDefaults]; sd = [defClass hackInServerDefaults:ud - withAppDomainPath:appDomainPath - globalDomainPath:globalDomainPath]; + withAppDomainPath:appDomainPath + globalDomainPath:globalDomainPath]; #if 0 if (((sd == nil) || (sd == ud)) && (appDomainPath != nil)) { Index: sope-appserver/NGObjWeb/GNUmakefile.postamble =================================================================== --- sope-appserver/NGObjWeb/GNUmakefile.postamble (revision 1664) +++ 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/WOMessage+XML.m =================================================================== --- sope-appserver/NGObjWeb/WOMessage+XML.m (revision 1664) +++ sope-appserver/NGObjWeb/WOMessage+XML.m (working copy) @@ -84,7 +84,7 @@ id builder; builder = [[[NSClassFromString(@"DOMSaxBuilder") alloc] init] autorelease]; - dom = [[builder buildFromData:data] retain]; + dom = [builder buildFromData:data]; } /* cache DOM structure */ Index: sope-appserver/NGObjWeb/ChangeLog =================================================================== --- sope-appserver/NGObjWeb/ChangeLog (revision 1664) +++ sope-appserver/NGObjWeb/ChangeLog (working copy) @@ -1,3 +1,108 @@ +2010-01-14 Wolfgang Sourdeau + + * SoObjects/SoObject.m (-isFolderish): now a real category method, + defaulting to NO. + + * WebDAV/SoWebDAVRenderer.m (-renderSearchResultEntry:...): take + the potential ending slash of the request to keep or remove the + ending slash of the hrefs to the returned objects, in order to + avoid confusing iCal with otherwise standard urls to DAV + collections. + +2009-12-22 Wolfgang Sourdeau + + * WOWatchDogApplicationMain.m (_listeningAddress): read "WOPort" + from the user defaults rather than by invoking [WOApplication + port], which returns an NSNumber. + +2009-12-14 Wolfgang Sourdeau + + * WOWatchDogApplicationMain.m (-run:argc:argv:): added a + repeatable timer, triggered every 0.5 seconds, that ensures the + proper looping of the runloop when a signal was received. + +2009-12-09 Wolfgang Sourdeau + + * WOWatchDogApplicationMain.m (_handleSIGCHLD:) + (_handleTermination:, _handleSIGHUP:): the actual handling is now + done elsewhere, in order to avoid messing with memory allocation + and risking a dead lock. + (-_handlePostTerminationSignal): we set "terminate" to YES if all + children are already down, in order to avoid another deadlock + where the process termination would stall waiting for SIGCHLD. + (-receivedEvent:type:extra:forMode:): check that the control + socket is still "alive" before reading from it. If not, we + unregister the filedescriptor passed as "data" from the runloop + listener. + +2009-12-07 Wolfgang Sourdeau + + * WOCoreApplication.m (+initialize): we invoke + "registerUserDefaults" from here now. This enables Defaults.plist + to be registered as soon as the watchdog is active. + + * WOWatchDogApplicationMain.m (-terminate): we use a SIGTERM to + terminate the children instead of passing a message. We also setup + a timer that will send a SIGKILL after 5 minutes. + (-_releaseListeningSocket): we close the socket here so that other + processes can start listening. + (WOWatchDogApplicationMain): we accept "-" as argument to + "WOLogFile" so that we avoid redirecting the output and the error + channels. + +2009-11-11 Wolfgang Sourdeau + + * WOCoreApplication.m (-setControlSocket, -controlSocket) + (-setListeningSocket, -listeningSocke): new helper accessors for + the new watchdog mechanism. + + * WOHttpAdaptor/WOHttpAdaptor.m: slightly refactored to use the + control socket provided by the watchdog. + + * WOWatchDogApplicationMain.m: rewritten the watchdog mechanism: + - added WOWatchDog and WOWatchDogChild classes + - make use of UnixSignalHandler + - added support for preforked preocesses (WOWorkersCount) + - detach watchdog processes from terminal by default (WONoDetach) + - redirect stderr and stdout to file + (WOLogFile = /var/log/[name]/[name].log) + - write pid file + (WOPidFile = /var/run/[name]/[name].pid) + - use "127.0.0.1:port" as default bind address, unless + WOHTTPAllowHost is specified + - added support for delaying process respawning + (WORespawnDelay = 5 seconds) + +2009-10-26 Wolfgang Sourdeau + + * WOMessage+XML.m (-contentAsDOMDocument): do not retain "dom" as + it will be assigned to self->domCache, to avoid a leak. + +2009-10-21 Wolfgang Sourdeau + + * WebDAV/SoObjectResultEntry.m (-valueForKey:): we now take + WOUseRelativeURLs into account when the "href" key is requested, + to work around a bug in iCal 4. + +2009-07-02 Wolfgang Sourdeau + + * WOMessage.m (-setHeaders:, -setHeader:forKey:, headerForKey:, + -appendHeader:forKey:, -appendHeaders:forKey:, setHeaders:forKey:, + -headersForKey:): convert the specified header key to lowercase, + to ensure they are managed case-insensitively. + * WOHttpAdaptor/WOHttpTransaction.m + (-deliverResponse:toRequest:onStream:): if the content-type is + specified and already has "text/plain" as prefix, we don't + override it. + +2009-07-01 Wolfgang Sourdeau + + * WOHttpAdaptor/WOHttpTransaction.m + (-deliverResponse:toRequest:onStream:): we test the content-length + and impose a content-type of text/plain when 0. This work-arounds + a bug in Mozilla clients where empty responses with a content-type + set to X/xml will trigger an exception. + 2009-06-10 Helge Hess * DAVPropMap.plist: mapped {DAV:}current-user-principal (v4.9.37) Index: sope-appserver/NGObjWeb/DAVPropMap.plist =================================================================== --- sope-appserver/NGObjWeb/DAVPropMap.plist (revision 1664) +++ sope-appserver/NGObjWeb/DAVPropMap.plist (working copy) @@ -157,6 +157,8 @@ "{urn:ietf:params:xml:ns:caldav}supported-calendar-data" = davSupportedCalendarDataTypes; "{urn:ietf:params:xml:ns:caldav}calendar-description" = davDescription; + "{urn:ietf:params:xml:ns:caldav}calendar-timezone" = davCalendarTimeZone; + "{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL" = davScheduleDefaultCalendarURL; /* CardDAV */ "{urn:ietf:params:xml:ns:carddav}addressbook-home-set" = davAddressbookHomeSet; @@ -168,6 +170,8 @@ "{http://calendarserver.org/ns/}dropbox-home-URL" = davDropboxHomeURL; "{http://calendarserver.org/ns/}notifications-URL" = davNotificationsURL; "{http://calendarserver.org/ns/}getctag" = davCollectionTag; + "{http://calendarserver.org/ns/}calendar-proxy-read-for" = davCalendarProxyReadFor; + "{http://calendarserver.org/ns/}calendar-proxy-write-for" = davCalendarProxyWriteFor; /* Apple extensions */ "{http://apple.com/ns/ical/}calendar-color" = davCalendarColor; Index: sope-appserver/NGObjWeb/WebDAV/SoObjectResultEntry.m =================================================================== --- sope-appserver/NGObjWeb/WebDAV/SoObjectResultEntry.m (revision 1664) +++ sope-appserver/NGObjWeb/WebDAV/SoObjectResultEntry.m (working copy) @@ -25,7 +25,14 @@ @implementation SoObjectResultEntry static BOOL debugOn = NO; +static BOOL useRelativeURLs = NO; ++ (void) initialize +{ + useRelativeURLs = [[NSUserDefaults standardUserDefaults] + boolForKey: @"WOUseRelativeURLs"]; +} + - (id)initWithURI:(NSString *)_href object:(id)_o values:(NSDictionary *)_d { if ((self = [super init])) { if (debugOn) { @@ -85,10 +92,36 @@ return YES; } +- (NSString *)_relativeHREF { + NSString *newHREF; + NSRange hostRange; + + if ([self->href hasPrefix: @"/"]) + return self->href; + else { + hostRange = [self->href rangeOfString: @"://"]; + if (hostRange.length > 0) { + newHREF = [self->href substringFromIndex: NSMaxRange (hostRange)]; + hostRange = [newHREF rangeOfString: @"/"]; + if (hostRange.length > 0) { + newHREF = [newHREF substringFromIndex: hostRange.location]; + } + } else { + newHREF = self->href; + } + + return newHREF; + } +} + - (id)valueForKey:(NSString *)_key { - if ([_key isEqualToString:@"{DAV:}href"]) - return self->href; - + if ([_key isEqualToString:@"{DAV:}href"]) { + if (useRelativeURLs) + return [self _relativeHREF]; + else + return self->href; + } + if ([_key isEqualToString:@"{DAV:}status"]) return nil; @@ -102,6 +135,12 @@ } } +/* SoObject */ +- (BOOL)isFolderish +{ + return [self->object isFolderish]; +} + /* description */ - (NSString *)description { Index: sope-appserver/NGObjWeb/WebDAV/SoWebDAVRenderer.m =================================================================== --- sope-appserver/NGObjWeb/WebDAV/SoWebDAVRenderer.m (revision 1664) +++ sope-appserver/NGObjWeb/WebDAV/SoWebDAVRenderer.m (working copy) @@ -25,6 +25,7 @@ #include "SoObject+SoDAV.h" #include "EOFetchSpecification+SoDAV.h" #include "NSException+HTTP.h" +#include #include #include #include @@ -49,6 +50,8 @@ #define XMLNS_INTTASK \ @"{http://schemas.microsoft.com/mapi/id/{00062003-0000-0000-C000-000000000046}/}" +static Class NSURLKlass = Nil; + @interface SoWebDAVRenderer(Privates) - (BOOL)renderStatusResult:(id)_object withDefaultStatus:(int)_defStatus inContext:(WOContext *)_ctx; @@ -79,6 +82,8 @@ if ((debugOn = [ud boolForKey:@"SoRendererDebugEnabled"])) NSLog(@"enabled debugging in SoWebDAVRenderer (SoRendererDebugEnabled)"); + + NSURLKlass = [NSURL class]; } + (id)sharedRenderer { @@ -616,16 +621,19 @@ [r appendContentString:s]; } else { + s = [self stringForValue:value ofProperty:_key prefixes:nsToPrefix]; [r appendContentCharacter:'<']; [r appendContentString:extName]; - [r appendContentCharacter:'>']; - - s = [self stringForValue:value ofProperty:_key prefixes:nsToPrefix]; - [r appendContentString:s]; - - [r appendContentString:@""]; + if ([s length] > 0) { + [r appendContentCharacter:'>']; + [r appendContentString:s]; + [r appendContentString:@""]; + } + else { + [r appendContentString:@"/>"]; + } if (formatOutput) [r appendContentCharacter:'\n']; } } @@ -646,8 +654,9 @@ NSString *key; id href = nil; id stat = nil; - BOOL isBrief; - + BOOL isBrief, hasSlash; + + hasSlash = [[[_ctx request] uri] hasSuffix: @"/"]; r = [_ctx response]; isBrief = [[[_ctx request] headerForKey:@"brief"] hasPrefix:@"t"] ? YES : NO; @@ -694,8 +703,13 @@ } /* tidy href */ - href = [self tidyHref:href baseURL:baseURL]; - + if (useRelativeURLs) { + if ([href isKindOfClass: NSURLKlass]) + href = [href path]; + } + else + href = [self tidyHref:href baseURL:baseURL]; + /* tidy status */ stat = [self tidyStatus:stat]; } @@ -703,7 +717,22 @@ href = [baseURL stringValue]; stat = @"HTTP/1.1 200 OK"; } - + + /* make the presence of the href slash correspond to the request slash */ + if (hasSlash) { + /* megahack: we consider entry to be the base entry if it's an + NSDictionary */ + if (![href hasSuffix: @"/"] + && ([entry isFolderish] + || [entry isKindOfClass: [NSDictionary class]])) { + href = [href stringByAppendingString: @"/"]; + } + } + else { + if ([href hasSuffix: @"/"]) + href = [href substringToIndex: [href length] - 2]; + } + if (debugOn) { [self debugWithFormat:@" status: %@", stat]; [self debugWithFormat:@" href: %@", href]; Index: sope-appserver/NGObjWeb/WODirectAction.m =================================================================== --- sope-appserver/NGObjWeb/WODirectAction.m (revision 1664) +++ 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 1664) +++ 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 1664) +++ 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 1664) +++ 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/WOMessage.m =================================================================== --- sope-appserver/NGObjWeb/WOMessage.m (revision 1664) +++ sope-appserver/NGObjWeb/WOMessage.m (working copy) @@ -182,7 +182,7 @@ NSString *key; keys = [_headers keyEnumerator]; - while ((key = [keys nextObject])) { + while ((key = [[keys nextObject] lowercaseString])) { id value; value = [_headers objectForKey:key]; @@ -198,34 +198,39 @@ } - (void)setHeader:(NSString *)_header forKey:(NSString *)_key { - [self->header setObject:[_header stringValue] forKey:_key]; + [self->header setObject:[_header stringValue] + forKey:[_key lowercaseString]]; } - (NSString *)headerForKey:(NSString *)_key { - return [[self->header objectEnumeratorForKey:_key] nextObject]; + return [[self->header objectEnumeratorForKey:[_key lowercaseString]] + nextObject]; } - (void)appendHeader:(NSString *)_header forKey:(NSString *)_key { - [self->header addObject:_header forKey:_key]; + [self->header addObject:_header forKey:[_key lowercaseString]]; } - (void)appendHeaders:(NSArray *)_headers forKey:(NSString *)_key { - [self->header addObjects:_headers forKey:_key]; + [self->header addObjects:_headers forKey:[_key lowercaseString]]; } - (void)setHeaders:(NSArray *)_headers forKey:(NSString *)_key { NSEnumerator *e; id value; + NSString *lowerKey; + lowerKey = [_key lowercaseString]; e = [_headers objectEnumerator]; - [self->header removeAllObjectsForKey:_key]; + [self->header removeAllObjectsForKey:lowerKey]; while ((value = [e nextObject])) - [self->header addObject:value forKey:_key]; + [self->header addObject:value forKey:lowerKey]; } - (NSArray *)headersForKey:(NSString *)_key { NSEnumerator *values; - if ((values = [self->header objectEnumeratorForKey:_key])) { + if ((values + = [self->header objectEnumeratorForKey:[_key lowercaseString]])) { NSMutableArray *array = nil; id value = nil; @@ -243,17 +248,14 @@ NSEnumerator *values; if ((values = [self->header keyEnumerator])) { - NSMutableArray *array = nil; + NSMutableArray *array; id name = nil; - array = [[NSMutableArray alloc] init]; - + array = [NSMutableArray array]; + while ((name = [values nextObject])) [array addObject:name]; - name = [array copy]; - [array release]; - - return [name autorelease]; + return array; } return nil; } Index: sope-appserver/NGObjWeb/SoObjects/SoObject.h =================================================================== --- sope-appserver/NGObjWeb/SoObjects/SoObject.h (revision 1664) +++ sope-appserver/NGObjWeb/SoObjects/SoObject.h (working copy) @@ -59,6 +59,8 @@ - (NSString *)defaultMethodNameInContext:(id)_ctx; - (id)lookupDefaultMethod; +- (BOOL)isFolderish; + /* binding (returns self by default [unbound objects]) */ - (id)bindToObject:(id)_object inContext:(id)_ctx; Index: sope-appserver/NGObjWeb/SoObjects/SoObject.m =================================================================== --- sope-appserver/NGObjWeb/SoObjects/SoObject.m (revision 1664) +++ sope-appserver/NGObjWeb/SoObjects/SoObject.m (working copy) @@ -30,31 +30,39 @@ #include #include "common.h" -@interface NSObject(Folders) -- (BOOL)isFolderish; -@end - @implementation NSObject(SoObject) 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 */ @@ -241,6 +249,11 @@ return pathArray; } +- (BOOL) isFolderish +{ + return NO; +} + - (NSString *)baseURLInContext:(id)_ctx { NSString *baseURL; id parent; @@ -284,10 +297,8 @@ /* add a trailing slash for folders */ if (![baseURL hasSuffix:@"/"]) { - if ([self respondsToSelector:@selector(isFolderish)]) { - if ([self isFolderish]) - baseURL = [baseURL stringByAppendingString:@"/"]; - } + if ([self isFolderish]) + baseURL = [baseURL stringByAppendingString:@"/"]; } return baseURL; @@ -318,56 +329,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/NGObjWeb/WOAdaptor.h =================================================================== --- sope-appserver/NGObjWeb/NGObjWeb/WOAdaptor.h (revision 1664) +++ sope-appserver/NGObjWeb/NGObjWeb/WOAdaptor.h (working copy) @@ -27,6 +27,13 @@ @class NSString, NSDictionary; @class WOCoreApplication; +typedef enum { + WOChildMessageAccept = 0, + WOChildMessageReady, + WOChildMessageShutdown, + WOChildMessageMax +} WOChildMessage; + @interface WOAdaptor : NSObject { @protected Index: sope-appserver/NGObjWeb/NGObjWeb/WOCoreApplication.h =================================================================== --- sope-appserver/NGObjWeb/NGObjWeb/WOCoreApplication.h (revision 1664) +++ sope-appserver/NGObjWeb/NGObjWeb/WOCoreApplication.h (working copy) @@ -31,6 +31,8 @@ @class WOAdaptor, WORequest, WOResponse, WORequestHandler; @class NSBundle; +@class NGActiveSocket, NGPassiveSocket; + NGObjWeb_EXPORT NSString *WOApplicationWillFinishLaunchingNotification; NGObjWeb_EXPORT NSString *WOApplicationDidFinishLaunchingNotification; NGObjWeb_EXPORT NSString *WOApplicationWillTerminateNotification; @@ -41,6 +43,9 @@ NSRecursiveLock *lock; NSLock *requestLock; + NGActiveSocket *controlSocket; + NGPassiveSocket *listeningSocket; + struct { BOOL isTerminating:1; } cappFlags; @@ -55,6 +60,14 @@ - (void)activateApplication; - (void)deactivateApplication; +/* Watchdog helpers */ + +- (void)setControlSocket: (NGActiveSocket *) newSocket; +- (NGActiveSocket *)controlSocket; + +- (void)setListeningSocket: (NGPassiveSocket *) newSocket; +- (NGPassiveSocket *)listeningSocket; + /* adaptors */ - (NSArray *)adaptors; Index: sope-appserver/NGObjWeb/WOHttpAdaptor/WOHttpAdaptor.h =================================================================== --- sope-appserver/NGObjWeb/WOHttpAdaptor/WOHttpAdaptor.h (revision 1664) +++ sope-appserver/NGObjWeb/WOHttpAdaptor/WOHttpAdaptor.h (working copy) @@ -46,7 +46,6 @@ NSMutableArray *delayedResponses; } -+ (BOOL)optionLogStream; + (BOOL)optionLogPerf; @end Index: sope-appserver/NGObjWeb/WOHttpAdaptor/WOHttpTransaction.m =================================================================== --- sope-appserver/NGObjWeb/WOHttpAdaptor/WOHttpTransaction.m (revision 1664) +++ sope-appserver/NGObjWeb/WOHttpAdaptor/WOHttpTransaction.m (working copy) @@ -48,8 +48,8 @@ NSString *WOAsyncResponseReadyNotificationName = @"WOAsyncResponseReadyNotification"; NSString *WOAsyncResponse = @"WOAsyncResponse"; +static BOOL WOHttpAdaptor_LogStream = NO; - @interface WOCoreApplication(SimpleParserSelection) - (BOOL)shouldUseSimpleHTTPParserForTransaction:(id)_tx; @@ -85,13 +85,14 @@ ud = [NSUserDefaults standardUserDefaults]; useSimpleParser = [ud boolForKey:@"WOHttpTransactionUseSimpleParser"]; doCore = [[ud objectForKey:@"WOCoreOnHTTPAdaptorException"] boolValue]?1:0; + WOHttpAdaptor_LogStream = [ud boolForKey:@"WOHttpAdaptor_LogStream"]; adLogPath = [[ud stringForKey:@"WOAdaptorLogPath"] copy]; if (adLogPath == nil) adLogPath = @""; } - (BOOL)optionLogStream { - return [WOHttpAdaptor optionLogStream]; + return WOHttpAdaptor_LogStream; } - (BOOL)optionLogPerf { return perfLogger ? YES : NO; @@ -108,6 +109,9 @@ NSAssert(_app, @"missing application ..."); self->socket = [_socket retain]; self->application = [_app retain]; + if ([[_app recordingPath] length] > 0) + WOHttpAdaptor_LogStream = YES; + return self; } @@ -696,7 +700,7 @@ *(&out) = nil; [self _httpValidateResponse:_response]; - + out = [(NGCTextStream *)[NGCTextStream alloc] initWithSource:_out]; NS_DURING { @@ -705,6 +709,7 @@ id body; BOOL doZip; BOOL isok = YES; + int length; doZip = [_response shouldZipResponseToRequest:_request]; @@ -738,7 +743,11 @@ /* add content length header */ - snprintf((char *)buf, sizeof(buf), "%d", [body length]); + if ((length = [body length]) == 0 + && ![[_response headerForKey: @"content-type"] hasPrefix:@"text/plain"]) { + [_response setHeader:@"text/plain" forKey:@"content-type"]; + } + snprintf((char *)buf, sizeof(buf), "%d", length); t1 = [[NSString alloc] initWithCString:(char *)buf]; [_response setHeader:t1 forKey:@"content-length"]; [t1 release]; t1 = nil; @@ -766,7 +775,7 @@ NSString *value; if (!hasConnectionHeader) { - if ([fieldName caseInsensitiveCompare:@"connection"]==NSOrderedSame) + if ([fieldName isEqualToString:@"connection"]) hasConnectionHeader = YES; } @@ -789,7 +798,7 @@ NSLog(@" END: %@", fieldName); #endif } - + #if HEAVY_DEBUG NSLog(@" HEADER:\n%@", header); NSLog(@" OUT: %@", out); Index: sope-appserver/NGObjWeb/WOHttpAdaptor/WOHttpAdaptor.m =================================================================== --- sope-appserver/NGObjWeb/WOHttpAdaptor/WOHttpAdaptor.m (revision 1664) +++ sope-appserver/NGObjWeb/WOHttpAdaptor/WOHttpAdaptor.m (working copy) @@ -71,18 +71,13 @@ static NGLogger *logger = nil; static NGLogger *perfLogger = nil; -static BOOL WOHttpAdaptor_LogStream = NO; static BOOL WOContactSNS = NO; static BOOL WOCoreOnHTTPAdaptorException = NO; static int WOHttpAdaptorSendTimeout = 10; static int WOHttpAdaptorReceiveTimeout = 10; -static int WOHttpAdaptorForkCount = 0; static id allow = nil; static BOOL debugOn = NO; -+ (BOOL)optionLogStream { - return WOHttpAdaptor_LogStream; -} + (BOOL)optionLogPerf { return perfLogger != nil ? YES : NO; } @@ -108,8 +103,6 @@ logger = [lm loggerForClass:self]; perfLogger = [lm loggerForDefaultKey:@"WOProfileHttpAdaptor"]; - WOHttpAdaptor_LogStream = [ud boolForKey:@"WOHttpAdaptor_LogStream"]; - // TODO: this should be queried on demand to allow different defaults WOContactSNS = [[ud objectForKey:@"WOContactSNS"] boolValue]; @@ -134,9 +127,6 @@ allow = [allow copy]; } - WOHttpAdaptorForkCount = - [[ud objectForKey:@"WOHttpAdaptorForkCount"] intValue]; - if (WOCoreOnHTTPAdaptorException) [logger warnWithFormat:@"will dump core on HTTP adaptor exception!"]; } @@ -219,33 +209,31 @@ application:_application])) { id arg = nil; - if ([[_application recordingPath] length] > 0) - WOHttpAdaptor_LogStream = YES; - [self _registerForSignals]; + if (![_application controlSocket]) { + if ([_args count] < 1) + self->address = [self addressFromDefaultsOfApplication:_application]; + else + self->address = [self addressFromArguments:_args]; - if ([_args count] < 1) - self->address = [self addressFromDefaultsOfApplication:_application]; - else - self->address = [self addressFromArguments:_args]; + self->address = [self->address retain]; - self->address = [self->address retain]; + if (self->address == nil) { + [_application errorWithFormat: + @"got no address for HTTP server (using arg '%@')", arg]; + [self release]; + return nil; + } - if (self->address == nil) { - [_application errorWithFormat: - @"got no address for HTTP server (using arg '%@')", arg]; - [self release]; - return nil; - } - - if (!WOContactSNS) { - [_application logWithFormat:@"%@ listening on address %@", + if (!WOContactSNS) { + [_application logWithFormat:@"%@ listening on address %@", NSStringFromClass([self class]), [(id)self->address stringValue]]; + } } self->lock = [[NSRecursiveLock alloc] init]; - + self->maxThreadCount = [[WOCoreApplication workerThreadCount] intValue]; [self setSendTimeout:WOHttpAdaptorSendTimeout]; @@ -270,145 +258,76 @@ return self->address; } -/* forking */ - -static pid_t *childPIDs = NULL; -static BOOL isForkMaster = YES; - -- (void)forkChildren { - unsigned i; - - if (WOHttpAdaptorForkCount == 0) - return; - - [self logWithFormat:@"Note: forking %d children for socket processing.", - WOHttpAdaptorForkCount]; - -#if !defined(__MINGW32__) - [[UnixSignalHandler sharedHandler] - addObserver:self selector:@selector(handleSIGCHLD:) - forSignal:SIGCHLD immediatelyNotifyOnSignal:NO]; -#endif - - childPIDs = calloc(WOHttpAdaptorForkCount + 1, sizeof(pid_t)); - for (i = 0; i < WOHttpAdaptorForkCount; i++) { - childPIDs[i] = fork(); - - if (childPIDs[i] == 0) { - /* child */ - isForkMaster = NO; - return; - } - else if (childPIDs[i] > 0) - printf("Note: successfully forked child: %i\n", childPIDs[i]); - else - [self errorWithFormat:@"failed to fork child %i.", i]; - } -} -- (void)killChildren { - int i; - - if (!isForkMaster) - return; - - for (i = 0; i < WOHttpAdaptorForkCount; i++) { - if (childPIDs[i] != 0) - kill(childPIDs[i], SIGKILL); - } -} - -- (void)checkStatusOfChildren { - /* - Note: currently this does not refork crashed processes. Reforking is harder - than it may sound because the crash can happen at arbitary execution - states. - That is, the "master process" is not virgin anymore, eg it might have - open database connections. - - So the solution might be to refork the whole cluster once a minimum - backend threshold is reached. - */ - unsigned int i; - - if (!isForkMaster) - return; - - for (i = 0; i < WOHttpAdaptorForkCount; i++) { - pid_t result; - int status; - - if (childPIDs[i] == 0) - continue; - - result = waitpid(childPIDs[i], &status, WNOHANG); - if (result == 0) /* did not exit yet */ - continue; - - if (result == -1) { /* error */ - [self errorWithFormat:@"failed to get status of child %i: %s", - childPIDs[i], strerror(errno)]; - continue; - } - - [self logWithFormat:@"Note: child %i terminated.", childPIDs[i]]; - childPIDs[i] = 0; - } -} - /* events */ - (void)handleSIGPIPE:(int)_signal { [self warnWithFormat:@"caught SIGPIPE !"]; } -- (void)handleSIGCHLD:(int)_signal { - [self checkStatusOfChildren]; -} - (void)registerForEvents { int backlog; + NGActiveSocket *controlSocket; + WOChildMessage message; + + controlSocket = [[WOCoreApplication application] controlSocket]; + if (controlSocket) { + ASSIGN(self->socket, [[WOCoreApplication application] listeningSocket]); + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(acceptControlMessage:) + name:NSFileObjectBecameActiveNotificationName + object:nil]; + [(WORunLoop *)[WORunLoop currentRunLoop] + addFileObject:controlSocket + activities:NSPosixReadableActivity + forMode:NSDefaultRunLoopMode]; + message = WOChildMessageReady; + [controlSocket safeWriteBytes: &message + count: sizeof (WOChildMessage)]; + // [self logWithFormat: @"notified the watchdog that we are ready"]; + } + else { + backlog = [[WOCoreApplication listenQueueSize] intValue]; - backlog = [[WOCoreApplication listenQueueSize] intValue]; + if (backlog == 0) + backlog = 5; - if (backlog == 0) - backlog = 5; + [self->socket release]; self->socket = nil; - [self->socket release]; self->socket = nil; + self->socket = + [[NGPassiveSocket alloc] initWithDomain:[self->address domain]]; - self->socket = - [[NGPassiveSocket alloc] initWithDomain:[self->address domain]]; + [self->socket bindToAddress:self->address]; - [self->socket bindToAddress:self->address]; - - if ([[self->address domain] isEqual:[NGInternetSocketDomain domain]]) { - if ([(NGInternetSocketAddress *)self->address port] == 0) { - /* let the kernel choose an IP address */ + if ([[self->address domain] isEqual:[NGInternetSocketDomain domain]]) { + if ([(NGInternetSocketAddress *)self->address port] == 0) { + /* let the kernel choose an IP address */ - [self debugWithFormat:@"bound to wildcard: %@", self->address]; - [self debugWithFormat:@"got local: %@", [self->socket localAddress]]; + [self debugWithFormat:@"bound to wildcard: %@", self->address]; + [self debugWithFormat:@"got local: %@", [self->socket localAddress]]; - self->address = [[self->socket localAddress] retain]; + self->address = [[self->socket localAddress] retain]; - [self logWithFormat:@"bound to kernel assigned address %@: %@", + [self logWithFormat:@"bound to kernel assigned address %@: %@", self->address, self->socket]; + } } - } - [self->socket listenWithBacklog:backlog]; + [self->socket listenWithBacklog:backlog]; - [[NSNotificationCenter defaultCenter] + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(acceptConnection:) - name:NSFileObjectBecameActiveNotificationName - object:self->socket]; - [(WORunLoop *)[WORunLoop currentRunLoop] + name:NSFileObjectBecameActiveNotificationName + object:self->socket]; + + [(WORunLoop *)[WORunLoop currentRunLoop] addFileObject:self->socket activities:NSPosixReadableActivity forMode:NSDefaultRunLoopMode]; - - [self forkChildren]; + } } + - (void)unregisterForEvents { - [self killChildren]; - [(WORunLoop *)[WORunLoop currentRunLoop] removeFileObject:self->socket forMode:NSDefaultRunLoopMode]; [[NSNotificationCenter defaultCenter] removeObserver:self]; @@ -603,52 +522,91 @@ return _connection; } +- (NGActiveSocket *)_accept { + NGActiveSocket *connection; + + NS_DURING { + connection = [self->socket accept]; + if (!connection) + [self _serverCatched:[self->socket lastException]]; + else + [self debugWithFormat:@"accepted connection: %@", connection]; + } + NS_HANDLER { + connection = nil; + [self _serverCatched:localException]; + } + NS_ENDHANDLER; + + return connection; +} + +- (void)_handleConnection:(NGActiveSocket *)connection { + if (connection != nil) { + if (self->maxThreadCount <= 1) { + NS_DURING + [self _handleAcceptedConnection:[connection retain]]; + NS_HANDLER + [self _serverCatched:localException]; + NS_ENDHANDLER; + } + else { + [NSThread detachNewThreadSelector: + @selector(_handleAcceptedConnectionInThread:) + toTarget:self + withObject:[connection retain]]; + [self logWithFormat:@"detached new thread for request."]; + //[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]]; + } + connection = nil; + } +} + +- (void) acceptControlMessage: (NSNotification *) aNotification +{ + NGActiveSocket *controlSocket, *connection; + WOChildMessage message; + NSAutoreleasePool *pool; + + // NSLog (@"received control message"); + controlSocket = [aNotification object]; + // [self logWithFormat:@"child accepting message from socket: %@", controlSocket]; + while (![controlSocket safeReadBytes: &message + count: sizeof (WOChildMessage)]) + NSLog (@"renotifying watchdog"); + if (message == WOChildMessageAccept) { + pool = [NSAutoreleasePool new]; + connection = [self _accept]; + if ([controlSocket safeWriteBytes: &message + count: sizeof (WOChildMessage)]) + ; + [self _handleConnection: connection]; + message = WOChildMessageReady; + [controlSocket safeWriteBytes: &message count: sizeof (WOChildMessage)]; + [pool release]; + } + else if (message == WOChildMessageShutdown) { + [controlSocket safeWriteBytes: &message + count: sizeof (WOChildMessage)]; + [[WOCoreApplication application] terminate]; + } +} + - (void)acceptConnection:(id)_notification { + NGActiveSocket *connection; #if USE_POOLS NSAutoreleasePool *pool; - *(&pool) = [[NSAutoreleasePool alloc] init]; + + pool = [[NSAutoreleasePool alloc] init]; #endif { - NGActiveSocket *connection; - - NS_DURING { - *(&connection) = (NGActiveSocket *)[self->socket accept]; - if (connection == nil) - [self _serverCatched:[self->socket lastException]]; - else - [self debugWithFormat:@"accepted connection: %@", connection]; - } - NS_HANDLER { - connection = nil; - [self _serverCatched:localException]; - } - NS_ENDHANDLER; - - connection = (NGActiveSocket *)[self _checkAccessOnConnection:connection]; - - if (connection != nil) { - if (self->maxThreadCount <= 1) { - NS_DURING - [self _handleAcceptedConnection:[connection retain]]; - NS_HANDLER - [self _serverCatched:localException]; - NS_ENDHANDLER; - } - else { - [NSThread detachNewThreadSelector: - @selector(_handleAcceptedConnectionInThread:) - toTarget:self - withObject:[connection retain]]; - [self logWithFormat:@"detached new thread for request."]; - //[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]]; - } - connection = nil; - } + connection = [self _checkAccessOnConnection:[self _accept]]; + [self _handleConnection: connection]; } #if USE_POOLS [pool release]; pool = nil; #endif - + if (self->isTerminated) { if (self->socket) { [[NSNotificationCenter defaultCenter] Index: sope-appserver/NGObjWeb/WOCoreApplication.m =================================================================== --- sope-appserver/NGObjWeb/WOCoreApplication.m (revision 1664) +++ sope-appserver/NGObjWeb/WOCoreApplication.m (working copy) @@ -75,6 +75,43 @@ NGObjWeb_DECLARE id WOApp = nil; static NSMutableArray *activeApps = nil; // THREAD ++ (void)registerUserDefaults { + NSDictionary *owDefaults = nil; + NSString *apath; + + apath = [[self class] findNGObjWebResource:@"Defaults" ofType:@"plist"]; + if (apath == nil) + [self errorWithFormat:@"Cannot find Defaults.plist resource of " + @"NGObjWeb library!"]; +#if HEAVY_DEBUG + else + [self debugWithFormat:@"Note: loading default defaults: %@", apath]; +#endif + + owDefaults = [NSDictionary dictionaryWithContentsOfFile:apath]; + if (owDefaults) { + [[NSUserDefaults standardUserDefaults] registerDefaults:owDefaults]; +#if HEAVY_DEBUG + [self debugWithFormat:@"did register NGObjWeb defaults: %@\n%@", + apath, owDefaults]; +#endif + } + else { + [self errorWithFormat:@"could not load NGObjWeb defaults: '%@'", + apath]; + } +} + ++ (void)initialize +{ + static BOOL initialized = NO; + + if (!initialized) { + [self registerUserDefaults]; + initialized = YES; + } +} + + (id)application { if (WOApp == nil) { [self warnWithFormat:@"%s: some code called +application without an " @@ -115,33 +152,6 @@ } } -- (void)registerUserDefaults { - NSDictionary *owDefaults = nil; - NSString *apath; - - apath = [[self class] findNGObjWebResource:@"Defaults" ofType:@"plist"]; - if (apath == nil) - [self errorWithFormat:@"Cannot find Defaults.plist resource of " - @"NGObjWeb library!"]; -#if HEAVY_DEBUG - else - [self debugWithFormat:@"Note: loading default defaults: %@", apath]; -#endif - - owDefaults = [NSDictionary dictionaryWithContentsOfFile:apath]; - if (owDefaults) { - [[NSUserDefaults standardUserDefaults] registerDefaults:owDefaults]; -#if HEAVY_DEBUG - [self debugWithFormat:@"did register NGObjWeb defaults: %@\n%@", - apath, owDefaults]; -#endif - } - else { - [self errorWithFormat:@"could not load NGObjWeb defaults: '%@'", - apath]; - } -} - - (id)init { #if COCOA_Foundation_LIBRARY /* @@ -157,7 +167,6 @@ NSUserDefaults *ud; NGLoggerManager *lm; - [self registerUserDefaults]; ud = [NSUserDefaults standardUserDefaults]; lm = [NGLoggerManager defaultLoggerManager]; logger = [lm loggerForClass:[self class]]; @@ -190,6 +199,9 @@ forSignal:SIGHUP immediatelyNotifyOnSignal:NO]; } #endif + + controlSocket = nil; + listeningSocket = nil; } return self; } @@ -202,9 +214,32 @@ [self->adaptors release]; [self->requestLock release]; [self->lock release]; + [self->listeningSocket release]; + [self->controlSocket release]; [super dealloc]; } +/* Watchdog helpers */ +- (void)setControlSocket: (NGActiveSocket *) newSocket +{ + ASSIGN(self->controlSocket, newSocket); +} + +- (NGActiveSocket *)controlSocket +{ + return self->controlSocket; +} + +- (void)setListeningSocket: (NGPassiveSocket *) newSocket +{ + ASSIGN(self->listeningSocket, newSocket); +} + +- (NGPassiveSocket *)listeningSocket +{ + return self->listeningSocket; +} + /* NGLogging */ + (id)logger { @@ -225,6 +260,7 @@ /* STDIO is forbidden in signal handlers !!! no malloc !!! */ #if 1 self->cappFlags.isTerminating = 1; + [self->listeningSocket close]; #else static int termCount = 0; unsigned pid; @@ -786,7 +822,9 @@ id woport; id addr; - woport = [[self userDefaults] objectForKey:@"WOPort"]; + woport = [[self userDefaults] objectForKey:@"p"]; + if (!woport) + woport = [[self userDefaults] objectForKey:@"WOPort"]; if ([woport isKindOfClass:[NSNumber class]]) return woport; woport = [woport stringValue]; Index: sope-appserver/NGObjWeb/NGHttp/NGHttpRequest.m =================================================================== --- sope-appserver/NGObjWeb/NGHttp/NGHttpRequest.m (revision 1664) +++ sope-appserver/NGObjWeb/NGHttp/NGHttpRequest.m (working copy) @@ -59,6 +59,8 @@ /* RFC 3253 (DeltaV) */ @"REPORT", @"VERSION-CONTROL", + /* RFC 3744 (WebDAV ACL) */ + @"ACL", /* RFC 4791 (CalDAV) */ @"MKCALENDAR", /* http://ietfreport.isoc.org/idref/draft-daboo-carddav/ (CardDAV) */