From 212ff648282d5d56b57af3623556a51605a9bd06 Mon Sep 17 00:00:00 2001 From: Euan Thoms Date: Wed, 4 Nov 2015 03:36:34 +0800 Subject: [PATCH 1/2] Stage 2 of clang compiler warning patches. --- SOPE/NGCards/iCalPerson.m | 2 +- SOPE/NGCards/iCalPerson.m.orig | 299 ++++++ SoObjects/Contacts/SOGoContactSourceFolder.m | 2 +- .../Contacts/SOGoContactSourceFolder.m.orig | 808 +++++++++++++++ SoObjects/SOGo/SOGoCacheGCSFolder.m | 2 +- SoObjects/SOGo/SOGoCacheGCSFolder.m.orig | 486 +++++++++ SoObjects/SOGo/SQLSource.m | 12 +- SoObjects/SOGo/SQLSource.m.orig | 971 ++++++++++++++++++ 8 files changed, 2573 insertions(+), 9 deletions(-) create mode 100644 SOPE/NGCards/iCalPerson.m.orig create mode 100644 SoObjects/Contacts/SOGoContactSourceFolder.m.orig create mode 100644 SoObjects/SOGo/SOGoCacheGCSFolder.m.orig create mode 100644 SoObjects/SOGo/SQLSource.m.orig diff --git a/SOPE/NGCards/iCalPerson.m b/SOPE/NGCards/iCalPerson.m index 7904fd0f8..97a1bf527 100644 --- a/SOPE/NGCards/iCalPerson.m +++ b/SOPE/NGCards/iCalPerson.m @@ -267,7 +267,7 @@ - (BOOL)isEqual:(id)_other { if(_other == nil) return NO; - if([_other class] != self->isa) + if([_other class] != object_getClass(self)) return NO; if([_other hash] != [self hash]) return NO; diff --git a/SOPE/NGCards/iCalPerson.m.orig b/SOPE/NGCards/iCalPerson.m.orig new file mode 100644 index 000000000..7904fd0f8 --- /dev/null +++ b/SOPE/NGCards/iCalPerson.m.orig @@ -0,0 +1,299 @@ +/* + Copyright (C) 2000-2005 SKYRIX Software AG + + This file is part of SOPE. + + SOPE is free software; you can redistribute it and/or modify it under + the terms of the GNU Lesser General Public License as published by the + Free Software Foundation; either version 2, or (at your option) any + later version. + + SOPE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with SOPE; see the file COPYING. If not, write to the + Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +#import + +#import "iCalPerson.h" + +@implementation iCalPerson + ++ (NSString *) descriptionForParticipationStatus: (iCalPersonPartStat) _status +{ + NSString *stat; + + switch (_status) { + case iCalPersonPartStatUndefined: + stat = @""; + break; + case iCalPersonPartStatAccepted: + stat = @"ACCEPTED"; + break; + case iCalPersonPartStatDeclined: + stat = @"DECLINED"; + break; + case iCalPersonPartStatTentative: + stat = @"TENTATIVE"; + break; + case iCalPersonPartStatDelegated: + stat = @"DELEGATED"; + break; + case iCalPersonPartStatCompleted: + stat = @"COMPLETED"; + break; + case iCalPersonPartStatInProcess: + stat = @"IN-PROCESS"; + break; + case iCalPersonPartStatExperimental: + case iCalPersonPartStatOther: +// [NSException raise:NSInternalInconsistencyException +// format:@"Attempt to set meaningless " +// @"participationStatus (%d)!", _status]; + stat = nil; /* keep compiler happy */ + break; + default: + stat = @"NEEDS-ACTION"; + break; + } + + return stat; +} + + +/* accessors */ + +- (void) setCn: (NSString *) _s +{ + [self setValue: 0 ofAttribute: @"cn" to: _s]; +} + +- (NSString *) cn +{ + return [self value: 0 ofAttribute: @"cn"]; +} + +- (NSString *) cnWithoutQuotes +{ + /* remove quotes around a CN */ + NSString *_cn; + + _cn = [self cn]; + if ([_cn length] <= 2) + return _cn; + if ([_cn characterAtIndex:0] != '"') + return _cn; + if (![_cn hasSuffix:@"\""]) + return _cn; + + return [_cn substringWithRange:NSMakeRange(1, [_cn length] - 2)]; +} + +- (void) setEmail: (NSString *)_s +{ + /* iCal.app compatibility: + - "mailto" prefix must be in lowercase; */ + [self setSingleValue: [NSString stringWithFormat: @"mailto:%@", _s] + forKey: @""]; +} + +- (NSString *) email +{ + return [self flattenedValuesForKey: @""]; +} + +- (NSString *) rfc822Email +{ + NSString *_email; + unsigned idx; + + _email = [self email]; + idx = NSMaxRange([_email rangeOfString:@":"]); + + if ((idx > 0) && ([_email length] > idx)) + return [_email substringFromIndex:idx]; + + return _email; +} + +- (void) setRsvp: (NSString *) _s +{ + [self setValue: 0 ofAttribute: @"rsvp" to: _s]; +} + +- (NSString *) rsvp +{ + return [[self value: 0 ofAttribute: @"rsvp"] lowercaseString]; +} + +// - (void)setXuid:(NSString *)_s { +// ASSIGNCOPY(self->xuid, _s); +// } +// - (NSString *)xuid { +// return self->xuid; +// } + +- (void)setRole:(NSString *)_s +{ + [self setValue: 0 ofAttribute: @"role" to: _s]; +} + +- (NSString *) role +{ + return [self value: 0 ofAttribute: @"role"]; +} + +- (void)setPartStat:(NSString *)_s +{ + [self setValue: 0 ofAttribute: @"partstat" to: _s]; +} + +- (NSString *) partStat +{ + return [self value: 0 ofAttribute: @"partstat"]; +} + +- (NSString *) partStatWithDefault +{ + NSString *s; + + s = [self partStat]; + if ([s length] > 0) + return s; + + return @"NEEDS-ACTION"; +} + +- (void) setParticipationStatus: (iCalPersonPartStat) _status +{ + NSString *stat; + + stat = [iCalPerson descriptionForParticipationStatus: _status]; + + if (stat) + [self setPartStat:stat]; +} + +- (iCalPersonPartStat) participationStatus { + NSString *stat; + + stat = [[self partStat] uppercaseString]; + if (![stat length]) + return iCalPersonPartStatUndefined; + else if ([stat isEqualToString:@"NEEDS-ACTION"]) + return iCalPersonPartStatNeedsAction; + else if ([stat isEqualToString:@"ACCEPTED"]) + return iCalPersonPartStatAccepted; + else if ([stat isEqualToString:@"DECLINED"]) + return iCalPersonPartStatDeclined; + else if ([stat isEqualToString:@"TENTATIVE"]) + return iCalPersonPartStatTentative; + else if ([stat isEqualToString:@"DELEGATED"]) + return iCalPersonPartStatDelegated; + else if ([stat isEqualToString:@"COMPLETED"]) + return iCalPersonPartStatCompleted; + else if ([stat isEqualToString:@"IN-PROCESS"]) + return iCalPersonPartStatInProcess; + else if ([stat hasPrefix:@"X-"]) + return iCalPersonPartStatExperimental; + return iCalPersonPartStatOther; +} + +- (void) _setValueOfMailtoAttribute: (NSString *) name + to: (NSString *) value +{ + if ([value length] && ![value hasPrefix: @"\""]) + value = [NSString stringWithFormat: @"\"%@\"", value]; + + [self setValue: 0 ofAttribute: name to: value]; +} + +- (NSString *) _valueOfMailtoAttribute: (NSString *) name +{ + NSString *mailTo; + + mailTo = [self value: 0 ofAttribute: name]; + if ([mailTo hasPrefix: @"\""]) + mailTo + = [mailTo substringWithRange: NSMakeRange (1, [mailTo length] - 2)]; + + return mailTo; +} + +- (void) setDelegatedTo: (NSString *) newDelegate +{ + [self _setValueOfMailtoAttribute: @"delegated-to" to: newDelegate]; +} + +- (NSString *) delegatedTo +{ + return [self _valueOfMailtoAttribute: @"delegated-to"]; +} + +- (void) setDelegatedFrom: (NSString *) newDelegator +{ + [self _setValueOfMailtoAttribute: @"delegated-from" to: newDelegator]; +} + +- (NSString *) delegatedFrom +{ + return [self _valueOfMailtoAttribute: @"delegated-from"]; +} + +- (void) setSentBy: (NSString *) newSentBy +{ + [self _setValueOfMailtoAttribute: @"sent-by" to: newSentBy]; +} + +- (NSString *) sentBy +{ + return [self _valueOfMailtoAttribute: @"sent-by"]; +} + +/* comparison */ + +- (NSUInteger) hash { + if ([self email]) + return [[self email] hash]; + return [super hash]; +} + +- (BOOL)isEqual:(id)_other { + if(_other == nil) + return NO; + if([_other class] != self->isa) + return NO; + if([_other hash] != [self hash]) + return NO; + return [self isEqualToPerson:_other]; +} + +- (BOOL)isEqualToPerson:(iCalPerson *)_other { + if(![self hasSameEmailAddress:_other]) + return NO; + if(!IS_EQUAL([self cn], [_other cn], isEqualToString:)) + return NO; + if(!IS_EQUAL([self rsvp], [_other rsvp], isEqualToString:)) + return NO; + if(!IS_EQUAL([self partStat], [_other partStat], isEqualToString:)) + return NO; + if(!IS_EQUAL([self role], [_other role], isEqualToString:)) + return NO; +// if(!IS_EQUAL([self xuid], [_other xuid], isEqualToString:)) +// return NO; + return YES; +} + +- (BOOL)hasSameEmailAddress:(iCalPerson *)_other { + return IS_EQUAL([[self email] lowercaseString], + [[_other email] lowercaseString], + isEqualToString:); +} + +@end /* iCalPerson */ diff --git a/SoObjects/Contacts/SOGoContactSourceFolder.m b/SoObjects/Contacts/SOGoContactSourceFolder.m index a52ec20b9..b618d6f5a 100644 --- a/SoObjects/Contacts/SOGoContactSourceFolder.m +++ b/SoObjects/Contacts/SOGoContactSourceFolder.m @@ -697,7 +697,7 @@ BOOL otherIsPersonal; otherIsPersonal = ([otherFolder isKindOfClass: [SOGoContactGCSFolder class]] - || ([otherFolder isKindOfClass: isa] && [otherFolder isPersonalSource])); + || ([otherFolder isKindOfClass: object_getClass(self)] && [otherFolder isPersonalSource])); if (isPersonalSource) { diff --git a/SoObjects/Contacts/SOGoContactSourceFolder.m.orig b/SoObjects/Contacts/SOGoContactSourceFolder.m.orig new file mode 100644 index 000000000..a52ec20b9 --- /dev/null +++ b/SoObjects/Contacts/SOGoContactSourceFolder.m.orig @@ -0,0 +1,808 @@ +/* SOGoContactSourceFolder.m - this file is part of SOGo + * + * Copyright (C) 2006-2015 Inverse inc. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#import +#import +#import +#import +#import +#import + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +#import "SOGoContactFolders.h" +#import "SOGoContactGCSFolder.h" +#import "SOGoContactLDIFEntry.h" +#import "SOGoContactSourceFolder.h" + +@class WOContext; + +@implementation SOGoContactSourceFolder + ++ (id) folderWithName: (NSString *) aName + andDisplayName: (NSString *) aDisplayName + inContainer: (id) aContainer +{ + id folder; + + folder = [[self alloc] initWithName: aName + andDisplayName: aDisplayName + inContainer: aContainer]; + [folder autorelease]; + + return folder; +} + +- (id) init +{ + if ((self = [super init])) + { + childRecords = [NSMutableDictionary new]; + source = nil; + } + + return self; +} + +- (id) initWithName: (NSString *) newName + andDisplayName: (NSString *) newDisplayName + inContainer: (id) newContainer +{ + if ((self = [self initWithName: newName + inContainer: newContainer])) + { + if (![newDisplayName length]) + newDisplayName = newName; + ASSIGN (displayName, newDisplayName); + } + + return self; +} + +- (void) dealloc +{ + [childRecords release]; + [source release]; + [super dealloc]; +} + +- (void) setSource: (id ) newSource +{ + ASSIGN (source, newSource); +} + +- (id ) source +{ + return source; +} + +- (void) setIsPersonalSource: (BOOL) isPersonal +{ + isPersonalSource = isPersonal; +} + +- (BOOL) isPersonalSource +{ + return isPersonalSource; +} + +- (NSString *) groupDavResourceType +{ + return @"vcard-collection"; +} + +- (NSArray *) davResourceType +{ + NSMutableArray *resourceType; + NSArray *type; + + resourceType = [NSMutableArray arrayWithArray: [super davResourceType]]; + type = [NSArray arrayWithObjects: @"addressbook", XMLNS_CARDDAV, nil]; + [resourceType addObject: type]; + type = [NSArray arrayWithObjects: @"directory", XMLNS_CARDDAV, nil]; + [resourceType addObject: type]; + + return resourceType; +} + +- (id) lookupName: (NSString *) objectName + inContext: (WOContext *) lookupContext + acquire: (BOOL) acquire +{ + NSDictionary *ldifEntry; + SOGoContactLDIFEntry *obj; + NSString *url; + BOOL isNew = NO; + NSArray *baseClasses; + + /* first check attributes directly bound to the application */ + obj = [super lookupName: objectName inContext: lookupContext acquire: NO]; + + if (!obj) + { + ldifEntry = [childRecords objectForKey: objectName]; + if (!ldifEntry) + { + ldifEntry = [source lookupContactEntry: objectName]; + if (ldifEntry) + [childRecords setObject: ldifEntry forKey: objectName]; + else if ([self isValidContentName: objectName]) + { + url = [[[lookupContext request] uri] urlWithoutParameters]; + if ([url hasSuffix: @"AsContact"]) + { + baseClasses = [NSArray arrayWithObjects: @"inetorgperson", + @"mozillaabpersonalpha", nil]; + ldifEntry = [NSMutableDictionary + dictionaryWithObject: baseClasses + forKey: @"objectclass"]; + isNew = YES; + } + } + } + if (ldifEntry) + { + obj = [SOGoContactLDIFEntry contactEntryWithName: objectName + withLDIFEntry: ldifEntry + inContainer: self]; + if (isNew) + [obj setIsNew: YES]; + } + else + obj = [NSException exceptionWithHTTPStatus: 404]; + } + + return obj; +} + +- (NSArray *) toOneRelationshipKeys +{ + NSString *userDomain; + + userDomain = [[context activeUser] domain]; + return [source allEntryIDsVisibleFromDomain: userDomain]; +} + +- (NSException *) saveLDIFEntry: (SOGoContactLDIFEntry *) ldifEntry +{ + return (([ldifEntry isNew]) + ? [source addContactEntry: [ldifEntry ldifRecord] + withID: [ldifEntry nameInContainer]] + : [source updateContactEntry: [ldifEntry ldifRecord]]); +} + +- (NSException *) deleteLDIFEntry: (SOGoContactLDIFEntry *) ldifEntry +{ + return [source removeContactEntryWithID: [ldifEntry nameInContainer]]; +} + +/** + * Normalize keys of dictionary representing a contact. + * @param oldRecord a dictionary with pairs from the source folder (LDAP or SQL) + * @see [SOGoContactGCSFolder _fixupContactRecord] + */ +- (NSDictionary *) _flattenedRecord: (NSDictionary *) oldRecord +{ + NSMutableDictionary *newRecord; + id data; + NSObject *recordSource; + + newRecord = [NSMutableDictionary dictionaryWithCapacity: 8]; + [newRecord setObject: [oldRecord objectForKey: @"c_uid"] + forKey: @"c_uid"]; + + // c_name => id + [newRecord setObject: [oldRecord objectForKey: @"c_name"] + forKey: @"c_name"]; + [newRecord setObject: [oldRecord objectForKey: @"c_name"] + forKey: @"id"]; + + // displayname || c_cn => fn + data = [oldRecord objectForKey: @"displayname"]; + if (!data) + data = [oldRecord objectForKey: @"c_cn"]; + if (data) + [newRecord setObject: data forKey: @"fn"]; + else + data = @""; + [newRecord setObject: data forKey: @"c_cn"]; + + // mail => emails[] + data = [oldRecord objectForKey: @"c_emails"]; + if (data) + { + if ([data isKindOfClass: [NSArray class]]) + { + if ([data count] > 0) + { + NSEnumerator *emails; + NSMutableArray *recordEmails; + NSString *email; + emails = [(NSArray *)data objectEnumerator]; + recordEmails = [NSMutableArray arrayWithCapacity: [data count]]; + while ((email = [emails nextObject])) + { + [recordEmails addObject: [NSDictionary dictionaryWithObject: email forKey: @"value"]]; + } + [newRecord setObject: recordEmails forKey: @"emails"]; + } + } + else if (data) + { + NSDictionary *email; + email = [NSDictionary dictionaryWithObjectsAndKeys: @"pref", @"type", data, @"value", nil]; + [newRecord setObject: [NSArray arrayWithObject: email] forKey: @"emails"]; + } + else + data = @""; + } + else + data = @""; + [newRecord setObject: data forKey: @"c_mail"]; + + data = [oldRecord objectForKey: @"nsaimid"]; + if (![data length]) + data = [oldRecord objectForKey: @"nscpaimscreenname"]; + if (![data length]) + data = @""; + [newRecord setObject: data forKey: @"c_screenname"]; + + // o => org + data = [oldRecord objectForKey: @"o"]; + if (data) + [newRecord setObject: data forKey: @"org"]; + else + data = @""; + [newRecord setObject: data forKey: @"c_o"]; + + // telephonenumber || cellphone || homephone => phones[] + data = [oldRecord objectForKey: @"telephonenumber"]; + if (![data length]) + data = [oldRecord objectForKey: @"cellphone"]; + if (![data length]) + data = [oldRecord objectForKey: @"homephone"]; + if (data) + { + NSDictionary *phonenumber; + phonenumber = [NSDictionary dictionaryWithObjectsAndKeys: @"pref", @"type", data, @"value", nil]; + [newRecord setObject: [NSArray arrayWithObject: phonenumber] forKey: @"phones"]; + } + else + data = @""; + [newRecord setObject: data forKey: @"c_telephonenumber"]; + + // Custom attribute for group-lookups. See LDAPSource.m where + // it's set. + data = [oldRecord objectForKey: @"isGroup"]; + if (data) + { + [newRecord setObject: data forKey: @"isGroup"]; + [newRecord setObject: @"vlist" forKey: @"c_component"]; + } +#warning TODO: create a custom icon for resources + else + { + [newRecord setObject: @"vcard" forKey: @"c_component"]; + } + + // c_info => note + data = [oldRecord objectForKey: @"c_info"]; + if ([data length] > 0) + { + [newRecord setObject: data forKey: @"note"]; + [newRecord setObject: data forKey: @"contactInfo"]; + } + + recordSource = [oldRecord objectForKey: @"source"]; + if ([recordSource conformsToProtocol: @protocol (SOGoDNSource)] && + [[(NSObject *) recordSource MSExchangeHostname] length]) + [newRecord setObject: [NSNumber numberWithInt: 1] forKey: @"isMSExchange"]; + + return newRecord; +} + +- (NSArray *) _flattenedRecords: (NSArray *) records +{ + NSMutableArray *newRecords; + NSEnumerator *oldRecords; + NSDictionary *oldRecord; + + newRecords = [NSMutableArray arrayWithCapacity: [records count]]; + + oldRecords = [records objectEnumerator]; + while ((oldRecord = [oldRecords nextObject])) + [newRecords addObject: [self _flattenedRecord: oldRecord]]; + + return newRecords; +} + +/* This method returns the entry corresponding to the name passed as + parameter. */ +- (NSDictionary *) lookupContactWithName: (NSString *) aName +{ + NSDictionary *record; + + if (aName && [aName length] > 0) + record = [self _flattenedRecord: [source lookupContactEntry: aName]]; + else + record = nil; + + return record; +} + +- (NSArray *) lookupContactsWithFilter: (NSString *) filter + onCriteria: (NSString *) criteria + sortBy: (NSString *) sortKey + ordering: (NSComparisonResult) sortOrdering + inDomain: (NSString *) domain +{ + NSArray *records, *result; + EOSortOrdering *ordering; + + result = nil; + + if (([filter length] > 0 && [criteria isEqualToString: @"name_or_address"]) + || ![source listRequiresDot]) + { + records = [source fetchContactsMatching: filter + inDomain: domain]; + [childRecords setObjects: records + forKeys: [records objectsForKey: @"c_name" + notFoundMarker: nil]]; + records = [self _flattenedRecords: records]; + ordering + = [EOSortOrdering sortOrderingWithKey: sortKey + selector: ((sortOrdering == NSOrderedDescending) + ? EOCompareCaseInsensitiveDescending + : EOCompareCaseInsensitiveAscending)]; + result + = [records sortedArrayUsingKeyOrderArray: + [NSArray arrayWithObject: ordering]]; + } + + return result; +} + +- (NSString *) _deduceObjectNameFromURL: (NSString *) url + fromBaseURL: (NSString *) baseURL +{ + NSRange urlRange; + NSString *name; + + urlRange = [url rangeOfString: baseURL]; + if (urlRange.location != NSNotFound) + { + name = [url substringFromIndex: NSMaxRange (urlRange)]; + if ([name hasPrefix: @"/"]) + name = [name substringFromIndex: 1]; + } + else + name = nil; + + return name; +} + +/* TODO: multiget reorg */ +- (NSString *) _nodeTagForProperty: (NSString *) property +{ + NSString *namespace, *nodeName, *nsRep; + NSRange nsEnd; + + nsEnd = [property rangeOfString: @"}"]; + namespace + = [property substringFromRange: NSMakeRange (1, nsEnd.location - 1)]; + nodeName = [property substringFromIndex: nsEnd.location + 1]; + if ([namespace isEqualToString: XMLNS_CARDDAV]) + nsRep = @"C"; + else + nsRep = @"D"; + + return [NSString stringWithFormat: @"%@:%@", nsRep, nodeName]; +} + +- (NSString *) _nodeTag: (NSString *) property +{ + static NSMutableDictionary *tags = nil; + NSString *nodeTag; + + if (!tags) + tags = [NSMutableDictionary new]; + nodeTag = [tags objectForKey: property]; + if (!nodeTag) + { + nodeTag = [self _nodeTagForProperty: property]; + [tags setObject: nodeTag forKey: property]; + } + + return nodeTag; +} + +- (NSString **) _properties: (NSString **) properties + count: (unsigned int) propertiesCount + ofObject: (NSDictionary *) object +{ + SOGoContactLDIFEntry *ldifEntry; + NSString **currentProperty; + NSString **values, **currentValue; + SEL methodSel; + +// NSLog (@"_properties:ofObject:: %@", [NSDate date]); + + values = NSZoneMalloc (NULL, + (propertiesCount + 1) * sizeof (NSString *)); + *(values + propertiesCount) = nil; + + ldifEntry = [SOGoContactLDIFEntry + contactEntryWithName: [object objectForKey: @"c_name"] + withLDIFEntry: object + inContainer: self]; + currentProperty = properties; + currentValue = values; + while (*currentProperty) + { + methodSel = SOGoSelectorForPropertyGetter (*currentProperty); + if (methodSel && [ldifEntry respondsToSelector: methodSel]) + *currentValue = [[ldifEntry performSelector: methodSel] + stringByEscapingXMLString]; + currentProperty++; + currentValue++; + } + +// NSLog (@"/_properties:ofObject:: %@", [NSDate date]); + + return values; +} + +- (NSArray *) _propstats: (NSString **) properties + count: (unsigned int) propertiesCount + ofObject: (NSDictionary *) object +{ + NSMutableArray *propstats, *properties200, *properties404, *propDict; + NSString **property, **values, **currentValue; + NSString *propertyValue, *nodeTag; + +// NSLog (@"_propstats:ofObject:: %@", [NSDate date]); + + propstats = [NSMutableArray array]; + + properties200 = [NSMutableArray array]; + properties404 = [NSMutableArray array]; + + values = [self _properties: properties count: propertiesCount + ofObject: object]; + currentValue = values; + + property = properties; + while (*property) + { + nodeTag = [self _nodeTag: *property]; + if (*currentValue) + { + propertyValue = [NSString stringWithFormat: @"<%@>%@", + nodeTag, *currentValue, nodeTag]; + propDict = properties200; + } + else + { + propertyValue = [NSString stringWithFormat: @"<%@/>", nodeTag]; + propDict = properties404; + } + [propDict addObject: propertyValue]; + property++; + currentValue++; + } + free (values); + + if ([properties200 count]) + [propstats addObject: [NSDictionary dictionaryWithObjectsAndKeys: + properties200, @"properties", + @"HTTP/1.1 200 OK", @"status", + nil]]; + if ([properties404 count]) + [propstats addObject: [NSDictionary dictionaryWithObjectsAndKeys: + properties404, @"properties", + @"HTTP/1.1 404 Not Found", @"status", + nil]]; +// NSLog (@"/_propstats:ofObject:: %@", [NSDate date]); + + return propstats; +} + +- (void) _appendPropstat: (NSDictionary *) propstat + toBuffer: (NSMutableString *) r +{ + NSArray *properties; + unsigned int count, max; + + [r appendString: @""]; + properties = [propstat objectForKey: @"properties"]; + max = [properties count]; + for (count = 0; count < max; count++) + [r appendString: [properties objectAtIndex: count]]; + [r appendString: @""]; + [r appendString: [propstat objectForKey: @"status"]]; + [r appendString: @""]; +} + +- (void) appendObject: (NSDictionary *) object + properties: (NSString **) properties + count: (unsigned int) propertiesCount + withBaseURL: (NSString *) baseURL + toBuffer: (NSMutableString *) r +{ + NSArray *propstats; + unsigned int count, max; + + [r appendFormat: @""]; + [r appendString: baseURL]; + [r appendString: [[object objectForKey: @"c_name"] stringByEscapingURL]]; + [r appendString: @""]; + + propstats = [self _propstats: properties count: propertiesCount + ofObject: object]; + max = [propstats count]; + for (count = 0; count < max; count++) + [self _appendPropstat: [propstats objectAtIndex: count] + toBuffer: r]; + + [r appendString: @""]; +} + +- (void) appendMissingObjectRef: (NSString *) href + toBuffer: (NSMutableString *) r +{ + [r appendString: @""]; + [r appendString: href]; + [r appendString: @"HTTP/1.1 404 Not Found"]; +} + +- (void) _appendComponentProperties: (NSArray *) properties + matchingURLs: (id ) refs + toResponse: (WOResponse *) response +{ + NSObject *element; + NSString *url, *baseURL, *cname; + NSString **propertiesArray; + NSMutableString *buffer; + NSDictionary *object; + + unsigned int count, max, propertiesCount; + + baseURL = [self davURLAsString]; +#warning review this when fixing http://www.scalableogo.org/bugs/view.php?id=276 + if (![baseURL hasSuffix: @"/"]) + baseURL = [NSString stringWithFormat: @"%@/", baseURL]; + + propertiesArray = [properties asPointersOfObjects]; + propertiesCount = [properties count]; + + max = [refs length]; + buffer = [NSMutableString stringWithCapacity: max*512]; + + for (count = 0; count < max; count++) + { + element = [refs objectAtIndex: count]; + url = [[[element firstChild] nodeValue] stringByUnescapingURL]; + cname = [self _deduceObjectNameFromURL: url fromBaseURL: baseURL]; + object = [source lookupContactEntry: cname]; + if (object) + [self appendObject: object + properties: propertiesArray + count: propertiesCount + withBaseURL: baseURL + toBuffer: buffer]; + else + [self appendMissingObjectRef: url + toBuffer: buffer]; + } + [response appendContentString: buffer]; +// NSLog (@"/adding properties with url"); + + NSZoneFree (NULL, propertiesArray); +} + +- (WOResponse *) performMultigetInContext: (WOContext *) queryContext + inNamespace: (NSString *) namespace +{ + WOResponse *r; + id document; + id documentElement, propElement; + + r = [context response]; + [r prepareDAVResponse]; + [r appendContentString: + [NSString stringWithFormat: @"", namespace]]; + document = [[queryContext request] contentAsDOMDocument]; + documentElement = [document documentElement]; + propElement = [(NGDOMNodeWithChildren *) documentElement + firstElementWithTag: @"prop" + inNamespace: @"DAV:"]; + [self _appendComponentProperties: [(NGDOMNodeWithChildren *) propElement flatPropertyNameOfSubElements] + matchingURLs: [documentElement getElementsByTagName: @"href"] + toResponse: r]; + [r appendContentString:@""]; + + return r; +} + +- (id) davAddressbookMultiget: (id) queryContext +{ + return [self performMultigetInContext: queryContext + inNamespace: XMLNS_CARDDAV]; +} + +- (NSString *) davDisplayName +{ + return displayName; +} + +- (BOOL) isFolderish +{ + return YES; +} + +/* folder type */ + +- (NSString *) folderType +{ + return @"Contact"; +} + +/* sorting */ +- (NSComparisonResult) compare: (id) otherFolder +{ + NSComparisonResult comparison; + BOOL otherIsPersonal; + + otherIsPersonal = ([otherFolder isKindOfClass: [SOGoContactGCSFolder class]] + || ([otherFolder isKindOfClass: isa] && [otherFolder isPersonalSource])); + + if (isPersonalSource) + { + if (otherIsPersonal && ![nameInContainer isEqualToString: @"personal"]) + { + if ([[otherFolder nameInContainer] isEqualToString: @"personal"]) + comparison = NSOrderedDescending; + else + comparison + = [[self displayName] + localizedCaseInsensitiveCompare: [otherFolder displayName]]; + } + else + comparison = NSOrderedAscending; + } + else + { + if (otherIsPersonal) + comparison = NSOrderedDescending; + else + comparison + = [[self displayName] + localizedCaseInsensitiveCompare: [otherFolder displayName]]; + } + + return comparison; +} + +/* common methods */ + +- (NSException *) delete +{ + NSException *error; + + if (isPersonalSource) + { + error = [(SOGoContactFolders *) container + removeLDAPAddressBook: nameInContainer]; + if (!error && [[context request] handledByDefaultHandler]) + [self sendFolderAdvisoryTemplate: @"Removal"]; + } + else + error = [NSException exceptionWithHTTPStatus: 501 /* not implemented */ + reason: @"delete not available on system sources"]; + + return error; +} + +- (void) renameTo: (NSString *) newName +{ + NSException *error; + + if (isPersonalSource) + { + if (![[source displayName] isEqualToString: newName]) + { + error = [(SOGoContactFolders *) container + renameLDAPAddressBook: nameInContainer + withDisplayName: newName]; + if (!error) + [self setDisplayName: newName]; + } + } + /* If public source then method is ignored, maybe we should return an + NSException instead... */ +} + +/* acls */ +- (NSString *) ownerInContext: (WOContext *) noContext +{ + NSString *sourceOwner; + + if (isPersonalSource) + sourceOwner = [[source modifiers] objectAtIndex: 0]; + else + sourceOwner = @"nobody"; + + return sourceOwner; +} + +- (NSArray *) subscriptionRoles +{ + return [NSArray arrayWithObject: SoRole_Authenticated]; +} + +- (NSArray *) aclsForUser: (NSString *) uid +{ + NSArray *acls, *modifiers; + static NSArray *modifierRoles = nil; + + if (!modifierRoles) + modifierRoles = [[NSArray alloc] initWithObjects: @"Owner", + @"ObjectViewer", + @"ObjectEditor", @"ObjectCreator", + @"ObjectEraser", nil]; + + modifiers = [source modifiers]; + if ([modifiers containsObject: uid]) + acls = [modifierRoles copy]; + else + acls = [NSArray new]; + + [acls autorelease]; + + return acls; +} + +@end diff --git a/SoObjects/SOGo/SOGoCacheGCSFolder.m b/SoObjects/SOGo/SOGoCacheGCSFolder.m index b29287fcb..2e483bc32 100644 --- a/SoObjects/SOGo/SOGoCacheGCSFolder.m +++ b/SoObjects/SOGo/SOGoCacheGCSFolder.m @@ -364,7 +364,7 @@ Class SOGoCacheGCSObjectK = Nil; if (record) { if ([[record objectForKey: @"c_type"] intValue] == MAPIFolderCacheObject) - objectClass = isa; + objectClass = object_getClass(self); else objectClass = SOGoCacheGCSObjectK; diff --git a/SoObjects/SOGo/SOGoCacheGCSFolder.m.orig b/SoObjects/SOGo/SOGoCacheGCSFolder.m.orig new file mode 100644 index 000000000..b29287fcb --- /dev/null +++ b/SoObjects/SOGo/SOGoCacheGCSFolder.m.orig @@ -0,0 +1,486 @@ +/* SOGoCacheGCSFolder.m - this file is part of SOGo + * + * Copyright (C) 2012-2014 Inverse inc. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#import +#import +#import +#import +#import +#import +#import +#import + +#import +#import +#import +#import + +#import +#import +#import +#import +#import "EOQualifier+SOGoCacheObject.h" +#import "GCSSpecialQueries+SOGoCacheObject.h" + +#import "SOGoCacheGCSFolder.h" + +#undef DEBUG +//#include +//#include +//#include +//#include +//#include +//#include +//#include + +Class SOGoCacheGCSObjectK = Nil; + +@implementation SOGoCacheGCSFolder + ++ (void) initialize +{ + SOGoCacheGCSObjectK = [SOGoCacheGCSObject class]; +} + +- (id) init +{ + if ((self = [super init])) + { + pathPrefix = nil; + } + + return self; +} + +- (id) initWithName: (NSString *) name inContainer: (id) newContainer +{ + if ((self = [super initWithName: name inContainer: newContainer])) + { + objectType = MAPIFolderCacheObject; + aclMessage = [SOGoCacheGCSObject objectWithName: @"permissions" + inContainer: self]; + [aclMessage setObjectType: MAPIInternalCacheObject]; + [aclMessage retain]; + } + + return self; +} + +- (void) dealloc +{ + [aclMessage release]; + [pathPrefix release]; + [super dealloc]; +} + +- (BOOL) isFolderish +{ + return YES; +} + +- (void) setPathPrefix: (NSString *) newPathPrefix +{ + ASSIGN (pathPrefix, newPathPrefix); +} + +- (NSMutableString *) pathForChild: (NSString *) childName +{ + NSMutableString *path; + + path = [self path]; + [path appendFormat: @"/%@", childName]; + + return path; +} + +- (NSMutableString *) path +{ + NSMutableString *path; + + path = [super path]; + if (pathPrefix) + [path insertString: pathPrefix atIndex: 0]; + + return path; +} + +// - (SOGoMAPIDBMessage *) newMessage +// { +// NSString *newFilename; + +// newFilename = [NSString stringWithFormat: @"%@.plist", +// [SOGoObject globallyUniqueObjectId]]; + +// return [SOGoMAPIDBMessage objectWithName: filename inContainer: self]; +// } + +- (NSArray *) childKeysOfType: (SOGoCacheObjectType) type + includeDeleted: (BOOL) includeDeleted + matchingQualifier: (EOQualifier *) qualifier + andSortOrderings: (NSArray *) sortOrderings +{ + NSMutableArray *childKeys; + NSMutableString *sql// , *qualifierClause + ; + NSString *childPathPrefix, *childPath, *childKey; + NSMutableArray *whereClause; + NSArray *records; + NSDictionary *record; + NSUInteger childPathPrefixLen, count, max; + SOGoCacheGCSObject *currentChild; + + /* query construction */ + sql = [NSMutableString stringWithCapacity: 256]; + [sql appendFormat: @"SELECT * FROM %@", [self tableName]]; + + whereClause = [NSMutableArray arrayWithCapacity: 2]; + [whereClause addObject: [NSString stringWithFormat: @"c_parent_path = '%@'", + [self path]]]; + [whereClause addObject: [NSString stringWithFormat: @"c_type = %d", type]]; + if (!includeDeleted) + [whereClause addObject: @"c_deleted = 0"]; + + [sql appendFormat: @" WHERE %@", + [whereClause componentsJoinedByString: @" AND "]]; + + childPathPrefix = [NSString stringWithFormat: @"%@/", [self path]]; + + /* results */ + records = [self performSQLQuery: sql]; + if (records) + { + max = [records count]; + childKeys = [NSMutableArray arrayWithCapacity: max]; + childPathPrefixLen = [childPathPrefix length]; + for (count = 0; count < max; count++) + { + record = [records objectAtIndex: count]; + childPath = [record objectForKey: @"c_path"]; + childKey = [childPath substringFromIndex: childPathPrefixLen]; + if ([childKey rangeOfString: @"/"].location == NSNotFound) + { + if (qualifier) + { + currentChild = [SOGoCacheGCSObject objectWithName: childKey + inContainer: self]; + [currentChild setupFromRecord: record]; + if ([qualifier evaluateSOGoMAPIDBObject: currentChild]) + [childKeys addObject: childKey]; + } + else + [childKeys addObject: childKey]; + } + } + } + else + childKeys = nil; + + return childKeys; +} + +- (NSArray *) toManyRelationshipKeys +{ + return [self childKeysOfType: MAPIFolderCacheObject + includeDeleted: NO + matchingQualifier: nil + andSortOrderings: nil]; +} + +- (NSArray *) toOneRelationshipKeys +{ + return [self childKeysOfType: MAPIMessageCacheObject + includeDeleted: NO + matchingQualifier: nil + andSortOrderings: nil]; +} + +- (void) setNameInContainer: (NSString *) newName +{ + NSMutableString *sql; + NSString *oldPath, *newPath, *path, *parentPath; + NSMutableArray *queries; + NSArray *records; + NSDictionary *record; + NSUInteger count, max; + + /* change the paths in children records */ + if (nameInContainer) + oldPath = [self path]; + + [super setNameInContainer: newName]; + + if (nameInContainer) + { + newPath = [self path]; + + sql = [NSMutableString stringWithFormat: + @"SELECT c_path, c_parent_path FROM %@" + @" WHERE c_path LIKE '%@/%%'", + [self tableName], oldPath]; + records = [self performSQLQuery: sql]; + max = [records count]; + queries = [NSMutableArray arrayWithCapacity: max + 1]; + if (max > 0) + { + for (count = 0; count < max; count++) + { + record = [records objectAtIndex: count]; + path = [record objectForKey: @"c_path"]; + sql = [NSMutableString stringWithFormat: @"UPDATE %@" + @" SET c_path = '%@'", + [self tableName], + [path stringByReplacingPrefix: oldPath + withPrefix: newPath]]; + parentPath = [record objectForKey: @"c_parent_path"]; + if ([parentPath isNotNull]) + [sql appendFormat: @", c_parent_path = '%@'", + [parentPath stringByReplacingPrefix: oldPath + withPrefix: newPath]]; + [sql appendFormat: @" WHERE c_path = '%@'", path]; + [queries addObject: sql]; + } + [self performBatchSQLQueries: queries]; + } + } +} + +- (void) changePathTo: (NSString *) newPath +{ + NSMutableString *sql// , *qualifierClause + ; + NSString *oldPath, *oldPathAsPrefix, *path, *parentPath; + NSMutableArray *queries; + NSArray *records; + NSDictionary *record; + NSUInteger count, max; + + /* change the paths in children records */ + oldPath = [self path]; + oldPathAsPrefix = [NSString stringWithFormat: @"%@/", oldPath]; + + sql = [NSMutableString stringWithFormat: + @"SELECT c_path, c_parent_path FROM %@" + @" WHERE c_path LIKE '%@%%'", + [self tableName], oldPathAsPrefix]; + records = [self performSQLQuery: sql]; + max = [records count]; + queries = [NSMutableArray arrayWithCapacity: max + 1]; + if (max > 0) + { + for (count = 0; count < max; count++) + { + record = [records objectAtIndex: count]; + path = [record objectForKey: @"c_path"]; + sql = [NSMutableString stringWithFormat: @"UPDATE %@" + @" SET c_path = '%@'", + [self tableName], + [path stringByReplacingPrefix: oldPath + withPrefix: newPath]]; + parentPath = [record objectForKey: @"c_parent_path"]; + if ([parentPath isNotNull]) + [sql appendFormat: @", c_parent_path = '%@'", + [parentPath stringByReplacingPrefix: oldPath + withPrefix: newPath]]; + [sql appendFormat: @" WHERE c_path = '%@'", path]; + [queries addObject: sql]; + } + [self performBatchSQLQueries: queries]; + } + + /* change the path in this folder record */ + [super changePathTo: newPath]; +} + +- (void) changePathTo: (NSString *) newPath intoNewContainer: (id) newContainer +{ + [self changePathTo: newPath]; + container = newContainer; + if ([self doesRetainContainer]) + [container retain]; +} + +// - (NSArray *) toOneRelationshipKeysMatchingQualifier: (EOQualifier *) qualifier +// andSortOrderings: (NSArray *) sortOrderings +// { +// NSArray *allKeys; +// NSMutableArray *keys; +// NSUInteger count, max; +// NSString *messageKey; +// SOGoMAPIDBMessage *message; + +// if (sortOrderings) +// [self warnWithFormat: @"sorting is not handled yet"]; + +// allKeys = [self toOneRelationshipKeys]; +// if (qualifier) +// { +// [self logWithFormat: @"%s: getting restricted FAI keys", __PRETTY_FUNCTION__]; +// max = [allKeys count]; +// keys = [NSMutableArray arrayWithCapacity: max]; +// for (count = 0; count < max; count++) +// { +// messageKey = [allKeys objectAtIndex: count]; +// message = [self lookupName: messageKey +// inContext: nil +// acquire: NO]; +// if ([qualifier evaluateMAPIVolatileMessage: message]) +// [keys addObject: messageKey]; +// } +// } +// else +// keys = (NSMutableArray *) allKeys; + +// return keys; +// } + +- (id) lookupName: (NSString *) childName + inContext: (WOContext *) woContext + acquire: (BOOL) acquire +{ + id object; + Class objectClass; + NSString *childPath; + NSDictionary *record; + + childPath = [self pathForChild: childName]; + record = [self lookupRecord: childPath newerThanVersion: -1]; + if (record) + { + if ([[record objectForKey: @"c_type"] intValue] == MAPIFolderCacheObject) + objectClass = isa; + else + objectClass = SOGoCacheGCSObjectK; + + object = [objectClass objectWithName: childName + inContainer: self]; + [object setupFromRecord: record]; + } + else + object = nil; + + return object; +} + +- (id) lookupFolder: (NSString *) folderName + inContext: (WOContext *) woContext +{ + id object; + + object = [SOGoCacheGCSFolder objectWithName: folderName + inContainer: self]; + [object reloadIfNeeded]; + + return object; +} + +// - (id) _fileAttributeForKey: (NSString *) key +// { +// NSDictionary *attributes; + +// attributes = [[NSFileManager defaultManager] +// fileAttributesAtPath: directory +// traverseLink: NO]; + +// return [attributes objectForKey: key]; +// } + +// - (NSCalendarDate *) creationTime +// { +// return [self _fileAttributeForKey: NSFileCreationDate]; +// } + +// - (NSCalendarDate *) lastModificationTime +// { +// return [self _fileAttributeForKey: NSFileModificationDate]; +// } + +/* acl */ +- (NSString *) defaultUserID +{ + return @"default"; +} + +- (NSMutableDictionary *) _aclEntries +{ + NSMutableDictionary *aclEntries; + + [aclMessage reloadIfNeeded]; + aclEntries = [aclMessage properties]; + if (![aclEntries objectForKey: @"users"]) + [aclEntries setObject: [NSMutableArray array] forKey: @"users"]; + if (![aclEntries objectForKey: @"entries"]) + [aclEntries setObject: [NSMutableDictionary dictionary] + forKey: @"entries"]; + + return aclEntries; +} + +- (void) addUserInAcls: (NSString *) user +{ + NSMutableDictionary *acl; + NSMutableArray *users; + + acl = [self _aclEntries]; + users = [acl objectForKey: @"users"]; + [users addObjectUniquely: user]; + [aclMessage save]; +} + +- (void) removeAclsForUsers: (NSArray *) oldUsers +{ + NSDictionary *acl; + NSMutableDictionary *entries; + NSMutableArray *users; + + acl = [self _aclEntries]; + entries = [acl objectForKey: @"entries"]; + [entries removeObjectsForKeys: oldUsers]; + users = [acl objectForKey: @"users"]; + [users removeObjectsInArray: oldUsers]; + [aclMessage save]; +} + +- (NSArray *) aclUsers +{ + return [[self _aclEntries] objectForKey: @"users"]; +} + +- (NSArray *) aclsForUser: (NSString *) uid +{ + NSDictionary *entries; + + entries = [[self _aclEntries] objectForKey: @"entries"]; + + return [entries objectForKey: uid]; +} + +- (void) setRoles: (NSArray *) roles + forUser: (NSString *) uid +{ + NSMutableDictionary *acl; + NSMutableDictionary *entries; + + acl = [self _aclEntries]; + entries = [acl objectForKey: @"entries"]; + [entries setObject: roles forKey: uid]; + [aclMessage save]; +} + +@end diff --git a/SoObjects/SOGo/SQLSource.m b/SoObjects/SOGo/SQLSource.m index aa881b9d3..2e117f54d 100644 --- a/SoObjects/SOGo/SQLSource.m +++ b/SoObjects/SOGo/SQLSource.m @@ -880,7 +880,7 @@ reason = [NSString stringWithFormat: @"method '%@' is not available" @" for class '%@'", NSStringFromSelector (_cmd), - NSStringFromClass (isa)]; + NSStringFromClass (object_getClass(self))]; return [NSException exceptionWithName: @"SQLSourceIOException" reason: reason @@ -893,7 +893,7 @@ reason = [NSString stringWithFormat: @"method '%@' is not available" @" for class '%@'", NSStringFromSelector (_cmd), - NSStringFromClass (isa)]; + NSStringFromClass (object_getClass(self))]; return [NSException exceptionWithName: @"SQLSourceIOException" reason: reason @@ -906,7 +906,7 @@ reason = [NSString stringWithFormat: @"method '%@' is not available" @" for class '%@'", NSStringFromSelector (_cmd), - NSStringFromClass (isa)]; + NSStringFromClass (object_getClass(self))]; return [NSException exceptionWithName: @"SQLSourceIOException" reason: reason @@ -932,7 +932,7 @@ reason = [NSString stringWithFormat: @"method '%@' is not available" @" for class '%@'", NSStringFromSelector (_cmd), - NSStringFromClass (isa)]; + NSStringFromClass (object_getClass(self))]; return [NSException exceptionWithName: @"SQLSourceIOException" reason: reason @@ -947,7 +947,7 @@ reason = [NSString stringWithFormat: @"method '%@' is not available" @" for class '%@'", NSStringFromSelector (_cmd), - NSStringFromClass (isa)]; + NSStringFromClass (object_getClass(self))]; return [NSException exceptionWithName: @"SQLSourceIOException" reason: reason @@ -961,7 +961,7 @@ reason = [NSString stringWithFormat: @"method '%@' is not available" @" for class '%@'", NSStringFromSelector (_cmd), - NSStringFromClass (isa)]; + NSStringFromClass (object_getClass(self))]; return [NSException exceptionWithName: @"SQLSourceIOException" reason: reason diff --git a/SoObjects/SOGo/SQLSource.m.orig b/SoObjects/SOGo/SQLSource.m.orig new file mode 100644 index 000000000..aa881b9d3 --- /dev/null +++ b/SoObjects/SOGo/SQLSource.m.orig @@ -0,0 +1,971 @@ +/* SQLSource.h - this file is part of SOGo + * + * Copyright (C) 2009-2012 Inverse inc. + * + * Authors: Ludovic Marcotte + * Francis Lachapelle + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#import +#import +#import +#import +#import +#import +#import + +#import +#import + +#import +#import +#import +#import + +#import + +#import "SOGoConstants.h" +#import "NSString+Utilities.h" +#import "NSString+Crypto.h" + +#import "SQLSource.h" + +/** + * The view MUST contain the following columns: + * + * c_uid - will be used for authentication - it's a username or username@domain.tld) + * c_name - which can be identical to c_uid - will be used to uniquely identify entries) + * c_password - password of the user, can be encoded in {scheme}pass format, or when stored without + * scheme it uses the scheme set in userPasswordAlgorithm. + * Possible algorithms are: plain, md5, crypt-md5, sha, ssha (including 256/512 variants), + * cram-md5, smd5, crypt, crypt-md5 + * c_cn - the user's common name + * mail - the user's mail address + * + * Other columns can be defined - see LDAPSource.m for the complete list. + * + * + * A SQL source can be defined like this: + * + * { + * id = zot; + * type = sql; + * viewURL = "mysql://sogo:sogo@127.0.0.1:5432/sogo/sogo_view"; + * canAuthenticate = YES; + * isAddressBook = YES; + * userPasswordAlgorithm = md5; + * prependPasswordScheme = YES; + * } + * + * If prependPasswordScheme is set to YES, the generated passwords will have the format {scheme}password. + * If it is NO (the default), the password will be written to database without encryption scheme. + * + */ + +@implementation SQLSource + ++ (id) sourceFromUDSource: (NSDictionary *) udSource + inDomain: (NSString *) domain +{ + return [[[self alloc] initFromUDSource: udSource + inDomain: domain] autorelease]; +} + +- (id) init +{ + if ((self = [super init])) + { + _sourceID = nil; + _domainField = nil; + _authenticationFilter = nil; + _loginFields = nil; + _mailFields = nil; + _userPasswordAlgorithm = nil; + _viewURL = nil; + _kindField = nil; + _multipleBookingsField = nil; + _imapHostField = nil; + _sieveHostField = nil; + } + + return self; +} + +- (void) dealloc +{ + [_sourceID release]; + [_authenticationFilter release]; + [_loginFields release]; + [_mailFields release]; + [_userPasswordAlgorithm release]; + [_viewURL release]; + [_kindField release]; + [_multipleBookingsField release]; + [_domainField release]; + [_imapHostField release]; + [_sieveHostField release]; + + [super dealloc]; +} + +- (id) initFromUDSource: (NSDictionary *) udSource + inDomain: (NSString *) sourceDomain +{ + self = [self init]; + + ASSIGN(_sourceID, [udSource objectForKey: @"id"]); + ASSIGN(_authenticationFilter, [udSource objectForKey: @"authenticationFilter"]); + ASSIGN(_loginFields, [udSource objectForKey: @"LoginFieldNames"]); + ASSIGN(_mailFields, [udSource objectForKey: @"MailFieldNames"]); + ASSIGN(_userPasswordAlgorithm, [udSource objectForKey: @"userPasswordAlgorithm"]); + ASSIGN(_imapLoginField, [udSource objectForKey: @"IMAPLoginFieldName"]); + ASSIGN(_imapHostField, [udSource objectForKey: @"IMAPHostFieldName"]); + ASSIGN(_sieveHostField, [udSource objectForKey: @"SieveHostFieldName"]); + ASSIGN(_kindField, [udSource objectForKey: @"KindFieldName"]); + ASSIGN(_multipleBookingsField, [udSource objectForKey: @"MultipleBookingsFieldName"]); + ASSIGN(_domainField, [udSource objectForKey: @"DomainFieldName"]); + if ([udSource objectForKey: @"prependPasswordScheme"]) + _prependPasswordScheme = [[udSource objectForKey: @"prependPasswordScheme"] boolValue]; + else + _prependPasswordScheme = NO; + + if (!_userPasswordAlgorithm) + _userPasswordAlgorithm = @"none"; + + if ([udSource objectForKey: @"viewURL"]) + _viewURL = [[NSURL alloc] initWithString: [udSource objectForKey: @"viewURL"]]; + +#warning this domain code has no effect yet + if ([sourceDomain length]) + ASSIGN (_domain, sourceDomain); + + if (!_viewURL) + { + [self autorelease]; + return nil; + } + + return self; +} + +- (NSString *) domain +{ + return _domain; +} + +- (BOOL) _isPassword: (NSString *) plainPassword + equalTo: (NSString *) encryptedPassword +{ + if (!plainPassword || !encryptedPassword) + return NO; + + return [plainPassword isEqualToCrypted: encryptedPassword + withDefaultScheme: _userPasswordAlgorithm]; +} + +/** + * Encrypts a string using this source password algorithm. + * @param plainPassword the unencrypted password. + * @return a new encrypted string. + * @see _isPassword:equalTo: + */ +- (NSString *) _encryptPassword: (NSString *) plainPassword +{ + NSString *pass; + NSString* result; + + pass = [plainPassword asCryptedPassUsingScheme: _userPasswordAlgorithm]; + + if (pass == nil) + { + [self errorWithFormat: @"Unsupported user-password algorithm: %@", _userPasswordAlgorithm]; + return nil; + } + + if (_prependPasswordScheme) + result = [NSString stringWithFormat: @"{%@}%@", _userPasswordAlgorithm, pass]; + else + result = pass; + + return result; +} + +// +// SQL sources don't support right now all the password policy +// stuff supported by OpenLDAP (and others). If we want to support +// this for SQL sources, we'll have to implement the same +// kind of logic in this module. +// +- (BOOL) checkLogin: (NSString *) _login + password: (NSString *) _pwd + perr: (SOGoPasswordPolicyError *) _perr + expire: (int *) _expire + grace: (int *) _grace +{ + EOAdaptorChannel *channel; + EOQualifier *qualifier; + GCSChannelManager *cm; + NSException *ex; + NSMutableString *sql; + BOOL rc; + + rc = NO; + + _login = [_login stringByReplacingString: @"'" withString: @"''"]; + cm = [GCSChannelManager defaultChannelManager]; + channel = [cm acquireOpenChannelForURL: _viewURL]; + if (channel) + { + if (_loginFields) + { + NSMutableArray *qualifiers; + NSString *field; + EOQualifier *loginQualifier; + int i; + + qualifiers = [NSMutableArray arrayWithCapacity: [_loginFields count]]; + for (i = 0; i < [_loginFields count]; i++) + { + field = [_loginFields objectAtIndex: i]; + loginQualifier = [[EOKeyValueQualifier alloc] initWithKey: field + operatorSelector: EOQualifierOperatorEqual + value: _login]; + [loginQualifier autorelease]; + [qualifiers addObject: loginQualifier]; + } + qualifier = [[EOOrQualifier alloc] initWithQualifierArray: qualifiers]; + } + else + { + qualifier = [[EOKeyValueQualifier alloc] initWithKey: @"c_uid" + operatorSelector: EOQualifierOperatorEqual + value: _login]; + } + [qualifier autorelease]; + sql = [NSMutableString stringWithFormat: @"SELECT c_password" + @" FROM %@" + @" WHERE ", + [_viewURL gcsTableName]]; + if (_authenticationFilter) + { + qualifier = [[EOAndQualifier alloc] initWithQualifiers: + qualifier, + [EOQualifier qualifierWithQualifierFormat: _authenticationFilter], + nil]; + [qualifier autorelease]; + } + [qualifier _gcsAppendToString: sql]; + + ex = [channel evaluateExpressionX: sql]; + if (!ex) + { + NSDictionary *row; + NSArray *attrs; + NSString *value; + + attrs = [channel describeResults: NO]; + row = [channel fetchAttributes: attrs withZone: NULL]; + value = [row objectForKey: @"c_password"]; + + rc = [self _isPassword: _pwd equalTo: value]; + [channel cancelFetch]; + } + else + [self errorWithFormat: @"could not run SQL '%@': %@", qualifier, ex]; + + [cm releaseChannel: channel]; + } + else + [self errorWithFormat:@"failed to acquire channel for URL: %@", + [_viewURL absoluteString]]; + + return rc; +} + +/** + * Change a user's password. + * @param login the user's login name. + * @param oldPassword the previous password. + * @param newPassword the new password. + * @param perr is not used. + * @return YES if the password was successfully changed. + */ +- (BOOL) changePasswordForLogin: (NSString *) login + oldPassword: (NSString *) oldPassword + newPassword: (NSString *) newPassword + perr: (SOGoPasswordPolicyError *) perr +{ + EOAdaptorChannel *channel; + GCSChannelManager *cm; + NSException *ex; + NSString *sqlstr; + BOOL didChange; + BOOL isOldPwdOk; + + isOldPwdOk = NO; + didChange = NO; + + // Verify current password + isOldPwdOk = [self checkLogin:login password:oldPassword perr:perr expire:0 grace:0]; + + if (isOldPwdOk) + { + // Encrypt new password + NSString *encryptedPassword = [self _encryptPassword: newPassword]; + if(encryptedPassword == nil) + return NO; + + // Save new password + login = [login stringByReplacingString: @"'" withString: @"''"]; + cm = [GCSChannelManager defaultChannelManager]; + channel = [cm acquireOpenChannelForURL: _viewURL]; + if (channel) + { + sqlstr = [NSString stringWithFormat: (@"UPDATE %@" + @" SET c_password = '%@'" + @" WHERE c_uid = '%@'"), + [_viewURL gcsTableName], encryptedPassword, login]; + + ex = [channel evaluateExpressionX: sqlstr]; + if (!ex) + { + didChange = YES; + } + else + { + [self errorWithFormat: @"could not run SQL '%@': %@", sqlstr, ex]; + } + [cm releaseChannel: channel]; + } + } + + return didChange; +} + +- (NSString *) _whereClauseFromArray: (NSArray *) theArray + value: (NSString *) theValue + exact: (BOOL) theBOOL +{ + NSMutableString *s; + int i; + + s = [NSMutableString string]; + + for (i = 0; i < [theArray count]; i++) + { + if (theBOOL) + [s appendFormat: @" OR LOWER(%@) = '%@'", [theArray objectAtIndex: i], theValue]; + else + [s appendFormat: @" OR LOWER(%@) LIKE '%%%@%%'", [theArray objectAtIndex: i], theValue]; + } + + return s; +} + +- (NSDictionary *) _lookupContactEntry: (NSString *) theID + considerEmail: (BOOL) b + inDomain: (NSString *) domain +{ + NSMutableDictionary *response; + NSMutableArray *qualifiers; + NSArray *fieldNames; + EOAdaptorChannel *channel; + EOQualifier *loginQualifier, *domainQualifier, *qualifier; + GCSChannelManager *cm; + NSMutableString *sql; + NSString *value, *field; + NSException *ex; + int i; + + response = nil; + + theID = [theID stringByReplacingString: @"'" withString: @"''"]; + cm = [GCSChannelManager defaultChannelManager]; + channel = [cm acquireOpenChannelForURL: _viewURL]; + if (channel) + { + qualifiers = [NSMutableArray arrayWithCapacity: [_loginFields count] + 1]; + + // Always compare against the c_uid field + loginQualifier = [[EOKeyValueQualifier alloc] initWithKey: @"c_uid" + operatorSelector: EOQualifierOperatorEqual + value: theID]; + [loginQualifier autorelease]; + [qualifiers addObject: loginQualifier]; + + if (_loginFields) + { + for (i = 0; i < [_loginFields count]; i++) + { + field = [_loginFields objectAtIndex: i]; + if ([field caseInsensitiveCompare: @"c_uid"] != NSOrderedSame) + { + loginQualifier = [[EOKeyValueQualifier alloc] initWithKey: field + operatorSelector: EOQualifierOperatorEqual + value: theID]; + [loginQualifier autorelease]; + [qualifiers addObject: loginQualifier]; + } + } + } + + domainQualifier = nil; + if (_domainField && domain) + { + domainQualifier = [[EOKeyValueQualifier alloc] initWithKey: _domainField + operatorSelector: EOQualifierOperatorEqual + value: domain]; + [domainQualifier autorelease]; + } + + if (b) + { + // Always compare againts the mail field + loginQualifier = [[EOKeyValueQualifier alloc] initWithKey: @"mail" + operatorSelector: EOQualifierOperatorEqual + value: [theID lowercaseString]]; + [loginQualifier autorelease]; + [qualifiers addObject: loginQualifier]; + + if (_mailFields) + { + for (i = 0; i < [_mailFields count]; i++) + { + field = [_mailFields objectAtIndex: i]; + if ([field caseInsensitiveCompare: @"mail"] != NSOrderedSame + && ![_loginFields containsObject: field]) + { + loginQualifier = [[EOKeyValueQualifier alloc] initWithKey: field + operatorSelector: EOQualifierOperatorEqual + value: [theID lowercaseString]]; + [loginQualifier autorelease]; + [qualifiers addObject: loginQualifier]; + } + } + } + } + + sql = [NSMutableString stringWithFormat: @"SELECT *" + @" FROM %@" + @" WHERE ", + [_viewURL gcsTableName]]; + qualifier = [[EOOrQualifier alloc] initWithQualifierArray: qualifiers]; + if (domainQualifier) + qualifier = [[EOAndQualifier alloc] initWithQualifiers: domainQualifier, qualifier, nil]; + [qualifier _gcsAppendToString: sql]; + + ex = [channel evaluateExpressionX: sql]; + if (!ex) + { + NSMutableArray *emails; + + response = [[channel fetchAttributes: [channel describeResults: NO] + withZone: NULL] mutableCopy]; + [response autorelease]; + [channel cancelFetch]; + + /* Convert all c_ fields to obtain their ldif equivalent */ + fieldNames = [response allKeys]; + for (i = 0; i < [fieldNames count]; i++) + { + field = [fieldNames objectAtIndex: i]; + if ([field hasPrefix: @"c_"]) + [response setObject: [response objectForKey: field] + forKey: [field substringFromIndex: 2]]; + } + + // FIXME + // We have to do this here since we do not manage modules + // constraints right now over a SQL backend. + [response setObject: [NSNumber numberWithBool: YES] forKey: @"CalendarAccess"]; + [response setObject: [NSNumber numberWithBool: YES] forKey: @"MailAccess"]; + [response setObject: [NSNumber numberWithBool: YES] forKey: @"ActiveSyncAccess"]; + + // We set the domain, if any + value = nil; + if (_domain) + value = _domain; + else if (_domainField) + value = [response objectForKey: _domainField]; + if (![value isNotNull]) + value = @""; + [response setObject: value forKey: @"c_domain"]; + + // We populate all mail fields + emails = [NSMutableArray array]; + + if ([response objectForKey: @"mail"]) + [emails addObject: [response objectForKey: @"mail"]]; + + if (_mailFields && [_mailFields count] > 0) + { + NSString *s; + int i; + + for (i = 0; i < [_mailFields count]; i++) + if ((s = [response objectForKey: [_mailFields objectAtIndex: i]]) && + [[s stringByTrimmingSpaces] length] > 0) + [emails addObject: s]; + } + + [response setObject: emails forKey: @"c_emails"]; + if (_imapHostField) + { + value = [response objectForKey: _imapHostField]; + if ([value isNotNull]) + [response setObject: value forKey: @"c_imaphostname"]; + } + + if (_sieveHostField) + { + value = [response objectForKey: _sieveHostField]; + if ([value isNotNull]) + [response setObject: value forKey: @"c_sievehostname"]; + } + + // We check if the user can authenticate + if (_authenticationFilter) + { + EOQualifier *q_uid, *q_auth; + + sql = [NSMutableString stringWithFormat: @"SELECT c_uid" + @" FROM %@" + @" WHERE ", + [_viewURL gcsTableName]]; + + q_auth = [EOQualifier qualifierWithQualifierFormat: _authenticationFilter]; + + q_uid = [[EOKeyValueQualifier alloc] initWithKey: @"c_uid" + operatorSelector: EOQualifierOperatorEqual + value: theID]; + [q_uid autorelease]; + + qualifier = [[EOAndQualifier alloc] initWithQualifiers: q_uid, q_auth, nil]; + [qualifier autorelease]; + [qualifier _gcsAppendToString: sql]; + + ex = [channel evaluateExpressionX: sql]; + if (!ex) + { + NSDictionary *authResponse; + + authResponse = [channel fetchAttributes: [channel describeResults: NO] withZone: NULL]; + [response setObject: [NSNumber numberWithBool: [authResponse count] > 0] forKey: @"canAuthenticate"]; + [channel cancelFetch]; + } + else + [self errorWithFormat: @"could not run SQL '%@': %@", sql, ex]; + } + else + [response setObject: [NSNumber numberWithBool: YES] forKey: @"canAuthenticate"]; + + // We check if we should use a different login for IMAP + if (_imapLoginField) + { + if ([[response objectForKey: _imapLoginField] isNotNull]) + [response setObject: [response objectForKey: _imapLoginField] forKey: @"c_imaplogin"]; + } + + // We check if it's a resource of not + if (_kindField) + { + if ((value = [response objectForKey: _kindField]) && [value isNotNull]) + { + if ([value caseInsensitiveCompare: @"location"] == NSOrderedSame || + [value caseInsensitiveCompare: @"thing"] == NSOrderedSame || + [value caseInsensitiveCompare: @"group"] == NSOrderedSame) + { + [response setObject: [NSNumber numberWithInt: 1] + forKey: @"isResource"]; + } + } + } + + if (_multipleBookingsField) + { + if ((value = [response objectForKey: _multipleBookingsField])) + { + [response setObject: [NSNumber numberWithInt: [value intValue]] + forKey: @"numberOfSimultaneousBookings"]; + } + } + + [response setObject: self forKey: @"source"]; + } + else + [self errorWithFormat: @"could not run SQL '%@': %@", sql, ex]; + [cm releaseChannel: channel]; + } + else + [self errorWithFormat:@"failed to acquire channel for URL: %@", + [_viewURL absoluteString]]; + + return response; +} + + +- (NSDictionary *) lookupContactEntry: (NSString *) theID +{ + return [self _lookupContactEntry: theID considerEmail: NO inDomain: nil]; +} + +- (NSDictionary *) lookupContactEntryWithUIDorEmail: (NSString *) entryID + inDomain: (NSString *) domain +{ + return [self _lookupContactEntry: entryID considerEmail: YES inDomain: domain]; +} + +/* Returns an EOQualifier of the following form: + * (_domainField = domain OR _domainField = visibleDomain1 [...]) + * Should only be called on SQL sources using _domainField name. + */ +- (EOQualifier *) _visibleDomainsQualifierFromDomain: (NSString *) domain +{ + int i; + EOQualifier *qualifier, *domainQualifier; + NSArray *visibleDomains; + NSMutableArray *qualifiers; + NSString *currentDomain; + + SOGoSystemDefaults *sd; + + /* Return early if no domain or if being called on a 'static' sql source */ + if (!domain || !_domainField) + return nil; + + sd = [SOGoSystemDefaults sharedSystemDefaults]; + visibleDomains = [sd visibleDomainsForDomain: domain]; + qualifier = nil; + + domainQualifier = + [[EOKeyValueQualifier alloc] initWithKey: _domainField + operatorSelector: EOQualifierOperatorEqual + value: domain]; + [domainQualifier autorelease]; + + if ([visibleDomains count]) + { + qualifiers = [NSMutableArray arrayWithCapacity: [visibleDomains count] + 1]; + [qualifiers addObject: domainQualifier]; + for(i = 0; i < [visibleDomains count]; i++) + { + currentDomain = [visibleDomains objectAtIndex: i]; + qualifier = + [[EOKeyValueQualifier alloc] initWithKey: _domainField + operatorSelector: EOQualifierOperatorEqual + value: currentDomain]; + [qualifier autorelease]; + [qualifiers addObject: qualifier]; + } + qualifier = [[EOOrQualifier alloc] initWithQualifierArray: qualifiers]; + [qualifier autorelease]; + } + + return qualifier ? qualifier : domainQualifier; +} + + +- (NSArray *) allEntryIDsVisibleFromDomain: (NSString *) domain +{ + EOAdaptorChannel *channel; + EOQualifier *domainQualifier; + GCSChannelManager *cm; + NSException *ex; + NSMutableArray *results; + NSMutableString *sql; + + results = [NSMutableArray array]; + + cm = [GCSChannelManager defaultChannelManager]; + channel = [cm acquireOpenChannelForURL: _viewURL]; + if (channel) + { + sql = [NSMutableString stringWithFormat: @"SELECT c_uid FROM %@", + [_viewURL gcsTableName]]; + + if (_domainField) + { + if ([domain length]) + { + domainQualifier = + [self _visibleDomainsQualifierFromDomain: domain]; + if (domainQualifier) + { + [sql appendString: @" WHERE "]; + [domainQualifier _gcsAppendToString: sql]; + } + } + else + { + /* Should not happen but avoid returning the whole table + * if a domain should have been defined */ + [sql appendFormat: @" WHERE %@ is NULL", _domainField]; + } + } + + ex = [channel evaluateExpressionX: sql]; + if (!ex) + { + NSDictionary *row; + NSArray *attrs; + NSString *value; + + attrs = [channel describeResults: NO]; + + while ((row = [channel fetchAttributes: attrs withZone: NULL])) + { + value = [row objectForKey: @"c_uid"]; + if (value) + [results addObject: value]; + } + } + else + [self errorWithFormat: @"could not run SQL '%@': %@", sql, ex]; + [cm releaseChannel: channel]; + } + else + [self errorWithFormat:@"failed to acquire channel for URL: %@", + [_viewURL absoluteString]]; + + + return results; +} + +- (NSArray *) allEntryIDs +{ + return [self allEntryIDsVisibleFromDomain: nil]; +} + +- (NSArray *) fetchContactsMatching: (NSString *) filter + inDomain: (NSString *) domain +{ + EOAdaptorChannel *channel; + NSMutableArray *results; + GCSChannelManager *cm; + NSException *ex; + NSMutableString *sql; + NSString *lowerFilter; + + results = [NSMutableArray array]; + + cm = [GCSChannelManager defaultChannelManager]; + channel = [cm acquireOpenChannelForURL: _viewURL]; + if (channel) + { + lowerFilter = [filter lowercaseString]; + lowerFilter = [lowerFilter stringByReplacingString: @"'" withString: @"''"]; + + sql = [NSMutableString stringWithFormat: (@"SELECT *" + @" FROM %@" + @" WHERE" + @" (LOWER(c_cn) LIKE '%%%@%%'" + @" OR LOWER(mail) LIKE '%%%@%%'"), + [_viewURL gcsTableName], + lowerFilter, lowerFilter]; + + if (_mailFields && [_mailFields count] > 0) + { + [sql appendString: [self _whereClauseFromArray: _mailFields value: lowerFilter exact: NO]]; + } + + [sql appendString: @")"]; + + if (_domainField) + { + if ([domain length]) + { + EOQualifier *domainQualifier; + domainQualifier = + [self _visibleDomainsQualifierFromDomain: domain]; + if (domainQualifier) + { + [sql appendFormat: @" AND ("]; + [domainQualifier _gcsAppendToString: sql]; + [sql appendFormat: @")"]; + } + } + else + [sql appendFormat: @" AND %@ IS NULL", _domainField]; + } + + ex = [channel evaluateExpressionX: sql]; + if (!ex) + { + NSDictionary *row; + NSArray *attrs; + + attrs = [channel describeResults: NO]; + + while ((row = [channel fetchAttributes: attrs withZone: NULL])) + { + row = [row mutableCopy]; + [(NSMutableDictionary *) row setObject: self forKey: @"source"]; + [results addObject: row]; + [row release]; + } + } + else + [self errorWithFormat: @"could not run SQL '%@': %@", sql, ex]; + [cm releaseChannel: channel]; + } + else + [self errorWithFormat:@"failed to acquire channel for URL: %@", + [_viewURL absoluteString]]; + + return results; +} + +- (void) setSourceID: (NSString *) newSourceID +{ +} + +- (NSString *) sourceID +{ + return _sourceID; +} + +- (void) setDisplayName: (NSString *) newDisplayName +{ +} + +- (NSString *) displayName +{ + /* This method is only used when supporting user "source" addressbooks, + which is only supported by the LDAP backend for now. */ + return _sourceID; +} + +- (void) setListRequiresDot: (BOOL) newListRequiresDot +{ +} + +- (BOOL) listRequiresDot +{ + /* This method is not implemented for SQLSource. It must enable a mechanism + where using "." is not required to list the content of addressbooks. */ + return YES; +} + +/* card editing */ +- (void) setModifiers: (NSArray *) newModifiers +{ +} + +- (NSArray *) modifiers +{ + /* This method is only used when supporting card editing, + which is only supported by the LDAP backend for now. */ + return nil; +} + +- (NSException *) addContactEntry: (NSDictionary *) roLdifRecord + withID: (NSString *) aId +{ + NSString *reason; + + reason = [NSString stringWithFormat: @"method '%@' is not available" + @" for class '%@'", NSStringFromSelector (_cmd), + NSStringFromClass (isa)]; + + return [NSException exceptionWithName: @"SQLSourceIOException" + reason: reason + userInfo: nil]; +} + +- (NSException *) updateContactEntry: (NSDictionary *) roLdifRecord +{ + NSString *reason; + + reason = [NSString stringWithFormat: @"method '%@' is not available" + @" for class '%@'", NSStringFromSelector (_cmd), + NSStringFromClass (isa)]; + + return [NSException exceptionWithName: @"SQLSourceIOException" + reason: reason + userInfo: nil]; +} + +- (NSException *) removeContactEntryWithID: (NSString *) aId +{ + NSString *reason; + + reason = [NSString stringWithFormat: @"method '%@' is not available" + @" for class '%@'", NSStringFromSelector (_cmd), + NSStringFromClass (isa)]; + + return [NSException exceptionWithName: @"SQLSourceIOException" + reason: reason + userInfo: nil]; +} + +/* user addressbooks */ +- (BOOL) hasUserAddressBooks +{ + return NO; +} + +- (NSArray *) addressBookSourcesForUser: (NSString *) user +{ + return nil; +} + +- (NSException *) addAddressBookSource: (NSString *) newId + withDisplayName: (NSString *) newDisplayName + forUser: (NSString *) user +{ + NSString *reason; + + reason = [NSString stringWithFormat: @"method '%@' is not available" + @" for class '%@'", NSStringFromSelector (_cmd), + NSStringFromClass (isa)]; + + return [NSException exceptionWithName: @"SQLSourceIOException" + reason: reason + userInfo: nil]; +} + +- (NSException *) renameAddressBookSource: (NSString *) newId + withDisplayName: (NSString *) newDisplayName + forUser: (NSString *) user +{ + NSString *reason; + + reason = [NSString stringWithFormat: @"method '%@' is not available" + @" for class '%@'", NSStringFromSelector (_cmd), + NSStringFromClass (isa)]; + + return [NSException exceptionWithName: @"SQLSourceIOException" + reason: reason + userInfo: nil]; +} + +- (NSException *) removeAddressBookSource: (NSString *) newId + forUser: (NSString *) user +{ + NSString *reason; + + reason = [NSString stringWithFormat: @"method '%@' is not available" + @" for class '%@'", NSStringFromSelector (_cmd), + NSStringFromClass (isa)]; + + return [NSException exceptionWithName: @"SQLSourceIOException" + reason: reason + userInfo: nil]; +} + +@end From 9a3f8549ea8b2e3358c419708f19d8c43ccb4cf9 Mon Sep 17 00:00:00 2001 From: Euan Thoms Date: Wed, 4 Nov 2015 03:40:53 +0800 Subject: [PATCH 2/2] Delete .orig files left over from applying patches to Stage 2. --- SOPE/NGCards/iCalPerson.m.orig | 299 ------ .../Contacts/SOGoContactSourceFolder.m.orig | 808 --------------- SoObjects/SOGo/SOGoCacheGCSFolder.m.orig | 486 --------- SoObjects/SOGo/SQLSource.m.orig | 971 ------------------ 4 files changed, 2564 deletions(-) delete mode 100644 SOPE/NGCards/iCalPerson.m.orig delete mode 100644 SoObjects/Contacts/SOGoContactSourceFolder.m.orig delete mode 100644 SoObjects/SOGo/SOGoCacheGCSFolder.m.orig delete mode 100644 SoObjects/SOGo/SQLSource.m.orig diff --git a/SOPE/NGCards/iCalPerson.m.orig b/SOPE/NGCards/iCalPerson.m.orig deleted file mode 100644 index 7904fd0f8..000000000 --- a/SOPE/NGCards/iCalPerson.m.orig +++ /dev/null @@ -1,299 +0,0 @@ -/* - Copyright (C) 2000-2005 SKYRIX Software AG - - This file is part of SOPE. - - SOPE is free software; you can redistribute it and/or modify it under - the terms of the GNU Lesser General Public License as published by the - Free Software Foundation; either version 2, or (at your option) any - later version. - - SOPE is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with SOPE; see the file COPYING. If not, write to the - Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA - 02111-1307, USA. -*/ - -#import - -#import "iCalPerson.h" - -@implementation iCalPerson - -+ (NSString *) descriptionForParticipationStatus: (iCalPersonPartStat) _status -{ - NSString *stat; - - switch (_status) { - case iCalPersonPartStatUndefined: - stat = @""; - break; - case iCalPersonPartStatAccepted: - stat = @"ACCEPTED"; - break; - case iCalPersonPartStatDeclined: - stat = @"DECLINED"; - break; - case iCalPersonPartStatTentative: - stat = @"TENTATIVE"; - break; - case iCalPersonPartStatDelegated: - stat = @"DELEGATED"; - break; - case iCalPersonPartStatCompleted: - stat = @"COMPLETED"; - break; - case iCalPersonPartStatInProcess: - stat = @"IN-PROCESS"; - break; - case iCalPersonPartStatExperimental: - case iCalPersonPartStatOther: -// [NSException raise:NSInternalInconsistencyException -// format:@"Attempt to set meaningless " -// @"participationStatus (%d)!", _status]; - stat = nil; /* keep compiler happy */ - break; - default: - stat = @"NEEDS-ACTION"; - break; - } - - return stat; -} - - -/* accessors */ - -- (void) setCn: (NSString *) _s -{ - [self setValue: 0 ofAttribute: @"cn" to: _s]; -} - -- (NSString *) cn -{ - return [self value: 0 ofAttribute: @"cn"]; -} - -- (NSString *) cnWithoutQuotes -{ - /* remove quotes around a CN */ - NSString *_cn; - - _cn = [self cn]; - if ([_cn length] <= 2) - return _cn; - if ([_cn characterAtIndex:0] != '"') - return _cn; - if (![_cn hasSuffix:@"\""]) - return _cn; - - return [_cn substringWithRange:NSMakeRange(1, [_cn length] - 2)]; -} - -- (void) setEmail: (NSString *)_s -{ - /* iCal.app compatibility: - - "mailto" prefix must be in lowercase; */ - [self setSingleValue: [NSString stringWithFormat: @"mailto:%@", _s] - forKey: @""]; -} - -- (NSString *) email -{ - return [self flattenedValuesForKey: @""]; -} - -- (NSString *) rfc822Email -{ - NSString *_email; - unsigned idx; - - _email = [self email]; - idx = NSMaxRange([_email rangeOfString:@":"]); - - if ((idx > 0) && ([_email length] > idx)) - return [_email substringFromIndex:idx]; - - return _email; -} - -- (void) setRsvp: (NSString *) _s -{ - [self setValue: 0 ofAttribute: @"rsvp" to: _s]; -} - -- (NSString *) rsvp -{ - return [[self value: 0 ofAttribute: @"rsvp"] lowercaseString]; -} - -// - (void)setXuid:(NSString *)_s { -// ASSIGNCOPY(self->xuid, _s); -// } -// - (NSString *)xuid { -// return self->xuid; -// } - -- (void)setRole:(NSString *)_s -{ - [self setValue: 0 ofAttribute: @"role" to: _s]; -} - -- (NSString *) role -{ - return [self value: 0 ofAttribute: @"role"]; -} - -- (void)setPartStat:(NSString *)_s -{ - [self setValue: 0 ofAttribute: @"partstat" to: _s]; -} - -- (NSString *) partStat -{ - return [self value: 0 ofAttribute: @"partstat"]; -} - -- (NSString *) partStatWithDefault -{ - NSString *s; - - s = [self partStat]; - if ([s length] > 0) - return s; - - return @"NEEDS-ACTION"; -} - -- (void) setParticipationStatus: (iCalPersonPartStat) _status -{ - NSString *stat; - - stat = [iCalPerson descriptionForParticipationStatus: _status]; - - if (stat) - [self setPartStat:stat]; -} - -- (iCalPersonPartStat) participationStatus { - NSString *stat; - - stat = [[self partStat] uppercaseString]; - if (![stat length]) - return iCalPersonPartStatUndefined; - else if ([stat isEqualToString:@"NEEDS-ACTION"]) - return iCalPersonPartStatNeedsAction; - else if ([stat isEqualToString:@"ACCEPTED"]) - return iCalPersonPartStatAccepted; - else if ([stat isEqualToString:@"DECLINED"]) - return iCalPersonPartStatDeclined; - else if ([stat isEqualToString:@"TENTATIVE"]) - return iCalPersonPartStatTentative; - else if ([stat isEqualToString:@"DELEGATED"]) - return iCalPersonPartStatDelegated; - else if ([stat isEqualToString:@"COMPLETED"]) - return iCalPersonPartStatCompleted; - else if ([stat isEqualToString:@"IN-PROCESS"]) - return iCalPersonPartStatInProcess; - else if ([stat hasPrefix:@"X-"]) - return iCalPersonPartStatExperimental; - return iCalPersonPartStatOther; -} - -- (void) _setValueOfMailtoAttribute: (NSString *) name - to: (NSString *) value -{ - if ([value length] && ![value hasPrefix: @"\""]) - value = [NSString stringWithFormat: @"\"%@\"", value]; - - [self setValue: 0 ofAttribute: name to: value]; -} - -- (NSString *) _valueOfMailtoAttribute: (NSString *) name -{ - NSString *mailTo; - - mailTo = [self value: 0 ofAttribute: name]; - if ([mailTo hasPrefix: @"\""]) - mailTo - = [mailTo substringWithRange: NSMakeRange (1, [mailTo length] - 2)]; - - return mailTo; -} - -- (void) setDelegatedTo: (NSString *) newDelegate -{ - [self _setValueOfMailtoAttribute: @"delegated-to" to: newDelegate]; -} - -- (NSString *) delegatedTo -{ - return [self _valueOfMailtoAttribute: @"delegated-to"]; -} - -- (void) setDelegatedFrom: (NSString *) newDelegator -{ - [self _setValueOfMailtoAttribute: @"delegated-from" to: newDelegator]; -} - -- (NSString *) delegatedFrom -{ - return [self _valueOfMailtoAttribute: @"delegated-from"]; -} - -- (void) setSentBy: (NSString *) newSentBy -{ - [self _setValueOfMailtoAttribute: @"sent-by" to: newSentBy]; -} - -- (NSString *) sentBy -{ - return [self _valueOfMailtoAttribute: @"sent-by"]; -} - -/* comparison */ - -- (NSUInteger) hash { - if ([self email]) - return [[self email] hash]; - return [super hash]; -} - -- (BOOL)isEqual:(id)_other { - if(_other == nil) - return NO; - if([_other class] != self->isa) - return NO; - if([_other hash] != [self hash]) - return NO; - return [self isEqualToPerson:_other]; -} - -- (BOOL)isEqualToPerson:(iCalPerson *)_other { - if(![self hasSameEmailAddress:_other]) - return NO; - if(!IS_EQUAL([self cn], [_other cn], isEqualToString:)) - return NO; - if(!IS_EQUAL([self rsvp], [_other rsvp], isEqualToString:)) - return NO; - if(!IS_EQUAL([self partStat], [_other partStat], isEqualToString:)) - return NO; - if(!IS_EQUAL([self role], [_other role], isEqualToString:)) - return NO; -// if(!IS_EQUAL([self xuid], [_other xuid], isEqualToString:)) -// return NO; - return YES; -} - -- (BOOL)hasSameEmailAddress:(iCalPerson *)_other { - return IS_EQUAL([[self email] lowercaseString], - [[_other email] lowercaseString], - isEqualToString:); -} - -@end /* iCalPerson */ diff --git a/SoObjects/Contacts/SOGoContactSourceFolder.m.orig b/SoObjects/Contacts/SOGoContactSourceFolder.m.orig deleted file mode 100644 index a52ec20b9..000000000 --- a/SoObjects/Contacts/SOGoContactSourceFolder.m.orig +++ /dev/null @@ -1,808 +0,0 @@ -/* SOGoContactSourceFolder.m - this file is part of SOGo - * - * Copyright (C) 2006-2015 Inverse inc. - * - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This file is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. - */ - -#import -#import -#import -#import -#import -#import - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -#import "SOGoContactFolders.h" -#import "SOGoContactGCSFolder.h" -#import "SOGoContactLDIFEntry.h" -#import "SOGoContactSourceFolder.h" - -@class WOContext; - -@implementation SOGoContactSourceFolder - -+ (id) folderWithName: (NSString *) aName - andDisplayName: (NSString *) aDisplayName - inContainer: (id) aContainer -{ - id folder; - - folder = [[self alloc] initWithName: aName - andDisplayName: aDisplayName - inContainer: aContainer]; - [folder autorelease]; - - return folder; -} - -- (id) init -{ - if ((self = [super init])) - { - childRecords = [NSMutableDictionary new]; - source = nil; - } - - return self; -} - -- (id) initWithName: (NSString *) newName - andDisplayName: (NSString *) newDisplayName - inContainer: (id) newContainer -{ - if ((self = [self initWithName: newName - inContainer: newContainer])) - { - if (![newDisplayName length]) - newDisplayName = newName; - ASSIGN (displayName, newDisplayName); - } - - return self; -} - -- (void) dealloc -{ - [childRecords release]; - [source release]; - [super dealloc]; -} - -- (void) setSource: (id ) newSource -{ - ASSIGN (source, newSource); -} - -- (id ) source -{ - return source; -} - -- (void) setIsPersonalSource: (BOOL) isPersonal -{ - isPersonalSource = isPersonal; -} - -- (BOOL) isPersonalSource -{ - return isPersonalSource; -} - -- (NSString *) groupDavResourceType -{ - return @"vcard-collection"; -} - -- (NSArray *) davResourceType -{ - NSMutableArray *resourceType; - NSArray *type; - - resourceType = [NSMutableArray arrayWithArray: [super davResourceType]]; - type = [NSArray arrayWithObjects: @"addressbook", XMLNS_CARDDAV, nil]; - [resourceType addObject: type]; - type = [NSArray arrayWithObjects: @"directory", XMLNS_CARDDAV, nil]; - [resourceType addObject: type]; - - return resourceType; -} - -- (id) lookupName: (NSString *) objectName - inContext: (WOContext *) lookupContext - acquire: (BOOL) acquire -{ - NSDictionary *ldifEntry; - SOGoContactLDIFEntry *obj; - NSString *url; - BOOL isNew = NO; - NSArray *baseClasses; - - /* first check attributes directly bound to the application */ - obj = [super lookupName: objectName inContext: lookupContext acquire: NO]; - - if (!obj) - { - ldifEntry = [childRecords objectForKey: objectName]; - if (!ldifEntry) - { - ldifEntry = [source lookupContactEntry: objectName]; - if (ldifEntry) - [childRecords setObject: ldifEntry forKey: objectName]; - else if ([self isValidContentName: objectName]) - { - url = [[[lookupContext request] uri] urlWithoutParameters]; - if ([url hasSuffix: @"AsContact"]) - { - baseClasses = [NSArray arrayWithObjects: @"inetorgperson", - @"mozillaabpersonalpha", nil]; - ldifEntry = [NSMutableDictionary - dictionaryWithObject: baseClasses - forKey: @"objectclass"]; - isNew = YES; - } - } - } - if (ldifEntry) - { - obj = [SOGoContactLDIFEntry contactEntryWithName: objectName - withLDIFEntry: ldifEntry - inContainer: self]; - if (isNew) - [obj setIsNew: YES]; - } - else - obj = [NSException exceptionWithHTTPStatus: 404]; - } - - return obj; -} - -- (NSArray *) toOneRelationshipKeys -{ - NSString *userDomain; - - userDomain = [[context activeUser] domain]; - return [source allEntryIDsVisibleFromDomain: userDomain]; -} - -- (NSException *) saveLDIFEntry: (SOGoContactLDIFEntry *) ldifEntry -{ - return (([ldifEntry isNew]) - ? [source addContactEntry: [ldifEntry ldifRecord] - withID: [ldifEntry nameInContainer]] - : [source updateContactEntry: [ldifEntry ldifRecord]]); -} - -- (NSException *) deleteLDIFEntry: (SOGoContactLDIFEntry *) ldifEntry -{ - return [source removeContactEntryWithID: [ldifEntry nameInContainer]]; -} - -/** - * Normalize keys of dictionary representing a contact. - * @param oldRecord a dictionary with pairs from the source folder (LDAP or SQL) - * @see [SOGoContactGCSFolder _fixupContactRecord] - */ -- (NSDictionary *) _flattenedRecord: (NSDictionary *) oldRecord -{ - NSMutableDictionary *newRecord; - id data; - NSObject *recordSource; - - newRecord = [NSMutableDictionary dictionaryWithCapacity: 8]; - [newRecord setObject: [oldRecord objectForKey: @"c_uid"] - forKey: @"c_uid"]; - - // c_name => id - [newRecord setObject: [oldRecord objectForKey: @"c_name"] - forKey: @"c_name"]; - [newRecord setObject: [oldRecord objectForKey: @"c_name"] - forKey: @"id"]; - - // displayname || c_cn => fn - data = [oldRecord objectForKey: @"displayname"]; - if (!data) - data = [oldRecord objectForKey: @"c_cn"]; - if (data) - [newRecord setObject: data forKey: @"fn"]; - else - data = @""; - [newRecord setObject: data forKey: @"c_cn"]; - - // mail => emails[] - data = [oldRecord objectForKey: @"c_emails"]; - if (data) - { - if ([data isKindOfClass: [NSArray class]]) - { - if ([data count] > 0) - { - NSEnumerator *emails; - NSMutableArray *recordEmails; - NSString *email; - emails = [(NSArray *)data objectEnumerator]; - recordEmails = [NSMutableArray arrayWithCapacity: [data count]]; - while ((email = [emails nextObject])) - { - [recordEmails addObject: [NSDictionary dictionaryWithObject: email forKey: @"value"]]; - } - [newRecord setObject: recordEmails forKey: @"emails"]; - } - } - else if (data) - { - NSDictionary *email; - email = [NSDictionary dictionaryWithObjectsAndKeys: @"pref", @"type", data, @"value", nil]; - [newRecord setObject: [NSArray arrayWithObject: email] forKey: @"emails"]; - } - else - data = @""; - } - else - data = @""; - [newRecord setObject: data forKey: @"c_mail"]; - - data = [oldRecord objectForKey: @"nsaimid"]; - if (![data length]) - data = [oldRecord objectForKey: @"nscpaimscreenname"]; - if (![data length]) - data = @""; - [newRecord setObject: data forKey: @"c_screenname"]; - - // o => org - data = [oldRecord objectForKey: @"o"]; - if (data) - [newRecord setObject: data forKey: @"org"]; - else - data = @""; - [newRecord setObject: data forKey: @"c_o"]; - - // telephonenumber || cellphone || homephone => phones[] - data = [oldRecord objectForKey: @"telephonenumber"]; - if (![data length]) - data = [oldRecord objectForKey: @"cellphone"]; - if (![data length]) - data = [oldRecord objectForKey: @"homephone"]; - if (data) - { - NSDictionary *phonenumber; - phonenumber = [NSDictionary dictionaryWithObjectsAndKeys: @"pref", @"type", data, @"value", nil]; - [newRecord setObject: [NSArray arrayWithObject: phonenumber] forKey: @"phones"]; - } - else - data = @""; - [newRecord setObject: data forKey: @"c_telephonenumber"]; - - // Custom attribute for group-lookups. See LDAPSource.m where - // it's set. - data = [oldRecord objectForKey: @"isGroup"]; - if (data) - { - [newRecord setObject: data forKey: @"isGroup"]; - [newRecord setObject: @"vlist" forKey: @"c_component"]; - } -#warning TODO: create a custom icon for resources - else - { - [newRecord setObject: @"vcard" forKey: @"c_component"]; - } - - // c_info => note - data = [oldRecord objectForKey: @"c_info"]; - if ([data length] > 0) - { - [newRecord setObject: data forKey: @"note"]; - [newRecord setObject: data forKey: @"contactInfo"]; - } - - recordSource = [oldRecord objectForKey: @"source"]; - if ([recordSource conformsToProtocol: @protocol (SOGoDNSource)] && - [[(NSObject *) recordSource MSExchangeHostname] length]) - [newRecord setObject: [NSNumber numberWithInt: 1] forKey: @"isMSExchange"]; - - return newRecord; -} - -- (NSArray *) _flattenedRecords: (NSArray *) records -{ - NSMutableArray *newRecords; - NSEnumerator *oldRecords; - NSDictionary *oldRecord; - - newRecords = [NSMutableArray arrayWithCapacity: [records count]]; - - oldRecords = [records objectEnumerator]; - while ((oldRecord = [oldRecords nextObject])) - [newRecords addObject: [self _flattenedRecord: oldRecord]]; - - return newRecords; -} - -/* This method returns the entry corresponding to the name passed as - parameter. */ -- (NSDictionary *) lookupContactWithName: (NSString *) aName -{ - NSDictionary *record; - - if (aName && [aName length] > 0) - record = [self _flattenedRecord: [source lookupContactEntry: aName]]; - else - record = nil; - - return record; -} - -- (NSArray *) lookupContactsWithFilter: (NSString *) filter - onCriteria: (NSString *) criteria - sortBy: (NSString *) sortKey - ordering: (NSComparisonResult) sortOrdering - inDomain: (NSString *) domain -{ - NSArray *records, *result; - EOSortOrdering *ordering; - - result = nil; - - if (([filter length] > 0 && [criteria isEqualToString: @"name_or_address"]) - || ![source listRequiresDot]) - { - records = [source fetchContactsMatching: filter - inDomain: domain]; - [childRecords setObjects: records - forKeys: [records objectsForKey: @"c_name" - notFoundMarker: nil]]; - records = [self _flattenedRecords: records]; - ordering - = [EOSortOrdering sortOrderingWithKey: sortKey - selector: ((sortOrdering == NSOrderedDescending) - ? EOCompareCaseInsensitiveDescending - : EOCompareCaseInsensitiveAscending)]; - result - = [records sortedArrayUsingKeyOrderArray: - [NSArray arrayWithObject: ordering]]; - } - - return result; -} - -- (NSString *) _deduceObjectNameFromURL: (NSString *) url - fromBaseURL: (NSString *) baseURL -{ - NSRange urlRange; - NSString *name; - - urlRange = [url rangeOfString: baseURL]; - if (urlRange.location != NSNotFound) - { - name = [url substringFromIndex: NSMaxRange (urlRange)]; - if ([name hasPrefix: @"/"]) - name = [name substringFromIndex: 1]; - } - else - name = nil; - - return name; -} - -/* TODO: multiget reorg */ -- (NSString *) _nodeTagForProperty: (NSString *) property -{ - NSString *namespace, *nodeName, *nsRep; - NSRange nsEnd; - - nsEnd = [property rangeOfString: @"}"]; - namespace - = [property substringFromRange: NSMakeRange (1, nsEnd.location - 1)]; - nodeName = [property substringFromIndex: nsEnd.location + 1]; - if ([namespace isEqualToString: XMLNS_CARDDAV]) - nsRep = @"C"; - else - nsRep = @"D"; - - return [NSString stringWithFormat: @"%@:%@", nsRep, nodeName]; -} - -- (NSString *) _nodeTag: (NSString *) property -{ - static NSMutableDictionary *tags = nil; - NSString *nodeTag; - - if (!tags) - tags = [NSMutableDictionary new]; - nodeTag = [tags objectForKey: property]; - if (!nodeTag) - { - nodeTag = [self _nodeTagForProperty: property]; - [tags setObject: nodeTag forKey: property]; - } - - return nodeTag; -} - -- (NSString **) _properties: (NSString **) properties - count: (unsigned int) propertiesCount - ofObject: (NSDictionary *) object -{ - SOGoContactLDIFEntry *ldifEntry; - NSString **currentProperty; - NSString **values, **currentValue; - SEL methodSel; - -// NSLog (@"_properties:ofObject:: %@", [NSDate date]); - - values = NSZoneMalloc (NULL, - (propertiesCount + 1) * sizeof (NSString *)); - *(values + propertiesCount) = nil; - - ldifEntry = [SOGoContactLDIFEntry - contactEntryWithName: [object objectForKey: @"c_name"] - withLDIFEntry: object - inContainer: self]; - currentProperty = properties; - currentValue = values; - while (*currentProperty) - { - methodSel = SOGoSelectorForPropertyGetter (*currentProperty); - if (methodSel && [ldifEntry respondsToSelector: methodSel]) - *currentValue = [[ldifEntry performSelector: methodSel] - stringByEscapingXMLString]; - currentProperty++; - currentValue++; - } - -// NSLog (@"/_properties:ofObject:: %@", [NSDate date]); - - return values; -} - -- (NSArray *) _propstats: (NSString **) properties - count: (unsigned int) propertiesCount - ofObject: (NSDictionary *) object -{ - NSMutableArray *propstats, *properties200, *properties404, *propDict; - NSString **property, **values, **currentValue; - NSString *propertyValue, *nodeTag; - -// NSLog (@"_propstats:ofObject:: %@", [NSDate date]); - - propstats = [NSMutableArray array]; - - properties200 = [NSMutableArray array]; - properties404 = [NSMutableArray array]; - - values = [self _properties: properties count: propertiesCount - ofObject: object]; - currentValue = values; - - property = properties; - while (*property) - { - nodeTag = [self _nodeTag: *property]; - if (*currentValue) - { - propertyValue = [NSString stringWithFormat: @"<%@>%@", - nodeTag, *currentValue, nodeTag]; - propDict = properties200; - } - else - { - propertyValue = [NSString stringWithFormat: @"<%@/>", nodeTag]; - propDict = properties404; - } - [propDict addObject: propertyValue]; - property++; - currentValue++; - } - free (values); - - if ([properties200 count]) - [propstats addObject: [NSDictionary dictionaryWithObjectsAndKeys: - properties200, @"properties", - @"HTTP/1.1 200 OK", @"status", - nil]]; - if ([properties404 count]) - [propstats addObject: [NSDictionary dictionaryWithObjectsAndKeys: - properties404, @"properties", - @"HTTP/1.1 404 Not Found", @"status", - nil]]; -// NSLog (@"/_propstats:ofObject:: %@", [NSDate date]); - - return propstats; -} - -- (void) _appendPropstat: (NSDictionary *) propstat - toBuffer: (NSMutableString *) r -{ - NSArray *properties; - unsigned int count, max; - - [r appendString: @""]; - properties = [propstat objectForKey: @"properties"]; - max = [properties count]; - for (count = 0; count < max; count++) - [r appendString: [properties objectAtIndex: count]]; - [r appendString: @""]; - [r appendString: [propstat objectForKey: @"status"]]; - [r appendString: @""]; -} - -- (void) appendObject: (NSDictionary *) object - properties: (NSString **) properties - count: (unsigned int) propertiesCount - withBaseURL: (NSString *) baseURL - toBuffer: (NSMutableString *) r -{ - NSArray *propstats; - unsigned int count, max; - - [r appendFormat: @""]; - [r appendString: baseURL]; - [r appendString: [[object objectForKey: @"c_name"] stringByEscapingURL]]; - [r appendString: @""]; - - propstats = [self _propstats: properties count: propertiesCount - ofObject: object]; - max = [propstats count]; - for (count = 0; count < max; count++) - [self _appendPropstat: [propstats objectAtIndex: count] - toBuffer: r]; - - [r appendString: @""]; -} - -- (void) appendMissingObjectRef: (NSString *) href - toBuffer: (NSMutableString *) r -{ - [r appendString: @""]; - [r appendString: href]; - [r appendString: @"HTTP/1.1 404 Not Found"]; -} - -- (void) _appendComponentProperties: (NSArray *) properties - matchingURLs: (id ) refs - toResponse: (WOResponse *) response -{ - NSObject *element; - NSString *url, *baseURL, *cname; - NSString **propertiesArray; - NSMutableString *buffer; - NSDictionary *object; - - unsigned int count, max, propertiesCount; - - baseURL = [self davURLAsString]; -#warning review this when fixing http://www.scalableogo.org/bugs/view.php?id=276 - if (![baseURL hasSuffix: @"/"]) - baseURL = [NSString stringWithFormat: @"%@/", baseURL]; - - propertiesArray = [properties asPointersOfObjects]; - propertiesCount = [properties count]; - - max = [refs length]; - buffer = [NSMutableString stringWithCapacity: max*512]; - - for (count = 0; count < max; count++) - { - element = [refs objectAtIndex: count]; - url = [[[element firstChild] nodeValue] stringByUnescapingURL]; - cname = [self _deduceObjectNameFromURL: url fromBaseURL: baseURL]; - object = [source lookupContactEntry: cname]; - if (object) - [self appendObject: object - properties: propertiesArray - count: propertiesCount - withBaseURL: baseURL - toBuffer: buffer]; - else - [self appendMissingObjectRef: url - toBuffer: buffer]; - } - [response appendContentString: buffer]; -// NSLog (@"/adding properties with url"); - - NSZoneFree (NULL, propertiesArray); -} - -- (WOResponse *) performMultigetInContext: (WOContext *) queryContext - inNamespace: (NSString *) namespace -{ - WOResponse *r; - id document; - id documentElement, propElement; - - r = [context response]; - [r prepareDAVResponse]; - [r appendContentString: - [NSString stringWithFormat: @"", namespace]]; - document = [[queryContext request] contentAsDOMDocument]; - documentElement = [document documentElement]; - propElement = [(NGDOMNodeWithChildren *) documentElement - firstElementWithTag: @"prop" - inNamespace: @"DAV:"]; - [self _appendComponentProperties: [(NGDOMNodeWithChildren *) propElement flatPropertyNameOfSubElements] - matchingURLs: [documentElement getElementsByTagName: @"href"] - toResponse: r]; - [r appendContentString:@""]; - - return r; -} - -- (id) davAddressbookMultiget: (id) queryContext -{ - return [self performMultigetInContext: queryContext - inNamespace: XMLNS_CARDDAV]; -} - -- (NSString *) davDisplayName -{ - return displayName; -} - -- (BOOL) isFolderish -{ - return YES; -} - -/* folder type */ - -- (NSString *) folderType -{ - return @"Contact"; -} - -/* sorting */ -- (NSComparisonResult) compare: (id) otherFolder -{ - NSComparisonResult comparison; - BOOL otherIsPersonal; - - otherIsPersonal = ([otherFolder isKindOfClass: [SOGoContactGCSFolder class]] - || ([otherFolder isKindOfClass: isa] && [otherFolder isPersonalSource])); - - if (isPersonalSource) - { - if (otherIsPersonal && ![nameInContainer isEqualToString: @"personal"]) - { - if ([[otherFolder nameInContainer] isEqualToString: @"personal"]) - comparison = NSOrderedDescending; - else - comparison - = [[self displayName] - localizedCaseInsensitiveCompare: [otherFolder displayName]]; - } - else - comparison = NSOrderedAscending; - } - else - { - if (otherIsPersonal) - comparison = NSOrderedDescending; - else - comparison - = [[self displayName] - localizedCaseInsensitiveCompare: [otherFolder displayName]]; - } - - return comparison; -} - -/* common methods */ - -- (NSException *) delete -{ - NSException *error; - - if (isPersonalSource) - { - error = [(SOGoContactFolders *) container - removeLDAPAddressBook: nameInContainer]; - if (!error && [[context request] handledByDefaultHandler]) - [self sendFolderAdvisoryTemplate: @"Removal"]; - } - else - error = [NSException exceptionWithHTTPStatus: 501 /* not implemented */ - reason: @"delete not available on system sources"]; - - return error; -} - -- (void) renameTo: (NSString *) newName -{ - NSException *error; - - if (isPersonalSource) - { - if (![[source displayName] isEqualToString: newName]) - { - error = [(SOGoContactFolders *) container - renameLDAPAddressBook: nameInContainer - withDisplayName: newName]; - if (!error) - [self setDisplayName: newName]; - } - } - /* If public source then method is ignored, maybe we should return an - NSException instead... */ -} - -/* acls */ -- (NSString *) ownerInContext: (WOContext *) noContext -{ - NSString *sourceOwner; - - if (isPersonalSource) - sourceOwner = [[source modifiers] objectAtIndex: 0]; - else - sourceOwner = @"nobody"; - - return sourceOwner; -} - -- (NSArray *) subscriptionRoles -{ - return [NSArray arrayWithObject: SoRole_Authenticated]; -} - -- (NSArray *) aclsForUser: (NSString *) uid -{ - NSArray *acls, *modifiers; - static NSArray *modifierRoles = nil; - - if (!modifierRoles) - modifierRoles = [[NSArray alloc] initWithObjects: @"Owner", - @"ObjectViewer", - @"ObjectEditor", @"ObjectCreator", - @"ObjectEraser", nil]; - - modifiers = [source modifiers]; - if ([modifiers containsObject: uid]) - acls = [modifierRoles copy]; - else - acls = [NSArray new]; - - [acls autorelease]; - - return acls; -} - -@end diff --git a/SoObjects/SOGo/SOGoCacheGCSFolder.m.orig b/SoObjects/SOGo/SOGoCacheGCSFolder.m.orig deleted file mode 100644 index b29287fcb..000000000 --- a/SoObjects/SOGo/SOGoCacheGCSFolder.m.orig +++ /dev/null @@ -1,486 +0,0 @@ -/* SOGoCacheGCSFolder.m - this file is part of SOGo - * - * Copyright (C) 2012-2014 Inverse inc. - * - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3, or (at your option) - * any later version. - * - * This file is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. - */ - -#import -#import -#import -#import -#import -#import -#import -#import - -#import -#import -#import -#import - -#import -#import -#import -#import -#import "EOQualifier+SOGoCacheObject.h" -#import "GCSSpecialQueries+SOGoCacheObject.h" - -#import "SOGoCacheGCSFolder.h" - -#undef DEBUG -//#include -//#include -//#include -//#include -//#include -//#include -//#include - -Class SOGoCacheGCSObjectK = Nil; - -@implementation SOGoCacheGCSFolder - -+ (void) initialize -{ - SOGoCacheGCSObjectK = [SOGoCacheGCSObject class]; -} - -- (id) init -{ - if ((self = [super init])) - { - pathPrefix = nil; - } - - return self; -} - -- (id) initWithName: (NSString *) name inContainer: (id) newContainer -{ - if ((self = [super initWithName: name inContainer: newContainer])) - { - objectType = MAPIFolderCacheObject; - aclMessage = [SOGoCacheGCSObject objectWithName: @"permissions" - inContainer: self]; - [aclMessage setObjectType: MAPIInternalCacheObject]; - [aclMessage retain]; - } - - return self; -} - -- (void) dealloc -{ - [aclMessage release]; - [pathPrefix release]; - [super dealloc]; -} - -- (BOOL) isFolderish -{ - return YES; -} - -- (void) setPathPrefix: (NSString *) newPathPrefix -{ - ASSIGN (pathPrefix, newPathPrefix); -} - -- (NSMutableString *) pathForChild: (NSString *) childName -{ - NSMutableString *path; - - path = [self path]; - [path appendFormat: @"/%@", childName]; - - return path; -} - -- (NSMutableString *) path -{ - NSMutableString *path; - - path = [super path]; - if (pathPrefix) - [path insertString: pathPrefix atIndex: 0]; - - return path; -} - -// - (SOGoMAPIDBMessage *) newMessage -// { -// NSString *newFilename; - -// newFilename = [NSString stringWithFormat: @"%@.plist", -// [SOGoObject globallyUniqueObjectId]]; - -// return [SOGoMAPIDBMessage objectWithName: filename inContainer: self]; -// } - -- (NSArray *) childKeysOfType: (SOGoCacheObjectType) type - includeDeleted: (BOOL) includeDeleted - matchingQualifier: (EOQualifier *) qualifier - andSortOrderings: (NSArray *) sortOrderings -{ - NSMutableArray *childKeys; - NSMutableString *sql// , *qualifierClause - ; - NSString *childPathPrefix, *childPath, *childKey; - NSMutableArray *whereClause; - NSArray *records; - NSDictionary *record; - NSUInteger childPathPrefixLen, count, max; - SOGoCacheGCSObject *currentChild; - - /* query construction */ - sql = [NSMutableString stringWithCapacity: 256]; - [sql appendFormat: @"SELECT * FROM %@", [self tableName]]; - - whereClause = [NSMutableArray arrayWithCapacity: 2]; - [whereClause addObject: [NSString stringWithFormat: @"c_parent_path = '%@'", - [self path]]]; - [whereClause addObject: [NSString stringWithFormat: @"c_type = %d", type]]; - if (!includeDeleted) - [whereClause addObject: @"c_deleted = 0"]; - - [sql appendFormat: @" WHERE %@", - [whereClause componentsJoinedByString: @" AND "]]; - - childPathPrefix = [NSString stringWithFormat: @"%@/", [self path]]; - - /* results */ - records = [self performSQLQuery: sql]; - if (records) - { - max = [records count]; - childKeys = [NSMutableArray arrayWithCapacity: max]; - childPathPrefixLen = [childPathPrefix length]; - for (count = 0; count < max; count++) - { - record = [records objectAtIndex: count]; - childPath = [record objectForKey: @"c_path"]; - childKey = [childPath substringFromIndex: childPathPrefixLen]; - if ([childKey rangeOfString: @"/"].location == NSNotFound) - { - if (qualifier) - { - currentChild = [SOGoCacheGCSObject objectWithName: childKey - inContainer: self]; - [currentChild setupFromRecord: record]; - if ([qualifier evaluateSOGoMAPIDBObject: currentChild]) - [childKeys addObject: childKey]; - } - else - [childKeys addObject: childKey]; - } - } - } - else - childKeys = nil; - - return childKeys; -} - -- (NSArray *) toManyRelationshipKeys -{ - return [self childKeysOfType: MAPIFolderCacheObject - includeDeleted: NO - matchingQualifier: nil - andSortOrderings: nil]; -} - -- (NSArray *) toOneRelationshipKeys -{ - return [self childKeysOfType: MAPIMessageCacheObject - includeDeleted: NO - matchingQualifier: nil - andSortOrderings: nil]; -} - -- (void) setNameInContainer: (NSString *) newName -{ - NSMutableString *sql; - NSString *oldPath, *newPath, *path, *parentPath; - NSMutableArray *queries; - NSArray *records; - NSDictionary *record; - NSUInteger count, max; - - /* change the paths in children records */ - if (nameInContainer) - oldPath = [self path]; - - [super setNameInContainer: newName]; - - if (nameInContainer) - { - newPath = [self path]; - - sql = [NSMutableString stringWithFormat: - @"SELECT c_path, c_parent_path FROM %@" - @" WHERE c_path LIKE '%@/%%'", - [self tableName], oldPath]; - records = [self performSQLQuery: sql]; - max = [records count]; - queries = [NSMutableArray arrayWithCapacity: max + 1]; - if (max > 0) - { - for (count = 0; count < max; count++) - { - record = [records objectAtIndex: count]; - path = [record objectForKey: @"c_path"]; - sql = [NSMutableString stringWithFormat: @"UPDATE %@" - @" SET c_path = '%@'", - [self tableName], - [path stringByReplacingPrefix: oldPath - withPrefix: newPath]]; - parentPath = [record objectForKey: @"c_parent_path"]; - if ([parentPath isNotNull]) - [sql appendFormat: @", c_parent_path = '%@'", - [parentPath stringByReplacingPrefix: oldPath - withPrefix: newPath]]; - [sql appendFormat: @" WHERE c_path = '%@'", path]; - [queries addObject: sql]; - } - [self performBatchSQLQueries: queries]; - } - } -} - -- (void) changePathTo: (NSString *) newPath -{ - NSMutableString *sql// , *qualifierClause - ; - NSString *oldPath, *oldPathAsPrefix, *path, *parentPath; - NSMutableArray *queries; - NSArray *records; - NSDictionary *record; - NSUInteger count, max; - - /* change the paths in children records */ - oldPath = [self path]; - oldPathAsPrefix = [NSString stringWithFormat: @"%@/", oldPath]; - - sql = [NSMutableString stringWithFormat: - @"SELECT c_path, c_parent_path FROM %@" - @" WHERE c_path LIKE '%@%%'", - [self tableName], oldPathAsPrefix]; - records = [self performSQLQuery: sql]; - max = [records count]; - queries = [NSMutableArray arrayWithCapacity: max + 1]; - if (max > 0) - { - for (count = 0; count < max; count++) - { - record = [records objectAtIndex: count]; - path = [record objectForKey: @"c_path"]; - sql = [NSMutableString stringWithFormat: @"UPDATE %@" - @" SET c_path = '%@'", - [self tableName], - [path stringByReplacingPrefix: oldPath - withPrefix: newPath]]; - parentPath = [record objectForKey: @"c_parent_path"]; - if ([parentPath isNotNull]) - [sql appendFormat: @", c_parent_path = '%@'", - [parentPath stringByReplacingPrefix: oldPath - withPrefix: newPath]]; - [sql appendFormat: @" WHERE c_path = '%@'", path]; - [queries addObject: sql]; - } - [self performBatchSQLQueries: queries]; - } - - /* change the path in this folder record */ - [super changePathTo: newPath]; -} - -- (void) changePathTo: (NSString *) newPath intoNewContainer: (id) newContainer -{ - [self changePathTo: newPath]; - container = newContainer; - if ([self doesRetainContainer]) - [container retain]; -} - -// - (NSArray *) toOneRelationshipKeysMatchingQualifier: (EOQualifier *) qualifier -// andSortOrderings: (NSArray *) sortOrderings -// { -// NSArray *allKeys; -// NSMutableArray *keys; -// NSUInteger count, max; -// NSString *messageKey; -// SOGoMAPIDBMessage *message; - -// if (sortOrderings) -// [self warnWithFormat: @"sorting is not handled yet"]; - -// allKeys = [self toOneRelationshipKeys]; -// if (qualifier) -// { -// [self logWithFormat: @"%s: getting restricted FAI keys", __PRETTY_FUNCTION__]; -// max = [allKeys count]; -// keys = [NSMutableArray arrayWithCapacity: max]; -// for (count = 0; count < max; count++) -// { -// messageKey = [allKeys objectAtIndex: count]; -// message = [self lookupName: messageKey -// inContext: nil -// acquire: NO]; -// if ([qualifier evaluateMAPIVolatileMessage: message]) -// [keys addObject: messageKey]; -// } -// } -// else -// keys = (NSMutableArray *) allKeys; - -// return keys; -// } - -- (id) lookupName: (NSString *) childName - inContext: (WOContext *) woContext - acquire: (BOOL) acquire -{ - id object; - Class objectClass; - NSString *childPath; - NSDictionary *record; - - childPath = [self pathForChild: childName]; - record = [self lookupRecord: childPath newerThanVersion: -1]; - if (record) - { - if ([[record objectForKey: @"c_type"] intValue] == MAPIFolderCacheObject) - objectClass = isa; - else - objectClass = SOGoCacheGCSObjectK; - - object = [objectClass objectWithName: childName - inContainer: self]; - [object setupFromRecord: record]; - } - else - object = nil; - - return object; -} - -- (id) lookupFolder: (NSString *) folderName - inContext: (WOContext *) woContext -{ - id object; - - object = [SOGoCacheGCSFolder objectWithName: folderName - inContainer: self]; - [object reloadIfNeeded]; - - return object; -} - -// - (id) _fileAttributeForKey: (NSString *) key -// { -// NSDictionary *attributes; - -// attributes = [[NSFileManager defaultManager] -// fileAttributesAtPath: directory -// traverseLink: NO]; - -// return [attributes objectForKey: key]; -// } - -// - (NSCalendarDate *) creationTime -// { -// return [self _fileAttributeForKey: NSFileCreationDate]; -// } - -// - (NSCalendarDate *) lastModificationTime -// { -// return [self _fileAttributeForKey: NSFileModificationDate]; -// } - -/* acl */ -- (NSString *) defaultUserID -{ - return @"default"; -} - -- (NSMutableDictionary *) _aclEntries -{ - NSMutableDictionary *aclEntries; - - [aclMessage reloadIfNeeded]; - aclEntries = [aclMessage properties]; - if (![aclEntries objectForKey: @"users"]) - [aclEntries setObject: [NSMutableArray array] forKey: @"users"]; - if (![aclEntries objectForKey: @"entries"]) - [aclEntries setObject: [NSMutableDictionary dictionary] - forKey: @"entries"]; - - return aclEntries; -} - -- (void) addUserInAcls: (NSString *) user -{ - NSMutableDictionary *acl; - NSMutableArray *users; - - acl = [self _aclEntries]; - users = [acl objectForKey: @"users"]; - [users addObjectUniquely: user]; - [aclMessage save]; -} - -- (void) removeAclsForUsers: (NSArray *) oldUsers -{ - NSDictionary *acl; - NSMutableDictionary *entries; - NSMutableArray *users; - - acl = [self _aclEntries]; - entries = [acl objectForKey: @"entries"]; - [entries removeObjectsForKeys: oldUsers]; - users = [acl objectForKey: @"users"]; - [users removeObjectsInArray: oldUsers]; - [aclMessage save]; -} - -- (NSArray *) aclUsers -{ - return [[self _aclEntries] objectForKey: @"users"]; -} - -- (NSArray *) aclsForUser: (NSString *) uid -{ - NSDictionary *entries; - - entries = [[self _aclEntries] objectForKey: @"entries"]; - - return [entries objectForKey: uid]; -} - -- (void) setRoles: (NSArray *) roles - forUser: (NSString *) uid -{ - NSMutableDictionary *acl; - NSMutableDictionary *entries; - - acl = [self _aclEntries]; - entries = [acl objectForKey: @"entries"]; - [entries setObject: roles forKey: uid]; - [aclMessage save]; -} - -@end diff --git a/SoObjects/SOGo/SQLSource.m.orig b/SoObjects/SOGo/SQLSource.m.orig deleted file mode 100644 index aa881b9d3..000000000 --- a/SoObjects/SOGo/SQLSource.m.orig +++ /dev/null @@ -1,971 +0,0 @@ -/* SQLSource.h - this file is part of SOGo - * - * Copyright (C) 2009-2012 Inverse inc. - * - * Authors: Ludovic Marcotte - * Francis Lachapelle - * - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This file is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. - */ - -#import -#import -#import -#import -#import -#import -#import - -#import -#import - -#import -#import -#import -#import - -#import - -#import "SOGoConstants.h" -#import "NSString+Utilities.h" -#import "NSString+Crypto.h" - -#import "SQLSource.h" - -/** - * The view MUST contain the following columns: - * - * c_uid - will be used for authentication - it's a username or username@domain.tld) - * c_name - which can be identical to c_uid - will be used to uniquely identify entries) - * c_password - password of the user, can be encoded in {scheme}pass format, or when stored without - * scheme it uses the scheme set in userPasswordAlgorithm. - * Possible algorithms are: plain, md5, crypt-md5, sha, ssha (including 256/512 variants), - * cram-md5, smd5, crypt, crypt-md5 - * c_cn - the user's common name - * mail - the user's mail address - * - * Other columns can be defined - see LDAPSource.m for the complete list. - * - * - * A SQL source can be defined like this: - * - * { - * id = zot; - * type = sql; - * viewURL = "mysql://sogo:sogo@127.0.0.1:5432/sogo/sogo_view"; - * canAuthenticate = YES; - * isAddressBook = YES; - * userPasswordAlgorithm = md5; - * prependPasswordScheme = YES; - * } - * - * If prependPasswordScheme is set to YES, the generated passwords will have the format {scheme}password. - * If it is NO (the default), the password will be written to database without encryption scheme. - * - */ - -@implementation SQLSource - -+ (id) sourceFromUDSource: (NSDictionary *) udSource - inDomain: (NSString *) domain -{ - return [[[self alloc] initFromUDSource: udSource - inDomain: domain] autorelease]; -} - -- (id) init -{ - if ((self = [super init])) - { - _sourceID = nil; - _domainField = nil; - _authenticationFilter = nil; - _loginFields = nil; - _mailFields = nil; - _userPasswordAlgorithm = nil; - _viewURL = nil; - _kindField = nil; - _multipleBookingsField = nil; - _imapHostField = nil; - _sieveHostField = nil; - } - - return self; -} - -- (void) dealloc -{ - [_sourceID release]; - [_authenticationFilter release]; - [_loginFields release]; - [_mailFields release]; - [_userPasswordAlgorithm release]; - [_viewURL release]; - [_kindField release]; - [_multipleBookingsField release]; - [_domainField release]; - [_imapHostField release]; - [_sieveHostField release]; - - [super dealloc]; -} - -- (id) initFromUDSource: (NSDictionary *) udSource - inDomain: (NSString *) sourceDomain -{ - self = [self init]; - - ASSIGN(_sourceID, [udSource objectForKey: @"id"]); - ASSIGN(_authenticationFilter, [udSource objectForKey: @"authenticationFilter"]); - ASSIGN(_loginFields, [udSource objectForKey: @"LoginFieldNames"]); - ASSIGN(_mailFields, [udSource objectForKey: @"MailFieldNames"]); - ASSIGN(_userPasswordAlgorithm, [udSource objectForKey: @"userPasswordAlgorithm"]); - ASSIGN(_imapLoginField, [udSource objectForKey: @"IMAPLoginFieldName"]); - ASSIGN(_imapHostField, [udSource objectForKey: @"IMAPHostFieldName"]); - ASSIGN(_sieveHostField, [udSource objectForKey: @"SieveHostFieldName"]); - ASSIGN(_kindField, [udSource objectForKey: @"KindFieldName"]); - ASSIGN(_multipleBookingsField, [udSource objectForKey: @"MultipleBookingsFieldName"]); - ASSIGN(_domainField, [udSource objectForKey: @"DomainFieldName"]); - if ([udSource objectForKey: @"prependPasswordScheme"]) - _prependPasswordScheme = [[udSource objectForKey: @"prependPasswordScheme"] boolValue]; - else - _prependPasswordScheme = NO; - - if (!_userPasswordAlgorithm) - _userPasswordAlgorithm = @"none"; - - if ([udSource objectForKey: @"viewURL"]) - _viewURL = [[NSURL alloc] initWithString: [udSource objectForKey: @"viewURL"]]; - -#warning this domain code has no effect yet - if ([sourceDomain length]) - ASSIGN (_domain, sourceDomain); - - if (!_viewURL) - { - [self autorelease]; - return nil; - } - - return self; -} - -- (NSString *) domain -{ - return _domain; -} - -- (BOOL) _isPassword: (NSString *) plainPassword - equalTo: (NSString *) encryptedPassword -{ - if (!plainPassword || !encryptedPassword) - return NO; - - return [plainPassword isEqualToCrypted: encryptedPassword - withDefaultScheme: _userPasswordAlgorithm]; -} - -/** - * Encrypts a string using this source password algorithm. - * @param plainPassword the unencrypted password. - * @return a new encrypted string. - * @see _isPassword:equalTo: - */ -- (NSString *) _encryptPassword: (NSString *) plainPassword -{ - NSString *pass; - NSString* result; - - pass = [plainPassword asCryptedPassUsingScheme: _userPasswordAlgorithm]; - - if (pass == nil) - { - [self errorWithFormat: @"Unsupported user-password algorithm: %@", _userPasswordAlgorithm]; - return nil; - } - - if (_prependPasswordScheme) - result = [NSString stringWithFormat: @"{%@}%@", _userPasswordAlgorithm, pass]; - else - result = pass; - - return result; -} - -// -// SQL sources don't support right now all the password policy -// stuff supported by OpenLDAP (and others). If we want to support -// this for SQL sources, we'll have to implement the same -// kind of logic in this module. -// -- (BOOL) checkLogin: (NSString *) _login - password: (NSString *) _pwd - perr: (SOGoPasswordPolicyError *) _perr - expire: (int *) _expire - grace: (int *) _grace -{ - EOAdaptorChannel *channel; - EOQualifier *qualifier; - GCSChannelManager *cm; - NSException *ex; - NSMutableString *sql; - BOOL rc; - - rc = NO; - - _login = [_login stringByReplacingString: @"'" withString: @"''"]; - cm = [GCSChannelManager defaultChannelManager]; - channel = [cm acquireOpenChannelForURL: _viewURL]; - if (channel) - { - if (_loginFields) - { - NSMutableArray *qualifiers; - NSString *field; - EOQualifier *loginQualifier; - int i; - - qualifiers = [NSMutableArray arrayWithCapacity: [_loginFields count]]; - for (i = 0; i < [_loginFields count]; i++) - { - field = [_loginFields objectAtIndex: i]; - loginQualifier = [[EOKeyValueQualifier alloc] initWithKey: field - operatorSelector: EOQualifierOperatorEqual - value: _login]; - [loginQualifier autorelease]; - [qualifiers addObject: loginQualifier]; - } - qualifier = [[EOOrQualifier alloc] initWithQualifierArray: qualifiers]; - } - else - { - qualifier = [[EOKeyValueQualifier alloc] initWithKey: @"c_uid" - operatorSelector: EOQualifierOperatorEqual - value: _login]; - } - [qualifier autorelease]; - sql = [NSMutableString stringWithFormat: @"SELECT c_password" - @" FROM %@" - @" WHERE ", - [_viewURL gcsTableName]]; - if (_authenticationFilter) - { - qualifier = [[EOAndQualifier alloc] initWithQualifiers: - qualifier, - [EOQualifier qualifierWithQualifierFormat: _authenticationFilter], - nil]; - [qualifier autorelease]; - } - [qualifier _gcsAppendToString: sql]; - - ex = [channel evaluateExpressionX: sql]; - if (!ex) - { - NSDictionary *row; - NSArray *attrs; - NSString *value; - - attrs = [channel describeResults: NO]; - row = [channel fetchAttributes: attrs withZone: NULL]; - value = [row objectForKey: @"c_password"]; - - rc = [self _isPassword: _pwd equalTo: value]; - [channel cancelFetch]; - } - else - [self errorWithFormat: @"could not run SQL '%@': %@", qualifier, ex]; - - [cm releaseChannel: channel]; - } - else - [self errorWithFormat:@"failed to acquire channel for URL: %@", - [_viewURL absoluteString]]; - - return rc; -} - -/** - * Change a user's password. - * @param login the user's login name. - * @param oldPassword the previous password. - * @param newPassword the new password. - * @param perr is not used. - * @return YES if the password was successfully changed. - */ -- (BOOL) changePasswordForLogin: (NSString *) login - oldPassword: (NSString *) oldPassword - newPassword: (NSString *) newPassword - perr: (SOGoPasswordPolicyError *) perr -{ - EOAdaptorChannel *channel; - GCSChannelManager *cm; - NSException *ex; - NSString *sqlstr; - BOOL didChange; - BOOL isOldPwdOk; - - isOldPwdOk = NO; - didChange = NO; - - // Verify current password - isOldPwdOk = [self checkLogin:login password:oldPassword perr:perr expire:0 grace:0]; - - if (isOldPwdOk) - { - // Encrypt new password - NSString *encryptedPassword = [self _encryptPassword: newPassword]; - if(encryptedPassword == nil) - return NO; - - // Save new password - login = [login stringByReplacingString: @"'" withString: @"''"]; - cm = [GCSChannelManager defaultChannelManager]; - channel = [cm acquireOpenChannelForURL: _viewURL]; - if (channel) - { - sqlstr = [NSString stringWithFormat: (@"UPDATE %@" - @" SET c_password = '%@'" - @" WHERE c_uid = '%@'"), - [_viewURL gcsTableName], encryptedPassword, login]; - - ex = [channel evaluateExpressionX: sqlstr]; - if (!ex) - { - didChange = YES; - } - else - { - [self errorWithFormat: @"could not run SQL '%@': %@", sqlstr, ex]; - } - [cm releaseChannel: channel]; - } - } - - return didChange; -} - -- (NSString *) _whereClauseFromArray: (NSArray *) theArray - value: (NSString *) theValue - exact: (BOOL) theBOOL -{ - NSMutableString *s; - int i; - - s = [NSMutableString string]; - - for (i = 0; i < [theArray count]; i++) - { - if (theBOOL) - [s appendFormat: @" OR LOWER(%@) = '%@'", [theArray objectAtIndex: i], theValue]; - else - [s appendFormat: @" OR LOWER(%@) LIKE '%%%@%%'", [theArray objectAtIndex: i], theValue]; - } - - return s; -} - -- (NSDictionary *) _lookupContactEntry: (NSString *) theID - considerEmail: (BOOL) b - inDomain: (NSString *) domain -{ - NSMutableDictionary *response; - NSMutableArray *qualifiers; - NSArray *fieldNames; - EOAdaptorChannel *channel; - EOQualifier *loginQualifier, *domainQualifier, *qualifier; - GCSChannelManager *cm; - NSMutableString *sql; - NSString *value, *field; - NSException *ex; - int i; - - response = nil; - - theID = [theID stringByReplacingString: @"'" withString: @"''"]; - cm = [GCSChannelManager defaultChannelManager]; - channel = [cm acquireOpenChannelForURL: _viewURL]; - if (channel) - { - qualifiers = [NSMutableArray arrayWithCapacity: [_loginFields count] + 1]; - - // Always compare against the c_uid field - loginQualifier = [[EOKeyValueQualifier alloc] initWithKey: @"c_uid" - operatorSelector: EOQualifierOperatorEqual - value: theID]; - [loginQualifier autorelease]; - [qualifiers addObject: loginQualifier]; - - if (_loginFields) - { - for (i = 0; i < [_loginFields count]; i++) - { - field = [_loginFields objectAtIndex: i]; - if ([field caseInsensitiveCompare: @"c_uid"] != NSOrderedSame) - { - loginQualifier = [[EOKeyValueQualifier alloc] initWithKey: field - operatorSelector: EOQualifierOperatorEqual - value: theID]; - [loginQualifier autorelease]; - [qualifiers addObject: loginQualifier]; - } - } - } - - domainQualifier = nil; - if (_domainField && domain) - { - domainQualifier = [[EOKeyValueQualifier alloc] initWithKey: _domainField - operatorSelector: EOQualifierOperatorEqual - value: domain]; - [domainQualifier autorelease]; - } - - if (b) - { - // Always compare againts the mail field - loginQualifier = [[EOKeyValueQualifier alloc] initWithKey: @"mail" - operatorSelector: EOQualifierOperatorEqual - value: [theID lowercaseString]]; - [loginQualifier autorelease]; - [qualifiers addObject: loginQualifier]; - - if (_mailFields) - { - for (i = 0; i < [_mailFields count]; i++) - { - field = [_mailFields objectAtIndex: i]; - if ([field caseInsensitiveCompare: @"mail"] != NSOrderedSame - && ![_loginFields containsObject: field]) - { - loginQualifier = [[EOKeyValueQualifier alloc] initWithKey: field - operatorSelector: EOQualifierOperatorEqual - value: [theID lowercaseString]]; - [loginQualifier autorelease]; - [qualifiers addObject: loginQualifier]; - } - } - } - } - - sql = [NSMutableString stringWithFormat: @"SELECT *" - @" FROM %@" - @" WHERE ", - [_viewURL gcsTableName]]; - qualifier = [[EOOrQualifier alloc] initWithQualifierArray: qualifiers]; - if (domainQualifier) - qualifier = [[EOAndQualifier alloc] initWithQualifiers: domainQualifier, qualifier, nil]; - [qualifier _gcsAppendToString: sql]; - - ex = [channel evaluateExpressionX: sql]; - if (!ex) - { - NSMutableArray *emails; - - response = [[channel fetchAttributes: [channel describeResults: NO] - withZone: NULL] mutableCopy]; - [response autorelease]; - [channel cancelFetch]; - - /* Convert all c_ fields to obtain their ldif equivalent */ - fieldNames = [response allKeys]; - for (i = 0; i < [fieldNames count]; i++) - { - field = [fieldNames objectAtIndex: i]; - if ([field hasPrefix: @"c_"]) - [response setObject: [response objectForKey: field] - forKey: [field substringFromIndex: 2]]; - } - - // FIXME - // We have to do this here since we do not manage modules - // constraints right now over a SQL backend. - [response setObject: [NSNumber numberWithBool: YES] forKey: @"CalendarAccess"]; - [response setObject: [NSNumber numberWithBool: YES] forKey: @"MailAccess"]; - [response setObject: [NSNumber numberWithBool: YES] forKey: @"ActiveSyncAccess"]; - - // We set the domain, if any - value = nil; - if (_domain) - value = _domain; - else if (_domainField) - value = [response objectForKey: _domainField]; - if (![value isNotNull]) - value = @""; - [response setObject: value forKey: @"c_domain"]; - - // We populate all mail fields - emails = [NSMutableArray array]; - - if ([response objectForKey: @"mail"]) - [emails addObject: [response objectForKey: @"mail"]]; - - if (_mailFields && [_mailFields count] > 0) - { - NSString *s; - int i; - - for (i = 0; i < [_mailFields count]; i++) - if ((s = [response objectForKey: [_mailFields objectAtIndex: i]]) && - [[s stringByTrimmingSpaces] length] > 0) - [emails addObject: s]; - } - - [response setObject: emails forKey: @"c_emails"]; - if (_imapHostField) - { - value = [response objectForKey: _imapHostField]; - if ([value isNotNull]) - [response setObject: value forKey: @"c_imaphostname"]; - } - - if (_sieveHostField) - { - value = [response objectForKey: _sieveHostField]; - if ([value isNotNull]) - [response setObject: value forKey: @"c_sievehostname"]; - } - - // We check if the user can authenticate - if (_authenticationFilter) - { - EOQualifier *q_uid, *q_auth; - - sql = [NSMutableString stringWithFormat: @"SELECT c_uid" - @" FROM %@" - @" WHERE ", - [_viewURL gcsTableName]]; - - q_auth = [EOQualifier qualifierWithQualifierFormat: _authenticationFilter]; - - q_uid = [[EOKeyValueQualifier alloc] initWithKey: @"c_uid" - operatorSelector: EOQualifierOperatorEqual - value: theID]; - [q_uid autorelease]; - - qualifier = [[EOAndQualifier alloc] initWithQualifiers: q_uid, q_auth, nil]; - [qualifier autorelease]; - [qualifier _gcsAppendToString: sql]; - - ex = [channel evaluateExpressionX: sql]; - if (!ex) - { - NSDictionary *authResponse; - - authResponse = [channel fetchAttributes: [channel describeResults: NO] withZone: NULL]; - [response setObject: [NSNumber numberWithBool: [authResponse count] > 0] forKey: @"canAuthenticate"]; - [channel cancelFetch]; - } - else - [self errorWithFormat: @"could not run SQL '%@': %@", sql, ex]; - } - else - [response setObject: [NSNumber numberWithBool: YES] forKey: @"canAuthenticate"]; - - // We check if we should use a different login for IMAP - if (_imapLoginField) - { - if ([[response objectForKey: _imapLoginField] isNotNull]) - [response setObject: [response objectForKey: _imapLoginField] forKey: @"c_imaplogin"]; - } - - // We check if it's a resource of not - if (_kindField) - { - if ((value = [response objectForKey: _kindField]) && [value isNotNull]) - { - if ([value caseInsensitiveCompare: @"location"] == NSOrderedSame || - [value caseInsensitiveCompare: @"thing"] == NSOrderedSame || - [value caseInsensitiveCompare: @"group"] == NSOrderedSame) - { - [response setObject: [NSNumber numberWithInt: 1] - forKey: @"isResource"]; - } - } - } - - if (_multipleBookingsField) - { - if ((value = [response objectForKey: _multipleBookingsField])) - { - [response setObject: [NSNumber numberWithInt: [value intValue]] - forKey: @"numberOfSimultaneousBookings"]; - } - } - - [response setObject: self forKey: @"source"]; - } - else - [self errorWithFormat: @"could not run SQL '%@': %@", sql, ex]; - [cm releaseChannel: channel]; - } - else - [self errorWithFormat:@"failed to acquire channel for URL: %@", - [_viewURL absoluteString]]; - - return response; -} - - -- (NSDictionary *) lookupContactEntry: (NSString *) theID -{ - return [self _lookupContactEntry: theID considerEmail: NO inDomain: nil]; -} - -- (NSDictionary *) lookupContactEntryWithUIDorEmail: (NSString *) entryID - inDomain: (NSString *) domain -{ - return [self _lookupContactEntry: entryID considerEmail: YES inDomain: domain]; -} - -/* Returns an EOQualifier of the following form: - * (_domainField = domain OR _domainField = visibleDomain1 [...]) - * Should only be called on SQL sources using _domainField name. - */ -- (EOQualifier *) _visibleDomainsQualifierFromDomain: (NSString *) domain -{ - int i; - EOQualifier *qualifier, *domainQualifier; - NSArray *visibleDomains; - NSMutableArray *qualifiers; - NSString *currentDomain; - - SOGoSystemDefaults *sd; - - /* Return early if no domain or if being called on a 'static' sql source */ - if (!domain || !_domainField) - return nil; - - sd = [SOGoSystemDefaults sharedSystemDefaults]; - visibleDomains = [sd visibleDomainsForDomain: domain]; - qualifier = nil; - - domainQualifier = - [[EOKeyValueQualifier alloc] initWithKey: _domainField - operatorSelector: EOQualifierOperatorEqual - value: domain]; - [domainQualifier autorelease]; - - if ([visibleDomains count]) - { - qualifiers = [NSMutableArray arrayWithCapacity: [visibleDomains count] + 1]; - [qualifiers addObject: domainQualifier]; - for(i = 0; i < [visibleDomains count]; i++) - { - currentDomain = [visibleDomains objectAtIndex: i]; - qualifier = - [[EOKeyValueQualifier alloc] initWithKey: _domainField - operatorSelector: EOQualifierOperatorEqual - value: currentDomain]; - [qualifier autorelease]; - [qualifiers addObject: qualifier]; - } - qualifier = [[EOOrQualifier alloc] initWithQualifierArray: qualifiers]; - [qualifier autorelease]; - } - - return qualifier ? qualifier : domainQualifier; -} - - -- (NSArray *) allEntryIDsVisibleFromDomain: (NSString *) domain -{ - EOAdaptorChannel *channel; - EOQualifier *domainQualifier; - GCSChannelManager *cm; - NSException *ex; - NSMutableArray *results; - NSMutableString *sql; - - results = [NSMutableArray array]; - - cm = [GCSChannelManager defaultChannelManager]; - channel = [cm acquireOpenChannelForURL: _viewURL]; - if (channel) - { - sql = [NSMutableString stringWithFormat: @"SELECT c_uid FROM %@", - [_viewURL gcsTableName]]; - - if (_domainField) - { - if ([domain length]) - { - domainQualifier = - [self _visibleDomainsQualifierFromDomain: domain]; - if (domainQualifier) - { - [sql appendString: @" WHERE "]; - [domainQualifier _gcsAppendToString: sql]; - } - } - else - { - /* Should not happen but avoid returning the whole table - * if a domain should have been defined */ - [sql appendFormat: @" WHERE %@ is NULL", _domainField]; - } - } - - ex = [channel evaluateExpressionX: sql]; - if (!ex) - { - NSDictionary *row; - NSArray *attrs; - NSString *value; - - attrs = [channel describeResults: NO]; - - while ((row = [channel fetchAttributes: attrs withZone: NULL])) - { - value = [row objectForKey: @"c_uid"]; - if (value) - [results addObject: value]; - } - } - else - [self errorWithFormat: @"could not run SQL '%@': %@", sql, ex]; - [cm releaseChannel: channel]; - } - else - [self errorWithFormat:@"failed to acquire channel for URL: %@", - [_viewURL absoluteString]]; - - - return results; -} - -- (NSArray *) allEntryIDs -{ - return [self allEntryIDsVisibleFromDomain: nil]; -} - -- (NSArray *) fetchContactsMatching: (NSString *) filter - inDomain: (NSString *) domain -{ - EOAdaptorChannel *channel; - NSMutableArray *results; - GCSChannelManager *cm; - NSException *ex; - NSMutableString *sql; - NSString *lowerFilter; - - results = [NSMutableArray array]; - - cm = [GCSChannelManager defaultChannelManager]; - channel = [cm acquireOpenChannelForURL: _viewURL]; - if (channel) - { - lowerFilter = [filter lowercaseString]; - lowerFilter = [lowerFilter stringByReplacingString: @"'" withString: @"''"]; - - sql = [NSMutableString stringWithFormat: (@"SELECT *" - @" FROM %@" - @" WHERE" - @" (LOWER(c_cn) LIKE '%%%@%%'" - @" OR LOWER(mail) LIKE '%%%@%%'"), - [_viewURL gcsTableName], - lowerFilter, lowerFilter]; - - if (_mailFields && [_mailFields count] > 0) - { - [sql appendString: [self _whereClauseFromArray: _mailFields value: lowerFilter exact: NO]]; - } - - [sql appendString: @")"]; - - if (_domainField) - { - if ([domain length]) - { - EOQualifier *domainQualifier; - domainQualifier = - [self _visibleDomainsQualifierFromDomain: domain]; - if (domainQualifier) - { - [sql appendFormat: @" AND ("]; - [domainQualifier _gcsAppendToString: sql]; - [sql appendFormat: @")"]; - } - } - else - [sql appendFormat: @" AND %@ IS NULL", _domainField]; - } - - ex = [channel evaluateExpressionX: sql]; - if (!ex) - { - NSDictionary *row; - NSArray *attrs; - - attrs = [channel describeResults: NO]; - - while ((row = [channel fetchAttributes: attrs withZone: NULL])) - { - row = [row mutableCopy]; - [(NSMutableDictionary *) row setObject: self forKey: @"source"]; - [results addObject: row]; - [row release]; - } - } - else - [self errorWithFormat: @"could not run SQL '%@': %@", sql, ex]; - [cm releaseChannel: channel]; - } - else - [self errorWithFormat:@"failed to acquire channel for URL: %@", - [_viewURL absoluteString]]; - - return results; -} - -- (void) setSourceID: (NSString *) newSourceID -{ -} - -- (NSString *) sourceID -{ - return _sourceID; -} - -- (void) setDisplayName: (NSString *) newDisplayName -{ -} - -- (NSString *) displayName -{ - /* This method is only used when supporting user "source" addressbooks, - which is only supported by the LDAP backend for now. */ - return _sourceID; -} - -- (void) setListRequiresDot: (BOOL) newListRequiresDot -{ -} - -- (BOOL) listRequiresDot -{ - /* This method is not implemented for SQLSource. It must enable a mechanism - where using "." is not required to list the content of addressbooks. */ - return YES; -} - -/* card editing */ -- (void) setModifiers: (NSArray *) newModifiers -{ -} - -- (NSArray *) modifiers -{ - /* This method is only used when supporting card editing, - which is only supported by the LDAP backend for now. */ - return nil; -} - -- (NSException *) addContactEntry: (NSDictionary *) roLdifRecord - withID: (NSString *) aId -{ - NSString *reason; - - reason = [NSString stringWithFormat: @"method '%@' is not available" - @" for class '%@'", NSStringFromSelector (_cmd), - NSStringFromClass (isa)]; - - return [NSException exceptionWithName: @"SQLSourceIOException" - reason: reason - userInfo: nil]; -} - -- (NSException *) updateContactEntry: (NSDictionary *) roLdifRecord -{ - NSString *reason; - - reason = [NSString stringWithFormat: @"method '%@' is not available" - @" for class '%@'", NSStringFromSelector (_cmd), - NSStringFromClass (isa)]; - - return [NSException exceptionWithName: @"SQLSourceIOException" - reason: reason - userInfo: nil]; -} - -- (NSException *) removeContactEntryWithID: (NSString *) aId -{ - NSString *reason; - - reason = [NSString stringWithFormat: @"method '%@' is not available" - @" for class '%@'", NSStringFromSelector (_cmd), - NSStringFromClass (isa)]; - - return [NSException exceptionWithName: @"SQLSourceIOException" - reason: reason - userInfo: nil]; -} - -/* user addressbooks */ -- (BOOL) hasUserAddressBooks -{ - return NO; -} - -- (NSArray *) addressBookSourcesForUser: (NSString *) user -{ - return nil; -} - -- (NSException *) addAddressBookSource: (NSString *) newId - withDisplayName: (NSString *) newDisplayName - forUser: (NSString *) user -{ - NSString *reason; - - reason = [NSString stringWithFormat: @"method '%@' is not available" - @" for class '%@'", NSStringFromSelector (_cmd), - NSStringFromClass (isa)]; - - return [NSException exceptionWithName: @"SQLSourceIOException" - reason: reason - userInfo: nil]; -} - -- (NSException *) renameAddressBookSource: (NSString *) newId - withDisplayName: (NSString *) newDisplayName - forUser: (NSString *) user -{ - NSString *reason; - - reason = [NSString stringWithFormat: @"method '%@' is not available" - @" for class '%@'", NSStringFromSelector (_cmd), - NSStringFromClass (isa)]; - - return [NSException exceptionWithName: @"SQLSourceIOException" - reason: reason - userInfo: nil]; -} - -- (NSException *) removeAddressBookSource: (NSString *) newId - forUser: (NSString *) user -{ - NSString *reason; - - reason = [NSString stringWithFormat: @"method '%@' is not available" - @" for class '%@'", NSStringFromSelector (_cmd), - NSStringFromClass (isa)]; - - return [NSException exceptionWithName: @"SQLSourceIOException" - reason: reason - userInfo: nil]; -} - -@end