mirror of
https://github.com/inverse-inc/sogo.git
synced 2026-02-17 07:33:57 +00:00
Merge pull request #331 from leecher1337/maint
Merge compatible EAS changes from 5.x branch to 2.x
This commit is contained in:
@@ -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]];
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:"><foot@bar.com></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.
|
||||
|
||||
@@ -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]];
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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"]];
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user