mirror of
https://github.com/inverse-inc/sogo.git
synced 2026-02-17 07:33:57 +00:00
fix(addressbook(dav)): improve handling of addressbook-query
This commit is contained in:
@@ -25,10 +25,13 @@
|
||||
#include <EOControl/EOQualifier.h>
|
||||
|
||||
@class NSMutableString;
|
||||
@class EOAdaptor;
|
||||
|
||||
@interface EOQualifier(GCS)
|
||||
|
||||
- (void) _gcsAppendToString: (NSMutableString *) _ms;
|
||||
- (void) appendSQLToString: (NSMutableString *) _ms;
|
||||
- (void) appendSQLToString: (NSMutableString *) _ms
|
||||
withAdaptor: (EOAdaptor *) _adaptor;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -24,6 +24,10 @@
|
||||
#import <NGExtensions/NSNull+misc.h>
|
||||
#import <NGExtensions/NSObject+Logs.h>
|
||||
|
||||
#import <GDLAccess/EOAdaptor.h>
|
||||
#import <GDLAccess/EOAttribute.h>
|
||||
#import <GDLAccess/EOSQLExpression.h>
|
||||
|
||||
#import "EOQualifier+GCS.h"
|
||||
|
||||
#if (defined(__GNU_LIBOBJC__) && (__GNU_LIBOBJC__ >= 20100911)) || defined(APPLE_RUNTIME) || defined(__GNUSTEP_RUNTIME__)
|
||||
@@ -33,9 +37,9 @@
|
||||
@implementation EOQualifier(GCS)
|
||||
|
||||
- (void) _appendAndQualifier: (EOAndQualifier *) _q
|
||||
withAdaptor: (EOAdaptor *) _adaptor
|
||||
toString: (NSMutableString *) _ms
|
||||
{
|
||||
// TODO: move to EOQualifier category
|
||||
NSArray *qs;
|
||||
unsigned i, count;
|
||||
|
||||
@@ -46,14 +50,15 @@
|
||||
for (i = 0; i < count; i++) {
|
||||
if (i != 0) [_ms appendString:@" AND "];
|
||||
if (count > 1) [_ms appendString:@"("];
|
||||
[[qs objectAtIndex:i] _gcsAppendToString: _ms];
|
||||
[[qs objectAtIndex:i] appendSQLToString: _ms
|
||||
withAdaptor: _adaptor];
|
||||
if (count > 1) [_ms appendString:@")"];
|
||||
}
|
||||
}
|
||||
- (void)_appendOrQualifier: (EOAndQualifier *) _q
|
||||
toString: (NSMutableString *) _ms
|
||||
- (void) _appendOrQualifier: (EOAndQualifier *) _q
|
||||
withAdaptor: (EOAdaptor *) _adaptor
|
||||
toString: (NSMutableString *) _ms
|
||||
{
|
||||
// TODO: move to EOQualifier category
|
||||
NSArray *qs;
|
||||
unsigned i, count;
|
||||
|
||||
@@ -64,23 +69,28 @@
|
||||
for (i = 0; i < count; i++) {
|
||||
if (i != 0) [_ms appendString:@" OR "];
|
||||
if (count > 1) [_ms appendString:@"("];
|
||||
[[qs objectAtIndex:i] _gcsAppendToString: _ms];
|
||||
[[qs objectAtIndex:i] appendSQLToString: _ms
|
||||
withAdaptor: _adaptor];
|
||||
if (count > 1) [_ms appendString:@")"];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_appendNotQualifier: (EONotQualifier *)_q
|
||||
toString:(NSMutableString *) _ms
|
||||
- (void) _appendNotQualifier: (EONotQualifier *) _q
|
||||
withAdaptor: (EOAdaptor *) _adaptor
|
||||
toString:(NSMutableString *) _ms
|
||||
{
|
||||
[_ms appendString:@" NOT ("];
|
||||
[[_q qualifier] _gcsAppendToString: _ms];
|
||||
[[_q qualifier] appendSQLToString: _ms
|
||||
withAdaptor: _adaptor];
|
||||
[_ms appendString:@")"];
|
||||
}
|
||||
|
||||
- (void) _appendKeyValueQualifier: (EOKeyValueQualifier *) _q
|
||||
withAdaptor: (EOAdaptor *) _adaptor
|
||||
toString: (NSMutableString *) _ms
|
||||
{
|
||||
id val;
|
||||
EOAttribute *attribute;
|
||||
NSString *qKey, *qOperator, *qValue, *qFormat;
|
||||
BOOL isCI;
|
||||
|
||||
@@ -119,9 +129,33 @@
|
||||
qValue = [val stringValue];
|
||||
else if ([val isKindOfClass: [NSString class]]) {
|
||||
if ([(EOKeyValueQualifier *)self formatted])
|
||||
qValue = val;
|
||||
{
|
||||
qValue = val;
|
||||
}
|
||||
else
|
||||
qValue = [NSString stringWithFormat: @"'%@'", val];
|
||||
{
|
||||
if (_adaptor)
|
||||
{
|
||||
// Assume qualifier applies to a varchar column type
|
||||
attribute = [EOAttribute new];
|
||||
[attribute setExternalType: @"varchar"];
|
||||
[attribute autorelease];
|
||||
|
||||
if (sel_isEqual([_q selector], EOQualifierOperatorLike) ||
|
||||
sel_isEqual([_q selector], EOQualifierOperatorCaseInsensitiveLike))
|
||||
{
|
||||
qValue = [[_adaptor expressionClass] sqlPatternFromShellPattern: val];
|
||||
qValue = [_adaptor formatValue: qValue
|
||||
forAttribute: attribute];
|
||||
}
|
||||
else
|
||||
qValue = [_adaptor formatValue: val
|
||||
forAttribute: attribute];
|
||||
}
|
||||
else
|
||||
// No adaptor provided, don't parse value
|
||||
qValue = [NSString stringWithFormat: @"'%@'", val];
|
||||
}
|
||||
}
|
||||
else {
|
||||
qValue = @"NULL";
|
||||
@@ -155,29 +189,43 @@
|
||||
}
|
||||
|
||||
- (void) _appendQualifier: (EOQualifier *) _q
|
||||
withAdaptor: (EOAdaptor *) _adaptor
|
||||
toString: (NSMutableString *) _ms
|
||||
{
|
||||
if (_q == nil) return;
|
||||
|
||||
if ([_q isKindOfClass: [EOAndQualifier class]])
|
||||
[self _appendAndQualifier: (id)_q
|
||||
withAdaptor: _adaptor
|
||||
toString: _ms];
|
||||
else if ([_q isKindOfClass: [EOOrQualifier class]])
|
||||
[self _appendOrQualifier: (id)_q
|
||||
toString:_ms];
|
||||
withAdaptor: _adaptor
|
||||
toString: _ms];
|
||||
else if ([_q isKindOfClass: [EOKeyValueQualifier class]])
|
||||
[self _appendKeyValueQualifier: (id)_q
|
||||
toString:_ms];
|
||||
withAdaptor: _adaptor
|
||||
toString: _ms];
|
||||
else if ([_q isKindOfClass: [EONotQualifier class]])
|
||||
[self _appendNotQualifier: (id)_q
|
||||
toString:_ms];
|
||||
withAdaptor: (EOAdaptor *) _adaptor
|
||||
toString: _ms];
|
||||
else
|
||||
[self errorWithFormat:@"unknown qualifier: %@", _q];
|
||||
}
|
||||
|
||||
- (void) _gcsAppendToString: (NSMutableString *) _ms
|
||||
- (void) appendSQLToString: (NSMutableString *) _ms
|
||||
{
|
||||
[self _appendQualifier: self
|
||||
withAdaptor: nil
|
||||
toString: _ms];
|
||||
}
|
||||
|
||||
- (void) appendSQLToString: (NSMutableString *) _ms
|
||||
withAdaptor: (EOAdaptor *) _adaptor
|
||||
{
|
||||
[self _appendQualifier: self
|
||||
withAdaptor: _adaptor
|
||||
toString: _ms];
|
||||
}
|
||||
|
||||
|
||||
@@ -159,10 +159,6 @@
|
||||
/* helpers */
|
||||
|
||||
- (EOAttribute *) _attributeForColumn: (NSString *) _field;
|
||||
- (void) _findQualifiers: (id) qualifier
|
||||
withAdaptor: (EOAdaptor *) adaptor;
|
||||
- (void) _formatQualifierValue: (EOKeyValueQualifier *) qualifier
|
||||
withAdaptor: (EOAdaptor *) adaptor;
|
||||
@end
|
||||
|
||||
#endif /* __GDLContentStore_GCSFolder_H__ */
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
Copyright (C) 2004-2007 SKYRIX Software AG
|
||||
Copyright (C) 2007 Helge Hess
|
||||
Copyright (c) 2008-2019 Inverse inc.
|
||||
Copyright (c) 2008-2022 Inverse inc.
|
||||
|
||||
This file is part of SOGo.
|
||||
|
||||
@@ -26,9 +26,11 @@
|
||||
|
||||
#import <EOControl/EOFetchSpecification.h>
|
||||
#import <EOControl/EOSortOrdering.h>
|
||||
#import <EOControl/EOQualifier.h>
|
||||
|
||||
#import <GDLAccess/EOEntity.h>
|
||||
#import <GDLAccess/EOAttribute.h>
|
||||
#import <GDLAccess/EOSQLExpression.h>
|
||||
#import <GDLAccess/EOSQLQualifier.h>
|
||||
#import <GDLAccess/EOAdaptor.h>
|
||||
#import <GDLAccess/EOAdaptorContext.h>
|
||||
@@ -401,19 +403,9 @@ static GCSStringFormatter *stringFormatter = nil;
|
||||
adaptor = [adaptorCtx adaptor];
|
||||
}
|
||||
|
||||
if ([qualifier isKindOfClass: [EOAndQualifier class]])
|
||||
[self _findQualifiers: (id)qualifier withAdaptor: adaptor];
|
||||
else if ([qualifier isKindOfClass: [EOOrQualifier class]])
|
||||
[self _findQualifiers: (id)qualifier withAdaptor: adaptor];
|
||||
else if ([qualifier isKindOfClass: [EOKeyValueQualifier class]])
|
||||
[self _formatQualifierValue: (EOKeyValueQualifier *)qualifier withAdaptor: adaptor];
|
||||
else if ([qualifier isKindOfClass: [EONotQualifier class]])
|
||||
[self _formatQualifierValue: (EOKeyValueQualifier *)[(id)qualifier qualifier] withAdaptor: adaptor];
|
||||
else
|
||||
[self errorWithFormat:@"unknown qualifier: %@", qualifier];
|
||||
|
||||
ms = [NSMutableString stringWithCapacity:32];
|
||||
[qualifier _gcsAppendToString: ms];
|
||||
[qualifier appendSQLToString: ms
|
||||
withAdaptor: adaptor];
|
||||
}
|
||||
else
|
||||
ms = nil;
|
||||
@@ -421,51 +413,6 @@ static GCSStringFormatter *stringFormatter = nil;
|
||||
return ms;
|
||||
}
|
||||
|
||||
- (void) _findQualifiers: (id) qualifier
|
||||
withAdaptor: (EOAdaptor *) adaptor
|
||||
{
|
||||
NSArray *qs;
|
||||
unsigned i, count;
|
||||
|
||||
if (qualifier == nil) return;
|
||||
|
||||
qs = [qualifier qualifiers];
|
||||
if ((count = [qs count]) == 0)
|
||||
return;
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
id q = [qs objectAtIndex: i];
|
||||
if ([q isKindOfClass: [EOAndQualifier class]])
|
||||
[self _findQualifiers: q withAdaptor: adaptor];
|
||||
else if ([q isKindOfClass:[EOOrQualifier class]])
|
||||
[self _findQualifiers: q withAdaptor: adaptor];
|
||||
else if ([q isKindOfClass:[EOKeyValueQualifier class]])
|
||||
[self _formatQualifierValue: (EOKeyValueQualifier *)q withAdaptor: adaptor];
|
||||
else if ([q isKindOfClass:[EONotQualifier class]])
|
||||
[self _formatQualifierValue: (EOKeyValueQualifier *)[q qualifier] withAdaptor: adaptor];
|
||||
else
|
||||
[self errorWithFormat:@"unknown qualifier: %@", q];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) _formatQualifierValue: (EOKeyValueQualifier *) qualifier
|
||||
withAdaptor: (EOAdaptor *) adaptor
|
||||
{
|
||||
NSString *field;
|
||||
EOAttribute *attribute;
|
||||
NSString *formattedValue;
|
||||
|
||||
field = [qualifier key];
|
||||
attribute = [self _attributeForColumn: field];
|
||||
if (attribute && [[qualifier value] isNotNull])
|
||||
{
|
||||
formattedValue = [adaptor formatValue: [qualifier value]
|
||||
forAttribute: attribute];
|
||||
[qualifier setValue: formattedValue];
|
||||
[qualifier setFormatted: YES];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)_sqlForSortOrderings:(NSArray *)_so {
|
||||
NSMutableString *sql;
|
||||
unsigned i, count;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2006-2017 Inverse inc.
|
||||
Copyright (C) 2006-2022 Inverse inc.
|
||||
|
||||
This file is part of SOGo.
|
||||
|
||||
@@ -34,6 +34,8 @@
|
||||
|
||||
#import <SOGo/SOGoFolder.h>
|
||||
|
||||
@class EOQualifier;
|
||||
@class EOSortOrdering;
|
||||
@class NSArray;
|
||||
@class NSDictionary;
|
||||
@class NSString;
|
||||
@@ -46,8 +48,26 @@
|
||||
sortBy: (NSString *) sortKey
|
||||
ordering: (NSComparisonResult) sortOrdering
|
||||
inDomain: (NSString *) domain;
|
||||
- (NSArray *) lookupContactsWithQualifier: (EOQualifier *) qualifier
|
||||
andSortOrdering: (EOSortOrdering *) ordering
|
||||
inDomain: (NSString *) domain;
|
||||
- (NSDictionary *) lookupContactWithName: (NSString *) aName;
|
||||
|
||||
/**
|
||||
Map a vCard property to a source field name.
|
||||
|
||||
Possible vCard properties are:
|
||||
|
||||
- EMAIL
|
||||
- FN
|
||||
- N
|
||||
- ORG
|
||||
- ADR
|
||||
- TEL
|
||||
*/
|
||||
- (void) addVCardProperty: (NSString *) property
|
||||
toCriteria: (NSMutableArray *) criteria;
|
||||
|
||||
@end
|
||||
|
||||
#endif /* __Contacts_SOGoContactFolder_H__ */
|
||||
|
||||
@@ -80,6 +80,19 @@ static NSArray *folderListingFields = nil;
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (NSArray *) nameFields
|
||||
{
|
||||
static NSArray *nameFields = nil;
|
||||
|
||||
if (!nameFields)
|
||||
{
|
||||
nameFields = [NSArray arrayWithObjects: @"c_sn", @"c_givenname", @"c_cn", nil];
|
||||
[nameFields retain];
|
||||
}
|
||||
|
||||
return nameFields;
|
||||
}
|
||||
|
||||
- (NSArray *) searchFields
|
||||
{
|
||||
static NSArray *searchFields = nil;
|
||||
@@ -438,6 +451,61 @@ static NSArray *folderListingFields = nil;
|
||||
return records;
|
||||
}
|
||||
|
||||
- (NSArray *) lookupContactsWithQualifier: (EOQualifier *) qualifier
|
||||
andSortOrdering: (EOSortOrdering *) ordering
|
||||
inDomain: (NSString *) domain
|
||||
{
|
||||
NSArray *dbRecords, *records;
|
||||
EOFetchSpecification *spec;
|
||||
|
||||
spec = [EOFetchSpecification fetchSpecificationWithEntityName: [[self ocsFolder] folderName]
|
||||
qualifier: qualifier
|
||||
sortOrderings: [NSArray arrayWithObject: ordering]];
|
||||
|
||||
dbRecords = [[self ocsFolder] fetchFields: folderListingFields
|
||||
fetchSpecification: spec
|
||||
ignoreDeleted: YES];
|
||||
|
||||
if ([dbRecords count] > 0)
|
||||
records = [self _flattenedRecords: dbRecords];
|
||||
else
|
||||
records = dbRecords;
|
||||
|
||||
[self debugWithFormat:@"fetched %i records.", [records count]];
|
||||
return records;
|
||||
}
|
||||
|
||||
- (void) addVCardProperty: (NSString *) property
|
||||
toCriteria: (NSMutableArray *) criteria
|
||||
{
|
||||
static NSDictionary *vCardSQLFieldsTable = nil;
|
||||
NSEnumerator *fields;
|
||||
id field;
|
||||
|
||||
if (!vCardSQLFieldsTable)
|
||||
vCardSQLFieldsTable = [[NSDictionary alloc] initWithObjectsAndKeys:
|
||||
[self nameFields], @"fn",
|
||||
[self nameFields], @"n",
|
||||
@"c_mail", @"email",
|
||||
@"c_telephonenumber", @"tel",
|
||||
@"c_o", @"org",
|
||||
@"c_l", @"adr",
|
||||
nil];
|
||||
|
||||
field = [vCardSQLFieldsTable objectForKey: property];
|
||||
if (field)
|
||||
{
|
||||
if ([field isKindOfClass: [NSArray class]])
|
||||
{
|
||||
fields = [(NSArray *)field objectEnumerator];
|
||||
while ((field = [fields nextObject]))
|
||||
[criteria addObjectUniquely: field];
|
||||
}
|
||||
else
|
||||
[criteria addObjectUniquely: field];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSDictionary *) davSQLFieldsTable
|
||||
{
|
||||
static NSMutableDictionary *davSQLFieldsTable = nil;
|
||||
@@ -470,6 +538,10 @@ static NSArray *folderListingFields = nil;
|
||||
return resourceType;
|
||||
}
|
||||
|
||||
/**
|
||||
CARDDAV:addressbook-multiget Report
|
||||
https://datatracker.ietf.org/doc/html/rfc6352#section-8.6
|
||||
*/
|
||||
- (id) davAddressbookMultiget: (id) queryContext
|
||||
{
|
||||
return [self performMultigetInContext: queryContext
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#import <NGObjWeb/WOContext+SoObjects.h>
|
||||
#import <NGExtensions/NSObject+Logs.h>
|
||||
#import <NGExtensions/NSString+misc.h>
|
||||
#import <EOControl/EOQualifier.h>
|
||||
#import <EOControl/EOSortOrdering.h>
|
||||
#import <SaxObjC/XMLNamespaces.h>
|
||||
|
||||
@@ -451,6 +452,33 @@
|
||||
return result;
|
||||
}
|
||||
|
||||
- (NSArray *) lookupContactsWithQualifier: (EOQualifier *) qualifier
|
||||
andSortOrdering: (EOSortOrdering *) ordering
|
||||
inDomain: (NSString *) domain
|
||||
{
|
||||
NSArray *records;
|
||||
|
||||
records = nil;
|
||||
|
||||
if ([qualifier count] > 0 || ![source listRequiresDot])
|
||||
{
|
||||
records = [source lookupContactsWithQualifier: qualifier
|
||||
andSortOrdering: ordering
|
||||
inDomain: domain];
|
||||
records = [self _flattenedRecords: records];
|
||||
}
|
||||
|
||||
return records;
|
||||
}
|
||||
|
||||
- (void) addVCardProperty: (NSString *) property
|
||||
toCriteria: (NSMutableArray *) criteria
|
||||
{
|
||||
[source addVCardProperty: property
|
||||
toCriteria: criteria];
|
||||
}
|
||||
|
||||
|
||||
- (NSString *) _deduceObjectNameFromURL: (NSString *) url
|
||||
fromBaseURL: (NSString *) baseURL
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* NSObject+CardDAV.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2007-2015 Inverse inc.
|
||||
* Copyright (C) 2007-2022 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
|
||||
@@ -26,20 +26,24 @@
|
||||
#import <NGExtensions/NSObject+Logs.h>
|
||||
#import <NGExtensions/NSString+misc.h>
|
||||
#import <DOM/DOMNode.h>
|
||||
#import <EOControl/EOQualifier.h>
|
||||
#import <EOControl/EOSortOrdering.h>
|
||||
#import <SaxObjC/SaxObjC.h>
|
||||
|
||||
#import <SOGo/NSString+Utilities.h>
|
||||
#import <SOGo/SOGoUser.h>
|
||||
#import <SOGo/WOResponse+SOGo.h>
|
||||
#import <SOGo/DOMNode+SOGo.h>
|
||||
|
||||
#import "SOGoContactFolder.h"
|
||||
#import "SOGoContactGCSEntry.h"
|
||||
|
||||
@implementation SOGoFolder (CardDAV)
|
||||
|
||||
- (void) _appendObject: (NSDictionary *) object
|
||||
withBaseURL: (NSString *) baseURL
|
||||
toREPORTResponse: (WOResponse *) r
|
||||
- (void) _appendProperties: (NSArray *) properties
|
||||
forObject: (NSDictionary *) object
|
||||
withBaseURL: (NSString *) baseURL
|
||||
toREPORTResponse: (WOResponse *) r
|
||||
{
|
||||
id component;
|
||||
NSString *name, *etagLine, *contactString;
|
||||
@@ -47,14 +51,14 @@
|
||||
name = [object objectForKey: @"c_name"];
|
||||
if ([name length])
|
||||
{
|
||||
contactString = nil;
|
||||
component = [self lookupName: name inContext: context acquire: NO];
|
||||
if ([component isKindOfClass: [NSException class]])
|
||||
{
|
||||
[self logWithFormat: @"Object with name '%@' not found. You likely have a LDAP configuration issue.", name];
|
||||
[self logWithFormat: @"Object with name '%@' not found.", name];
|
||||
return;
|
||||
}
|
||||
|
||||
#warning we provide both "address-data" and "addressbook-data" for compatibility reasons, we should actually check which one has been queried
|
||||
[r appendContentString: @"<D:response>"
|
||||
@"<D:href>"];
|
||||
[r appendContentString: baseURL];
|
||||
@@ -64,53 +68,75 @@
|
||||
[r appendContentString: @"</D:href>"
|
||||
@"<D:propstat>"
|
||||
@"<D:prop>"];
|
||||
etagLine = [NSString stringWithFormat: @"<D:getetag>%@</D:getetag>",
|
||||
[component davEntityTag]];
|
||||
[r appendContentString: etagLine];
|
||||
[r appendContentString: @"<C:address-data>"];
|
||||
contactString = [[component contentAsString] safeStringByEscapingXMLString];
|
||||
[r appendContentString: contactString];
|
||||
[r appendContentString: @"</C:address-data>"
|
||||
@"<C:addressbook-data>"];
|
||||
[r appendContentString: contactString];
|
||||
[r appendContentString: @"</C:addressbook-data>"
|
||||
@"</D:prop>"
|
||||
@"<D:status>HTTP/1.1 200 OK</D:status>"
|
||||
@"</D:propstat>"
|
||||
@"</D:response>"];
|
||||
|
||||
|
||||
if ([properties containsObject: @"{DAV:}getetag"])
|
||||
{
|
||||
etagLine = [NSString stringWithFormat: @"<D:getetag>%@</D:getetag>",
|
||||
[component davEntityTag]];
|
||||
[r appendContentString: etagLine];
|
||||
}
|
||||
|
||||
if ([properties containsObject: @"{urn:ietf:params:xml:ns:carddav}address-data"])
|
||||
{
|
||||
[r appendContentString: @"<C:address-data>"];
|
||||
contactString = [[component contentAsString] safeStringByEscapingXMLString];
|
||||
[r appendContentString: contactString];
|
||||
[r appendContentString: @"</C:address-data>"];
|
||||
}
|
||||
|
||||
if ([properties containsObject: @"{urn:ietf:params:xml:ns:carddav}addressbook-data"])
|
||||
{
|
||||
[r appendContentString: @"<C:addressbook-data>"];
|
||||
if (!contactString)
|
||||
contactString = [[component contentAsString] safeStringByEscapingXMLString];
|
||||
[r appendContentString: contactString];
|
||||
[r appendContentString: @"</C:addressbook-data>"];
|
||||
}
|
||||
|
||||
[r appendContentString: @"</D:prop>"
|
||||
@"<D:status>HTTP/1.1 200 OK</D:status>"
|
||||
@"</D:propstat>"
|
||||
@"</D:response>"];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) _appendComponentsMatchingFilters: (NSArray *) filters
|
||||
toResponse: (WOResponse *) response
|
||||
context: (id) localContext
|
||||
- (void) _appendComponentsProperties: (NSArray *) properties
|
||||
matchingQualifier: (EOQualifier *) qualifier
|
||||
toResponse: (WOResponse *) response
|
||||
context: (id) localContext
|
||||
{
|
||||
unsigned int count,i , max;
|
||||
EOSortOrdering *sort;
|
||||
NSAutoreleasePool *pool;
|
||||
NSDictionary *currentFilter, *contact;
|
||||
NSDictionary *contact;
|
||||
NSMutableArray *names;
|
||||
NSEnumerator *contacts;
|
||||
NSString *baseURL, *domain;
|
||||
NSString *baseURL, *domain, *name;
|
||||
unsigned int i;
|
||||
|
||||
baseURL = [self baseURLInContext: localContext];
|
||||
domain = [[localContext activeUser] domain];
|
||||
sort = [EOSortOrdering sortOrderingWithKey: @"c_cn"
|
||||
selector: EOCompareCaseInsensitiveAscending];
|
||||
names = [NSMutableArray array];
|
||||
|
||||
max = [filters count];
|
||||
for (count = 0; count < max; count++)
|
||||
contacts = [[(id<SOGoContactFolder>)self lookupContactsWithQualifier: qualifier
|
||||
andSortOrdering: sort
|
||||
inDomain: domain] objectEnumerator];
|
||||
|
||||
i = 0;
|
||||
pool = [[NSAutoreleasePool alloc] init];
|
||||
while ((contact = [contacts nextObject]))
|
||||
{
|
||||
currentFilter = [filters objectAtIndex: count];
|
||||
contacts =
|
||||
[[(id<SOGoContactFolder>)self lookupContactsWithFilter: [[currentFilter allValues] lastObject]
|
||||
onCriteria: nil
|
||||
sortBy: @"c_givenname"
|
||||
ordering: NSOrderedDescending
|
||||
inDomain: domain] objectEnumerator];
|
||||
|
||||
pool = [[NSAutoreleasePool alloc] init];
|
||||
i = 0;
|
||||
while ((contact = [contacts nextObject]))
|
||||
// Don't append suspected duplicates
|
||||
name = [contact objectForKey: @"c_name"]; // primary key of contacts
|
||||
if (![names containsObject: name])
|
||||
{
|
||||
[self _appendObject: contact withBaseURL: baseURL
|
||||
toREPORTResponse: response];
|
||||
[self _appendProperties: properties
|
||||
forObject: contact
|
||||
withBaseURL: baseURL
|
||||
toREPORTResponse: response];
|
||||
[names addObject: name];
|
||||
if (i % 10 == 0)
|
||||
{
|
||||
RELEASE(pool);
|
||||
@@ -118,97 +144,170 @@
|
||||
}
|
||||
i++;
|
||||
}
|
||||
RELEASE(pool);
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL) _isValidFilter: (NSString *) theString
|
||||
{
|
||||
NSString *newString;
|
||||
|
||||
newString = [theString lowercaseString];
|
||||
|
||||
return ([newString isEqualToString: @"sn"]
|
||||
|| [newString isEqualToString: @"givenname"]
|
||||
|| [newString isEqualToString: @"email"]
|
||||
|| [newString isEqualToString: @"mail"]
|
||||
|| [newString isEqualToString: @"telephonenumber"]);
|
||||
}
|
||||
|
||||
- (NSDictionary *) _parseContactFilter: (id <DOMElement>) filterElement
|
||||
{
|
||||
NSMutableDictionary *filterData;
|
||||
id <DOMNode> parentNode;
|
||||
id <DOMNodeList> ranges;
|
||||
|
||||
filterData = nil;
|
||||
|
||||
parentNode = [filterElement parentNode];
|
||||
|
||||
if ([[(id)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 dictionary];
|
||||
[filterData setObject: [(NGDOMNode *)[ranges objectAtIndex: 0] textValue]
|
||||
forKey: [filterElement attribute: @"name"]];
|
||||
}
|
||||
}
|
||||
|
||||
return filterData;
|
||||
}
|
||||
|
||||
- (NSArray *) _parseContactFilters: (id <DOMElement>) parentNode
|
||||
{
|
||||
NSEnumerator *children;
|
||||
id <DOMElement> node;
|
||||
NSMutableArray *filters;
|
||||
NSDictionary *filter;
|
||||
|
||||
filters = [NSMutableArray array];
|
||||
|
||||
children = [(NSArray *)[parentNode getElementsByTagName: @"prop-filter"]
|
||||
objectEnumerator];
|
||||
while ((node = [children nextObject]))
|
||||
{
|
||||
filter = [self _parseContactFilter: node];
|
||||
if (filter)
|
||||
[filters addObject: filter];
|
||||
}
|
||||
|
||||
// If no filters are provided, we return everything.
|
||||
if (![filters count])
|
||||
{
|
||||
[filters addObject: [NSDictionary dictionaryWithObject: @"." forKey: @"email"]];
|
||||
[filters addObject: [NSDictionary dictionaryWithObject: @"%" forKey: @"name"]];
|
||||
}
|
||||
|
||||
return filters;
|
||||
RELEASE(pool);
|
||||
}
|
||||
|
||||
/**
|
||||
Validate the prop-filter name of the addressbook-query. Must match the supported vCard
|
||||
properties of all SOGoContactFolder classes.
|
||||
@see [SOGoContactFolder addVCardProperty:toCriteria:]
|
||||
@see [SOGoContactGCSFolder addVCardProperty:toCriteria:]
|
||||
@see [LDAPSource addVCardProperty:toCriteria:]
|
||||
@see [SQLSource addVCardProperty:toCriteria:]
|
||||
*/
|
||||
- (BOOL) _isValidFilter: (NSString *) theString
|
||||
{
|
||||
NSString *newString;
|
||||
BOOL isValid;
|
||||
|
||||
newString = [theString lowercaseString];
|
||||
|
||||
isValid = ([newString isEqualToString: @"fn"]
|
||||
|| [newString isEqualToString: @"n"]
|
||||
|| [newString isEqualToString: @"email"]
|
||||
|| [newString isEqualToString: @"tel"]
|
||||
|| [newString isEqualToString: @"org"]
|
||||
|| [newString isEqualToString: @"adr"]);
|
||||
|
||||
if (!isValid)
|
||||
[self warnWithFormat: @"Unsupported prop-filter name '%@'", theString];
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
- (EOQualifier *) _parseContactFilter: (id <DOMElement>) filterElement // a prop-filter element
|
||||
{
|
||||
NSMutableArray *qualifiers;
|
||||
NSMutableArray *criteria;
|
||||
NSString *name, *test;
|
||||
NGDOMElement *match;
|
||||
EOQualifier *qualifier;
|
||||
id <DOMNode> parentNode;
|
||||
id <DOMNodeList> ranges;
|
||||
unsigned int i;
|
||||
|
||||
qualifier = nil;
|
||||
|
||||
parentNode = [filterElement parentNode];
|
||||
name = [[filterElement attribute: @"name"] lowercaseString];
|
||||
|
||||
if ([[(id)parentNode tagName] isEqualToString: @"filter"]
|
||||
&& [self _isValidFilter: name])
|
||||
{
|
||||
qualifiers = [NSMutableArray array];
|
||||
criteria = [NSMutableArray array];
|
||||
test = [[filterElement attribute: @"test"] lowercaseString];
|
||||
ranges = [filterElement getElementsByTagName: @"text-match"];
|
||||
|
||||
[(id<SOGoContactFolder>)self addVCardProperty: name
|
||||
toCriteria: criteria];
|
||||
|
||||
for (i = 0; i < [ranges length]; i++)
|
||||
{
|
||||
match = (NGDOMElement *)[ranges objectAtIndex: i];
|
||||
if ([(NSArray *)[match childNodes] count])
|
||||
{
|
||||
SEL currentOperator;
|
||||
EOQualifier *currentQualifier;
|
||||
NSString *currentMatchType, *currentMatch;
|
||||
|
||||
currentMatch = [match textValue];
|
||||
currentMatchType = [[match attribute: @"match-type"] lowercaseString];
|
||||
if ([currentMatchType isEqualToString: @"equals"])
|
||||
currentOperator = EOQualifierOperatorEqual;
|
||||
else // contains, starts-with, ends-with
|
||||
{
|
||||
currentOperator = EOQualifierOperatorCaseInsensitiveLike;
|
||||
currentMatch = [NSString stringWithFormat: @"*%@*", currentMatch];
|
||||
}
|
||||
|
||||
currentQualifier = [[EOKeyValueQualifier alloc] initWithKey: [criteria objectAtIndex: 0]
|
||||
operatorSelector: currentOperator
|
||||
value: currentMatch];
|
||||
[currentQualifier autorelease];
|
||||
[qualifiers addObject: currentQualifier];
|
||||
}
|
||||
}
|
||||
|
||||
if ([qualifiers count] > 1)
|
||||
{
|
||||
if ([test isEqualToString: @"allof"])
|
||||
qualifier = [[EOAndQualifier alloc] initWithQualifierArray: qualifiers];
|
||||
else // anyof
|
||||
qualifier = [[EOOrQualifier alloc] initWithQualifierArray: qualifiers];
|
||||
[qualifier autorelease];
|
||||
}
|
||||
else if ([qualifiers count])
|
||||
qualifier = [qualifiers objectAtIndex: 0];
|
||||
}
|
||||
|
||||
return qualifier;
|
||||
}
|
||||
|
||||
- (EOQualifier *) _parseContactFilters: (id <DOMElement>) parentNode
|
||||
{
|
||||
EOQualifier *qualifier, *currentQualifier;
|
||||
NSEnumerator *children;
|
||||
id <DOMElement> filterElement, node;
|
||||
NSMutableArray *qualifiers;
|
||||
NSString *test;
|
||||
|
||||
qualifier = nil;
|
||||
filterElement = [(NGDOMNodeWithChildren *) parentNode firstElementWithTag: @"filter"
|
||||
inNamespace: @"urn:ietf:params:xml:ns:carddav"];
|
||||
|
||||
if (filterElement)
|
||||
{
|
||||
qualifiers = [NSMutableArray array];
|
||||
test = [[filterElement attribute: @"test"] lowercaseString];
|
||||
children = [(NSArray *)[parentNode getElementsByTagName: @"prop-filter"] objectEnumerator];
|
||||
while ((node = [children nextObject]))
|
||||
{
|
||||
currentQualifier = [self _parseContactFilter: node];
|
||||
if (currentQualifier)
|
||||
[qualifiers addObject: currentQualifier];
|
||||
}
|
||||
|
||||
if ([qualifiers count] > 1)
|
||||
{
|
||||
if ([test isEqualToString: @"allof"])
|
||||
qualifier = [[EOAndQualifier alloc] initWithQualifierArray: qualifiers];
|
||||
else
|
||||
qualifier = [[EOOrQualifier alloc] initWithQualifierArray: qualifiers];
|
||||
[qualifier autorelease];
|
||||
}
|
||||
else if ([qualifiers count])
|
||||
qualifier = [qualifiers objectAtIndex: 0];
|
||||
}
|
||||
|
||||
return qualifier;
|
||||
}
|
||||
|
||||
/**
|
||||
CARDDAV:addressbook-query Report
|
||||
https://datatracker.ietf.org/doc/html/rfc6352#section-8.6
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<C:addressbook-query xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:carddav">
|
||||
<D:prop>
|
||||
<D:getetag/>
|
||||
<C:address-data/>
|
||||
</D:prop>
|
||||
<C:filter>
|
||||
<C:prop-filter name="mail">
|
||||
<C:text-match collation="i;unicasemap" match-type="starts-with">foo</C:text-match>
|
||||
<C:filter test="anyof">
|
||||
<C:prop-filter name="EMAIL" test="allof">
|
||||
<C:text-match collation="i;unicode-casemap" match-type="starts-with">foo</C:text-match>
|
||||
</C:prop-filter>
|
||||
</C:filter>
|
||||
</C:addressbook-query>
|
||||
*/
|
||||
- (id) davAddressbookQuery: (id) queryContext
|
||||
{
|
||||
EOQualifier *qualifier;
|
||||
WOResponse *r;
|
||||
NSArray *filters;
|
||||
NSArray *properties;
|
||||
id <DOMDocument> document;
|
||||
id <DOMElement> documentElement, propElement;
|
||||
|
||||
r = [queryContext response];
|
||||
[r prepareDAVResponse];
|
||||
@@ -216,11 +315,16 @@
|
||||
@" xmlns:C=\"urn:ietf:params:xml:ns:carddav\">"];
|
||||
|
||||
document = [[queryContext request] contentAsDOMDocument];
|
||||
filters = [self _parseContactFilters: [document documentElement]];
|
||||
documentElement = [document documentElement];
|
||||
propElement = [(NGDOMNodeWithChildren *) documentElement firstElementWithTag: @"prop"
|
||||
inNamespace: @"DAV:"];
|
||||
properties = [(NGDOMNodeWithChildren *) propElement flatPropertyNameOfSubElements];
|
||||
qualifier = [self _parseContactFilters: documentElement];
|
||||
|
||||
[self _appendComponentsMatchingFilters: filters
|
||||
toResponse: r
|
||||
context: queryContext];
|
||||
[self _appendComponentsProperties: properties
|
||||
matchingQualifier: qualifier
|
||||
toResponse: r
|
||||
context: queryContext];
|
||||
[r appendContentString: @"</D:multistatus>"];
|
||||
|
||||
return r;
|
||||
|
||||
@@ -919,6 +919,29 @@ groupObjectClasses: (NSArray *) newGroupObjectClasses
|
||||
return [EOQualifier qualifierWithQualifierFormat: qs];
|
||||
}
|
||||
|
||||
- (void) addVCardProperty: (NSString *) property
|
||||
toCriteria: (NSMutableArray *) criteria
|
||||
{
|
||||
static NSDictionary *vCardLDAPFieldsTable = nil;
|
||||
NSString *field;
|
||||
|
||||
if (!vCardLDAPFieldsTable)
|
||||
vCardLDAPFieldsTable = [[NSDictionary alloc] initWithObjectsAndKeys:
|
||||
@"cn", @"fn",
|
||||
@"cn", @"n",
|
||||
@"mail", @"email",
|
||||
@"telephonenumber", @"tel",
|
||||
@"o", @"org",
|
||||
@"l", @"adr",
|
||||
nil];
|
||||
|
||||
field = [vCardLDAPFieldsTable objectForKey: property];
|
||||
if (field)
|
||||
{
|
||||
[criteria addObjectUniquely: field];
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
- (NSArray *) _constraintsFields
|
||||
{
|
||||
@@ -1326,8 +1349,7 @@ groupObjectClasses: (NSArray *) newGroupObjectClasses
|
||||
pool = [NSAutoreleasePool new];
|
||||
while ((currentEntry = [entries nextObject]))
|
||||
{
|
||||
[contacts addObject:
|
||||
[self _convertLDAPEntryToContact: currentEntry]];
|
||||
[contacts addObject: [self _convertLDAPEntryToContact: currentEntry]];
|
||||
i++;
|
||||
if (i % 10 == 0)
|
||||
{
|
||||
@@ -1524,6 +1546,55 @@ groupObjectClasses: (NSArray *) newGroupObjectClasses
|
||||
andValue: theEmail];
|
||||
}
|
||||
|
||||
- (NSArray *) lookupContactsWithQualifier: (EOQualifier *) qualifier
|
||||
andSortOrdering: (EOSortOrdering *) ordering
|
||||
inDomain: (NSString *) domain
|
||||
{
|
||||
NSAutoreleasePool *pool;
|
||||
NGLdapConnection *ldapConnection;
|
||||
NGLdapEntry *currentEntry;
|
||||
NSEnumerator *entries;
|
||||
NSMutableArray *contacts;
|
||||
unsigned int i;
|
||||
|
||||
contacts = [NSMutableArray array];
|
||||
|
||||
if ([qualifier count] > 0 || !_listRequiresDot)
|
||||
{
|
||||
ldapConnection = [self _ldapConnection];
|
||||
|
||||
// Perform the query if the qualifier is defined or if no critera was defined
|
||||
if ([_scope caseInsensitiveCompare: @"BASE"] == NSOrderedSame)
|
||||
entries = [ldapConnection baseSearchAtBaseDN: _baseDN
|
||||
qualifier: qualifier
|
||||
attributes: _lookupFields];
|
||||
else if ([_scope caseInsensitiveCompare: @"ONE"] == NSOrderedSame)
|
||||
entries = [ldapConnection flatSearchAtBaseDN: _baseDN
|
||||
qualifier: qualifier
|
||||
attributes: _lookupFields];
|
||||
else /* we do it like before */
|
||||
entries = [ldapConnection deepSearchAtBaseDN: _baseDN
|
||||
qualifier: qualifier
|
||||
attributes: _lookupFields];
|
||||
|
||||
i = 0;
|
||||
pool = [NSAutoreleasePool new];
|
||||
while ((currentEntry = [entries nextObject]))
|
||||
{
|
||||
[contacts addObject: [self _convertLDAPEntryToContact: currentEntry]];
|
||||
i++;
|
||||
if (i % 10 == 0)
|
||||
{
|
||||
[pool release];
|
||||
pool = [NSAutoreleasePool new];
|
||||
}
|
||||
}
|
||||
[pool release];
|
||||
}
|
||||
|
||||
return contacts;
|
||||
}
|
||||
|
||||
- (void) setSourceID: (NSString *) newSourceID
|
||||
{
|
||||
ASSIGN(_sourceID, newSourceID);
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
|
||||
#import "SOGoConstants.h"
|
||||
|
||||
@class EOQualifier;
|
||||
@class EOSortOrdering;
|
||||
@class NSDictionary;
|
||||
@class NSException;
|
||||
@class NSString;
|
||||
@@ -70,6 +72,9 @@
|
||||
- (NSArray *) fetchContactsMatching: (NSString *) filter
|
||||
withCriteria: (NSArray *) criteria
|
||||
inDomain: (NSString *) domain;
|
||||
- (NSArray *) lookupContactsWithQualifier: (EOQualifier *) qualifier
|
||||
andSortOrdering: (EOSortOrdering *) ordering
|
||||
inDomain: (NSString *) domain;
|
||||
|
||||
- (void) setSourceID: (NSString *) newSourceID;
|
||||
- (NSString *) sourceID;
|
||||
@@ -88,6 +93,9 @@
|
||||
- (NSException *) updateContactEntry: (NSDictionary *) ldifRecord;
|
||||
- (NSException *) removeContactEntryWithID: (NSString *) aId;
|
||||
|
||||
- (void) addVCardProperty: (NSString *) property
|
||||
toCriteria: (NSMutableArray *) criteria;
|
||||
|
||||
/* user address books */
|
||||
- (NSArray *) addressBookSourcesForUser: (NSString *) user;
|
||||
|
||||
|
||||
@@ -30,7 +30,12 @@
|
||||
#import <GDLContentStore/GCSChannelManager.h>
|
||||
#import <GDLContentStore/NSURL+GCS.h>
|
||||
#import <GDLContentStore/EOQualifier+GCS.h>
|
||||
#import <GDLAccess/EOAdaptor.h>
|
||||
#import <GDLAccess/EOAdaptorChannel.h>
|
||||
#import <GDLAccess/EOAdaptorContext.h>
|
||||
#import <GDLAccess/EOAttribute.h>
|
||||
#import <GDLAccess/EOEntity.h>
|
||||
#import <GDLAccess/EOSQLQualifier.h>
|
||||
|
||||
#import <SOGo/SOGoSystemDefaults.h>
|
||||
|
||||
@@ -290,7 +295,7 @@
|
||||
nil];
|
||||
[qualifier autorelease];
|
||||
}
|
||||
[qualifier _gcsAppendToString: sql];
|
||||
[qualifier appendSQLToString: sql];
|
||||
|
||||
ex = [channel evaluateExpressionX: sql];
|
||||
if (!ex)
|
||||
@@ -535,7 +540,7 @@
|
||||
qualifier = [[EOOrQualifier alloc] initWithQualifierArray: qualifiers];
|
||||
if (domainQualifier)
|
||||
qualifier = [[EOAndQualifier alloc] initWithQualifiers: domainQualifier, qualifier, nil];
|
||||
[qualifier _gcsAppendToString: sql];
|
||||
[qualifier appendSQLToString: sql];
|
||||
|
||||
ex = [channel evaluateExpressionX: sql];
|
||||
if (!ex)
|
||||
@@ -623,7 +628,7 @@
|
||||
|
||||
qualifier = [[EOAndQualifier alloc] initWithQualifiers: q_uid, q_auth, nil];
|
||||
[qualifier autorelease];
|
||||
[qualifier _gcsAppendToString: sql];
|
||||
[qualifier appendSQLToString: sql];
|
||||
|
||||
ex = [channel evaluateExpressionX: sql];
|
||||
if (!ex)
|
||||
@@ -727,6 +732,29 @@
|
||||
return [self _lookupContactEntry: entryID considerEmail: YES inDomain: domain];
|
||||
}
|
||||
|
||||
- (void) addVCardProperty: (NSString *) property
|
||||
toCriteria: (NSMutableArray *) criteria
|
||||
{
|
||||
static NSDictionary *vCardSQLFieldsTable = nil;
|
||||
NSString *field;
|
||||
|
||||
if (!vCardSQLFieldsTable)
|
||||
vCardSQLFieldsTable = [[NSDictionary alloc] initWithObjectsAndKeys:
|
||||
@"c_cn", @"fn",
|
||||
@"c_cn", @"n",
|
||||
@"mail", @"email",
|
||||
@"c_telephonenumber", @"tel",
|
||||
@"c_o", @"org",
|
||||
@"c_l", @"adr",
|
||||
nil];
|
||||
|
||||
field = [vCardSQLFieldsTable objectForKey: property];
|
||||
if (field)
|
||||
{
|
||||
[criteria addObjectUniquely: field];
|
||||
}
|
||||
}
|
||||
|
||||
/* Returns an EOQualifier of the following form:
|
||||
* (_domainField = domain OR _domainField = visibleDomain1 [...])
|
||||
* Should only be called on SQL sources using _domainField name.
|
||||
@@ -802,7 +830,7 @@
|
||||
if (domainQualifier)
|
||||
{
|
||||
[sql appendString: @" WHERE "];
|
||||
[domainQualifier _gcsAppendToString: sql];
|
||||
[domainQualifier appendSQLToString: sql];
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -911,7 +939,7 @@
|
||||
if (domainQualifier)
|
||||
{
|
||||
[sql appendFormat: @" AND ("];
|
||||
[domainQualifier _gcsAppendToString: sql];
|
||||
[domainQualifier appendSQLToString: sql];
|
||||
[sql appendFormat: @")"];
|
||||
}
|
||||
}
|
||||
@@ -948,6 +976,85 @@
|
||||
return results;
|
||||
}
|
||||
|
||||
- (NSArray *) lookupContactsWithQualifier: (EOQualifier *) qualifier
|
||||
andSortOrdering: (EOSortOrdering *) ordering
|
||||
inDomain: (NSString *) domain
|
||||
{
|
||||
static EOAdaptor *adaptor = nil;
|
||||
NSException *ex;
|
||||
NSMutableArray *results;
|
||||
NSMutableString *sql;
|
||||
EOAdaptorChannel *channel;
|
||||
GCSChannelManager *cm;
|
||||
|
||||
results = [NSMutableArray array];
|
||||
|
||||
if (qualifier || !_listRequiresDot)
|
||||
{
|
||||
cm = [GCSChannelManager defaultChannelManager];
|
||||
channel = [cm acquireOpenChannelForURL: _viewURL];
|
||||
if (channel)
|
||||
{
|
||||
if (!adaptor)
|
||||
{
|
||||
EOAdaptorContext *adaptorCtx;
|
||||
adaptorCtx = [channel adaptorContext];
|
||||
adaptor = [adaptorCtx adaptor];
|
||||
}
|
||||
sql = [NSMutableString stringWithFormat: @"SELECT c_uid FROM %@ WHERE (", [_viewURL gcsTableName]];
|
||||
|
||||
if (qualifier)
|
||||
[qualifier appendSQLToString: sql
|
||||
withAdaptor: adaptor];
|
||||
else
|
||||
[sql appendString: @"1 = 1"];
|
||||
[sql appendString: @")"];
|
||||
|
||||
if (_domainField)
|
||||
{
|
||||
if ([domain length])
|
||||
{
|
||||
EOQualifier *domainQualifier;
|
||||
domainQualifier = [self visibleDomainsQualifierFromDomain: domain];
|
||||
if (domainQualifier)
|
||||
{
|
||||
[sql appendFormat: @" AND ("];
|
||||
[domainQualifier appendSQLToString: sql];
|
||||
[sql appendFormat: @")"];
|
||||
}
|
||||
}
|
||||
else
|
||||
[sql appendFormat: @" AND %@ IS NULL", _domainField];
|
||||
}
|
||||
|
||||
ex = [channel evaluateExpressionX: sql];
|
||||
if (!ex)
|
||||
{
|
||||
NSDictionary *row;
|
||||
NSMutableDictionary *mutableRow;
|
||||
NSArray *attrs;
|
||||
|
||||
attrs = [channel describeResults: NO];
|
||||
|
||||
while ((row = [channel fetchAttributes: attrs withZone: NULL]))
|
||||
{
|
||||
mutableRow = [row mutableCopy];
|
||||
// [mutableRow setObject: self forKey: @"source"];
|
||||
[results addObject: mutableRow];
|
||||
[mutableRow release];
|
||||
}
|
||||
}
|
||||
else
|
||||
[self errorWithFormat: @"could not run SQL '%@': %@", sql, ex];
|
||||
[cm releaseChannel: channel];
|
||||
}
|
||||
[self errorWithFormat:@"failed to acquire channel for URL: %@",
|
||||
[_viewURL absoluteString]];
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
- (void) setSourceID: (NSString *) newSourceID
|
||||
{
|
||||
}
|
||||
|
||||
@@ -27,8 +27,8 @@ TEL;TYPE=cell:portable
|
||||
TEL;TYPE=fax:fax
|
||||
TEL;TYPE=pager:pager
|
||||
X-MOZILLA-HTML:FALSE
|
||||
EMAIL;TYPE=work:address.email@domaine.ca
|
||||
EMAIL;TYPE=home:address.email@domaine2.com
|
||||
EMAIL;TYPE=work:address.email1@domaine.ca
|
||||
EMAIL;TYPE=home:address.email1@domaine2.com
|
||||
URL;TYPE=home:web perso
|
||||
TITLE:fonction
|
||||
URL;TYPE=work:page soc
|
||||
@@ -49,14 +49,14 @@ ORG:societe;service
|
||||
NICKNAME:surnom
|
||||
ADR;TYPE=work:adr2 societe;;adr societe;ville societe;etat soc;code soc;pays soc
|
||||
ADR;TYPE=home:rue perso 2;;rue perso;ville perso;etat perso;code post perso;pays perso
|
||||
TEL;TYPE=work:+1 514 123-3372
|
||||
TEL;TYPE=work:+1 555 222-2222
|
||||
TEL;TYPE=home:tel dom
|
||||
TEL;TYPE=cell:portable
|
||||
TEL;TYPE=fax:fax
|
||||
TEL;TYPE=pager:pager
|
||||
X-MOZILLA-HTML:FALSE
|
||||
EMAIL;TYPE=work:address.email@domaine.ca
|
||||
EMAIL;TYPE=home:address.email@domaine2.com
|
||||
EMAIL;TYPE=work:address.email2@domaine.ca
|
||||
EMAIL;TYPE=home:address.email2@domaine2.com
|
||||
URL;TYPE=home:web perso
|
||||
TITLE:fonction
|
||||
URL;TYPE=work:page soc
|
||||
@@ -96,6 +96,137 @@ describe('CardDAV extensions', function() {
|
||||
await webdav_su.deleteObject(resource)
|
||||
})
|
||||
|
||||
// CARDDAV:addressbook-query Report
|
||||
// https://datatracker.ietf.org/doc/html/rfc6352#section-8.6
|
||||
it("supports for addressbook-query on GCS folder", async function() {
|
||||
const name = Object.keys(cards)[1]
|
||||
const ns = DAVNamespaceShorthandMap[DAVNamespace.CARDDAV]
|
||||
const response = await davRequest({
|
||||
url: webdav.serverUrl + resource,
|
||||
init: {
|
||||
method: 'REPORT',
|
||||
namespace: ns,
|
||||
headers: { ...webdav.headers, depth: '1' },
|
||||
body: {
|
||||
'addressbook-query': {
|
||||
_attributes: getDAVAttribute([
|
||||
DAVNamespace.CARDDAV,
|
||||
DAVNamespace.DAV
|
||||
]),
|
||||
[`${DAVNamespaceShorthandMap[DAVNamespace.DAV]}:prop`]: formatProps([{ name: 'address-data', namespace: DAVNamespace.CARDDAV }]),
|
||||
filter: {
|
||||
_attributes: { test: 'anyof' },
|
||||
'prop-filter': [
|
||||
{
|
||||
_attributes: { name: 'FN', test: 'anyof' },
|
||||
'text-match': [
|
||||
{
|
||||
_attributes: { collation: 'i;unicasemap', 'match-type': 'starts-with' },
|
||||
_text: 'Carte modifiee' // should match the second card
|
||||
},
|
||||
{
|
||||
_attributes: { collation: 'i;unicasemap', 'match-type': 'contains' },
|
||||
_text: 'No match' // should not match any card
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
_attributes: { name: 'EMAIL', test: 'allof' },
|
||||
'text-match': {
|
||||
_attributes: { collation: 'i;unicasemap', 'match-type': 'starts-with' },
|
||||
_text: 'email2' // should match the second card
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
elementNameFn: (name) => {
|
||||
if (!/^.+:.+/.test(name)) {
|
||||
return `${ns}:${name}`
|
||||
}
|
||||
return name
|
||||
}
|
||||
}
|
||||
})
|
||||
expect(response.length)
|
||||
.withContext(`Number of results from addressbook-query`)
|
||||
.toBe(1)
|
||||
expect(response[0].status)
|
||||
.withContext(`HTTP status code of addressbook-query`)
|
||||
.toEqual(207)
|
||||
expect(utility.componentsAreEqual(response[0].props.addressData, cards[name]))
|
||||
.withContext(`Returned vCard matches ${name}`)
|
||||
.toBe(true)
|
||||
})
|
||||
|
||||
// CARDDAV:addressbook-query Report
|
||||
// https://datatracker.ietf.org/doc/html/rfc6352#section-8.6
|
||||
it("supports for addressbook-query on source folder", async function() {
|
||||
let vcard, emails
|
||||
const ns = DAVNamespaceShorthandMap[DAVNamespace.CARDDAV]
|
||||
const response = await davRequest({
|
||||
url: webdav.serverUrl + `/SOGo/dav/${config.username}/Contacts/public/`,
|
||||
init: {
|
||||
method: 'REPORT',
|
||||
namespace: ns,
|
||||
headers: { ...webdav.headers, depth: '1' },
|
||||
body: {
|
||||
'addressbook-query': {
|
||||
_attributes: getDAVAttribute([
|
||||
DAVNamespace.CARDDAV,
|
||||
DAVNamespace.DAV
|
||||
]),
|
||||
[`${DAVNamespaceShorthandMap[DAVNamespace.DAV]}:prop`]: formatProps([{ name: 'address-data', namespace: DAVNamespace.CARDDAV }]),
|
||||
filter: {
|
||||
_attributes: { test: 'anyof' },
|
||||
'prop-filter': [
|
||||
{
|
||||
_attributes: { name: 'FN', test: 'allof' },
|
||||
'text-match': [
|
||||
{
|
||||
_attributes: { collation: 'i;unicasemap', 'match-type': 'contains' },
|
||||
_text: 'No match' // should not match any card
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
_attributes: { name: 'EMAIL', test: 'allof' },
|
||||
'text-match': {
|
||||
_attributes: { collation: 'i;unicasemap', 'match-type': 'starts-with' },
|
||||
_text: `${config.attendee1}`
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
elementNameFn: (name) => {
|
||||
if (!/^.+:.+/.test(name)) {
|
||||
return `${ns}:${name}`
|
||||
}
|
||||
return name
|
||||
}
|
||||
}
|
||||
})
|
||||
expect(response.length)
|
||||
.withContext(`Number of results from addressbook-query`)
|
||||
.toBe(1)
|
||||
expect(response[0].status)
|
||||
.withContext(`HTTP status code of addressbook-query`)
|
||||
.toEqual(207)
|
||||
|
||||
vcard = ICAL.Component.fromString(response[0].props.addressData.toString())
|
||||
emails = []
|
||||
for (const prop of vcard.getAllProperties('email')) {
|
||||
emails.push(prop.getFirstValue())
|
||||
}
|
||||
expect(emails)
|
||||
.withContext(`Returned vCard has email of ${config.attendee1_username} (${config.attendee1})`)
|
||||
.toContain(config.attendee1)
|
||||
})
|
||||
|
||||
// CARDDAV:addressbook-multiget Report
|
||||
// https://datatracker.ietf.org/doc/html/rfc6352#section-8.7
|
||||
it("supports for addressbook-multiget", async function() {
|
||||
const hrefs = Object.keys(cards).map(c => `${resource}${c}`)
|
||||
|
||||
Reference in New Issue
Block a user