From cc0237bc45368239fbc985112f989068393640f5 Mon Sep 17 00:00:00 2001 From: Wolfgang Sourdeau Date: Mon, 5 Nov 2007 14:51:50 +0000 Subject: [PATCH] - external patch Monotone-Parent: ba5973b414572fb5f085f8980182645843a42958 Monotone-Revision: ecf850422f6e93462013857de0eaef9e5ed5eb02 Monotone-Author: wsourdeau@inverse.ca Monotone-Date: 2007-11-05T14:51:50 Monotone-Branch: ca.inverse.sogo --- SoObjects/Contacts/GNUmakefile | 3 +- SoObjects/Contacts/NSObject+CardDAV.h | 32 ++++ SoObjects/Contacts/NSObject+CardDAV.m | 164 ++++++++++++++++++ SoObjects/Contacts/SOGoContactFolder.h | 5 + SoObjects/Contacts/SOGoContactGCSFolder.h | 1 + SoObjects/Contacts/SOGoContactGCSFolder.m | 189 ++++----------------- SoObjects/Contacts/SOGoContactLDAPFolder.h | 1 + SoObjects/Contacts/SOGoContactLDAPFolder.m | 82 ++++++++- 8 files changed, 317 insertions(+), 160 deletions(-) create mode 100644 SoObjects/Contacts/NSObject+CardDAV.h create mode 100644 SoObjects/Contacts/NSObject+CardDAV.m diff --git a/SoObjects/Contacts/GNUmakefile b/SoObjects/Contacts/GNUmakefile index a68f8212e..37c9ad23f 100644 --- a/SoObjects/Contacts/GNUmakefile +++ b/SoObjects/Contacts/GNUmakefile @@ -1,3 +1,4 @@ + # GNUstep makefile include ../common.make @@ -7,8 +8,8 @@ BUNDLE_NAME = Contacts Contacts_PRINCIPAL_CLASS = SOGoContactsProduct Contacts_OBJC_FILES = \ + NSObject+CardDAV.m \ Product.m \ - \ SOGoContactFolders.m \ SOGoContactGCSEntry.m \ SOGoContactGCSFolder.m \ diff --git a/SoObjects/Contacts/NSObject+CardDAV.h b/SoObjects/Contacts/NSObject+CardDAV.h new file mode 100644 index 000000000..d87dff40f --- /dev/null +++ b/SoObjects/Contacts/NSObject+CardDAV.h @@ -0,0 +1,32 @@ +/* NSObject+CardDAV.h - this file is part of SOGo + * + * Copyright (C) 2007 Inverse groupe conseil + * + * Author: Ludovic Marcotte + * + * 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. + */ + +#ifndef __Contacts_NSObject_CardDAV_H__ +#define __Contacts_NSObject_CardDAV_H__ + +@interface NSObject (CardDAV) + +- (id) davAddressbookQuery: (id) queryContext; + +@end + +#endif diff --git a/SoObjects/Contacts/NSObject+CardDAV.m b/SoObjects/Contacts/NSObject+CardDAV.m new file mode 100644 index 000000000..1a1df88b0 --- /dev/null +++ b/SoObjects/Contacts/NSObject+CardDAV.m @@ -0,0 +1,164 @@ +/* NSObject+CardDAV.m - this file is part of SOGo + * + * Copyright (C) 2007 Inverse groupe conseil + * + * Author: Ludovic Marcotte + * + * 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 "SOGoContactFolder.h" +#import "SOGoContactGCSEntry.h" + +#import +#import +#import +#import +#import +#import +#import +#import + +@implementation NSObject (CardDAV) + +- (void) _appendComponentsMatchingFilters: (NSArray *) filters + toResponse: (WOResponse *) response + context: (id) context +{ + unsigned int count, max; + NSDictionary *currentFilter, *contact; + NSEnumerator *contacts; + NSString *baseURL; + id o; + + o = (id)self; + baseURL = [o baseURLInContext: context]; + + max = [filters count]; + for (count = 0; count < max; count++) + { + currentFilter = [filters objectAtIndex: count]; + contacts = [[o lookupContactsWithFilter: [[currentFilter allValues] lastObject] + sortBy: @"c_givenname" + ordering: NSOrderedDescending] + objectEnumerator]; + + while ((contact = [contacts nextObject])) + { + [o appendObject: contact + withBaseURL: baseURL + toREPORTResponse: response]; + } + } +} + +- (BOOL) _isValidFilter: (NSString *) theString +{ + if ([theString caseInsensitiveCompare: @"sn"] == NSOrderedSame) + return YES; + + if ([theString caseInsensitiveCompare: @"givenname"] == NSOrderedSame) + return YES; + + if ([theString caseInsensitiveCompare: @"mail"] == NSOrderedSame) + return YES; + + if ([theString caseInsensitiveCompare: @"telephonenumber"] == NSOrderedSame) + return YES; + + return NO; +} + +- (NSDictionary *) _parseContactFilter: (id ) filterElement +{ + NSMutableDictionary *filterData; + id parentNode; + id ranges; + + parentNode = [filterElement parentNode]; + + if ([[parentNode tagName] isEqualToString: @"filter"] && + [self _isValidFilter: [filterElement attribute: @"name"]]) + { + ranges = [filterElement getElementsByTagName: @"text-match"]; + + if ([(NSArray *)ranges count] && [(NSArray *)[[ranges objectAtIndex: 0] childNodes] count]) + { + filterData = [NSMutableDictionary new]; + [filterData autorelease]; + [filterData setObject: [[(NSArray *)[[ranges objectAtIndex: 0] childNodes] lastObject] data] + forKey: [filterElement attribute: @"name"]]; + } + } + else + filterData = nil; + + return filterData; +} + +- (NSArray *) _parseContactFilters: (id ) parentNode +{ + NSEnumerator *children; + id node; + NSMutableArray *filters; + NSDictionary *filter; + + filters = [[NSMutableArray new] autorelease]; + + children = [[parentNode getElementsByTagName: @"prop-filter"] + objectEnumerator]; + + node = [children nextObject]; + + while (node) + { + filter = [self _parseContactFilter: node]; + if (filter) + [filters addObject: filter]; + node = [children nextObject]; + } + + return filters; +} + +- (id) davAddressbookQuery: (id) queryContext +{ + WOResponse *r; + NSArray *filters; + id document; + + r = [queryContext response]; + [r setStatus: 207]; + [r setContentEncoding: NSUTF8StringEncoding]; + [r setHeader: @"text/xml; charset=\"utf-8\"" forKey: @"content-type"]; + [r setHeader: @"no-cache" forKey: @"pragma"]; + [r setHeader: @"no-cache" forKey: @"cache-control"]; + [r appendContentString:@"\r\n"]; + [r appendContentString: @"\r\n"]; + + document = [[queryContext request] contentAsDOMDocument]; + filters = [self _parseContactFilters: [document documentElement]]; + + [self _appendComponentsMatchingFilters: filters + toResponse: r + context: queryContext]; + [r appendContentString:@"\r\n"]; + + return r; +} + +@end diff --git a/SoObjects/Contacts/SOGoContactFolder.h b/SoObjects/Contacts/SOGoContactFolder.h index c1ff89685..709e4ae62 100644 --- a/SoObjects/Contacts/SOGoContactFolder.h +++ b/SoObjects/Contacts/SOGoContactFolder.h @@ -36,6 +36,7 @@ @class NSString, NSArray; @class SOGoContactObject; @class SOGoObject; +@class WOResponse; @protocol SOGoContactObject; @@ -43,6 +44,10 @@ @protocol SOGoContactFolder +- (void) appendObject: (NSDictionary *) object + withBaseURL: (NSString *) baseURL + toREPORTResponse: (WOResponse *) r; + - (NSArray *) lookupContactsWithFilter: (NSString *) filter sortBy: (NSString *) sortKey ordering: (NSComparisonResult) sortOrdering; diff --git a/SoObjects/Contacts/SOGoContactGCSFolder.h b/SoObjects/Contacts/SOGoContactGCSFolder.h index d10913d0b..9ac8fb1fe 100644 --- a/SoObjects/Contacts/SOGoContactGCSFolder.h +++ b/SoObjects/Contacts/SOGoContactGCSFolder.h @@ -25,6 +25,7 @@ #import #import "SOGoContactFolder.h" +#import "NSObject+CardDAV.h" @class NSArray; @class NSString; diff --git a/SoObjects/Contacts/SOGoContactGCSFolder.m b/SoObjects/Contacts/SOGoContactGCSFolder.m index 26e0e974c..f248f5146 100644 --- a/SoObjects/Contacts/SOGoContactGCSFolder.m +++ b/SoObjects/Contacts/SOGoContactGCSFolder.m @@ -25,16 +25,12 @@ #import #import #import -#import #import #import #import #import #import #import -#import -#import -#import #import #import "SOGoContactGCSEntry.h" @@ -71,6 +67,7 @@ isPut = NO; obj = [super lookupName:_key inContext:_ctx acquire:NO]; + if (!obj) { if ([[[_ctx request] method] isEqualToString: @"PUT"]) @@ -174,74 +171,38 @@ return newRecords; } -- (BOOL) _isValidFilter: (NSString *) theString +- (NSArray *) lookupContactsWithFilter: (NSString *) filter + sortBy: (NSString *) sortKey + ordering: (NSComparisonResult) sortOrdering { - if ([theString caseInsensitiveCompare: @"sn"] == NSOrderedSame) - return YES; + NSArray *fields, *dbRecords, *records; + EOQualifier *qualifier; + EOSortOrdering *ordering; - if ([theString caseInsensitiveCompare: @"givenname"] == NSOrderedSame) - return YES; + fields = folderListingFields; + qualifier = [self _qualifierForFilter: filter]; + dbRecords = [[self ocsFolder] fetchFields: fields + matchingQualifier: qualifier]; - if ([theString caseInsensitiveCompare: @"mail"] == NSOrderedSame) - return YES; - - if ([theString caseInsensitiveCompare: @"telephonenumber"] == NSOrderedSame) - return YES; - - return NO; -} - -- (NSDictionary *) _parseContactFilter: (id ) filterElement -{ - NSMutableDictionary *filterData; - id parentNode; - id ranges; - - parentNode = [filterElement parentNode]; - - if ([[parentNode tagName] isEqualToString: @"filter"] && - [self _isValidFilter: [filterElement attribute: @"name"]]) + if ([dbRecords count] > 0) { - ranges = [filterElement getElementsByTagName: @"text-match"]; - - if ([ranges count] && [[[ranges objectAtIndex: 0] childNodes] count]) - { - filterData = [NSMutableDictionary new]; - [filterData autorelease]; - [filterData setObject: [[[[ranges objectAtIndex: 0] childNodes] lastObject] data] - forKey: [filterElement attribute: @"name"]]; - } + records = [self _flattenedRecords: dbRecords]; + ordering + = [EOSortOrdering sortOrderingWithKey: sortKey + selector: ((sortOrdering == NSOrderedDescending) + ? EOCompareCaseInsensitiveDescending + : EOCompareCaseInsensitiveAscending)]; + records + = [records sortedArrayUsingKeyOrderArray: + [NSArray arrayWithObject: ordering]]; } else - filterData = nil; + records = nil; + // else + //[self errorWithFormat:@"(%s): fetch failed!", __PRETTY_FUNCTION__]; - return filterData; -} - -#warning filters is leaked here -- (NSArray *) _parseContactFilters: (id ) parentNode -{ - NSEnumerator *children; - id node; - NSMutableArray *filters; - NSDictionary *filter; - - filters = [NSMutableArray new]; - - children = [[parentNode getElementsByTagName: @"prop-filter"] - objectEnumerator]; - - node = [children nextObject]; - - while (node) - { - filter = [self _parseContactFilter: node]; - if (filter) - [filters addObject: filter]; - node = [children nextObject]; - } - - return filters; + [self debugWithFormat:@"fetched %i records.", [records count]]; + return records; } - (void) appendObject: (NSDictionary *) object @@ -280,99 +241,6 @@ [r appendContentString: @" \r\n"]; } -- (void) _appendComponentsMatchingFilters: (NSArray *) filters - toResponse: (WOResponse *) response -{ - unsigned int count, max; - NSDictionary *currentFilter, *contact; - NSEnumerator *contacts; - NSString *baseURL; - - baseURL = [self baseURLInContext: context]; - - max = [filters count]; - for (count = 0; count < max; count++) - { - currentFilter = [filters objectAtIndex: count]; - contacts = [[self lookupContactsWithFilter: [[currentFilter allValues] lastObject] - sortBy: @"c_givenname" - ordering: NSOrderedDescending] - objectEnumerator]; - - while ((contact = [contacts nextObject])) - { - [self appendObject: contact - withBaseURL: baseURL - toREPORTResponse: response]; - } - } -} - -- (NSArray *) lookupContactsWithFilter: (NSString *) filter - sortBy: (NSString *) sortKey - ordering: (NSComparisonResult) sortOrdering -{ - NSArray *fields, *dbRecords, *records; - EOQualifier *qualifier; - EOSortOrdering *ordering; - - fields = folderListingFields; - qualifier = [self _qualifierForFilter: filter]; - dbRecords = [[self ocsFolder] fetchFields: fields - matchingQualifier: qualifier]; - - if ([dbRecords count] > 0) - { - records = [self _flattenedRecords: dbRecords]; - ordering - = [EOSortOrdering sortOrderingWithKey: sortKey - selector: ((sortOrdering == NSOrderedDescending) - ? EOCompareCaseInsensitiveDescending - : EOCompareCaseInsensitiveAscending)]; - records - = [records sortedArrayUsingKeyOrderArray: - [NSArray arrayWithObject: ordering]]; - } - else - records = nil; -// else -// [self errorWithFormat:@"(%s): fetch failed!", __PRETTY_FUNCTION__]; - - [self debugWithFormat:@"fetched %i records.", [records count]]; - return records; -} - -- (NSArray *) davNamespaces -{ - return [NSArray arrayWithObject: @"urn:ietf:params:xml:ns:carddav"]; -} - -- (id) davAddressbookQuery: (id) queryContext -{ - WOResponse *r; - NSArray *filters; - id document; - - r = [context response]; - [r setStatus: 207]; - [r setContentEncoding: NSUTF8StringEncoding]; - [r setHeader: @"text/xml; charset=\"utf-8\"" forKey: @"content-type"]; - [r setHeader: @"no-cache" forKey: @"pragma"]; - [r setHeader: @"no-cache" forKey: @"cache-control"]; - [r appendContentString:@"\r\n"]; - [r appendContentString: @"\r\n"]; - - document = [[context request] contentAsDOMDocument]; - filters = [self _parseContactFilters: [document documentElement]]; - - [self _appendComponentsMatchingFilters: filters - toResponse: r]; - [r appendContentString:@"\r\n"]; - - return r; -} - - (NSArray *) davComplianceClassesInContext: (id)_ctx { NSMutableArray *classes; @@ -390,6 +258,11 @@ return classes; } +- (NSArray *) davNamespaces +{ + return [NSArray arrayWithObject: @"urn:ietf:params:xml:ns:carddav"]; +} + - (NSString *) groupDavResourceType { return @"vcard-collection"; diff --git a/SoObjects/Contacts/SOGoContactLDAPFolder.h b/SoObjects/Contacts/SOGoContactLDAPFolder.h index e405a3faf..7cddf7e60 100644 --- a/SoObjects/Contacts/SOGoContactLDAPFolder.h +++ b/SoObjects/Contacts/SOGoContactLDAPFolder.h @@ -24,6 +24,7 @@ #define SOGOCONTACTLDAPFOLDER_H #import "SOGoContactFolder.h" +#import "NSObject+CardDAV.h" @class NSMutableDictionary; diff --git a/SoObjects/Contacts/SOGoContactLDAPFolder.m b/SoObjects/Contacts/SOGoContactLDAPFolder.m index 4a266c4b3..13772500e 100644 --- a/SoObjects/Contacts/SOGoContactLDAPFolder.m +++ b/SoObjects/Contacts/SOGoContactLDAPFolder.m @@ -34,13 +34,56 @@ #import #import +#import #import "SOGoContactLDIFEntry.h" #import "SOGoContactLDAPFolder.h" +#import +#import +#import @class WOContext; + + @implementation SOGoContactLDAPFolder +- (void) appendObject: (NSDictionary *) object + withBaseURL: (NSString *) baseURL + toREPORTResponse: (WOResponse *) r +{ + SOGoContactLDIFEntry *component; + Class componentClass; + NSString *name, *etagLine, *contactString; + + name = [object objectForKey: @"c_name"]; + componentClass = [SOGoContactLDIFEntry class]; + + + component = [componentClass contactEntryWithName: name withLDIFEntry: object inContainer: self]; + + [r appendContentString: @" \r\n"]; + [r appendContentString: @" "]; + [r appendContentString: baseURL]; + if (![baseURL hasSuffix: @"/"]) + [r appendContentString: @"/"]; + [r appendContentString: name]; + [r appendContentString: @"\r\n"]; + + [r appendContentString: @" \r\n"]; + [r appendContentString: @" \r\n"]; + etagLine = [NSString stringWithFormat: @" %@\r\n", + [component davEntityTag]]; + [r appendContentString: etagLine]; + [r appendContentString: @" \r\n"]; + [r appendContentString: @" HTTP/1.1 200 OK\r\n"]; + [r appendContentString: @" \r\n"]; + [r appendContentString: @" "]; + contactString = [[component contentAsString] stringByEscapingXMLString]; + [r appendContentString: contactString]; + [r appendContentString: @"\r\n"]; + [r appendContentString: @" \r\n"]; +} + + (id) folderWithName: (NSString *) aName andDisplayName: (NSString *) aDisplayName inContainer: (id) aContainer @@ -99,6 +142,11 @@ return displayName; } +- (NSArray *) davNamespaces +{ + return [NSArray arrayWithObject: @"urn:ietf:params:xml:ns:carddav"]; +} + - (id) lookupName: (NSString *) objectName inContext: (WOContext *) lookupContext acquire: (BOOL) acquire @@ -106,20 +154,52 @@ id obj; NSDictionary *ldifEntry; -// NSLog (@"looking up name '%@'...", name); + //NSLog (@"looking up name '%@'...", objectName); /* first check attributes directly bound to the application */ ignoreSoObjectHunger = YES; obj = [super lookupName: objectName inContext: lookupContext acquire: NO]; ignoreSoObjectHunger = NO; + if (!obj) { ldifEntry = [ldapSource lookupContactEntry: objectName]; +#if 0 obj = ((ldifEntry) ? [SOGoContactLDIFEntry contactEntryWithName: objectName withLDIFEntry: ldifEntry inContainer: self] : [NSException exceptionWithHTTPStatus: 404]); +#else + if (ldifEntry) + obj = [SOGoContactLDIFEntry contactEntryWithName: objectName + withLDIFEntry: ldifEntry + inContainer: self]; + else + { + NSArray *davNamespaces; + NSDictionary *davInvocation; + NSString *objcMethod; + + davNamespaces = [self davNamespaces]; + if ([davNamespaces count] > 0) + { + davInvocation = [objectName asDavInvocation]; + if (davInvocation + && [davNamespaces + containsObject: [davInvocation objectForKey: @"ns"]]) + { + objcMethod = [[davInvocation objectForKey: @"method"] + davMethodToObjC]; + obj = [[SoSelectorInvocation alloc] + initWithSelectorNamed: + [NSString stringWithFormat: @"%@:", objcMethod] + addContextParameter: YES]; + [obj autorelease]; + } + } + } +#endif } return obj;