Merge pull request #331 from leecher1337/maint

Merge compatible EAS changes from 5.x branch to 2.x
This commit is contained in:
WoodySlum (Le Programmeur)
2022-11-08 08:25:07 +01:00
committed by GitHub
13 changed files with 3393 additions and 122 deletions

View File

@@ -34,6 +34,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <Foundation/NSString.h>
static NSArray *asElementArray = nil;
static NSArray *considerAsSameArray = nil;
@implementation NGDOMElement (ActiveSync)
@@ -101,7 +102,44 @@ static NSArray *asElementArray = nil;
int i, count;
if (!asElementArray)
asElementArray = [[NSArray alloc] initWithObjects: @"Attendee", @"Category", @"Exception", nil];
asElementArray = [[NSArray alloc] initWithObjects: @"Attendee", @"Category", @"Exception", @"Add", @"Delete", nil];
// FIXME
/* if the client sends
<attachments>
<add>
...
</add>
<add>
...
</add>
</attachments>
the result is a NSarray i.e. {add1; add2; ...}
if the client sends
<attachments>
<add>
...
</add>
<add>
...
</add>
<delete>
...
</delete>
<delete>
...
</delete>
</attachments>
the result is a NSDictionary ie. Add = { }; Delete = { }; the dictionary would contain only one entry for add
*/
if (!considerAsSameArray)
considerAsSameArray = [[NSArray alloc] initWithObjects: @"Add", @"Delete", nil];
data = [NSMutableDictionary dictionary];
@@ -151,7 +189,7 @@ static NSArray *asElementArray = nil;
if (!innerTag)
innerTag = [innerElement tagName];
if ([innerTag isEqualToString: [innerElement tagName]])
if ([innerTag isEqualToString: [innerElement tagName]] || [considerAsSameArray containsObject: innerTag])
{
if ([(id)innerElement isTextNode])
[innerElements addObject: [(NGDOMElement *)innerElement textValue]];

View File

@@ -103,7 +103,19 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if ((o = [n flattenedValueAtIndex: 1 forKey: @""]))
[s appendFormat: @"<FirstName xmlns=\"Contacts:\">%@</FirstName>", [o activeSyncRepresentationInContext: context]];
if ((o = [n flattenedValueAtIndex: 2 forKey: @""]))
[s appendFormat: @"<MiddleName xmlns=\"Contacts:\">%@</MiddleName>", [o activeSyncRepresentationInContext: context]];
if ((o = [n flattenedValueAtIndex: 3 forKey: @""]))
[s appendFormat: @"<Title xmlns=\"Contacts:\">%@</Title>", [o activeSyncRepresentationInContext: context]];
if ((o = [n flattenedValueAtIndex: 4 forKey: @""]))
[s appendFormat: @"<Suffix xmlns=\"Contacts:\">%@</Suffix>", [o activeSyncRepresentationInContext: context]];
if ((o = [self fn]))
[s appendFormat: @"<FileAs xmlns=\"Contacts:\">%@</FileAs>", [o activeSyncRepresentationInContext: context]];
if ((o = [self workCompany]))
[s appendFormat: @"<CompanyName xmlns=\"Contacts:\">%@</CompanyName>", [o activeSyncRepresentationInContext: context]];
@@ -306,8 +318,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{
CardElement *element;
NSMutableArray *addressLines, *other_addresses;
id o;
id o, l, m, f, p, s;
l = m = f = p = s = nil;
// Contact's note
if ((o = [[theValues objectForKey: @"Body"] objectForKey: @"Data"]))
[self setNote: o];
@@ -533,10 +547,23 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if ((o = [theValues objectForKey: @"FileAs"]) || ![self _isGhosted: @"FileAs" inContext: context])
[self setFn: [theValues objectForKey: @"FileAs"]];
[self setNWithFamily: [theValues objectForKey: @"LastName"]
given: [theValues objectForKey: @"FirstName"]
additional: nil prefixes: nil suffixes: nil];
if ((o = [theValues objectForKey: @"LastName"]) || ![self _isGhosted: @"LastName" inContext: context])
l = o ? o : @"";
if ((o = [theValues objectForKey: @"FirstName"]) || ![self _isGhosted: @"FirstName" inContext: context])
f = o ? o : @"";
if ((o = [theValues objectForKey: @"MiddleName"]) || ![self _isGhosted: @"MiddleName" inContext: context])
m = o ? o : @"";
if ((o = [theValues objectForKey: @"Title"]) || ![self _isGhosted: @"Title" inContext: context])
p = o ? o : @"";
if ((o = [theValues objectForKey: @"Suffix"]) || ![self _isGhosted: @"Suffix" inContext: context])
s = o ? o : @"";
[self setNWithFamily: l given: f additional: m prefixes: p suffixes: s];
// IM information
if ((o = [theValues objectForKey: @"IMAddress"]) || ![self _isGhosted: @"IMAddress" inContext: context])
[[self uniqueChildWithTag: @"x-aim"]

View File

@@ -117,7 +117,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if (ret != WBXML_OK)
{
[self errorWithFormat: @"wbxml2xmlFromContent: failed: %s\n", wbxml_errors_string(ret)];
[self errorWithFormat: @"wbxml2xml failed: %s\n", wbxml_errors_string(ret)];
[self _dumpToFile];
return nil;
}
@@ -150,7 +150,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if (ret != WBXML_OK)
{
[self logWithFormat: @"xml2wbxmlFromContent: failed: %s\n", wbxml_errors_string(ret)];
[self logWithFormat: @"xml2wbxml failed: %s\n", wbxml_errors_string(ret)];
[self _dumpToFile];
return nil;
}
@@ -165,7 +165,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if (ret != WBXML_OK)
{
[self errorWithFormat: @"xml2wbxmlFromContent: failed: %s\n", wbxml_errors_string(ret)];
[self errorWithFormat: @"xml2wbxml failed: %s\n", wbxml_errors_string(ret)];
[self _dumpToFile];
free(wbxml);
wbxml_conv_xml2wbxml_destroy(conv);

View File

@@ -237,7 +237,7 @@ static NSArray *easCommandParameters = nil;
// parameter_code 7 == Options
// http://msdn.microsoft.com/en-us/library/ee237789(v=exchg.80).aspx
if (parameter_code == 7)
if (parameter_code == 7 && ! [parameterValue isEqualToString: @"\000"] )
[components addObject: [NSString stringWithFormat: @"%@=%@", [easCommandParameters objectAtIndex: parameter_code],
([parameterValue isEqualToString: @"\001"]) ? @"SaveInSent" : @"AcceptMultiPart"]];
else

View File

@@ -189,7 +189,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
processIdentifierInCache = [[self globalMetadataForDevice] objectForKey: key];
// Don't update the cache if another request is processing the same collection.
// I case of a merged folder we have to check personal folder's lock.
// In case of a merged folder we have to check personal folder's lock.
a = [key componentsSeparatedByString: @"/"];
pkey = [NSString stringWithFormat: @"%@/personal", [a objectAtIndex:0]];
@@ -346,8 +346,34 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
anAddition = [additions objectAtIndex: i];
is_new = YES;
clientId = [[(id)[anAddition getElementsByTagName: @"ClientId"] lastObject] textValue];
/*
FIXME
<Add>
<ClientId>338</ClientId>
<ApplicationData>
<To xmlns="Email:">&lt;foot@bar.com&gt;</To>
<Cc xmlns="Email:"/>
<Subject xmlns="Email:">test</Subject>
<Reply-To xmlns="Email:">foo@bar.com</Reply-To>
<Importance xmlns="Email:">1</Importance>
<Read xmlns="Email:">1</Read>
<Attachments xmlns="AirSyncBase:">
<Add>
<ClientId>152-ab557915-8451-49a7-a9c6-a9ac153021ad</ClientId>
-> lastObject returns the ClientId in Attachments element -> try with objectAtIndex: 0 -> is this correct?
*/
//clientId = [[(id)[anAddition getElementsByTagName: @"ClientId"] lastObject] textValue];
clientId = [[(id)[anAddition getElementsByTagName: @"ClientId"] objectAtIndex: 0] textValue];
allValues = [NSMutableDictionary dictionaryWithDictionary: [[(id)[anAddition getElementsByTagName: @"ApplicationData"] lastObject] applicationData]];
// FIXME: ignore the <Add> elements of Attachemnts - above (id)[theDocumentElement getElementsByTagName: @"Add"]; return any <Add> elements instead of only the direct childs of the <commands> element ..
if (![allValues count])
continue;
switch (theFolderType)
{
@@ -402,23 +428,39 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
case ActiveSyncMailFolder:
default:
{
// Support SMS to Exchange eMail sync.
// Support Draft Mail/SMS to Exchange eMail sync.
NSString *serverId;
NSMutableString *s;
NSDictionary *result;
NSNumber *modseq;
serverId = nil;
s = [NSMutableString string];
sogoObject = [SOGoMailObject objectWithName: @"Mail" inContainer: theCollection];
serverId = [sogoObject storeMail: allValues inContext: context];
serverId = [sogoObject storeMail: allValues inBuffer: s inContext: context];
if (serverId)
{
sogoObject = [theCollection lookupName: serverId inContext: context acquire: 0];
[sogoObject takeActiveSyncValues: allValues inContext: context];
// Everything is fine, lets generate our response
// serverId = clientId - There is no furhter processing after adding the SMS to the inbox.
[theBuffer appendString: @"<Add>"];
[theBuffer appendFormat: @"<ClientId>%@</ClientId>", clientId];
[theBuffer appendFormat: @"<ServerId>%@</ServerId>", serverId];
[theBuffer appendFormat: @"<Status>%d</Status>", 1];
[theBuffer appendString: s];
[theBuffer appendString: @"</Add>"];
folderMetadata = [self _folderMetadataForKey: [self _getNameInCache: theCollection withType: theFolderType]];
syncCache = [folderMetadata objectForKey: @"SyncCache"];
[syncCache setObject: @"0" forKey: serverId];
result = [sogoObject fetchParts: [NSArray arrayWithObject: @"MODSEQ"]];
modseq = [[[result objectForKey: @"RawResponse"] objectForKey: @"fetch"] objectForKey: @"modseq"];
[syncCache setObject: [modseq stringValue] forKey: serverId];
[self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: theCollection withType: theFolderType]];
continue;
@@ -688,6 +730,24 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
NSDictionary *result;
NSNumber *modseq;
// Process an update to a Draft Mail.
if ([allChanges objectForKey: @"Body"])
{
NSString *serverId;
NSMutableString *s;
serverId = nil;
s = [NSMutableString string];
serverId = [sogoObject storeMail: allChanges inBuffer: s inContext: context];
if (serverId)
{
// we delete the original email - next sync will update the client with the new mail
[sogoObject delete];
sogoObject = [theCollection lookupName: serverId inContext: context acquire: 0];
}
}
[sogoObject takeActiveSyncValues: allChanges inContext: context];
result = [sogoObject fetchParts: [NSArray arrayWithObject: @"MODSEQ"]];
@@ -702,7 +762,13 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[theBuffer appendString: @"<Change>"];
[theBuffer appendFormat: @"<ServerId>%@</ServerId>", origServerId];
[theBuffer appendFormat: @"<Status>%d</Status>", 1];
// A body element is sent only for draft mails - status 8 will delete the mail on the client - the next sync update fetch the new mail
if ([allChanges objectForKey: @"Body"] && theFolderType == ActiveSyncMailFolder)
[theBuffer appendFormat: @"<Status>%d</Status>", 8];
else
[theBuffer appendFormat: @"<Status>%d</Status>", 1];
[theBuffer appendString: @"</Change>"];
}
}
@@ -824,7 +890,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// FIXME: handle errors here
if (deletesAsMoves && theFolderType == ActiveSyncMailFolder)
{
[(SOGoMailFolder *)[sogoObject container] deleteUIDs: [NSArray arrayWithObjects: serverId, nil] useTrashFolder: &useTrash inContext: context];
[(SOGoMailFolder *)[sogoObject container] deleteUIDs: [NSArray arrayWithObjects: serverId, nil]
useTrashFolder: &useTrash
inContext: context];
}
else if (theFolderType == ActiveSyncEventFolder || theFolderType == ActiveSyncTaskFolder || theFolderType == ActiveSyncContactFolder)
{
@@ -1401,7 +1469,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
}
}
allMessages = [theCollection syncTokenFieldsWithProperties: nil matchingSyncToken: theSyncKey fromDate: theFilterType initialLoad: initialLoadInProgress];
allMessages = [theCollection syncTokenFieldsWithProperties: nil
matchingSyncToken: theSyncKey
fromDate: theFilterType
initialLoad: initialLoadInProgress];
max = [allMessages count];
allCacheObjects = [NSMutableArray array];
@@ -2525,15 +2596,41 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//[output appendFormat: @"<Status>%d</Status>", 14];
}
[output appendString: @"<Collections>"];
s = nil;
// We enter our loop detection change
for (i = 0; i < (heartbeatInterval/internalInterval); i++)
{
// Terminate the process if we need to
if ([self easShouldTerminate])
break;
// We first check of any of the collections we want to sync are already
// in an other sync process. If that's the case, we do not do anything
// and we return immediately. So we'll let the other sync process terminate
for (j = 0; j < [allCollections count]; j++)
{
aCollection = [allCollections objectAtIndex: j];
globalMetadata = [self globalMetadataForDevice];
key = [NSString stringWithFormat: @"SyncRequest+%@", [[[(id)[aCollection getElementsByTagName: @"CollectionId"] lastObject] textValue] stringByUnescapingURL]];
if (!([[globalMetadata objectForKey: key] isEqual: processIdentifier]))
{
if (debugOn)
[self logWithFormat: @"EAS - Discard response %@", [self globalMetadataForDevice]];
[output appendString: @"<Status>13</Status>"];
[output appendString: @"</Sync>"];
d = [[output dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
[theResponse setContent: d];
RELEASE(output);
return;
}
}
// We're good to go to sync the collections
s = [NSMutableString string];
for (j = 0; j < [allCollections count]; j++)
@@ -2545,21 +2642,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
changeDetected: &changeDetected
maxSyncResponseSize: maxSyncResponseSize];
// Don't return a response if another Sync is waiting.
globalMetadata = [self globalMetadataForDevice];
key = [NSString stringWithFormat: @"SyncRequest+%@", [[[(id)[aCollection getElementsByTagName: @"CollectionId"] lastObject] textValue] stringByUnescapingURL]];
if (!([[globalMetadata objectForKey: key] isEqual: processIdentifier]))
{
if (debugOn)
[self logWithFormat: @"EAS - Discard response %@", [self globalMetadataForDevice]];
[theResponse setStatus: 503];
RELEASE(output);
return;
}
if ((maxSyncResponseSize > 0 && [s length] >= maxSyncResponseSize))
break;
}
@@ -2616,6 +2698,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
if (changeDetected || [[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"2.5"] || [[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"12.0"])
{
[output appendString: @"<Collections>"];
// We always return the last generated response.
// If we only return <Sync><Collections/></Sync>,
// iOS powered devices will simply crash.

View File

@@ -290,6 +290,39 @@ void handle_eas_terminate(int signum)
return theIdToTranslate;
}
//
//
//
- (SOGoAppointmentObject *) _eventObjectWithUID: (NSString *) uid
{
SOGoAppointmentFolder *folder;
SOGoAppointmentObject *eventObject;
NSArray *folders;
NSEnumerator *e;
NSString *cname;
eventObject = nil;
folders = [[[context activeUser] calendarsFolderInContext: context] subFolders];
e = [folders objectEnumerator];
while (eventObject == nil && (folder = [e nextObject]))
{
cname = [folder resourceNameForEventUID: uid];
if (cname)
{
eventObject = [folder lookupName: cname inContext: context
acquire: NO];
if ([eventObject isKindOfClass: [NSException class]])
eventObject = nil;
}
}
if (eventObject)
return eventObject;
else
return [NSException exceptionWithHTTPStatus:404 /* Not Found */];
}
//
//
//
@@ -313,7 +346,9 @@ void handle_eas_terminate(int signum)
case ActiveSyncEventFolder:
case ActiveSyncTaskFolder:
{
collection = [[[[context activeUser] homeFolderInContext: context] lookupName: @"Calendar" inContext: context acquire: NO] lookupName: theCollectionId inContext: context acquire: NO];
collection = [[[context activeUser] homeFolderInContext: context] lookupName: @"Calendar" inContext: context acquire: NO];
if (![collection isKindOfClass: [NSException class]])
collection = [collection lookupName: theCollectionId inContext: context acquire: NO];
if (!collection || ([collection isKindOfClass: [NSException class]]))
collection = nil;
}
@@ -643,6 +678,12 @@ void handle_eas_terminate(int signum)
SOGoMailFolder *folderToUpdate;
accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
if ([accountsFolder isKindOfClass: [NSException class]])
{
[theResponse setStatus: 403];
[self logWithFormat: @"Mail - Forbidden access for user %@", [[context activeUser] loginInDomain]];
return;
}
currentFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO];
folderToUpdate = [currentFolder lookupName: [NSString stringWithFormat: @"folder%@", serverId]
@@ -688,6 +729,12 @@ void handle_eas_terminate(int signum)
NSString *nameInCache;
appointmentFolders = [userFolder privateCalendars: @"Calendar" inContext: context];
if ([appointmentFolders isKindOfClass: [NSException class]])
{
[theResponse setStatus: 403];
[self logWithFormat: @"Calendar - Forbidden access for user %@", [[context activeUser] loginInDomain]];
return;
}
folderToUpdate = [appointmentFolders lookupName: [NSString stringWithFormat: @"%@", serverId]
inContext: context
@@ -798,7 +845,7 @@ void handle_eas_terminate(int signum)
- (void) processFolderSync: (id <DOMElement>) theDocumentElement
inResponse: (WOResponse *) theResponse
{
NSString *key, *cKey, *nkey, *name, *serverId, *parentId, *nameInCache, *personalFolderName, *syncKey, *folderType, *operation;
NSString *key, *cKey, *nkey, *name, *serverId, *parentId, *nameInCache, *personalFolderName, *syncKey, *folderType, *operation, *parent;
NSMutableDictionary *cachedGUIDs, *metadata;
NSMutableArray *folders, *processedFolders, *allFoldersMetadata;
NSDictionary *folderMetadata, *imapGUIDs;
@@ -814,17 +861,18 @@ void handle_eas_terminate(int signum)
int status, command_count, i, type, fi, count;
BOOL first_sync;
metadata = [self globalMetadataForDevice];
syncKey = [[(id)[theDocumentElement getElementsByTagName: @"SyncKey"] lastObject] textValue];
s = [NSMutableString string];
personalFolderName = [[[context activeUser] personalCalendarFolderInContext: context] nameInContainer];
first_sync = NO;
status = 1;
command_count = 0;
personalFolderName = nil;
commands = [NSMutableString string];
processedFolders = [NSMutableArray array];
s = [NSMutableString string];
metadata = [self globalMetadataForDevice];
syncKey = [[(id)[theDocumentElement getElementsByTagName: @"SyncKey"] lastObject] textValue];
if ([[context activeUser] canAccessModule: @"Calendar"])
personalFolderName = [[[context activeUser] personalCalendarFolderInContext: context] nameInContainer];
[s appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
[s appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
@@ -1099,14 +1147,16 @@ void handle_eas_terminate(int signum)
[[o properties] removeObjectForKey: @"CleanoutDate"];
[o save];
command_count++;
}
}
folders = [NSMutableArray array];
// We get the list of subscribed calendars
folders = [[[[[context activeUser] homeFolderInContext: context] lookupName: @"Calendar" inContext: context acquire: NO] subFolders] mutableCopy];
[folders autorelease];
if ([personalFolderName length])
[folders addObjectsFromArray: [[[[context activeUser] homeFolderInContext: context] lookupName: @"Calendar" inContext: context acquire: NO] subFolders]];
// We get the list of subscribed address books
[folders addObjectsFromArray: [[[[context activeUser] homeFolderInContext: context] lookupName: @"Contacts" inContext: context acquire: NO] subFolders]];
@@ -1166,9 +1216,19 @@ void handle_eas_terminate(int signum)
{
if ([[folders objectAtIndex:fi] isKindOfClass: [SOGoAppointmentFolder class]])
{
type = ([[[folders objectAtIndex:fi] nameInContainer] isEqualToString: personalFolderName] ? 8 : 13);
if ([[[folders objectAtIndex:fi] nameInContainer] isEqualToString: personalFolderName])
{
type = 8;
parent = @"0";
}
else
{
type = 13;
parent = [NSString stringWithFormat: @"vevent/%@",personalFolderName];
}
[commands appendFormat: @"<%@><ServerId>%@</ServerId><ParentId>%@</ParentId><DisplayName>%@</DisplayName><Type>%d</Type></%@>", operation,
[name stringByEscapingURL], @"0", [[[folders objectAtIndex:fi] displayName] activeSyncRepresentationInContext: context], type, operation];
[name stringByEscapingURL], [parent stringByEscapingURL], [[[folders objectAtIndex:fi] displayName] activeSyncRepresentationInContext: context], type, operation];
command_count++;
@@ -1176,7 +1236,17 @@ void handle_eas_terminate(int signum)
[o save];
name = [NSString stringWithFormat: @"vtodo/%@", [[folders objectAtIndex:fi] nameInContainer]];
type = ([[[folders objectAtIndex:fi] nameInContainer] isEqualToString: personalFolderName] ? 7 : 15);
if ([[[folders objectAtIndex:fi] nameInContainer] isEqualToString: personalFolderName])
{
type = 7;
parent = @"0";
}
else
{
type = 15;
parent = [NSString stringWithFormat: @"vtodo/%@",personalFolderName];
}
// We always sync the "Default Tasks folder" (7). For "User-created Tasks folder" (15), we check if we include it in
// the sync process by checking if "Show tasks" is enabled. If not, we skip the folder entirely.
@@ -1184,7 +1254,7 @@ void handle_eas_terminate(int signum)
(type == 15 && [[folders objectAtIndex: fi] showCalendarTasks]))
{
[commands appendFormat: @"<%@><ServerId>%@</ServerId><ParentId>%@</ParentId><DisplayName>%@</DisplayName><Type>%d</Type></%@>", operation,
[name stringByEscapingURL], @"0", [[[folders objectAtIndex:fi] displayName] activeSyncRepresentationInContext: context], type, operation];
[name stringByEscapingURL], [parent stringByEscapingURL], [[[folders objectAtIndex:fi] displayName] activeSyncRepresentationInContext: context], type, operation];
command_count++;
@@ -1219,9 +1289,19 @@ void handle_eas_terminate(int signum)
}
else if ([[folders objectAtIndex:fi] isKindOfClass: [SOGoContactGCSFolder class]])
{
type = ([[[folders objectAtIndex:fi] nameInContainer] isEqualToString: personalFolderName] ? 9 : 14);
if ([[[folders objectAtIndex:fi] nameInContainer] isEqualToString: personalFolderName])
{
type = 9;
parent = @"0";
}
else
{
type = 14;
parent = [NSString stringWithFormat: @"vcard/%@",personalFolderName];
}
[commands appendFormat: @"<%@><ServerId>%@</ServerId><ParentId>%@</ParentId><DisplayName>%@</DisplayName><Type>%d</Type></%@>", operation,
[name stringByEscapingURL], @"0", [[[folders objectAtIndex:fi] displayName] activeSyncRepresentationInContext: context], type, operation];
[name stringByEscapingURL], [parent stringByEscapingURL], [[[folders objectAtIndex:fi] displayName] activeSyncRepresentationInContext: context], type, operation];
command_count++;
@@ -1848,6 +1928,9 @@ void handle_eas_terminate(int signum)
inContext: context
acquire: NO];
if ([appointmentObject isKindOfClass: [NSException class]])
appointmentObject = [self _eventObjectWithUID:[event uid]];
// Create the appointment if it is not added to calendar yet
if ([appointmentObject isKindOfClass: [NSException class]])
{
@@ -2416,6 +2499,11 @@ void handle_eas_terminate(int signum)
realCollectionId = [collectionId realCollectionIdWithFolderType: &folderType];
realCollectionId = [self globallyUniqueIDToIMAPFolderName: realCollectionId type: folderType];
// We avoid loading the cache metadata if we can't get the real connection. This can happen
// for example if the IMAP server is down. We just skip the folder for now.
if (!realCollectionId)
continue;
if (folderType == ActiveSyncMailFolder)
folderMetadata = [self _folderMetadataForKey: [NSString stringWithFormat: @"folder%@", [[collectionId stringByUnescapingURL] substringFromIndex:5]]];
else
@@ -2857,7 +2945,7 @@ void handle_eas_terminate(int signum)
{
currentFolder = [systemSources objectForKey: [allKeys objectAtIndex: i]];
allContacts = [currentFolder lookupContactsWithFilter: query
onCriteria: @"name_or_address"
onCriteria: nil
sortBy: @"c_cn"
ordering: NSOrderedAscending
inDomain: [[context activeUser] domain]];
@@ -3026,7 +3114,7 @@ void handle_eas_terminate(int signum)
- (void) processSearchMailbox: (id <DOMElement>) theDocumentElement
inResponse: (WOResponse *) theResponse
{
NSString *folderId, *realCollectionId, *itemId;
NSString *folderId, *realCollectionId, *itemId, *bodyPreferenceType, *mimeSupport;
NSMutableArray *folderIdentifiers;
SOGoMailAccounts *accountsFolder;
SOGoMailAccount *accountFolder;
@@ -3050,12 +3138,20 @@ void handle_eas_terminate(int signum)
return;
}
bodyPreferenceType = [[(id)[[(id)[theDocumentElement getElementsByTagName: @"BodyPreference"] lastObject] getElementsByTagName: @"Type"] lastObject] textValue];
[context setObject: bodyPreferenceType forKey: @"BodyPreferenceType"];
mimeSupport = [[(id)[theDocumentElement getElementsByTagName: @"MIMESupport"] lastObject] textValue];
[context setObject: mimeSupport forKey: @"MIMESupport"];
[context setObject: @"8" forKey: @"MIMETruncation"];
// FIXME: support more than one CollectionId tag + DeepTraversal
folderId = [[(id)[[(id)[theDocumentElement getElementsByTagName: @"Query"] lastObject] getElementsByTagName: @"CollectionId"] lastObject] textValue];
folderIdentifiers = [NSMutableArray array];
// Android 6 will send search requests with no collection ID - so we search in all folders.
if (!folderId)
// Outlook Mobile App sends search requests with CollectionId=0 - We treat this as an all-folder-search.
if (!folderId || [folderId isEqualToString: @"0"])
{
NSArray *foldersInCache;
SOGoCacheGCSObject *o;
@@ -3123,7 +3219,7 @@ void handle_eas_terminate(int signum)
[s appendFormat: @"<LongId>%@+%@</LongId>", folderId, itemId];
[s appendFormat: @"<CollectionId xmlns=\"AirSyncBase:\">%@</CollectionId>", folderId];
[s appendString: @"<Properties>"];
[s appendFormat: [mailObject activeSyncRepresentationInContext: context]];
[s appendString: [mailObject activeSyncRepresentationInContext: context]];
[s appendString: @"</Properties>"];
[s appendFormat: @"</Result>"];
}
@@ -3179,7 +3275,7 @@ void handle_eas_terminate(int signum)
NSException *error;
NSString *from;
authenticator = [SOGoDAVAuthenticator sharedSOGoDAVAuthenticator];
authenticator = [[context activeUser] authenticatorInContext: context];
dd = [[context activeUser] domainDefaults];
// We generate the Sender
@@ -3573,6 +3669,48 @@ void handle_eas_terminate(int signum)
}
}
if ([(id)[[(id)[theDocumentElement getElementsByTagName: @"UserInformation"] lastObject] getElementsByTagName: @"Get"] lastObject])
{
NSArray *identities;
int i;
identities = [[context activeUser] allIdentities];
[s appendString: @"<UserInformation>"];
[s appendString: @"<Get>"];
if ([[context objectForKey: @"ASProtocolVersion"] floatValue] >= 14.1)
{
[s appendString: @"<Accounts>"];
[s appendString: @"<Account>"];
[s appendFormat: @"<UserDisplayName>%@</UserDisplayName>", [[[identities objectAtIndex: 0] objectForKey: @"fullName"] activeSyncRepresentationInContext: context] ];
}
[s appendString: @"<EmailAddresses>"];
if ([[context objectForKey: @"ASProtocolVersion"] floatValue] >= 14.1)
[s appendFormat: @"<PrimarySmtpAddress>%@</PrimarySmtpAddress>", [[[identities objectAtIndex: 0] objectForKey: @"email"] activeSyncRepresentationInContext: context] ];
else
[s appendFormat: @"<SmtpAddress>%@</SmtpAddress>", [[[identities objectAtIndex: 0] objectForKey: @"email"] activeSyncRepresentationInContext: context] ];
if ([identities count] > 1)
{
for (i = 1; i < [identities count]; i++)
[s appendFormat: @"<SmtpAddress>%@</SmtpAddress>", [[[identities objectAtIndex: i] objectForKey: @"email"] activeSyncRepresentationInContext: context] ];
}
[s appendString: @"</EmailAddresses>"];
if ([[context objectForKey: @"ASProtocolVersion"] floatValue] >= 14.1)
{
[s appendString: @"</Account>"];
[s appendString: @"</Accounts>"];
}
[s appendString: @"</Get>"];
[s appendString: @"</UserInformation>"];
}
[s appendString: @"</Settings>"];
d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
@@ -3891,6 +4029,8 @@ void handle_eas_terminate(int signum)
[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"];
if ([[currentAttachment objectForKey: @"bodyId"] length])
[map setObject: [currentAttachment objectForKey: @"bodyId"] forKey: @"content-id"];
bodyPart = [[[NGMimeBodyPart alloc] initWithHeader: map] autorelease];
fdata = [[NGMimeFileData alloc] initWithBytes:[bodydata bytes] length:[bodydata length]];

View File

@@ -43,6 +43,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- (void) takeActiveSyncValues: (NSDictionary *) theValues
inContext: (WOContext *) context;
- (NSString *) storeMail: (NSDictionary *) theValues
inBuffer: (NSMutableString *) theBuffer
inContext: (WOContext *) _context;
@end

View File

@@ -36,6 +36,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <Foundation/NSString.h>
#import <Foundation/NSCharacterSet.h>
#import <Foundation/NSTimeZone.h>
#import <Foundation/NSValue.h>
#import <NGCards/iCalCalendar.h>
#import <NGCards/iCalDateTime.h>
@@ -60,6 +61,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <NGMime/NGMimeFileData.h>
#import <NGMime/NGMimeMultipartBody.h>
#import <NGMime/NGMimeType.h>
#import <NGMime/NGMimeHeaderFields.h>
#import <NGMail/NGMimeMessageParser.h>
#import <NGMail/NGMimeMessage.h>
#import <NGMail/NGMimeMessageGenerator.h>
@@ -68,6 +70,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <SOGo/SOGoUserDefaults.h>
#import <SOGo/SOGoUserFolder.h>
#import <SOGo/NSArray+Utilities.h>
#include "iCalTimeZone+ActiveSync.h"
#include "NSData+ActiveSync.h"
@@ -256,6 +259,68 @@ struct GlobalObjectId {
return rc;
}
//
//
//
- (NSString *) _personalNameFrom: (NSArray *) enveloppeAddresses
{
NGImap4EnvelopeAddress *address;
NSString *email, *rc, *name;
NSMutableArray *addresses;
int i, max;
rc = nil;
max = [enveloppeAddresses count];
if (max > 0)
{
addresses = [NSMutableArray array];
for (i = 0; i < max; i++)
{
address = [enveloppeAddresses objectAtIndex: i];
name = [address personalName];
email = [NSString stringWithFormat: @"%@", (name ? name : [address baseEMail])];
if (email)
[addresses addObject: email];
}
rc = [addresses componentsJoinedByString: @";"];
}
return rc;
}
// Format replyTo
//
// If there are multiple e-mail addresses, they are separated by a semi-colon.
- (NSString *) _replyToAddressesFrom: (NSArray *) enveloppeAddresses
{
NSMutableArray *addresses;
NSString *rc;
NGImap4EnvelopeAddress *address;
NSString *email;
int count, max;
rc = nil;
max = [enveloppeAddresses count];
if (max > 0)
{
addresses = [NSMutableArray array];
for (count = 0; count < max; count++)
{
address = [enveloppeAddresses objectAtIndex: count];
email = [NSString stringWithFormat: @"%@", [address email]];
[addresses addObject: email];
}
rc = [addresses componentsJoinedByString: @"; "];
}
return rc;
}
//
//
//
@@ -392,7 +457,7 @@ struct GlobalObjectId {
body = [thePart body];
if ([body isKindOfClass: [NGMimeMultipartBody class]])
if ([body isKindOfClass: [NGMimeMultipartBody class]] || [body isKindOfClass: [NGMimeMessage class]])
{
[self _sanitizedMIMEPart: body
performed: b];
@@ -444,6 +509,13 @@ struct GlobalObjectId {
RELEASE(fdata);
*b = YES;
}
else if ([body isKindOfClass: [NSData class]])
{
[thePart setHeader: @"base64" forKey: @"content-transfer-encoding"];
[thePart setBody: [body dataByEncodingBase64]];
[thePart setHeader: [NSString stringWithFormat:@"%d", (int)[[thePart body] length]]
forKey: @"content-length"];
}
}
}
@@ -727,7 +799,19 @@ struct GlobalObjectId {
break;
}
return [theContent substringToIndex: i];
// If we didn't find a "space" character search again for &# to avoid
// truncating the content in the middle of a XML entity
if (i < 0)
{
for (i = len-2 ; i >= 0; i--)
{
if ([theContent characterAtIndex: i] == '&' && [theContent characterAtIndex: i+1] == '#')
break;
}
}
if (i >= 0)
return [theContent substringToIndex: i];
}
*wasTruncated = 0;
@@ -836,7 +920,13 @@ struct GlobalObjectId {
// If there are multiple e-mail addresses, they are separated by commas."
value = [self _emailAddressesFrom: [[self envelope] to]];
if (value)
[s appendFormat: @"<To xmlns=\"Email:\">%@</To>", [value activeSyncRepresentationInContext: context]];
{
[s appendFormat: @"<To xmlns=\"Email:\">%@</To>", [value activeSyncRepresentationInContext: context]];
// DisplayTo - If there are multiple display names, they are separated by semi-colons.
value = [self _personalNameFrom: [[self envelope] to]];
if (value)
[s appendFormat: @"<DisplayTo xmlns=\"Email:\">%@</DisplayTo>", [value activeSyncRepresentationInContext: context]];
}
// From
value = [self _emailAddressesFrom: [[self envelope] from]];
@@ -855,10 +945,9 @@ struct GlobalObjectId {
value = [self date];
if (value)
[s appendFormat: @"<DateReceived xmlns=\"Email:\">%@</DateReceived>", [value activeSyncRepresentationInContext: context]];
else
[s appendFormat: @"<DateReceived xmlns=\"Email:\">%@</DateReceived>", [[NSDate date] activeSyncRepresentationInContext: context]];
// DisplayTo
[s appendFormat: @"<DisplayTo xmlns=\"Email:\">%@</DisplayTo>", [[context activeUser] login]];
// Cc - same syntax as the To field
value = [self _emailAddressesFrom: [[self envelope] cc]];
if (value)
@@ -959,7 +1048,7 @@ struct GlobalObjectId {
if ([event startDate])
[s appendFormat: @"<StartTime xmlns=\"Email:\">%@</StartTime>", [[event startDate] activeSyncRepresentationInContext: context]];
if ([event timeStampAsDate])
if ([event timeStampAsDate] && [[event timeStampAsDate] dayOfMonth] > 0 && [[event timeStampAsDate] monthOfYear] > 0)
[s appendFormat: @"<DTStamp xmlns=\"Email:\">%@</DTStamp>", [[event timeStampAsDate] activeSyncRepresentationInContext: context]];
else if ([event created])
[s appendFormat: @"<DTStamp xmlns=\"Email:\">%@</DTStamp>", [[event created] activeSyncRepresentationInContext: context]];
@@ -1040,10 +1129,11 @@ struct GlobalObjectId {
[s appendFormat: @"<ContentClass xmlns=\"Email:\">%@</ContentClass>", @"urn:content-classes:message"];
}
// Reply-To - FIXME
//NSArray *replyTo = [[message objectForKey: @"envelope"] replyTo];
//if ([replyTo count])
// [s appendFormat: @"<Reply-To xmlns=\"Email:\">%@</Reply-To>", [addressFormatter stringForArray: replyTo]];
// ReplyTo
value = [self _replyToAddressesFrom: [self replyToEnvelopeAddresses]];
if (value)
[s appendFormat: @"<Reply-To xmlns=\"Email:\">%@</Reply-To>", [value activeSyncRepresentationInContext: context]];
// InternetCPID - 65001 == UTF-8, we use this all the time for now.
// - 20127 == US-ASCII
@@ -1249,7 +1339,7 @@ struct GlobalObjectId {
{
if ([[value objectForKey: @"bodyId"] length])
{
[s appendFormat: @"<ContentId>%@</ContentId>", [[value objectForKey: @"bodyId"] activeSyncRepresentationInContext: context]];
[s appendFormat: @"<ContentId>%@</ContentId>", [[[value objectForKey: @"bodyId"] stringByTrimmingCharactersInSet: [NSCharacterSet characterSetWithCharactersInString: @"<>"]] activeSyncRepresentationInContext: context]];
[s appendFormat: @"<IsInline>%d</IsInline>", 1];
}
@@ -1439,18 +1529,22 @@ struct GlobalObjectId {
}
- (NSString *) storeMail: (NSDictionary *) theValues
inBuffer: (NSMutableString *) theBuffer
inContext: (WOContext *) _context
{
NSString *dateReceived, *folder, *s;
NSString *dateReceived, *folder, *s, *serverId, *messageId;
NGMimeMessageGenerator *generator;
NGMimeMessage *bounceMessage;
NGMimeMessage *draftMessage;
NGMimeBodyPart *bodyPart;
NGMimeMultipartBody *body;
NSDictionary *identity;
NGMutableHashMap *map;
NGImap4Client *client;
NSData *message_data;
NSArray *attachmentKeys;
id o, result;
int bodyType;
id o, a, result, attachments;
int bodyType, i;
identity = [[context activeUser] primaryIdentity];
message_data = nil;
@@ -1470,11 +1564,21 @@ struct GlobalObjectId {
map = [[[NGMutableHashMap alloc] initWithCapacity: 1] autorelease];
[map setObject: [NSString stringWithFormat: @"%@ <%@>", [identity objectForKey: @"fullName"], [identity objectForKey: @"email"]] forKey: @"from"];
if ((o = [theValues objectForKey: @"To"]))
[map setObject: o forKey: @"to"];
if ((o = [theValues objectForKey: @"Cc"]))
[map setObject: o forKey: @"cc"];
if ((o = [theValues objectForKey: @"Bcc"]))
[map setObject: o forKey: @"bcc"];
if ((o = [theValues objectForKey: @"Reply-To"]))
[map setObject: o forKey: @"Reply-To"];
if ((o = [theValues objectForKey: @"Subject"]))
[map setObject: o forKey: @"subject"];
[map setObject: o forKey: @"subject"];
o = [[theValues objectForKey: @"DateReceived"] calendarDate];
@@ -1500,17 +1604,114 @@ struct GlobalObjectId {
#endif
[map setObject: dateReceived forKey: @"date"];
[map setObject: [NSString generateMessageID] forKey: @"message-id"];
[map setObject: (bodyType == 1 ? @"text/plain; charset=utf-8" : @"text/html; charset=utf-8")
forKey: @"content-type"];
[map setObject: @"quoted-printable" forKey: @"content-transfer-encoding"];
bounceMessage = [[[NGMimeMessage alloc] initWithHeader: map] autorelease];
messageId = [NSString generateMessageID];
[map setObject: messageId forKey: @"message-id"];
[bounceMessage setBody: [[NSString stringWithFormat: @"%@", [[theValues objectForKey: @"Body"] objectForKey: @"Data"] ] dataUsingEncoding: NSUTF8StringEncoding]];
attachmentKeys = [self fetchFileAttachmentKeys];
if ((attachments = [theValues objectForKey: @"Attachments"]) || [attachmentKeys count])
{
[map setObject: @"multipart/mixed" forKey: @"content-type"];
draftMessage = [[[NGMimeMessage alloc] initWithHeader: map] autorelease];
body = [[[NGMimeMultipartBody alloc] initWithPart: draftMessage] autorelease];
map = [[[NGMutableHashMap alloc] initWithCapacity: 1] autorelease];
[map setObject: (bodyType == 1 ? @"text/plain; charset=utf-8" : @"text/html; charset=utf-8")
forKey: @"content-type"];
[map setObject: @"quoted-printable" forKey: @"content-transfer-encoding"];
bodyPart = [[[NGMimeBodyPart alloc] initWithHeader:map] autorelease];
[bodyPart setBody: [[NSString stringWithFormat: @"%@", [[theValues objectForKey: @"Body"] objectForKey: @"Data"] ] dataUsingEncoding: NSUTF8StringEncoding]];
[body addBodyPart: bodyPart];
// Add attachments from the original mail - skip deletions
if ([attachmentKeys count])
{
id currentAttachment;
NGHashMap *response;
NSData *bodydata;
NSArray *paths;
NGMimeFileData *fdata;
int i, ii;
BOOL found;
paths = [attachmentKeys keysWithFormat: @"BODY[%{path}]"];
response = [[self fetchParts: paths] objectForKey: @"RawResponse"];
for (i = 0; i < [attachmentKeys count]; i++)
{
currentAttachment = [attachmentKeys objectAtIndex: i];
found = NO;
for (ii = 0; ii < [attachments count]; ii++)
{
// no ClientId means its a deletio
if (![[attachments objectAtIndex: ii] objectForKey: @"ClientId"] &&
[[[[attachments objectAtIndex: ii] objectForKey: @"FileReference"] lastPathComponent] isEqualToString: [currentAttachment objectForKey: @"path"]])
{
found = YES;
break;
}
}
// skip deletions
if (found)
continue;
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];
}
}
// add new attachments
for (i = 0; i < [attachments count]; i++)
{
map = [[[NGMutableHashMap alloc] initWithCapacity: 1] autorelease];
a = [attachments objectAtIndex: i];
// no ClientId means its a deletion
if (![a objectForKey: @"ClientId"])
continue;
[map setObject: @"application/octet-stream" forKey: @"content-type"]; // FIXME ?? can we guess the right content-type
[map setObject: @"base64" forKey: @"content-transfer-encoding"];
[map setObject: [NSString stringWithFormat: @"attachment; filename=\"%@\"", [a objectForKey: @"DisplayName"]] forKey: @"content-disposition"];
bodyPart = [[[NGMimeBodyPart alloc] initWithHeader:map] autorelease];
[bodyPart setBody: [[a objectForKey: @"Content"] stringByDecodingBase64]];
[body addBodyPart: bodyPart];
}
[draftMessage setBody: body];
}
else
{
[map setObject: (bodyType == 1 ? @"text/plain; charset=utf-8" : @"text/html; charset=utf-8")
forKey: @"content-type"];
[map setObject: @"quoted-printable" forKey: @"content-transfer-encoding"];
draftMessage = [[[NGMimeMessage alloc] initWithHeader: map] autorelease];
[draftMessage setBody: [[NSString stringWithFormat: @"%@", [[theValues objectForKey: @"Body"] objectForKey: @"Data"] ] dataUsingEncoding: NSUTF8StringEncoding]];
}
generator = [[[NGMimeMessageGenerator alloc] init] autorelease];
message_data = [generator generateMimeFromPart: bounceMessage];
message_data = [generator generateMimeFromPart: draftMessage];
}
if (message_data)
@@ -1530,8 +1731,32 @@ struct GlobalObjectId {
toFolder: folder
withFlags: [NSArray arrayWithObjects: @"draft", nil]];
serverId = [NSString stringWithFormat: @"%d", [self IMAP4IDFromAppendResult: result]];
[theBuffer appendString: @"<ApplicationData>"];
[theBuffer appendFormat: @"<ConversationId xmlns=\"Email2:\">%@</ConversationId>", [[messageId dataUsingEncoding: NSUTF8StringEncoding] activeSyncRepresentationInContext: context]];
if ([attachments count])
{
[theBuffer appendString: @"<Attachments xmlns=\"AirSyncBase:\">"];
for (i = 0; i < [attachments count]; i++)
{
a = [attachments objectAtIndex: i];
[theBuffer appendString: @"<Attachment>"];
[theBuffer appendFormat: @"<ClientId xmlns=\"AirSyncBase:\">%@</ClientId>", [a objectForKey: @"ClientId"]];
[theBuffer appendFormat: @"<FileReference xmlns=\"AirSyncBase:\">mail/%@/%@/%d</FileReference>", [[[self container] relativeImap4Name] stringByEscapingURL], serverId, i+2];
[theBuffer appendString: @"</Attachment>"];
}
[theBuffer appendString: @"</Attachments>"];
}
[theBuffer appendString: @"</ApplicationData>"];
if ([[result objectForKey: @"result"] boolValue])
return [NSString stringWithFormat: @"%d", [self IMAP4IDFromAppendResult: result]];
return serverId;
}
return nil;

View File

@@ -48,27 +48,23 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- (NSString *) activeSyncRepresentationInContext: (WOContext *) context
{
NSMutableString *s;
NSCalendarDate *nextAlarmDate;
NSInteger delta;
s = [NSMutableString string];
nextAlarmDate = [self nextAlarmDate];
delta = (int)(([[(iCalEvent *)parent startDate] timeIntervalSince1970] - [nextAlarmDate timeIntervalSince1970])/60);
if ([[self action] caseInsensitiveCompare: @"DISPLAY"] == NSOrderedSame)
if ([parent isKindOfClass: [iCalEvent class]])
{
NSCalendarDate *nextAlarmDate;
NSInteger delta;
nextAlarmDate = [self nextAlarmDate];
delta = (int)(([[(iCalEvent *)parent startDate] timeIntervalSince1970] - [nextAlarmDate timeIntervalSince1970])/60);
if ([parent isKindOfClass: [iCalEvent class]])
{
// don't send negative reminder - not supported
if (delta > 0)
[s appendFormat: @"<Reminder xmlns=\"Calendar:\">%d</Reminder>", (int)delta];
}
else
{
[s appendFormat: @"<ReminderTime xmlns=\"Task:\">%@</ReminderTime>", [nextAlarmDate activeSyncRepresentationInContext: context]];
}
// don't send negative reminder - not supported
if (delta > 0)
[s appendFormat: @"<Reminder xmlns=\"Calendar:\">%d</Reminder>", (int)delta];
}
else
{
[s appendFormat: @"<ReminderTime xmlns=\"Task:\">%@</ReminderTime>", [nextAlarmDate activeSyncRepresentationInContext: context]];
}
return s;
@@ -89,7 +85,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
trigger = [iCalTrigger elementWithTag: @"TRIGGER"];
[trigger setValueType: @"DURATION"];
[self setTrigger: trigger];
[self setAction: @"DISPLAY"];
if (![self action])
[self setAction: @"DISPLAY"];
// SOGo web ui only supports 1w but not 2w (custom reminder only supports min/hours/days)
// 1week = -P1W
@@ -118,8 +115,16 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
trigger = [iCalTrigger elementWithTag: @"TRIGGER"];
[trigger setValueType: @"DATE-TIME"];
[trigger setSingleValue: [NSString stringWithFormat: @"%@Z", [o iCalFormattedDateTimeString]] forKey: @""];
if ((o = [theValues objectForKey: @"ReminderSet"]))
{
if ([o intValue] == 0)
[trigger setValue: 0 ofAttribute: @"x-webstatus" to: @"triggered"];
}
[self setTrigger: trigger];
[self setAction: @"DISPLAY"];
if (![self action])
[self setAction: @"DISPLAY"];
}
}

View File

@@ -135,7 +135,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// StartTime -- http://msdn.microsoft.com/en-us/library/ee157132(v=exchg.80).aspx
if ([self startDate])
{
if ([self isAllDay] && !tz)
if ([self isAllDay] && !tz && [[context objectForKey: @"ASProtocolVersion"] floatValue] < 16.0)
[s appendFormat: @"<StartTime xmlns=\"Calendar:\">%@</StartTime>",
[[[self startDate] dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0
@@ -148,7 +148,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// EndTime -- http://msdn.microsoft.com/en-us/library/ee157945(v=exchg.80).aspx
if ([self endDate])
{
if ([self isAllDay] && !tz)
if ([self isAllDay] && !tz && [[context objectForKey: @"ASProtocolVersion"] floatValue] < 16.0)
[s appendFormat: @"<EndTime xmlns=\"Calendar:\">%@</EndTime>",
[[[self endDate] dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0
@@ -194,7 +194,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
attendee = [attendees objectAtIndex: i];
[s appendFormat: @"<Attendee_Email xmlns=\"Calendar:\">%@</Attendee_Email>", [[attendee rfc822Email] activeSyncRepresentationInContext: context]];
[s appendFormat: @"<Attendee_Name xmlns=\"Calendar:\">%@</Attendee_Name>", [[attendee cn] activeSyncRepresentationInContext: context]];
if ([[attendee cn] length])
[s appendFormat: @"<Attendee_Name xmlns=\"Calendar:\">%@</Attendee_Name>", [[attendee cn] activeSyncRepresentationInContext: context]];
else
[s appendFormat: @"<Attendee_Name xmlns=\"Calendar:\">%@</Attendee_Name>", [[attendee rfc822Email] activeSyncRepresentationInContext: context]];
attendee_status = [self _attendeeStatus: attendee];
@@ -328,7 +331,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{
iCalAlarm *alarm;
alarm = [self firstDisplayOrAudioAlarm];
alarm = [self firstSupportedAlarm];
[s appendString: [alarm activeSyncRepresentationInContext: context]];
}
@@ -454,6 +457,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
NSCalendarDate *oldstart;
NSTimeZone *userTimeZone;
iCalTimeZone *tz;
iCalAlarm *alarm;
id o;
int deltasecs;
@@ -589,23 +593,19 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
}
}
//
// If an alarm is deinfed with an action != DISPLAY, we ignore the alarm - don't want to overwrite.
//
if ([self hasAlarms] && [[[[self alarms] objectAtIndex: 0] action] caseInsensitiveCompare: @"DISPLAY"] != NSOrderedSame)
{
// Ignore the alarm for now
}
else if ((o = [theValues objectForKey: @"Reminder"]) && [o length])
if ((o = [theValues objectForKey: @"Reminder"]) && [o length])
{
if ([self hasAlarms])
alarm = [[self firstSupportedAlarm] mutableCopy];
else
alarm = [[iCalAlarm alloc] init];
// NOTE: Outlook sends a 15 min reminder (18 hour for allday) if no reminder is specified
// although no default reminder is defined (File -> Options -> Clendar -> Calendar Options - > Default Reminders)
//
// http://answers.microsoft.com/en-us/office/forum/office_2013_release-outlook/desktop-outlook-calendar-creates-entries-with/9aef72d8-81bb-4a32-a6ab-bf7d216fb811?page=5&tm=1395690285088
//
iCalAlarm *alarm;
alarm = [[iCalAlarm alloc] init];
[alarm takeActiveSyncValues: theValues inContext: context];
[self removeAllAlarms];
@@ -868,6 +868,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// { "Attendee_Email" = "sogo3@example.com"; "Attendee_Name" = "Wolfgang Fritz"; "Attendee_Status" = 5; "Attendee_Type" = 1; }
attendee = [o objectAtIndex: i];
if ([self isOrganizer: [attendee objectForKey: @"Attendee_Email"]])
continue;
person = [iCalPerson elementWithTag: @"attendee"];
[person setCn: [attendee objectForKey: @"Attendee_Name"]];
[person setEmail: [attendee objectForKey: @"Attendee_Email"]];

View File

@@ -83,10 +83,28 @@ struct SYSTEMTIME {
tzData->wDay = ([mask firstOccurrence] == -1) ? 5 : [mask firstOccurrence];
dateValue = [self startDate];
tzData->wHour = [dateValue hourOfDay];
tzData->wMinute = [dateValue minuteOfHour];
tzData->wSecond = [dateValue secondOfMinute];
tzData->wMilliseconds = 0;
if (![dateValue hourOfDay])
{
if ([mask firstDay]-1 < 0)
tzData->wDayOfWeek = 6;
else
tzData->wDayOfWeek = [mask firstDay]-1;
tzData->wHour = 23;
tzData->wMinute = 59;
tzData->wSecond = 59;
tzData->wMilliseconds = 999;
}
else
{
tzData->wDayOfWeek = [mask firstDay];
tzData->wHour = [dateValue hourOfDay];
tzData->wMinute = [dateValue minuteOfHour];
tzData->wSecond = [dateValue secondOfMinute];
tzData->wMilliseconds = 0;
}
}
}

View File

@@ -46,6 +46,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <NGCards/iCalCalendar.h>
#import <NGCards/iCalDateTime.h>
#import <NGCards/iCalTimeZone.h>
#import <NGCards/iCalTrigger.h>
#import <Appointments/iCalEntityObject+SOGo.h>

2729
NEWS Normal file

File diff suppressed because it is too large Load Diff