2014-01-10 20:12:53 +01:00
|
|
|
/*
|
|
|
|
|
|
|
|
Copyright (c) 2014, Inverse inc.
|
|
|
|
All rights reserved.
|
|
|
|
|
2014-01-13 17:46:32 +01:00
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
|
|
modification, are permitted provided that the following conditions are met:
|
|
|
|
|
|
|
|
* Redistributions of source code must retain the above copyright
|
|
|
|
notice, this list of conditions and the following disclaimer.
|
|
|
|
* Redistributions in binary form must reproduce the above copyright
|
|
|
|
notice, this list of conditions and the following disclaimer in the
|
|
|
|
documentation and/or other materials provided with the distribution.
|
|
|
|
* Neither the name of the Inverse inc. nor the
|
|
|
|
names of its contributors may be used to endorse or promote products
|
|
|
|
derived from this software without specific prior written permission.
|
|
|
|
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
|
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
|
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
|
|
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
|
|
|
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
|
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
|
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
|
|
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
|
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
|
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
2014-01-10 20:12:53 +01:00
|
|
|
|
|
|
|
*/
|
|
|
|
#include "SOGoActiveSyncDispatcher.h"
|
|
|
|
|
|
|
|
#import <Foundation/NSArray.h>
|
2015-10-15 21:31:46 +02:00
|
|
|
#import <Foundation/NSData.h>
|
2014-12-04 17:27:10 +01:00
|
|
|
#import <Foundation/NSAutoreleasePool.h>
|
2014-01-10 20:12:53 +01:00
|
|
|
#import <Foundation/NSCalendarDate.h>
|
2015-10-15 21:54:25 +02:00
|
|
|
#if GNUSTEP_BASE_MINOR_VERSION >= 21
|
2015-10-14 15:57:56 +02:00
|
|
|
#import <Foundation/NSLocale.h>
|
2015-10-15 21:54:25 +02:00
|
|
|
#endif
|
2014-01-10 20:12:53 +01:00
|
|
|
#import <Foundation/NSProcessInfo.h>
|
|
|
|
#import <Foundation/NSTimeZone.h>
|
|
|
|
#import <Foundation/NSURL.h>
|
2015-01-12 20:38:55 +01:00
|
|
|
#import <Foundation/NSValue.h>
|
2014-01-10 20:12:53 +01:00
|
|
|
|
|
|
|
#import <NGObjWeb/NSException+HTTP.h>
|
2014-10-29 19:20:03 +01:00
|
|
|
#import <NGObjWeb/SoPermissions.h>
|
|
|
|
#import <NGObjWeb/SoSecurityManager.h>
|
2014-01-10 20:12:53 +01:00
|
|
|
#import <NGObjWeb/SoApplication.h>
|
|
|
|
#import <NGObjWeb/SoObject.h>
|
|
|
|
#import <NGObjWeb/WOContext.h>
|
|
|
|
#import <NGObjWeb/WOContext+SoObjects.h>
|
|
|
|
#import <NGObjWeb/WOCookie.h>
|
|
|
|
#import <NGObjWeb/WODirectAction.h>
|
|
|
|
#import <NGObjWeb/WORequest.h>
|
|
|
|
#import <NGObjWeb/WOResponse.h>
|
2016-03-29 16:32:10 +02:00
|
|
|
#import <NGObjWeb/WOCoreApplication.h>
|
2014-01-10 20:12:53 +01:00
|
|
|
|
2014-02-03 16:24:33 +01:00
|
|
|
#import <NGCards/iCalCalendar.h>
|
2014-01-10 20:12:53 +01:00
|
|
|
#import <NGCards/iCalEntityObject.h>
|
|
|
|
#import <NGCards/iCalEvent.h>
|
|
|
|
#import <NGCards/iCalToDo.h>
|
|
|
|
#import <NGCards/NGVCard.h>
|
|
|
|
|
|
|
|
#import <NGExtensions/NGBase64Coding.h>
|
|
|
|
#import <NGExtensions/NSCalendarDate+misc.h>
|
|
|
|
#import <NGExtensions/NGCalendarDateRange.h>
|
|
|
|
#import <NGExtensions/NGHashMap.h>
|
2015-01-22 19:31:31 +01:00
|
|
|
#import <NGExtensions/NSObject+Logs.h>
|
2014-01-10 20:12:53 +01:00
|
|
|
#import <NGExtensions/NSString+misc.h>
|
2015-05-14 21:49:53 +02:00
|
|
|
#import <NGExtensions/NSString+Encoding.h>
|
2014-01-10 20:12:53 +01:00
|
|
|
|
2014-01-13 16:19:00 +01:00
|
|
|
#import <NGImap4/NGImap4Client.h>
|
|
|
|
#import <NGImap4/NGImap4Connection.h>
|
2014-01-10 20:12:53 +01:00
|
|
|
#import <NGImap4/NSString+Imap4.h>
|
|
|
|
|
|
|
|
#import <NGMime/NGMimeBodyPart.h>
|
2014-02-06 20:05:00 +01:00
|
|
|
#import <NGMime/NGMimeFileData.h>
|
2014-01-10 20:12:53 +01:00
|
|
|
#import <NGMime/NGMimeMultipartBody.h>
|
2014-06-25 21:05:25 +02:00
|
|
|
#import <NGMime/NGMimeType.h>
|
2014-01-10 20:12:53 +01:00
|
|
|
#import <NGMail/NGMimeMessageParser.h>
|
|
|
|
#import <NGMail/NGMimeMessage.h>
|
|
|
|
#import <NGMail/NGMimeMessageGenerator.h>
|
|
|
|
|
|
|
|
#import <DOM/DOMElement.h>
|
|
|
|
#import <DOM/DOMProtocols.h>
|
|
|
|
#import <DOM/DOMSaxBuilder.h>
|
|
|
|
|
|
|
|
#import <EOControl/EOQualifier.h>
|
|
|
|
|
|
|
|
#import <SOGo/NSArray+DAV.h>
|
|
|
|
#import <SOGo/NSDictionary+DAV.h>
|
2014-03-19 16:31:54 +01:00
|
|
|
#import <SOGo/SOGoCache.h>
|
2014-05-15 21:03:24 +02:00
|
|
|
#import <SOGo/SOGoCacheGCSObject.h>
|
2014-01-10 20:12:53 +01:00
|
|
|
#import <SOGo/SOGoDAVAuthenticator.h>
|
|
|
|
#import <SOGo/SOGoDomainDefaults.h>
|
|
|
|
#import <SOGo/SOGoMailer.h>
|
2014-02-11 02:16:43 +01:00
|
|
|
#import <SOGo/SOGoSystemDefaults.h>
|
2014-01-10 20:12:53 +01:00
|
|
|
#import <SOGo/SOGoUser.h>
|
|
|
|
#import <SOGo/SOGoUserFolder.h>
|
|
|
|
#import <SOGo/SOGoUserManager.h>
|
|
|
|
#import <SOGo/SOGoUserSettings.h>
|
2014-05-15 21:03:24 +02:00
|
|
|
#import <SOGo/GCSSpecialQueries+SOGoCacheObject.h>
|
|
|
|
#import <SOGo/NSString+Utilities.h>
|
2014-10-29 19:20:03 +01:00
|
|
|
#import <SOGo/WORequest+SOGo.h>
|
2015-05-14 21:49:53 +02:00
|
|
|
#import <SOGo/NSArray+Utilities.h>
|
2014-01-10 20:12:53 +01:00
|
|
|
|
|
|
|
#import <Appointments/SOGoAppointmentFolder.h>
|
|
|
|
#import <Appointments/SOGoAppointmentFolders.h>
|
2014-02-03 16:24:33 +01:00
|
|
|
#import <Appointments/SOGoAppointmentObject.h>
|
2014-01-10 20:12:53 +01:00
|
|
|
|
|
|
|
#import <Contacts/SOGoContactGCSFolder.h>
|
|
|
|
#import <Contacts/SOGoContactFolders.h>
|
|
|
|
#import <Contacts/SOGoContactObject.h>
|
|
|
|
#import <Contacts/SOGoContactSourceFolder.h>
|
|
|
|
|
|
|
|
#import <Mailer/SOGoMailAccount.h>
|
|
|
|
#import <Mailer/SOGoMailAccounts.h>
|
|
|
|
#import <Mailer/SOGoMailBodyPart.h>
|
|
|
|
#import <Mailer/SOGoMailFolder.h>
|
|
|
|
#import <Mailer/SOGoMailObject.h>
|
2015-05-14 21:49:53 +02:00
|
|
|
#import <Mailer/SOGoMailObject+Draft.h>
|
|
|
|
#import <Mailer/NSString+Mail.h>
|
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
|
|
|
|
#import <Foundation/NSObject.h>
|
|
|
|
#import <Foundation/NSString.h>
|
|
|
|
|
|
|
|
#include "iCalEvent+ActiveSync.h"
|
|
|
|
#include "iCalToDo+ActiveSync.h"
|
|
|
|
#include "NGMimeMessage+ActiveSync.h"
|
|
|
|
#include "NGVCard+ActiveSync.h"
|
2014-01-20 16:13:16 +01:00
|
|
|
#include "NSCalendarDate+ActiveSync.h"
|
2014-01-10 20:12:53 +01:00
|
|
|
#include "NSData+ActiveSync.h"
|
2014-01-20 16:13:16 +01:00
|
|
|
#include "NSDate+ActiveSync.h"
|
2014-01-10 20:12:53 +01:00
|
|
|
#include "NSString+ActiveSync.h"
|
|
|
|
#include "SOGoActiveSyncConstants.h"
|
|
|
|
#include "SOGoMailObject+ActiveSync.h"
|
|
|
|
|
2014-05-15 21:03:24 +02:00
|
|
|
#import <GDLContentStore/GCSChannelManager.h>
|
|
|
|
|
2016-03-30 14:45:19 +02:00
|
|
|
#include <signal.h>
|
2014-02-04 17:19:33 +01:00
|
|
|
#include <unistd.h>
|
|
|
|
|
2015-10-15 21:31:46 +02:00
|
|
|
#ifdef HAVE_OPENSSL
|
|
|
|
#include <openssl/bio.h>
|
|
|
|
#include <openssl/err.h>
|
|
|
|
#include <openssl/x509.h>
|
|
|
|
#endif
|
|
|
|
|
2016-04-04 14:35:19 +02:00
|
|
|
void handle_eas_terminate(int signum)
|
2016-03-29 16:32:10 +02:00
|
|
|
{
|
|
|
|
NSLog(@"Forcing termination of EAS loop.");
|
2016-04-07 20:22:17 +02:00
|
|
|
easShouldTerminate = YES;
|
2016-03-29 16:32:10 +02:00
|
|
|
[[WOCoreApplication application] terminateAfterTimeInterval: 1];
|
|
|
|
}
|
|
|
|
|
2014-06-10 17:04:27 +02:00
|
|
|
@interface SOGoActiveSyncDispatcher (Sync)
|
|
|
|
|
|
|
|
- (NSMutableDictionary *) _folderMetadataForKey: (NSString *) theFolderKey;
|
2014-12-08 16:25:37 +01:00
|
|
|
- (void) _setFolderMetadata: (NSDictionary *) theFolderMetadata forKey: (NSString *) theFolderKey;
|
2014-06-10 17:04:27 +02:00
|
|
|
|
|
|
|
@end
|
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
@implementation SOGoActiveSyncDispatcher
|
|
|
|
|
2014-05-15 21:03:24 +02:00
|
|
|
- (id) init
|
2014-01-14 22:08:04 +01:00
|
|
|
{
|
2014-05-15 21:03:24 +02:00
|
|
|
[super init];
|
|
|
|
|
2015-05-14 21:26:18 +02:00
|
|
|
debugOn = [[SOGoSystemDefaults sharedSystemDefaults] easDebugEnabled];
|
2014-05-15 21:03:24 +02:00
|
|
|
folderTableURL = nil;
|
2015-09-09 16:12:32 +02:00
|
|
|
imapFolderGUIDS = nil;
|
2015-10-20 14:48:39 +02:00
|
|
|
syncRequest = nil;
|
2016-03-29 16:32:10 +02:00
|
|
|
|
2016-04-07 20:22:17 +02:00
|
|
|
easShouldTerminate = NO;
|
2016-04-04 14:35:19 +02:00
|
|
|
signal(SIGTERM, handle_eas_terminate);
|
2016-03-29 16:32:10 +02:00
|
|
|
|
2014-05-15 21:03:24 +02:00
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void) dealloc
|
|
|
|
{
|
|
|
|
RELEASE(folderTableURL);
|
2015-09-09 16:12:32 +02:00
|
|
|
RELEASE(imapFolderGUIDS);
|
2015-10-20 14:48:39 +02:00
|
|
|
RELEASE(syncRequest);
|
2014-05-15 21:03:24 +02:00
|
|
|
[super dealloc];
|
|
|
|
}
|
|
|
|
|
2016-03-28 17:13:41 +02:00
|
|
|
- (void) _ensureFolder: (SOGoMailFolder *) mailFolder
|
|
|
|
{
|
|
|
|
BOOL rc;
|
|
|
|
|
|
|
|
if (![mailFolder isKindOfClass: [NSException class]])
|
|
|
|
{
|
|
|
|
rc = [mailFolder exists];
|
|
|
|
if (!rc)
|
|
|
|
rc = [mailFolder create];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-05-15 21:03:24 +02:00
|
|
|
- (void) _setFolderSyncKey: (NSString *) theSyncKey
|
|
|
|
{
|
|
|
|
SOGoCacheGCSObject *o;
|
|
|
|
|
|
|
|
o = [SOGoCacheGCSObject objectWithName: [context objectForKey: @"DeviceId"] inContainer: nil];
|
|
|
|
[o setObjectType: ActiveSyncGlobalCacheObject];
|
|
|
|
[o setTableUrl: [self folderTableURL]];
|
|
|
|
[o reloadIfNeeded];
|
2014-01-14 22:08:04 +01:00
|
|
|
|
2015-10-14 15:21:32 +02:00
|
|
|
[[o properties] setObject: theSyncKey
|
|
|
|
forKey: @"FolderSyncKey"];
|
2014-05-15 21:03:24 +02:00
|
|
|
[o save];
|
|
|
|
}
|
2014-01-14 22:08:04 +01:00
|
|
|
|
2015-10-14 15:21:32 +02:00
|
|
|
- (NSMutableDictionary *) globalMetadataForDevice
|
2014-05-15 21:03:24 +02:00
|
|
|
{
|
|
|
|
SOGoCacheGCSObject *o;
|
2014-01-14 22:08:04 +01:00
|
|
|
|
2015-10-14 15:21:32 +02:00
|
|
|
o = [SOGoCacheGCSObject objectWithName: [context objectForKey: @"DeviceId"] inContainer: nil useCache: NO];
|
2014-05-15 21:03:24 +02:00
|
|
|
[o setObjectType: ActiveSyncGlobalCacheObject];
|
|
|
|
[o setTableUrl: [self folderTableURL]];
|
|
|
|
[o reloadIfNeeded];
|
|
|
|
|
|
|
|
return [o properties];
|
2014-01-14 22:08:04 +01:00
|
|
|
}
|
|
|
|
|
2014-06-25 21:05:25 +02:00
|
|
|
- (unsigned int) _softDeleteCountWithFilter: (NSCalendarDate *) theFilter
|
|
|
|
collectionId: (NSString *) theCollectionId
|
|
|
|
{
|
|
|
|
NSMutableDictionary *dateCache;
|
|
|
|
NSMutableArray *sdUids;
|
|
|
|
SOGoCacheGCSObject *o;
|
|
|
|
NSArray *allKeys;
|
|
|
|
NSString *key;
|
|
|
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
sdUids = [NSMutableArray array];
|
|
|
|
|
|
|
|
if (theFilter)
|
|
|
|
{
|
2014-10-29 19:20:03 +01:00
|
|
|
o = [SOGoCacheGCSObject objectWithName: [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], theCollectionId] inContainer: nil];
|
2015-10-14 15:21:32 +02:00
|
|
|
[o setObjectType: ActiveSyncFolderCacheObject];
|
2014-06-25 21:05:25 +02:00
|
|
|
[o setTableUrl: [self folderTableURL]];
|
|
|
|
[o reloadIfNeeded];
|
|
|
|
|
|
|
|
dateCache = [[o properties] objectForKey: @"DateCache"];
|
|
|
|
allKeys = [dateCache allKeys];
|
|
|
|
|
|
|
|
for (i = 0; i < [allKeys count]; i++)
|
|
|
|
{
|
|
|
|
key = [allKeys objectAtIndex: i];
|
|
|
|
|
|
|
|
if ([[dateCache objectForKey:key] compare: theFilter ] == NSOrderedAscending)
|
|
|
|
[sdUids addObject: [dateCache objectForKey:key]];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return [sdUids count];
|
|
|
|
}
|
|
|
|
|
2014-05-27 20:44:57 +02:00
|
|
|
- (id) globallyUniqueIDToIMAPFolderName: (NSString *) theIdToTranslate
|
|
|
|
type: (SOGoMicrosoftActiveSyncFolderType) theFolderType
|
|
|
|
{
|
|
|
|
if (theFolderType == ActiveSyncMailFolder)
|
|
|
|
{
|
|
|
|
SOGoMailAccounts *accountsFolder;
|
|
|
|
SOGoMailAccount *accountFolder;
|
|
|
|
SOGoUserFolder *userFolder;
|
2015-09-09 16:12:32 +02:00
|
|
|
|
|
|
|
if (!imapFolderGUIDS)
|
|
|
|
{
|
|
|
|
userFolder = [[context activeUser] homeFolderInContext: context];
|
|
|
|
accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
|
|
|
|
accountFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO];
|
|
|
|
|
|
|
|
// Get the GUID of the IMAP folder
|
|
|
|
imapFolderGUIDS = [accountFolder imapFolderGUIDs];
|
|
|
|
[imapFolderGUIDS retain];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return [[[imapFolderGUIDS allKeysForObject: [NSString stringWithFormat: @"folder%@", theIdToTranslate]] objectAtIndex: 0] substringFromIndex: 6] ;
|
2014-05-27 20:44:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return theIdToTranslate;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-09-09 16:12:32 +02:00
|
|
|
|
2014-02-03 16:24:33 +01:00
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
- (id) collectionFromId: (NSString *) theCollectionId
|
|
|
|
type: (SOGoMicrosoftActiveSyncFolderType) theFolderType
|
|
|
|
{
|
|
|
|
id collection;
|
|
|
|
|
|
|
|
collection = nil;
|
|
|
|
|
|
|
|
switch (theFolderType)
|
|
|
|
{
|
|
|
|
case ActiveSyncContactFolder:
|
|
|
|
{
|
2014-10-29 19:20:03 +01:00
|
|
|
collection = [[[[context activeUser] homeFolderInContext: context] lookupName: @"Contacts" inContext: context acquire: NO] lookupName: theCollectionId inContext: context acquire: NO];
|
|
|
|
if (!collection || ([collection isKindOfClass: [NSException class]]))
|
|
|
|
collection = nil;
|
|
|
|
|
2014-02-03 16:24:33 +01:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case ActiveSyncEventFolder:
|
|
|
|
case ActiveSyncTaskFolder:
|
|
|
|
{
|
2014-10-29 19:20:03 +01:00
|
|
|
collection = [[[[context activeUser] homeFolderInContext: context] lookupName: @"Calendar" inContext: context acquire: NO] lookupName: theCollectionId inContext: context acquire: NO];
|
|
|
|
if (!collection || ([collection isKindOfClass: [NSException class]]))
|
|
|
|
collection = nil;
|
2014-02-03 16:24:33 +01:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case ActiveSyncMailFolder:
|
|
|
|
default:
|
|
|
|
{
|
|
|
|
SOGoMailAccounts *accountsFolder;
|
|
|
|
SOGoMailFolder *currentFolder;
|
|
|
|
SOGoUserFolder *userFolder;
|
|
|
|
|
|
|
|
userFolder = [[context activeUser] homeFolderInContext: context];
|
|
|
|
accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
|
|
|
|
currentFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO];
|
2014-05-27 20:44:57 +02:00
|
|
|
|
2014-02-03 16:24:33 +01:00
|
|
|
collection = [currentFolder lookupName: [NSString stringWithFormat: @"folder%@", theCollectionId]
|
|
|
|
inContext: context
|
|
|
|
acquire: NO];
|
2014-10-29 19:20:03 +01:00
|
|
|
if (![(SOGoMailFolder *)collection exists])
|
|
|
|
collection = nil;
|
2014-02-03 16:24:33 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return collection;
|
|
|
|
}
|
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
- (void) processFolderCreate: (id <DOMElement>) theDocumentElement
|
|
|
|
inResponse: (WOResponse *) theResponse
|
|
|
|
{
|
|
|
|
NSString *parentId, *displayName, *nameInContainer, *syncKey;
|
|
|
|
SOGoUserFolder *userFolder;
|
|
|
|
NSMutableString *s;
|
|
|
|
NSData *d;
|
|
|
|
|
|
|
|
int type;
|
|
|
|
|
|
|
|
parentId = [[(id)[theDocumentElement getElementsByTagName: @"ParentId"] lastObject] textValue];
|
|
|
|
displayName = [[(id)[theDocumentElement getElementsByTagName: @"DisplayName"] lastObject] textValue];
|
|
|
|
type = [[[(id)[theDocumentElement getElementsByTagName: @"Type"] lastObject] textValue] intValue];
|
|
|
|
userFolder = [[context activeUser] homeFolderInContext: context];
|
|
|
|
|
|
|
|
// See 2.2.3.170.2 Type (FolderCreate) - http://msdn.microsoft.com/en-us/library/gg675445(v=exchg.80).aspx
|
|
|
|
// We support the following types:
|
|
|
|
//
|
|
|
|
// 12 User-created mail folder
|
|
|
|
// 13 User-created Calendar folder
|
|
|
|
// 14 User-created Contacts folder
|
|
|
|
// 15 User-created Tasks folder
|
|
|
|
//
|
2014-01-14 16:49:55 +01:00
|
|
|
switch (type)
|
2014-01-10 20:12:53 +01:00
|
|
|
{
|
|
|
|
case 12:
|
|
|
|
{
|
|
|
|
SOGoMailAccounts *accountsFolder;
|
|
|
|
SOGoMailFolder *newFolder;
|
|
|
|
id currentFolder;
|
|
|
|
|
|
|
|
accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
|
|
|
|
currentFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO];
|
2014-04-09 16:57:56 +02:00
|
|
|
|
|
|
|
// If the parrent is 0 -> ok ; otherwise need to build the foldername based on parentId + displayName
|
|
|
|
if ([parentId isEqualToString: @"0"])
|
|
|
|
newFolder = [currentFolder lookupName: [NSString stringWithFormat: @"folder%@", [displayName stringByEncodingImap4FolderName]]
|
|
|
|
inContext: context
|
|
|
|
acquire: NO];
|
|
|
|
else
|
2014-05-27 20:44:57 +02:00
|
|
|
{
|
|
|
|
parentId = [self globallyUniqueIDToIMAPFolderName: [[parentId stringByUnescapingURL] substringFromIndex: 5] type: ActiveSyncMailFolder];
|
|
|
|
newFolder = [currentFolder lookupName: [NSString stringWithFormat: @"folder%@/%@", [parentId stringByEncodingImap4FolderName],
|
|
|
|
[displayName stringByEncodingImap4FolderName]]
|
|
|
|
inContext: context
|
|
|
|
acquire: NO];
|
|
|
|
}
|
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
// FIXME
|
|
|
|
// handle exists (status == 2)
|
|
|
|
// handle right synckey
|
|
|
|
if ([newFolder create])
|
|
|
|
{
|
2014-05-27 20:44:57 +02:00
|
|
|
SOGoMailAccount *accountFolder;
|
|
|
|
NSDictionary *imapGUIDs;
|
|
|
|
SOGoCacheGCSObject *o;
|
|
|
|
NSString *key;
|
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
nameInContainer = [newFolder nameInContainer];
|
2014-05-27 20:44:57 +02:00
|
|
|
|
|
|
|
accountFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO];
|
|
|
|
imapGUIDs = [accountFolder imapFolderGUIDs];
|
2014-10-29 19:20:03 +01:00
|
|
|
nameInContainer =[imapGUIDs objectForKey: nameInContainer];
|
2014-05-27 20:44:57 +02:00
|
|
|
|
2014-10-29 19:20:03 +01:00
|
|
|
key = [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], nameInContainer ];
|
2014-05-27 20:44:57 +02:00
|
|
|
o = [SOGoCacheGCSObject objectWithName: key inContainer: nil];
|
|
|
|
[o setObjectType: ActiveSyncFolderCacheObject];
|
|
|
|
[o setTableUrl: [self folderTableURL]];
|
|
|
|
[o reloadIfNeeded];
|
2014-10-29 19:20:03 +01:00
|
|
|
[[o properties ] setObject: [[newFolder nameInContainer] substringFromIndex: 6] forKey: @"displayName"];
|
2014-05-27 20:44:57 +02:00
|
|
|
[o save];
|
|
|
|
|
2016-01-07 15:58:44 +01:00
|
|
|
nameInContainer = [NSString stringWithFormat: @"mail/%@", [nameInContainer substringFromIndex: 6]];
|
2014-01-10 20:12:53 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
[theResponse setStatus: 500];
|
|
|
|
[theResponse appendContentString: @"Unable to create folder."];
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 13:
|
|
|
|
case 15:
|
|
|
|
{
|
|
|
|
SOGoAppointmentFolders *appointmentFolders;
|
2014-10-29 19:20:03 +01:00
|
|
|
SOGoCacheGCSObject *o;
|
|
|
|
NSString *key;
|
2016-01-25 16:21:23 +01:00
|
|
|
id newFolder;
|
2014-10-29 19:20:03 +01:00
|
|
|
|
|
|
|
nameInContainer = nil;
|
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
appointmentFolders = [userFolder privateCalendars: @"Calendar" inContext: context];
|
|
|
|
[appointmentFolders newFolderWithName: displayName
|
|
|
|
nameInContainer: &nameInContainer];
|
2016-01-25 16:21:23 +01:00
|
|
|
|
|
|
|
newFolder = [appointmentFolders lookupName: nameInContainer
|
|
|
|
inContext: context
|
|
|
|
acquire: NO];
|
|
|
|
[newFolder setSynchronize: YES];
|
|
|
|
|
2014-01-14 17:44:33 +01:00
|
|
|
if (type == 13)
|
|
|
|
nameInContainer = [NSString stringWithFormat: @"vevent/%@", nameInContainer];
|
|
|
|
else
|
|
|
|
nameInContainer = [NSString stringWithFormat: @"vtodo/%@", nameInContainer];
|
2016-01-25 16:21:23 +01:00
|
|
|
|
2014-10-29 19:20:03 +01:00
|
|
|
key = [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], nameInContainer ];
|
|
|
|
o = [SOGoCacheGCSObject objectWithName: key inContainer: nil];
|
|
|
|
[o setObjectType: ActiveSyncFolderCacheObject];
|
|
|
|
[o setTableUrl: [self folderTableURL]];
|
|
|
|
[o reloadIfNeeded];
|
|
|
|
[[o properties ] setObject: displayName forKey: @"displayName"];
|
|
|
|
[o save];
|
2014-01-10 20:12:53 +01:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 14:
|
|
|
|
{
|
|
|
|
SOGoContactFolders *contactFolders;
|
2014-10-29 19:20:03 +01:00
|
|
|
SOGoCacheGCSObject *o;
|
|
|
|
NSString *key;
|
2016-01-25 16:21:23 +01:00
|
|
|
id newFolder;
|
2014-10-29 19:20:03 +01:00
|
|
|
|
|
|
|
nameInContainer = nil;
|
2014-01-10 20:12:53 +01:00
|
|
|
|
|
|
|
contactFolders = [userFolder privateContacts: @"Contacts" inContext: context];
|
|
|
|
[contactFolders newFolderWithName: displayName
|
|
|
|
nameInContainer: &nameInContainer];
|
2016-01-25 16:21:23 +01:00
|
|
|
|
|
|
|
newFolder = [contactFolders lookupName: nameInContainer
|
|
|
|
inContext: context
|
|
|
|
acquire: NO];
|
|
|
|
[newFolder setSynchronize: YES];
|
|
|
|
|
2014-01-14 17:44:33 +01:00
|
|
|
nameInContainer = [NSString stringWithFormat: @"vcard/%@", nameInContainer];
|
2014-10-29 19:20:03 +01:00
|
|
|
|
|
|
|
key = [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], nameInContainer ];
|
|
|
|
o = [SOGoCacheGCSObject objectWithName: key inContainer: nil];
|
|
|
|
[o setObjectType: ActiveSyncFolderCacheObject];
|
|
|
|
[o setTableUrl: [self folderTableURL]];
|
|
|
|
[o reloadIfNeeded];
|
|
|
|
[[o properties ] setObject: displayName forKey: @"displayName"];
|
|
|
|
[o save];
|
2014-01-10 20:12:53 +01:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
{
|
|
|
|
[theResponse setStatus: 500];
|
|
|
|
[theResponse appendContentString: @"Unsupported folder type during creation."];
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} // switch (type) ...
|
|
|
|
|
|
|
|
//
|
2014-01-14 22:08:04 +01:00
|
|
|
// We update the FolderSync's synckey
|
|
|
|
//
|
|
|
|
syncKey = [[NSProcessInfo processInfo] globallyUniqueString];
|
|
|
|
|
|
|
|
[self _setFolderSyncKey: syncKey];
|
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
// All good, we send our response. The format is documented here:
|
|
|
|
// 6.7 FolderCreate Response Schema - http://msdn.microsoft.com/en-us/library/dn338950(v=exchg.80).aspx
|
|
|
|
//
|
|
|
|
s = [NSMutableString string];
|
|
|
|
[s appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
|
|
|
|
[s appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
|
|
|
|
[s appendString: @"<FolderCreate xmlns=\"FolderHierarchy:\">"];
|
|
|
|
[s appendFormat: @"<Status>%d</Status>", 1];
|
|
|
|
[s appendFormat: @"<SyncKey>%@</SyncKey>", syncKey];
|
2015-06-10 16:12:15 +02:00
|
|
|
[s appendFormat: @"<ServerId>%@</ServerId>", [nameInContainer stringByEscapingURL]];
|
2014-01-10 20:12:53 +01:00
|
|
|
[s appendString: @"</FolderCreate>"];
|
|
|
|
|
|
|
|
d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
|
|
|
|
|
|
|
|
[theResponse setContent: d];
|
|
|
|
}
|
|
|
|
|
2014-01-15 15:36:25 +01:00
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
- (void) processFolderDelete: (id <DOMElement>) theDocumentElement
|
|
|
|
inResponse: (WOResponse *) theResponse
|
|
|
|
{
|
|
|
|
SOGoMailAccounts *accountsFolder;
|
|
|
|
SOGoUserFolder *userFolder;
|
2014-10-29 19:20:03 +01:00
|
|
|
id currentFolder, folderToDelete;
|
|
|
|
NSString *serverId, *nameInCache, *key, *syncKey;
|
|
|
|
SOGoCacheGCSObject *o;
|
|
|
|
NSMutableString *s;
|
|
|
|
NSData *d;
|
2014-01-15 15:36:25 +01:00
|
|
|
|
2014-10-29 19:20:03 +01:00
|
|
|
SOGoMicrosoftActiveSyncFolderType folderType;
|
|
|
|
|
2014-01-15 15:36:25 +01:00
|
|
|
serverId = [[[(id)[theDocumentElement getElementsByTagName: @"ServerId"] lastObject] textValue] realCollectionIdWithFolderType: &folderType];
|
2014-10-29 19:20:03 +01:00
|
|
|
nameInCache = serverId;
|
2014-05-27 20:44:57 +02:00
|
|
|
serverId = [self globallyUniqueIDToIMAPFolderName: serverId type: folderType];
|
2014-01-15 15:36:25 +01:00
|
|
|
userFolder = [[context activeUser] homeFolderInContext: context];
|
|
|
|
|
2014-10-29 19:20:03 +01:00
|
|
|
switch (folderType)
|
2014-01-15 15:36:25 +01:00
|
|
|
{
|
2014-10-29 19:20:03 +01:00
|
|
|
case ActiveSyncMailFolder:
|
|
|
|
{
|
|
|
|
nameInCache = [NSString stringWithFormat: @"folder%@", nameInCache];
|
|
|
|
accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
|
|
|
|
currentFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO];
|
|
|
|
|
|
|
|
folderToDelete = [currentFolder lookupName: [NSString stringWithFormat: @"folder%@", serverId]
|
|
|
|
inContext: context
|
|
|
|
acquire: NO];
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case ActiveSyncEventFolder:
|
|
|
|
case ActiveSyncTaskFolder:
|
|
|
|
{
|
|
|
|
SOGoAppointmentFolders *appointmentFolders;
|
2014-01-15 15:36:25 +01:00
|
|
|
|
2014-10-29 19:20:03 +01:00
|
|
|
if (folderType == ActiveSyncEventFolder)
|
|
|
|
nameInCache = [NSString stringWithFormat: @"vevent/%@", serverId];
|
|
|
|
else
|
|
|
|
nameInCache = [NSString stringWithFormat: @"vtodo/%@", serverId];
|
|
|
|
|
|
|
|
appointmentFolders = [userFolder privateCalendars: @"Calendar" inContext: context];
|
|
|
|
|
|
|
|
folderToDelete = [appointmentFolders lookupName: [NSString stringWithFormat: @"%@", serverId]
|
|
|
|
inContext: context
|
|
|
|
acquire: NO];
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
{
|
|
|
|
[theResponse setStatus: 500];
|
|
|
|
[theResponse appendContentString: @"Unsupported folder type during creation."];
|
|
|
|
return;
|
|
|
|
}
|
2014-01-15 15:36:25 +01:00
|
|
|
}
|
2014-10-29 19:20:03 +01:00
|
|
|
|
|
|
|
// FIXME: we should handle exception here
|
|
|
|
[folderToDelete delete];
|
|
|
|
|
|
|
|
//
|
|
|
|
// We destroy the cache object
|
|
|
|
//
|
|
|
|
key = [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], nameInCache];
|
|
|
|
o = [SOGoCacheGCSObject objectWithName: key inContainer: nil];
|
|
|
|
[o setTableUrl: [self folderTableURL]];
|
|
|
|
[o destroy];
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
// We update the FolderSync's synckey
|
|
|
|
//
|
|
|
|
syncKey = [[NSProcessInfo processInfo] globallyUniqueString];
|
|
|
|
|
|
|
|
[self _setFolderSyncKey: syncKey];
|
|
|
|
|
|
|
|
s = [NSMutableString string];
|
|
|
|
[s appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
|
|
|
|
[s appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
|
|
|
|
[s appendString: @"<FolderDelete xmlns=\"FolderHierarchy:\">"];
|
|
|
|
[s appendFormat: @"<Status>%d</Status>", 1];
|
|
|
|
[s appendFormat: @"<SyncKey>%@</SyncKey>", syncKey];
|
|
|
|
[s appendString: @"</FolderDelete>"];
|
|
|
|
|
|
|
|
d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
|
|
|
|
[theResponse setContent: d];
|
2014-01-15 15:36:25 +01:00
|
|
|
}
|
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
- (void) processFolderUpdate: (id <DOMElement>) theDocumentElement
|
|
|
|
inResponse: (WOResponse *) theResponse
|
|
|
|
{
|
2014-10-29 19:20:03 +01:00
|
|
|
NSString *serverId, *parentId, *displayName, *newName, *nameInCache, *syncKey, *key;
|
2014-01-10 20:12:53 +01:00
|
|
|
SOGoUserFolder *userFolder;
|
2014-10-29 19:20:03 +01:00
|
|
|
SOGoCacheGCSObject *o;
|
|
|
|
NSMutableString *s;
|
2014-01-10 20:12:53 +01:00
|
|
|
id currentFolder;
|
2014-10-29 19:20:03 +01:00
|
|
|
NSData *d;
|
2014-01-10 20:12:53 +01:00
|
|
|
|
2014-01-14 19:50:17 +01:00
|
|
|
SOGoMicrosoftActiveSyncFolderType folderType;
|
|
|
|
|
|
|
|
serverId = [[[(id)[theDocumentElement getElementsByTagName: @"ServerId"] lastObject] textValue] realCollectionIdWithFolderType: &folderType];
|
2014-10-29 19:20:03 +01:00
|
|
|
|
|
|
|
nameInCache = [NSString stringWithFormat: @"folder%@", serverId];
|
|
|
|
|
2014-05-27 20:44:57 +02:00
|
|
|
serverId = [self globallyUniqueIDToIMAPFolderName: serverId type: folderType];
|
2014-01-10 20:12:53 +01:00
|
|
|
parentId = [[(id)[theDocumentElement getElementsByTagName: @"ParentId"] lastObject] textValue];
|
|
|
|
displayName = [[(id)[theDocumentElement getElementsByTagName: @"DisplayName"] lastObject] textValue];
|
|
|
|
|
|
|
|
userFolder = [[context activeUser] homeFolderInContext: context];
|
|
|
|
|
|
|
|
|
2014-10-29 19:20:03 +01:00
|
|
|
switch (folderType)
|
2014-01-10 20:12:53 +01:00
|
|
|
{
|
2014-10-29 19:20:03 +01:00
|
|
|
case ActiveSyncMailFolder:
|
|
|
|
{
|
|
|
|
SOGoMailAccounts *accountsFolder;
|
|
|
|
SOGoMailFolder *folderToUpdate;
|
2014-06-10 19:07:14 +02:00
|
|
|
|
2014-10-29 19:20:03 +01:00
|
|
|
accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
|
|
|
|
currentFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO];
|
|
|
|
|
|
|
|
folderToUpdate = [currentFolder lookupName: [NSString stringWithFormat: @"folder%@", serverId]
|
|
|
|
inContext: context
|
|
|
|
acquire: NO];
|
2014-01-24 22:28:08 +01:00
|
|
|
|
2014-10-29 19:20:03 +01:00
|
|
|
// If parent is 0 or displayname is not changed it is either a rename of a folder in 0 or a move to 0
|
|
|
|
if ([parentId isEqualToString: @"0"] ||
|
|
|
|
([serverId hasSuffix: [NSString stringWithFormat: @"/%@", displayName]] && [parentId isEqualToString: @"0"]))
|
|
|
|
{
|
|
|
|
newName = [NSString stringWithFormat: @"%@", [displayName stringByEncodingImap4FolderName]];
|
2014-01-24 22:28:08 +01:00
|
|
|
|
2014-10-29 19:20:03 +01:00
|
|
|
// FIXME: handle exception here
|
|
|
|
[folderToUpdate renameTo: [NSString stringWithFormat: @"/%@", [displayName stringByEncodingImap4FolderName]]];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
parentId = [self globallyUniqueIDToIMAPFolderName: [[parentId stringByUnescapingURL] substringFromIndex: 5] type: folderType];
|
|
|
|
newName = [NSString stringWithFormat: @"%@/%@", [parentId stringByEncodingImap4FolderName], [displayName stringByEncodingImap4FolderName]];
|
|
|
|
|
|
|
|
// FIXME: handle exception here
|
|
|
|
[folderToUpdate renameTo: newName];
|
|
|
|
}
|
2014-01-14 22:08:04 +01:00
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
|
2014-10-29 19:20:03 +01:00
|
|
|
//
|
|
|
|
// We update our cache
|
|
|
|
//
|
|
|
|
key = [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], nameInCache];
|
|
|
|
o = [SOGoCacheGCSObject objectWithName: key inContainer: nil];
|
|
|
|
[o setObjectType: ActiveSyncFolderCacheObject];
|
|
|
|
[o setTableUrl: [self folderTableURL]];
|
|
|
|
[o reloadIfNeeded];
|
|
|
|
[[o properties ] setObject: newName forKey: @"displayName"];
|
|
|
|
[o save];
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case ActiveSyncEventFolder:
|
|
|
|
case ActiveSyncTaskFolder:
|
|
|
|
{
|
|
|
|
SOGoAppointmentFolders *appointmentFolders;
|
|
|
|
SOGoAppointmentFolder *folderToUpdate;
|
|
|
|
NSString *nameInCache;
|
|
|
|
|
|
|
|
appointmentFolders = [userFolder privateCalendars: @"Calendar" inContext: context];
|
|
|
|
|
|
|
|
folderToUpdate = [appointmentFolders lookupName: [NSString stringWithFormat: @"%@", serverId]
|
|
|
|
inContext: context
|
|
|
|
acquire: NO];
|
|
|
|
|
|
|
|
// update the cache anyway regardless of any error; if the rename fails next folderSync will to the cleanup
|
|
|
|
[folderToUpdate renameTo: [NSString stringWithFormat: @"%@", [displayName stringByEncodingImap4FolderName]]];
|
|
|
|
|
|
|
|
if (folderType == ActiveSyncEventFolder)
|
|
|
|
nameInCache = [NSString stringWithFormat: @"vevent/%@", serverId];
|
|
|
|
else
|
|
|
|
nameInCache = [NSString stringWithFormat: @"vtodo/%@",serverId];
|
|
|
|
|
|
|
|
key = [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], nameInCache ];
|
|
|
|
o = [SOGoCacheGCSObject objectWithName: key inContainer: nil];
|
|
|
|
[o setObjectType: ActiveSyncFolderCacheObject];
|
|
|
|
[o setTableUrl: [self folderTableURL]];
|
|
|
|
[o reloadIfNeeded];
|
|
|
|
[[o properties ] setObject: displayName forKey: @"displayName"];
|
|
|
|
[o save];
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
{
|
|
|
|
[theResponse setStatus: 500];
|
|
|
|
[theResponse appendContentString: @"Unsupported folder type during creation."];
|
|
|
|
return;
|
|
|
|
}
|
2014-01-10 20:12:53 +01:00
|
|
|
}
|
2014-10-29 19:20:03 +01:00
|
|
|
|
|
|
|
//
|
|
|
|
// We update the FolderSync's synckey
|
|
|
|
//
|
|
|
|
syncKey = [[NSProcessInfo processInfo] globallyUniqueString];
|
|
|
|
|
|
|
|
[self _setFolderSyncKey: syncKey];
|
|
|
|
|
|
|
|
s = [NSMutableString string];
|
|
|
|
[s appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
|
|
|
|
[s appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
|
|
|
|
[s appendString: @"<FolderUpdate xmlns=\"FolderHierarchy:\">"];
|
|
|
|
[s appendFormat: @"<Status>%d</Status>", 1];
|
|
|
|
[s appendFormat: @"<SyncKey>%@</SyncKey>", syncKey];
|
|
|
|
[s appendString: @"</FolderUpdate>"];
|
|
|
|
|
|
|
|
d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
|
|
|
|
|
|
|
|
[theResponse setContent: d];
|
2014-01-10 20:12:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
// <?xml version="1.0"?>
|
|
|
|
// <!DOCTYPE ActiveSync PUBLIC "-//MICROSOFT//DTD ActiveSync//EN" "http://www.microsoft.com/">
|
|
|
|
// <FolderSync xmlns="FolderHierarchy:">
|
|
|
|
// <SyncKey>0</SyncKey>
|
|
|
|
// </FolderSync>
|
|
|
|
//
|
|
|
|
- (void) processFolderSync: (id <DOMElement>) theDocumentElement
|
|
|
|
inResponse: (WOResponse *) theResponse
|
|
|
|
{
|
2015-10-20 21:33:18 +02:00
|
|
|
NSString *key, *cKey, *nkey, *name, *serverId, *parentId, *nameInCache, *personalFolderName, *syncKey, *folderType, *operation;
|
|
|
|
NSMutableDictionary *cachedGUIDs, *metadata;
|
|
|
|
NSMutableArray *folders, *processedFolders;
|
2014-10-29 19:20:03 +01:00
|
|
|
NSDictionary *folderMetadata, *imapGUIDs;
|
|
|
|
NSArray *allFoldersMetadata, *allKeys;
|
|
|
|
SOGoMailAccounts *accountsFolder;
|
|
|
|
SOGoMailAccount *accountFolder;
|
|
|
|
NSMutableString *s, *commands;
|
|
|
|
SOGoUserFolder *userFolder;
|
|
|
|
SoSecurityManager *sm;
|
|
|
|
SOGoCacheGCSObject *o;
|
|
|
|
id currentFolder;
|
2014-01-10 20:12:53 +01:00
|
|
|
NSData *d;
|
2014-10-29 19:20:03 +01:00
|
|
|
|
|
|
|
int status, command_count, i, type, fi, count;
|
2014-01-10 20:12:53 +01:00
|
|
|
BOOL first_sync;
|
|
|
|
|
2014-10-29 19:20:03 +01:00
|
|
|
sm = [SoSecurityManager sharedSecurityManager];
|
2015-10-14 15:21:32 +02:00
|
|
|
metadata = [self globalMetadataForDevice];
|
2014-01-10 20:12:53 +01:00
|
|
|
syncKey = [[(id)[theDocumentElement getElementsByTagName: @"SyncKey"] lastObject] textValue];
|
|
|
|
s = [NSMutableString string];
|
|
|
|
|
|
|
|
first_sync = NO;
|
|
|
|
status = 1;
|
2014-10-29 19:20:03 +01:00
|
|
|
command_count = 0;
|
|
|
|
commands = [NSMutableString string];
|
|
|
|
|
2015-07-22 15:15:34 +02:00
|
|
|
processedFolders = [NSMutableArray array];
|
|
|
|
|
2014-10-29 19:20:03 +01:00
|
|
|
[s appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
|
|
|
|
[s appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
|
2014-01-10 20:12:53 +01:00
|
|
|
|
|
|
|
if ([syncKey isEqualToString: @"0"])
|
|
|
|
{
|
|
|
|
first_sync = YES;
|
|
|
|
syncKey = @"1";
|
|
|
|
}
|
2015-07-22 15:15:34 +02:00
|
|
|
else if (![metadata objectForKey: @"FolderSyncKey"])
|
2014-01-10 20:12:53 +01:00
|
|
|
{
|
|
|
|
// Synchronization key mismatch or invalid synchronization key
|
2014-10-29 19:20:03 +01:00
|
|
|
//NSLog(@"FolderSync syncKey mismatch %@ <> %@", syncKey, metadata);
|
|
|
|
[s appendFormat: @"<FolderSync xmlns=\"FolderHierarchy:\"><Status>9</Status></FolderSync>"];
|
2014-01-10 20:12:53 +01:00
|
|
|
|
2014-10-29 19:20:03 +01:00
|
|
|
d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
|
|
|
|
[theResponse setContent: d];
|
|
|
|
return;
|
|
|
|
}
|
2014-01-10 20:12:53 +01:00
|
|
|
|
2014-10-29 19:20:03 +01:00
|
|
|
userFolder = [[context activeUser] homeFolderInContext: context];
|
|
|
|
accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
|
|
|
|
accountFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO];
|
2014-01-10 20:12:53 +01:00
|
|
|
|
2016-03-28 17:13:41 +02:00
|
|
|
if (first_sync)
|
|
|
|
{
|
|
|
|
[self _ensureFolder: (SOGoMailFolder *)[accountFolder draftsFolderInContext: context]];
|
2016-03-29 16:38:37 +02:00
|
|
|
[self _ensureFolder: (SOGoMailFolder *)[accountFolder sentFolderInContext: context]];
|
2016-03-28 17:13:41 +02:00
|
|
|
[self _ensureFolder: (SOGoMailFolder *)[accountFolder trashFolderInContext: context]];
|
|
|
|
}
|
|
|
|
|
2016-03-29 16:38:37 +02:00
|
|
|
allFoldersMetadata = [accountFolder allFoldersMetadata];
|
2014-05-27 20:44:57 +02:00
|
|
|
|
2014-10-29 19:20:03 +01:00
|
|
|
// Get GUIDs of folder (IMAP)
|
|
|
|
// e.g. {folderINBOX = folder6b93c528176f1151c7260000aef6df92}
|
|
|
|
imapGUIDs = [accountFolder imapFolderGUIDs];
|
2014-05-27 20:44:57 +02:00
|
|
|
|
2014-10-29 19:20:03 +01:00
|
|
|
cachedGUIDs = [NSMutableDictionary dictionary];
|
2014-05-27 20:44:57 +02:00
|
|
|
|
2014-10-29 19:20:03 +01:00
|
|
|
// No need to read cached folder infos during first sync. Otherwise, pull it from the database.
|
|
|
|
// e.g. {folder6b93c528176f1151c7260000aef6df92 = folderINBOX} - guid = foldername for easy reverse lookup with imapGUIDs
|
|
|
|
if (!first_sync)
|
|
|
|
{
|
|
|
|
NSArray *foldersInCache;
|
2014-05-27 20:44:57 +02:00
|
|
|
|
2014-10-29 19:20:03 +01:00
|
|
|
o = [SOGoCacheGCSObject objectWithName: @"0" inContainer: nil];
|
|
|
|
[o setObjectType: ActiveSyncFolderCacheObject];
|
|
|
|
[o setTableUrl: folderTableURL];
|
|
|
|
|
|
|
|
foldersInCache = [o cacheEntriesForDeviceId: [context objectForKey: @"DeviceId"] newerThanVersion: -1];
|
|
|
|
|
|
|
|
// get guids of folders stored in cache
|
|
|
|
for (i = 0; i < [foldersInCache count]; i++)
|
|
|
|
{
|
|
|
|
key = [[foldersInCache objectAtIndex: i] substringFromIndex: 1];
|
|
|
|
o = [SOGoCacheGCSObject objectWithName: key inContainer: nil];
|
|
|
|
[o setObjectType: ActiveSyncFolderCacheObject];
|
|
|
|
[o setTableUrl: [self folderTableURL]];
|
|
|
|
[o reloadIfNeeded];
|
|
|
|
|
|
|
|
// When the GUID entry exists the name of the entry has to be changed to new name
|
|
|
|
if ([[o properties] objectForKey: @"GUID"])
|
|
|
|
{
|
|
|
|
//NSLog(@"Old cacheEntry: %@ displayName: %@ GUID: %@", key, [[o properties] objectForKey: @"displayName"], [[o properties] objectForKey: @"GUID"]);
|
|
|
|
key = [NSString stringWithFormat: @"%@+folder%@", [context objectForKey: @"DeviceId"], [[o properties] objectForKey: @"GUID"]];
|
|
|
|
//NSLog(@"New cacheEntry: %@", key);
|
|
|
|
[[o properties] removeObjectForKey: @"GUID"];
|
|
|
|
[[o properties ] setObject: @"updateMe" forKey: @"displayName"];
|
|
|
|
[o save];
|
|
|
|
[o changePathTo: [NSString stringWithFormat: @"%@", key]];
|
|
|
|
}
|
|
|
|
|
|
|
|
// no dispalay Name
|
|
|
|
if (![[o properties] objectForKey: @"displayName"])
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if ([key rangeOfString: @"+folder" options: NSCaseInsensitiveSearch].location != NSNotFound)
|
|
|
|
[cachedGUIDs setObject: [NSString stringWithFormat: @"folder%@", [[o properties] objectForKey: @"displayName"]] // e.g. CDB648DDBC5040F8AC90792383DBBBAA+folderINBOX
|
|
|
|
forKey: [key substringFromIndex: [key rangeOfString: @"+"].location+1]];
|
|
|
|
else
|
|
|
|
[cachedGUIDs setObject: [key substringFromIndex: [key rangeOfString: @"+"].location+1] // e.g. CDB648DDBC5040F8AC90792383DBBBAA+vcard/personal
|
|
|
|
forKey: [key substringFromIndex: [key rangeOfString: @"+"].location+1]];
|
|
|
|
}
|
|
|
|
}
|
2014-05-27 20:44:57 +02:00
|
|
|
|
2014-10-29 19:20:03 +01:00
|
|
|
// Handle folders that have been deleted on server
|
|
|
|
allKeys = [cachedGUIDs allKeys];
|
2014-01-10 20:12:53 +01:00
|
|
|
|
2014-10-29 19:20:03 +01:00
|
|
|
for (i = 0; i < [allKeys count]; i++)
|
|
|
|
{
|
|
|
|
cKey = [allKeys objectAtIndex: i];
|
|
|
|
|
|
|
|
// if a cache entry is not found in imapGUIDs its either an imap which has been deleted or its an other folder type which can be checked via lookupName.
|
|
|
|
if (![imapGUIDs allKeysForObject: cKey])
|
|
|
|
{
|
|
|
|
// Destroy folders cache content to avoid stale data if a new folder gets created with the same name
|
|
|
|
key = [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], cKey];
|
|
|
|
o = [SOGoCacheGCSObject objectWithName: key inContainer: nil];
|
|
|
|
[o setObjectType: ActiveSyncFolderCacheObject];
|
|
|
|
[o setTableUrl: [self folderTableURL]];
|
|
|
|
[o reloadIfNeeded];
|
|
|
|
|
2014-12-29 18:43:20 +01:00
|
|
|
if ([cKey hasPrefix: @"folder"] || [cKey isEqualToString:@"(null)"])
|
2014-10-29 19:20:03 +01:00
|
|
|
{
|
|
|
|
[commands appendFormat: @"<Delete><ServerId>%@</ServerId></Delete>", [[NSString stringWithFormat: @"mail/%@", [cKey substringFromIndex: 6]] stringByEscapingURL]] ;
|
|
|
|
command_count++;
|
|
|
|
|
|
|
|
[o destroy];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2015-10-20 21:33:18 +02:00
|
|
|
if ([cKey rangeOfString: @"vevent" options: NSCaseInsensitiveSearch].location != NSNotFound ||
|
|
|
|
[cKey rangeOfString: @"vtodo" options: NSCaseInsensitiveSearch].location != NSNotFound)
|
|
|
|
folderType = @"Calendar";
|
|
|
|
else
|
|
|
|
folderType = @"Contacts";
|
|
|
|
|
2016-06-06 19:28:42 +02:00
|
|
|
currentFolder = nil;
|
|
|
|
|
|
|
|
if ([cKey rangeOfString: @"/"].location != NSNotFound)
|
2015-10-20 21:33:18 +02:00
|
|
|
currentFolder = [[[[context activeUser] homeFolderInContext: context] lookupName: folderType inContext: context acquire: NO]
|
2014-10-29 19:20:03 +01:00
|
|
|
lookupName: [cKey substringFromIndex: [cKey rangeOfString: @"/"].location+1] inContext: context acquire: NO];
|
|
|
|
|
2015-11-05 20:58:58 +01:00
|
|
|
// We skip personal GCS folders - we always want to synchronize these
|
|
|
|
if ([currentFolder isKindOfClass: [SOGoGCSFolder class]] &&
|
|
|
|
[[currentFolder nameInContainer] isEqualToString: @"personal"])
|
|
|
|
continue;
|
|
|
|
|
2015-10-20 21:33:18 +02:00
|
|
|
// Remove the folder from device if it doesn't exist, we don't want to sync it, or it doesn't have the proper permissions
|
|
|
|
if (!currentFolder ||
|
|
|
|
![currentFolder synchronize] ||
|
|
|
|
[sm validatePermission: SoPerm_DeleteObjects
|
|
|
|
onObject: currentFolder
|
|
|
|
inContext: context] ||
|
|
|
|
[sm validatePermission: SoPerm_AddDocumentsImagesAndFiles
|
|
|
|
onObject: currentFolder
|
|
|
|
inContext: context])
|
|
|
|
{
|
|
|
|
[commands appendFormat: @"<Delete><ServerId>%@</ServerId></Delete>", [cKey stringByEscapingURL] ];
|
|
|
|
command_count++;
|
|
|
|
[o destroy];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-10-29 19:20:03 +01:00
|
|
|
|
|
|
|
// Handle addition and changes
|
|
|
|
for (i = 0; i < [allFoldersMetadata count]; i++)
|
|
|
|
{
|
|
|
|
folderMetadata = [allFoldersMetadata objectAtIndex: i];
|
|
|
|
|
|
|
|
nameInCache = [NSString stringWithFormat: @"folder%@", [[folderMetadata objectForKey: @"path"] substringFromIndex: 1]];
|
2014-12-29 18:43:20 +01:00
|
|
|
|
|
|
|
// we have no guid - ignore the folder
|
|
|
|
if (![imapGUIDs objectForKey: nameInCache])
|
|
|
|
continue;
|
|
|
|
|
2014-10-29 19:20:03 +01:00
|
|
|
serverId = [NSString stringWithFormat: @"mail/%@", [[imapGUIDs objectForKey: nameInCache] substringFromIndex: 6]];
|
|
|
|
name = [folderMetadata objectForKey: @"displayName"];
|
2015-07-22 15:15:34 +02:00
|
|
|
|
|
|
|
// avoid duplicate folders if folder is returned by different imap namespaces
|
|
|
|
if ([processedFolders indexOfObject: serverId] == NSNotFound)
|
|
|
|
[processedFolders addObject: serverId];
|
|
|
|
else
|
|
|
|
continue;
|
|
|
|
|
2014-10-29 19:20:03 +01:00
|
|
|
if ([name hasPrefix: @"/"])
|
|
|
|
name = [name substringFromIndex: 1];
|
2014-05-27 20:44:57 +02:00
|
|
|
|
2014-10-29 19:20:03 +01:00
|
|
|
if ([name hasSuffix: @"/"])
|
|
|
|
name = [name substringToIndex: [name length]-1];
|
2014-05-27 20:44:57 +02:00
|
|
|
|
2014-10-29 19:20:03 +01:00
|
|
|
type = [[folderMetadata objectForKey: @"type"] activeSyncFolderType];
|
|
|
|
parentId = @"0";
|
|
|
|
|
|
|
|
if ([folderMetadata objectForKey: @"parent"])
|
|
|
|
{
|
2015-07-22 15:15:34 +02:00
|
|
|
// make sure that parent of main-folders is always 0
|
|
|
|
if (type == 12)
|
|
|
|
parentId = [NSString stringWithFormat: @"mail/%@", [[imapGUIDs objectForKey: [NSString stringWithFormat: @"folder%@", [[folderMetadata objectForKey: @"parent"] substringFromIndex: 1]]] substringFromIndex: 6]];
|
|
|
|
|
2014-10-29 19:20:03 +01:00
|
|
|
name = [[name pathComponents] lastObject];
|
|
|
|
}
|
2014-05-27 20:44:57 +02:00
|
|
|
|
2014-10-29 19:20:03 +01:00
|
|
|
// Decide between add and change
|
|
|
|
if ([cachedGUIDs objectForKey: [imapGUIDs objectForKey: nameInCache]])
|
|
|
|
{
|
|
|
|
// Search GUID to check name change in cache (diff between IMAP and cache)
|
|
|
|
key = [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], [cachedGUIDs objectForKey: [imapGUIDs objectForKey: nameInCache ]]];
|
|
|
|
nkey = [NSString stringWithFormat: @"%@+folder%@", [context objectForKey: @"DeviceId"], [[folderMetadata objectForKey: @"path"] substringFromIndex: 1] ];
|
|
|
|
|
|
|
|
if (![key isEqualToString: nkey])
|
|
|
|
{
|
|
|
|
[commands appendFormat: @"<Update><ServerId>%@</ServerId><ParentId>%@</ParentId><DisplayName>%@</DisplayName><Type>%d</Type></Update>",
|
|
|
|
[serverId stringByEscapingURL],
|
|
|
|
[parentId stringByEscapingURL],
|
|
|
|
[name activeSyncRepresentationInContext: context], type];
|
2014-05-27 20:44:57 +02:00
|
|
|
|
2014-10-29 19:20:03 +01:00
|
|
|
// Change path in cache
|
|
|
|
o = [SOGoCacheGCSObject objectWithName: [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], [imapGUIDs objectForKey: nameInCache ]] inContainer: nil];
|
|
|
|
[o setObjectType: ActiveSyncFolderCacheObject];
|
|
|
|
[o setTableUrl: [self folderTableURL]];
|
|
|
|
[o reloadIfNeeded];
|
|
|
|
|
|
|
|
[[o properties ] setObject: [[folderMetadata objectForKey: @"path"] substringFromIndex: 1] forKey: @"displayName"];
|
|
|
|
[o save];
|
|
|
|
|
|
|
|
command_count++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
[commands appendFormat: @"<Add><ServerId>%@</ServerId><ParentId>%@</ParentId><DisplayName>%@</DisplayName><Type>%d</Type></Add>",
|
2014-05-27 20:44:57 +02:00
|
|
|
[serverId stringByEscapingURL],
|
|
|
|
[parentId stringByEscapingURL],
|
2014-10-29 19:20:03 +01:00
|
|
|
[name activeSyncRepresentationInContext: context], type];
|
2014-05-27 20:44:57 +02:00
|
|
|
|
2014-10-29 19:20:03 +01:00
|
|
|
// Store folder's displayName in cache
|
|
|
|
key = [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], [imapGUIDs objectForKey: nameInCache ]];
|
|
|
|
o = [SOGoCacheGCSObject objectWithName: key inContainer: nil];
|
|
|
|
[o setObjectType: ActiveSyncFolderCacheObject];
|
|
|
|
[o setTableUrl: [self folderTableURL]];
|
|
|
|
[o reloadIfNeeded];
|
2014-05-27 20:44:57 +02:00
|
|
|
|
2014-10-29 19:20:03 +01:00
|
|
|
[[o properties ] setObject: [[folderMetadata objectForKey: @"path"] substringFromIndex: 1] forKey: @"displayName"];
|
2014-12-08 16:45:34 +01:00
|
|
|
|
|
|
|
// clean cache content to avoid stale data
|
|
|
|
[[o properties] removeObjectForKey: @"SyncKey"];
|
|
|
|
[[o properties] removeObjectForKey: @"SyncCache"];
|
|
|
|
[[o properties] removeObjectForKey: @"DateCache"];
|
|
|
|
[[o properties] removeObjectForKey: @"MoreAvailable"];
|
2015-03-30 15:49:44 +02:00
|
|
|
[[o properties] removeObjectForKey: @"BodyPreferenceType"];
|
2015-10-26 15:15:35 +01:00
|
|
|
[[o properties] removeObjectForKey: @"SupportedElements"];
|
2014-12-08 16:45:34 +01:00
|
|
|
[[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"];
|
2015-09-09 16:20:31 +02:00
|
|
|
[[o properties] removeObjectForKey: @"InitialLoadSequence"];
|
2016-03-28 14:43:56 +02:00
|
|
|
[[o properties] removeObjectForKey: @"FirstIdInCache"];
|
|
|
|
[[o properties] removeObjectForKey: @"LastIdInCache"];
|
|
|
|
|
2014-10-29 19:20:03 +01:00
|
|
|
[o save];
|
2014-05-27 20:44:57 +02:00
|
|
|
|
2014-10-29 19:20:03 +01:00
|
|
|
command_count++;
|
|
|
|
}
|
2014-01-10 20:12:53 +01:00
|
|
|
}
|
2014-10-29 19:20:03 +01:00
|
|
|
|
|
|
|
personalFolderName = [[[context activeUser] personalCalendarFolderInContext: context] nameInContainer];
|
|
|
|
folders = [[[[[context activeUser] homeFolderInContext: context] lookupName: @"Calendar" inContext: context acquire: NO] subFolders] mutableCopy];
|
2015-11-05 17:11:18 +01:00
|
|
|
[folders autorelease];
|
|
|
|
|
2014-10-29 19:20:03 +01:00
|
|
|
[folders addObjectsFromArray: [[[[context activeUser] homeFolderInContext: context] lookupName: @"Contacts" inContext: context acquire: NO] subFolders]];
|
|
|
|
|
2015-10-20 21:33:18 +02:00
|
|
|
// We remove all the folders that aren't GCS-ones, that we don't want to synchronize and
|
2015-11-05 17:11:18 +01:00
|
|
|
// the ones without write/delete permissions.
|
2014-10-29 19:20:03 +01:00
|
|
|
count = [folders count]-1;
|
|
|
|
for (; count >= 0; count--)
|
|
|
|
{
|
2015-11-05 17:11:18 +01:00
|
|
|
currentFolder = [folders objectAtIndex: count];
|
|
|
|
|
|
|
|
// We skip personal GCS folders - we always want to synchronize these
|
|
|
|
if ([currentFolder isKindOfClass: [SOGoGCSFolder class]] &&
|
|
|
|
[[currentFolder nameInContainer] isEqualToString: @"personal"])
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (![currentFolder isKindOfClass: [SOGoGCSFolder class]] ||
|
|
|
|
![currentFolder synchronize] ||
|
2015-10-20 21:33:18 +02:00
|
|
|
[sm validatePermission: SoPerm_DeleteObjects
|
2015-11-05 17:11:18 +01:00
|
|
|
onObject: currentFolder
|
2014-10-29 19:20:03 +01:00
|
|
|
inContext: context] ||
|
|
|
|
[sm validatePermission: SoPerm_AddDocumentsImagesAndFiles
|
2015-11-05 17:11:18 +01:00
|
|
|
onObject: currentFolder
|
2014-10-29 19:20:03 +01:00
|
|
|
inContext: context])
|
|
|
|
{
|
|
|
|
[folders removeObjectAtIndex: count];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
count = [folders count]-1;
|
|
|
|
|
|
|
|
for (fi = 0; fi <= count ; fi++)
|
|
|
|
{
|
|
|
|
if ([[folders objectAtIndex:fi] isKindOfClass: [SOGoAppointmentFolder class]])
|
|
|
|
name = [NSString stringWithFormat: @"vevent/%@", [[folders objectAtIndex:fi] nameInContainer]];
|
|
|
|
else
|
|
|
|
name = [NSString stringWithFormat: @"vcard/%@", [[folders objectAtIndex:fi] nameInContainer]];
|
|
|
|
|
|
|
|
key = [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], name];
|
|
|
|
o = [SOGoCacheGCSObject objectWithName: key inContainer: nil];
|
|
|
|
[o setObjectType: ActiveSyncFolderCacheObject];
|
|
|
|
[o setTableUrl: [self folderTableURL]];
|
|
|
|
[o reloadIfNeeded];
|
|
|
|
|
|
|
|
// Decide between add and change
|
|
|
|
if (![[o properties ] objectForKey: @"displayName"] || first_sync)
|
2014-12-08 16:45:34 +01:00
|
|
|
operation = @"Add";
|
2014-10-29 19:20:03 +01:00
|
|
|
else if (![[[o properties ] objectForKey: @"displayName"] isEqualToString: [[folders objectAtIndex:fi] displayName]])
|
2014-12-08 16:45:34 +01:00
|
|
|
operation = @"Update";
|
|
|
|
else
|
|
|
|
operation = nil;
|
2014-10-29 19:20:03 +01:00
|
|
|
|
|
|
|
if (operation)
|
|
|
|
{
|
|
|
|
if ([[folders objectAtIndex:fi] isKindOfClass: [SOGoAppointmentFolder class]])
|
|
|
|
{
|
|
|
|
type = ([[[folders objectAtIndex:fi] nameInContainer] isEqualToString: personalFolderName] ? 8 : 13);
|
|
|
|
[commands appendFormat: @"<%@><ServerId>%@</ServerId><ParentId>%@</ParentId><DisplayName>%@</DisplayName><Type>%d</Type></%@>", operation,
|
|
|
|
[name stringByEscapingURL], @"0", [[[folders objectAtIndex:fi] displayName] activeSyncRepresentationInContext: context], type, operation];
|
|
|
|
|
|
|
|
command_count++;
|
|
|
|
|
|
|
|
[[o properties ] setObject: [[folders objectAtIndex:fi] displayName] forKey: @"displayName"];
|
|
|
|
[o save];
|
|
|
|
|
|
|
|
name = [NSString stringWithFormat: @"vtodo/%@", [[folders objectAtIndex:fi] nameInContainer]];
|
|
|
|
type = ([[[folders objectAtIndex:fi] nameInContainer] isEqualToString: personalFolderName] ? 7 : 15);
|
|
|
|
[commands appendFormat: @"<%@><ServerId>%@</ServerId><ParentId>%@</ParentId><DisplayName>%@</DisplayName><Type>%d</Type></%@>", operation,
|
|
|
|
[name stringByEscapingURL], @"0", [[[folders objectAtIndex:fi] displayName] activeSyncRepresentationInContext: context], type, operation];
|
|
|
|
|
|
|
|
command_count++;
|
|
|
|
key = [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], name];
|
|
|
|
|
|
|
|
o = [SOGoCacheGCSObject objectWithName: key inContainer: nil];
|
|
|
|
[o setObjectType: ActiveSyncFolderCacheObject];
|
|
|
|
[o setTableUrl: [self folderTableURL]];
|
|
|
|
[o reloadIfNeeded];
|
|
|
|
[[o properties ] setObject: [[folders objectAtIndex:fi] displayName] forKey: @"displayName"];
|
2014-12-08 16:45:34 +01:00
|
|
|
|
|
|
|
if ([operation isEqualToString: @"Add"])
|
|
|
|
{
|
|
|
|
// clean cache content to avoid stale data
|
|
|
|
[[o properties] removeObjectForKey: @"SyncKey"];
|
|
|
|
[[o properties] removeObjectForKey: @"SyncCache"];
|
|
|
|
[[o properties] removeObjectForKey: @"DateCache"];
|
|
|
|
[[o properties] removeObjectForKey: @"MoreAvailable"];
|
2015-03-30 15:49:44 +02:00
|
|
|
[[o properties] removeObjectForKey: @"BodyPreferenceType"];
|
2015-10-26 15:15:35 +01:00
|
|
|
[[o properties] removeObjectForKey: @"SupportedElements"];
|
2014-12-08 16:45:34 +01:00
|
|
|
[[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"];
|
2015-09-09 16:20:31 +02:00
|
|
|
[[o properties] removeObjectForKey: @"InitialLoadSequence"];
|
2016-03-28 14:43:56 +02:00
|
|
|
[[o properties] removeObjectForKey: @"FirstIdInCache"];
|
|
|
|
[[o properties] removeObjectForKey: @"LastIdInCache"];
|
2014-12-08 16:45:34 +01:00
|
|
|
}
|
|
|
|
|
2014-10-29 19:20:03 +01:00
|
|
|
[o save];
|
|
|
|
}
|
2014-12-10 20:24:06 +01:00
|
|
|
else if ([[folders objectAtIndex:fi] isKindOfClass: [SOGoContactGCSFolder class]])
|
2014-10-29 19:20:03 +01:00
|
|
|
{
|
|
|
|
type = ([[[folders objectAtIndex:fi] nameInContainer] isEqualToString: personalFolderName] ? 9 : 14);
|
|
|
|
[commands appendFormat: @"<%@><ServerId>%@</ServerId><ParentId>%@</ParentId><DisplayName>%@</DisplayName><Type>%d</Type></%@>", operation,
|
|
|
|
[name stringByEscapingURL], @"0", [[[folders objectAtIndex:fi] displayName] activeSyncRepresentationInContext: context], type, operation];
|
|
|
|
|
|
|
|
command_count++;
|
|
|
|
|
|
|
|
[[o properties ] setObject: [[folders objectAtIndex:fi] displayName] forKey: @"displayName"];
|
2014-12-08 16:45:34 +01:00
|
|
|
|
|
|
|
if ([operation isEqualToString: @"Add"])
|
|
|
|
{
|
|
|
|
// clean cache content to avoid stale data
|
|
|
|
[[o properties] removeObjectForKey: @"SyncKey"];
|
|
|
|
[[o properties] removeObjectForKey: @"SyncCache"];
|
|
|
|
[[o properties] removeObjectForKey: @"DateCache"];
|
|
|
|
[[o properties] removeObjectForKey: @"MoreAvailable"];
|
2015-03-30 15:49:44 +02:00
|
|
|
[[o properties] removeObjectForKey: @"BodyPreferenceType"];
|
2015-10-26 15:15:35 +01:00
|
|
|
[[o properties] removeObjectForKey: @"SupportedElements"];
|
2014-12-08 16:45:34 +01:00
|
|
|
[[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"];
|
2015-09-09 16:20:31 +02:00
|
|
|
[[o properties] removeObjectForKey: @"InitialLoadSequence"];
|
2016-03-28 14:43:56 +02:00
|
|
|
[[o properties] removeObjectForKey: @"FirstIdInCache"];
|
|
|
|
[[o properties] removeObjectForKey: @"LastIdInCache"];
|
2014-12-08 16:45:34 +01:00
|
|
|
}
|
|
|
|
|
2014-10-29 19:20:03 +01:00
|
|
|
[o save];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-05-27 20:44:57 +02:00
|
|
|
|
2014-10-29 19:20:03 +01:00
|
|
|
// set a new syncKey if there are folder changes
|
|
|
|
if (command_count > 0)
|
|
|
|
{
|
|
|
|
syncKey = [[NSProcessInfo processInfo] globallyUniqueString];
|
|
|
|
[self _setFolderSyncKey: syncKey];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[s appendFormat: @"<FolderSync xmlns=\"FolderHierarchy:\"><Status>%d</Status>", status];
|
|
|
|
[s appendFormat: @"<SyncKey>%@</SyncKey><Changes><Count>%d</Count>%@</Changes></FolderSync>", syncKey, command_count, commands];
|
2014-05-27 20:44:57 +02:00
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
|
2014-05-27 20:44:57 +02:00
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
[theResponse setContent: d];
|
2014-05-27 20:44:57 +02:00
|
|
|
}
|
2014-01-10 20:12:53 +01:00
|
|
|
|
|
|
|
//
|
|
|
|
// From: http://msdn.microsoft.com/en-us/library/ee157980(v=exchg.80).aspx :
|
|
|
|
//
|
|
|
|
// <2> Section 2.2.2.6: The GetAttachment command is not supported when the MS-ASProtocolVersion header is set to 14.0 or 14.1
|
|
|
|
// in the GetAttachment command request. Use the Fetch element of the ItemOperations command instead. For more information about
|
|
|
|
// the MS-ASProtocolVersion header, see [MS-ASHTTP] section 2.2.1.1.2.4.
|
|
|
|
//
|
|
|
|
- (void) processGetAttachment: (id <DOMElement>) theDocumentElement
|
|
|
|
inResponse: (WOResponse *) theResponse
|
|
|
|
{
|
2014-06-09 15:25:06 +02:00
|
|
|
NSString *fileReference, *realCollectionId;
|
2014-01-10 20:12:53 +01:00
|
|
|
|
2014-06-09 15:25:06 +02:00
|
|
|
SOGoMicrosoftActiveSyncFolderType folderType;
|
|
|
|
|
|
|
|
fileReference = [context objectForKey: @"AttachmentName"];
|
|
|
|
|
|
|
|
realCollectionId = [fileReference realCollectionIdWithFolderType: &folderType];
|
|
|
|
|
|
|
|
if (folderType == ActiveSyncMailFolder)
|
|
|
|
{
|
|
|
|
id currentFolder, currentCollection, currentBodyPart;
|
|
|
|
NSString *folderName, *messageName, *pathToPart;
|
|
|
|
SOGoMailAccounts *accountsFolder;
|
|
|
|
SOGoUserFolder *userFolder;
|
|
|
|
SOGoMailObject *mailObject;
|
2016-02-11 20:33:39 +01:00
|
|
|
NSMutableArray *a;
|
2015-06-02 19:05:37 +02:00
|
|
|
NSArray *partKeys;
|
|
|
|
int p;
|
2014-06-09 15:25:06 +02:00
|
|
|
|
|
|
|
|
2016-02-11 20:33:39 +01:00
|
|
|
a = [[realCollectionId componentsSeparatedByString: @"/"] mutableCopy];
|
2016-02-11 21:04:44 +01:00
|
|
|
[a autorelease];
|
2016-02-11 20:33:39 +01:00
|
|
|
pathToPart = [a lastObject];
|
|
|
|
[a removeLastObject];
|
|
|
|
messageName = [a lastObject];
|
|
|
|
[a removeLastObject];
|
|
|
|
folderName = [a componentsJoinedByString: @"/"];
|
2014-06-09 15:25:06 +02:00
|
|
|
|
|
|
|
userFolder = [[context activeUser] homeFolderInContext: context];
|
|
|
|
accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
|
|
|
|
currentFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO];
|
|
|
|
|
|
|
|
currentCollection = [currentFolder lookupName: [NSString stringWithFormat: @"folder%@", folderName]
|
|
|
|
inContext: context
|
|
|
|
acquire: NO];
|
|
|
|
|
|
|
|
mailObject = [currentCollection lookupName: messageName inContext: context acquire: NO];
|
2015-06-02 19:05:37 +02:00
|
|
|
|
|
|
|
partKeys = [pathToPart componentsSeparatedByString: @"."];
|
|
|
|
|
|
|
|
currentBodyPart = [mailObject lookupImap4BodyPartKey: [partKeys objectAtIndex:0] inContext: context];
|
|
|
|
for (p = 1; p < [partKeys count]; p++)
|
|
|
|
{
|
|
|
|
currentBodyPart = [currentBodyPart lookupImap4BodyPartKey: [partKeys objectAtIndex:p] inContext: context];
|
|
|
|
}
|
2014-06-09 15:25:06 +02:00
|
|
|
|
|
|
|
[theResponse setHeader: [NSString stringWithFormat: @"%@/%@", [[currentBodyPart partInfo] objectForKey: @"type"], [[currentBodyPart partInfo] objectForKey: @"subtype"]]
|
|
|
|
forKey: @"Content-Type"];
|
|
|
|
|
2016-06-01 15:38:45 +02:00
|
|
|
[theResponse setContent: [currentBodyPart fetchBLOBWithPeek: YES] ];
|
2014-06-09 15:25:06 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
[theResponse setStatus: 500];
|
|
|
|
}
|
2014-01-10 20:12:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// <?xml version="1.0"?>
|
|
|
|
// <!DOCTYPE ActiveSync PUBLIC "-//MICROSOFT//DTD ActiveSync//EN" "http://www.microsoft.com/">
|
|
|
|
// <GetItemEstimate xmlns="GetItemEstimate:">
|
|
|
|
// <Collections>
|
|
|
|
// <Collection>
|
|
|
|
// <SyncKey xmlns="AirSync:">1</SyncKey>
|
|
|
|
// <CollectionId>folderINBOX</CollectionId>
|
|
|
|
// <Options xmlns="AirSync:">
|
|
|
|
// <FilterType>3</FilterType>
|
|
|
|
// </Options>
|
|
|
|
// </Collection>
|
|
|
|
// </Collections>
|
|
|
|
// </GetItemEstimate>
|
|
|
|
//
|
|
|
|
- (void) processGetItemEstimate: (id <DOMElement>) theDocumentElement
|
|
|
|
inResponse: (WOResponse *) theResponse
|
|
|
|
{
|
2014-10-29 19:20:03 +01:00
|
|
|
NSString *collectionId, *realCollectionId, *nameInCache;
|
2014-02-03 16:24:33 +01:00
|
|
|
id currentCollection;
|
2014-01-10 20:12:53 +01:00
|
|
|
NSMutableString *s;
|
|
|
|
NSData *d;
|
2015-02-26 23:46:34 +01:00
|
|
|
NSArray *allCollections;
|
|
|
|
int j;
|
2014-01-10 20:12:53 +01:00
|
|
|
|
|
|
|
SOGoMicrosoftActiveSyncFolderType folderType;
|
2014-02-05 22:12:08 +01:00
|
|
|
int status, count;
|
2014-01-10 20:12:53 +01:00
|
|
|
|
|
|
|
s = [NSMutableString string];
|
|
|
|
status = 1;
|
2014-02-05 22:12:08 +01:00
|
|
|
count = 0;
|
2014-01-10 20:12:53 +01:00
|
|
|
|
2015-02-26 23:46:34 +01:00
|
|
|
[s appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
|
|
|
|
[s appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
|
|
|
|
[s appendString: @"<GetItemEstimate xmlns=\"GetItemEstimate:\">"];
|
2014-10-29 19:20:03 +01:00
|
|
|
|
2015-02-26 23:46:34 +01:00
|
|
|
allCollections = (id)[theDocumentElement getElementsByTagName: @"Collection"];
|
2014-10-29 19:20:03 +01:00
|
|
|
|
2015-02-26 23:46:34 +01:00
|
|
|
for (j = 0; j < [allCollections count]; j++)
|
|
|
|
{
|
|
|
|
collectionId = [[(id)[[allCollections objectAtIndex: j] getElementsByTagName: @"CollectionId"] lastObject] textValue];
|
|
|
|
realCollectionId = [collectionId realCollectionIdWithFolderType: &folderType];
|
2015-06-02 19:05:37 +02:00
|
|
|
|
2015-02-26 23:46:34 +01:00
|
|
|
if (folderType == ActiveSyncMailFolder)
|
2015-06-02 19:05:37 +02:00
|
|
|
nameInCache = [NSString stringWithFormat: @"folder%@", realCollectionId];
|
2015-02-26 23:46:34 +01:00
|
|
|
else
|
2015-06-02 19:05:37 +02:00
|
|
|
nameInCache = collectionId;
|
2014-10-29 19:20:03 +01:00
|
|
|
|
2015-02-26 23:46:34 +01:00
|
|
|
realCollectionId = [self globallyUniqueIDToIMAPFolderName: realCollectionId type: folderType];
|
2014-10-29 19:20:03 +01:00
|
|
|
|
2015-02-26 23:46:34 +01:00
|
|
|
currentCollection = [self collectionFromId: realCollectionId type: folderType];
|
2014-01-10 20:12:53 +01:00
|
|
|
|
2015-02-26 23:46:34 +01:00
|
|
|
//
|
|
|
|
// For IMAP, we simply build a request like this:
|
|
|
|
//
|
|
|
|
// . UID SORT (SUBJECT) UTF-8 SINCE 1-Jan-2014 NOT DELETED
|
|
|
|
// * SORT 124576 124577 124579 124578
|
|
|
|
// . OK Completed (4 msgs in 0.000 secs)
|
|
|
|
//
|
|
|
|
if (folderType == ActiveSyncMailFolder)
|
|
|
|
{
|
|
|
|
NSCalendarDate *filter;
|
2015-03-23 16:35:45 +01:00
|
|
|
NSString *syncKey;
|
|
|
|
NSArray *allMessages;
|
2014-02-05 22:12:08 +01:00
|
|
|
|
2015-02-26 23:46:34 +01:00
|
|
|
filter = [NSCalendarDate dateFromFilterType: [[(id)[[allCollections objectAtIndex: j] getElementsByTagName: @"FilterType"] lastObject] textValue]];
|
2015-03-23 16:35:45 +01:00
|
|
|
syncKey = [[(id)[[allCollections objectAtIndex: j] getElementsByTagName: @"SyncKey"] lastObject] textValue];
|
2014-02-05 22:12:08 +01:00
|
|
|
|
2015-09-09 16:20:31 +02:00
|
|
|
allMessages = [currentCollection syncTokenFieldsWithProperties: nil matchingSyncToken: syncKey fromDate: filter initialLoad: NO];
|
2015-03-23 16:35:45 +01:00
|
|
|
|
|
|
|
count = [allMessages count];
|
2014-02-05 22:12:08 +01:00
|
|
|
|
2015-02-26 23:46:34 +01:00
|
|
|
// Add the number of UIDs expected to "soft delete"
|
|
|
|
count += [self _softDeleteCountWithFilter: filter collectionId: nameInCache];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
count = [[currentCollection toOneRelationshipKeys] count];
|
|
|
|
}
|
2014-06-25 21:05:25 +02:00
|
|
|
|
|
|
|
|
2015-02-26 23:46:34 +01:00
|
|
|
[s appendString: @"<Response>"];
|
|
|
|
[s appendFormat: @"<Status>%d</Status><Collection>", status];
|
|
|
|
|
|
|
|
if (folderType == ActiveSyncMailFolder)
|
|
|
|
[s appendString: @"<Class>Email</Class>"];
|
|
|
|
else if (folderType == ActiveSyncContactFolder)
|
|
|
|
[s appendString: @"<Class>Contacts</Class>"];
|
|
|
|
else if (folderType == ActiveSyncEventFolder)
|
|
|
|
[s appendString: @"<Class>Calendar</Class>"];
|
|
|
|
else if (folderType == ActiveSyncTaskFolder)
|
|
|
|
[s appendString: @"<Class>Tasks</Class>"];
|
|
|
|
|
|
|
|
[s appendFormat: @"<CollectionId>%@</CollectionId>",collectionId];
|
|
|
|
[s appendFormat: @"<Estimate>%d</Estimate></Collection></Response>", count];
|
|
|
|
}
|
2014-01-10 20:12:53 +01:00
|
|
|
|
2015-02-26 23:46:34 +01:00
|
|
|
[s appendString: @"</GetItemEstimate>"];
|
2014-01-10 20:12:53 +01:00
|
|
|
|
|
|
|
d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
|
|
|
|
|
|
|
|
[theResponse setContent: d];
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// <?xml version="1.0"?>
|
|
|
|
// <!DOCTYPE ActiveSync PUBLIC "-//MICROSOFT//DTD ActiveSync//EN" "http://www.microsoft.com/">
|
|
|
|
// <ItemOperations xmlns="ItemOperations:">
|
|
|
|
// <Fetch>
|
|
|
|
// <Store>Mailbox</Store> -- http://msdn.microsoft.com/en-us/library/gg663522(v=exchg.80).aspx
|
|
|
|
// <FileReference xmlns="AirSyncBase:">2</FileReference> --
|
|
|
|
// <Options/>
|
|
|
|
// </Fetch>
|
|
|
|
// </ItemOperations>
|
|
|
|
//
|
|
|
|
- (void) processItemOperations: (id <DOMElement>) theDocumentElement
|
|
|
|
inResponse: (WOResponse *) theResponse
|
|
|
|
{
|
2015-10-15 21:31:46 +02:00
|
|
|
NSString *fileReference, *realCollectionId, *serverId, *bodyPreferenceType, *mimeSupport, *collectionId;
|
2014-01-10 20:12:53 +01:00
|
|
|
NSMutableString *s;
|
2015-01-12 20:38:55 +01:00
|
|
|
NSArray *fetchRequests;
|
|
|
|
id aFetch;
|
2015-06-02 19:05:37 +02:00
|
|
|
NSData *d;
|
2015-01-12 20:38:55 +01:00
|
|
|
int i;
|
2014-01-10 20:12:53 +01:00
|
|
|
|
|
|
|
SOGoMicrosoftActiveSyncFolderType folderType;
|
|
|
|
|
2015-01-12 20:38:55 +01:00
|
|
|
s = [NSMutableString string];
|
2014-01-10 20:12:53 +01:00
|
|
|
|
2015-01-12 20:38:55 +01:00
|
|
|
[s appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
|
|
|
|
[s appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
|
|
|
|
[s appendString: @"<ItemOperations xmlns=\"ItemOperations:\">"];
|
|
|
|
|
|
|
|
fetchRequests = (id)[theDocumentElement getElementsByTagName: @"Fetch"];
|
2014-01-10 20:12:53 +01:00
|
|
|
|
2015-01-12 20:38:55 +01:00
|
|
|
if ([fetchRequests count])
|
2014-01-10 20:12:53 +01:00
|
|
|
{
|
2015-01-12 20:38:55 +01:00
|
|
|
NSMutableData *bytes, *parts;
|
|
|
|
NSMutableArray *partLength;
|
2014-01-10 20:12:53 +01:00
|
|
|
|
2015-01-12 20:38:55 +01:00
|
|
|
bytes = [NSMutableData data];
|
|
|
|
parts = [NSMutableData data];
|
|
|
|
partLength = [NSMutableArray array];
|
2014-01-10 20:12:53 +01:00
|
|
|
|
2015-06-02 19:05:37 +02:00
|
|
|
[s appendString: @"<Status>1</Status>"];
|
|
|
|
[s appendString: @"<Response>"];
|
|
|
|
|
2015-01-12 20:38:55 +01:00
|
|
|
for (i = 0; i < [fetchRequests count]; i++)
|
|
|
|
{
|
|
|
|
aFetch = [fetchRequests objectAtIndex: i];
|
|
|
|
fileReference = [[[(id)[aFetch getElementsByTagName: @"FileReference"] lastObject] textValue] stringByUnescapingURL];
|
2015-06-02 19:05:37 +02:00
|
|
|
collectionId = [[(id)[theDocumentElement getElementsByTagName: @"CollectionId"] lastObject] textValue];
|
|
|
|
|
|
|
|
// its either a itemOperation to fetch an attachment or an email
|
|
|
|
if ([fileReference length])
|
|
|
|
realCollectionId = [fileReference realCollectionIdWithFolderType: &folderType];
|
|
|
|
else
|
|
|
|
realCollectionId = [collectionId realCollectionIdWithFolderType: &folderType];
|
2014-01-10 20:12:53 +01:00
|
|
|
|
2015-01-12 20:38:55 +01:00
|
|
|
if (folderType == ActiveSyncMailFolder)
|
|
|
|
{
|
|
|
|
id currentFolder, currentCollection, currentBodyPart;
|
|
|
|
NSString *folderName, *messageName, *pathToPart;
|
|
|
|
SOGoMailAccounts *accountsFolder;
|
|
|
|
SOGoUserFolder *userFolder;
|
|
|
|
SOGoMailObject *mailObject;
|
2016-02-11 20:33:39 +01:00
|
|
|
NSMutableArray *a;
|
2015-01-12 20:38:55 +01:00
|
|
|
|
2015-06-02 19:05:37 +02:00
|
|
|
if ([fileReference length])
|
|
|
|
{
|
|
|
|
// fetch attachment
|
|
|
|
NSArray *partKeys;
|
|
|
|
int p;
|
2015-01-12 20:38:55 +01:00
|
|
|
|
2016-02-11 20:33:39 +01:00
|
|
|
a = [[realCollectionId componentsSeparatedByString: @"/"] mutableCopy];
|
2016-02-11 21:04:44 +01:00
|
|
|
[a autorelease];
|
|
|
|
pathToPart = [a lastObject];
|
2016-02-11 20:33:39 +01:00
|
|
|
[a removeLastObject];
|
|
|
|
messageName = [a lastObject];
|
|
|
|
[a removeLastObject];
|
|
|
|
folderName = [a componentsJoinedByString: @"/"];
|
2015-06-02 19:05:37 +02:00
|
|
|
|
|
|
|
userFolder = [[context activeUser] homeFolderInContext: context];
|
|
|
|
accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
|
|
|
|
currentFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO];
|
|
|
|
|
|
|
|
currentCollection = [currentFolder lookupName: [NSString stringWithFormat: @"folder%@", folderName]
|
|
|
|
inContext: context
|
|
|
|
acquire: NO];
|
|
|
|
|
|
|
|
mailObject = [currentCollection lookupName: messageName inContext: context acquire: NO];
|
|
|
|
|
|
|
|
partKeys = [pathToPart componentsSeparatedByString: @"."];
|
|
|
|
|
|
|
|
currentBodyPart = [mailObject lookupImap4BodyPartKey: [partKeys objectAtIndex:0] inContext: context];
|
|
|
|
for (p = 1; p < [partKeys count]; p++)
|
2016-02-11 20:33:39 +01:00
|
|
|
{
|
|
|
|
currentBodyPart = [currentBodyPart lookupImap4BodyPartKey: [partKeys objectAtIndex:p] inContext: context];
|
|
|
|
}
|
2015-06-02 19:05:37 +02:00
|
|
|
|
|
|
|
[s appendString: @"<Fetch>"];
|
|
|
|
[s appendString: @"<Status>1</Status>"];
|
2016-02-11 20:33:39 +01:00
|
|
|
[s appendFormat: @"<FileReference xmlns=\"AirSyncBase:\">mail/%@/%@/%@</FileReference>", [folderName stringByEscapingURL], messageName, pathToPart];
|
2015-06-02 19:05:37 +02:00
|
|
|
[s appendString: @"<Properties>"];
|
|
|
|
|
|
|
|
[s appendFormat: @"<ContentType xmlns=\"AirSyncBase:\">%@/%@</ContentType>", [[currentBodyPart partInfo] objectForKey: @"type"], [[currentBodyPart partInfo] objectForKey: @"subtype"]];
|
|
|
|
|
|
|
|
if ([[theResponse headerForKey: @"Content-Type"] isEqualToString:@"application/vnd.ms-sync.multipart"])
|
|
|
|
{
|
|
|
|
NSData *d;
|
2016-06-01 14:52:56 +02:00
|
|
|
d = [currentBodyPart fetchBLOBWithPeek: YES];
|
2015-06-02 19:05:37 +02:00
|
|
|
|
|
|
|
[s appendFormat: @"<Part>%d</Part>", i+1];
|
|
|
|
[partLength addObject: [NSNumber numberWithInteger: [d length]]];
|
|
|
|
[parts appendData: d];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
NSString *a;
|
2016-06-01 15:38:45 +02:00
|
|
|
a = [[currentBodyPart fetchBLOBWithPeek: YES] activeSyncRepresentationInContext: context];
|
2015-06-02 19:05:37 +02:00
|
|
|
|
|
|
|
[s appendFormat: @"<Range>0-%d</Range>", [a length]-1];
|
|
|
|
[s appendFormat: @"<Data>%@</Data>", a];
|
|
|
|
}
|
2015-01-12 20:38:55 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2015-06-02 19:05:37 +02:00
|
|
|
// fetch mail
|
|
|
|
realCollectionId = [self globallyUniqueIDToIMAPFolderName: realCollectionId type: folderType];
|
|
|
|
serverId = [[(id)[theDocumentElement getElementsByTagName: @"ServerId"] lastObject] textValue];
|
|
|
|
bodyPreferenceType = [[(id)[[(id)[theDocumentElement getElementsByTagName: @"BodyPreference"] lastObject] getElementsByTagName: @"Type"] lastObject] textValue];
|
|
|
|
[context setObject: bodyPreferenceType forKey: @"BodyPreferenceType"];
|
2015-10-15 21:31:46 +02:00
|
|
|
mimeSupport = [[(id)[theDocumentElement getElementsByTagName: @"MIMESupport"] lastObject] textValue];
|
|
|
|
[context setObject: mimeSupport forKey: @"MIMESupport"];
|
2015-06-02 19:05:37 +02:00
|
|
|
|
2016-03-09 14:49:17 +01:00
|
|
|
// https://msdn.microsoft.com/en-us/library/gg675490%28v=exchg.80%29.aspx
|
|
|
|
// The fetch element is used to request the application data of an item that was truncated in a synchronization response from the server.
|
|
|
|
// The complete item is then returned to the client in a server response.
|
|
|
|
[context setObject: @"8" forKey: @"MIMETruncation"];
|
|
|
|
|
2015-06-02 19:05:37 +02:00
|
|
|
currentCollection = [self collectionFromId: realCollectionId type: folderType];
|
|
|
|
|
|
|
|
mailObject = [currentCollection lookupName: serverId inContext: context acquire: NO];
|
|
|
|
[s appendString: @"<Fetch>"];
|
|
|
|
[s appendString: @"<Status>1</Status>"];
|
|
|
|
[s appendFormat: @"<CollectionId xmlns=\"AirSyncBase:\">%@</CollectionId>", collectionId];
|
|
|
|
[s appendFormat: @"<ServerId xmlns=\"AirSyncBase:\">%@</ServerId>", serverId];
|
|
|
|
[s appendString: @"<Properties>"];
|
|
|
|
[s appendString: [mailObject activeSyncRepresentationInContext: context]];
|
2015-01-12 20:38:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
[s appendString: @"</Properties>"];
|
|
|
|
[s appendString: @"</Fetch>"];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
[theResponse setStatus: 500];
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2014-01-10 20:12:53 +01:00
|
|
|
|
|
|
|
[s appendString: @"</Response>"];
|
|
|
|
[s appendString: @"</ItemOperations>"];
|
2015-01-12 20:38:55 +01:00
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
|
2015-01-12 20:38:55 +01:00
|
|
|
|
|
|
|
if ([[theResponse headerForKey: @"Content-Type"] isEqualToString:@"application/vnd.ms-sync.multipart"])
|
|
|
|
{
|
|
|
|
uint32_t PartCount;
|
|
|
|
uint32_t Offset;
|
|
|
|
uint32_t Len;
|
|
|
|
|
|
|
|
// 2.2.2.9.1.1 - MultiPartResponse -- http://msdn.microsoft.com/en-us/library/jj663270%28v=exchg.80%29.aspx
|
|
|
|
PartCount = [partLength count] + 1;
|
|
|
|
Offset = ((PartCount) * 2) * 4 + 4;
|
|
|
|
Len = [d length];
|
|
|
|
|
|
|
|
[bytes appendBytes: &PartCount length: 4];
|
|
|
|
[bytes appendBytes: &Offset length: 4];
|
|
|
|
[bytes appendBytes: &Len length: 4];
|
|
|
|
|
|
|
|
// 2.2.2.9.1.1.1 - PartMetaData -- http://msdn.microsoft.com/en-us/library/jj663267%28v=exchg.80%29.aspx
|
|
|
|
for (i = 0; i < [fetchRequests count]; i++)
|
|
|
|
{
|
|
|
|
Offset = Offset + Len;
|
|
|
|
Len = [[partLength objectAtIndex:i] intValue];
|
|
|
|
[bytes appendBytes: &Offset length: 4];
|
|
|
|
[bytes appendBytes: &Len length: 4];
|
|
|
|
}
|
|
|
|
|
|
|
|
// First part - webxml
|
|
|
|
[bytes appendData: d];
|
|
|
|
|
|
|
|
// Subsequent parts - requested data
|
|
|
|
[bytes appendData: parts];
|
|
|
|
|
|
|
|
[theResponse setContent: bytes];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
[theResponse setContent: d];
|
|
|
|
}
|
2015-06-02 19:05:37 +02:00
|
|
|
}
|
|
|
|
else if ([theDocumentElement getElementsByTagName: @"EmptyFolderContents"])
|
|
|
|
{
|
|
|
|
NGImap4Connection *connection;
|
|
|
|
NSEnumerator *subfolders;
|
|
|
|
NSException *error;
|
|
|
|
NSURL *currentURL;
|
|
|
|
id co;
|
|
|
|
|
|
|
|
collectionId = [[(id)[theDocumentElement getElementsByTagName: @"CollectionId"] lastObject] textValue];
|
|
|
|
realCollectionId = [collectionId realCollectionIdWithFolderType: &folderType];
|
|
|
|
realCollectionId = [self globallyUniqueIDToIMAPFolderName: realCollectionId type: folderType];
|
|
|
|
|
|
|
|
if (folderType == ActiveSyncMailFolder)
|
|
|
|
{
|
|
|
|
co = [self collectionFromId: realCollectionId type: folderType];
|
|
|
|
error = [co addFlagsToAllMessages: @"deleted"];
|
|
|
|
|
|
|
|
if (!error)
|
|
|
|
error = [(SOGoMailFolder *)co expunge];
|
|
|
|
|
|
|
|
if (!error)
|
|
|
|
{
|
|
|
|
[co flushMailCaches];
|
|
|
|
|
|
|
|
if ([theDocumentElement getElementsByTagName: @"DeleteSubFolders"])
|
|
|
|
{
|
|
|
|
// Delete sub-folders
|
|
|
|
connection = [co imap4Connection];
|
|
|
|
subfolders = [[co allFolderURLs] objectEnumerator];
|
|
|
|
|
|
|
|
while ((currentURL = [subfolders nextObject]))
|
|
|
|
{
|
|
|
|
[[connection client] unsubscribe: [currentURL path]];
|
|
|
|
[connection deleteMailboxAtURL: currentURL];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
[s appendString: @"<Status>1</Status>"];
|
|
|
|
[s appendString: @"</ItemOperations>"];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (error)
|
|
|
|
{
|
|
|
|
[s appendString: @"<Status>3</Status>"];
|
|
|
|
[s appendString: @"</ItemOperations>"];
|
|
|
|
}
|
|
|
|
|
|
|
|
d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
|
|
|
|
[theResponse setContent: d];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
[theResponse setStatus: 500];
|
|
|
|
return;
|
|
|
|
}
|
2014-01-10 20:12:53 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-01-14 16:49:55 +01:00
|
|
|
//
|
2014-02-03 16:24:33 +01:00
|
|
|
// <?xml version="1.0"?>
|
|
|
|
// <!DOCTYPE ActiveSync PUBLIC "-//MICROSOFT//DTD ActiveSync//EN" "http://www.microsoft.com/">
|
|
|
|
// <MeetingResponse xmlns="MeetingResponse:">
|
|
|
|
// <Request>
|
|
|
|
// <UserResponse>1</UserResponse>
|
|
|
|
// <CollectionId>mail%2FINBOX</CollectionId>
|
|
|
|
// <RequestId>283</RequestId>
|
|
|
|
// </Request>
|
|
|
|
// </MeetingResponse>
|
2014-01-14 16:49:55 +01:00
|
|
|
//
|
|
|
|
- (void) processMeetingResponse: (id <DOMElement>) theDocumentElement
|
|
|
|
inResponse: (WOResponse *) theResponse
|
|
|
|
{
|
2014-02-17 14:46:05 +01:00
|
|
|
NSString *realCollectionId, *requestId, *participationStatus, *calendarId;
|
|
|
|
SOGoAppointmentObject *appointmentObject;
|
2014-02-11 02:16:43 +01:00
|
|
|
SOGoMailObject *mailObject;
|
2014-02-03 16:24:33 +01:00
|
|
|
NSMutableString *s;
|
|
|
|
NSData *d;
|
2014-01-14 16:49:55 +01:00
|
|
|
|
2014-02-03 16:24:33 +01:00
|
|
|
id collection;
|
|
|
|
|
|
|
|
SOGoMicrosoftActiveSyncFolderType folderType;
|
|
|
|
int userResponse;
|
|
|
|
int status;
|
|
|
|
|
|
|
|
s = [NSMutableString string];
|
|
|
|
status = 1;
|
|
|
|
|
|
|
|
realCollectionId = [[[(id)[theDocumentElement getElementsByTagName: @"CollectionId"] lastObject] textValue] realCollectionIdWithFolderType: &folderType];
|
2014-05-27 20:44:57 +02:00
|
|
|
realCollectionId = [self globallyUniqueIDToIMAPFolderName: realCollectionId type: folderType];
|
2014-02-03 16:24:33 +01:00
|
|
|
userResponse = [[[(id)[theDocumentElement getElementsByTagName: @"UserResponse"] lastObject] textValue] intValue];
|
2014-02-17 14:46:05 +01:00
|
|
|
requestId = [[(id)[theDocumentElement getElementsByTagName: @"RequestId"] lastObject] textValue];
|
|
|
|
appointmentObject = nil;
|
|
|
|
calendarId = nil;
|
|
|
|
|
|
|
|
// Outlook 2013 calls MeetingResponse on the calendar folder! We have
|
|
|
|
// no way of handling as we can't retrieve the email (using the id found
|
|
|
|
// in requestId) in any mail folder! If that happens, let's simply
|
|
|
|
// assume it comes from the INBOX. This should be generally safe as people
|
|
|
|
// will answer email invitations as they receive them on their INBOX.
|
|
|
|
// Note that the mail should also still be there as MeetingResponse is
|
|
|
|
// called *before* MoveItems.
|
2014-02-03 16:24:33 +01:00
|
|
|
//
|
2014-02-17 14:46:05 +01:00
|
|
|
// Apple iOS will also call MeetingResponse on the calendar folder when the
|
|
|
|
// user accepts/declines the meeting from the Calendar application. Before
|
|
|
|
// falling back on INBOX, we first check if we can find the event in the
|
|
|
|
// personal calendar.
|
|
|
|
if (folderType == ActiveSyncEventFolder)
|
2014-02-03 16:24:33 +01:00
|
|
|
{
|
|
|
|
collection = [[context activeUser] personalCalendarFolderInContext: context];
|
2014-02-17 14:46:05 +01:00
|
|
|
appointmentObject = [collection lookupName: [requestId sanitizedServerIdWithType: ActiveSyncEventFolder]
|
2014-02-03 16:24:33 +01:00
|
|
|
inContext: context
|
|
|
|
acquire: NO];
|
2014-02-17 14:46:05 +01:00
|
|
|
calendarId = requestId;
|
|
|
|
|
|
|
|
// Object not found, let's fallback on the INBOX folder
|
|
|
|
if ([appointmentObject isKindOfClass: [NSException class]])
|
|
|
|
{
|
|
|
|
folderType = ActiveSyncMailFolder;
|
|
|
|
realCollectionId = @"INBOX";
|
|
|
|
appointmentObject = nil;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fetch the appointment object from the mail message
|
|
|
|
if (!appointmentObject)
|
|
|
|
{
|
|
|
|
collection = [self collectionFromId: realCollectionId type: folderType];
|
|
|
|
|
|
|
|
//
|
|
|
|
// We fetch the calendar information based on the email (requestId) in the user's INBOX (or elsewhere)
|
|
|
|
//
|
|
|
|
// FIXME: that won't work too well for external invitations...
|
|
|
|
mailObject = [collection lookupName: requestId
|
|
|
|
inContext: context
|
|
|
|
acquire: 0];
|
|
|
|
|
|
|
|
if (![mailObject isKindOfClass: [NSException class]])
|
|
|
|
{
|
|
|
|
iCalCalendar *calendar;
|
|
|
|
iCalEvent *event;
|
|
|
|
|
|
|
|
calendar = [mailObject calendarFromIMIPMessage];
|
|
|
|
event = [[calendar events] lastObject];
|
|
|
|
calendarId = [event uid];
|
|
|
|
|
|
|
|
// Fetch the SOGoAppointmentObject
|
|
|
|
collection = [[context activeUser] personalCalendarFolderInContext: context];
|
|
|
|
appointmentObject = [collection lookupName: [NSString stringWithFormat: @"%@.ics", [event uid]]
|
|
|
|
inContext: context
|
|
|
|
acquire: NO];
|
2015-05-12 16:12:57 +02:00
|
|
|
|
|
|
|
// Create the appointment if it is not added to calendar yet
|
|
|
|
if ([appointmentObject isKindOfClass: [NSException class]])
|
|
|
|
{
|
|
|
|
appointmentObject = [[SOGoAppointmentObject alloc] initWithName: [NSString stringWithFormat: @"%@.ics", [event uid]]
|
|
|
|
inContainer: collection];
|
|
|
|
[appointmentObject saveComponent: event];
|
|
|
|
}
|
2014-02-17 14:46:05 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (appointmentObject &&
|
|
|
|
calendarId &&
|
|
|
|
(![appointmentObject isKindOfClass: [NSException class]]))
|
|
|
|
{
|
|
|
|
// 1 -> accepted, 2 -> tentative, 3 -> declined
|
2014-02-03 16:24:33 +01:00
|
|
|
if (userResponse == 1)
|
|
|
|
participationStatus = @"ACCEPTED";
|
|
|
|
else if (userResponse == 2)
|
|
|
|
participationStatus = @"TENTATIVE";
|
|
|
|
else
|
|
|
|
participationStatus = @"DECLINED";
|
|
|
|
|
2014-02-17 14:46:05 +01:00
|
|
|
[appointmentObject changeParticipationStatus: participationStatus
|
2014-12-16 15:20:27 +01:00
|
|
|
withDelegate: nil
|
|
|
|
alarm: nil];
|
2014-02-17 14:46:05 +01:00
|
|
|
|
|
|
|
[s appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
|
|
|
|
[s appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
|
|
|
|
[s appendString: @"<MeetingResponse xmlns=\"MeetingResponse:\">"];
|
|
|
|
[s appendString: @"<Result>"];
|
|
|
|
[s appendFormat: @"<RequestId>%@</RequestId>", requestId];
|
|
|
|
[s appendFormat: @"<CalendarId>%@</CalendarId>", calendarId];
|
|
|
|
[s appendFormat: @"<Status>%d</Status>", status];
|
|
|
|
[s appendString: @"</Result>"];
|
|
|
|
[s appendString: @"</MeetingResponse>"];
|
|
|
|
|
|
|
|
d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
|
|
|
|
|
|
|
|
[theResponse setContent: d];
|
2014-02-03 16:24:33 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2015-05-12 16:12:57 +02:00
|
|
|
[s appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
|
|
|
|
[s appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
|
|
|
|
[s appendString: @"<MeetingResponse xmlns=\"MeetingResponse:\">"];
|
|
|
|
[s appendString: @"<Result>"];
|
|
|
|
[s appendFormat: @"<RequestId>%@</RequestId>", requestId];
|
|
|
|
[s appendFormat: @"<Status>%d</Status>", 2];
|
|
|
|
[s appendString: @"</Result>"];
|
|
|
|
[s appendString: @"</MeetingResponse>"];
|
|
|
|
d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
|
|
|
|
|
|
|
|
[theResponse setContent: d];
|
2014-02-03 16:24:33 +01:00
|
|
|
}
|
2014-01-14 16:49:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
//
|
|
|
|
// <?xml version="1.0"?>
|
|
|
|
// <!DOCTYPE ActiveSync PUBLIC "-//MICROSOFT//DTD ActiveSync//EN" "http://www.microsoft.com/">
|
|
|
|
// <MoveItems xmlns="Move:">
|
|
|
|
// <Move>
|
|
|
|
// <SrcMsgId>85</SrcMsgId>
|
|
|
|
// <SrcFldId>mail/INBOX</SrcFldId>
|
|
|
|
// <DstFldId>mail/toto</DstFldId>
|
|
|
|
// </Move>
|
|
|
|
// </MoveItems>
|
|
|
|
//
|
|
|
|
- (void) processMoveItems: (id <DOMElement>) theDocumentElement
|
|
|
|
inResponse: (WOResponse *) theResponse
|
|
|
|
{
|
2014-12-08 16:25:37 +01:00
|
|
|
NSString *srcMessageId, *srcFolderId, *dstFolderId, *dstMessageId, *nameInCache, *currentFolder;
|
|
|
|
NSMutableDictionary *folderMetadata, *prevSuccessfulMoveItemsOps, *newSuccessfulMoveItemsOps;
|
2014-01-10 20:12:53 +01:00
|
|
|
SOGoMicrosoftActiveSyncFolderType srcFolderType, dstFolderType;
|
2014-03-28 20:22:45 +01:00
|
|
|
id <DOMElement> aMoveOperation;
|
|
|
|
NSArray *moveOperations;
|
2014-10-29 19:20:03 +01:00
|
|
|
SoSecurityManager *sm;
|
2014-03-28 20:22:45 +01:00
|
|
|
NSMutableString *s;
|
|
|
|
NSData *d;
|
|
|
|
int i;
|
2014-12-08 16:25:37 +01:00
|
|
|
|
2014-03-28 20:22:45 +01:00
|
|
|
moveOperations = (id)[theDocumentElement getElementsByTagName: @"Move"];
|
2016-06-06 19:28:42 +02:00
|
|
|
|
|
|
|
newSuccessfulMoveItemsOps = [NSMutableDictionary dictionary];
|
|
|
|
prevSuccessfulMoveItemsOps = nil;
|
|
|
|
folderMetadata = nil;
|
|
|
|
currentFolder = nil;
|
|
|
|
|
2014-03-28 20:22:45 +01:00
|
|
|
s = [NSMutableString string];
|
2014-01-10 20:12:53 +01:00
|
|
|
|
2014-03-28 20:22:45 +01:00
|
|
|
[s appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
|
|
|
|
[s appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
|
|
|
|
[s appendString: @"<MoveItems xmlns=\"Move:\">"];
|
2014-01-13 16:19:00 +01:00
|
|
|
|
2014-03-28 20:22:45 +01:00
|
|
|
for (i = 0; i < [moveOperations count]; i++)
|
|
|
|
{
|
|
|
|
aMoveOperation = [moveOperations objectAtIndex: i];
|
2014-01-10 20:12:53 +01:00
|
|
|
|
2014-03-28 20:22:45 +01:00
|
|
|
srcMessageId = [[(id)[aMoveOperation getElementsByTagName: @"SrcMsgId"] lastObject] textValue];
|
|
|
|
srcFolderId = [[[(id)[aMoveOperation getElementsByTagName: @"SrcFldId"] lastObject] textValue] realCollectionIdWithFolderType: &srcFolderType];
|
|
|
|
dstFolderId = [[[(id)[aMoveOperation getElementsByTagName: @"DstFldId"] lastObject] textValue] realCollectionIdWithFolderType: &dstFolderType];
|
2014-12-08 16:25:37 +01:00
|
|
|
|
|
|
|
if (srcFolderType == ActiveSyncMailFolder)
|
|
|
|
nameInCache = [NSString stringWithFormat: @"folder%@", [[[[(id)[aMoveOperation getElementsByTagName: @"SrcFldId"] lastObject] textValue] stringByUnescapingURL] substringFromIndex: 5]];
|
|
|
|
else
|
|
|
|
nameInCache = [[[(id)[aMoveOperation getElementsByTagName: @"SrcFldId"] lastObject] textValue] stringByUnescapingURL];
|
|
|
|
|
|
|
|
if (![nameInCache isEqualToString: currentFolder])
|
|
|
|
{
|
|
|
|
folderMetadata = [self _folderMetadataForKey: nameInCache];
|
|
|
|
prevSuccessfulMoveItemsOps = [folderMetadata objectForKey: @"SuccessfulMoveItemsOps"];
|
|
|
|
currentFolder = nameInCache;
|
|
|
|
}
|
2014-03-28 20:22:45 +01:00
|
|
|
|
|
|
|
[s appendString: @"<Response>"];
|
2014-01-13 16:19:00 +01:00
|
|
|
|
2014-03-28 20:22:45 +01:00
|
|
|
if (srcFolderType == ActiveSyncMailFolder && dstFolderType == ActiveSyncMailFolder)
|
2014-01-10 20:12:53 +01:00
|
|
|
{
|
2014-03-28 20:22:45 +01:00
|
|
|
NGImap4Client *client;
|
|
|
|
id currentCollection;
|
2014-01-13 16:19:00 +01:00
|
|
|
|
2014-03-28 20:22:45 +01:00
|
|
|
NSDictionary *response;
|
|
|
|
NSString *v;
|
|
|
|
|
2014-05-27 20:44:57 +02:00
|
|
|
srcFolderId = [self globallyUniqueIDToIMAPFolderName: srcFolderId type: srcFolderType];
|
|
|
|
dstFolderId = [self globallyUniqueIDToIMAPFolderName: dstFolderId type: dstFolderType];
|
|
|
|
|
2014-03-28 20:22:45 +01:00
|
|
|
currentCollection = [self collectionFromId: srcFolderId type: srcFolderType];
|
2014-03-19 16:31:54 +01:00
|
|
|
|
2014-03-28 20:22:45 +01:00
|
|
|
client = [[currentCollection imap4Connection] client];
|
|
|
|
[client select: srcFolderId];
|
|
|
|
response = [client copyUid: [srcMessageId intValue]
|
|
|
|
toFolder: [NSString stringWithFormat: @"/%@", dstFolderId]];
|
2014-01-10 20:12:53 +01:00
|
|
|
|
2014-03-28 20:22:45 +01:00
|
|
|
// We extract the destionation message id
|
|
|
|
dstMessageId = nil;
|
2014-01-10 20:12:53 +01:00
|
|
|
|
2014-03-28 20:22:45 +01:00
|
|
|
if ([[response objectForKey: @"result"] boolValue]
|
|
|
|
&& (v = [[[response objectForKey: @"RawResponse"] objectForKey: @"ResponseResult"] objectForKey: @"flag"])
|
|
|
|
&& [v hasPrefix: @"COPYUID "])
|
|
|
|
{
|
|
|
|
dstMessageId = [[v componentsSeparatedByString: @" "] lastObject];
|
|
|
|
|
|
|
|
// We mark the original message as deleted
|
|
|
|
response = [client storeFlags: [NSArray arrayWithObject: @"Deleted"]
|
|
|
|
forUIDs: [NSArray arrayWithObject: srcMessageId]
|
|
|
|
addOrRemove: YES];
|
|
|
|
|
|
|
|
if ([[response valueForKey: @"result"] boolValue])
|
|
|
|
[(SOGoMailFolder *)currentCollection expunge];
|
|
|
|
|
|
|
|
}
|
2014-01-10 20:12:53 +01:00
|
|
|
|
2014-03-28 20:22:45 +01:00
|
|
|
if (!dstMessageId)
|
|
|
|
{
|
2014-12-05 19:52:10 +01:00
|
|
|
// Our destination message ID doesn't exist OR even our source message ID doesn't.
|
|
|
|
// This can happen if you Move items from your EAS client and immediately closes it
|
|
|
|
// before the server had the time to receive or process the query. Then, if that message
|
|
|
|
// is moved away by an other client behing the EAS' client back, it obvisouly won't find it.
|
|
|
|
// The issue the "result" will still be a success, but in fact, it's a failure. Cyrus generates
|
|
|
|
// this kind of query/response for an 'unkknown' message UID (696969) when trying to copy it
|
|
|
|
// over to the folder "Trash".
|
|
|
|
//
|
|
|
|
// 3 uid copy 696969 "Trash"
|
|
|
|
// 3 OK Completed
|
|
|
|
//
|
|
|
|
// See http://msdn.microsoft.com/en-us/library/gg651088(v=exchg.80).aspx for Status response codes.
|
|
|
|
//
|
2015-05-14 01:09:58 +02:00
|
|
|
[s appendFormat: @"<SrcMsgId>%@</SrcMsgId>", srcMessageId];
|
2014-12-08 16:25:37 +01:00
|
|
|
if ([prevSuccessfulMoveItemsOps objectForKey: srcMessageId])
|
|
|
|
{
|
|
|
|
// Previous move failed operation but we can recover the dstMessageId from previous request
|
|
|
|
[s appendFormat: @"<DstMsgId>%@</DstMsgId>", [prevSuccessfulMoveItemsOps objectForKey: srcMessageId]];
|
|
|
|
[s appendFormat: @"<Status>%d</Status>", 3];
|
|
|
|
[newSuccessfulMoveItemsOps setObject: [prevSuccessfulMoveItemsOps objectForKey: srcMessageId] forKey: srcMessageId];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
[s appendFormat: @"<Status>%d</Status>", 1];
|
|
|
|
}
|
2014-03-28 20:22:45 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//
|
|
|
|
// If the MoveItems operation is initiated by an Outlook client, we save the "deviceType+dstMessageId" to use it later in order to
|
|
|
|
// modify the Sync command from "add" to "change" (see SOGoActiveSyncDispatcher+Sync.m: -processSyncGetChanges: ...).
|
|
|
|
// This is to avoid Outlook creating dupes when moving messages across folfers.
|
|
|
|
//
|
|
|
|
if ([[context objectForKey: @"DeviceType"] isEqualToString: @"WindowsOutlook15"])
|
|
|
|
{
|
|
|
|
NSString *key;
|
|
|
|
|
|
|
|
// The key must be pretty verbose. We use the <uid>+<DeviceType>+<target folder>+<DstMsgId>
|
|
|
|
key = [NSString stringWithFormat: @"%@+%@+%@+%@",
|
|
|
|
[[context activeUser] login],
|
|
|
|
[context objectForKey: @"DeviceType"],
|
|
|
|
dstFolderId,
|
|
|
|
dstMessageId];
|
|
|
|
|
|
|
|
|
|
|
|
[[SOGoCache sharedCache] setValue: @"MovedItem"
|
|
|
|
forKey: key];
|
|
|
|
}
|
|
|
|
|
|
|
|
// Everything is alright, lets return the proper response. "Status == 3" means success.
|
|
|
|
[s appendFormat: @"<SrcMsgId>%@</SrcMsgId>", srcMessageId];
|
|
|
|
[s appendFormat: @"<DstMsgId>%@</DstMsgId>", dstMessageId];
|
|
|
|
[s appendFormat: @"<Status>%d</Status>", 3];
|
2014-12-08 16:25:37 +01:00
|
|
|
|
|
|
|
// Save dstMessageId in cache - it will help to recover if the request fails before the response can be sent to client
|
|
|
|
[newSuccessfulMoveItemsOps setObject: dstMessageId forKey: srcMessageId];
|
2014-03-28 20:22:45 +01:00
|
|
|
}
|
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
}
|
2014-03-28 20:22:45 +01:00
|
|
|
else
|
|
|
|
{
|
2014-10-29 19:20:03 +01:00
|
|
|
id srcCollection, dstCollection, srcSogoObject, dstSogoObject;
|
|
|
|
NSArray *elements;
|
|
|
|
NSString *newUID;
|
|
|
|
NSException *ex;
|
|
|
|
|
|
|
|
unsigned int count, max;
|
|
|
|
|
|
|
|
srcCollection = [self collectionFromId: srcFolderId type: srcFolderType];
|
|
|
|
dstCollection = [self collectionFromId: dstFolderId type: srcFolderType];
|
|
|
|
|
|
|
|
srcSogoObject = [srcCollection lookupName: [srcMessageId sanitizedServerIdWithType: srcFolderType]
|
|
|
|
inContext: context
|
|
|
|
acquire: NO];
|
|
|
|
|
|
|
|
sm = [SoSecurityManager sharedSecurityManager];
|
|
|
|
if (![sm validatePermission: SoPerm_DeleteObjects
|
|
|
|
onObject: srcCollection
|
|
|
|
inContext: context])
|
|
|
|
{
|
|
|
|
if (![sm validatePermission: SoPerm_AddDocumentsImagesAndFiles
|
|
|
|
onObject: dstCollection
|
|
|
|
inContext: context])
|
|
|
|
{
|
|
|
|
newUID = [srcSogoObject globallyUniqueObjectId];
|
|
|
|
dstSogoObject = [[SOGoAppointmentObject alloc] initWithName: [newUID sanitizedServerIdWithType: srcFolderType]
|
|
|
|
inContainer: dstCollection];
|
|
|
|
elements = [[srcSogoObject calendar: NO secure: NO] allObjects];
|
|
|
|
max = [elements count];
|
|
|
|
for (count = 0; count < max; count++)
|
|
|
|
[[elements objectAtIndex: count] setUid: newUID];
|
|
|
|
|
|
|
|
ex = [dstSogoObject saveCalendar: [srcSogoObject calendar: NO secure: NO]];
|
|
|
|
if (!ex)
|
|
|
|
{
|
|
|
|
ex = [srcSogoObject delete];
|
|
|
|
[s appendFormat: @"<SrcMsgId>%@</SrcMsgId>", srcMessageId];
|
|
|
|
[s appendFormat: @"<DstMsgId>%@</DstMsgId>", newUID];
|
|
|
|
[s appendFormat: @"<Status>%d</Status>", 3];
|
2014-12-08 16:25:37 +01:00
|
|
|
|
|
|
|
// Save dstMessageId in cache - it will help to recover if the request fails before the response can be sent to client
|
|
|
|
[newSuccessfulMoveItemsOps setObject: newUID forKey: srcMessageId];
|
2014-10-29 19:20:03 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2014-12-08 16:25:37 +01:00
|
|
|
if ([prevSuccessfulMoveItemsOps objectForKey: srcMessageId])
|
|
|
|
{
|
|
|
|
// Move failed but we can recover the dstMessageId from previous request
|
|
|
|
[s appendFormat: @"<SrcMsgId>%@</SrcMsgId>", srcMessageId];
|
|
|
|
[s appendFormat: @"<DstMsgId>%@</DstMsgId>", [prevSuccessfulMoveItemsOps objectForKey: srcMessageId] ];
|
|
|
|
[s appendFormat: @"<Status>%d</Status>", 3];
|
|
|
|
[newSuccessfulMoveItemsOps setObject: [prevSuccessfulMoveItemsOps objectForKey: srcMessageId] forKey: srcMessageId];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
[s appendFormat: @"<SrcMsgId>%@</SrcMsgId>", srcMessageId];
|
|
|
|
[s appendFormat: @"<Status>%d</Status>", 1];
|
|
|
|
}
|
2014-10-29 19:20:03 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
[s appendFormat: @"<SrcMsgId>%@</SrcMsgId>", srcMessageId];
|
|
|
|
[s appendFormat: @"<Status>%d</Status>", 2];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
[s appendFormat: @"<SrcMsgId>%@</SrcMsgId>", srcMessageId];
|
|
|
|
[s appendFormat: @"<Status>%d</Status>", 1];
|
|
|
|
}
|
2014-03-28 20:22:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
[s appendString: @"</Response>"];
|
2014-12-08 16:25:37 +01:00
|
|
|
|
|
|
|
[folderMetadata removeObjectForKey: @"SuccessfulMoveItemsOps"];
|
|
|
|
[folderMetadata setObject: newSuccessfulMoveItemsOps forKey: @"SuccessfulMoveItemsOps"];
|
|
|
|
[self _setFolderMetadata: folderMetadata forKey: nameInCache];
|
2014-01-10 20:12:53 +01:00
|
|
|
}
|
2014-03-28 20:22:45 +01:00
|
|
|
|
|
|
|
[s appendString: @"</MoveItems>"];
|
|
|
|
|
|
|
|
d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
|
|
|
|
|
|
|
|
[theResponse setContent: d];
|
2014-01-10 20:12:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//
|
2014-06-10 17:04:27 +02:00
|
|
|
// <?xml version="1.0"?>
|
|
|
|
// <!DOCTYPE ActiveSync PUBLIC "-//MICROSOFT//DTD ActiveSync//EN" "http://www.microsoft.com/">
|
|
|
|
// <Ping xmlns="Ping:">
|
|
|
|
// <HeartbeatInterval>3540</HeartbeatInterval>
|
|
|
|
// <Folders>
|
|
|
|
// <Folder>
|
|
|
|
// <Id>mail%2Fsogo_680f_193506d5_0</Id>
|
|
|
|
// <Class>Email</Class>
|
|
|
|
// </Folder>
|
|
|
|
// <Folder>
|
|
|
|
// <Id>vevent/personal</Id>
|
|
|
|
// <Class>Calendar</Class>
|
|
|
|
// </Folder>
|
|
|
|
// <Folder>
|
|
|
|
// <Id>vcard/personal</Id>
|
|
|
|
// <Class>Contacts</Class>
|
|
|
|
// </Folder>
|
|
|
|
// <Folder>
|
|
|
|
// <Id>mail%2Fsogo_680f_193506d5_1</Id>
|
|
|
|
// <Class>Email</Class>
|
|
|
|
// </Folder>
|
|
|
|
// <Folder>
|
|
|
|
// <Id>mail%2Fsogo_680f_193506d5_2</Id>
|
|
|
|
// <Class>Email</Class>
|
|
|
|
// </Folder>
|
|
|
|
// <Folder>
|
|
|
|
// <Id>vtodo/personal</Id>
|
|
|
|
// <Class>Tasks</Class>
|
|
|
|
// </Folder>
|
|
|
|
// <Folder>
|
|
|
|
// <Id>mail%2Fsogo_753e_193511a1_0</Id>
|
|
|
|
// <Class>Email</Class>
|
|
|
|
// </Folder>
|
|
|
|
// <Folder>
|
|
|
|
// <Id>mail%2Fsogo_753e_193511a1_1</Id>
|
|
|
|
// <Class>Email</Class>
|
|
|
|
// </Folder>
|
|
|
|
// </Folders>
|
|
|
|
// </Ping>
|
2014-01-10 20:12:53 +01:00
|
|
|
//
|
|
|
|
- (void) processPing: (id <DOMElement>) theDocumentElement
|
|
|
|
inResponse: (WOResponse *) theResponse
|
|
|
|
{
|
2016-03-28 23:37:07 +02:00
|
|
|
NSString *collectionId, *realCollectionId, *syncKey, *processIdentifier, *pingRequestInCache;
|
2014-06-10 17:04:27 +02:00
|
|
|
NSMutableArray *foldersWithChanges, *allFoldersID;
|
|
|
|
SOGoMicrosoftActiveSyncFolderType folderType;
|
|
|
|
NSMutableDictionary *folderMetadata;
|
2014-02-11 02:16:43 +01:00
|
|
|
SOGoSystemDefaults *defaults;
|
2016-03-28 23:37:07 +02:00
|
|
|
SOGoCacheGCSObject *o;
|
2014-06-10 17:04:27 +02:00
|
|
|
id <DOMElement> aCollection;
|
|
|
|
NSArray *allCollections;
|
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
NSMutableString *s;
|
2014-06-10 17:04:27 +02:00
|
|
|
id collection;
|
2014-01-10 20:12:53 +01:00
|
|
|
NSData *d;
|
2016-03-12 13:51:17 +01:00
|
|
|
NSAutoreleasePool *pool;
|
2014-06-10 17:04:27 +02:00
|
|
|
|
2016-03-30 20:31:57 +02:00
|
|
|
int i, j, heartbeatInterval, defaultInterval, internalInterval, status, total_sleep, sleepInterval;
|
2014-02-11 02:16:43 +01:00
|
|
|
|
2016-03-28 23:37:07 +02:00
|
|
|
// Let other ping requests know that a new request has arrived.
|
|
|
|
processIdentifier = [NSString stringWithFormat: @"%d", [[NSProcessInfo processInfo] processIdentifier]];
|
|
|
|
o = [SOGoCacheGCSObject objectWithName: [context objectForKey: @"DeviceId"] inContainer: nil useCache: NO];
|
|
|
|
[o setObjectType: ActiveSyncGlobalCacheObject];
|
|
|
|
[o setTableUrl: [self folderTableURL]];
|
|
|
|
[o reloadIfNeeded];
|
|
|
|
[[o properties] setObject: processIdentifier forKey: @"PingRequest"];
|
|
|
|
[o save];
|
|
|
|
|
2014-02-11 02:16:43 +01:00
|
|
|
defaults = [SOGoSystemDefaults sharedSystemDefaults];
|
|
|
|
defaultInterval = [defaults maximumPingInterval];
|
2014-06-10 17:04:27 +02:00
|
|
|
internalInterval = [defaults internalSyncInterval];
|
2016-03-30 20:31:57 +02:00
|
|
|
sleepInterval = (internalInterval < 5) ? internalInterval : 5;
|
2014-02-04 17:19:33 +01:00
|
|
|
|
|
|
|
if (theDocumentElement)
|
|
|
|
heartbeatInterval = [[[(id)[theDocumentElement getElementsByTagName: @"HeartbeatInterval"] lastObject] textValue] intValue];
|
|
|
|
else
|
2014-02-11 02:16:43 +01:00
|
|
|
heartbeatInterval = defaultInterval;
|
2014-06-10 17:04:27 +02:00
|
|
|
|
2014-02-11 02:16:43 +01:00
|
|
|
if (heartbeatInterval > defaultInterval || heartbeatInterval == 0)
|
2014-02-04 17:19:33 +01:00
|
|
|
{
|
2014-02-11 02:16:43 +01:00
|
|
|
heartbeatInterval = defaultInterval;
|
2014-02-04 17:19:33 +01:00
|
|
|
status = 5;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-02-12 19:07:34 +01:00
|
|
|
if (heartbeatInterval < internalInterval)
|
|
|
|
heartbeatInterval = internalInterval;
|
|
|
|
|
2014-02-04 17:19:33 +01:00
|
|
|
status = 1;
|
|
|
|
}
|
|
|
|
|
2014-06-10 17:04:27 +02:00
|
|
|
// We build the list of folders to "ping". When the payload is empty, we use the list
|
|
|
|
// of "cached" folders.
|
2015-01-28 21:03:49 +01:00
|
|
|
allCollections = (id)[theDocumentElement getElementsByTagName: @"Folder"];
|
2014-06-10 17:04:27 +02:00
|
|
|
allFoldersID = [NSMutableArray array];
|
|
|
|
|
|
|
|
if (![allCollections count])
|
|
|
|
{
|
2016-04-01 20:52:31 +02:00
|
|
|
heartbeatInterval = [[[o properties] objectForKey: @"PingHeartbeatInterval"] intValue];
|
2014-06-10 17:04:27 +02:00
|
|
|
|
2016-04-01 20:52:31 +02:00
|
|
|
if (debugOn)
|
|
|
|
[self logWithFormat: @"EAS - Empty Ping request - using cached HeatbeatInterval (%d)", heartbeatInterval];
|
2014-06-10 17:04:27 +02:00
|
|
|
|
2016-04-01 20:52:31 +02:00
|
|
|
if (heartbeatInterval > defaultInterval || heartbeatInterval == 0)
|
|
|
|
{
|
|
|
|
heartbeatInterval = defaultInterval;
|
|
|
|
status = 5;
|
|
|
|
}
|
2014-06-10 17:04:27 +02:00
|
|
|
|
2016-04-01 20:52:31 +02:00
|
|
|
allFoldersID = [[o properties] objectForKey: @"PingCachedFolders"];
|
|
|
|
if (![allFoldersID count])
|
|
|
|
{
|
|
|
|
// We received an empty Ping request. Return status '3' to ask client to resend the request with complete body.
|
|
|
|
s = [NSMutableString string];
|
|
|
|
[s appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
|
|
|
|
[s appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
|
|
|
|
[s appendString: @"<Ping xmlns=\"Ping:\">"];
|
|
|
|
[s appendString: @"<Status>3</Status>"];
|
|
|
|
[s appendString: @"</Ping>"];
|
|
|
|
|
|
|
|
d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
|
|
|
|
|
|
|
|
[theResponse setContent: d];
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (debugOn)
|
|
|
|
[self logWithFormat: @"EAS - Empty Ping request - using cached folders %@", allFoldersID];
|
2014-06-10 17:04:27 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
for (i = 0; i < [allCollections count]; i++)
|
|
|
|
{
|
|
|
|
aCollection = [allCollections objectAtIndex: i];
|
|
|
|
collectionId = [[(id) [aCollection getElementsByTagName: @"Id"] lastObject] textValue];
|
|
|
|
[allFoldersID addObject: collectionId];
|
|
|
|
}
|
2016-04-01 20:52:31 +02:00
|
|
|
|
|
|
|
if (![allFoldersID isEqualToArray: [[o properties] objectForKey: @"PingCachedFolders"]])
|
|
|
|
{
|
|
|
|
if (debugOn)
|
|
|
|
[self logWithFormat: @"EAS - Ping - Save folderlist to cache (HeartbeatInterval: %d) (%@)", heartbeatInterval, allFoldersID];
|
|
|
|
|
|
|
|
[[o properties] setObject: [NSNumber numberWithInteger: heartbeatInterval] forKey: @"PingHeartbeatInterval"];
|
|
|
|
[[o properties] setObject: allFoldersID forKey: @"PingCachedFolders"];
|
|
|
|
[o save];
|
|
|
|
}
|
2014-06-10 17:04:27 +02:00
|
|
|
}
|
2014-02-11 02:16:43 +01:00
|
|
|
|
2014-06-10 17:04:27 +02:00
|
|
|
foldersWithChanges = [NSMutableArray array];
|
|
|
|
|
|
|
|
// We enter our loop detection change
|
|
|
|
for (i = 0; i < (heartbeatInterval/internalInterval); i++)
|
|
|
|
{
|
2016-04-07 20:22:17 +02:00
|
|
|
if (easShouldTerminate)
|
2016-03-29 16:32:10 +02:00
|
|
|
break;
|
|
|
|
|
2016-03-12 13:51:17 +01:00
|
|
|
pool = [[NSAutoreleasePool alloc] init];
|
2014-06-10 17:04:27 +02:00
|
|
|
for (j = 0; j < [allFoldersID count]; j++)
|
|
|
|
{
|
|
|
|
collectionId = [allFoldersID objectAtIndex: j];
|
|
|
|
realCollectionId = [collectionId realCollectionIdWithFolderType: &folderType];
|
|
|
|
realCollectionId = [self globallyUniqueIDToIMAPFolderName: realCollectionId type: folderType];
|
2014-10-29 19:20:03 +01:00
|
|
|
|
|
|
|
if (folderType == ActiveSyncMailFolder)
|
2014-10-29 19:56:03 +01:00
|
|
|
folderMetadata = [self _folderMetadataForKey: [NSString stringWithFormat: @"folder%@", [[collectionId stringByUnescapingURL] substringFromIndex:5]]];
|
2014-10-29 19:20:03 +01:00
|
|
|
else
|
|
|
|
folderMetadata = [self _folderMetadataForKey: [collectionId stringByUnescapingURL]];
|
|
|
|
|
2014-06-10 17:04:27 +02:00
|
|
|
collection = [self collectionFromId: realCollectionId type: folderType];
|
2014-10-29 19:20:03 +01:00
|
|
|
|
2014-10-29 19:56:03 +01:00
|
|
|
// If collection doesn't exist skip it - next foldersync will do the cleanup
|
2014-10-29 19:20:03 +01:00
|
|
|
if (!collection)
|
|
|
|
continue;
|
2014-06-10 17:04:27 +02:00
|
|
|
|
|
|
|
syncKey = [folderMetadata objectForKey: @"SyncKey"];
|
|
|
|
|
2014-10-29 19:56:03 +01:00
|
|
|
if (syncKey && ![syncKey isEqualToString: [collection davCollectionTag]])
|
2014-06-10 17:04:27 +02:00
|
|
|
{
|
|
|
|
[foldersWithChanges addObject: collectionId];
|
|
|
|
}
|
|
|
|
}
|
2016-03-12 13:51:17 +01:00
|
|
|
DESTROY(pool);
|
|
|
|
|
2014-06-10 17:04:27 +02:00
|
|
|
if ([foldersWithChanges count])
|
|
|
|
{
|
2015-10-14 15:21:32 +02:00
|
|
|
[self logWithFormat: @"Change detected using Ping, we let the EAS client know to send a Sync."];
|
2014-06-10 17:04:27 +02:00
|
|
|
status = 2;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-03-28 23:37:07 +02:00
|
|
|
total_sleep = 0;
|
|
|
|
|
2016-04-07 20:22:17 +02:00
|
|
|
while (!easShouldTerminate && total_sleep < internalInterval)
|
2016-03-28 23:37:07 +02:00
|
|
|
{
|
|
|
|
// We check if we must break the current ping request since an other ping request
|
|
|
|
// has just arrived.
|
|
|
|
pingRequestInCache = [[self globalMetadataForDevice] objectForKey: @"PingRequest"];
|
|
|
|
if (pingRequestInCache && ![pingRequestInCache isEqualToString: processIdentifier])
|
|
|
|
{
|
|
|
|
if (debugOn)
|
|
|
|
[self logWithFormat: @"EAS - Ping request canceled (%@)", pingRequestInCache];
|
|
|
|
|
|
|
|
// Make sure we end the heardbeat-loop.
|
|
|
|
internalInterval = heartbeatInterval;
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
[self logWithFormat: @"Sleeping %d seconds while detecting changes in Ping...", internalInterval-total_sleep];
|
2016-03-30 20:31:57 +02:00
|
|
|
sleep(sleepInterval);
|
|
|
|
total_sleep += sleepInterval;
|
2016-03-28 23:37:07 +02:00
|
|
|
}
|
|
|
|
}
|
2014-06-10 17:04:27 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-02-04 17:19:33 +01:00
|
|
|
// We generate our response
|
2014-01-10 20:12:53 +01:00
|
|
|
s = [NSMutableString string];
|
|
|
|
[s appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
|
|
|
|
[s appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
|
|
|
|
[s appendString: @"<Ping xmlns=\"Ping:\">"];
|
2014-02-04 17:19:33 +01:00
|
|
|
[s appendFormat: @"<Status>%d</Status>", status];
|
2014-06-10 17:04:27 +02:00
|
|
|
|
|
|
|
if ([foldersWithChanges count])
|
|
|
|
{
|
|
|
|
[s appendString: @"<Folders>"];
|
|
|
|
|
|
|
|
for (i = 0; i < [foldersWithChanges count]; i++)
|
|
|
|
{
|
|
|
|
// A bit tricky here because we must call stringByEscapingURL on mail folders, but not on GCS ones.
|
|
|
|
// We do the same thing in -processFolderSync
|
|
|
|
collectionId = [foldersWithChanges objectAtIndex: i];
|
|
|
|
|
|
|
|
if ([collectionId hasPrefix: @"mail/"])
|
|
|
|
collectionId = [collectionId stringByEscapingURL];
|
|
|
|
|
|
|
|
[s appendFormat: @"<Folder>%@</Folder>", collectionId];
|
|
|
|
}
|
|
|
|
|
|
|
|
[s appendString: @"</Folders>"];
|
|
|
|
}
|
2014-02-04 17:19:33 +01:00
|
|
|
|
|
|
|
if (status == 5)
|
|
|
|
{
|
|
|
|
[s appendFormat: @"<HeartbeatInterval>%d</HeartbeatInterval>", heartbeatInterval];
|
|
|
|
}
|
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
[s appendString: @"</Ping>"];
|
|
|
|
|
|
|
|
d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
|
|
|
|
|
|
|
|
[theResponse setContent: d];
|
|
|
|
}
|
|
|
|
|
2014-05-27 15:42:02 +02:00
|
|
|
//
|
|
|
|
// We ignore everything for now.
|
|
|
|
//
|
|
|
|
- (void) processProvision: (id <DOMElement>) theDocumentElement
|
|
|
|
inResponse: (WOResponse *) theResponse
|
|
|
|
{
|
|
|
|
NSMutableString *s;
|
|
|
|
NSData *d;
|
|
|
|
|
|
|
|
s = [NSMutableString string];
|
|
|
|
[s appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
|
|
|
|
[s appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
|
|
|
|
[s appendString: @"<Provision xmlns=\"Provision:\">"];
|
2015-03-23 19:55:14 +01:00
|
|
|
[s appendString: @"<AllowHTMLEmail>1</AllowHTMLEmail>"];
|
2014-05-27 15:42:02 +02:00
|
|
|
[s appendString: @"</Provision>"];
|
|
|
|
|
|
|
|
d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
|
|
|
|
|
|
|
|
[theResponse setContent: d];
|
|
|
|
}
|
|
|
|
|
2015-10-15 21:31:46 +02:00
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
#ifdef HAVE_OPENSSL
|
|
|
|
- (unsigned int) validateCert: (NSString *) theCert
|
|
|
|
{
|
|
|
|
NSData *d;
|
|
|
|
|
|
|
|
const unsigned char *data;
|
|
|
|
X509_STORE_CTX *ctx;
|
|
|
|
X509_LOOKUP *lookup;
|
|
|
|
X509_STORE *store;
|
|
|
|
X509 *cert;
|
|
|
|
|
|
|
|
BOOL success;
|
|
|
|
size_t len;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
success = NO;
|
|
|
|
|
|
|
|
d = [theCert dataByDecodingBase64];
|
|
|
|
data = (unsigned char *)[d bytes];
|
|
|
|
len = [d length];
|
|
|
|
|
|
|
|
cert = d2i_X509(NULL, &data, len);
|
|
|
|
if (!cert)
|
|
|
|
{
|
|
|
|
[self logWithFormat: @"EAS - validateCert failed for device %@: d2i_X509 failed", [context objectForKey: @"DeviceId"]];
|
|
|
|
return 17;
|
|
|
|
}
|
|
|
|
|
|
|
|
store = X509_STORE_new();
|
|
|
|
OpenSSL_add_all_algorithms();
|
|
|
|
|
|
|
|
if (store)
|
|
|
|
{
|
|
|
|
lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file());
|
|
|
|
if (lookup)
|
|
|
|
{
|
|
|
|
X509_LOOKUP_load_file(lookup, NULL, X509_FILETYPE_DEFAULT);
|
|
|
|
lookup = X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir());
|
|
|
|
if (lookup)
|
|
|
|
{
|
|
|
|
X509_LOOKUP_add_dir(lookup, NULL, X509_FILETYPE_DEFAULT);
|
|
|
|
ERR_clear_error();
|
|
|
|
success = YES;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!success)
|
|
|
|
{
|
|
|
|
if (store)
|
|
|
|
{
|
|
|
|
X509_STORE_free(store);
|
|
|
|
store = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx = X509_STORE_CTX_new();
|
|
|
|
if (!ctx)
|
|
|
|
{
|
|
|
|
[self logWithFormat: @"EAS - validateCert failed for device %@: X509_STORE_CTX_new failed", [context objectForKey: @"DeviceId"]];
|
|
|
|
return 17;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (X509_STORE_CTX_init(ctx, store, cert, NULL) != 1)
|
|
|
|
{
|
|
|
|
[self logWithFormat: @"EAS - validateCert failed for device %@: X509_STORE_CTX_init failed", [context objectForKey: @"DeviceId"]];
|
|
|
|
X509_STORE_CTX_free(ctx);
|
|
|
|
return 17;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = X509_verify_cert(ctx);
|
|
|
|
X509_STORE_CTX_free(ctx);
|
|
|
|
X509_free(cert);
|
|
|
|
|
|
|
|
if (rc)
|
|
|
|
{
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
[self logWithFormat: @"EAS - validateCert failed for device %@: err=%d", [context objectForKey: @"DeviceId"], X509_STORE_CTX_get_error(ctx)];
|
|
|
|
return 17;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
- (unsigned int) validateCert: (NSString *) theCert
|
|
|
|
{
|
|
|
|
return 17;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
- (void) processValidateCert: (id <DOMElement>) theDocumentElement
|
|
|
|
inResponse: (WOResponse *) theResponse
|
|
|
|
{
|
|
|
|
NSMutableString *s;
|
|
|
|
NSString *cert;
|
|
|
|
NSData *d;
|
|
|
|
|
|
|
|
cert = [[(id)[theDocumentElement getElementsByTagName: @"Certificate"] lastObject] textValue];
|
|
|
|
|
|
|
|
s = [NSMutableString string];
|
|
|
|
[s appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
|
|
|
|
[s appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
|
|
|
|
[s appendString: @"<ValidateCert xmlns=\"ValidateCert:\">"];
|
|
|
|
[s appendString: @"<Status>1</Status><Certificate>"];
|
|
|
|
[s appendFormat: @"<Status>%d</Status>", [self validateCert: cert]];
|
|
|
|
[s appendString: @"</Certificate></ValidateCert>"];
|
|
|
|
|
|
|
|
d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
|
|
|
|
|
|
|
|
[theResponse setContent: d];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
//
|
|
|
|
// <?xml version="1.0"?>
|
|
|
|
// <!DOCTYPE ActiveSync PUBLIC "-//MICROSOFT//DTD ActiveSync//EN" "http://www.microsoft.com/">
|
|
|
|
// <ResolveRecipients xmlns="ResolveRecipients:">
|
|
|
|
// <To>sogo1@example.com</To>
|
|
|
|
// <To>sogo10@sogoludo.inverse</To>
|
|
|
|
// <Options>
|
|
|
|
// <MaxAmbiguousRecipients>19</MaxAmbiguousRecipients>
|
|
|
|
// <Availability>
|
|
|
|
// <StartTime>2014-01-16T05:00:00.000Z</StartTime>
|
|
|
|
// <EndTime>2014-01-17T04:59:00.000Z</EndTime>
|
|
|
|
// </Availability>
|
|
|
|
// </Options>
|
|
|
|
// </ResolveRecipients>
|
|
|
|
//
|
|
|
|
- (void) processResolveRecipients: (id <DOMElement>) theDocumentElement
|
|
|
|
inResponse: (WOResponse *) theResponse
|
|
|
|
{
|
|
|
|
NSArray *allRecipients;
|
|
|
|
int i, j, k;
|
|
|
|
|
|
|
|
allRecipients = (id)[theDocumentElement getElementsByTagName: @"To"];
|
|
|
|
|
|
|
|
if ([allRecipients count] && [(id)[theDocumentElement getElementsByTagName: @"Availability"] count])
|
|
|
|
{
|
|
|
|
NSCalendarDate *startDate, *endDate;
|
|
|
|
SOGoAppointmentFolder *folder;
|
|
|
|
NSString *aRecipient, *login;
|
|
|
|
NSMutableString *s;
|
|
|
|
NSArray *freebusy;
|
|
|
|
SOGoUser *user;
|
|
|
|
NSData *d;
|
|
|
|
|
|
|
|
unsigned int startdate, enddate, increments;
|
|
|
|
char c;
|
|
|
|
|
|
|
|
startDate = [[[(id)[theDocumentElement getElementsByTagName: @"StartTime"] lastObject] textValue] calendarDate];
|
|
|
|
startdate = [startDate timeIntervalSince1970];
|
|
|
|
|
|
|
|
endDate = [[[(id)[theDocumentElement getElementsByTagName: @"EndTime"] lastObject] textValue] calendarDate];
|
|
|
|
enddate = [endDate timeIntervalSince1970];
|
|
|
|
|
|
|
|
// Number of 30 mins increments between our two dates
|
|
|
|
increments = ceil((float)((enddate - startdate)/60/30)) + 1;
|
|
|
|
|
|
|
|
s = [NSMutableString string];
|
|
|
|
|
|
|
|
[s appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
|
|
|
|
[s appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
|
|
|
|
[s appendString: @"<ResolveRecipients xmlns=\"ResolveRecipients:\">"];
|
|
|
|
[s appendFormat: @"<Status>%d</Status>", 1];
|
|
|
|
|
|
|
|
for (i = 0; i < [allRecipients count]; i++)
|
|
|
|
{
|
|
|
|
aRecipient = [[allRecipients objectAtIndex: i] textValue];
|
|
|
|
|
|
|
|
login = [[SOGoUserManager sharedUserManager] getUIDForEmail: aRecipient];
|
|
|
|
|
|
|
|
if (login)
|
|
|
|
{
|
|
|
|
user = [SOGoUser userWithLogin: login];
|
|
|
|
|
|
|
|
[s appendString: @"<Response>"];
|
|
|
|
[s appendFormat: @"<To>%@</To>", aRecipient];
|
|
|
|
[s appendFormat: @"<Status>%d</Status>", 1];
|
|
|
|
[s appendFormat: @"<RecipientCount>%d</RecipientCount>", 1];
|
|
|
|
|
|
|
|
[s appendString: @"<Recipient>"];
|
|
|
|
[s appendFormat: @"<Type>%d</Type>", 1];
|
|
|
|
[s appendFormat: @"<DisplayName>%@</DisplayName>", [user cn]];
|
2014-02-17 14:46:05 +01:00
|
|
|
[s appendFormat: @"<EmailAddress>%@</EmailAddress>", [[user allEmails] objectAtIndex: 0]];
|
2014-01-10 20:12:53 +01:00
|
|
|
|
|
|
|
// Freebusy structure: http://msdn.microsoft.com/en-us/library/gg663493(v=exchg.80).aspx
|
|
|
|
[s appendString: @"<Availability>"];
|
|
|
|
[s appendFormat: @"<Status>%d</Status>", 1];
|
|
|
|
[s appendString: @"<MergedFreeBusy>"];
|
|
|
|
|
|
|
|
folder = [user personalCalendarFolderInContext: context];
|
|
|
|
freebusy = [folder fetchFreeBusyInfosFrom: startDate to: endDate];
|
|
|
|
|
|
|
|
|
|
|
|
NGCalendarDateRange *r1, *r2;
|
|
|
|
|
|
|
|
for (j = 1; j <= increments; j++)
|
|
|
|
{
|
|
|
|
c = '0';
|
|
|
|
|
|
|
|
r1 = [NGCalendarDateRange calendarDateRangeWithStartDate: [NSDate dateWithTimeIntervalSince1970: (startdate+j*30*60)]
|
|
|
|
endDate: [NSDate dateWithTimeIntervalSince1970: (startdate+j*30*60 + 30)]];
|
|
|
|
|
|
|
|
|
|
|
|
for (k = 0; k < [freebusy count]; k++)
|
|
|
|
{
|
|
|
|
|
|
|
|
r2 = [NGCalendarDateRange calendarDateRangeWithStartDate: [[freebusy objectAtIndex: k] objectForKey: @"startDate"]
|
|
|
|
endDate: [[freebusy objectAtIndex: k] objectForKey: @"endDate"]];
|
|
|
|
|
|
|
|
if ([r2 doesIntersectWithDateRange: r1])
|
|
|
|
{
|
|
|
|
c = '2';
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[s appendFormat: @"%c", c];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[s appendString: @"</MergedFreeBusy>"];
|
|
|
|
[s appendString: @"</Availability>"];
|
|
|
|
|
|
|
|
|
|
|
|
[s appendString: @"</Recipient>"];
|
|
|
|
[s appendString: @"</Response>"];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
[s appendString: @"</ResolveRecipients>"];
|
|
|
|
|
|
|
|
d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
|
|
|
|
|
|
|
|
[theResponse setContent: d];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// <?xml version="1.0"?>
|
|
|
|
// <!DOCTYPE ActiveSync PUBLIC "-//MICROSOFT//DTD ActiveSync//EN" "http://www.microsoft.com/">
|
|
|
|
// <Search xmlns="Search:">
|
|
|
|
// <Store>
|
|
|
|
// <Name>GAL</Name>
|
|
|
|
// <Query>so</Query>
|
|
|
|
// <Options>
|
|
|
|
// <Range>0-19</Range>
|
|
|
|
// </Options>
|
|
|
|
// </Store>
|
|
|
|
// </Search>
|
|
|
|
//
|
|
|
|
- (void) processSearch: (id <DOMElement>) theDocumentElement
|
|
|
|
inResponse: (WOResponse *) theResponse
|
|
|
|
{
|
|
|
|
SOGoContactSourceFolder *currentFolder;
|
2015-05-12 16:28:47 +02:00
|
|
|
NSArray *allKeys, *allContacts, *mails;
|
2014-01-10 20:12:53 +01:00
|
|
|
NSDictionary *systemSources, *contact;
|
2015-05-12 16:28:47 +02:00
|
|
|
NSString *name, *query, *current_mail;
|
2014-01-10 20:12:53 +01:00
|
|
|
SOGoContactFolders *contactFolders;
|
|
|
|
SOGoUserFolder *userFolder;
|
|
|
|
NSMutableString *s;
|
|
|
|
NSData *d;
|
2015-05-12 16:28:47 +02:00
|
|
|
id o;
|
2014-01-10 20:12:53 +01:00
|
|
|
|
|
|
|
int i, j, total;
|
|
|
|
|
|
|
|
name = [[(id)[theDocumentElement getElementsByTagName: @"Name"] lastObject] textValue];
|
|
|
|
query = [[(id)[theDocumentElement getElementsByTagName: @"Query"] lastObject] textValue];
|
|
|
|
|
|
|
|
// FIXME: for now, we only search in the GAL
|
|
|
|
if (![name isEqualToString: @"GAL"])
|
|
|
|
{
|
|
|
|
[theResponse setStatus: 500];
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
userFolder = [[context activeUser] homeFolderInContext: context];
|
|
|
|
contactFolders = [userFolder privateContacts: @"Contacts" inContext: context];
|
|
|
|
systemSources = [contactFolders systemSources];
|
|
|
|
allKeys = [systemSources allKeys];
|
|
|
|
|
|
|
|
s = [NSMutableString string];
|
|
|
|
|
|
|
|
[s appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
|
|
|
|
[s appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
|
|
|
|
[s appendString: @"<Search xmlns=\"Search:\">"];
|
|
|
|
[s appendFormat: @"<Status>1</Status>"];
|
|
|
|
[s appendFormat: @"<Response>"];
|
|
|
|
[s appendFormat: @"<Store>"];
|
|
|
|
[s appendFormat: @"<Status>1</Status>"];
|
|
|
|
|
|
|
|
total = 0;
|
|
|
|
|
|
|
|
for (i = 0; i < [allKeys count]; i++)
|
|
|
|
{
|
|
|
|
currentFolder = [systemSources objectForKey: [allKeys objectAtIndex: i]];
|
|
|
|
allContacts = [currentFolder lookupContactsWithFilter: query
|
|
|
|
onCriteria: @"name_or_address"
|
|
|
|
sortBy: @"c_cn"
|
|
|
|
ordering: NSOrderedAscending
|
|
|
|
inDomain: [[context activeUser] domain]];
|
|
|
|
|
|
|
|
for (j = 0; j < [allContacts count]; j++)
|
|
|
|
{
|
|
|
|
contact = [allContacts objectAtIndex: j];
|
|
|
|
|
2016-06-06 19:07:48 +02:00
|
|
|
// We skip lists for now and bogus entries
|
|
|
|
if ([[contact objectForKey: @"c_component"] isEqualToString: @"vlist"] ||
|
|
|
|
[[contact objectForKey: @"c_name"] length] == 0)
|
2014-01-10 20:12:53 +01:00
|
|
|
continue;
|
|
|
|
|
|
|
|
// We get the LDIF entry of our record, for easier processing
|
|
|
|
contact = [[currentFolder lookupName: [contact objectForKey: @"c_name"] inContext: context acquire: NO] ldifRecord];
|
2015-05-12 16:28:47 +02:00
|
|
|
|
|
|
|
o = [contact objectForKey: @"mail"];
|
|
|
|
if ([o isKindOfClass: [NSArray class]])
|
|
|
|
mails = o;
|
|
|
|
else
|
|
|
|
mails = [NSArray arrayWithObjects: o ? o : @"", nil];
|
|
|
|
|
|
|
|
for (total = 0; total < [mails count]; total++)
|
|
|
|
{
|
|
|
|
current_mail = [mails objectAtIndex: total];
|
|
|
|
|
|
|
|
[s appendString: @"<Result xmlns=\"Search:\">"];
|
|
|
|
[s appendString: @"<Properties>"];
|
|
|
|
|
|
|
|
if ((o = [contact objectForKey: @"displayname"]))
|
|
|
|
[s appendFormat: @"<DisplayName xmlns=\"Gal:\">%@</DisplayName>", o];
|
|
|
|
|
|
|
|
if ((o = [contact objectForKey: @"title"]))
|
|
|
|
[s appendFormat: @"<Title xmlns=\"Gal:\">%@</Title>", o];
|
|
|
|
|
|
|
|
if ((o = [contact objectForKey: @"givenname"]))
|
|
|
|
[s appendFormat: @"<FirstName xmlns=\"Gal:\">%@</FirstName>", o];
|
|
|
|
|
|
|
|
if ((o = [contact objectForKey: @"sn"]))
|
|
|
|
[s appendFormat: @"<LastName xmlns=\"Gal:\">%@</LastName>", o];
|
|
|
|
|
|
|
|
if ([current_mail length] > 0)
|
|
|
|
[s appendFormat: @"<EmailAddress xmlns=\"Gal:\">%@</EmailAddress>", current_mail];
|
|
|
|
|
|
|
|
if ((o = [contact objectForKey: @"telephonenumber"]))
|
|
|
|
[s appendFormat: @"<Phone xmlns=\"Gal:\">%@</Phone>", o];
|
|
|
|
|
|
|
|
if ((o = [contact objectForKey: @"homephone"]))
|
|
|
|
[s appendFormat: @"<HomePhone xmlns=\"Gal:\">%@</HomePhone>", o];
|
|
|
|
|
|
|
|
if ((o = [contact objectForKey: @"mobile"]))
|
|
|
|
[s appendFormat: @"<MobilePhone xmlns=\"Gal:\">%@</MobilePhone>", o];
|
|
|
|
|
|
|
|
if ((o = [contact objectForKey: @"o"]))
|
|
|
|
[s appendFormat: @"<Company xmlns=\"Gal:\">%@</Company>", o];
|
|
|
|
|
|
|
|
[s appendString: @"</Properties>"];
|
|
|
|
[s appendString: @"</Result>"];
|
|
|
|
}
|
2014-01-10 20:12:53 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
[s appendFormat: @"<Range>0-%d</Range>", total-1];
|
|
|
|
[s appendFormat: @"<Total>%d</Total>", total];
|
|
|
|
[s appendString: @"</Store>"];
|
|
|
|
[s appendString: @"</Response>"];
|
|
|
|
[s appendString: @"</Search>"];
|
|
|
|
|
|
|
|
d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
|
|
|
|
|
|
|
|
[theResponse setContent: d];
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
- (NSException *) _sendMail: (NSData *) theMail
|
|
|
|
recipients: (NSArray *) theRecipients
|
|
|
|
saveInSentItems: (BOOL) saveInSentItems
|
|
|
|
{
|
|
|
|
id <SOGoAuthenticator> authenticator;
|
|
|
|
SOGoDomainDefaults *dd;
|
|
|
|
NSException *error;
|
|
|
|
NSString *from;
|
|
|
|
|
|
|
|
authenticator = [SOGoDAVAuthenticator sharedSOGoDAVAuthenticator];
|
|
|
|
dd = [[context activeUser] domainDefaults];
|
|
|
|
|
|
|
|
// We generate the Sender
|
|
|
|
from = [[[context activeUser] allEmails] objectAtIndex: 0];
|
|
|
|
|
|
|
|
error = [[SOGoMailer mailerWithDomainDefaults: dd]
|
|
|
|
sendMailData: theMail
|
|
|
|
toRecipients: theRecipients
|
|
|
|
sender: from
|
|
|
|
withAuthenticator: authenticator
|
|
|
|
inContext: context];
|
|
|
|
|
|
|
|
if (error)
|
|
|
|
{
|
|
|
|
return error;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (saveInSentItems)
|
|
|
|
{
|
|
|
|
SOGoMailAccounts *accountsFolder;
|
|
|
|
SOGoMailAccount *accountFolder;
|
|
|
|
SOGoUserFolder *userFolder;
|
|
|
|
SOGoSentFolder *sentFolder;
|
|
|
|
|
|
|
|
userFolder = [[context activeUser] homeFolderInContext: context];
|
|
|
|
accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
|
|
|
|
accountFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO];
|
|
|
|
sentFolder = [accountFolder sentFolderInContext: context];
|
|
|
|
|
|
|
|
[sentFolder postData: theMail flags: @"seen"];
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
2016-06-23 17:06:44 +02:00
|
|
|
- (BOOL) _isEMailValid: (NSString *) email
|
|
|
|
{
|
|
|
|
NSArray *identities;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
identities = [[context activeUser] allIdentities];
|
|
|
|
|
|
|
|
for (i = 0; i < [identities count]; i++)
|
|
|
|
{
|
|
|
|
if ([email isEqualToString: [[identities objectAtIndex: i] objectForKey: @"email"]])
|
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSString *) _fullNameForEMail: (NSString *) email
|
|
|
|
{
|
|
|
|
NSArray *identities;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
identities = [[context activeUser] allIdentities];
|
|
|
|
|
|
|
|
for (i = 0; i < [identities count]; i++)
|
|
|
|
{
|
|
|
|
if ([email isEqualToString: [[identities objectAtIndex: i] objectForKey: @"email"]])
|
|
|
|
return [[identities objectAtIndex: i] objectForKey: @"fullName"];
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
- (void) processSendMail: (id <DOMElement>) theDocumentElement
|
|
|
|
inResponse: (WOResponse *) theResponse
|
|
|
|
{
|
|
|
|
NGMimeMessageParser *parser;
|
|
|
|
NGMimeMessage *message;
|
|
|
|
NSException *error;
|
2015-10-15 21:31:46 +02:00
|
|
|
NSMutableData *data;
|
|
|
|
NSData *new_from_header;
|
2015-01-12 15:09:06 +01:00
|
|
|
NSDictionary *identity;
|
|
|
|
NSString *fullName, *email;
|
2016-06-23 17:06:44 +02:00
|
|
|
NSArray *from;
|
2015-10-15 21:31:46 +02:00
|
|
|
|
|
|
|
const char *bytes;
|
2015-11-25 21:08:24 +01:00
|
|
|
int i, e, len;
|
2015-10-15 21:31:46 +02:00
|
|
|
BOOL found_header;
|
2016-06-23 17:06:44 +02:00
|
|
|
email = nil;
|
2014-01-10 20:12:53 +01:00
|
|
|
|
|
|
|
// We get the mail's data
|
2015-10-15 21:31:46 +02:00
|
|
|
data = [NSMutableData dataWithData: [[[[(id)[theDocumentElement getElementsByTagName: @"MIME"] lastObject] textValue] stringByDecodingBase64] dataUsingEncoding: NSUTF8StringEncoding]];
|
2014-01-10 20:12:53 +01:00
|
|
|
|
|
|
|
// We extract the recipients
|
|
|
|
parser = [[NGMimeMessageParser alloc] init];
|
|
|
|
message = [parser parsePartFromData: data];
|
|
|
|
RELEASE(parser);
|
2015-01-12 15:09:06 +01:00
|
|
|
|
2016-06-23 17:06:44 +02:00
|
|
|
from = [message headersForKey: @"from"];
|
2015-10-15 21:31:46 +02:00
|
|
|
|
2016-06-23 17:06:44 +02:00
|
|
|
if (![from count] || ![self _isEMailValid: [[from objectAtIndex: 0] pureEMailAddress]] ||
|
|
|
|
[[[from objectAtIndex: 0] pureEMailAddress] isEqualToString: [from objectAtIndex: 0]] ||
|
|
|
|
[[NSString stringWithFormat: @"<%@>", [[from objectAtIndex: 0] pureEMailAddress]] isEqualToString: [from objectAtIndex: 0]])
|
2015-10-15 21:31:46 +02:00
|
|
|
{
|
2016-06-23 17:06:44 +02:00
|
|
|
if ([from count] && [self _isEMailValid: [[from objectAtIndex: 0] pureEMailAddress]])
|
2015-10-15 21:31:46 +02:00
|
|
|
{
|
2016-06-23 17:06:44 +02:00
|
|
|
// We have a valid email address, lets fill in the fullname.
|
|
|
|
email = [[from objectAtIndex: 0] pureEMailAddress];
|
|
|
|
fullName = [self _fullNameForEMail: email];
|
2015-10-15 21:31:46 +02:00
|
|
|
}
|
2016-06-23 17:06:44 +02:00
|
|
|
else
|
2015-10-15 21:31:46 +02:00
|
|
|
{
|
2016-06-23 17:06:44 +02:00
|
|
|
// Fallback to primary identity.
|
|
|
|
identity = [[context activeUser] primaryIdentity];
|
|
|
|
fullName = [identity objectForKey: @"fullName"];
|
|
|
|
email = [identity objectForKey: @"email"];
|
2015-10-15 21:31:46 +02:00
|
|
|
}
|
|
|
|
|
2016-06-23 17:06:44 +02:00
|
|
|
if ([fullName length])
|
|
|
|
new_from_header = [[NSString stringWithFormat: @"From: %@ <%@>\r\n", [fullName asQPSubjectString: @"utf-8"], email] dataUsingEncoding: NSUTF8StringEncoding];
|
|
|
|
else
|
|
|
|
new_from_header = [[NSString stringWithFormat: @"From: %@\r\n", email] dataUsingEncoding: NSUTF8StringEncoding];
|
2015-10-15 21:31:46 +02:00
|
|
|
|
2016-06-23 17:06:44 +02:00
|
|
|
bytes = [data bytes];
|
|
|
|
len = [data length];
|
|
|
|
i = 0;
|
|
|
|
found_header = NO;
|
2015-11-25 21:08:24 +01:00
|
|
|
|
2016-06-23 17:06:44 +02:00
|
|
|
// Search for the from-header
|
|
|
|
while (i < len)
|
|
|
|
{
|
|
|
|
if (i == 0 &&
|
|
|
|
(*bytes == 'f' || *bytes == 'F') &&
|
|
|
|
(*(bytes+1) == 'r' || *(bytes+1) == 'R') &&
|
|
|
|
(*(bytes+2) == 'o' || *(bytes+2) == 'O') &&
|
|
|
|
(*(bytes+3) == 'm' || *(bytes+3) == 'M') &&
|
|
|
|
(*(bytes+4) == ':'))
|
|
|
|
{
|
|
|
|
found_header = YES;
|
|
|
|
break;
|
|
|
|
}
|
2015-11-25 21:08:24 +01:00
|
|
|
|
2016-06-23 17:06:44 +02:00
|
|
|
if (((*bytes == '\r') && (*(bytes+1) == '\n')) &&
|
|
|
|
(*(bytes+2) == 'f' || *(bytes+2) == 'F') &&
|
|
|
|
(*(bytes+3) == 'r' || *(bytes+3) == 'R') &&
|
|
|
|
(*(bytes+4) == 'o' || *(bytes+4) == 'O') &&
|
|
|
|
(*(bytes+5) == 'm' || *(bytes+5) == 'M') &&
|
|
|
|
(*(bytes+6) == ':'))
|
|
|
|
{
|
|
|
|
found_header = YES;
|
|
|
|
i = i + 2; // \r\n
|
|
|
|
bytes = bytes + 2;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
bytes++;
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
|
|
|
|
// We search for the first \r\n AFTER the From: header to get the length of the string to replace.
|
|
|
|
e = i;
|
|
|
|
while (e < len)
|
|
|
|
{
|
|
|
|
if ((*bytes == '\r') && (*(bytes+1) == '\n'))
|
|
|
|
{
|
|
|
|
e = e + 2;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
bytes++;
|
|
|
|
e++;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update/Add the From header in the MIMEBody of the SendMail request.
|
|
|
|
// Any other way to modify the mail body would break s/mime emails.
|
|
|
|
if (found_header)
|
|
|
|
{
|
|
|
|
// Change the From header
|
|
|
|
[data replaceBytesInRange: NSMakeRange(i, (NSUInteger)(e-i))
|
|
|
|
withBytes: [new_from_header bytes]
|
|
|
|
length: [new_from_header length]];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Add a From header
|
|
|
|
[data replaceBytesInRange: NSMakeRange(0, 0)
|
|
|
|
withBytes: [new_from_header bytes]
|
|
|
|
length: [new_from_header length]];
|
|
|
|
}
|
2015-10-15 21:31:46 +02:00
|
|
|
}
|
2015-01-12 15:09:06 +01:00
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
error = [self _sendMail: data
|
|
|
|
recipients: [message allRecipients]
|
2015-03-18 14:36:35 +01:00
|
|
|
saveInSentItems: ([(id)[theDocumentElement getElementsByTagName: @"SaveInSentItems"] count] ? YES : NO)];
|
2014-01-10 20:12:53 +01:00
|
|
|
|
|
|
|
if (error)
|
|
|
|
{
|
|
|
|
[theResponse setStatus: 500];
|
|
|
|
[theResponse appendContentString: @"FATAL ERROR occured during SendMail"];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
//
|
|
|
|
// Examples:
|
|
|
|
//
|
|
|
|
// <?xml version="1.0"?>
|
|
|
|
// <!DOCTYPE ActiveSync PUBLIC "-//MICROSOFT//DTD ActiveSync//EN" "http://www.microsoft.com/">
|
|
|
|
// <Settings xmlns="Settings:">
|
|
|
|
// <Oof>
|
|
|
|
// <Get>
|
|
|
|
// <BodyType>text</BodyType>
|
|
|
|
// </Get>
|
|
|
|
// </Oof>
|
|
|
|
// </Settings>
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
// "POST /SOGo/Microsoft-Server-ActiveSync?Cmd=Settings&User=sogo10&DeviceId=SEC17CD1A3E9E3F2&DeviceType=SAMSUNGSGHI317M HTTP/1.1"
|
|
|
|
//
|
|
|
|
// <?xml version="1.0"?>
|
|
|
|
// <!DOCTYPE ActiveSync PUBLIC "-//MICROSOFT//DTD ActiveSync//EN" "http://www.microsoft.com/">
|
|
|
|
// <Settings xmlns="Settings:">
|
|
|
|
// <DeviceInformation>
|
|
|
|
// <Set>
|
|
|
|
// <Model>SGH-I317M</Model>
|
|
|
|
// <IMEI>354422050248226</IMEI>
|
|
|
|
// <FriendlyName>t0ltevl</FriendlyName>
|
|
|
|
// <OS>Android</OS>
|
|
|
|
// <OSLanguage>English</OSLanguage>
|
|
|
|
// <PhoneNumber>15147553630</PhoneNumber>
|
|
|
|
// <UserAgent>SAMSUNG-SGH-I317M/100.40102</UserAgent>
|
|
|
|
// <EnableOutboundSMS>0</EnableOutboundSMS>
|
|
|
|
// <MobileOperator>Koodo</MobileOperator>
|
|
|
|
// </Set>
|
|
|
|
// </DeviceInformation>
|
|
|
|
// </Settings>
|
|
|
|
//
|
|
|
|
// We ignore everything for now
|
|
|
|
//
|
|
|
|
- (void) processSettings: (id <DOMElement>) theDocumentElement
|
|
|
|
inResponse: (WOResponse *) theResponse
|
|
|
|
{
|
2016-06-23 17:34:50 +02:00
|
|
|
SOGoDomainDefaults *dd;
|
|
|
|
NSMutableDictionary *vacationOptions;
|
2014-01-10 20:12:53 +01:00
|
|
|
NSMutableString *s;
|
|
|
|
NSData *d;
|
2016-06-23 17:34:50 +02:00
|
|
|
int OofState, time, i;
|
|
|
|
id setElements;
|
2016-06-29 17:00:07 +02:00
|
|
|
NSCalendarDate *startDate, *endDate;
|
2016-06-23 17:34:50 +02:00
|
|
|
NSString *autoReplyText;
|
|
|
|
NSArray *OofMessages;
|
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
s = [NSMutableString string];
|
2016-06-23 17:34:50 +02:00
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
[s appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
|
|
|
|
[s appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
|
|
|
|
[s appendString: @"<Settings xmlns=\"Settings:\">"];
|
2016-06-23 17:34:50 +02:00
|
|
|
[s appendString: @"<Status>1</Status>"];
|
|
|
|
|
|
|
|
if ([(id)[[(id)[theDocumentElement getElementsByTagName: @"Oof"] lastObject] getElementsByTagName: @"Get"] lastObject])
|
|
|
|
{
|
|
|
|
dd = [[context activeUser] domainDefaults];
|
|
|
|
if ([dd vacationEnabled])
|
|
|
|
{
|
|
|
|
vacationOptions = [[[[context activeUser] userDefaults] vacationOptions] mutableCopy];
|
|
|
|
if (!vacationOptions)
|
|
|
|
vacationOptions = [NSMutableDictionary new];
|
|
|
|
|
|
|
|
if ([[vacationOptions objectForKey: @"enabled"] boolValue] && [[vacationOptions objectForKey: @"endDateEnabled"] intValue])
|
|
|
|
OofState = 2;
|
|
|
|
else if ([[vacationOptions objectForKey: @"enabled"] boolValue])
|
|
|
|
OofState = 1;
|
|
|
|
else
|
|
|
|
OofState = 0;
|
|
|
|
|
|
|
|
[s appendString: @"<Oof>"];
|
|
|
|
[s appendString: @"<Status>1</Status>"];
|
|
|
|
[s appendString: @"<Get>"];
|
|
|
|
[s appendFormat: @"<OofState>%d</OofState>", OofState];
|
|
|
|
|
2016-06-29 17:00:07 +02:00
|
|
|
time = [[vacationOptions objectForKey: @"startDate"] intValue];
|
|
|
|
[s appendFormat: @"<StartTime>%@</StartTime>", [[NSCalendarDate dateWithTimeIntervalSince1970: time] activeSyncRepresentationInContext: context]];
|
2016-06-23 17:34:50 +02:00
|
|
|
|
2016-06-29 17:00:07 +02:00
|
|
|
time = [[vacationOptions objectForKey: @"endDate"] intValue];
|
2016-06-23 17:34:50 +02:00
|
|
|
[s appendFormat: @"<EndTime>%@</EndTime>", [[NSCalendarDate dateWithTimeIntervalSince1970: time] activeSyncRepresentationInContext: context]];
|
|
|
|
|
|
|
|
[s appendFormat: @"<OofMessage>"];
|
|
|
|
[s appendFormat: @"<AppliesToInternal/>"];
|
|
|
|
[s appendFormat: @"<Enabled>%d</Enabled>", (OofState) ? 1 : 0];
|
|
|
|
[s appendFormat: @"<ReplyMessage>%@</ReplyMessage>", [vacationOptions objectForKey: @"autoReplyText"]];
|
|
|
|
[s appendFormat: @"<BodyType>TEXT</BodyType>"];
|
|
|
|
[s appendFormat: @"</OofMessage>"];
|
|
|
|
|
|
|
|
[s appendFormat: @"<OofMessage>"];
|
|
|
|
[s appendFormat: @"<AppliesToExternalKnown/>"];
|
|
|
|
[s appendFormat: @"<Enabled>0</Enabled>"];
|
|
|
|
[s appendFormat: @"<ReplyMessage/>"];
|
|
|
|
[s appendFormat: @"</OofMessage>"];
|
|
|
|
|
|
|
|
[s appendFormat: @"<OofMessage>"];
|
|
|
|
[s appendFormat: @"<AppliesToExternalUnknown/>"];
|
|
|
|
[s appendFormat: @"<Enabled>0</Enabled>"];
|
|
|
|
[s appendFormat: @"<ReplyMessage/>"];
|
|
|
|
[s appendFormat: @"</OofMessage>"];
|
|
|
|
|
|
|
|
[s appendString: @"</Get>"];
|
|
|
|
[s appendString: @"</Oof>"];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ([(id)[[(id)[theDocumentElement getElementsByTagName: @"Oof"] lastObject] getElementsByTagName: @"Set"] lastObject])
|
|
|
|
{
|
|
|
|
dd = [[context activeUser] domainDefaults];
|
|
|
|
if ([dd vacationEnabled])
|
|
|
|
{
|
|
|
|
setElements = [(id)[[(id)[theDocumentElement getElementsByTagName: @"Oof"] lastObject] getElementsByTagName: @"Set"] lastObject];
|
|
|
|
OofState = [[[(id)[setElements getElementsByTagName: @"OofState"] lastObject] textValue] intValue];
|
|
|
|
OofMessages = (id)[setElements getElementsByTagName: @"OofMessage"];
|
|
|
|
|
|
|
|
autoReplyText = [NSMutableString string];
|
|
|
|
|
|
|
|
for (i = 0; i < [OofMessages count]; i++)
|
|
|
|
{
|
|
|
|
if ([(id)[[OofMessages objectAtIndex: i] getElementsByTagName: @"AppliesToInternal"] lastObject])
|
|
|
|
{
|
|
|
|
autoReplyText = [[(id)[[OofMessages objectAtIndex: i] getElementsByTagName: @"ReplyMessage"] lastObject] textValue];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
vacationOptions = [[[[context activeUser] userDefaults] vacationOptions] mutableCopy];
|
|
|
|
|
|
|
|
if (!vacationOptions)
|
|
|
|
vacationOptions = [NSMutableDictionary new];
|
|
|
|
|
|
|
|
[vacationOptions setObject: [NSNumber numberWithBool: (OofState > 0) ? YES : NO]
|
|
|
|
forKey: @"enabled"];
|
|
|
|
|
2016-06-29 17:00:07 +02:00
|
|
|
startDate = [[[(id)[setElements getElementsByTagName: @"StartTime"] lastObject] textValue] calendarDate];
|
|
|
|
|
|
|
|
if (startDate)
|
|
|
|
[vacationOptions setObject: [NSNumber numberWithInt: [startDate timeIntervalSince1970]] forKey: @"startDate"];
|
|
|
|
|
|
|
|
[vacationOptions setObject: [NSNumber numberWithBool: (OofState == 2) ? YES : NO]
|
|
|
|
forKey: @"startDateEnabled"];
|
|
|
|
|
2016-06-23 17:34:50 +02:00
|
|
|
[vacationOptions setObject: [NSNumber numberWithBool: (OofState == 2) ? YES : NO]
|
|
|
|
forKey: @"endDateEnabled"];
|
|
|
|
|
|
|
|
endDate = [[[(id)[setElements getElementsByTagName: @"EndTime"] lastObject] textValue] calendarDate];
|
|
|
|
|
|
|
|
if (endDate)
|
|
|
|
[vacationOptions setObject: [NSNumber numberWithInt: [endDate timeIntervalSince1970]] forKey: @"endDate"];
|
|
|
|
|
|
|
|
if (autoReplyText)
|
|
|
|
[vacationOptions setObject: autoReplyText forKey: @"autoReplyText"];
|
|
|
|
|
|
|
|
[[[context activeUser] userDefaults] setVacationOptions: vacationOptions];
|
|
|
|
[[[context activeUser] userDefaults] synchronize];
|
|
|
|
|
|
|
|
[s appendString: @"<Oof><Status>1</Status></Oof>"];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
[s appendString: @"</Settings>"];
|
|
|
|
|
|
|
|
d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
|
|
|
|
|
|
|
|
[theResponse setContent: d];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-05-14 21:49:53 +02:00
|
|
|
- (void) _processSmartCommand: (id <DOMElement>) theDocumentElement
|
|
|
|
inResponse: (WOResponse *) theResponse
|
|
|
|
isSmartForward: (BOOL ) isSmartForward
|
2014-01-10 20:12:53 +01:00
|
|
|
{
|
|
|
|
NSString *folderId, *itemId, *realCollectionId;
|
|
|
|
SOGoMicrosoftActiveSyncFolderType folderType;
|
2015-05-14 21:49:53 +02:00
|
|
|
SOGoUserDefaults *ud;
|
|
|
|
|
|
|
|
BOOL htmlComposition, isHTML;
|
2014-04-09 18:19:23 +02:00
|
|
|
id value;
|
2015-05-14 21:49:53 +02:00
|
|
|
|
|
|
|
isHTML = NO;
|
|
|
|
ud = [[context activeUser] userDefaults];
|
2014-01-10 20:12:53 +01:00
|
|
|
|
|
|
|
folderId = [[(id)[theDocumentElement getElementsByTagName: @"FolderId"] lastObject] textValue];
|
2015-02-26 23:53:58 +01:00
|
|
|
|
|
|
|
// if folderId is not there try to get it from URL
|
|
|
|
if (!folderId)
|
|
|
|
{
|
|
|
|
folderId = [[[context request] uri] collectionid];
|
|
|
|
}
|
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
itemId = [[(id)[theDocumentElement getElementsByTagName: @"ItemId"] lastObject] textValue];
|
2015-02-26 23:53:58 +01:00
|
|
|
|
|
|
|
// if itemId is not there try to get it from URL
|
|
|
|
if (!itemId)
|
|
|
|
{
|
|
|
|
itemId = [[[context request] uri] itemid];
|
|
|
|
}
|
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
realCollectionId = [folderId realCollectionIdWithFolderType: &folderType];
|
2014-05-27 20:44:57 +02:00
|
|
|
realCollectionId = [self globallyUniqueIDToIMAPFolderName: realCollectionId type: folderType];
|
2014-01-10 20:12:53 +01:00
|
|
|
|
2014-04-09 18:19:23 +02:00
|
|
|
value = [theDocumentElement getElementsByTagName: @"ReplaceMime"];
|
|
|
|
|
2014-06-11 18:55:51 +02:00
|
|
|
// ReplaceMime IS specified so we must NOT use the server copy
|
2014-04-09 18:19:23 +02:00
|
|
|
// but rather take the data as-is from the client.
|
2014-06-11 18:55:51 +02:00
|
|
|
if ([value count])
|
2014-04-09 18:19:23 +02:00
|
|
|
{
|
|
|
|
[self processSendMail: theDocumentElement
|
|
|
|
inResponse: theResponse];
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
if (folderType == ActiveSyncMailFolder)
|
|
|
|
{
|
|
|
|
SOGoMailAccounts *accountsFolder;
|
|
|
|
SOGoMailFolder *currentFolder;
|
|
|
|
SOGoUserFolder *userFolder;
|
|
|
|
SOGoMailObject *mailObject;
|
|
|
|
id currentCollection;
|
|
|
|
|
|
|
|
NGMimeMessage *messageFromSmartForward, *messageToSend;
|
|
|
|
NGMimeMessageParser *parser;
|
|
|
|
NSData *data;
|
|
|
|
|
2014-02-06 20:05:00 +01:00
|
|
|
NGMimeMessageGenerator *generator;
|
2014-06-11 18:55:51 +02:00
|
|
|
NGMimeBodyPart *bodyPart;
|
2014-02-06 20:05:00 +01:00
|
|
|
NGMutableHashMap *map;
|
|
|
|
NGMimeFileData *fdata;
|
|
|
|
NSException *error;
|
2015-05-14 21:49:53 +02:00
|
|
|
NSArray *attachmentKeys;
|
2015-07-22 16:26:09 +02:00
|
|
|
NSMutableArray *attachments, *references;
|
2014-06-11 18:55:51 +02:00
|
|
|
|
2015-05-14 21:49:53 +02:00
|
|
|
id body, bodyFromSmartForward, htmlPart, textPart;
|
2016-06-23 17:06:44 +02:00
|
|
|
NSString *fullName, *email, *charset, *s, *from;
|
2015-01-12 15:09:06 +01:00
|
|
|
NSDictionary *identity;
|
2014-02-06 20:05:00 +01:00
|
|
|
|
2015-05-14 21:49:53 +02:00
|
|
|
int a;
|
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
userFolder = [[context activeUser] homeFolderInContext: context];
|
|
|
|
accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
|
|
|
|
currentFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO];
|
|
|
|
|
|
|
|
currentCollection = [currentFolder lookupName: [NSString stringWithFormat: @"folder%@", realCollectionId]
|
|
|
|
inContext: context
|
|
|
|
acquire: NO];
|
|
|
|
|
|
|
|
mailObject = [currentCollection lookupName: itemId inContext: context acquire: NO];
|
|
|
|
|
|
|
|
parser = [[NGMimeMessageParser alloc] init];
|
|
|
|
data = [[[[(id)[theDocumentElement getElementsByTagName: @"MIME"] lastObject] textValue] stringByDecodingBase64] dataUsingEncoding: NSUTF8StringEncoding];
|
|
|
|
messageFromSmartForward = [parser parsePartFromData: data];
|
|
|
|
RELEASE(parser);
|
|
|
|
|
|
|
|
// We create a new MIME multipart/mixed message. The first part will be the text part
|
|
|
|
// of our "smart forward" and the second part will be the message/rfc822 part of the
|
|
|
|
// "smart forwarded" message.
|
|
|
|
map = [NGHashMap hashMapWithDictionary: [messageFromSmartForward headers]];
|
|
|
|
[map setObject: @"multipart/mixed" forKey: @"content-type"];
|
|
|
|
|
2016-06-23 17:06:44 +02:00
|
|
|
from = [map objectForKey: @"from"];
|
2015-01-12 15:09:06 +01:00
|
|
|
|
2016-06-23 17:06:44 +02:00
|
|
|
if (![from length] || ![self _isEMailValid: [from pureEMailAddress]] ||
|
|
|
|
[[from pureEMailAddress] isEqualToString: from] ||
|
|
|
|
[[NSString stringWithFormat: @"<%@>", [from pureEMailAddress]] isEqualToString: from])
|
|
|
|
{
|
|
|
|
if ([from length] && [self _isEMailValid: [from pureEMailAddress]])
|
|
|
|
{
|
|
|
|
// We have a valid email address, lets fill in the fullname.
|
|
|
|
email = [from pureEMailAddress];
|
|
|
|
fullName = [self _fullNameForEMail: email];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Fallback to primary identity.
|
|
|
|
identity = [[context activeUser] primaryIdentity];
|
|
|
|
|
|
|
|
fullName = [identity objectForKey: @"fullName"];
|
|
|
|
email = [identity objectForKey: @"email"];
|
|
|
|
}
|
|
|
|
|
|
|
|
if ([fullName length])
|
|
|
|
[map setObject: [NSString stringWithFormat: @"%@ <%@>", fullName, email] forKey: @"from"];
|
|
|
|
else
|
|
|
|
[map setObject: email forKey: @"from"];
|
|
|
|
}
|
2015-01-12 15:09:06 +01:00
|
|
|
|
2015-05-14 21:49:53 +02:00
|
|
|
if ([mailObject messageId])
|
2015-07-22 16:26:09 +02:00
|
|
|
{
|
|
|
|
[map setObject: [mailObject messageId] forKey: @"in-reply-to"];
|
|
|
|
|
2016-03-16 13:55:21 +01:00
|
|
|
references = [[[[[mailObject mailHeaders] objectForKey: @"references"] componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] mutableCopy] autorelease];
|
2015-09-17 22:28:42 +02:00
|
|
|
|
|
|
|
// If there is no References: header, initialize it with In-Reply-To.
|
|
|
|
if ([mailObject inReplyTo] && ![references count])
|
|
|
|
references = [NSMutableArray arrayWithObject: [mailObject inReplyTo]];
|
|
|
|
|
2015-07-22 16:26:09 +02:00
|
|
|
if ([references count] > 0)
|
|
|
|
{
|
|
|
|
// If there are more than ten identifiers listed, we eliminate the second one.
|
|
|
|
if ([references count] >= 10)
|
|
|
|
[references removeObjectAtIndex: 1];
|
|
|
|
|
|
|
|
[references addObject: [mailObject messageId]];
|
|
|
|
|
2016-05-02 15:42:39 +02:00
|
|
|
[map setObject: [references componentsJoinedByString: @" "] forKey: @"references"];
|
2015-07-22 16:26:09 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
[map setObject: [mailObject messageId] forKey: @"references"];
|
|
|
|
}
|
|
|
|
}
|
2015-05-12 16:44:51 +02:00
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
messageToSend = [[[NGMimeMessage alloc] initWithHeader: map] autorelease];
|
|
|
|
body = [[[NGMimeMultipartBody alloc] initWithPart: messageToSend] autorelease];
|
|
|
|
|
2014-06-11 18:55:51 +02:00
|
|
|
// First part - either a text/* or a multipart/*. If it's a multipart,
|
|
|
|
// we take the first part text/* part we see.
|
2014-01-10 20:12:53 +01:00
|
|
|
map = [[[NGMutableHashMap alloc] initWithCapacity: 1] autorelease];
|
2014-06-11 18:55:51 +02:00
|
|
|
bodyFromSmartForward = nil;
|
2015-05-14 21:49:53 +02:00
|
|
|
textPart = nil;
|
|
|
|
htmlPart = nil;
|
|
|
|
|
|
|
|
attachments = [NSMutableArray array];
|
2014-06-11 18:55:51 +02:00
|
|
|
|
|
|
|
if ([[messageFromSmartForward body] isKindOfClass: [NGMimeMultipartBody class]])
|
|
|
|
{
|
2015-05-14 21:49:53 +02:00
|
|
|
NGMimeBodyPart *part, *apart;
|
|
|
|
NSArray *parts, *aparts;
|
|
|
|
int i, j;
|
2014-06-11 18:55:51 +02:00
|
|
|
|
|
|
|
parts = [[messageFromSmartForward body] parts];
|
|
|
|
|
|
|
|
for (i = 0; i < [parts count]; i++)
|
|
|
|
{
|
|
|
|
part = [parts objectAtIndex: i];
|
|
|
|
|
2015-05-14 21:49:53 +02:00
|
|
|
if ([[[part contentType] type] isEqualToString: @"multipart"] && [[[part contentType] subType] isEqualToString: @"alternative"])
|
2014-06-11 18:55:51 +02:00
|
|
|
{
|
2015-05-14 21:49:53 +02:00
|
|
|
aparts = [[part body] parts];
|
|
|
|
for (j = 0; j < [aparts count]; j++)
|
|
|
|
{
|
|
|
|
apart = [aparts objectAtIndex: j];
|
|
|
|
if ([[[apart contentType] type] isEqualToString: @"text"] && [[[apart contentType] subType] isEqualToString: @"html"])
|
2016-05-02 15:42:39 +02:00
|
|
|
htmlPart = apart;
|
2015-05-14 21:49:53 +02:00
|
|
|
if ([[[apart contentType] type] isEqualToString: @"text"] && [[[apart contentType] subType] isEqualToString: @"plain"])
|
2016-05-02 15:42:39 +02:00
|
|
|
textPart = apart;
|
2015-05-14 21:49:53 +02:00
|
|
|
}
|
2014-06-11 18:55:51 +02:00
|
|
|
}
|
2015-05-14 21:49:53 +02:00
|
|
|
else
|
|
|
|
{
|
|
|
|
if ([[[part contentType] type] isEqualToString: @"text"] && [[[part contentType] subType] isEqualToString: @"html"])
|
2016-05-02 15:42:39 +02:00
|
|
|
htmlPart = part;
|
2015-05-14 21:49:53 +02:00
|
|
|
else if ([[[part contentType] type] isEqualToString: @"text"] && [[[part contentType] subType] isEqualToString: @"plain"])
|
2016-05-02 15:42:39 +02:00
|
|
|
textPart = part;
|
2015-05-14 21:49:53 +02:00
|
|
|
else
|
2016-05-02 15:42:39 +02:00
|
|
|
[attachments addObject: part];
|
2015-05-14 21:49:53 +02:00
|
|
|
}
|
2014-06-11 18:55:51 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2015-05-14 21:49:53 +02:00
|
|
|
if ([[[messageFromSmartForward contentType] type] isEqualToString: @"text"] && [[[messageFromSmartForward contentType] subType] isEqualToString: @"html"])
|
2016-05-02 15:42:39 +02:00
|
|
|
htmlPart = messageFromSmartForward;
|
2015-05-14 21:49:53 +02:00
|
|
|
else
|
2016-05-02 15:42:39 +02:00
|
|
|
textPart = messageFromSmartForward;
|
2014-06-11 18:55:51 +02:00
|
|
|
}
|
|
|
|
|
2015-05-14 21:49:53 +02:00
|
|
|
htmlComposition = [[ud mailComposeMessageType] isEqualToString: @"html"];
|
|
|
|
|
|
|
|
if (htmlComposition && htmlPart)
|
|
|
|
{
|
|
|
|
bodyFromSmartForward = [htmlPart body];
|
|
|
|
charset = [[htmlPart contentType] valueOfParameter: @"charset"];
|
|
|
|
isHTML = YES;
|
|
|
|
}
|
|
|
|
else if (!htmlComposition && !textPart)
|
|
|
|
{
|
|
|
|
bodyFromSmartForward = [htmlPart body];
|
|
|
|
charset = [[htmlPart contentType] valueOfParameter: @"charset"];
|
|
|
|
isHTML = YES;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
bodyFromSmartForward = [textPart body];
|
|
|
|
charset = [[textPart contentType] valueOfParameter: @"charset"];
|
|
|
|
}
|
|
|
|
|
|
|
|
// We make sure everything is encoded in UTF-8.
|
|
|
|
if ([bodyFromSmartForward isKindOfClass: [NSData class]])
|
|
|
|
{
|
|
|
|
if (![charset length])
|
|
|
|
charset = @"utf-8";
|
|
|
|
|
|
|
|
s = [NSString stringWithData: bodyFromSmartForward usingEncodingNamed: charset];
|
|
|
|
|
|
|
|
// We fallback to ISO-8859-1 string encoding. We avoid #3103.
|
|
|
|
if (!s)
|
|
|
|
s = [[[NSString alloc] initWithData: bodyFromSmartForward encoding: NSISOLatin1StringEncoding] autorelease];
|
|
|
|
|
|
|
|
bodyFromSmartForward = s;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (htmlComposition && !isHTML)
|
|
|
|
{
|
|
|
|
[map setObject: @"text/html; charset=utf-8" forKey: @"content-type"];
|
|
|
|
bodyFromSmartForward = [[bodyFromSmartForward stringByEscapingHTMLString] stringByConvertingCRLNToHTML];
|
|
|
|
}
|
|
|
|
else if (!htmlComposition && isHTML)
|
|
|
|
{
|
|
|
|
[map setObject: @"text/plain; charset=utf-8" forKey: @"content-type"];
|
|
|
|
bodyFromSmartForward = [bodyFromSmartForward htmlToText];
|
|
|
|
}
|
|
|
|
else if (htmlComposition && isHTML)
|
|
|
|
{
|
|
|
|
[map setObject: @"text/html; charset=utf-8" forKey: @"content-type"];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
[map setObject: @"text/plain; charset=utf-8" forKey: @"content-type"];
|
|
|
|
}
|
2014-01-10 20:12:53 +01:00
|
|
|
|
|
|
|
bodyPart = [[[NGMimeBodyPart alloc] initWithHeader:map] autorelease];
|
2014-02-06 20:05:00 +01:00
|
|
|
|
2015-05-14 21:49:53 +02:00
|
|
|
if (isSmartForward && [[ud mailMessageForwarding] isEqualToString: @"attached"])
|
|
|
|
[bodyPart setBody: [bodyFromSmartForward dataUsingEncoding: NSUTF8StringEncoding]];
|
|
|
|
else
|
|
|
|
[bodyPart setBody: [[NSString stringWithFormat: @"%@%@", bodyFromSmartForward, [mailObject contentForEditing]] dataUsingEncoding: NSUTF8StringEncoding]];
|
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
[body addBodyPart: bodyPart];
|
2015-05-14 21:49:53 +02:00
|
|
|
|
|
|
|
// Add attachments
|
|
|
|
for (a = 0; a < [attachments count]; a++)
|
|
|
|
{
|
|
|
|
[body addBodyPart: [attachments objectAtIndex: a]];
|
|
|
|
}
|
|
|
|
|
|
|
|
// For a forward decide whether do it inline or as an attachment.
|
|
|
|
if (isSmartForward)
|
|
|
|
{
|
|
|
|
if ([[ud mailMessageForwarding] isEqualToString: @"attached"])
|
|
|
|
{
|
|
|
|
map = [[[NGMutableHashMap alloc] initWithCapacity: 1] autorelease];
|
|
|
|
[map setObject: @"message/rfc822" forKey: @"content-type"];
|
|
|
|
[map setObject: @"8bit" forKey: @"content-transfer-encoding"];
|
|
|
|
[map addObject: [NSString stringWithFormat: @"attachment; filename=\"%@\"", [mailObject filenameForForward]] forKey: @"content-disposition"];
|
|
|
|
bodyPart = [[[NGMimeBodyPart alloc] initWithHeader: map] autorelease];
|
|
|
|
|
|
|
|
data = [mailObject content];
|
|
|
|
fdata = [[NGMimeFileData alloc] initWithBytes: [data bytes] length: [data length]];
|
|
|
|
|
|
|
|
[bodyPart setBody: fdata];
|
|
|
|
RELEASE(fdata);
|
|
|
|
[body addBodyPart: bodyPart];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
attachmentKeys = [mailObject fetchFileAttachmentKeys];
|
|
|
|
if ([attachmentKeys count])
|
|
|
|
{
|
|
|
|
id currentAttachment;
|
|
|
|
NGHashMap *response;
|
|
|
|
NSData *bodydata;
|
|
|
|
NSArray *paths;
|
|
|
|
|
|
|
|
paths = [attachmentKeys keysWithFormat: @"BODY[%{path}]"];
|
|
|
|
response = [[mailObject fetchParts: paths] objectForKey: @"RawResponse"];
|
|
|
|
|
|
|
|
for (a = 0; a < [attachmentKeys count]; a++)
|
2016-05-02 15:42:39 +02:00
|
|
|
{
|
|
|
|
currentAttachment = [attachmentKeys objectAtIndex: a];
|
|
|
|
bodydata = [[[response objectForKey: @"fetch"] objectForKey: [NSString stringWithFormat: @"body[%@]", [currentAttachment objectForKey: @"path"]]] valueForKey: @"data"];
|
|
|
|
|
|
|
|
map = [[[NGMutableHashMap alloc] initWithCapacity: 1] autorelease];
|
|
|
|
[map setObject: [currentAttachment objectForKey: @"mimetype"] forKey: @"content-type"];
|
|
|
|
[map setObject: [currentAttachment objectForKey: @"encoding"] forKey: @"content-transfer-encoding"];
|
|
|
|
[map addObject: [NSString stringWithFormat: @"attachment; filename=\"%@\"", [currentAttachment objectForKey: @"filename"]] forKey: @"content-disposition"];
|
|
|
|
bodyPart = [[[NGMimeBodyPart alloc] initWithHeader: map] autorelease];
|
|
|
|
|
|
|
|
fdata = [[NGMimeFileData alloc] initWithBytes:[bodydata bytes] length:[bodydata length]];
|
|
|
|
[bodyPart setBody: fdata];
|
|
|
|
RELEASE(fdata);
|
|
|
|
[body addBodyPart: bodyPart];
|
|
|
|
}
|
2015-05-14 21:49:53 +02:00
|
|
|
}
|
|
|
|
}
|
2016-05-02 15:42:39 +02:00
|
|
|
} // if (isSmartForward)
|
2015-05-14 21:49:53 +02:00
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
[messageToSend setBody: body];
|
|
|
|
|
|
|
|
generator = [[[NGMimeMessageGenerator alloc] init] autorelease];
|
|
|
|
data = [generator generateMimeFromPart: messageToSend];
|
|
|
|
|
2014-02-06 20:05:00 +01:00
|
|
|
error = [self _sendMail: data
|
|
|
|
recipients: [messageFromSmartForward allRecipients]
|
|
|
|
saveInSentItems: ([(id)[theDocumentElement getElementsByTagName: @"SaveInSentItems"] count] ? YES : NO)];
|
2014-01-10 20:12:53 +01:00
|
|
|
|
|
|
|
if (error)
|
|
|
|
{
|
|
|
|
[theResponse setStatus: 500];
|
|
|
|
[theResponse appendContentString: @"FATAL ERROR occured during SmartForward"];
|
|
|
|
}
|
2016-05-02 15:42:39 +02:00
|
|
|
else if (!isSmartForward)
|
|
|
|
{
|
|
|
|
[mailObject addFlags: @"Answered"];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
[mailObject addFlags: @"$Forwarded"];
|
|
|
|
}
|
2014-01-10 20:12:53 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// FIXME
|
|
|
|
[theResponse setStatus: 500];
|
|
|
|
[theResponse appendContentString: @"SmartForward not-implemented on non-mail folders."];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-05-14 21:49:53 +02:00
|
|
|
//
|
|
|
|
// <?xml version="1.0"?>
|
|
|
|
// <!DOCTYPE ActiveSync PUBLIC "-//MICROSOFT//DTD ActiveSync//EN" "http://www.microsoft.com/">
|
|
|
|
// <SmartForward xmlns="ComposeMail:">
|
|
|
|
// <ClientId>C9FF94FE-EA40-473A-B3E2-AAEE94F753A4</ClientId>
|
|
|
|
// <SaveInSentItems/>
|
|
|
|
// <ReplaceMime/>
|
|
|
|
// <Source>
|
|
|
|
// <FolderId>mail/INBOX</FolderId>
|
|
|
|
// <ItemId>82</ItemId>
|
|
|
|
// </Source>
|
|
|
|
// <MIME>... the data ...</MIME>
|
|
|
|
// </SmartForward>
|
|
|
|
//
|
|
|
|
- (void) processSmartForward: (id <DOMElement>) theDocumentElement
|
|
|
|
inResponse: (WOResponse *) theResponse
|
|
|
|
{
|
|
|
|
[self _processSmartCommand: theDocumentElement
|
|
|
|
inResponse: theResponse
|
|
|
|
isSmartForward: YES];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
//
|
|
|
|
// <?xml version="1.0"?>
|
|
|
|
// <!DOCTYPE ActiveSync PUBLIC "-//MICROSOFT//DTD ActiveSync//EN" "http://www.microsoft.com/">
|
|
|
|
// <SmartReply xmlns="ComposeMail:">
|
|
|
|
// <ClientId>DD40B5DC-4BDF-4A6A-9D8B-4B02BE5342CD</ClientId>
|
|
|
|
// <SaveInSentItems/>
|
|
|
|
// <ReplaceMime/> -- http://msdn.microsoft.com/en-us/library/gg663506(v=exchg.80).aspx
|
|
|
|
// <Source>
|
|
|
|
// <FolderId>mail/INBOX</FolderId>
|
|
|
|
// <ItemId>82</ItemId>
|
|
|
|
// </Source>
|
|
|
|
// <MIME>... the data ...</MIME>
|
|
|
|
// </SmartReply>
|
|
|
|
//
|
|
|
|
- (void) processSmartReply: (id <DOMElement>) theDocumentElement
|
|
|
|
inResponse: (WOResponse *) theResponse
|
|
|
|
{
|
2015-05-14 21:49:53 +02:00
|
|
|
[self _processSmartCommand: theDocumentElement
|
|
|
|
inResponse: theResponse
|
|
|
|
isSmartForward: NO];
|
2014-01-10 20:12:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
- (NSException *) dispatchRequest: (id) theRequest
|
|
|
|
inResponse: (id) theResponse
|
|
|
|
context: (id) theContext
|
|
|
|
{
|
|
|
|
id <DOMElement> documentElement;
|
2014-12-04 17:27:10 +01:00
|
|
|
NSAutoreleasePool *pool;
|
2014-01-10 20:12:53 +01:00
|
|
|
id builder, dom;
|
|
|
|
SEL aSelector;
|
2015-06-18 18:19:02 +02:00
|
|
|
id activeUser;
|
2014-01-10 20:12:53 +01:00
|
|
|
|
|
|
|
NSString *cmdName, *deviceId;
|
|
|
|
NSData *d;
|
|
|
|
|
2014-12-04 17:27:10 +01:00
|
|
|
pool = [[NSAutoreleasePool alloc] init];
|
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
ASSIGN(context, theContext);
|
2015-05-14 21:26:18 +02:00
|
|
|
|
2015-06-18 18:19:02 +02:00
|
|
|
activeUser = [context activeUser];
|
|
|
|
if (![activeUser canAccessModule: @"ActiveSync"])
|
|
|
|
{
|
2015-07-22 15:25:29 +02:00
|
|
|
[(WOResponse *)theResponse setStatus: 403];
|
2015-06-18 18:19:02 +02:00
|
|
|
[self logWithFormat: @"EAS - Forbidden access for user %@", [activeUser loginInDomain]];
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
2014-03-19 16:31:54 +01:00
|
|
|
// Get the device ID, device type and "stash" them
|
2014-01-10 20:12:53 +01:00
|
|
|
deviceId = [[theRequest uri] deviceId];
|
|
|
|
[context setObject: deviceId forKey: @"DeviceId"];
|
2014-03-19 16:31:54 +01:00
|
|
|
[context setObject: [[theRequest uri] deviceType] forKey: @"DeviceType"];
|
2014-06-09 15:25:06 +02:00
|
|
|
[context setObject: [[theRequest uri] attachmentName] forKey: @"AttachmentName"];
|
|
|
|
|
2016-06-23 17:20:40 +02:00
|
|
|
// Save ASProtocolVersion to context
|
|
|
|
if ([[context request] headerForKey: @"MS-ASProtocolVersion"])
|
|
|
|
[context setObject: [[context request] headerForKey: @"MS-ASProtocolVersion"] forKey: @"ASProtocolVersion"];
|
|
|
|
else
|
|
|
|
[context setObject: [[theRequest uri] protocolVersion] forKey: @"ASProtocolVersion"];
|
|
|
|
|
2014-03-21 18:55:59 +01:00
|
|
|
cmdName = [[theRequest uri] command];
|
|
|
|
|
2014-05-15 21:03:24 +02:00
|
|
|
// We make sure our cache table exists
|
|
|
|
[self ensureFolderTableExists];
|
|
|
|
|
2014-03-21 18:55:59 +01:00
|
|
|
//
|
|
|
|
// If the MS-ASProtocolVersion header is set to "12.1", the body of the SendMail request is
|
|
|
|
// is a "message/rfc822" payload - otherwise, it's a WBXML blob.
|
|
|
|
//
|
2015-02-26 23:53:58 +01:00
|
|
|
if (([cmdName caseInsensitiveCompare: @"SendMail"] == NSOrderedSame ||
|
|
|
|
[cmdName caseInsensitiveCompare: @"SmartReply"] == NSOrderedSame ||
|
|
|
|
[cmdName caseInsensitiveCompare: @"SmartForward"] == NSOrderedSame) &&
|
2014-03-21 18:55:59 +01:00
|
|
|
[[theRequest headerForKey: @"content-type"] caseInsensitiveCompare: @"message/rfc822"] == NSOrderedSame)
|
|
|
|
{
|
|
|
|
NSString *s, *xml;
|
|
|
|
|
|
|
|
if ([[theRequest contentAsString] rangeOfString: @"Date: "
|
|
|
|
options: NSCaseInsensitiveSearch].location == NSNotFound)
|
|
|
|
{
|
|
|
|
NSString *value;
|
2015-10-15 21:54:25 +02:00
|
|
|
#if GNUSTEP_BASE_MINOR_VERSION < 21
|
|
|
|
value = [[NSDate date] descriptionWithCalendarFormat: @"%a, %d %b %Y %H:%M:%S %z"
|
|
|
|
timeZone: [NSTimeZone timeZoneWithName: @"GMT"]
|
|
|
|
locale: nil];
|
|
|
|
#else
|
2015-10-14 15:57:56 +02:00
|
|
|
value = [[NSDate date] descriptionWithCalendarFormat: @"%a, %d %b %Y %H:%M:%S %z"
|
|
|
|
timeZone: [NSTimeZone timeZoneWithName: @"GMT"]
|
2015-11-09 20:30:58 +01:00
|
|
|
locale: [NSDictionary dictionaryWithObjectsAndKeys:
|
|
|
|
[NSArray arrayWithObjects: @"Jan", @"Feb", @"Mar", @"Apr",
|
|
|
|
@"May", @"Jun", @"Jul", @"Aug",
|
|
|
|
@"Sep", @"Oct", @"Nov", @"Dec", nil],
|
|
|
|
@"NSShortMonthNameArray",
|
|
|
|
[NSArray arrayWithObjects: @"Sun", @"Mon", @"Tue", @"Wed", @"Thu",
|
|
|
|
@"Fri", @"Sat", nil],
|
|
|
|
@"NSShortWeekDayNameArray",
|
|
|
|
nil]];
|
|
|
|
|
2015-10-15 21:54:25 +02:00
|
|
|
#endif
|
2015-11-25 21:08:24 +01:00
|
|
|
s = [NSString stringWithFormat: @"Date: %@\r\n%@", value, [theRequest contentAsString]];
|
2014-03-21 18:55:59 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
s = [theRequest contentAsString];
|
|
|
|
}
|
|
|
|
|
2015-02-26 23:53:58 +01:00
|
|
|
xml = [NSString stringWithFormat: @"<?xml version=\"1.0\"?><!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\"><%@ xmlns=\"ComposeMail:\"><SaveInSentItems/><MIME>%@</MIME></%@>", cmdName, [s stringByEncodingBase64], cmdName];
|
|
|
|
|
2015-02-26 23:46:34 +01:00
|
|
|
|
2014-03-21 18:55:59 +01:00
|
|
|
|
|
|
|
d = [xml dataUsingEncoding: NSASCIIStringEncoding];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
d = [[theRequest content] wbxml2xml];
|
|
|
|
}
|
2014-12-04 17:27:10 +01:00
|
|
|
|
2014-02-04 17:19:33 +01:00
|
|
|
documentElement = nil;
|
2014-01-10 20:12:53 +01:00
|
|
|
|
|
|
|
if (!d)
|
|
|
|
{
|
2014-01-13 16:19:00 +01:00
|
|
|
// We check if it's a Ping command with no body.
|
2014-03-21 18:55:59 +01:00
|
|
|
// See http://msdn.microsoft.com/en-us/library/ee200913(v=exchg.80).aspx for details
|
2014-12-22 14:36:55 +01:00
|
|
|
if ([cmdName caseInsensitiveCompare: @"Ping"] != NSOrderedSame && [cmdName caseInsensitiveCompare: @"GetAttachment"] != NSOrderedSame && [cmdName caseInsensitiveCompare: @"Sync"] != NSOrderedSame)
|
2014-12-22 22:12:26 +01:00
|
|
|
{
|
|
|
|
RELEASE(context);
|
|
|
|
RELEASE(pool);
|
|
|
|
return [NSException exceptionWithHTTPStatus: 500];
|
|
|
|
}
|
2014-01-10 20:12:53 +01:00
|
|
|
}
|
|
|
|
|
2014-01-13 16:19:00 +01:00
|
|
|
if (d)
|
|
|
|
{
|
2015-05-14 21:26:18 +02:00
|
|
|
if (debugOn)
|
|
|
|
[self logWithFormat: @"EAS - request for device %@: %@", [context objectForKey: @"DeviceId"], [[[NSString alloc] initWithData: d encoding: NSUTF8StringEncoding] autorelease]];
|
|
|
|
|
2014-01-13 16:19:00 +01:00
|
|
|
builder = [[[NSClassFromString(@"DOMSaxBuilder") alloc] init] autorelease];
|
|
|
|
dom = [builder buildFromData: d];
|
|
|
|
documentElement = [dom documentElement];
|
|
|
|
|
|
|
|
// See 2.2.2 Commands - http://msdn.microsoft.com/en-us/library/ee202197(v=exchg.80).aspx
|
|
|
|
// for all potential commands
|
|
|
|
cmdName = [NSString stringWithFormat: @"process%@:inResponse:", [documentElement tagName]];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2014-11-12 21:05:05 +01:00
|
|
|
// Ping or Sync command with empty body
|
2014-01-13 16:19:00 +01:00
|
|
|
cmdName = [NSString stringWithFormat: @"process%@:inResponse:", cmdName];
|
|
|
|
}
|
2015-01-12 20:38:55 +01:00
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
aSelector = NSSelectorFromString(cmdName);
|
|
|
|
|
2015-01-12 20:38:55 +01:00
|
|
|
// The -processItemOperations: method will generate a multipart response when Content-Type is application/vnd.ms-sync.multipart
|
2015-03-30 15:42:32 +02:00
|
|
|
if (([cmdName rangeOfString: @"ItemOperations" options: NSCaseInsensitiveSearch].location != NSNotFound) &&
|
|
|
|
([[theRequest headerForKey: @"MS-ASAcceptMultiPart"] isEqualToString:@"T"] || [[theRequest uri] acceptsMultiPart]))
|
2015-01-12 20:38:55 +01:00
|
|
|
[theResponse setHeader: @"application/vnd.ms-sync.multipart" forKey: @"Content-Type"];
|
|
|
|
else
|
|
|
|
[theResponse setHeader: @"application/vnd.ms-sync.wbxml" forKey: @"Content-Type"];
|
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
[self performSelector: aSelector withObject: documentElement withObject: theResponse];
|
|
|
|
|
2014-02-11 02:16:43 +01:00
|
|
|
[theResponse setHeader: @"14.1" forKey: @"MS-Server-ActiveSync"];
|
2014-03-19 00:06:41 +01:00
|
|
|
[theResponse setHeader: @"Sync,SendMail,SmartForward,SmartReply,GetAttachment,GetHierarchy,CreateCollection,DeleteCollection,MoveCollection,FolderSync,FolderCreate,FolderDelete,FolderUpdate,MoveItems,GetItemEstimate,MeetingResponse,Search,Settings,Ping,ItemOperations,ResolveRecipients,ValidateCert" forKey: @"MS-ASProtocolCommands"];
|
2015-01-28 21:16:21 +01:00
|
|
|
[theResponse setHeader: @"2.5,12.0,12.1,14.0,14.1" forKey: @"MS-ASProtocolVersions"];
|
2014-01-10 20:12:53 +01:00
|
|
|
|
2016-01-25 16:25:54 +01:00
|
|
|
if (debugOn && [[theResponse headerForKey: @"Content-Type"] isEqualToString:@"application/vnd.ms-sync.wbxml"] && [[theResponse content] length])
|
2015-05-14 21:26:18 +02:00
|
|
|
[self logWithFormat: @"EAS - response for device %@: %@", [context objectForKey: @"DeviceId"], [[[NSString alloc] initWithData: [[theResponse content] wbxml2xml] encoding: NSUTF8StringEncoding] autorelease]];
|
|
|
|
|
2014-12-04 17:27:10 +01:00
|
|
|
RELEASE(context);
|
|
|
|
RELEASE(pool);
|
2015-01-12 20:38:55 +01:00
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
2014-05-15 21:03:24 +02:00
|
|
|
- (NSURL *) folderTableURL
|
|
|
|
{
|
2014-12-16 16:35:32 +01:00
|
|
|
NSMutableString *ocFSTableName;
|
2014-05-15 21:03:24 +02:00
|
|
|
NSMutableArray *parts;
|
2014-12-16 16:35:32 +01:00
|
|
|
NSString *urlString;
|
2014-05-15 21:03:24 +02:00
|
|
|
SOGoUser *user;
|
|
|
|
|
|
|
|
if (!folderTableURL)
|
|
|
|
{
|
|
|
|
user = [context activeUser];
|
2014-05-22 20:39:58 +02:00
|
|
|
|
|
|
|
if (![user loginInDomain])
|
|
|
|
return nil;
|
|
|
|
|
2014-05-15 21:03:24 +02:00
|
|
|
urlString = [[user domainDefaults] folderInfoURL];
|
2015-11-05 17:11:18 +01:00
|
|
|
parts = [[urlString componentsSeparatedByString: @"/"] mutableCopy];
|
2014-05-15 21:03:24 +02:00
|
|
|
[parts autorelease];
|
|
|
|
if ([parts count] == 5)
|
|
|
|
{
|
|
|
|
/* If "OCSFolderInfoURL" is properly configured, we must have 5
|
2014-06-10 14:45:35 +02:00
|
|
|
parts in this url. We strip the '-' character in case we have
|
|
|
|
this in the domain part - like foo@bar-zot.com */
|
2014-12-16 16:35:32 +01:00
|
|
|
ocFSTableName = [NSMutableString stringWithFormat: @"sogo_cache_folder_%@",
|
2015-04-14 13:08:21 +02:00
|
|
|
[[user login] asCSSIdentifier]];
|
2014-12-16 16:35:32 +01:00
|
|
|
[ocFSTableName replaceOccurrencesOfString: @"-"
|
|
|
|
withString: @"_"
|
|
|
|
options: 0
|
|
|
|
range: NSMakeRange(0, [ocFSTableName length])];
|
2014-05-15 21:03:24 +02:00
|
|
|
[parts replaceObjectAtIndex: 4 withObject: ocFSTableName];
|
|
|
|
folderTableURL
|
|
|
|
= [NSURL URLWithString: [parts componentsJoinedByString: @"/"]];
|
|
|
|
[folderTableURL retain];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
[NSException raise: @"MAPIStoreIOException"
|
|
|
|
format: @"'OCSFolderInfoURL' is not set"];
|
|
|
|
}
|
|
|
|
|
|
|
|
return folderTableURL;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void) ensureFolderTableExists
|
|
|
|
{
|
|
|
|
GCSChannelManager *cm;
|
|
|
|
EOAdaptorChannel *channel;
|
|
|
|
NSString *tableName, *query;
|
|
|
|
GCSSpecialQueries *queries;
|
|
|
|
|
|
|
|
[self folderTableURL];
|
|
|
|
|
|
|
|
cm = [GCSChannelManager defaultChannelManager];
|
|
|
|
channel = [cm acquireOpenChannelForURL: folderTableURL];
|
|
|
|
|
|
|
|
/* FIXME: make use of [EOChannelAdaptor describeTableNames] instead */
|
|
|
|
tableName = [[folderTableURL path] lastPathComponent];
|
2014-05-22 20:39:58 +02:00
|
|
|
if (tableName &&
|
|
|
|
[channel evaluateExpressionX:
|
|
|
|
[NSString stringWithFormat: @"SELECT count(*) FROM %@",
|
|
|
|
tableName]])
|
2014-05-15 21:03:24 +02:00
|
|
|
{
|
|
|
|
queries = [channel specialQueries];
|
|
|
|
query = [queries createSOGoCacheGCSFolderTableWithName: tableName];
|
|
|
|
if ([channel evaluateExpressionX: query])
|
|
|
|
[NSException raise: @"MAPIStoreIOException"
|
|
|
|
format: @"could not create special table '%@'", tableName];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
[channel cancelFetch];
|
|
|
|
|
|
|
|
|
|
|
|
[cm releaseChannel: channel];
|
|
|
|
}
|
|
|
|
|
2016-04-07 20:22:17 +02:00
|
|
|
- (BOOL) easShouldTerminate
|
|
|
|
{
|
|
|
|
return easShouldTerminate;
|
|
|
|
}
|
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
@end
|