sogo/SoObjects/Contacts/SOGoFolder+CardDAV.m

230 lines
7.3 KiB
Mathematica
Raw Normal View History

/* NSObject+CardDAV.m - this file is part of SOGo
*
* Copyright (C) 2007-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 <Foundation/NSAutoreleasePool.h>
#import <Foundation/NSDictionary.h>
#import <NGObjWeb/WOContext+SoObjects.h>
#import <NGObjWeb/WORequest.h>
#import <NGExtensions/NSObject+Logs.h>
#import <NGExtensions/NSString+misc.h>
#import <DOM/DOMNode.h>
#import <SaxObjC/SaxObjC.h>
#import <SOGo/NSString+Utilities.h>
#import <SOGo/SOGoUser.h>
#import <SOGo/WOResponse+SOGo.h>
#import "SOGoContactFolder.h"
#import "SOGoContactGCSEntry.h"
@implementation SOGoFolder (CardDAV)
- (void) _appendObject: (NSDictionary *) object
withBaseURL: (NSString *) baseURL
toREPORTResponse: (WOResponse *) r
{
id component;
NSString *name, *etagLine, *contactString;
name = [object objectForKey: @"c_name"];
if ([name length])
{
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];
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];
if (![baseURL hasSuffix: @"/"])
2013-09-13 17:13:02 +02:00
[r appendContentString: @"/"];
[r appendContentString: name];
[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>"];
}
}
- (void) _appendComponentsMatchingFilters: (NSArray *) filters
toResponse: (WOResponse *) response
2013-09-13 17:13:02 +02:00
context: (id) localContext
{
unsigned int count,i , max;
NSAutoreleasePool *pool;
NSDictionary *currentFilter, *contact;
NSEnumerator *contacts;
NSString *baseURL, *domain;
baseURL = [self baseURLInContext: localContext];
domain = [[localContext activeUser] domain];
max = [filters count];
for (count = 0; count < max; count++)
{
currentFilter = [filters objectAtIndex: count];
contacts =
[[(id<SOGoContactFolder>)self lookupContactsWithFilter: [[currentFilter allValues] lastObject]
2017-11-22 15:26:57 +01:00
onCriteria: nil
sortBy: @"c_givenname"
ordering: NSOrderedDescending
inDomain: domain] objectEnumerator];
pool = [[NSAutoreleasePool alloc] init];
i = 0;
while ((contact = [contacts nextObject]))
{
[self _appendObject: contact withBaseURL: baseURL
toREPORTResponse: response];
if (i % 10 == 0)
{
RELEASE(pool);
pool = [[NSAutoreleasePool alloc] init];
}
i++;
}
RELEASE(pool);
}
}
- (BOOL) _isValidFilter: (NSString *) theString
{
NSString *newString;
newString = [theString lowercaseString];
return ([newString isEqualToString: @"sn"]
2013-09-13 17:13:02 +02:00
|| [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]
2013-09-13 17:13:02 +02:00
&& [(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"]
2013-09-13 17:13:02 +02:00
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])
fix (revised) for issue with CardDAV sync with the DAVDroid sync client. This is a revised fix for the issue raiased in sogo bug tracker 3370 and 3373. It supercedes the fix in commit 2c723070c69899055b456119f6e8f42c279d0c57 . The fix was noted in NEWS with the comment "we now return all cards when we receive an empty addressbook-query REPORT". However it did not work for me and at least two others, as can be seen in the commit comments. In summary, only contacts with email addresses were synced. The suggested change from kwirk fixes the regular address book sync, but it completely breaks syncing of the read-only Group Directory (Corporate Directory). My suggested changes work in full (as far as I'm able to test). I have done some fairly extensive testing of CardDAV sync (with DAVDroid only) and it seems to work 100% now. In addition to the obvious tests, I have tested with contacts that only have one field of data entered. The feilds I've tested (with all other fields empty) are as follows: First name Last name Display name email address Work (telephone) Home (telephone) Fax (telephone) Mobile (telephone) Additionally, I tested syncing of a contact with only the Work Address fully populated. In the webmail, since the name fields are all missing, the "Organization" field of the Work Address takes the place of the name field in the 'Name' column. This does get synced to my phone and it also appears my Android contact list with 'Name' set to the 'Organization' field data. The address, organization and website fields being in tact also. In addition, I tested a Group Directory (Corporate Directory) [SOGoUserSources->isAddressBook] sync. It seems contacts without email addresses do not sync. This seems to be the behaviour across the board with a "." search filter. This happens despite the filter in SOGoUserSources including ldap entries without a mail attribute. Nothing I can do to patch this in SOGoFolder+CardDAV.m, that would have to be fixed in the code that deals with the special "." search filter (I guess). I think the contact search system needs some looking into, particularly the "." search filter behaviour. There is another bug related to contact search in the webmail address book view. I will make a bug report on that soon. It's a shame there isn't an "all" search filter, it would seem it would make various parts of SOGo easier to get the right behaviour.
2016-01-23 20:43:24 +01:00
{
[filters addObject: [NSDictionary dictionaryWithObject: @"." forKey: @"email"]];
[filters addObject: [NSDictionary dictionaryWithObject: @"%" forKey: @"name"]];
}
return filters;
}
2018-02-06 22:20:20 +01:00
/**
<?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:prop-filter>
</C:filter>
</C:addressbook-query>
*/
- (id) davAddressbookQuery: (id) queryContext
{
WOResponse *r;
NSArray *filters;
id <DOMDocument> document;
r = [queryContext response];
[r prepareDAVResponse];
[r appendContentString: @"<D:multistatus xmlns:D=\"DAV:\""
@" xmlns:C=\"urn:ietf:params:xml:ns:carddav\">"];
document = [[queryContext request] contentAsDOMDocument];
filters = [self _parseContactFilters: [document documentElement]];
[self _appendComponentsMatchingFilters: filters
2018-02-06 22:20:20 +01:00
toResponse: r
context: queryContext];
[r appendContentString: @"</D:multistatus>"];
return r;
}
@end