From 212ff648282d5d56b57af3623556a51605a9bd06 Mon Sep 17 00:00:00 2001 From: Euan Thoms Date: Wed, 4 Nov 2015 03:36:34 +0800 Subject: [PATCH] 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