Merge branch 'master' into feature/addMissingStrings2
Conflicts: UI/AdministrationUI/English.lproj/Localizable.strings UI/PreferencesUI/English.lproj/Localizable.strings UI/Templates/PreferencesUI/UIxFilterEditor.woxpull/101/head
commit
9b0a2c5fae
|
@ -9,6 +9,7 @@
|
|||
*/*/obj/
|
||||
*/obj/
|
||||
.scss-lint-config.yml_
|
||||
ActiveSync/ActiveSync.SOGo
|
||||
Documentation/*.docbook
|
||||
Documentation/*.pdf
|
||||
SoObjects/SOGo/SOGo.framework/
|
||||
|
@ -20,11 +21,8 @@ UI/WebServerResources/bower_components/
|
|||
UI/WebServerResources/css/
|
||||
UI/WebServerResources/css/styles.css
|
||||
UI/WebServerResources/css/styles.css.map
|
||||
UI/WebServerResources/js/Common.js*
|
||||
UI/WebServerResources/js/Contacts.js*
|
||||
UI/WebServerResources/js/Mailer.js*
|
||||
UI/WebServerResources/js/Preferences.js*
|
||||
UI/WebServerResources/js/Scheduler.js*
|
||||
UI/WebServerResources/js/*.js
|
||||
UI/WebServerResources/js/*.js.map
|
||||
UI/WebServerResources/js/vendor/
|
||||
UI/WebServerResources/node_modules/
|
||||
UI/WebServerResources/scss/.sass-cache/
|
||||
|
|
|
@ -1 +1,4 @@
|
|||
# compilation settings
|
||||
ifeq ($(HAS_LIBRARY_ssl),yes)
|
||||
ADDITIONAL_CPPFLAGS += -DHAVE_OPENSSL=1
|
||||
BUNDLE_LIBS += -lcrypto
|
||||
endif
|
||||
|
|
|
@ -101,7 +101,7 @@ static NSArray *asElementArray = nil;
|
|||
int i, count;
|
||||
|
||||
if (!asElementArray)
|
||||
asElementArray = [[NSArray alloc] initWithObjects: @"Attendee", @"Category", nil];
|
||||
asElementArray = [[NSArray alloc] initWithObjects: @"Attendee", @"Category", @"Exception", nil];
|
||||
|
||||
data = [NSMutableDictionary dictionary];
|
||||
|
||||
|
|
|
@ -48,11 +48,42 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
||||
@implementation NGVCard (ActiveSync)
|
||||
|
||||
//
|
||||
// This function is called for each elements which can be ghosted according to specs.
|
||||
// https://msdn.microsoft.com/en-us/library/gg650908%28v=exchg.80%29.aspx
|
||||
//
|
||||
- (BOOL) _isGhosted: (NSString *) element
|
||||
inContext: (WOContext *) context
|
||||
{
|
||||
NSArray *supportedElements;
|
||||
|
||||
supportedElements = [context objectForKey: @"SupportedElements"];
|
||||
|
||||
// If the client does not include a Supported element in the initial Sync command request for
|
||||
// a folder, then all of the elements that can be ghosted are considered not ghosted.
|
||||
if (!supportedElements)
|
||||
return NO;
|
||||
|
||||
// If the client includes an empty Supported element in the initial Sync command request for
|
||||
// a folder, then all elements that can be ghosted are considered ghosted.
|
||||
if (![supportedElements count])
|
||||
return YES;
|
||||
|
||||
// If the client includes a Supported element that contains child elements in the initial
|
||||
// Sync command request for a folder, then each child element of that Supported element is
|
||||
// considered not ghosted. All elements that can be ghosted that are not included as child
|
||||
// elements of the Supported element are considered ghosted.
|
||||
if (!([supportedElements indexOfObject: element] == NSNotFound))
|
||||
return YES;
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSString *) activeSyncRepresentationInContext: (WOContext *) context
|
||||
{
|
||||
NSArray *emails, *addresses, *categories, *elements;
|
||||
CardElement *n, *homeAdr, *workAdr;
|
||||
NSMutableString *s;
|
||||
NSMutableString *s, *a;
|
||||
NSString *url;
|
||||
id o;
|
||||
|
||||
|
@ -146,9 +177,15 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
if ([addresses count])
|
||||
{
|
||||
homeAdr = [addresses objectAtIndex: 0];
|
||||
a = [NSMutableString string];
|
||||
|
||||
if ((o = [homeAdr flattenedValueAtIndex: 2 forKey: @""]))
|
||||
[s appendFormat: @"<HomeStreet xmlns=\"Contacts:\">%@</HomeStreet>", [o activeSyncRepresentationInContext: context]];
|
||||
[a appendString: o];
|
||||
|
||||
if ((o = [homeAdr flattenedValueAtIndex: 1 forKey: @""]) && [o length])
|
||||
[a appendFormat: @"\n%@", o];
|
||||
|
||||
[s appendFormat: @"<HomeStreet xmlns=\"Contacts:\">%@</HomeStreet>", [a activeSyncRepresentationInContext: context]];
|
||||
|
||||
if ((o = [homeAdr flattenedValueAtIndex: 3 forKey: @""]))
|
||||
[s appendFormat: @"<HomeCity xmlns=\"Contacts:\">%@</HomeCity>", [o activeSyncRepresentationInContext: context]];
|
||||
|
@ -171,9 +208,15 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
if ([addresses count])
|
||||
{
|
||||
workAdr = [addresses objectAtIndex: 0];
|
||||
a = [NSMutableString string];
|
||||
|
||||
if ((o = [workAdr flattenedValueAtIndex: 2 forKey: @""]))
|
||||
[s appendFormat: @"<BusinessStreet xmlns=\"Contacts:\">%@</BusinessStreet>", [o activeSyncRepresentationInContext: context]];
|
||||
[a appendString: o];
|
||||
|
||||
if ((o = [workAdr flattenedValueAtIndex: 1 forKey: @""]) && [o length])
|
||||
[a appendFormat: @"\n%@", o];
|
||||
|
||||
[s appendFormat: @"<BusinessStreet xmlns=\"Contacts:\">%@</BusinessStreet>", [a activeSyncRepresentationInContext: context]];
|
||||
|
||||
if ((o = [workAdr flattenedValueAtIndex: 3 forKey: @""]))
|
||||
[s appendFormat: @"<BusinessCity xmlns=\"Contacts:\">%@</BusinessCity>", [o activeSyncRepresentationInContext: context]];
|
||||
|
@ -217,6 +260,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
inContext: (WOContext *) context
|
||||
{
|
||||
CardElement *element;
|
||||
NSMutableArray *addressLines;
|
||||
id o;
|
||||
|
||||
// Contact's note
|
||||
|
@ -226,6 +270,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
// Categories
|
||||
if ((o = [theValues objectForKey: @"Categories"]) && [o length])
|
||||
[self setCategories: o];
|
||||
else
|
||||
[[self children] removeObjectsInArray: [self childrenWithTag: @"Categories"]];
|
||||
|
||||
// Birthday
|
||||
if ((o = [theValues objectForKey: @"Birthday"]))
|
||||
|
@ -233,6 +279,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
o = [o calendarDate];
|
||||
[self setBday: [o descriptionWithCalendarFormat: @"%Y-%m-%d" timeZone: nil locale: nil]];
|
||||
}
|
||||
else if (![self _isGhosted: @"Birthday" inContext: context])
|
||||
{
|
||||
[self setBday: @""];
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Business address information
|
||||
|
@ -244,18 +295,48 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
// BusinessCountry
|
||||
//
|
||||
element = [self elementWithTag: @"adr" ofType: @"work"];
|
||||
|
||||
if ((o = [theValues objectForKey: @"BusinessStreet"]) || ![self _isGhosted: @"BusinessStreet" inContext: context])
|
||||
{
|
||||
addressLines = [NSMutableArray arrayWithArray: [o componentsSeparatedByString: @"\n"]];
|
||||
|
||||
[element setSingleValue: @""
|
||||
atIndex: 1 forKey: @""];
|
||||
[element setSingleValue: [theValues objectForKey: @"BusinessStreet"]
|
||||
[element setSingleValue: [addressLines count] ? [addressLines objectAtIndex: 0] : @""
|
||||
atIndex: 2 forKey: @""];
|
||||
|
||||
// Extended address line. If there are more than 2 address lines we add them to the extended address line.
|
||||
if ([addressLines count] > 1)
|
||||
{
|
||||
[addressLines removeObjectAtIndex: 0];
|
||||
[element setSingleValue: [addressLines componentsJoinedByString: @" "]
|
||||
atIndex: 1 forKey: @""];
|
||||
}
|
||||
}
|
||||
|
||||
if ((o = [theValues objectForKey: @"BusinessCity"]) || ![self _isGhosted: @"BusinessCity" inContext: context])
|
||||
{
|
||||
[element setSingleValue: [theValues objectForKey: @"BusinessCity"]
|
||||
atIndex: 3 forKey: @""];
|
||||
}
|
||||
|
||||
if ((o = [theValues objectForKey: @"BusinessState"]) || ![self _isGhosted: @"BusinessState" inContext: context])
|
||||
{
|
||||
[element setSingleValue: [theValues objectForKey: @"BusinessState"]
|
||||
atIndex: 4 forKey: @""];
|
||||
}
|
||||
|
||||
if ((o = [theValues objectForKey: @"BusinessPostalCode"]) || ![self _isGhosted: @"BusinessPostalCode" inContext: context])
|
||||
{
|
||||
[element setSingleValue: [theValues objectForKey: @"BusinessPostalCode"]
|
||||
atIndex: 5 forKey: @""];
|
||||
}
|
||||
|
||||
if ((o = [theValues objectForKey: @"BusinessCountry"]) || ![self _isGhosted: @"BusinessCountry" inContext: context])
|
||||
{
|
||||
[element setSingleValue: [theValues objectForKey: @"BusinessCountry"]
|
||||
atIndex: 6 forKey: @""];
|
||||
}
|
||||
|
||||
//
|
||||
// Home address information
|
||||
|
@ -267,35 +348,69 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
// HomeCountry
|
||||
//
|
||||
element = [self elementWithTag: @"adr" ofType: @"home"];
|
||||
|
||||
if ((o = [theValues objectForKey: @"HomeStreet"]) || ![self _isGhosted: @"HomeStreet" inContext: context])
|
||||
{
|
||||
addressLines = [NSMutableArray arrayWithArray: [o componentsSeparatedByString: @"\n"]];
|
||||
|
||||
[element setSingleValue: @""
|
||||
atIndex: 1 forKey: @""];
|
||||
[element setSingleValue: [theValues objectForKey: @"HomeStreet"]
|
||||
[element setSingleValue: [addressLines count] ? [addressLines objectAtIndex: 0] : @""
|
||||
atIndex: 2 forKey: @""];
|
||||
|
||||
// Extended address line. If there are more then 2 address lines we add them to the extended address line.
|
||||
if ([addressLines count] > 1)
|
||||
{
|
||||
[addressLines removeObjectAtIndex: 0];
|
||||
[element setSingleValue: [addressLines componentsJoinedByString: @" "]
|
||||
atIndex: 1 forKey: @""];
|
||||
}
|
||||
}
|
||||
|
||||
if ((o = [theValues objectForKey: @"HomeCity"]) || ![self _isGhosted: @"HomeCity" inContext: context])
|
||||
{
|
||||
[element setSingleValue: [theValues objectForKey: @"HomeCity"]
|
||||
atIndex: 3 forKey: @""];
|
||||
}
|
||||
|
||||
if ((o = [theValues objectForKey: @"HomeState"]) || ![self _isGhosted: @"HomeState" inContext: context])
|
||||
{
|
||||
[element setSingleValue: [theValues objectForKey: @"HomeState"]
|
||||
atIndex: 4 forKey: @""];
|
||||
}
|
||||
|
||||
if ((o = [theValues objectForKey: @"HomePostalCode"]) || ![self _isGhosted: @"HomePostalCode" inContext: context])
|
||||
{
|
||||
[element setSingleValue: [theValues objectForKey: @"HomePostalCode"]
|
||||
atIndex: 5 forKey: @""];
|
||||
}
|
||||
|
||||
if ((o = [theValues objectForKey: @"HomeCountry"]) || ![self _isGhosted: @"HomeCountry" inContext: context])
|
||||
{
|
||||
[element setSingleValue: [theValues objectForKey: @"HomeCountry"]
|
||||
atIndex: 6 forKey: @""];
|
||||
}
|
||||
|
||||
// Company's name
|
||||
if ((o = [theValues objectForKey: @"CompanyName"]))
|
||||
[self setOrg: o units: nil];
|
||||
else if (![self _isGhosted: @"CompanyName" inContext: context])
|
||||
[self setOrg: @"" units: nil];
|
||||
|
||||
// Department
|
||||
if ((o = [theValues objectForKey: @"Department"]))
|
||||
[self setOrg: nil units: [NSArray arrayWithObjects:o,nil]];
|
||||
else if (![self _isGhosted: @"Department" inContext: context])
|
||||
[self setOrg: nil units: [NSArray arrayWithObjects:@"",nil]];
|
||||
|
||||
// Email addresses
|
||||
if ((o = [theValues objectForKey: @"Email1Address"]))
|
||||
if ((o = [theValues objectForKey: @"Email1Address"]) || ![self _isGhosted: @"Email1Address" inContext: context])
|
||||
{
|
||||
element = [self elementWithTag: @"email" ofType: @"work"];
|
||||
[element setSingleValue: [o pureEMailAddress] forKey: @""];
|
||||
}
|
||||
|
||||
if ((o = [theValues objectForKey: @"Email2Address"]))
|
||||
if ((o = [theValues objectForKey: @"Email2Address"]) || ![self _isGhosted: @"Email2Address" inContext: context])
|
||||
{
|
||||
element = [self elementWithTag: @"email" ofType: @"home"];
|
||||
[element setSingleValue: [o pureEMailAddress] forKey: @""];
|
||||
|
@ -303,7 +418,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
||||
// SOGo currently only supports 2 email addresses ... but AS clients might send 3
|
||||
// FIXME: revise this when the GUI revamp is done in SOGo
|
||||
if ((o = [theValues objectForKey: @"Email3Address"]))
|
||||
if ((o = [theValues objectForKey: @"Email3Address"]) || ![self _isGhosted: @"Email3Address" inContext: context])
|
||||
{
|
||||
element = [self elementWithTag: @"email" ofType: @"three"];
|
||||
[element setSingleValue: [o pureEMailAddress] forKey: @""];
|
||||
|
@ -313,6 +428,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
// MiddleName
|
||||
// Suffix (II)
|
||||
// Title (Mr.)
|
||||
if ((o = [theValues objectForKey: @"FileAs"]) || ![self _isGhosted: @"FileAs" inContext: context])
|
||||
[self setFn: [theValues objectForKey: @"FileAs"]];
|
||||
|
||||
[self setNWithFamily: [theValues objectForKey: @"LastName"]
|
||||
|
@ -320,6 +436,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
additional: nil prefixes: nil suffixes: nil];
|
||||
|
||||
// IM information
|
||||
if ((o = [theValues objectForKey: @"IMAddress"]) || ![self _isGhosted: @"IMAddress" inContext: context])
|
||||
[[self uniqueChildWithTag: @"x-aim"]
|
||||
setSingleValue: [theValues objectForKey: @"IMAddress"]
|
||||
forKey: @""];
|
||||
|
@ -327,31 +444,46 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
//
|
||||
// Phone numbrrs
|
||||
//
|
||||
if ((o = [theValues objectForKey: @"BusinessPhoneNumber"]) || ![self _isGhosted: @"BusinessPhoneNumber" inContext: context])
|
||||
{
|
||||
element = [self elementWithTag: @"tel" ofType: @"work"];
|
||||
[element setSingleValue: [theValues objectForKey: @"BusinessPhoneNumber"] forKey: @""];
|
||||
}
|
||||
|
||||
if ((o = [theValues objectForKey: @"HomePhoneNumber"]) || ![self _isGhosted: @"HomePhoneNumber" inContext: context])
|
||||
{
|
||||
element = [self elementWithTag: @"tel" ofType: @"home"];
|
||||
[element setSingleValue: [theValues objectForKey: @"HomePhoneNumber"] forKey: @""];
|
||||
}
|
||||
|
||||
if ((o = [theValues objectForKey: @"MobilePhoneNumber"]) || ![self _isGhosted: @"MobilePhoneNumber" inContext: context])
|
||||
{
|
||||
element = [self elementWithTag: @"tel" ofType: @"cell"];
|
||||
[element setSingleValue: [theValues objectForKey: @"MobilePhoneNumber"] forKey: @""];
|
||||
}
|
||||
|
||||
if ((o = [theValues objectForKey: @"BusinessFaxNumber"]) || ![self _isGhosted: @"BusinessFaxNumber" inContext: context])
|
||||
{
|
||||
element = [self elementWithTag: @"tel" ofType: @"fax"];
|
||||
[element setSingleValue: [theValues objectForKey: @"BusinessFaxNumber"] forKey: @""];
|
||||
}
|
||||
|
||||
if ((o = [theValues objectForKey: @"PagerNumber"]) || ![self _isGhosted: @"PagerNumber" inContext: context])
|
||||
{
|
||||
element = [self elementWithTag: @"tel" ofType: @"pager"];
|
||||
[element setSingleValue: [theValues objectForKey: @"PagerNumber"] forKey: @""];
|
||||
}
|
||||
|
||||
// Job's title
|
||||
if ((o = [theValues objectForKey: @"JobTitle"]))
|
||||
if ((o = [theValues objectForKey: @"JobTitle"]) || ![self _isGhosted: @"JobTitle" inContext: context])
|
||||
[self setTitle: o];
|
||||
|
||||
// WebPage (work)
|
||||
if ((o = [theValues objectForKey: @"WebPage"]))
|
||||
if ((o = [theValues objectForKey: @"WebPage"]) || ![self _isGhosted: @"WebPage" inContext: context])
|
||||
[[self elementWithTag: @"url" ofType: @"work"]
|
||||
setSingleValue: o forKey: @""];
|
||||
|
||||
if ((o = [theValues objectForKey: @"NickName"]))
|
||||
if ((o = [theValues objectForKey: @"NickName"]) || ![self _isGhosted: @"NickName" inContext: context])
|
||||
[self setNickname: o];
|
||||
|
||||
if ((o = [theValues objectForKey: @"Picture"]))
|
||||
|
|
|
@ -112,13 +112,73 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
||||
@implementation SOGoActiveSyncDispatcher (Sync)
|
||||
|
||||
- (void) _setOrUnsetSyncRequest: (BOOL) set
|
||||
collections: (NSArray *) collections
|
||||
{
|
||||
SOGoCacheGCSObject *o;
|
||||
NSNumber *processIdentifier;
|
||||
NSString *key;
|
||||
int i;
|
||||
|
||||
processIdentifier = [NSNumber numberWithInt: [[NSProcessInfo processInfo] processIdentifier]];
|
||||
|
||||
o = [SOGoCacheGCSObject objectWithName: [context objectForKey: @"DeviceId"] inContainer: nil useCache: NO];
|
||||
[o setObjectType: ActiveSyncGlobalCacheObject];
|
||||
[o setTableUrl: [self folderTableURL]];
|
||||
[o reloadIfNeeded];
|
||||
|
||||
if (set)
|
||||
{
|
||||
RELEASE(syncRequest);
|
||||
syncRequest = [NSNumber numberWithUnsignedInt: [[NSCalendarDate date] timeIntervalSince1970]];
|
||||
RETAIN(syncRequest);
|
||||
|
||||
[[o properties] setObject: syncRequest forKey: @"SyncRequest"];
|
||||
|
||||
for (i = 0; i < [collections count]; i++)
|
||||
{
|
||||
key = [NSString stringWithFormat: @"SyncRequest+%@", [[[(id)[[collections objectAtIndex: i] getElementsByTagName: @"CollectionId"] lastObject] textValue] stringByUnescapingURL]];
|
||||
[[o properties] setObject: processIdentifier forKey: key];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
[[o properties] removeObjectForKey: @"SyncRequest"];
|
||||
for (i = 0; i < [collections count]; i++)
|
||||
{
|
||||
key = [NSString stringWithFormat: @"SyncRequest+%@", [[[(id)[[collections objectAtIndex: i] getElementsByTagName: @"CollectionId"] lastObject] textValue] stringByUnescapingURL]];
|
||||
[[o properties] removeObjectForKey: key];
|
||||
}
|
||||
}
|
||||
|
||||
[o save];
|
||||
}
|
||||
|
||||
- (void) _setFolderMetadata: (NSDictionary *) theFolderMetadata
|
||||
forKey: (NSString *) theFolderKey
|
||||
{
|
||||
NSNumber *processIdentifier, *processIdentifierInCache;
|
||||
SOGoCacheGCSObject *o;
|
||||
NSDictionary *values;
|
||||
NSString *key;
|
||||
|
||||
if ([theFolderKey hasPrefix: @"folder"])
|
||||
key = [NSString stringWithFormat: @"SyncRequest+mail/%@", [theFolderKey substringFromIndex: 6]];
|
||||
else
|
||||
key = [NSString stringWithFormat: @"SyncRequest+%@", theFolderKey];
|
||||
|
||||
processIdentifier = [NSNumber numberWithInt: [[NSProcessInfo processInfo] processIdentifier]];
|
||||
processIdentifierInCache = [[self globalMetadataForDevice] objectForKey: key];
|
||||
|
||||
// Don't update the cache if another request is processing the same collection.
|
||||
if (!([processIdentifierInCache isEqual: processIdentifier]))
|
||||
{
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - We lost our lock - discard folder cache update %@ %@ <> %@", key, processIdentifierInCache, processIdentifier];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
key = [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], theFolderKey];
|
||||
values = [theFolderMetadata copy];
|
||||
|
||||
|
@ -132,7 +192,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
[[o properties] removeObjectForKey: @"DateCache"];
|
||||
[[o properties] removeObjectForKey: @"MoreAvailable"];
|
||||
[[o properties] removeObjectForKey: @"BodyPreferenceType"];
|
||||
[[o properties] removeObjectForKey: @"SupportedElements"];
|
||||
[[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"];
|
||||
[[o properties] removeObjectForKey: @"InitialLoadSequence"];
|
||||
|
||||
[[o properties] addEntriesFromDictionary: values];
|
||||
[o save];
|
||||
|
@ -159,7 +221,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
NSString *nameInCache;
|
||||
|
||||
if (theFolderType == ActiveSyncMailFolder)
|
||||
nameInCache= [[[theCollection mailAccountFolder] imapFolderGUIDs] objectForKey: [theCollection nameInContainer]];
|
||||
nameInCache = [imapFolderGUIDS objectForKey: [theCollection nameInContainer]];
|
||||
else
|
||||
{
|
||||
NSString *component_name;
|
||||
|
@ -403,8 +465,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
[o takeActiveSyncValues: allChanges inContext: context];
|
||||
[sogoObject saveComponent: o];
|
||||
|
||||
if ([syncCache objectForKey: serverId])
|
||||
[syncCache setObject: [NSString stringWithFormat:@"%f", [[sogoObject lastModified] timeIntervalSince1970]] forKey: serverId];
|
||||
|
||||
}
|
||||
break;
|
||||
case ActiveSyncEventFolder:
|
||||
|
@ -414,23 +476,23 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
[o takeActiveSyncValues: allChanges inContext: context];
|
||||
[sogoObject saveComponent: o];
|
||||
|
||||
if ([syncCache objectForKey: serverId])
|
||||
[syncCache setObject: [NSString stringWithFormat:@"%f", [[sogoObject lastModified] timeIntervalSince1970]] forKey: serverId];
|
||||
|
||||
}
|
||||
break;
|
||||
case ActiveSyncMailFolder:
|
||||
default:
|
||||
{
|
||||
NSDictionary *result;
|
||||
NSString *modseq;
|
||||
NSNumber *modseq;
|
||||
|
||||
[sogoObject takeActiveSyncValues: allChanges inContext: context];
|
||||
|
||||
result = [sogoObject fetchParts: [NSArray arrayWithObject: @"MODSEQ"]];
|
||||
modseq = [[[result objectForKey: @"RawResponse"] objectForKey: @"fetch"] objectForKey: @"modseq"];
|
||||
|
||||
if (modseq)
|
||||
[syncCache setObject: modseq forKey: serverId];
|
||||
if (modseq && [syncCache objectForKey: serverId])
|
||||
[syncCache setObject: [modseq stringValue] forKey: serverId];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -577,36 +639,44 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
withFilterType: (NSCalendarDate *) theFilterType
|
||||
inBuffer: (NSMutableString *) theBuffer
|
||||
lastServerKey: (NSString **) theLastServerKey
|
||||
|
||||
defaultInterval: (unsigned int) theDefaultInterval
|
||||
{
|
||||
NSMutableDictionary *folderMetadata, *dateCache, *syncCache;
|
||||
NSString *davCollectionTagToStore;
|
||||
NSAutoreleasePool *pool;
|
||||
NSMutableString *s;
|
||||
|
||||
BOOL more_available;
|
||||
BOOL cleanup_needed, more_available;
|
||||
int i, max;
|
||||
|
||||
s = [NSMutableString string];
|
||||
|
||||
more_available = NO;
|
||||
cleanup_needed = more_available = NO;
|
||||
|
||||
folderMetadata = [self _folderMetadataForKey: [self _getNameInCache: theCollection withType: theFolderType]];
|
||||
|
||||
// If this is a new sync operation, DateCache and SyncCache needs to be deleted
|
||||
// If this is a new sync operation, DateCache and SyncCache need to be deleted
|
||||
if ([theSyncKey isEqualToString: @"-1"])
|
||||
{
|
||||
[folderMetadata setObject: [NSMutableDictionary dictionary] forKey: @"SyncCache"];
|
||||
[folderMetadata setObject: [NSMutableDictionary dictionary] forKey: @"DateCache"];
|
||||
}
|
||||
else if ([folderMetadata objectForKey: @"SyncKey"] && !([theSyncKey isEqualToString: [folderMetadata objectForKey: @"SyncKey"]]))
|
||||
{
|
||||
// The syncKey received from the client doesn't match the syncKey we have in cache - client might have missed a response.
|
||||
// We need to cleanup this mess.
|
||||
[self logWithFormat: @"Cache cleanup needed for device %@ - user: %@ syncKey: %@ cache: %@", [context objectForKey: @"DeviceId"], [[context activeUser] login], theSyncKey, [folderMetadata objectForKey: @"SyncKey"]];
|
||||
cleanup_needed = YES;
|
||||
}
|
||||
|
||||
syncCache = [folderMetadata objectForKey: @"SyncCache"];
|
||||
dateCache = [folderMetadata objectForKey: @"DateCache"];
|
||||
|
||||
if ((theFolderType == ActiveSyncMailFolder || theFolderType == ActiveSyncEventFolder || theFolderType == ActiveSyncTaskFolder) &&
|
||||
!([folderMetadata objectForKey: @"MoreAvailable"]) && // previous sync operation reached the windowSize or maximumSyncReponseSize
|
||||
!([theSyncKey isEqualToString: @"-1"]) && // new sync operation
|
||||
theFilterType)
|
||||
(cleanup_needed ||
|
||||
( !([folderMetadata objectForKey: @"MoreAvailable"]) && // previous sync operation reached the windowSize or maximumSyncReponseSize
|
||||
!([folderMetadata objectForKey: @"InitialLoadSequence"]))) &&
|
||||
theFilterType
|
||||
)
|
||||
{
|
||||
NSArray *allKeys;
|
||||
NSString *key;
|
||||
|
@ -622,15 +692,37 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
||||
if ([[dateCache objectForKey:key] compare: theFilterType] == NSOrderedAscending)
|
||||
{
|
||||
if ([syncCache objectForKey:key])
|
||||
{
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - SoftDelete %@", key];
|
||||
|
||||
[s appendString: @"<SoftDelete xmlns=\"AirSync:\">"];
|
||||
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", key];
|
||||
[s appendString: @"</SoftDelete>"];
|
||||
|
||||
[syncCache removeObjectForKey: key];
|
||||
[dateCache removeObjectForKey: key];
|
||||
//[dateCache removeObjectForKey: key];
|
||||
|
||||
softdelete_count++;
|
||||
}
|
||||
else if (cleanup_needed)
|
||||
{
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - SoftDelete cleanup %@", key];
|
||||
|
||||
// With this we make sure that a SoftDelete is set again on next sync.
|
||||
[syncCache setObject: @"0" forKey: key];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - SoftDelete final delete %@", key];
|
||||
|
||||
// Now we are save to remove the dateCache entry.
|
||||
[dateCache removeObjectForKey: key];
|
||||
}
|
||||
}
|
||||
|
||||
if (softdelete_count >= theWindowSize || (theMaxSyncResponseSize > 0 && [s length] >= theMaxSyncResponseSize))
|
||||
{
|
||||
|
@ -657,8 +749,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
if ([theSyncKey isEqualToString: [theCollection davCollectionTag]] && !([s length]))
|
||||
return;
|
||||
|
||||
more_available = NO;
|
||||
|
||||
davCollectionTagToStore = [theCollection davCollectionTag];
|
||||
|
||||
switch (theFolderType)
|
||||
|
@ -673,7 +763,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
NSDictionary *component;
|
||||
NSArray *allComponents;
|
||||
|
||||
BOOL updated;
|
||||
BOOL updated, initialLoadInProgress;
|
||||
int deleted, return_count;
|
||||
|
||||
if (theFolderType == ActiveSyncContactFolder)
|
||||
|
@ -683,13 +773,76 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
else
|
||||
component_name = @"vtodo";
|
||||
|
||||
allComponents = [theCollection syncTokenFieldsWithProperties: nil matchingSyncToken: theSyncKey fromDate: theFilterType];
|
||||
allComponents = [allComponents sortedArrayUsingDescriptors: [NSArray arrayWithObjects: [[NSSortDescriptor alloc] initWithKey: @"c_lastmodified" ascending:YES], nil]];
|
||||
initialLoadInProgress = NO;
|
||||
|
||||
if ([theSyncKey isEqualToString: @"-1"])
|
||||
[folderMetadata setObject: davCollectionTagToStore forKey: @"InitialLoadSequence"];
|
||||
|
||||
if ([folderMetadata objectForKey: @"InitialLoadSequence"])
|
||||
{
|
||||
if ([theSyncKey intValue] < [[folderMetadata objectForKey: @"InitialLoadSequence"] intValue])
|
||||
initialLoadInProgress = YES;
|
||||
else
|
||||
[folderMetadata removeObjectForKey: @"InitialLoadSequence"];
|
||||
}
|
||||
|
||||
allComponents = [theCollection syncTokenFieldsWithProperties: nil
|
||||
matchingSyncToken: theSyncKey
|
||||
fromDate: theFilterType
|
||||
initialLoad: initialLoadInProgress];
|
||||
allComponents = [allComponents sortedArrayUsingDescriptors: [NSArray arrayWithObject: [[[NSSortDescriptor alloc] initWithKey: @"c_lastmodified" ascending: YES] autorelease]]];
|
||||
|
||||
|
||||
// Check for the WindowSize
|
||||
max = [allComponents count];
|
||||
|
||||
//
|
||||
// Cleanup the mess
|
||||
//
|
||||
if (cleanup_needed)
|
||||
{
|
||||
|
||||
for (i = 0; i < max; i++)
|
||||
{
|
||||
component = [allComponents objectAtIndex: i];
|
||||
deleted = [[component objectForKey: @"c_deleted"] intValue];
|
||||
|
||||
if (!deleted && ![[component objectForKey: @"c_component"] isEqualToString: component_name])
|
||||
continue;
|
||||
|
||||
uid = [[component objectForKey: @"c_name"] sanitizedServerIdWithType: theFolderType];
|
||||
|
||||
if (deleted)
|
||||
{
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - Cache cleanup: DELETE %@", uid];
|
||||
|
||||
// For deletes we have to recreate a cache entry to make sure the delete is sent again.
|
||||
[syncCache setObject: @"0" forKey: uid];
|
||||
}
|
||||
else
|
||||
{
|
||||
if ([syncCache objectForKey: uid] && [[component objectForKey: @"c_creationdate"] intValue] > [theSyncKey intValue])
|
||||
{
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - Cache cleanup: ADD %@", uid];
|
||||
|
||||
// Cleanup the cache to make sure the add is sent again.
|
||||
[syncCache removeObjectForKey: uid];
|
||||
[dateCache removeObjectForKey: uid];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - Cache cleanup: CHANGE %@", uid];
|
||||
|
||||
// Update cache entry to make sure the change is sent again.
|
||||
[syncCache setObject: @"0" forKey: uid];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return_count = 0;
|
||||
|
||||
for (i = 0; i < max; i++)
|
||||
|
@ -847,12 +1000,38 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
NSMutableArray *allCacheObjects, *sortedBySequence;
|
||||
|
||||
SOGoMailObject *mailObject;
|
||||
NSArray *allMessages;
|
||||
NSArray *allMessages, *a;
|
||||
NSString *firstUIDAdded;
|
||||
|
||||
int j, k, return_count;
|
||||
BOOL found_in_cache;
|
||||
int j, k, return_count, highestmodseq;
|
||||
BOOL found_in_cache, initialLoadInProgress;
|
||||
|
||||
allMessages = [theCollection syncTokenFieldsWithProperties: nil matchingSyncToken: theSyncKey fromDate: theFilterType];
|
||||
initialLoadInProgress = NO;
|
||||
found_in_cache = NO;
|
||||
firstUIDAdded = nil;
|
||||
|
||||
if ([theSyncKey isEqualToString: @"-1"])
|
||||
{
|
||||
highestmodseq = 0;
|
||||
|
||||
a = [[theCollection davCollectionTag] componentsSeparatedByString: @"-"];
|
||||
[folderMetadata setObject: [a objectAtIndex: 1] forKey: @"InitialLoadSequence"];
|
||||
}
|
||||
else
|
||||
{
|
||||
a = [theSyncKey componentsSeparatedByString: @"-"];
|
||||
highestmodseq = [[a objectAtIndex: 1] intValue];
|
||||
}
|
||||
|
||||
if ([folderMetadata objectForKey: @"InitialLoadSequence"])
|
||||
{
|
||||
if (highestmodseq < [[folderMetadata objectForKey: @"InitialLoadSequence"] intValue])
|
||||
initialLoadInProgress = YES;
|
||||
else
|
||||
[folderMetadata removeObjectForKey: @"InitialLoadSequence"];
|
||||
}
|
||||
|
||||
allMessages = [theCollection syncTokenFieldsWithProperties: nil matchingSyncToken: theSyncKey fromDate: theFilterType initialLoad: initialLoadInProgress];
|
||||
max = [allMessages count];
|
||||
|
||||
allCacheObjects = [NSMutableArray array];
|
||||
|
@ -869,15 +1048,82 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
||||
[allCacheObjects sortUsingSelector: @selector(compareSequence:)];
|
||||
|
||||
//NSLog(@"sortedBySequence (%d) - lastObject: %@", [sortedBySequence count], [sortedBySequence lastObject]);
|
||||
//NSLog(@"allCacheObjects (%d) - lastObject: %@", [allCacheObjects count], [allCacheObjects lastObject]);
|
||||
if (debugOn)
|
||||
{
|
||||
[self logWithFormat: @"EAS - sortedBySequence (%d) - lastObject: %@", [sortedBySequence count], [sortedBySequence lastObject]];
|
||||
[self logWithFormat: @"EAS - allCacheObjects (%d) - lastObject: %@", [allCacheObjects count], [allCacheObjects lastObject]];
|
||||
}
|
||||
|
||||
lastCacheObject = [sortedBySequence lastObject];
|
||||
|
||||
if ([folderMetadata objectForKey: @"MoreAvailable"] && lastCacheObject)
|
||||
//
|
||||
// Cleanup the mess
|
||||
//
|
||||
if (cleanup_needed)
|
||||
{
|
||||
NSMutableArray *sortedByUID;
|
||||
int uidnextFromCache;
|
||||
|
||||
sortedByUID = [[NSMutableArray alloc] initWithDictionary: syncCache];
|
||||
[sortedByUID sortUsingSelector: @selector(compareUID:)];
|
||||
|
||||
// Get the uid from SyncKey in cache. The uid is the first uid added to cache by the last sync request.
|
||||
a = [[folderMetadata objectForKey: @"SyncKey"] componentsSeparatedByString: @"-"];
|
||||
uidnextFromCache = [[a objectAtIndex: 0] intValue];
|
||||
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - Cache cleanup: from uid: %d to uid: %d", uidnextFromCache, [[[sortedByUID lastObject] uid] intValue]];
|
||||
|
||||
// Remove all entries from cache beginning with the first uid added by the last sync request.
|
||||
for (j = uidnextFromCache; j <= [[[sortedByUID lastObject] uid] intValue]; j++)
|
||||
{
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - Cache cleanup: ADD %d", j];
|
||||
|
||||
[syncCache removeObjectForKey: [NSString stringWithFormat:@"%d", j]];
|
||||
[dateCache removeObjectForKey: [NSString stringWithFormat:@"%d", j]];
|
||||
}
|
||||
|
||||
RELEASE(sortedByUID);
|
||||
|
||||
for (j = 0; j < [allCacheObjects count]; j++)
|
||||
{
|
||||
// Update the modseq in cache, sence othersie, it would be identical to the modseq from server
|
||||
//and we would skip the cache when generating the response.
|
||||
if ([syncCache objectForKey: [[allCacheObjects objectAtIndex: j] uid]] && ![[[allCacheObjects objectAtIndex: j] sequence] isEqual: [NSNull null]])
|
||||
{
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - Cache cleanup: CHANGE %@", [[allCacheObjects objectAtIndex: j] uid]];
|
||||
|
||||
[syncCache setObject: @"0" forKey:[[allCacheObjects objectAtIndex: j] uid]];
|
||||
}
|
||||
else if ([[[allCacheObjects objectAtIndex: j] sequence] isEqual: [NSNull null]])
|
||||
{
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - Cache cleanup: DELETE %@", [[allCacheObjects objectAtIndex: j] uid]];
|
||||
|
||||
// For deletes we have to recreate a cache entry to have the <Delete> included in the response.
|
||||
[syncCache setObject: @"0" forKey:[[allCacheObjects objectAtIndex: j] uid]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!cleanup_needed &&
|
||||
[folderMetadata objectForKey: @"MoreAvailable"] &&
|
||||
lastCacheObject &&
|
||||
!([[lastCacheObject sequence] isEqual: @"0"])) // Sequence 0 is set during cache cleanup.
|
||||
{
|
||||
for (j = 0; j < [allCacheObjects count]; j++)
|
||||
{
|
||||
if (([[[allCacheObjects objectAtIndex: j] sequence] isEqual: [NSNull null]] && [syncCache objectForKey: [[allCacheObjects objectAtIndex: j] uid]]) ||
|
||||
(![[[allCacheObjects objectAtIndex: j] sequence] isEqual: [NSNull null]] && ![syncCache objectForKey: [[allCacheObjects objectAtIndex: j] uid]]))
|
||||
{
|
||||
// We need to continue with adds or deletes from here.
|
||||
found_in_cache = YES;
|
||||
j--;
|
||||
break;
|
||||
}
|
||||
|
||||
if ([[lastCacheObject uid] isEqual: [[allCacheObjects objectAtIndex: j] uid]])
|
||||
{
|
||||
// Found out where we're at, let's continue from there...
|
||||
|
@ -892,12 +1138,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
if (found_in_cache)
|
||||
k = j+1;
|
||||
else
|
||||
{
|
||||
k = 0;
|
||||
j = 0;
|
||||
}
|
||||
j = k = 0;
|
||||
|
||||
//NSLog(@"found in cache: %d k = %d", found_in_cache, k);
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - found in cache: %d k = %d", found_in_cache, k];
|
||||
|
||||
return_count = 0;
|
||||
|
||||
|
@ -911,20 +1155,35 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
NSString *lastSequence;
|
||||
more_available = YES;
|
||||
|
||||
lastSequence = ([[aCacheObject sequence] isEqual: [NSNull null]] ? @"1" : [aCacheObject sequence]);
|
||||
*theLastServerKey = [[NSString alloc] initWithFormat: @"%@-%@", [aCacheObject uid], lastSequence];
|
||||
//NSLog(@"Reached windowSize - lastUID will be: %@", *theLastServerKey);
|
||||
if (!firstUIDAdded)
|
||||
{
|
||||
a = [davCollectionTagToStore componentsSeparatedByString: @"-"];
|
||||
firstUIDAdded = [a objectAtIndex: 0];
|
||||
RETAIN(firstUIDAdded);
|
||||
}
|
||||
lastSequence = ([[aCacheObject sequence] isEqual: [NSNull null]] ? [NSString stringWithFormat:@"%d", highestmodseq] : [aCacheObject sequence]);
|
||||
*theLastServerKey = [[NSString alloc] initWithFormat: @"%@-%@", firstUIDAdded, lastSequence];
|
||||
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - Reached windowSize - lastUID will be: %@", *theLastServerKey];
|
||||
|
||||
DESTROY(pool);
|
||||
break;
|
||||
}
|
||||
|
||||
aCacheObject = [allCacheObjects objectAtIndex: k];
|
||||
|
||||
// If found in cache, it's either a Change or a Delete
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - Dealing with cacheObject: %@", aCacheObject];
|
||||
|
||||
// If found in cache, it's either a Change or a Delete operation.
|
||||
if ([syncCache objectForKey: [aCacheObject uid]])
|
||||
{
|
||||
if ([[aCacheObject sequence] isEqual: [NSNull null]])
|
||||
{
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - DELETE!"];
|
||||
|
||||
// Deleted
|
||||
[s appendString: @"<Delete xmlns=\"AirSync:\">"];
|
||||
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", [aCacheObject uid]];
|
||||
|
@ -945,6 +1204,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
||||
if (![[aCacheObject sequence] isEqual: [syncCache objectForKey: [aCacheObject uid]]])
|
||||
{
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - CHANGE!"];
|
||||
|
||||
[s appendString: @"<Change xmlns=\"AirSync:\">"];
|
||||
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", [aCacheObject uid]];
|
||||
[s appendString: @"<ApplicationData xmlns=\"AirSync:\">"];
|
||||
|
@ -960,6 +1222,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
}
|
||||
else
|
||||
{
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - ADD!"];
|
||||
|
||||
// Added
|
||||
if (![[aCacheObject sequence] isEqual: [NSNull null]])
|
||||
{
|
||||
|
@ -992,11 +1257,22 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
||||
[syncCache setObject: [aCacheObject sequence] forKey: [aCacheObject uid]];
|
||||
[dateCache setObject: [NSCalendarDate date] forKey: [aCacheObject uid]];
|
||||
|
||||
// Save the frist UID we add. We will use it for the synckey late.
|
||||
if (!firstUIDAdded)
|
||||
{
|
||||
firstUIDAdded = [aCacheObject uid];
|
||||
RETAIN(firstUIDAdded);
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - first uid added %@", firstUIDAdded];
|
||||
}
|
||||
|
||||
return_count++;
|
||||
}
|
||||
else
|
||||
{
|
||||
//NSLog(@"skipping old deleted UID: %@", [aCacheObject uid]);
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - skipping old deleted UID: %@", [aCacheObject uid]];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1005,16 +1281,25 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
||||
if (more_available)
|
||||
{
|
||||
[folderMetadata setObject: [NSNumber numberWithBool: YES] forKey: @"MoreAvailable"];
|
||||
[folderMetadata setObject: [NSNumber numberWithInt: YES] forKey: @"MoreAvailable"];
|
||||
[folderMetadata setObject: *theLastServerKey forKey: @"SyncKey"];
|
||||
}
|
||||
else
|
||||
{
|
||||
[folderMetadata removeObjectForKey: @"MoreAvailable"];
|
||||
|
||||
if (firstUIDAdded)
|
||||
{
|
||||
a = [davCollectionTagToStore componentsSeparatedByString: @"-"];
|
||||
[folderMetadata setObject: [[NSString alloc] initWithFormat: @"%@-%@", firstUIDAdded, [a objectAtIndex: 1]] forKey: @"SyncKey"];
|
||||
RELEASE(firstUIDAdded);
|
||||
}
|
||||
else
|
||||
[folderMetadata setObject: davCollectionTagToStore forKey: @"SyncKey"];
|
||||
}
|
||||
|
||||
[self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: theCollection withType: theFolderType]];
|
||||
|
||||
RELEASE(*theLastServerKey);
|
||||
|
||||
} // default:
|
||||
|
@ -1113,14 +1398,15 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
changeDetected: (BOOL *) changeDetected
|
||||
maxSyncResponseSize: (int) theMaxSyncResponseSize
|
||||
{
|
||||
NSString *collectionId, *realCollectionId, *syncKey, *davCollectionTag, *bodyPreferenceType, *mimeSupport, *lastServerKey, *syncKeyInCache;
|
||||
SOGoMicrosoftActiveSyncFolderType folderType;
|
||||
NSString *collectionId, *realCollectionId, *syncKey, *davCollectionTag, *bodyPreferenceType, *mimeSupport, *lastServerKey, *syncKeyInCache, *folderKey;
|
||||
NSMutableDictionary *folderMetadata, *folderOptions;
|
||||
NSMutableArray *supportedElements, *supportedElementNames;
|
||||
NSMutableString *changeBuffer, *commandsBuffer;
|
||||
id collection, value;
|
||||
|
||||
NSMutableString *changeBuffer, *commandsBuffer;
|
||||
SOGoMicrosoftActiveSyncFolderType folderType;
|
||||
unsigned int windowSize, v, status, i;
|
||||
BOOL getChanges, first_sync;
|
||||
unsigned int windowSize, v, status;
|
||||
NSMutableDictionary *folderMetadata, *folderOptions;
|
||||
|
||||
changeBuffer = [NSMutableString string];
|
||||
commandsBuffer = [NSMutableString string];
|
||||
|
@ -1145,6 +1431,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// First check if we have any concurrent Sync requests going on for this device.
|
||||
// If we do and we are still within our maximumSyncInterval, we let our EAS
|
||||
// device know to retry.
|
||||
//
|
||||
folderKey = [self _getNameInCache: collection withType: folderType];
|
||||
folderMetadata = [self _folderMetadataForKey: folderKey];
|
||||
|
||||
// We check for a window size, default to 100 if not specfied or out of bounds
|
||||
windowSize = [[[(id)[theDocumentElement getElementsByTagName: @"WindowSize"] lastObject] textValue] intValue];
|
||||
|
||||
|
@ -1169,13 +1463,35 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
||||
first_sync = NO;
|
||||
|
||||
folderMetadata = [self _folderMetadataForKey: [self _getNameInCache: collection withType: folderType]];
|
||||
|
||||
if ([syncKey isEqualToString: @"0"])
|
||||
{
|
||||
davCollectionTag = @"-1";
|
||||
first_sync = YES;
|
||||
*changeDetected = YES;
|
||||
|
||||
supportedElementNames = [[[NSMutableArray alloc] init] autorelease];
|
||||
value = [theDocumentElement getElementsByTagName: @"Supported"];
|
||||
|
||||
if ([value count])
|
||||
{
|
||||
supportedElements = (id)[[value lastObject] childNodes];
|
||||
|
||||
if ([supportedElements count])
|
||||
{
|
||||
for (i = 0; i < [supportedElements count]; i++)
|
||||
{
|
||||
if ([[supportedElements objectAtIndex: i] nodeType] == DOM_ELEMENT_NODE)
|
||||
[supportedElementNames addObject: [[supportedElements objectAtIndex: i] tagName]];
|
||||
}
|
||||
}
|
||||
|
||||
[folderMetadata setObject: supportedElementNames forKey: @"SupportedElements"];
|
||||
|
||||
[self _setFolderMetadata: folderMetadata forKey: folderKey];
|
||||
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - %d %@: supportedElements saved: %@", [supportedElements count], [collection nameInContainer], supportedElementNames];
|
||||
}
|
||||
}
|
||||
else if ((![syncKey isEqualToString: @"-1"]) && !([folderMetadata objectForKey: @"SyncCache"]))
|
||||
{
|
||||
|
@ -1200,6 +1516,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
// By default, send MIME mails. See #3146 for details.
|
||||
if (!bodyPreferenceType)
|
||||
bodyPreferenceType = @"4";
|
||||
|
||||
mimeSupport = [[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"MIMESupport"];
|
||||
|
||||
if (!mimeSupport)
|
||||
mimeSupport = @"1";
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1225,11 +1546,13 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
{
|
||||
folderOptions = [[NSDictionary alloc] initWithObjectsAndKeys: mimeSupport, @"MIMESupport", bodyPreferenceType, @"BodyPreferenceType", nil];
|
||||
[folderMetadata setObject: folderOptions forKey: @"FolderOptions"];
|
||||
[self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: collection withType: folderType]];
|
||||
[self _setFolderMetadata: folderMetadata forKey: folderKey];
|
||||
}
|
||||
}
|
||||
|
||||
[context setObject: bodyPreferenceType forKey: @"BodyPreferenceType"];
|
||||
[context setObject: mimeSupport forKey: @"MIMESupport"];
|
||||
[context setObject: [folderMetadata objectForKey: @"SupportedElements"] forKey: @"SupportedElements"];
|
||||
|
||||
//
|
||||
// We process the commands from the request
|
||||
|
@ -1268,10 +1591,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
withFolderType: folderType
|
||||
withFilterType: [NSCalendarDate dateFromFilterType: [[(id)[theDocumentElement getElementsByTagName: @"FilterType"] lastObject] textValue]]
|
||||
inBuffer: changeBuffer
|
||||
lastServerKey: &lastServerKey];
|
||||
lastServerKey: &lastServerKey
|
||||
defaultInterval: [[SOGoSystemDefaults sharedSystemDefaults] maximumSyncInterval]];
|
||||
}
|
||||
|
||||
folderMetadata = [self _folderMetadataForKey: [self _getNameInCache: collection withType: folderType]];
|
||||
folderMetadata = [self _folderMetadataForKey: folderKey];
|
||||
|
||||
// If we got any changes or if we have applied any commands
|
||||
// let's regenerate our SyncKey based on the collection tag.
|
||||
|
@ -1430,19 +1754,24 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
SOGoSystemDefaults *defaults;
|
||||
id <DOMElement> aCollection;
|
||||
NSMutableString *output, *s;
|
||||
NSMutableDictionary *globalMetadata;
|
||||
NSNumber *syncRequestInCache, *processIdentifier;
|
||||
NSString *key;
|
||||
NSArray *allCollections;
|
||||
NSData *d;
|
||||
|
||||
int i, j, defaultInterval, heartbeatInterval, internalInterval, maxSyncResponseSize;
|
||||
int i, j, defaultInterval, heartbeatInterval, internalInterval, maxSyncResponseSize, total_sleep;
|
||||
BOOL changeDetected;
|
||||
|
||||
changeDetected = NO;
|
||||
|
||||
maxSyncResponseSize = [[SOGoSystemDefaults sharedSystemDefaults] maximumSyncResponseSize];
|
||||
|
||||
// We initialize our output buffer
|
||||
output = [[NSMutableString alloc] init];
|
||||
|
||||
defaults = [SOGoSystemDefaults sharedSystemDefaults];
|
||||
defaultInterval = [defaults maximumSyncInterval];
|
||||
processIdentifier = [NSNumber numberWithInt: [[NSProcessInfo processInfo] processIdentifier]];
|
||||
|
||||
allCollections = (id)[theDocumentElement getElementsByTagName: @"Collection"];
|
||||
|
||||
[output appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
|
||||
[output appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
|
||||
[output appendString: @"<Sync xmlns=\"AirSync:\">"];
|
||||
|
@ -1457,12 +1786,16 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
[output appendString: @"</Sync>"];
|
||||
d = [[output dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
|
||||
[theResponse setContent: d];
|
||||
RELEASE(output);
|
||||
return;
|
||||
}
|
||||
|
||||
defaults = [SOGoSystemDefaults sharedSystemDefaults];
|
||||
// Let other requests know about the collections we are dealing with.
|
||||
[self _setOrUnsetSyncRequest: YES collections: allCollections];
|
||||
|
||||
changeDetected = NO;
|
||||
maxSyncResponseSize = [[SOGoSystemDefaults sharedSystemDefaults] maximumSyncResponseSize];
|
||||
heartbeatInterval = [[[(id)[theDocumentElement getElementsByTagName: @"HeartbeatInterval"] lastObject] textValue] intValue];
|
||||
defaultInterval = [defaults maximumSyncInterval];
|
||||
internalInterval = [defaults internalSyncInterval];
|
||||
|
||||
// If the request doesn't contain "HeartbeatInterval" there is no reason to delay the response.
|
||||
|
@ -1472,18 +1805,21 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
// We check to see if our heartbeat interval falls into the supported ranges.
|
||||
if (heartbeatInterval > defaultInterval || heartbeatInterval < 1)
|
||||
{
|
||||
int limit;
|
||||
// Interval is too long, inform the client.
|
||||
heartbeatInterval = defaultInterval;
|
||||
|
||||
// Outlook doesn't like this...
|
||||
//[output appendFormat: @"<Limit>%d</Limit>", defaultInterval];
|
||||
// When Status = 14, the Wait interval is specified in minutes while
|
||||
// defaultInterval is specifed in seconds. Adjust accordinlgy.
|
||||
limit = defaultInterval/60;
|
||||
if (limit < 1) limit = 1;
|
||||
if (limit > 59) limit = 59;
|
||||
//[output appendFormat: @"<Limit>%d</Limit>", limit];
|
||||
//[output appendFormat: @"<Status>%d</Status>", 14];
|
||||
}
|
||||
|
||||
[output appendString: @"<Collections>"];
|
||||
|
||||
allCollections = (id)[theDocumentElement getElementsByTagName: @"Collection"];
|
||||
|
||||
// We enter our loop detection change
|
||||
for (i = 0; i < (heartbeatInterval/internalInterval); i++)
|
||||
{
|
||||
|
@ -1498,19 +1834,56 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
changeDetected: &changeDetected
|
||||
maxSyncResponseSize: maxSyncResponseSize];
|
||||
|
||||
if (maxSyncResponseSize > 0 && [s length] >= 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;
|
||||
}
|
||||
|
||||
if (changeDetected)
|
||||
{
|
||||
[self logWithFormat: @"Change detected, we push the content."];
|
||||
[self logWithFormat: @"Change detected during Sync, we push the content."];
|
||||
break;
|
||||
}
|
||||
else if (heartbeatInterval > 1)
|
||||
{
|
||||
[self logWithFormat: @"Sleeping %d seconds while detecting changes...", internalInterval];
|
||||
sleep(internalInterval);
|
||||
total_sleep = 0;
|
||||
|
||||
while (total_sleep < internalInterval)
|
||||
{
|
||||
// We check if we must break the current synchronization since an other Sync
|
||||
// has just arrived.
|
||||
syncRequestInCache = [[self globalMetadataForDevice] objectForKey: @"SyncRequest"];
|
||||
if (!([syncRequest isEqualToNumber: syncRequestInCache]))
|
||||
{
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - Heartbeat stopped %@", [self globalMetadataForDevice]];
|
||||
|
||||
// Make sure we end the heardbeat-loop.
|
||||
heartbeatInterval = internalInterval = 1;
|
||||
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
[self logWithFormat: @"Sleeping %d seconds while detecting changes in Sync...", internalInterval-total_sleep];
|
||||
sleep(5);
|
||||
total_sleep += 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1518,8 +1891,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// Only send a response if there are changes or MS-ASProtocolVersion is either 2.5 or 12.0 oterwise send an empty response.
|
||||
//
|
||||
// Only send a response if there are changes or MS-ASProtocolVersion is either 2.5 or 12.0,
|
||||
// otherwise send an empty response.
|
||||
//
|
||||
if (changeDetected || [[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"2.5"] || [[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"12.0"])
|
||||
{
|
||||
// We always return the last generated response.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
|
||||
Copyright (c) 2014, Inverse inc.
|
||||
Copyright (c) 2014-2015, Inverse inc.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
@ -31,15 +31,25 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
||||
#include "SOGoActiveSyncConstants.h"
|
||||
|
||||
@class NSCalendarDate;
|
||||
@class NSException;
|
||||
@class NSMutableDictionary;
|
||||
@class NSURL;
|
||||
@class NSNumber;
|
||||
|
||||
@interface SOGoActiveSyncDispatcher : NSObject
|
||||
{
|
||||
NSURL *folderTableURL;
|
||||
NSDictionary *imapFolderGUIDS;
|
||||
id context;
|
||||
|
||||
NSNumber *syncRequest;
|
||||
|
||||
BOOL debugOn;
|
||||
}
|
||||
|
||||
- (NSMutableDictionary *) globalMetadataForDevice;
|
||||
|
||||
- (id) collectionFromId: (NSString *) theCollectionId
|
||||
type: (SOGoMicrosoftActiveSyncFolderType) theFolderType;
|
||||
|
||||
|
|
|
@ -30,8 +30,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
#include "SOGoActiveSyncDispatcher.h"
|
||||
|
||||
#import <Foundation/NSArray.h>
|
||||
#import <Foundation/NSData.h>
|
||||
#import <Foundation/NSAutoreleasePool.h>
|
||||
#import <Foundation/NSCalendarDate.h>
|
||||
#if GNUSTEP_BASE_MINOR_VERSION >= 21
|
||||
#import <Foundation/NSLocale.h>
|
||||
#endif
|
||||
#import <Foundation/NSProcessInfo.h>
|
||||
#import <Foundation/NSTimeZone.h>
|
||||
#import <Foundation/NSURL.h>
|
||||
|
@ -134,6 +138,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
||||
#include <unistd.h>
|
||||
|
||||
#ifdef HAVE_OPENSSL
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/x509.h>
|
||||
#endif
|
||||
|
||||
@interface SOGoActiveSyncDispatcher (Sync)
|
||||
|
||||
- (NSMutableDictionary *) _folderMetadataForKey: (NSString *) theFolderKey;
|
||||
|
@ -143,20 +153,22 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
||||
@implementation SOGoActiveSyncDispatcher
|
||||
|
||||
static BOOL debugOn = NO;
|
||||
|
||||
- (id) init
|
||||
{
|
||||
[super init];
|
||||
|
||||
debugOn = [[SOGoSystemDefaults sharedSystemDefaults] easDebugEnabled];
|
||||
folderTableURL = nil;
|
||||
imapFolderGUIDS = nil;
|
||||
syncRequest = nil;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
RELEASE(folderTableURL);
|
||||
RELEASE(imapFolderGUIDS);
|
||||
RELEASE(syncRequest);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
|
@ -169,16 +181,16 @@ static BOOL debugOn = NO;
|
|||
[o setTableUrl: [self folderTableURL]];
|
||||
[o reloadIfNeeded];
|
||||
|
||||
[[o properties] removeAllObjects];
|
||||
[[o properties] addEntriesFromDictionary: [NSDictionary dictionaryWithObject: theSyncKey forKey: @"FolderSyncKey"]];
|
||||
[[o properties] setObject: theSyncKey
|
||||
forKey: @"FolderSyncKey"];
|
||||
[o save];
|
||||
}
|
||||
|
||||
- (NSMutableDictionary *) _globalMetadataForDevice
|
||||
- (NSMutableDictionary *) globalMetadataForDevice
|
||||
{
|
||||
SOGoCacheGCSObject *o;
|
||||
|
||||
o = [SOGoCacheGCSObject objectWithName: [context objectForKey: @"DeviceId"] inContainer: nil];
|
||||
o = [SOGoCacheGCSObject objectWithName: [context objectForKey: @"DeviceId"] inContainer: nil useCache: NO];
|
||||
[o setObjectType: ActiveSyncGlobalCacheObject];
|
||||
[o setTableUrl: [self folderTableURL]];
|
||||
[o reloadIfNeeded];
|
||||
|
@ -202,7 +214,7 @@ static BOOL debugOn = NO;
|
|||
if (theFilter)
|
||||
{
|
||||
o = [SOGoCacheGCSObject objectWithName: [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], theCollectionId] inContainer: nil];
|
||||
[o setObjectType: ActiveSyncGlobalCacheObject];
|
||||
[o setObjectType: ActiveSyncFolderCacheObject];
|
||||
[o setTableUrl: [self folderTableURL]];
|
||||
[o reloadIfNeeded];
|
||||
|
||||
|
@ -229,23 +241,27 @@ static BOOL debugOn = NO;
|
|||
SOGoMailAccounts *accountsFolder;
|
||||
SOGoMailAccount *accountFolder;
|
||||
SOGoUserFolder *userFolder;
|
||||
NSDictionary *imapGUIDs;
|
||||
|
||||
if (!imapFolderGUIDS)
|
||||
{
|
||||
userFolder = [[context activeUser] homeFolderInContext: context];
|
||||
accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
|
||||
accountFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO];
|
||||
|
||||
// Get the GUID of the IMAP folder
|
||||
imapGUIDs = [accountFolder imapFolderGUIDs];
|
||||
imapFolderGUIDS = [accountFolder imapFolderGUIDs];
|
||||
[imapFolderGUIDS retain];
|
||||
|
||||
//return [[imapGUIDs allKeysForObject: theIdToTranslate] objectAtIndex: 0];
|
||||
return [[[imapGUIDs allKeysForObject: [NSString stringWithFormat: @"folder%@", theIdToTranslate]] objectAtIndex: 0] substringFromIndex: 6] ;
|
||||
}
|
||||
|
||||
return [[[imapFolderGUIDS allKeysForObject: [NSString stringWithFormat: @"folder%@", theIdToTranslate]] objectAtIndex: 0] substringFromIndex: 6] ;
|
||||
}
|
||||
|
||||
return theIdToTranslate;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
|
@ -691,26 +707,25 @@ static BOOL debugOn = NO;
|
|||
- (void) processFolderSync: (id <DOMElement>) theDocumentElement
|
||||
inResponse: (WOResponse *) theResponse
|
||||
{
|
||||
NSString *key, *cKey, *nkey, *name, *serverId, *parentId, *nameInCache, *personalFolderName, *syncKey, *folderType;
|
||||
NSString *key, *cKey, *nkey, *name, *serverId, *parentId, *nameInCache, *personalFolderName, *syncKey, *folderType, *operation;
|
||||
NSMutableDictionary *cachedGUIDs, *metadata;
|
||||
NSMutableArray *folders, *processedFolders;
|
||||
NSDictionary *folderMetadata, *imapGUIDs;
|
||||
NSArray *allFoldersMetadata, *allKeys;
|
||||
NSMutableDictionary *cachedGUIDs, *metadata;
|
||||
SOGoMailAccounts *accountsFolder;
|
||||
SOGoMailAccount *accountFolder;
|
||||
NSMutableString *s, *commands;
|
||||
SOGoUserFolder *userFolder;
|
||||
NSMutableArray *folders, *processedFolders;
|
||||
SoSecurityManager *sm;
|
||||
SOGoCacheGCSObject *o;
|
||||
id currentFolder;
|
||||
NSData *d;
|
||||
|
||||
int status, command_count, i, type, fi, count;
|
||||
|
||||
BOOL first_sync;
|
||||
|
||||
sm = [SoSecurityManager sharedSecurityManager];
|
||||
metadata = [self _globalMetadataForDevice];
|
||||
metadata = [self globalMetadataForDevice];
|
||||
syncKey = [[(id)[theDocumentElement getElementsByTagName: @"SyncKey"] lastObject] textValue];
|
||||
s = [NSMutableString string];
|
||||
|
||||
|
@ -834,8 +849,14 @@ static BOOL debugOn = NO;
|
|||
currentFolder = [[[[context activeUser] homeFolderInContext: context] lookupName: folderType inContext: context acquire: NO]
|
||||
lookupName: [cKey substringFromIndex: [cKey rangeOfString: @"/"].location+1] inContext: context acquire: NO];
|
||||
|
||||
// remove the folder from device if it doesn't exists or it has not the proper permissions
|
||||
// We skip personal GCS folders - we always want to synchronize these
|
||||
if ([currentFolder isKindOfClass: [SOGoGCSFolder class]] &&
|
||||
[[currentFolder nameInContainer] isEqualToString: @"personal"])
|
||||
continue;
|
||||
|
||||
// Remove the folder from device if it doesn't exist, we don't want to sync it, or it doesn't have the proper permissions
|
||||
if (!currentFolder ||
|
||||
![currentFolder synchronize] ||
|
||||
[sm validatePermission: SoPerm_DeleteObjects
|
||||
onObject: currentFolder
|
||||
inContext: context] ||
|
||||
|
@ -937,7 +958,9 @@ static BOOL debugOn = NO;
|
|||
[[o properties] removeObjectForKey: @"DateCache"];
|
||||
[[o properties] removeObjectForKey: @"MoreAvailable"];
|
||||
[[o properties] removeObjectForKey: @"BodyPreferenceType"];
|
||||
[[o properties] removeObjectForKey: @"SupportedElements"];
|
||||
[[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"];
|
||||
[[o properties] removeObjectForKey: @"InitialLoadSequence"];
|
||||
[o save];
|
||||
|
||||
command_count++;
|
||||
|
@ -946,17 +969,29 @@ static BOOL debugOn = NO;
|
|||
|
||||
personalFolderName = [[[context activeUser] personalCalendarFolderInContext: context] nameInContainer];
|
||||
folders = [[[[[context activeUser] homeFolderInContext: context] lookupName: @"Calendar" inContext: context acquire: NO] subFolders] mutableCopy];
|
||||
[folders autorelease];
|
||||
|
||||
[folders addObjectsFromArray: [[[[context activeUser] homeFolderInContext: context] lookupName: @"Contacts" inContext: context acquire: NO] subFolders]];
|
||||
|
||||
// Inside this loop we remove all the folder without write/delete permissions
|
||||
// We remove all the folders that aren't GCS-ones, that we don't want to synchronize and
|
||||
// the ones without write/delete permissions.
|
||||
count = [folders count]-1;
|
||||
for (; count >= 0; count--)
|
||||
{
|
||||
if ([sm validatePermission: SoPerm_DeleteObjects
|
||||
onObject: [folders objectAtIndex: count]
|
||||
currentFolder = [folders objectAtIndex: count];
|
||||
|
||||
// We skip personal GCS folders - we always want to synchronize these
|
||||
if ([currentFolder isKindOfClass: [SOGoGCSFolder class]] &&
|
||||
[[currentFolder nameInContainer] isEqualToString: @"personal"])
|
||||
continue;
|
||||
|
||||
if (![currentFolder isKindOfClass: [SOGoGCSFolder class]] ||
|
||||
![currentFolder synchronize] ||
|
||||
[sm validatePermission: SoPerm_DeleteObjects
|
||||
onObject: currentFolder
|
||||
inContext: context] ||
|
||||
[sm validatePermission: SoPerm_AddDocumentsImagesAndFiles
|
||||
onObject: [folders objectAtIndex: count]
|
||||
onObject: currentFolder
|
||||
inContext: context])
|
||||
{
|
||||
[folders removeObjectAtIndex: count];
|
||||
|
@ -964,7 +999,6 @@ static BOOL debugOn = NO;
|
|||
}
|
||||
|
||||
count = [folders count]-1;
|
||||
NSString *operation;
|
||||
|
||||
for (fi = 0; fi <= count ; fi++)
|
||||
{
|
||||
|
@ -1022,7 +1056,9 @@ static BOOL debugOn = NO;
|
|||
[[o properties] removeObjectForKey: @"DateCache"];
|
||||
[[o properties] removeObjectForKey: @"MoreAvailable"];
|
||||
[[o properties] removeObjectForKey: @"BodyPreferenceType"];
|
||||
[[o properties] removeObjectForKey: @"SupportedElements"];
|
||||
[[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"];
|
||||
[[o properties] removeObjectForKey: @"InitialLoadSequence"];
|
||||
}
|
||||
|
||||
[o save];
|
||||
|
@ -1045,7 +1081,9 @@ static BOOL debugOn = NO;
|
|||
[[o properties] removeObjectForKey: @"DateCache"];
|
||||
[[o properties] removeObjectForKey: @"MoreAvailable"];
|
||||
[[o properties] removeObjectForKey: @"BodyPreferenceType"];
|
||||
[[o properties] removeObjectForKey: @"SupportedElements"];
|
||||
[[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"];
|
||||
[[o properties] removeObjectForKey: @"InitialLoadSequence"];
|
||||
}
|
||||
|
||||
[o save];
|
||||
|
@ -1204,7 +1242,7 @@ static BOOL debugOn = NO;
|
|||
filter = [NSCalendarDate dateFromFilterType: [[(id)[[allCollections objectAtIndex: j] getElementsByTagName: @"FilterType"] lastObject] textValue]];
|
||||
syncKey = [[(id)[[allCollections objectAtIndex: j] getElementsByTagName: @"SyncKey"] lastObject] textValue];
|
||||
|
||||
allMessages = [currentCollection syncTokenFieldsWithProperties: nil matchingSyncToken: syncKey fromDate: filter];
|
||||
allMessages = [currentCollection syncTokenFieldsWithProperties: nil matchingSyncToken: syncKey fromDate: filter initialLoad: NO];
|
||||
|
||||
count = [allMessages count];
|
||||
|
||||
|
@ -1254,7 +1292,7 @@ static BOOL debugOn = NO;
|
|||
- (void) processItemOperations: (id <DOMElement>) theDocumentElement
|
||||
inResponse: (WOResponse *) theResponse
|
||||
{
|
||||
NSString *fileReference, *realCollectionId, *serverId, *bodyPreferenceType, *collectionId;
|
||||
NSString *fileReference, *realCollectionId, *serverId, *bodyPreferenceType, *mimeSupport, *collectionId;
|
||||
NSMutableString *s;
|
||||
NSArray *fetchRequests;
|
||||
id aFetch;
|
||||
|
@ -1367,6 +1405,8 @@ static BOOL debugOn = NO;
|
|||
serverId = [[(id)[theDocumentElement getElementsByTagName: @"ServerId"] lastObject] textValue];
|
||||
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"];
|
||||
|
||||
currentCollection = [self collectionFromId: realCollectionId type: folderType];
|
||||
|
||||
|
@ -2024,13 +2064,13 @@ static BOOL debugOn = NO;
|
|||
|
||||
if ([foldersWithChanges count])
|
||||
{
|
||||
[self logWithFormat: @"Change detected, we push the content."];
|
||||
[self logWithFormat: @"Change detected using Ping, we let the EAS client know to send a Sync."];
|
||||
status = 2;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
[self logWithFormat: @"Sleeping %d seconds while detecting changes...", internalInterval];
|
||||
[self logWithFormat: @"Sleeping %d seconds while detecting changes in Ping...", internalInterval];
|
||||
sleep(internalInterval);
|
||||
}
|
||||
}
|
||||
|
@ -2094,6 +2134,123 @@ static BOOL debugOn = NO;
|
|||
[theResponse setContent: d];
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
#ifdef HAVE_OPENSSL
|
||||
- (unsigned int) validateCert: (NSString *) theCert
|
||||
{
|
||||
NSData *d;
|
||||
|
||||
const unsigned char *data;
|
||||
X509_STORE_CTX *ctx;
|
||||
X509_LOOKUP *lookup;
|
||||
X509_STORE *store;
|
||||
X509 *cert;
|
||||
|
||||
BOOL success;
|
||||
size_t len;
|
||||
int rc;
|
||||
|
||||
success = NO;
|
||||
|
||||
d = [theCert dataByDecodingBase64];
|
||||
data = (unsigned char *)[d bytes];
|
||||
len = [d length];
|
||||
|
||||
cert = d2i_X509(NULL, &data, len);
|
||||
if (!cert)
|
||||
{
|
||||
[self logWithFormat: @"EAS - validateCert failed for device %@: d2i_X509 failed", [context objectForKey: @"DeviceId"]];
|
||||
return 17;
|
||||
}
|
||||
|
||||
store = X509_STORE_new();
|
||||
OpenSSL_add_all_algorithms();
|
||||
|
||||
if (store)
|
||||
{
|
||||
lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file());
|
||||
if (lookup)
|
||||
{
|
||||
X509_LOOKUP_load_file(lookup, NULL, X509_FILETYPE_DEFAULT);
|
||||
lookup = X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir());
|
||||
if (lookup)
|
||||
{
|
||||
X509_LOOKUP_add_dir(lookup, NULL, X509_FILETYPE_DEFAULT);
|
||||
ERR_clear_error();
|
||||
success = YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!success)
|
||||
{
|
||||
if (store)
|
||||
{
|
||||
X509_STORE_free(store);
|
||||
store = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
ctx = X509_STORE_CTX_new();
|
||||
if (!ctx)
|
||||
{
|
||||
[self logWithFormat: @"EAS - validateCert failed for device %@: X509_STORE_CTX_new failed", [context objectForKey: @"DeviceId"]];
|
||||
return 17;
|
||||
}
|
||||
|
||||
if (X509_STORE_CTX_init(ctx, store, cert, NULL) != 1)
|
||||
{
|
||||
[self logWithFormat: @"EAS - validateCert failed for device %@: X509_STORE_CTX_init failed", [context objectForKey: @"DeviceId"]];
|
||||
X509_STORE_CTX_free(ctx);
|
||||
return 17;
|
||||
}
|
||||
|
||||
rc = X509_verify_cert(ctx);
|
||||
X509_STORE_CTX_free(ctx);
|
||||
X509_free(cert);
|
||||
|
||||
if (rc)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
[self logWithFormat: @"EAS - validateCert failed for device %@: err=%d", [context objectForKey: @"DeviceId"], X509_STORE_CTX_get_error(ctx)];
|
||||
return 17;
|
||||
}
|
||||
}
|
||||
#else
|
||||
- (unsigned int) validateCert: (NSString *) theCert
|
||||
{
|
||||
return 17;
|
||||
}
|
||||
#endif
|
||||
|
||||
- (void) processValidateCert: (id <DOMElement>) theDocumentElement
|
||||
inResponse: (WOResponse *) theResponse
|
||||
{
|
||||
NSMutableString *s;
|
||||
NSString *cert;
|
||||
NSData *d;
|
||||
|
||||
cert = [[(id)[theDocumentElement getElementsByTagName: @"Certificate"] lastObject] textValue];
|
||||
|
||||
s = [NSMutableString string];
|
||||
[s appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
|
||||
[s appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
|
||||
[s appendString: @"<ValidateCert xmlns=\"ValidateCert:\">"];
|
||||
[s appendString: @"<Status>1</Status><Certificate>"];
|
||||
[s appendFormat: @"<Status>%d</Status>", [self validateCert: cert]];
|
||||
[s appendString: @"</Certificate></ValidateCert>"];
|
||||
|
||||
d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
|
||||
|
||||
[theResponse setContent: d];
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// <?xml version="1.0"?>
|
||||
// <!DOCTYPE ActiveSync PUBLIC "-//MICROSOFT//DTD ActiveSync//EN" "http://www.microsoft.com/">
|
||||
|
@ -2410,38 +2567,99 @@ static BOOL debugOn = NO;
|
|||
NGMimeMessageParser *parser;
|
||||
NGMimeMessage *message;
|
||||
NSException *error;
|
||||
NSData *data;
|
||||
NGMutableHashMap *map;
|
||||
NGMimeMessage *messageToSend;
|
||||
NGMimeMessageGenerator *generator;
|
||||
NSMutableData *data;
|
||||
NSData *new_from_header;
|
||||
NSDictionary *identity;
|
||||
NSString *fullName, *email;
|
||||
|
||||
const char *bytes;
|
||||
int i, e, len;
|
||||
BOOL found_header;
|
||||
|
||||
// We get the mail's data
|
||||
data = [[[[(id)[theDocumentElement getElementsByTagName: @"MIME"] lastObject] textValue] stringByDecodingBase64] dataUsingEncoding: NSUTF8StringEncoding];
|
||||
data = [NSMutableData dataWithData: [[[[(id)[theDocumentElement getElementsByTagName: @"MIME"] lastObject] textValue] stringByDecodingBase64] dataUsingEncoding: NSUTF8StringEncoding]];
|
||||
|
||||
// We extract the recipients
|
||||
parser = [[NGMimeMessageParser alloc] init];
|
||||
message = [parser parsePartFromData: data];
|
||||
RELEASE(parser);
|
||||
|
||||
map = [NGHashMap hashMapWithDictionary: [message headers]];
|
||||
|
||||
identity = [[context activeUser] primaryIdentity];
|
||||
|
||||
fullName = [identity objectForKey: @"fullName"];
|
||||
email = [identity objectForKey: @"email"];
|
||||
|
||||
if ([fullName length])
|
||||
[map setObject: [NSString stringWithFormat: @"%@ <%@>", fullName, email] forKey: @"from"];
|
||||
new_from_header = [[NSString stringWithFormat: @"From: %@ <%@>\r\n", [fullName asQPSubjectString: @"utf-8"], email] dataUsingEncoding:NSUTF8StringEncoding];
|
||||
else
|
||||
[map setObject: email forKey: @"from"];
|
||||
new_from_header = [[NSString stringWithFormat: @"From: %@\r\n", email] dataUsingEncoding:NSUTF8StringEncoding];
|
||||
|
||||
messageToSend = [[[NGMimeMessage alloc] initWithHeader: map] autorelease];
|
||||
bytes = [data bytes];
|
||||
len = [data length];
|
||||
i = 0;
|
||||
found_header = NO;
|
||||
|
||||
[messageToSend setBody: [message body]];
|
||||
// Search for the from-header
|
||||
while (i < len)
|
||||
{
|
||||
if (i == 0 &&
|
||||
(*bytes == 'f' || *bytes == 'F') &&
|
||||
(*(bytes+1) == 'r' || *(bytes+1) == 'R') &&
|
||||
(*(bytes+2) == 'o' || *(bytes+2) == 'O') &&
|
||||
(*(bytes+3) == 'm' || *(bytes+3) == 'M') &&
|
||||
(*(bytes+4) == ':'))
|
||||
{
|
||||
found_header = YES;
|
||||
break;
|
||||
}
|
||||
|
||||
generator = [[[NGMimeMessageGenerator alloc] init] autorelease];
|
||||
data = [generator generateMimeFromPart: messageToSend];
|
||||
if (((*bytes == '\r') && (*(bytes+1) == '\n')) &&
|
||||
(*(bytes+2) == 'f' || *(bytes+2) == 'F') &&
|
||||
(*(bytes+3) == 'r' || *(bytes+3) == 'R') &&
|
||||
(*(bytes+4) == 'o' || *(bytes+4) == 'O') &&
|
||||
(*(bytes+5) == 'm' || *(bytes+5) == 'M') &&
|
||||
(*(bytes+6) == ':'))
|
||||
{
|
||||
found_header = YES;
|
||||
i = i + 2; // \r\n
|
||||
bytes = bytes + 2;
|
||||
break;
|
||||
}
|
||||
|
||||
bytes++;
|
||||
i++;
|
||||
}
|
||||
|
||||
// We search for the first \r\n AFTER the From: header to get the length of the string to replace.
|
||||
e = i;
|
||||
while (e < len)
|
||||
{
|
||||
if ((*bytes == '\r') && (*(bytes+1) == '\n'))
|
||||
{
|
||||
e = e + 2;
|
||||
break;
|
||||
}
|
||||
|
||||
bytes++;
|
||||
e++;
|
||||
}
|
||||
|
||||
// Update/Add the From header in the MIMEBody of the SendMail request.
|
||||
// Any other way to modify the mail body would break s/mime emails.
|
||||
if (found_header)
|
||||
{
|
||||
// Change the From header
|
||||
[data replaceBytesInRange: NSMakeRange(i, (NSUInteger)(e-i))
|
||||
withBytes: [new_from_header bytes]
|
||||
length: [new_from_header length]];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add a From header
|
||||
[data replaceBytesInRange: NSMakeRange(0, 0)
|
||||
withBytes: [new_from_header bytes]
|
||||
length: [new_from_header length]];
|
||||
}
|
||||
|
||||
error = [self _sendMail: data
|
||||
recipients: [message allRecipients]
|
||||
|
@ -2619,6 +2837,11 @@ static BOOL debugOn = NO;
|
|||
[map setObject: [mailObject messageId] forKey: @"in-reply-to"];
|
||||
|
||||
references = [[[[[mailObject mailHeaders] objectForKey: @"references"] componentsSeparatedByString: @" "] mutableCopy] autorelease];
|
||||
|
||||
// If there is no References: header, initialize it with In-Reply-To.
|
||||
if ([mailObject inReplyTo] && ![references count])
|
||||
references = [NSMutableArray arrayWithObject: [mailObject inReplyTo]];
|
||||
|
||||
if ([references count] > 0)
|
||||
{
|
||||
// If there are more than ten identifiers listed, we eliminate the second one.
|
||||
|
@ -2934,9 +3157,25 @@ static BOOL debugOn = NO;
|
|||
options: NSCaseInsensitiveSearch].location == NSNotFound)
|
||||
{
|
||||
NSString *value;
|
||||
#if GNUSTEP_BASE_MINOR_VERSION < 21
|
||||
value = [[NSDate date] descriptionWithCalendarFormat: @"%a, %d %b %Y %H:%M:%S %z"
|
||||
timeZone: [NSTimeZone timeZoneWithName: @"GMT"]
|
||||
locale: nil];
|
||||
#else
|
||||
value = [[NSDate date] descriptionWithCalendarFormat: @"%a, %d %b %Y %H:%M:%S %z"
|
||||
timeZone: [NSTimeZone timeZoneWithName: @"GMT"]
|
||||
locale: [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[NSArray arrayWithObjects: @"Jan", @"Feb", @"Mar", @"Apr",
|
||||
@"May", @"Jun", @"Jul", @"Aug",
|
||||
@"Sep", @"Oct", @"Nov", @"Dec", nil],
|
||||
@"NSShortMonthNameArray",
|
||||
[NSArray arrayWithObjects: @"Sun", @"Mon", @"Tue", @"Wed", @"Thu",
|
||||
@"Fri", @"Sat", nil],
|
||||
@"NSShortWeekDayNameArray",
|
||||
nil]];
|
||||
|
||||
value = [[NSDate date] descriptionWithCalendarFormat: @"%a, %d %b %Y %H:%M:%S %z" timeZone: [NSTimeZone timeZoneWithName: @"GMT"] locale: nil];
|
||||
s = [NSString stringWithFormat: @"Date: %@\n%@", value, [theRequest contentAsString]];
|
||||
#endif
|
||||
s = [NSString stringWithFormat: @"Date: %@\r\n%@", value, [theRequest contentAsString]];
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -3026,8 +3265,7 @@ static BOOL debugOn = NO;
|
|||
return nil;
|
||||
|
||||
urlString = [[user domainDefaults] folderInfoURL];
|
||||
parts = [[urlString componentsSeparatedByString: @"/"]
|
||||
mutableCopy];
|
||||
parts = [[urlString componentsSeparatedByString: @"/"] mutableCopy];
|
||||
[parts autorelease];
|
||||
if ([parts count] == 5)
|
||||
{
|
||||
|
|
|
@ -472,18 +472,46 @@ struct GlobalObjectId {
|
|||
return d;
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
- (BOOL) _sanitinizeNeeded: (NSArray *) theParts
|
||||
{
|
||||
NSString *encoding, *charset;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < [theParts count]; i++)
|
||||
{
|
||||
encoding = [[theParts objectAtIndex: i ] objectForKey: @"encoding"];
|
||||
charset = [[[theParts objectAtIndex: i ] objectForKey: @"parameterList"] objectForKey: @"charset"];
|
||||
if (encoding && [encoding caseInsensitiveCompare: @"8bit"] == NSOrderedSame &&
|
||||
charset && ![charset caseInsensitiveCompare: @"utf-8"] == NSOrderedSame
|
||||
&& ![charset caseInsensitiveCompare: @"us-ascii"] == NSOrderedSame)
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
if ([self _sanitinizeNeeded: [[theParts objectAtIndex: i ] objectForKey: @"parts"]])
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
- (NSData *) _preferredBodyDataUsingType: (int) theType
|
||||
mimeSupport: (int) theMimeSupport
|
||||
nativeType: (int *) theNativeType
|
||||
{
|
||||
NSString *type, *subtype, *encoding;
|
||||
NSData *d;
|
||||
BOOL isSMIME, sanitinizeNeeded;
|
||||
|
||||
type = [[[self bodyStructure] valueForKey: @"type"] lowercaseString];
|
||||
subtype = [[[self bodyStructure] valueForKey: @"subtype"] lowercaseString];
|
||||
|
||||
isSMIME = NO;
|
||||
d = nil;
|
||||
|
||||
// We determine the native type
|
||||
|
@ -494,8 +522,14 @@ struct GlobalObjectId {
|
|||
else if ([type isEqualToString: @"multipart"])
|
||||
*theNativeType = 4;
|
||||
|
||||
if (([subtype isEqualToString: @"signed"] || [subtype isEqualToString: @"pkcs7-mime"] ) && theMimeSupport > 0)
|
||||
{
|
||||
*theNativeType = 4;
|
||||
isSMIME = YES;
|
||||
}
|
||||
|
||||
// We get the right part based on the preference
|
||||
if (theType == 1 || theType == 2)
|
||||
if ((theType == 1 || theType == 2) && !isSMIME)
|
||||
{
|
||||
if ([type isEqualToString: @"text"] && ![subtype isEqualToString: @"calendar"])
|
||||
{
|
||||
|
@ -536,12 +570,12 @@ struct GlobalObjectId {
|
|||
d = [self _preferredBodyDataInMultipartUsingType: theType nativeTypeFound: theNativeType];
|
||||
}
|
||||
}
|
||||
else if (theType == 4)
|
||||
else if (theType == 4 || isSMIME)
|
||||
{
|
||||
// We sanitize the content *ONLY* for Outlook clients and if the content-transfer-encoding is 8bit. Outlook has strange issues
|
||||
// with quoted-printable/base64 encoded text parts. It just doesn't decode them.
|
||||
encoding = [[self lookupInfoForBodyPart: @""] objectForKey: @"encoding"];
|
||||
if ([[context objectForKey: @"DeviceType"] isEqualToString: @"WindowsOutlook15"] || ([encoding caseInsensitiveCompare: @"8bit"] == NSOrderedSame))
|
||||
// We sanitize the content if the content-transfer-encoding is 8bit and charset is not utf-8 or us-ascii.
|
||||
sanitinizeNeeded = [self _sanitinizeNeeded: [NSArray arrayWithObject: [self bodyStructure]]];
|
||||
|
||||
if (sanitinizeNeeded && !isSMIME)
|
||||
d = [self _sanitizedMIMEMessage];
|
||||
else
|
||||
d = [self content];
|
||||
|
@ -656,16 +690,18 @@ struct GlobalObjectId {
|
|||
{
|
||||
NSData *d, *globalObjId;
|
||||
NSArray *attachmentKeys;
|
||||
iCalCalendar *calendar;
|
||||
NSString *p, *subtype;
|
||||
NSMutableString *s;
|
||||
|
||||
uint32_t v;
|
||||
NSString *p;
|
||||
|
||||
id value;
|
||||
|
||||
iCalCalendar *calendar;
|
||||
int preferredBodyType, mimeSupport, nativeBodyType;
|
||||
uint32_t v;
|
||||
|
||||
int preferredBodyType, nativeBodyType;
|
||||
subtype = [[[self bodyStructure] valueForKey: @"subtype"] lowercaseString];
|
||||
|
||||
preferredBodyType = [[context objectForKey: @"BodyPreferenceType"] intValue];
|
||||
mimeSupport = [[context objectForKey: @"MIMESupport"] intValue];
|
||||
|
||||
s = [NSMutableString string];
|
||||
|
||||
|
@ -862,6 +898,11 @@ struct GlobalObjectId {
|
|||
else
|
||||
{
|
||||
// MesssageClass and ContentClass
|
||||
if ([subtype isEqualToString: @"signed"])
|
||||
[s appendFormat: @"<MessageClass xmlns=\"Email:\">%@</MessageClass>", @"IPM.Note.SMIME.MultipartSigned"];
|
||||
else if ([subtype isEqualToString: @"pkcs7-mime"])
|
||||
[s appendFormat: @"<MessageClass xmlns=\"Email:\">%@</MessageClass>", @"IPM.Note.SMIME"];
|
||||
else
|
||||
[s appendFormat: @"<MessageClass xmlns=\"Email:\">%@</MessageClass>", @"IPM.Note"];
|
||||
[s appendFormat: @"<ContentClass xmlns=\"Email:\">%@</ContentClass>", @"urn:content-classes:message"];
|
||||
}
|
||||
|
@ -876,10 +917,8 @@ struct GlobalObjectId {
|
|||
[s appendFormat: @"<InternetCPID xmlns=\"Email:\">%@</InternetCPID>", @"65001"];
|
||||
|
||||
// Body - namespace 17
|
||||
preferredBodyType = [[context objectForKey: @"BodyPreferenceType"] intValue];
|
||||
|
||||
nativeBodyType = 1;
|
||||
d = [self _preferredBodyDataUsingType: preferredBodyType nativeType: &nativeBodyType];
|
||||
d = [self _preferredBodyDataUsingType: preferredBodyType mimeSupport: mimeSupport nativeType: &nativeBodyType];
|
||||
|
||||
if (calendar && !d)
|
||||
{
|
||||
|
@ -981,9 +1020,12 @@ struct GlobalObjectId {
|
|||
{
|
||||
[s appendString: @"<Body xmlns=\"AirSyncBase:\">"];
|
||||
|
||||
// Set the correct type if client requested text/html but we got text/plain
|
||||
// Set the correct type if client requested text/html but we got text/plain.
|
||||
// For s/mime mails type is always 4 if mimeSupport is 1 or 2.
|
||||
if (preferredBodyType == 2 && nativeBodyType == 1)
|
||||
[s appendString: @"<Type>1</Type>"];
|
||||
else if (([subtype isEqualToString: @"signed"] || [subtype isEqualToString: @"pkcs7-mime"] ) && mimeSupport > 0)
|
||||
[s appendString: @"<Type>4</Type>"];
|
||||
else
|
||||
[s appendFormat: @"<Type>%d</Type>", preferredBodyType];
|
||||
|
||||
|
@ -1001,7 +1043,8 @@ struct GlobalObjectId {
|
|||
|
||||
// Attachments -namespace 16
|
||||
attachmentKeys = [self fetchFileAttachmentKeys];
|
||||
if ([attachmentKeys count])
|
||||
|
||||
if ([attachmentKeys count] && !([subtype isEqualToString: @"signed"]))
|
||||
{
|
||||
int i;
|
||||
|
||||
|
@ -1071,9 +1114,15 @@ struct GlobalObjectId {
|
|||
if ([[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"14.0"] ||
|
||||
[[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"14.1"])
|
||||
{
|
||||
id value;
|
||||
NSString *reference;
|
||||
|
||||
reference = [[[[self mailHeaders] objectForKey: @"references"] componentsSeparatedByString: @" "] objectAtIndex: 0];
|
||||
value = [[self mailHeaders] objectForKey: @"references"];
|
||||
|
||||
if ([value isKindOfClass: [NSArray class]])
|
||||
reference = [[[value objectAtIndex: 0] componentsSeparatedByString: @" "] objectAtIndex: 0];
|
||||
else
|
||||
reference = [[value componentsSeparatedByString: @" "] objectAtIndex: 0];
|
||||
|
||||
if ([reference length] > 0)
|
||||
[s appendFormat: @"<ConversationId xmlns=\"Email2:\">%@</ConversationId>", [[reference dataUsingEncoding: NSUTF8StringEncoding] activeSyncRepresentationInContext: context]];
|
||||
|
|
|
@ -37,6 +37,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
||||
@interface SOGoSyncCacheObject : NSObject
|
||||
{
|
||||
@public
|
||||
id _uid;
|
||||
id _sequence;
|
||||
}
|
||||
|
|
|
@ -32,9 +32,17 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
||||
#import <Foundation/NSNull.h>
|
||||
#import <Foundation/NSString.h>
|
||||
#import <Foundation/NSValue.h>
|
||||
|
||||
static Class NSNullK;
|
||||
|
||||
@implementation SOGoSyncCacheObject
|
||||
|
||||
+ (void) initialize
|
||||
{
|
||||
NSNullK = [NSNull class];
|
||||
}
|
||||
|
||||
- (id) init
|
||||
{
|
||||
if ((self = [super init]))
|
||||
|
@ -46,14 +54,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
return self;
|
||||
}
|
||||
|
||||
+ (id) syncCacheObjectWithUID: (id) theUID sequence: (id) theSequence;
|
||||
+ (id) syncCacheObjectWithUID: (id) theUID sequence: (id) theSequence
|
||||
{
|
||||
id o;
|
||||
|
||||
o = [[self alloc] init];
|
||||
|
||||
[o setUID: theUID];
|
||||
[o setSequence: theSequence];
|
||||
[o setUID: [NSNumber numberWithInt: [theUID intValue]]];
|
||||
[o setSequence: ([theSequence isKindOfClass: NSNullK] ? theSequence : [NSNumber numberWithInt: [theSequence intValue]])];
|
||||
|
||||
return [o autorelease];
|
||||
}
|
||||
|
@ -67,7 +75,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
||||
- (id) uid
|
||||
{
|
||||
return _uid;
|
||||
return [_uid description];
|
||||
}
|
||||
|
||||
- (void) setUID: (id) theUID
|
||||
|
@ -77,7 +85,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
||||
- (id) sequence
|
||||
{
|
||||
return _sequence;
|
||||
return ([_sequence isKindOfClass: NSNullK] ? _sequence : [_sequence description]);
|
||||
}
|
||||
|
||||
- (void) setSequence: (id) theSequence
|
||||
|
@ -88,7 +96,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
||||
- (NSComparisonResult) compareUID: (SOGoSyncCacheObject *) theSyncCacheObject
|
||||
{
|
||||
return [[self uid] compare: [theSyncCacheObject uid]];
|
||||
return [self->_uid compare: theSyncCacheObject->_uid];
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -97,21 +105,21 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
//
|
||||
- (NSComparisonResult) compareSequence: (SOGoSyncCacheObject *) theSyncCacheObject
|
||||
{
|
||||
if ([[self sequence] isEqual: [NSNull null]] &&
|
||||
[[theSyncCacheObject sequence] isEqual: [NSNull null]])
|
||||
if ([self->_sequence isEqual: [NSNull null]] &&
|
||||
[theSyncCacheObject->_sequence isEqual: [NSNull null]])
|
||||
return [self compareUID: theSyncCacheObject];
|
||||
|
||||
if (![[self sequence] isEqual: [NSNull null]] && [[theSyncCacheObject sequence] isEqual: [NSNull null]])
|
||||
if (![self->_sequence isEqual: [NSNull null]] && [theSyncCacheObject->_sequence isEqual: [NSNull null]])
|
||||
return NSOrderedDescending;
|
||||
|
||||
if ([[self sequence] isEqual: [NSNull null]] && ![[theSyncCacheObject sequence] isEqual: [NSNull null]])
|
||||
if ([self->_sequence isEqual: [NSNull null]] && ![theSyncCacheObject->_sequence isEqual: [NSNull null]])
|
||||
return NSOrderedAscending;
|
||||
|
||||
// Must check this here, to avoid comparing NSNull objects
|
||||
if ([[self sequence] compare: [theSyncCacheObject sequence]] == NSOrderedSame)
|
||||
if ([self->_sequence compare: theSyncCacheObject->_sequence] == NSOrderedSame)
|
||||
return [self compareUID: theSyncCacheObject];
|
||||
|
||||
return [[self sequence] compare: [theSyncCacheObject sequence]];
|
||||
return [self->_sequence compare: theSyncCacheObject->_sequence];
|
||||
}
|
||||
|
||||
- (NSString *) description
|
||||
|
@ -120,3 +128,4 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
}
|
||||
|
||||
@end
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
||||
// don't send negative reminder - not supported
|
||||
if (delta > 0)
|
||||
[s appendFormat: @"<Reminder xmlns=\"Calendar:\">%d</Reminder>", delta];
|
||||
[s appendFormat: @"<Reminder xmlns=\"Calendar:\">%d</Reminder>", (int)delta];
|
||||
}
|
||||
|
||||
return s;
|
||||
|
|
|
@ -38,6 +38,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
#import <Foundation/NSTimeZone.h>
|
||||
|
||||
#import <NGExtensions/NSString+misc.h>
|
||||
#import <NGExtensions/NSCalendarDate+misc.h>
|
||||
#import <NGExtensions/NSObject+Logs.h>
|
||||
#import <NGObjWeb/WOContext.h>
|
||||
#import <NGObjWeb/WOContext+SoObjects.h>
|
||||
#import <NGObjWeb/WORequest.h>
|
||||
|
@ -45,11 +47,16 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
#import <NGCards/iCalCalendar.h>
|
||||
#import <NGCards/iCalDateTime.h>
|
||||
#import <NGCards/iCalPerson.h>
|
||||
#import <NGCards/NSCalendarDate+NGCards.h>
|
||||
#import <NGCards/NSString+NGCards.h>
|
||||
#import <NGCards/iCalCalendar.h>
|
||||
|
||||
#import <SOGo/SOGoUser.h>
|
||||
#import <SOGo/SOGoUserDefaults.h>
|
||||
#import <SOGo/WORequest+SOGo.h>
|
||||
|
||||
#import <Appointments/iCalEntityObject+SOGo.h>
|
||||
#import <Appointments/iCalRepeatableEntityObject+SOGo.h>
|
||||
|
||||
#include "iCalAlarm+ActiveSync.h"
|
||||
#include "iCalRecurrenceRule+ActiveSync.h"
|
||||
|
@ -98,10 +105,13 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
else if ([self created])
|
||||
[s appendFormat: @"<DTStamp xmlns=\"Calendar:\">%@</DTStamp>", [[self created] activeSyncRepresentationWithoutSeparatorsInContext: context]];
|
||||
|
||||
// Timezone
|
||||
tz = [(iCalDateTime *)[self firstChildWithTag: @"dtstart"] timeZone];
|
||||
|
||||
// StartTime -- http://msdn.microsoft.com/en-us/library/ee157132(v=exchg.80).aspx
|
||||
if ([self startDate])
|
||||
{
|
||||
if ([self isAllDay])
|
||||
if ([self isAllDay] && !tz)
|
||||
[s appendFormat: @"<StartTime xmlns=\"Calendar:\">%@</StartTime>",
|
||||
[[[self startDate] dateByAddingYears: 0 months: 0 days: 0
|
||||
hours: 0 minutes: 0
|
||||
|
@ -114,7 +124,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])
|
||||
if ([self isAllDay] && !tz)
|
||||
[s appendFormat: @"<EndTime xmlns=\"Calendar:\">%@</EndTime>",
|
||||
[[[self endDate] dateByAddingYears: 0 months: 0 days: 0
|
||||
hours: 0 minutes: 0
|
||||
|
@ -124,9 +134,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
[s appendFormat: @"<EndTime xmlns=\"Calendar:\">%@</EndTime>", [[self endDate] activeSyncRepresentationWithoutSeparatorsInContext: context]];
|
||||
}
|
||||
|
||||
// Timezone
|
||||
tz = [(iCalDateTime *)[self firstChildWithTag: @"dtstart"] timeZone];
|
||||
|
||||
if (!tz)
|
||||
tz = [iCalTimeZone timeZoneForName: [userTimeZone name]];
|
||||
|
||||
|
@ -226,7 +233,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
//[s appendFormat: @"<Importance xmlns=\"Calendar:\">%d</Importance>", v];
|
||||
|
||||
// UID -- http://msdn.microsoft.com/en-us/library/ee159919(v=exchg.80).aspx
|
||||
if ([[self uid] length])
|
||||
if (![self recurrenceId] && [[self uid] length])
|
||||
[s appendFormat: @"<UID xmlns=\"Calendar:\">%@</UID>", [self uid]];
|
||||
|
||||
// Sensitivity
|
||||
|
@ -251,7 +258,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
[s appendFormat: @"</Categories>"];
|
||||
}
|
||||
|
||||
|
||||
// Reminder -- http://msdn.microsoft.com/en-us/library/ee219691(v=exchg.80).aspx
|
||||
// TODO: improve this to handle more alarm types
|
||||
if ([self hasAlarms])
|
||||
|
@ -265,7 +271,74 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
// Recurrence rules
|
||||
if ([self isRecurrent])
|
||||
{
|
||||
NSMutableArray *components, *exdates;
|
||||
iCalEvent *current_component;
|
||||
NSString *recurrence_id;
|
||||
|
||||
unsigned int count, max, i;
|
||||
|
||||
[s appendString: [[[self recurrenceRules] lastObject] activeSyncRepresentationInContext: context]];
|
||||
|
||||
components = [NSMutableArray arrayWithArray: [[self parent] events]];
|
||||
max = [components count];
|
||||
|
||||
if (max > 1 || [self hasExceptionDates])
|
||||
{
|
||||
exdates = [NSMutableArray arrayWithArray: [self exceptionDates]];
|
||||
|
||||
[s appendString: @"<Exceptions xmlns=\"Calendar:\">"];
|
||||
|
||||
for (count = 1; count < max; count++)
|
||||
{
|
||||
current_component = [components objectAtIndex: count] ;
|
||||
|
||||
if ([self isAllDay])
|
||||
{
|
||||
recurrence_id = [NSString stringWithFormat: @"%@",
|
||||
[[[current_component recurrenceId] dateByAddingYears: 0 months: 0 days: 0
|
||||
hours: 0 minutes: 0
|
||||
seconds: -[userTimeZone secondsFromGMTForDate:
|
||||
[current_component recurrenceId]]]
|
||||
activeSyncRepresentationWithoutSeparatorsInContext: context]];
|
||||
}
|
||||
else
|
||||
{
|
||||
recurrence_id = [NSString stringWithFormat: @"%@", [[current_component recurrenceId]
|
||||
activeSyncRepresentationWithoutSeparatorsInContext: context]];
|
||||
}
|
||||
|
||||
[s appendString: @"<Exception>"];
|
||||
[s appendFormat: @"<Exception_StartTime xmlns=\"Calendar:\">%@</Exception_StartTime>", recurrence_id];
|
||||
[s appendFormat: @"%@", [current_component activeSyncRepresentationInContext: context]];
|
||||
[s appendString: @"</Exception>"];
|
||||
}
|
||||
|
||||
for (i = 0; i < [exdates count]; i++)
|
||||
{
|
||||
[s appendString: @"<Exception>"];
|
||||
[s appendString: @"<Exception_Deleted>1</Exception_Deleted>"];
|
||||
|
||||
if ([self isAllDay])
|
||||
{
|
||||
recurrence_id = [NSString stringWithFormat: @"%@",
|
||||
[[[[exdates objectAtIndex: i] asCalendarDate] dateByAddingYears: 0 months: 0 days: 0
|
||||
hours: 0 minutes: 0
|
||||
seconds: -[userTimeZone secondsFromGMTForDate:
|
||||
[[exdates objectAtIndex: i] asCalendarDate]]]
|
||||
activeSyncRepresentationWithoutSeparatorsInContext: context]];
|
||||
}
|
||||
else
|
||||
{
|
||||
recurrence_id = [NSString stringWithFormat: @"%@", [[[exdates objectAtIndex: i] asCalendarDate]
|
||||
activeSyncRepresentationWithoutSeparatorsInContext: context]];
|
||||
}
|
||||
|
||||
[s appendFormat: @"<Exception_StartTime xmlns=\"Calendar:\">%@</Exception_StartTime>", recurrence_id];
|
||||
[s appendString: @"</Exception>"];
|
||||
}
|
||||
|
||||
[s appendString: @"</Exceptions>"];
|
||||
}
|
||||
}
|
||||
|
||||
// Comment
|
||||
|
@ -285,12 +358,13 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
{
|
||||
[s appendString: @"<Body xmlns=\"AirSyncBase:\">"];
|
||||
[s appendFormat: @"<Type>%d</Type>", 1];
|
||||
[s appendFormat: @"<EstimatedDataSize>%d</EstimatedDataSize>", [o length]];
|
||||
[s appendFormat: @"<EstimatedDataSize>%d</EstimatedDataSize>", (int)[o length]];
|
||||
[s appendFormat: @"<Data>%@</Data>", o];
|
||||
[s appendString: @"</Body>"];
|
||||
}
|
||||
}
|
||||
|
||||
if (![self recurrenceId])
|
||||
[s appendFormat: @"<NativeBodyType xmlns=\"AirSyncBase:\">%d</NativeBodyType>", 1];
|
||||
|
||||
return s;
|
||||
|
@ -337,13 +411,19 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
- (void) takeActiveSyncValues: (NSDictionary *) theValues
|
||||
inContext: (WOContext *) context
|
||||
{
|
||||
iCalDateTime *start, *end;
|
||||
iCalDateTime *start, *end; //, *oldstart;
|
||||
NSCalendarDate *oldstart;
|
||||
NSTimeZone *userTimeZone;
|
||||
iCalTimeZone *tz;
|
||||
id o;
|
||||
int deltasecs;
|
||||
|
||||
BOOL isAllDay;
|
||||
|
||||
NSMutableArray *occurences;
|
||||
|
||||
occurences = [NSMutableArray arrayWithArray: [[self parent] events]];
|
||||
|
||||
if ((o = [theValues objectForKey: @"UID"]))
|
||||
[self setUid: o];
|
||||
|
||||
|
@ -356,6 +436,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
{
|
||||
isAllDay = YES;
|
||||
}
|
||||
else if ([occurences count] && !([theValues objectForKey: @"AllDayEvent"]))
|
||||
{
|
||||
// If the occurence has no AllDay tag use it from master.
|
||||
isAllDay = [[occurences objectAtIndex: 0] isAllDay];
|
||||
}
|
||||
|
||||
//
|
||||
// 0- free, 1- tentative, 2- busy and 3- out of office
|
||||
|
@ -422,6 +507,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
{
|
||||
o = [o calendarDate];
|
||||
start = (iCalDateTime *) [self uniqueChildWithTag: @"dtstart"];
|
||||
oldstart = [start dateTime];
|
||||
[start setTimeZone: tz];
|
||||
|
||||
if (isAllDay)
|
||||
|
@ -433,6 +519,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
{
|
||||
[start setDateTime: o];
|
||||
}
|
||||
|
||||
// Calculate delta if start date has been changed.
|
||||
if (oldstart)
|
||||
deltasecs = [[start dateTime ] timeIntervalSinceDate: oldstart] * -1;
|
||||
else
|
||||
deltasecs = 0;
|
||||
}
|
||||
|
||||
if ((o = [theValues objectForKey: @"EndTime"]))
|
||||
|
@ -491,6 +583,177 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
RELEASE(rule);
|
||||
|
||||
[rule takeActiveSyncValues: o inContext: context];
|
||||
|
||||
// Exceptions
|
||||
if ((o = [theValues objectForKey: @"Exceptions"]) && [o isKindOfClass: [NSArray class]])
|
||||
{
|
||||
NSCalendarDate *recurrenceId, *adjustedRecurrenceId, *currentId;
|
||||
NSMutableArray *exdates, *exceptionstouched;
|
||||
iCalEvent *currentOccurence;
|
||||
NSDictionary *exception;
|
||||
|
||||
unsigned int i, count, max;
|
||||
|
||||
[self removeAllExceptionDates];
|
||||
|
||||
exdates = [NSMutableArray array];
|
||||
exceptionstouched = [NSMutableArray array];
|
||||
|
||||
for (i = 0; i < [o count]; i++)
|
||||
{
|
||||
exception = [o objectAtIndex: i];
|
||||
|
||||
if (![[exception objectForKey: @"Exception_Deleted"] intValue])
|
||||
{
|
||||
recurrenceId = [[exception objectForKey: @"Exception_StartTime"] asCalendarDate];
|
||||
|
||||
if (isAllDay)
|
||||
recurrenceId = [recurrenceId dateByAddingYears: 0 months: 0 days: 0
|
||||
hours: 0 minutes: 0
|
||||
seconds: [userTimeZone secondsFromGMTForDate:recurrenceId]];
|
||||
|
||||
// When moving the calendar entry (i.e. changing the startDate) iOS and Android sometimes send a value for
|
||||
// Exception_StartTime which is not what we expect.
|
||||
// With this we make sure the recurrenceId is always correct.
|
||||
//
|
||||
// iOS problem - If the master is a no-allday-event but the exception is, an invalid Exception_StartTime is in the request.
|
||||
// e.g. it sends 20150409T040000Z instead of 20150409T060000Z;
|
||||
// This might be a special case since iPhone doesn't allow an allday-exception on a non-allday-event.
|
||||
if (!([[start dateTime] compare: [[start dateTime] hour:[recurrenceId hourOfDay] minute:[recurrenceId minuteOfHour]]] == NSOrderedSame))
|
||||
recurrenceId = [recurrenceId hour:[[start dateTime] hourOfDay] minute:[[start dateTime] minuteOfHour]];
|
||||
|
||||
// We need to store the recurrenceIds and exception dates adjusted by deltasecs.
|
||||
// This ensures that the adjustment in SOGoCalendarComponent->updateComponent->_updateRecurrenceIDsWithEvent gives the correct dates.
|
||||
adjustedRecurrenceId = [recurrenceId dateByAddingYears: 0 months: 0 days: 0
|
||||
hours: 0 minutes: 0 seconds: deltasecs];
|
||||
|
||||
// search for an existing occurence and update it
|
||||
max = [occurences count];
|
||||
currentOccurence = nil;
|
||||
count = 1;
|
||||
while (count < max)
|
||||
{
|
||||
currentOccurence = [occurences objectAtIndex: count] ;
|
||||
currentId = [currentOccurence recurrenceId];
|
||||
|
||||
if ([currentId compare: adjustedRecurrenceId] == NSOrderedSame)
|
||||
{
|
||||
[exceptionstouched addObject: adjustedRecurrenceId];
|
||||
[currentOccurence takeActiveSyncValues: exception inContext: context];
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
count++;
|
||||
currentOccurence = nil;
|
||||
}
|
||||
|
||||
// Create a new occurence if we found none to update.
|
||||
if (!currentOccurence)
|
||||
{
|
||||
iCalDateTime *recid;
|
||||
|
||||
currentOccurence = [self mutableCopy];
|
||||
[currentOccurence removeAllRecurrenceRules];
|
||||
[currentOccurence removeAllExceptionRules];
|
||||
[currentOccurence removeAllExceptionDates];
|
||||
|
||||
[[self parent] addToEvents: currentOccurence];
|
||||
[currentOccurence takeActiveSyncValues: exception inContext: context];
|
||||
|
||||
recid = (iCalDateTime *)[currentOccurence uniqueChildWithTag: @"recurrence-id"];
|
||||
if (isAllDay)
|
||||
[recid setDate: adjustedRecurrenceId];
|
||||
else
|
||||
[recid setDateTime: adjustedRecurrenceId];
|
||||
|
||||
[exceptionstouched addObject: [recid dateTime]];
|
||||
}
|
||||
}
|
||||
else if ([[exception objectForKey: @"Exception_Deleted"] intValue])
|
||||
{
|
||||
recurrenceId = [[exception objectForKey: @"Exception_StartTime"] asCalendarDate];
|
||||
|
||||
if (isAllDay)
|
||||
recurrenceId = [recurrenceId dateByAddingYears: 0 months: 0
|
||||
days: 0 hours: 0 minutes: 0
|
||||
seconds: [userTimeZone secondsFromGMTForDate:recurrenceId]];
|
||||
|
||||
// We add only valid exception dates.
|
||||
if ([self doesOccurOnDate: recurrenceId] &&
|
||||
([[start dateTime] compare: [[start dateTime] hour:[recurrenceId hourOfDay]
|
||||
minute:[recurrenceId minuteOfHour]]] == NSOrderedSame))
|
||||
{
|
||||
// We need to store the recurrenceIds and exception dates adjusted by deltasecs.
|
||||
// This ensures that the adjustment in SOGoCalendarComponent->updateComponent->_updateRecurrenceIDsWithEvent gives the correct dates.
|
||||
[exdates addObject: [recurrenceId dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: deltasecs]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update exception dates in master event.
|
||||
max = [exdates count];
|
||||
if (max > 0)
|
||||
{
|
||||
for (count = 0; count < max; count++)
|
||||
{
|
||||
if (([exceptionstouched indexOfObject: [exdates objectAtIndex: count]] == NSNotFound))
|
||||
[self addToExceptionDates: [exdates objectAtIndex: count]];
|
||||
}
|
||||
}
|
||||
|
||||
// Remove all exceptions included in the request.
|
||||
max = [occurences count];
|
||||
count = 1; // skip the master event
|
||||
while (count < max)
|
||||
{
|
||||
currentOccurence = [occurences objectAtIndex: count] ;
|
||||
currentId = [currentOccurence recurrenceId];
|
||||
|
||||
// Delete all occurences which are not touched (modified/added).
|
||||
if (([exceptionstouched indexOfObject: currentId] == NSNotFound))
|
||||
[[[self parent] children] removeObject: currentOccurence];
|
||||
|
||||
count++;
|
||||
}
|
||||
}
|
||||
else if ([self isRecurrent])
|
||||
{
|
||||
// We have no exceptions in request but there is a recurrence rule.
|
||||
// Remove all excpetions and exception dates.
|
||||
iCalEvent *currentOccurence;
|
||||
unsigned int count, max;
|
||||
|
||||
[self removeAllExceptionDates];
|
||||
max = [occurences count];
|
||||
count = 1; // skip the master event
|
||||
while (count < max)
|
||||
{
|
||||
currentOccurence = [occurences objectAtIndex: count];
|
||||
[[[self parent] children] removeObject: currentOccurence];
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ([self isRecurrent])
|
||||
{
|
||||
// We have no recurrence rule in request.
|
||||
// Remove all exceptions, exception dates and recurrence rules.
|
||||
iCalEvent *currentOccurence;
|
||||
unsigned int count, max;
|
||||
|
||||
[self removeAllRecurrenceRules];
|
||||
[self removeAllExceptionRules];
|
||||
[self removeAllExceptionDates];
|
||||
|
||||
max = [occurences count];
|
||||
count = 1; // skip the master event
|
||||
while (count < max)
|
||||
{
|
||||
currentOccurence = [occurences objectAtIndex: count];
|
||||
[[[self parent] children] removeObject: currentOccurence];
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
// Organizer - we don't touch the value unless we're the organizer
|
||||
|
|
|
@ -140,7 +140,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
// Simple reccurrence rule of type "Monthly"
|
||||
type = 2;
|
||||
[s appendFormat: @"<Recurrence_DayOfMonth xmlns=\"Calendar:\">%d</Recurrence_DayOfMonth>",
|
||||
[[[self parent] startDate] dayOfMonth]];
|
||||
(int)[[[self parent] startDate] dayOfMonth]];
|
||||
}
|
||||
}
|
||||
else if ([self frequency] == iCalRecurrenceFrequenceYearly)
|
||||
|
|
|
@ -140,7 +140,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
{
|
||||
[s appendString: @"<Body xmlns=\"AirSyncBase:\">"];
|
||||
[s appendFormat: @"<Type>%d</Type>", 1];
|
||||
[s appendFormat: @"<EstimatedDataSize>%d</EstimatedDataSize>", [o length]];
|
||||
[s appendFormat: @"<EstimatedDataSize>%d</EstimatedDataSize>", (int)[o length]];
|
||||
[s appendFormat: @"<Data>%@</Data>", o];
|
||||
[s appendString: @"</Body>"];
|
||||
}
|
||||
|
|
|
@ -170,7 +170,7 @@ supported by SOGo:
|
|||
* Red Hat Enterprise Linux (RHEL) Server 5, 6 and 7
|
||||
* Community ENTerprise Operating System (CentOS) 5, 6 and 7
|
||||
* Debian GNU/Linux 6.0 (Squeeze) to 8.0 (Jessie)
|
||||
* Ubuntu 10.04 (Lucid) to 14.04 (Trusty)
|
||||
* Ubuntu 12.04 (Precise) to 14.04 (Trusty)
|
||||
|
||||
Make sure the required components are started automatically at boot time
|
||||
and that they are running before proceeding with the SOGo configuration.
|
||||
|
|
|
@ -233,6 +233,7 @@ Installation
|
|||
This section will guide you through the installation of the native
|
||||
Microsoft Outlook compatibility layer SOGo offers.
|
||||
|
||||
////
|
||||
Red Hat Enterprise Linux v6 x86_64
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
@ -316,7 +317,7 @@ from this guide.
|
|||
On Ubuntu 12.04, the Samba init scripts need to be modified to
|
||||
disable the upstart check. For more details, refer to:
|
||||
https://wiki.samba.org/index.php/Samba4/InitScript
|
||||
|
||||
////
|
||||
|
||||
Debian 8 (Jessie) and Ubuntu 14.04 (Trusty Tahr)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
@ -387,7 +388,7 @@ Run the following commands as root:
|
|||
|
||||
----
|
||||
samba-tool domain provision --realm=example.com \
|
||||
--domain=OPENCHANGE \
|
||||
--domain=EXAMPLE \
|
||||
--adminpass='%1OpenChange' \
|
||||
--server-role='domain controller'
|
||||
|
||||
|
@ -395,7 +396,7 @@ samba-tool user setexpiry administrator --noexpiry
|
|||
----
|
||||
|
||||
You might consider changing the realm and domain used, to suit your
|
||||
enviroment.
|
||||
environment.
|
||||
|
||||
You might also have to
|
||||
remove `/etc/samba/smb.conf` prior running this command.
|
||||
|
@ -441,7 +442,7 @@ Your Samba 4 configuration file should look like this:
|
|||
OpenChange Configuration
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
OpenChange 2.2 stores its metadata in MySQL so you need to have it installed.
|
||||
Since v2.2, OpenChange stores its metadata in MySQL so you need to have it installed.
|
||||
|
||||
First, create the OpenChange MySQL user:
|
||||
|
||||
|
@ -512,9 +513,11 @@ mapistore:indexing_backend = mysql://openchange-user:openchange$123@localhost/op
|
|||
mapiproxy:openchangedb = mysql://openchange-user:openchange$123@localhost/openchange
|
||||
----
|
||||
|
||||
////
|
||||
On RHEL, make sure SELinux is disabled:
|
||||
|
||||
setenforce 0
|
||||
////
|
||||
|
||||
Next, you can start Samba using the usual command:
|
||||
|
||||
|
@ -545,9 +548,11 @@ This service runs as a WSGI application under apache (mod_wsgi).
|
|||
While HTTPS is not required to access this service, it is strongly
|
||||
recommended.
|
||||
|
||||
////
|
||||
On RHEL-based distributions, the apache configuration required by these
|
||||
services can be found in `/etc/httpd/conf.d/ocsmanager.conf` and
|
||||
`/etc/httpd/conf.d/rpcproxy.conf`.
|
||||
////
|
||||
|
||||
For Debian-based distributions, these files can be found
|
||||
in `/etc/apache2/conf.d/` or `/etc/apache2/conf-available`.
|
||||
|
@ -563,8 +568,10 @@ The OCS Manager and RPC Proxy configuration module can be enabled using:
|
|||
a2enconf ocsmanager
|
||||
a2enconf rpcproxy
|
||||
|
||||
////
|
||||
On RHEL-based distributions, make sure the `LoadModule` directive is
|
||||
uncommented in `/etc/httpd/conf.d/wsgi.conf`.
|
||||
////
|
||||
|
||||
The _reqtimeout_ apache module is known to cause problems when using the
|
||||
default configuration shipped with Debian-based systems. On such
|
||||
|
@ -580,10 +587,12 @@ To avoid this problem, use a much higher timeout or disable the module:
|
|||
a2dismod reqtimeout
|
||||
|
||||
You should now restart the Apache service and make sure it will start on
|
||||
boot. On RHEL-based distributions, do:
|
||||
boot.
|
||||
////
|
||||
On RHEL-based distributions, do:
|
||||
|
||||
chkconfig httpd on && /etc/init.d/httpd restart
|
||||
|
||||
////
|
||||
On Debian-based distributions, do:
|
||||
|
||||
update-rc.d apache2 defaults && /etc/init.d/apache2 restart
|
||||
|
@ -809,11 +818,10 @@ can be ignored for now. This feature is currently not supported.
|
|||
remove any data associated with the user from the SOGo server and
|
||||
recreate a Microsoft Outlook profile.
|
||||
To remove any data associated to a user, use
|
||||
the `openchange_user_cleanup` script distributed with SOGo. The script
|
||||
can be found in `/usr/share/doc/sogo/` (`/usr/share/sogo-VERSION/` on
|
||||
RHEL).
|
||||
the `openchange_user_cleanup` script distributed with OpenChange. The script
|
||||
can be found in `/usr/share/openchange/`.
|
||||
To reset a user, run the script as root:
|
||||
`python openchange_user_cleanup username`. See the usage output for additional options.
|
||||
`openchange_user_cleanup username`. See the usage output for additional options.
|
||||
* The "Out of Office Assistant" will not currently work. This feature
|
||||
has not been implemented.
|
||||
* Creating folders below INBOX (when not normally permitted by the IMAP
|
||||
|
|
|
@ -267,7 +267,7 @@
|
|||
NSObject <DOMNodeList> *list;
|
||||
NSObject <DOMNode> *valueNode;
|
||||
NSArray *elements;
|
||||
NSString *property, *match;
|
||||
NSString *property=nil, *match=nil;
|
||||
|
||||
list = [searchElement getElementsByTagName: @"prop"];
|
||||
if ([list length])
|
||||
|
|
88
NEWS
88
NEWS
|
@ -1,13 +1,88 @@
|
|||
2.3.x (2015-MM-DD)
|
||||
2.3.4 (YYYY-MM-DD)
|
||||
------------------
|
||||
|
||||
New features
|
||||
- Initial support for EAS calendar exceptions
|
||||
|
||||
Enhancements
|
||||
- limit the maximum width of toolbar buttons
|
||||
|
||||
|
||||
Bug fixes
|
||||
- JavaScript exception when printing events from calendars with no assigned color (#3203)
|
||||
- EAS fix for wrong charset being used (#3392)
|
||||
- EAS fix on qp-encoded subjects (#3390)
|
||||
- correctly handle all-day event exceptions when the master event changes
|
||||
- prevent characters in calendar component UID causing issues during import process
|
||||
|
||||
2.3.3a (2015-11-18)
|
||||
-------------------
|
||||
|
||||
Bug fixes
|
||||
- expanded mail folders list is not saved (#3386)
|
||||
- cleanup translations
|
||||
|
||||
2.3.3 (2015-11-11)
|
||||
------------------
|
||||
|
||||
New features
|
||||
- initial S/MIME support for EAS (#3327)
|
||||
- now possible to choose which folders to sync over EAS
|
||||
|
||||
Enhancements
|
||||
- we no longer always entirely rewrite messages for Outlook 2013 when using EAS
|
||||
- support for ghosted elements on contacts over EAS
|
||||
- added Macedonian (mk_MK) translation - thanks to Miroslav Jovanovic
|
||||
- added Portuguese (pt) translation - thanks to Eduardo Crispim
|
||||
|
||||
Bug fixes
|
||||
- numerous EAS fixes when connections are dropped before the EAS client receives the response (#3058, #2849)
|
||||
- correctly handle the References header over EAS (#3365)
|
||||
- make sure English is always used when generating Date headers using EAS (#3356)
|
||||
- don't escape quoted strings during versit generation
|
||||
- we now return all cards when we receive an empty addressbook-query REPORT
|
||||
- avoid crash when replying to a mail with no recipients (#3359)
|
||||
- inline images sent from SOGo webmail are not displayed in Mozilla Thunderbird (#3271)
|
||||
- prevent postal address showing on single line over EAS (#2614)
|
||||
- display missing events when printing working hours only
|
||||
- fix corner case making server crash when syncing hard deleted messages when clear offline items was set up (Zentyal)
|
||||
- avoid infinite Outlook client loops trying to set read flag when it is already set (Zentyal)
|
||||
- avoid crashing when calendar metadata is missing in the cache (Zentyal)
|
||||
- fix recurrence pattern event corner case created by Mozilla Thunderbird which made server crash (Zentyal)
|
||||
- fix corner case that removes attachments on sending messages from Outlook (Zentyal)
|
||||
- freebusy on web interface works again in multidomain environments (Zentyal)
|
||||
- fix double creation of folders in Outlook when the folder name starts with a digit (Zentyal)
|
||||
- avoid crashing Outlook after setting a custom view in a calendar folder (Zentyal)
|
||||
- handle emails having an attachment as their content
|
||||
- fixed JavaScript syntax error in attendees editor
|
||||
- fixed wrong comparison of meta vs. META tag in HTML mails
|
||||
- fixed popup menu position when moved to the left (#3381)
|
||||
- fixed dialog position when at the bottom of the window (#2646, #3378)
|
||||
- fixed addressbrook-only source entires having a c_uid set
|
||||
|
||||
2.3.2 (2015-09-16)
|
||||
------------------
|
||||
|
||||
Enhancements
|
||||
- improved EAS speed and memory usage, avoiding many IMAP LIST commands (#3294)
|
||||
- improved EAS speed during initial syncing of large mailboxes (#3293)
|
||||
- updated CKEditor to version 4.5.3
|
||||
|
||||
Bug fixes
|
||||
- fixed display of whitelisted attendees in Preferences window on Firefox (#3285)
|
||||
- Non-latin subfolder names are displayed correctly on Outlook (Zentyal)
|
||||
- Fixed several sync issues on environments with multiple users (Zentyal)
|
||||
- Folders from other users will no longer appear on your Outlook (Zentyal)
|
||||
- Use right auth in multidomain environments in contacts and calendar from Outlook (Zentyal)
|
||||
- Session fix when SOGoEnableDomainBasedUID is enabled but logins are domain-less
|
||||
- non-latin subfolder names are displayed correctly on Outlook (Zentyal)
|
||||
- fixed several sync issues on environments with multiple users (Zentyal)
|
||||
- folders from other users will no longer appear on your Outlook (Zentyal)
|
||||
- use right auth in multidomain environments in contacts and calendar from Outlook (Zentyal)
|
||||
- session fix when SOGoEnableDomainBasedUID is enabled but logins are domain-less
|
||||
- less sync issues when setting read flag (Zentyal)
|
||||
- attachments with non-latin filenames sent by Outlook are now received (Zentyal)
|
||||
- support attachments from more mail clients (Zentyal)
|
||||
- avoid conflicting message on saving a draft mail (Zentyal)
|
||||
- less conflicting messages in Outlook while moving messages between folders (Zentyal)
|
||||
- start/end shifting by 1 hour due to timezone change on last Sunday of October 2015 (#3344)
|
||||
- fixed localization of calendar categories with empty profile (#3295)
|
||||
- fixed options availability in contextual menu of Contacts module (#3342)
|
||||
|
||||
2.3.1 (2015-07-23)
|
||||
------------------
|
||||
|
@ -20,6 +95,7 @@ Enhancements
|
|||
- added create-folder subcommand to sogo-tool to create contact and calendar folders
|
||||
- group mail addresses can be used as recipient in Outlook
|
||||
- added 'ActiveSync' module constraints
|
||||
- updated CKEditor to version 4.5.1
|
||||
- added Slovenian translation - thanks to Jens Riecken
|
||||
- added Chinese (Taiwan) translation
|
||||
|
||||
|
|
|
@ -46,6 +46,9 @@
|
|||
forURL: (NSURL *) server
|
||||
forceRenew: (BOOL) renew;
|
||||
|
||||
- (NSString *) passwordInContext: (WOContext *) context;
|
||||
|
||||
|
||||
@end
|
||||
|
||||
#endif /* MAPISTOREAUTHENTICATOR_H */
|
||||
|
|
|
@ -72,4 +72,8 @@
|
|||
return imapPassword;
|
||||
}
|
||||
|
||||
- (NSString *) passwordInContext: (WOContext *) context
|
||||
{
|
||||
return password;
|
||||
}
|
||||
@end
|
||||
|
|
|
@ -671,7 +671,7 @@ static Class NSArrayK, MAPIStoreAppointmentWrapperK;
|
|||
return newAttachment;
|
||||
}
|
||||
|
||||
- (int) setReadFlag: (uint8_t) flag
|
||||
- (enum mapistore_error) setReadFlag: (uint8_t) flag
|
||||
{
|
||||
return MAPISTORE_SUCCESS;
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
|
||||
#import <Foundation/NSArray.h>
|
||||
#import <Foundation/NSCalendarDate.h>
|
||||
#import <Foundation/NSData.h>
|
||||
#import <Foundation/NSDictionary.h>
|
||||
#import <Foundation/NSString.h>
|
||||
#import <Foundation/NSValue.h>
|
||||
|
@ -34,6 +35,7 @@
|
|||
#import "MAPIStoreDBFolder.h"
|
||||
#import "MAPIStoreDBMessage.h"
|
||||
#import "MAPIStoreTypes.h"
|
||||
#import "NSData+MAPIStore.h"
|
||||
#import "NSObject+MAPIStore.h"
|
||||
#import "NSString+MAPIStore.h"
|
||||
|
||||
|
@ -43,16 +45,28 @@
|
|||
|
||||
@implementation MAPIStoreDBMessage
|
||||
|
||||
+ (int) getAvailableProperties: (struct SPropTagArray **) propertiesP
|
||||
+ (enum mapistore_error) getAvailableProperties: (struct SPropTagArray **) propertiesP
|
||||
inMemCtx: (TALLOC_CTX *) memCtx
|
||||
{
|
||||
struct SPropTagArray *properties;
|
||||
NSUInteger count;
|
||||
enum MAPITAGS faiProperties[] = { 0x68350102, 0x683c0102, 0x683e0102,
|
||||
0x683f0102, 0x68410003, 0x68420102,
|
||||
0x68450102, 0x68460003,
|
||||
// PR_VD_NAME_W, PR_VD_FLAGS, PR_VD_VERSION, PR_VIEW_CLSID
|
||||
0x7006001F, 0x70030003, 0x70070003, 0x68330048 };
|
||||
|
||||
enum MAPITAGS faiProperties[] = {
|
||||
0x68330048, /* PR_VIEW_CLSID */
|
||||
0x68350102, /* PR_VIEW_STATE */
|
||||
0x683c0102,
|
||||
0x683d0040,
|
||||
0x683e0102,
|
||||
0x683f0102, /* PR_VIEW_VIEWTYPE_KEY */
|
||||
0x68410003,
|
||||
0x68420102,
|
||||
0x68450102,
|
||||
0x68460003,
|
||||
0x7006001F, /* PR_VD_NAME_W */
|
||||
0x70030003, /* PR_VD_FLAGS */
|
||||
0x70070003 /* PR_VD_VERSION */
|
||||
};
|
||||
|
||||
size_t faiSize = sizeof(faiProperties) / sizeof(enum MAPITAGS);
|
||||
|
||||
properties = talloc_zero (memCtx, struct SPropTagArray);
|
||||
|
@ -74,6 +88,13 @@
|
|||
return MAPISTORE_SUCCESS;
|
||||
}
|
||||
|
||||
- (enum mapistore_error) getAvailableProperties: (struct SPropTagArray **) propertiesP
|
||||
inMemCtx: (TALLOC_CTX *) memCtx
|
||||
{
|
||||
return [[self class] getAvailableProperties: propertiesP
|
||||
inMemCtx: memCtx];
|
||||
}
|
||||
|
||||
- (id) initWithSOGoObject: (id) newSOGoObject
|
||||
inContainer: (MAPIStoreObject *) newContainer
|
||||
{
|
||||
|
@ -104,6 +125,105 @@
|
|||
return objectVersion;
|
||||
}
|
||||
|
||||
- (void) _updatePredecessorChangeList
|
||||
{
|
||||
BOOL updated;
|
||||
enum mapistore_error rc;
|
||||
NSData *currentChangeList, *changeKey;
|
||||
NSMutableArray *changeKeys;
|
||||
NSMutableData *newChangeList;
|
||||
NSUInteger count, len;
|
||||
struct SizedXid *changes;
|
||||
struct SPropValue property;
|
||||
struct SRow aRow;
|
||||
struct XID *currentChangeKey;
|
||||
TALLOC_CTX *localMemCtx;
|
||||
uint32_t nChanges;
|
||||
|
||||
localMemCtx = talloc_new (NULL);
|
||||
if (!localMemCtx)
|
||||
{
|
||||
[self errorWithFormat: @"No more memory"];
|
||||
return;
|
||||
}
|
||||
|
||||
changeKey = [self getReplicaKeyFromGlobCnt: [self objectVersion]];
|
||||
|
||||
currentChangeList = [properties objectForKey: MAPIPropertyKey (PidTagPredecessorChangeList)];
|
||||
if (!currentChangeList)
|
||||
{
|
||||
/* Create a new PredecessorChangeList */
|
||||
len = [changeKey length];
|
||||
newChangeList = [NSMutableData dataWithCapacity: len + 1];
|
||||
[newChangeList appendUInt8: len];
|
||||
[newChangeList appendData: changeKey];
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Update current predecessor change list with new change key */
|
||||
changes = [currentChangeList asSizedXidArrayInMemCtx: localMemCtx
|
||||
with: &nChanges];
|
||||
|
||||
updated = NO;
|
||||
currentChangeKey = [changeKey asXIDInMemCtx: localMemCtx];
|
||||
for (count = 0; count < nChanges && !updated; count++)
|
||||
{
|
||||
if (GUID_equal(&changes[count].XID.NameSpaceGuid, ¤tChangeKey->NameSpaceGuid))
|
||||
{
|
||||
NSData *globCnt, *oldGlobCnt;
|
||||
oldGlobCnt = [NSData dataWithBytes: changes[count].XID.LocalId.data length: changes[count].XID.LocalId.length];
|
||||
globCnt = [NSData dataWithBytes: currentChangeKey->LocalId.data length: currentChangeKey->LocalId.length];
|
||||
if ([globCnt compare: oldGlobCnt] == NSOrderedDescending)
|
||||
{
|
||||
if ([globCnt length] != [oldGlobCnt length])
|
||||
{
|
||||
[self errorWithFormat: @"Cannot compare globcnt with different length: %@ and %@", globCnt, oldGlobCnt];
|
||||
abort();
|
||||
}
|
||||
memcpy (changes[count].XID.LocalId.data, currentChangeKey->LocalId.data, currentChangeKey->LocalId.length);
|
||||
updated = YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Serialise it */
|
||||
changeKeys = [NSMutableArray array];
|
||||
|
||||
if (!updated)
|
||||
[changeKeys addObject: changeKey];
|
||||
|
||||
for (count = 0; count < nChanges; count++)
|
||||
{
|
||||
changeKey = [NSData dataWithXID: &changes[count].XID];
|
||||
[changeKeys addObject: changeKey];
|
||||
}
|
||||
|
||||
[changeKeys sortUsingFunction: MAPIChangeKeyGUIDCompare context: localMemCtx];
|
||||
|
||||
newChangeList = [NSMutableData data];
|
||||
len = [changeKeys count];
|
||||
for (count = 0; count < len; count++)
|
||||
{
|
||||
changeKey = [changeKeys objectAtIndex: count];
|
||||
[newChangeList appendUInt8: [changeKey length]];
|
||||
[newChangeList appendData: changeKey];
|
||||
}
|
||||
}
|
||||
|
||||
if ([newChangeList length] > 0)
|
||||
{
|
||||
property.ulPropTag = PidTagPredecessorChangeList;
|
||||
property.value.bin = *[newChangeList asBinaryInMemCtx: localMemCtx];
|
||||
aRow.cValues = 1;
|
||||
aRow.lpProps = &property;
|
||||
rc = [self addPropertiesFromRow: &aRow];
|
||||
if (rc != MAPISTORE_SUCCESS)
|
||||
[self errorWithFormat: @"Impossible to add a new predecessor change list: %d", rc];
|
||||
}
|
||||
|
||||
talloc_free (localMemCtx);
|
||||
}
|
||||
|
||||
//
|
||||
// FIXME: how this can happen?
|
||||
//
|
||||
|
@ -166,6 +286,9 @@
|
|||
[properties setObject: [NSNumber numberWithUnsignedLongLong: newVersion]
|
||||
forKey: @"version"];
|
||||
|
||||
/* Update PredecessorChangeList accordingly */
|
||||
[self _updatePredecessorChangeList];
|
||||
|
||||
[self logWithFormat: @"%d props in dict", [properties count]];
|
||||
|
||||
[sogoObject save];
|
||||
|
@ -209,4 +332,36 @@
|
|||
return [sogoObject lastModified];
|
||||
}
|
||||
|
||||
- (enum mapistore_error) setReadFlag: (uint8_t) flag
|
||||
{
|
||||
/* Modify PidTagMessageFlags from SetMessageReadFlag and
|
||||
SyncImportReadStateChanges ROPs */
|
||||
NSNumber *flags;
|
||||
uint32_t newFlag;
|
||||
|
||||
flags = [properties objectForKey: MAPIPropertyKey (PR_MESSAGE_FLAGS)];
|
||||
if (flags)
|
||||
{
|
||||
newFlag = [flags unsignedLongValue];
|
||||
if (flag & SUPPRESS_RECEIPT)
|
||||
newFlag |= MSGFLAG_READ;
|
||||
if (flag & CLEAR_RN_PENDING)
|
||||
newFlag &= ~MSGFLAG_RN_PENDING;
|
||||
if (flag & CLEAR_READ_FLAG)
|
||||
newFlag &= ~MSGFLAG_READ;
|
||||
if (flag & CLEAR_NRN_PENDING)
|
||||
newFlag &= ~MSGFLAG_NRN_PENDING;
|
||||
}
|
||||
else
|
||||
{
|
||||
newFlag = MSGFLAG_READ;
|
||||
if (flag & CLEAR_READ_FLAG)
|
||||
newFlag = 0x0;
|
||||
}
|
||||
[properties setObject: [NSNumber numberWithUnsignedLong: newFlag]
|
||||
forKey: MAPIPropertyKey (PR_MESSAGE_FLAGS)];
|
||||
|
||||
return MAPISTORE_SUCCESS;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -121,6 +121,7 @@
|
|||
fromFolder: (MAPIStoreFolder *) sourceFolder
|
||||
withMIDs: (uint64_t *) targetMids
|
||||
andChangeKeys: (struct Binary_r **) targetChangeKeys
|
||||
andPredecessorChangeLists: (struct Binary_r **) targetPredecessorChangeLists
|
||||
wantCopy: (uint8_t) want_copy
|
||||
inMemCtx: (TALLOC_CTX *) memCtx;
|
||||
|
||||
|
|
|
@ -642,6 +642,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
|
|||
fromFolder: (MAPIStoreFolder *) sourceFolder
|
||||
withMID: (uint64_t) targetMid
|
||||
andChangeKey: (struct Binary_r *) targetChangeKey
|
||||
andPredecessorChangeList: (struct Binary_r *) targetPredecessorChangeList
|
||||
wantCopy: (uint8_t) wantCopy
|
||||
inMemCtx: (TALLOC_CTX *) memCtx
|
||||
{
|
||||
|
@ -669,16 +670,19 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
|
|||
|
||||
[sourceMsg copyToMessage: destMsg inMemCtx: memCtx];
|
||||
|
||||
if (targetChangeKey)
|
||||
if (targetPredecessorChangeList)
|
||||
{
|
||||
property.ulPropTag = PidTagChangeKey;
|
||||
property.value.bin = *targetChangeKey;
|
||||
property.ulPropTag = PidTagPredecessorChangeList;
|
||||
property.value.bin = *targetPredecessorChangeList;
|
||||
aRow.cValues = 1;
|
||||
aRow.lpProps = &property;
|
||||
rc = [destMsg addPropertiesFromRow: &aRow];
|
||||
if (rc != MAPISTORE_SUCCESS)
|
||||
{
|
||||
[self errorWithFormat: @"Cannot add PredecessorChangeList on move"];
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
[destMsg save: memCtx];
|
||||
if (!wantCopy)
|
||||
/* We want to keep mid for restoring/shared data to work if mids are different. */
|
||||
|
@ -696,6 +700,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
|
|||
fromFolder: (MAPIStoreFolder *) sourceFolder
|
||||
withMIDs: (uint64_t *) targetMids
|
||||
andChangeKeys: (struct Binary_r **) targetChangeKeys
|
||||
andPredecessorChangeLists: (struct Binary_r **) targetPredecessorChangeLists
|
||||
wantCopy: (uint8_t) wantCopy
|
||||
inMemCtx: (TALLOC_CTX *) memCtx
|
||||
{
|
||||
|
@ -705,7 +710,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
|
|||
NSString *oldMessageURL;
|
||||
MAPIStoreMapping *mapping;
|
||||
SOGoUser *ownerUser;
|
||||
struct Binary_r *targetChangeKey;
|
||||
struct Binary_r *targetChangeKey, *targetPredecessorChangeList;
|
||||
//TALLOC_CTX *memCtx;
|
||||
|
||||
//memCtx = talloc_zero (NULL, TALLOC_CTX);
|
||||
|
@ -726,14 +731,21 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
|
|||
if (oldMessageURL)
|
||||
{
|
||||
[oldMessageURLs addObject: oldMessageURL];
|
||||
if (targetChangeKeys)
|
||||
if (targetChangeKeys && targetPredecessorChangeList)
|
||||
{
|
||||
targetChangeKey = targetChangeKeys[count];
|
||||
targetPredecessorChangeList = targetPredecessorChangeLists[count];
|
||||
}
|
||||
else
|
||||
{
|
||||
targetChangeKey = NULL;
|
||||
targetPredecessorChangeList = NULL;
|
||||
}
|
||||
rc = [self _moveCopyMessageWithMID: srcMids[count]
|
||||
fromFolder: sourceFolder
|
||||
withMID: targetMids[count]
|
||||
andChangeKey: targetChangeKey
|
||||
andPredecessorChangeList: targetPredecessorChangeList
|
||||
wantCopy: wantCopy
|
||||
inMemCtx: memCtx];
|
||||
}
|
||||
|
|
|
@ -41,8 +41,10 @@
|
|||
|
||||
/* synchronisation */
|
||||
- (BOOL) synchroniseCache;
|
||||
- (BOOL) synchroniseCacheFor: (NSString *) nameInContainer;
|
||||
- (void) updateVersionsForMessageWithKey: (NSString *) messageKey
|
||||
withChangeKey: (NSData *) newChangeKey;
|
||||
withChangeKey: (NSData *) oldChangeKey
|
||||
andPredecessorChangeList: (NSData *) pcl;
|
||||
- (NSNumber *) lastModifiedFromMessageChangeNumber: (NSString *) changeNumber;
|
||||
- (NSString *) changeNumberForMessageWithKey: (NSString *) messageKey;
|
||||
- (NSData *) changeKeyForMessageWithKey: (NSString *) messageKey;
|
||||
|
|
|
@ -75,6 +75,7 @@ static Class NSNumberK;
|
|||
[SOGoMAPIDBMessage objectWithName: @"versions.plist"
|
||||
inContainer: dbFolder]);
|
||||
[versionsMessage setObjectType: MAPIInternalCacheObject];
|
||||
[versionsMessage reloadIfNeeded];
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
|
@ -261,7 +262,6 @@ static Class NSNumberK;
|
|||
*/
|
||||
- (void) _setChangeKey: (NSData *) changeKey
|
||||
forMessageEntry: (NSMutableDictionary *) messageEntry
|
||||
inChangeListOnly: (BOOL) inChangeListOnly
|
||||
{
|
||||
struct XID *xid;
|
||||
NSString *guid;
|
||||
|
@ -270,19 +270,15 @@ static Class NSNumberK;
|
|||
NSMutableDictionary *changeList;
|
||||
|
||||
xid = [changeKey asXIDInMemCtx: NULL];
|
||||
guid = [NSString stringWithGUID: &xid->GUID];
|
||||
globCnt = [NSData dataWithBytes: xid->Data length: xid->Size];
|
||||
guid = [NSString stringWithGUID: &xid->NameSpaceGuid];
|
||||
globCnt = [NSData dataWithBytes: xid->LocalId.data length: xid->LocalId.length];
|
||||
talloc_free (xid);
|
||||
|
||||
if (!inChangeListOnly)
|
||||
{
|
||||
/* 1. set change key association */
|
||||
changeKeyDict = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
guid, @"GUID",
|
||||
changeKeyDict = [NSDictionary dictionaryWithObjectsAndKeys: guid, @"GUID",
|
||||
globCnt, @"LocalId",
|
||||
nil];
|
||||
[messageEntry setObject: changeKeyDict forKey: @"ChangeKey"];
|
||||
}
|
||||
|
||||
/* 2. append/update predecessor change list */
|
||||
changeList = [messageEntry objectForKey: @"PredecessorChangeList"];
|
||||
|
@ -296,6 +292,77 @@ static Class NSNumberK;
|
|||
[changeList setObject: globCnt forKey: guid];
|
||||
}
|
||||
|
||||
- (void) _updatePredecessorChangeList: (NSData *) predecessorChangeList
|
||||
forMessageEntry: (NSMutableDictionary *) messageEntry
|
||||
withOldChangeKey: (NSData *) oldChangeKey
|
||||
{
|
||||
NSData *globCnt, *oldGlobCnt;
|
||||
NSDictionary *changeKeyDict;
|
||||
NSString *guid;
|
||||
NSMutableDictionary *changeList;
|
||||
struct SizedXid *sizedXIDList;
|
||||
struct XID xid, *givenChangeKey;
|
||||
TALLOC_CTX *localMemCtx;
|
||||
uint32_t i, length;
|
||||
|
||||
localMemCtx = talloc_new (NULL);
|
||||
if (!localMemCtx)
|
||||
{
|
||||
[self errorWithFormat: @"No more memory"];
|
||||
return;
|
||||
}
|
||||
|
||||
if (predecessorChangeList)
|
||||
{
|
||||
sizedXIDList = [predecessorChangeList asSizedXidArrayInMemCtx: localMemCtx with: &length];
|
||||
|
||||
changeList = [messageEntry objectForKey: @"PredecessorChangeList"];
|
||||
if (!changeList)
|
||||
{
|
||||
changeList = [NSMutableDictionary new];
|
||||
[messageEntry setObject: changeList
|
||||
forKey: @"PredecessorChangeList"];
|
||||
[changeList release];
|
||||
}
|
||||
|
||||
if (sizedXIDList) {
|
||||
for (i = 0; i < length; i++)
|
||||
{
|
||||
xid = sizedXIDList[i].XID;
|
||||
guid = [NSString stringWithGUID: &xid.NameSpaceGuid];
|
||||
globCnt = [NSData dataWithBytes: xid.LocalId.data length: xid.LocalId.length];
|
||||
oldGlobCnt = [changeList objectForKey: guid];
|
||||
if (!oldGlobCnt || ([globCnt compare: oldGlobCnt] == NSOrderedDescending))
|
||||
[changeList setObject: globCnt forKey: guid];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (oldChangeKey)
|
||||
{
|
||||
givenChangeKey = [oldChangeKey asXIDInMemCtx: localMemCtx];
|
||||
if (givenChangeKey) {
|
||||
guid = [NSString stringWithGUID: &givenChangeKey->NameSpaceGuid];
|
||||
globCnt = [NSData dataWithBytes: givenChangeKey->LocalId.data length: givenChangeKey->LocalId.length];
|
||||
|
||||
changeKeyDict = [messageEntry objectForKey: @"ChangeKey"];
|
||||
if (!changeKeyDict ||
|
||||
([guid isEqualToString: [changeKeyDict objectForKey: @"GUID"]]
|
||||
&& ([globCnt compare: [changeKeyDict objectForKey: @"LocalId"]] == NSOrderedDescending)))
|
||||
{
|
||||
/* The given change key is greater than current one stored in
|
||||
metadata or it does not exist */
|
||||
[messageEntry setObject: [NSDictionary dictionaryWithObjectsAndKeys: guid, @"GUID",
|
||||
globCnt, @"LocalId",
|
||||
nil]
|
||||
forKey: @"ChangeKey"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
talloc_free (localMemCtx);
|
||||
}
|
||||
|
||||
- (EOQualifier *) componentQualifier
|
||||
{
|
||||
if (!componentQualifier)
|
||||
|
@ -465,8 +532,7 @@ static Class NSNumberK;
|
|||
// A GLOBCNT structure is a 6-byte global namespace counter,
|
||||
// we strip the first 2 bytes. The first two bytes is the ReplicaId
|
||||
changeKey = [self getReplicaKeyFromGlobCnt: newChangeNum >> 16];
|
||||
[self _setChangeKey: changeKey forMessageEntry: messageEntry
|
||||
inChangeListOnly: NO];
|
||||
[self _setChangeKey: changeKey forMessageEntry: messageEntry];
|
||||
}
|
||||
|
||||
now = [NSCalendarDate date];
|
||||
|
@ -482,13 +548,123 @@ static Class NSNumberK;
|
|||
return rc;
|
||||
}
|
||||
|
||||
- (BOOL) synchroniseCacheFor: (NSString *) nameInContainer
|
||||
{
|
||||
/* Try to synchronise old messages in versions.plist cache using an
|
||||
specific c_name. It returns a boolean indicating if the
|
||||
synchronisation was carried out succesfully.
|
||||
|
||||
It should be used as last resort, keeping synchroniseCache as the
|
||||
main sync entry point. */
|
||||
|
||||
uint64_t changeNumber;
|
||||
NSString *changeNumberStr;
|
||||
NSData *changeKey;
|
||||
NSNumber *cLastModified, *cDeleted, *cVersion;
|
||||
EOFetchSpecification *fs;
|
||||
EOQualifier *searchQualifier, *fetchQualifier;
|
||||
NSArray *fetchResults;
|
||||
NSDictionary *result;
|
||||
NSMutableDictionary *currentProperties, *messages, *mapping, *messageEntry;
|
||||
GCSFolder *ocsFolder;
|
||||
static NSArray *fields;
|
||||
|
||||
[versionsMessage reloadIfNeeded];
|
||||
currentProperties = [versionsMessage properties];
|
||||
|
||||
messages = [currentProperties objectForKey: @"Messages"];
|
||||
if (!messages)
|
||||
{
|
||||
messages = [NSMutableDictionary new];
|
||||
[currentProperties setObject: messages forKey: @"Messages"];
|
||||
[messages release];
|
||||
}
|
||||
|
||||
messageEntry = [messages objectForKey: nameInContainer];
|
||||
if (!messageEntry)
|
||||
{
|
||||
/* Fetch the message by its name */
|
||||
if (!fields)
|
||||
fields = [[NSArray alloc]
|
||||
initWithObjects: @"c_name", @"c_version", @"c_lastmodified",
|
||||
@"c_deleted", nil];
|
||||
|
||||
searchQualifier = [[EOKeyValueQualifier alloc] initWithKey: @"c_name"
|
||||
operatorSelector: EOQualifierOperatorEqual
|
||||
value: nameInContainer];
|
||||
fetchQualifier = [[EOAndQualifier alloc]
|
||||
initWithQualifiers: searchQualifier,
|
||||
[self contentComponentQualifier],
|
||||
nil];
|
||||
[fetchQualifier autorelease];
|
||||
[searchQualifier release];
|
||||
|
||||
ocsFolder = [sogoObject ocsFolder];
|
||||
fs = [EOFetchSpecification
|
||||
fetchSpecificationWithEntityName: [ocsFolder folderName]
|
||||
qualifier: fetchQualifier
|
||||
sortOrderings: nil];
|
||||
fetchResults = [ocsFolder fetchFields: fields
|
||||
fetchSpecification: fs
|
||||
ignoreDeleted: NO];
|
||||
|
||||
if ([fetchResults count] == 1)
|
||||
{
|
||||
result = [fetchResults objectAtIndex: 0];
|
||||
cLastModified = [result objectForKey: @"c_lastmodified"];
|
||||
cDeleted = [result objectForKey: @"c_deleted"];
|
||||
if ([cDeleted isKindOfClass: NSNumberK] && [cDeleted intValue])
|
||||
cVersion = [NSNumber numberWithInt: -1];
|
||||
else
|
||||
cVersion = [result objectForKey: @"c_version"];
|
||||
|
||||
changeNumber = [[self context] getNewChangeNumber];
|
||||
changeNumberStr = [NSString stringWithUnsignedLongLong: changeNumber];
|
||||
|
||||
/* Create new message entry in Messages dict */
|
||||
messageEntry = [NSMutableDictionary new];
|
||||
[messages setObject: messageEntry forKey: nameInContainer];
|
||||
[messageEntry release];
|
||||
|
||||
/* Store cLastModified, cVersion and the change number */
|
||||
[messageEntry setObject: cLastModified forKey: @"c_lastmodified"];
|
||||
[messageEntry setObject: cVersion forKey: @"c_version"];
|
||||
[messageEntry setObject: changeNumberStr forKey: @"version"];
|
||||
|
||||
/* Store the change key */
|
||||
changeKey = [self getReplicaKeyFromGlobCnt: changeNumber >> 16];
|
||||
[self _setChangeKey: changeKey forMessageEntry: messageEntry];
|
||||
|
||||
/* Store the changeNumber -> cLastModified mapping */
|
||||
mapping = [currentProperties objectForKey: @"VersionMapping"];
|
||||
if (!mapping)
|
||||
{
|
||||
mapping = [NSMutableDictionary new];
|
||||
[currentProperties setObject: mapping forKey: @"VersionMapping"];
|
||||
[mapping release];
|
||||
}
|
||||
[mapping setObject: cLastModified forKey: changeNumberStr];
|
||||
|
||||
/* Save the message */
|
||||
[versionsMessage save];
|
||||
return YES;
|
||||
}
|
||||
else
|
||||
return NO;
|
||||
}
|
||||
|
||||
/* If message entry exists, then synchroniseCache did its job */
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void) updateVersionsForMessageWithKey: (NSString *) messageKey
|
||||
withChangeKey: (NSData *) newChangeKey
|
||||
withChangeKey: (NSData *) oldChangeKey
|
||||
andPredecessorChangeList: (NSData *) pcl
|
||||
{
|
||||
NSMutableDictionary *messages, *messageEntry;
|
||||
|
||||
[self synchroniseCache];
|
||||
if (newChangeKey)
|
||||
if (oldChangeKey || pcl)
|
||||
{
|
||||
messages = [[versionsMessage properties] objectForKey: @"Messages"];
|
||||
messageEntry = [messages objectForKey: messageKey];
|
||||
|
@ -496,8 +672,8 @@ static Class NSNumberK;
|
|||
[NSException raise: @"MAPIStoreIOException"
|
||||
format: @"no version record found for message '%@'",
|
||||
messageKey];
|
||||
[self _setChangeKey: newChangeKey forMessageEntry: messageEntry
|
||||
inChangeListOnly: YES];
|
||||
[self _updatePredecessorChangeList: pcl forMessageEntry: messageEntry
|
||||
withOldChangeKey: oldChangeKey];
|
||||
[versionsMessage save];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -180,6 +180,7 @@
|
|||
{
|
||||
uint64_t version = ULLONG_MAX;
|
||||
NSString *changeNumber;
|
||||
BOOL synced;
|
||||
|
||||
if (!isNew)
|
||||
{
|
||||
|
@ -189,18 +190,30 @@
|
|||
{
|
||||
[self warnWithFormat: @"attempting to get change number"
|
||||
@" by synchronising folder..."];
|
||||
[(MAPIStoreGCSFolder *) container synchroniseCache];
|
||||
synced = [(MAPIStoreGCSFolder *) container synchroniseCache];
|
||||
if (synced)
|
||||
{
|
||||
changeNumber = [(MAPIStoreGCSFolder *) container
|
||||
changeNumberForMessageWithKey: [self nameInContainer]];
|
||||
|
||||
if (changeNumber)
|
||||
[self logWithFormat: @"got one"];
|
||||
else
|
||||
}
|
||||
if (!changeNumber)
|
||||
{
|
||||
[self warnWithFormat: @"attempting to get change number"
|
||||
@" by synchronising this specific message..."];
|
||||
synced = [(MAPIStoreGCSFolder *) container
|
||||
synchroniseCacheFor: [self nameInContainer]];
|
||||
if (synced)
|
||||
{
|
||||
changeNumber = [(MAPIStoreGCSFolder *) container
|
||||
changeNumberForMessageWithKey: [self nameInContainer]];
|
||||
}
|
||||
if (!changeNumber)
|
||||
{
|
||||
[self errorWithFormat: @"still nothing. We crash!"];
|
||||
abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
version = [changeNumber unsignedLongLongValue] >> 16;
|
||||
}
|
||||
|
||||
|
@ -209,13 +222,16 @@
|
|||
|
||||
- (void) updateVersions
|
||||
{
|
||||
NSData *newChangeKey;
|
||||
/* Update ChangeKey and PredecessorChangeList on message's save */
|
||||
NSData *newChangeKey, *predecessorChangeList;
|
||||
|
||||
newChangeKey = [properties objectForKey: MAPIPropertyKey (PR_CHANGE_KEY)];
|
||||
predecessorChangeList = [properties objectForKey: MAPIPropertyKey (PR_PREDECESSOR_CHANGE_LIST)];
|
||||
|
||||
[(MAPIStoreGCSFolder *) container
|
||||
updateVersionsForMessageWithKey: [self nameInContainer]
|
||||
withChangeKey: newChangeKey];
|
||||
withChangeKey: newChangeKey
|
||||
andPredecessorChangeList: predecessorChangeList];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#import <SOGo/NSArray+Utilities.h>
|
||||
#import <Mailer/SOGoMailBodyPart.h>
|
||||
#import <Mailer/SOGoMailObject.h>
|
||||
#import <Mailer/NSDictionary+Mail.h>
|
||||
|
||||
#import "MAPIStoreTypes.h"
|
||||
#import "MAPIStoreMailMessage.h"
|
||||
|
@ -117,19 +118,7 @@
|
|||
|
||||
- (NSString *) _fileName
|
||||
{
|
||||
NSString *fileName;
|
||||
NSDictionary *parameters;
|
||||
|
||||
fileName = [[bodyInfo objectForKey: @"parameterList"]
|
||||
objectForKey: @"name"];
|
||||
if (!fileName)
|
||||
{
|
||||
parameters = [[bodyInfo objectForKey: @"disposition"]
|
||||
objectForKey: @"parameterList"];
|
||||
fileName = [parameters objectForKey: @"filename"];
|
||||
}
|
||||
|
||||
return fileName;
|
||||
return [bodyInfo filename];
|
||||
}
|
||||
|
||||
- (int) getPidTagAttachLongFilename: (void **) data
|
||||
|
|
|
@ -160,23 +160,25 @@ MakeDisplayFolderName (NSString *folderName)
|
|||
for (count = 0; count < max; count++)
|
||||
{
|
||||
context = talloc_zero (memCtx, struct mapistore_contexts_list);
|
||||
// secondaryFolders has the names (1) Imap4Encoded and (2) asCSSIdentifier
|
||||
// e.g.: Probl&AOg-mes_SP_de_SP_synchronisation
|
||||
// secondaryFolders has the names (1) Imap4Encoded ,(2) asCSSIdentifier and (3) "folder"-prefixed
|
||||
// e.g.: folderProbl&AOg-mes_SP_de_SP_synchronisation
|
||||
currentName = [secondaryFolders objectAtIndex: count];
|
||||
// To get the real name we have to revert that (applying the decode functions)
|
||||
// in reverse order
|
||||
// To get the real name we have to revert that (applying the decode functions
|
||||
// in reverse order)
|
||||
// e.g.: Problèmes de synchronisation
|
||||
realName = [[currentName fromCSSIdentifier]
|
||||
realName = [[[currentName substringFromIndex: 6]
|
||||
fromCSSIdentifier]
|
||||
stringByDecodingImap4FolderName];
|
||||
// And finally to represent that as URI we have to (1) asCSSIdentifier,
|
||||
// (2) Imap4Encode and (3) AddPercentEscapes
|
||||
// e.g.: Probl&AOg-mes_SP_de_SP_synchronisation
|
||||
// (2) Imap4Encode (3) AddPercentEscapes and (4) add the "folder" prefix
|
||||
// e.g.: folderProbl&AOg-mes_SP_de_SP_synchronisation
|
||||
// In the example there are no percent escapes added because is already ok
|
||||
stringData = [[[realName asCSSIdentifier]
|
||||
stringByEncodingImap4FolderName]
|
||||
stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding];
|
||||
stringData = [NSString stringWithFormat: @"folder%@", stringData];
|
||||
context->url = [[NSString stringWithFormat: @"%@%@", urlBase, stringData] asUnicodeInMemCtx: context];
|
||||
context->name = [[realName substringFromIndex: 6] asUnicodeInMemCtx: context];
|
||||
context->name = [realName asUnicodeInMemCtx: context];
|
||||
context->main_folder = false;
|
||||
context->role = MAPISTORE_MAIL_ROLE;
|
||||
context->tag = "tag";
|
||||
|
@ -200,7 +202,7 @@ MakeDisplayFolderName (NSString *folderName)
|
|||
andTDBIndexing: NULL];
|
||||
accountFolder = [[userContext rootFolders] objectForKey: @"mail"];
|
||||
folderName = [NSString stringWithFormat: @"folder%@",
|
||||
[newFolderName asCSSIdentifier]];
|
||||
[[newFolderName stringByEncodingImap4FolderName] asCSSIdentifier]];
|
||||
newFolder = [SOGoMailFolder objectWithName: folderName
|
||||
inContainer: accountFolder];
|
||||
if ([newFolder create])
|
||||
|
@ -209,7 +211,7 @@ MakeDisplayFolderName (NSString *folderName)
|
|||
withString: @"%40"],
|
||||
[userName stringByReplacingOccurrencesOfString: @"@"
|
||||
withString: @"%40"],
|
||||
[[folderName stringByEncodingImap4FolderName] stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]];
|
||||
[folderName stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]];
|
||||
else
|
||||
mapistoreURI = nil;
|
||||
|
||||
|
|
|
@ -52,6 +52,8 @@
|
|||
- (NSString *) changeNumberForMessageUID: (NSString *) messageUid;
|
||||
- (void) setChangeKey: (NSData *) changeKey
|
||||
forMessageWithKey: (NSString *) messageKey;
|
||||
- (BOOL) updatePredecessorChangeListWith: (NSData *) changeKey
|
||||
forMessageWithKey: (NSString *) messageKey;
|
||||
- (NSData *) changeKeyForMessageWithKey: (NSString *) messageKey;
|
||||
- (NSData *) predecessorChangeListForMessageWithKey: (NSString *) messageKey;
|
||||
|
||||
|
|
|
@ -68,6 +68,8 @@
|
|||
|
||||
static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK;
|
||||
|
||||
#include <gen_ndr/exchange.h>
|
||||
|
||||
#undef DEBUG
|
||||
#include <util/attr.h>
|
||||
#include <libmapi/libmapi.h>
|
||||
|
@ -109,6 +111,7 @@ static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK;
|
|||
[SOGoMAPIDBMessage objectWithName: @"versions.plist"
|
||||
inContainer: dbFolder]);
|
||||
[versionsMessage setObjectType: MAPIInternalCacheObject];
|
||||
[versionsMessage reloadIfNeeded];
|
||||
}
|
||||
|
||||
- (BOOL) ensureFolderExists
|
||||
|
@ -516,6 +519,44 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
|||
return [modseq1 compare: modseq2];
|
||||
}
|
||||
|
||||
- (void) _updatePredecessorChangeListWith: (NSData *) predecessorChangeList
|
||||
forMessageEntry: (NSMutableDictionary *) messageEntry
|
||||
{
|
||||
NSData *globCnt, *oldGlobCnt;
|
||||
NSMutableDictionary *changeList;
|
||||
NSString *guid;
|
||||
struct SizedXid *sizedXIDList;
|
||||
struct XID xid;
|
||||
uint32_t i, length;
|
||||
|
||||
sizedXIDList = [predecessorChangeList asSizedXidArrayInMemCtx: NULL with: &length];
|
||||
|
||||
changeList = [messageEntry objectForKey: @"PredecessorChangeList"];
|
||||
if (!changeList)
|
||||
{
|
||||
changeList = [NSMutableDictionary new];
|
||||
[messageEntry setObject: changeList
|
||||
forKey: @"PredecessorChangeList"];
|
||||
[changeList release];
|
||||
}
|
||||
|
||||
if (sizedXIDList) {
|
||||
for (i = 0; i < length; i++)
|
||||
{
|
||||
xid = sizedXIDList[i].XID;
|
||||
guid = [NSString stringWithGUID: &xid.NameSpaceGuid];
|
||||
globCnt = [NSData dataWithBytes: xid.LocalId.data length: xid.LocalId.length];
|
||||
oldGlobCnt = [changeList objectForKey: guid];
|
||||
if (!oldGlobCnt || ([globCnt compare: oldGlobCnt] == NSOrderedDescending))
|
||||
[changeList setObject: globCnt forKey: guid];
|
||||
}
|
||||
|
||||
talloc_free (sizedXIDList);
|
||||
}
|
||||
|
||||
[versionsMessage save];
|
||||
}
|
||||
|
||||
- (void) _setChangeKey: (NSData *) changeKey
|
||||
forMessageEntry: (NSMutableDictionary *) messageEntry
|
||||
{
|
||||
|
@ -526,8 +567,8 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
|||
NSMutableDictionary *changeList;
|
||||
|
||||
xid = [changeKey asXIDInMemCtx: NULL];
|
||||
guid = [NSString stringWithGUID: &xid->GUID];
|
||||
globCnt = [NSData dataWithBytes: xid->Data length: xid->Size];
|
||||
guid = [NSString stringWithGUID: &xid->NameSpaceGuid];
|
||||
globCnt = [NSData dataWithBytes: xid->LocalId.data length: xid->LocalId.length];
|
||||
talloc_free (xid);
|
||||
|
||||
/* 1. set change key association */
|
||||
|
@ -553,8 +594,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
|||
{
|
||||
BOOL rc = YES;
|
||||
uint64_t newChangeNum;
|
||||
NSNumber *ti, *modseq, *initialLastModseq, *lastModseq,
|
||||
*nextModseq;
|
||||
NSNumber *ti, *modseq, *lastModseq, *nextModseq;
|
||||
NSString *changeNumber, *uid, *messageKey;
|
||||
uint64_t lastModseqNbr;
|
||||
EOQualifier *searchQualifier;
|
||||
|
@ -593,7 +633,6 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
|||
}
|
||||
|
||||
lastModseq = [currentProperties objectForKey: @"SyncLastModseq"];
|
||||
initialLastModseq = lastModseq;
|
||||
if (lastModseq)
|
||||
{
|
||||
lastModseqNbr = [lastModseq unsignedLongLongValue];
|
||||
|
@ -677,8 +716,6 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
|||
}
|
||||
|
||||
/* 2. we synchronise expunged UIDs */
|
||||
if (initialLastModseq)
|
||||
{
|
||||
fetchResults = [(SOGoMailFolder *) sogoObject
|
||||
fetchUIDsOfVanishedItems: lastModseqNbr];
|
||||
|
||||
|
@ -710,7 +747,6 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
|||
[mapping setObject: lastModseq forKey: changeNumber];
|
||||
foundChange = YES;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundChange)
|
||||
{
|
||||
|
@ -924,8 +960,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
|||
return changeNumber;
|
||||
}
|
||||
|
||||
- (void) setChangeKey: (NSData *) changeKey
|
||||
forMessageWithKey: (NSString *) messageKey
|
||||
- (NSMutableDictionary *) _messageEntryFromMessageKey: (NSString *) messageKey
|
||||
{
|
||||
NSMutableDictionary *messages, *messageEntry;
|
||||
NSString *messageUid;
|
||||
|
@ -936,7 +971,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
|||
messageEntry = [messages objectForKey: messageUid];
|
||||
if (!messageEntry)
|
||||
{
|
||||
[self warnWithFormat: @"attempting to synchronise to set the change key for "
|
||||
[self warnWithFormat: @"attempting to synchronise to get the message entry for "
|
||||
@"this message %@", messageKey];
|
||||
synced = [self synchroniseCacheForUID: messageUid];
|
||||
if (synced)
|
||||
|
@ -947,11 +982,57 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
|||
abort ();
|
||||
}
|
||||
}
|
||||
[self _setChangeKey: changeKey forMessageEntry: messageEntry];
|
||||
|
||||
return messageEntry;
|
||||
}
|
||||
|
||||
- (void) setChangeKey: (NSData *) changeKey
|
||||
forMessageWithKey: (NSString *) messageKey
|
||||
{
|
||||
[self _setChangeKey: changeKey
|
||||
forMessageEntry: [self _messageEntryFromMessageKey: messageKey]];
|
||||
|
||||
[versionsMessage save];
|
||||
}
|
||||
|
||||
- (BOOL) updatePredecessorChangeListWith: (NSData *) changeKey
|
||||
forMessageWithKey: (NSString *) messageKey
|
||||
{
|
||||
/* Update predecessor change list property given the change key. It
|
||||
returns if the change key has been added to the list or not */
|
||||
BOOL added = NO;
|
||||
NSData *globCnt, *oldGlobCnt;
|
||||
NSDictionary *messageEntry;
|
||||
NSMutableDictionary *changeList;
|
||||
NSString *guid;
|
||||
struct XID *xid;
|
||||
|
||||
xid = [changeKey asXIDInMemCtx: NULL];
|
||||
guid = [NSString stringWithGUID: &xid->NameSpaceGuid];
|
||||
globCnt = [NSData dataWithBytes: xid->LocalId.data length: xid->LocalId.length];
|
||||
talloc_free (xid);
|
||||
|
||||
messageEntry = [self _messageEntryFromMessageKey: messageKey];
|
||||
if (messageEntry)
|
||||
{
|
||||
changeList = [messageEntry objectForKey: @"PredecessorChangeList"];
|
||||
if (changeList)
|
||||
{
|
||||
oldGlobCnt = [changeList objectForKey: guid];
|
||||
if (!oldGlobCnt || ([globCnt compare: oldGlobCnt] == NSOrderedDescending))
|
||||
{
|
||||
[changeList setObject: globCnt forKey: guid];
|
||||
[versionsMessage save];
|
||||
added = YES;
|
||||
}
|
||||
}
|
||||
else
|
||||
[self errorWithFormat: @"Missing predecessor change list to update"];
|
||||
}
|
||||
|
||||
return added;
|
||||
}
|
||||
|
||||
- (NSData *) changeKeyForMessageWithKey: (NSString *) messageKey
|
||||
{
|
||||
NSDictionary *messages, *changeKeyDict;
|
||||
|
@ -1217,6 +1298,7 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP)
|
|||
fromFolder: (MAPIStoreFolder *) sourceFolder
|
||||
withMIDs: (uint64_t *) targetMids
|
||||
andChangeKeys: (struct Binary_r **) targetChangeKeys
|
||||
andPredecessorChangeLists: (struct Binary_r **) targetPredecessorChangeLists
|
||||
wantCopy: (uint8_t) wantCopy
|
||||
inMemCtx: (TALLOC_CTX *) memCtx
|
||||
|
||||
|
@ -1231,12 +1313,13 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP)
|
|||
NSDictionary *result;
|
||||
NSUInteger count;
|
||||
NSArray *a;
|
||||
NSData *changeKey;
|
||||
NSData *changeList;
|
||||
|
||||
if (![sourceFolder isKindOfClass: [MAPIStoreMailFolder class]])
|
||||
return [super moveCopyMessagesWithMIDs: srcMids andCount: midCount
|
||||
fromFolder: sourceFolder withMIDs: targetMids
|
||||
andChangeKeys: targetChangeKeys
|
||||
andPredecessorChangeLists: targetPredecessorChangeLists
|
||||
wantCopy: wantCopy
|
||||
inMemCtx: memCtx];
|
||||
|
||||
|
@ -1325,11 +1408,11 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP)
|
|||
[self synchroniseCache];
|
||||
for (count = 0; count < midCount; count++)
|
||||
{
|
||||
changeKey = [NSData dataWithBinary: targetChangeKeys[count]];
|
||||
changeList = [NSData dataWithBinary: targetPredecessorChangeLists[count]];
|
||||
messageKey = [NSString stringWithFormat: @"%@.eml",
|
||||
[destUIDs objectAtIndex: count]];
|
||||
[self setChangeKey: changeKey
|
||||
forMessageWithKey: messageKey];
|
||||
[self _updatePredecessorChangeListWith: changeList
|
||||
forMessageEntry: [self _messageEntryFromMessageKey: messageKey]];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -40,6 +40,7 @@
|
|||
#import <Mailer/NSData+Mail.h>
|
||||
#import <Mailer/SOGoMailBodyPart.h>
|
||||
#import <Mailer/SOGoMailObject.h>
|
||||
#import <Mailer/NSDictionary+Mail.h>
|
||||
|
||||
#import "Codepages.h"
|
||||
#import "NSData+MAPIStore.h"
|
||||
|
@ -1566,12 +1567,9 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
|||
withPrefix: (NSString *) keyPrefix
|
||||
{
|
||||
NSArray *parts;
|
||||
NSDictionary *parameters;
|
||||
NSUInteger count, max;
|
||||
|
||||
parameters = [[bodyInfo objectForKey: @"disposition"]
|
||||
objectForKey: @"parameterList"];
|
||||
if ([[parameters objectForKey: @"filename"] length] > 0)
|
||||
if ([[bodyInfo filename] length] > 0)
|
||||
{
|
||||
if ([keyPrefix length] == 0)
|
||||
keyPrefix = @"0";
|
||||
|
@ -1645,15 +1643,13 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
|||
return attachment;
|
||||
}
|
||||
|
||||
- (int) setReadFlag: (uint8_t) flag
|
||||
- (enum mapistore_error) setReadFlag: (uint8_t) flag
|
||||
{
|
||||
NSString *imapFlag = @"\\Seen";
|
||||
|
||||
/* TODO: notifications should probably be emitted from here */
|
||||
if (flag & CLEAR_READ_FLAG)
|
||||
[sogoObject removeFlags: imapFlag];
|
||||
[properties setObject: [NSNumber numberWithBool: NO] forKey: @"read_flag_set"];
|
||||
else
|
||||
[sogoObject addFlags: imapFlag];
|
||||
[properties setObject: [NSNumber numberWithBool: YES] forKey: @"read_flag_set"];
|
||||
|
||||
return MAPISTORE_SUCCESS;
|
||||
}
|
||||
|
@ -1696,7 +1692,10 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
|||
|
||||
- (void) save: (TALLOC_CTX *) memCtx
|
||||
{
|
||||
BOOL modified = NO;
|
||||
BOOL seen, storedSeenFlag;
|
||||
NSNumber *value;
|
||||
NSString *imapFlag = @"\\Seen";
|
||||
|
||||
value = [properties objectForKey: MAPIPropertyKey (PR_FLAG_STATUS)];
|
||||
if (value)
|
||||
|
@ -1706,8 +1705,35 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
|||
[sogoObject addFlags: @"\\Flagged"];
|
||||
else /* 0: unflagged, 1: follow up complete */
|
||||
[sogoObject removeFlags: @"\\Flagged"];
|
||||
|
||||
modified = YES;
|
||||
}
|
||||
|
||||
/* Manage seen flag on save */
|
||||
value = [properties objectForKey: @"read_flag_set"];
|
||||
if (value)
|
||||
{
|
||||
seen = [value boolValue];
|
||||
storedSeenFlag = [[[sogoObject fetchCoreInfos] objectForKey: @"flags"] containsObject: @"seen"];
|
||||
/* We modify the flags anyway to generate a new change number */
|
||||
if (seen)
|
||||
{
|
||||
if (storedSeenFlag)
|
||||
[sogoObject removeFlags: imapFlag];
|
||||
[sogoObject addFlags: imapFlag];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!storedSeenFlag)
|
||||
[sogoObject addFlags: imapFlag];
|
||||
[sogoObject removeFlags: imapFlag];
|
||||
}
|
||||
modified = YES;
|
||||
}
|
||||
|
||||
if (modified)
|
||||
[(MAPIStoreMailFolder *)[self container] synchroniseCache];
|
||||
|
||||
if (mailIsSharingObject)
|
||||
[[self _sharingObject] saveWithMessage: self
|
||||
andSOGoObject: sogoObject];
|
||||
|
|
|
@ -161,16 +161,31 @@ static Class MAPIStoreMailMessageK, NSDataK, NSStringK;
|
|||
//[self logWithFormat: @"change number from oxcfxics: %.16lx", [value unsignedLongLongValue]];
|
||||
//[self logWithFormat: @" modseq: %.16lx", [modseq unsignedLongLongValue]];
|
||||
if (modseq)
|
||||
{
|
||||
if (res->relop == RELOP_GT)
|
||||
modseq = [NSNumber numberWithUnsignedLongLong:
|
||||
[modseq unsignedLongLongValue] + 1];
|
||||
|
||||
}
|
||||
else
|
||||
modseq = [NSNumber numberWithUnsignedLongLong: 0];
|
||||
|
||||
if (res->relop == RELOP_GT || res->relop == RELOP_GE)
|
||||
{
|
||||
*qualifier = [[EOKeyValueQualifier alloc] initWithKey: @"MODSEQ"
|
||||
operatorSelector: EOQualifierOperatorGreaterThanOrEqualTo
|
||||
value: modseq];
|
||||
[*qualifier autorelease];
|
||||
rc = MAPIRestrictionStateNeedsEval;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Ignore other operations as IMAP only support MODSEQ >= X */
|
||||
[self warnWithFormat: @"Ignoring %@ as only supported operators are > and >=",
|
||||
[self operatorFromRestrictionOperator: res->relop]];
|
||||
rc = MAPIRestrictionStateAlwaysTrue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
#import <NGExtensions/NGBase64Coding.h>
|
||||
#import <NGExtensions/NGHashMap.h>
|
||||
#import <NGExtensions/NSObject+Logs.h>
|
||||
#import <NGExtensions/NSObject+Values.h>
|
||||
#import <NGExtensions/NSString+Encoding.h>
|
||||
#import <NGMime/NGMimeBodyPart.h>
|
||||
#import <NGMime/NGMimeMultipartBody.h>
|
||||
|
@ -166,9 +167,9 @@ static NSString *recTypes[] = { @"orig", @"to", @"cc", @"bcc" };
|
|||
|
||||
- (BOOL) hasContentId
|
||||
{
|
||||
return ([properties
|
||||
objectForKey: MAPIPropertyKey (PR_ATTACH_CONTENT_ID_UNICODE)]
|
||||
!= nil);
|
||||
NSString *contentId = [properties
|
||||
objectForKey: MAPIPropertyKey (PR_ATTACH_CONTENT_ID_UNICODE)];
|
||||
return contentId && [contentId length] > 0;
|
||||
}
|
||||
|
||||
- (NGMimeBodyPart *) asMIMEBodyPart
|
||||
|
@ -232,7 +233,7 @@ static NSString *recTypes[] = { @"orig", @"to", @"cc", @"bcc" };
|
|||
[map addObject: contentDisposition forKey: @"content-disposition"];
|
||||
contentId = [properties
|
||||
objectForKey: MAPIPropertyKey (PR_ATTACH_CONTENT_ID_UNICODE)];
|
||||
if (contentId)
|
||||
if (contentId && [contentId length] > 0)
|
||||
[map setObject: [NSString stringWithFormat: @"<%@>", contentId]
|
||||
forKey: @"content-id"];
|
||||
bodyPart = [NGMimeBodyPart bodyPartWithHeader: map];
|
||||
|
@ -285,7 +286,7 @@ static NSString *recTypes[] = { @"orig", @"to", @"cc", @"bcc" };
|
|||
version = [properties objectForKey: @"version"];
|
||||
|
||||
return (version
|
||||
? exchange_globcnt ([version unsignedLongLongValue])
|
||||
? [version unsignedLongLongValue]
|
||||
: ULLONG_MAX);
|
||||
}
|
||||
|
||||
|
@ -1110,7 +1111,8 @@ MakeMessageBody (NSDictionary *mailProperties, NSDictionary *attachmentParts, NS
|
|||
|
||||
- (void) save: (TALLOC_CTX *) memCtx
|
||||
{
|
||||
NSString *folderName, *flag, *newIdString, *messageKey;
|
||||
BOOL updatedMetadata;
|
||||
NSString *folderName, *flag, *newIdString, *messageKey, *changeNumber;
|
||||
NSData *changeKey, *messageData;
|
||||
NGImap4Connection *connection;
|
||||
NGImap4Client *client;
|
||||
|
@ -1146,21 +1148,33 @@ MakeMessageBody (NSDictionary *mailProperties, NSDictionary *attachmentParts, NS
|
|||
[sogoObject setNameInContainer: messageKey];
|
||||
[mapping registerURL: [self url] withID: mid];
|
||||
|
||||
/* synchronise the cache and update the change key with the one provided
|
||||
by the client. Before doing this, lets issue a unselect/select combo
|
||||
because of timing issues with Dovecot in obtaining the latest modseq.
|
||||
Sometimes, Dovecot doesn't return the newly appended UID if we do
|
||||
a "UID SORT (DATE) UTF-8 (MODSEQ XYZ) (NOT DELETED)" command (where
|
||||
XYZ is the HIGHESTMODSEQ+1) immediately after IMAP APPEND */
|
||||
/* synchronise the cache and update the predecessor change list
|
||||
with the change key provided by the client. Before doing
|
||||
this, lets issue a unselect/select combo because of timing
|
||||
issues with Dovecot in obtaining the latest modseq.
|
||||
Sometimes, Dovecot doesn't return the newly appended UID if
|
||||
we do a "UID SORT (DATE) UTF-8 (MODSEQ XYZ) (NOT DELETED)"
|
||||
command (where XYZ is the HIGHESTMODSEQ+1) immediately after
|
||||
IMAP APPEND */
|
||||
[client unselect];
|
||||
[client select: folderName];
|
||||
|
||||
[(MAPIStoreMailFolder *) container synchroniseCache];
|
||||
changeKey = [properties objectForKey: MAPIPropertyKey (PR_CHANGE_KEY)];
|
||||
if (changeKey)
|
||||
[(MAPIStoreMailFolder *) container
|
||||
setChangeKey: changeKey
|
||||
{
|
||||
updatedMetadata = [(MAPIStoreMailFolder *) container updatePredecessorChangeListWith: changeKey
|
||||
forMessageWithKey: messageKey];
|
||||
if (!updatedMetadata)
|
||||
[self warnWithFormat: @"Predecessor change list not updated with client data"];
|
||||
}
|
||||
|
||||
/* Update version property (PR_CHANGE_KEY indeed) as it is
|
||||
requested once it is saved */
|
||||
changeNumber = [(MAPIStoreMailFolder *) container changeNumberForMessageUID: newIdString];
|
||||
if (changeNumber)
|
||||
[properties setObject: [NSNumber numberWithUnsignedLongLong: [changeNumber unsignedLongLongValue] >> 16]
|
||||
forKey: @"version"];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -63,7 +63,7 @@
|
|||
withAID: (uint32_t) aid;
|
||||
- (int) getAttachmentTable: (MAPIStoreAttachmentTable **) tablePtr
|
||||
andRowCount: (uint32_t *) countPtr;
|
||||
- (int) setReadFlag: (uint8_t) flag;
|
||||
- (enum mapistore_error) setReadFlag: (uint8_t) flag;
|
||||
- (enum mapistore_error) saveMessage: (TALLOC_CTX *) memCtx;
|
||||
|
||||
- (NSArray *) activeContainerMessageTables;
|
||||
|
|
|
@ -549,11 +549,12 @@ rtf2html (NSData *compressedRTF)
|
|||
}
|
||||
|
||||
[self save: memCtx];
|
||||
/* We make sure that any change-related properties are removes from the
|
||||
/* We make sure that any change-related properties are removed from the
|
||||
properties dictionary, to make sure that related methods will be
|
||||
invoked the next time they are requested. */
|
||||
[properties removeObjectForKey: MAPIPropertyKey (PidTagChangeKey)];
|
||||
[properties removeObjectForKey: MAPIPropertyKey (PidTagChangeNumber)];
|
||||
[properties removeObjectForKey: MAPIPropertyKey (PidTagPredecessorChangeList)];
|
||||
|
||||
if ([container isKindOfClass: MAPIStoreFolderK])
|
||||
{
|
||||
|
@ -918,7 +919,7 @@ rtf2html (NSData *compressedRTF)
|
|||
return [self getNo: data inMemCtx: memCtx];;
|
||||
}
|
||||
|
||||
- (int) setReadFlag: (uint8_t) flag
|
||||
- (enum mapistore_error) setReadFlag: (uint8_t) flag
|
||||
{
|
||||
// [self subclassResponsibility: _cmd];
|
||||
|
||||
|
|
|
@ -674,6 +674,7 @@ sogo_folder_move_copy_messages(void *folder_object,
|
|||
uint32_t mid_count,
|
||||
uint64_t *src_mids, uint64_t *t_mids,
|
||||
struct Binary_r **target_change_keys,
|
||||
struct Binary_r **target_predecessor_change_lists,
|
||||
uint8_t want_copy)
|
||||
{
|
||||
MAPIStoreFolder *sourceFolder, *targetFolder;
|
||||
|
@ -698,6 +699,7 @@ sogo_folder_move_copy_messages(void *folder_object,
|
|||
fromFolder: sourceFolder
|
||||
withMIDs: t_mids
|
||||
andChangeKeys: target_change_keys
|
||||
andPredecessorChangeLists: target_predecessor_change_lists
|
||||
wantCopy: want_copy
|
||||
inMemCtx: mem_ctx];
|
||||
TRYCATCH_END(pool)
|
||||
|
@ -1118,7 +1120,7 @@ sogo_message_set_read_flag (void *message_object, uint8_t flag)
|
|||
struct MAPIStoreTallocWrapper *wrapper;
|
||||
NSAutoreleasePool *pool;
|
||||
MAPIStoreMessage *message;
|
||||
int rc;
|
||||
enum mapistore_error rc;
|
||||
|
||||
if (message_object)
|
||||
{
|
||||
|
|
|
@ -531,7 +531,7 @@
|
|||
}
|
||||
[vToDo setTimeStampAsDate: now];
|
||||
|
||||
[sogoObject saveComponent: vCalendar];
|
||||
[sogoObject saveCalendar: vCalendar];
|
||||
|
||||
[self updateVersions];
|
||||
}
|
||||
|
|
|
@ -41,6 +41,8 @@
|
|||
|
||||
+ (id) dataWithXID: (const struct XID *) xid;
|
||||
- (struct XID *) asXIDInMemCtx: (void *) memCtx;
|
||||
- (struct SizedXid *) asSizedXidArrayInMemCtx: (void *) memCtx
|
||||
with: (uint32_t *) length;
|
||||
|
||||
+ (id) dataWithChangeKeyGUID: (NSString *) guidString
|
||||
andCnt: (NSData *) globCnt;
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
|
||||
#import <NGExtensions/NSObject+Logs.h>
|
||||
|
||||
#import "MAPIStoreTypes.h"
|
||||
#import "NSObject+MAPIStore.h"
|
||||
#import "NSString+MAPIStore.h"
|
||||
|
||||
|
@ -29,6 +30,7 @@
|
|||
|
||||
#undef DEBUG
|
||||
#include <stdbool.h>
|
||||
#include <libmapi/libmapi.h>
|
||||
#include <talloc.h>
|
||||
#include <util/time.h>
|
||||
#include <gen_ndr/exchange.h>
|
||||
|
@ -136,11 +138,11 @@ static void _fillFlatUIDWithGUID (struct FlatUID_r *flatUID, const struct GUID *
|
|||
NSMutableData *xidData;
|
||||
struct FlatUID_r flatUID;
|
||||
|
||||
_fillFlatUIDWithGUID (&flatUID, &xid->GUID);
|
||||
_fillFlatUIDWithGUID (&flatUID, &xid->NameSpaceGuid);
|
||||
|
||||
xidData = [NSMutableData dataWithCapacity: 16 + xid->Size];
|
||||
xidData = [NSMutableData dataWithCapacity: 16 + xid->LocalId.length];
|
||||
[xidData appendBytes: flatUID.ab length: 16];
|
||||
[xidData appendBytes: xid->Data length: xid->Size];
|
||||
[xidData appendBytes: xid->LocalId.data length: xid->LocalId.length];
|
||||
|
||||
return xidData;
|
||||
}
|
||||
|
@ -156,12 +158,12 @@ static void _fillFlatUIDWithGUID (struct FlatUID_r *flatUID, const struct GUID *
|
|||
{
|
||||
xid = talloc_zero (memCtx, struct XID);
|
||||
|
||||
[self _extractGUID: &xid->GUID];
|
||||
[self _extractGUID: &xid->NameSpaceGuid];
|
||||
|
||||
xid->Size = max - 16;
|
||||
xid->LocalId.length = max - 16;
|
||||
|
||||
bytes = (uint8_t *) [self bytes];
|
||||
xid->Data = talloc_memdup (xid, (bytes+16), xid->Size);
|
||||
xid->LocalId.data = talloc_memdup (xid, (bytes+16), xid->LocalId.length);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -172,6 +174,38 @@ static void _fillFlatUIDWithGUID (struct FlatUID_r *flatUID, const struct GUID *
|
|||
return xid;
|
||||
}
|
||||
|
||||
- (struct SizedXid *) asSizedXidArrayInMemCtx: (void *) memCtx
|
||||
with: (uint32_t *) length
|
||||
{
|
||||
struct Binary_r bin;
|
||||
struct SizedXid *sizedXIDArray;
|
||||
|
||||
bin.cb = [self length];
|
||||
bin.lpb = (uint8_t *)[self bytes];
|
||||
|
||||
sizedXIDArray = get_SizedXidArray(memCtx, &bin, length);
|
||||
if (!sizedXIDArray)
|
||||
{
|
||||
NSLog (@"Impossible to parse SizedXID array");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return sizedXIDArray;
|
||||
}
|
||||
|
||||
- (NSComparisonResult) compare: (NSData *) otherGlobCnt
|
||||
{
|
||||
uint64_t globCnt = 0, oGlobCnt = 0;
|
||||
|
||||
if ([self length] > 0)
|
||||
globCnt = *(uint64_t *) [self bytes];
|
||||
|
||||
if ([otherGlobCnt length] > 0)
|
||||
oGlobCnt = *(uint64_t *) [otherGlobCnt bytes];
|
||||
|
||||
return MAPICNCompare (globCnt, oGlobCnt, NULL);
|
||||
}
|
||||
|
||||
+ (id) dataWithChangeKeyGUID: (NSString *) guidString
|
||||
andCnt: (NSData *) globCnt;
|
||||
{
|
||||
|
|
|
@ -500,7 +500,7 @@ static NSTimeInterval ChannelCollectionTimer = 5 * 60;
|
|||
ms = [NSMutableString stringWithCapacity: 256];
|
||||
[ms appendFormat: @"<0x%p[%@]: ", self, NSStringFromClass ([self class])];
|
||||
|
||||
[ms appendFormat: @" #adaptors=%d", [urlToAdaptor count]];
|
||||
[ms appendFormat: @" #adaptors=%d", (int)[urlToAdaptor count]];
|
||||
|
||||
[ms appendString: @">"];
|
||||
return ms;
|
||||
|
|
|
@ -414,7 +414,7 @@
|
|||
if (group)
|
||||
[str appendFormat: @"%@ (group: %@)\n", tag, group];
|
||||
else
|
||||
[str appendFormat: @"%@\n", tag, group];
|
||||
[str appendFormat: @"%@\n", tag];
|
||||
|
||||
[str appendString: [self versitString]];
|
||||
|
||||
|
|
|
@ -409,7 +409,7 @@ static NGCardsSaxHandler *sax = nil;
|
|||
max = [children count];
|
||||
if (max > 0)
|
||||
{
|
||||
[str appendFormat: @"\n %d children: {\n", [children count]];
|
||||
[str appendFormat: @"\n %d children: {\n", (int)[children count]];
|
||||
for (count = 0; count < max; count++)
|
||||
[str appendFormat: @" %@\n",
|
||||
[[children objectAtIndex: count] description]];
|
||||
|
|
|
@ -32,16 +32,16 @@
|
|||
- (NSString *) iCalFormattedDateTimeString
|
||||
{
|
||||
return [NSString stringWithFormat: @"%.4d%.2d%.2dT%.2d%.2d%.2d",
|
||||
[self yearOfCommonEra], [self monthOfYear],
|
||||
[self dayOfMonth], [self hourOfDay],
|
||||
[self minuteOfHour], [self secondOfMinute]];
|
||||
(int)[self yearOfCommonEra], (int)[self monthOfYear],
|
||||
(int)[self dayOfMonth], (int)[self hourOfDay],
|
||||
(int)[self minuteOfHour], (int)[self secondOfMinute]];
|
||||
}
|
||||
|
||||
- (NSString *) iCalFormattedDateString
|
||||
{
|
||||
return [NSString stringWithFormat: @"%.4d%.2d%.2d",
|
||||
[self yearOfCommonEra], [self monthOfYear],
|
||||
[self dayOfMonth]];
|
||||
(int)[self yearOfCommonEra], (int)[self monthOfYear],
|
||||
(int)[self dayOfMonth]];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -158,16 +158,31 @@
|
|||
NSMutableString *string;
|
||||
unsigned int len, i;
|
||||
unichar c;
|
||||
BOOL isQuoted;
|
||||
|
||||
len = [self length];
|
||||
string = [NSMutableString stringWithCapacity: len * 1.5];
|
||||
isQuoted = NO;
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
{
|
||||
c = [self characterAtIndex: i];
|
||||
|
||||
if (isQuoted)
|
||||
{
|
||||
if (c == '"')
|
||||
isQuoted = NO;
|
||||
|
||||
[string appendFormat: @"%C", c];
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (c)
|
||||
{
|
||||
case '"':
|
||||
isQuoted = YES;
|
||||
[string appendFormat: @"%C", c];
|
||||
break;
|
||||
case '\\':
|
||||
[string appendString: @"\\\\"];
|
||||
break;
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
#import "NSCalendarDate+NGCards.h"
|
||||
|
||||
#import "iCalAlarm.h"
|
||||
#import "iCalCalendar.h"
|
||||
#import "iCalDateTime.h"
|
||||
#import "iCalEntityObject.h"
|
||||
#import "iCalEvent.h"
|
||||
|
@ -268,9 +269,12 @@
|
|||
- (void) setRecurrenceId: (NSCalendarDate *) newRecId
|
||||
{
|
||||
iCalDateTime* recurrenceId;
|
||||
BOOL isMasterAllDay;
|
||||
|
||||
isMasterAllDay = [[[[self parent] events] objectAtIndex: 0] isAllDay];
|
||||
|
||||
recurrenceId = (iCalDateTime *) [self uniqueChildWithTag: @"recurrence-id"];
|
||||
if ([self isKindOfClass: [iCalEvent class]] && [(iCalEvent *)self isAllDay])
|
||||
if ([self isKindOfClass: [iCalEvent class]] && isMasterAllDay)
|
||||
[recurrenceId setDate: newRecId];
|
||||
else
|
||||
[recurrenceId setDateTime: newRecId];
|
||||
|
|
|
@ -348,7 +348,7 @@ static inline unsigned iCalDoWForNSDoW (int dow)
|
|||
if ([byDayMask occursOnDay: currentWeekDay])
|
||||
{
|
||||
if ([bySetPos containsObject:
|
||||
[NSString stringWithFormat: @"%d", currentPos]])
|
||||
[NSString stringWithFormat: @"%d", (int)currentPos]])
|
||||
monthDays[monthDay+1] = YES;
|
||||
currentPos++;
|
||||
}
|
||||
|
@ -362,7 +362,7 @@ static inline unsigned iCalDoWForNSDoW (int dow)
|
|||
if ([byDayMask occursOnDay: currentWeekDay])
|
||||
{
|
||||
if ([bySetPos containsObject:
|
||||
[NSString stringWithFormat: @"%d", currentPos]])
|
||||
[NSString stringWithFormat: @"%d", (int)currentPos]])
|
||||
monthDays[monthDay] = YES;
|
||||
currentPos--;
|
||||
}
|
||||
|
|
|
@ -267,7 +267,7 @@
|
|||
- (BOOL)isEqual:(id)_other {
|
||||
if(_other == nil)
|
||||
return NO;
|
||||
if([_other class] != self->isa)
|
||||
if([_other class] != object_getClass(self))
|
||||
return NO;
|
||||
if([_other hash] != [self hash])
|
||||
return NO;
|
||||
|
|
|
@ -309,10 +309,10 @@ NSString *iCalWeekDayString[] = { @"SU", @"MO", @"TU", @"WE", @"TH", @"FR",
|
|||
else if ([frequency isEqualToString:@"SECONDLY"])
|
||||
freq = iCalRecurrenceFrequenceSecondly;
|
||||
else
|
||||
freq = NSNotFound;
|
||||
freq = (iCalRecurrenceFrequency) NSNotFound;
|
||||
}
|
||||
else
|
||||
freq = NSNotFound;
|
||||
freq = (iCalRecurrenceFrequency) NSNotFound;
|
||||
|
||||
return freq;
|
||||
}
|
||||
|
|
|
@ -161,8 +161,11 @@
|
|||
minute: [tzStart minuteOfHour] second: 0
|
||||
timeZone: [NSTimeZone timeZoneWithName: @"GMT"]];
|
||||
|
||||
tmpDate = [tmpDate addYear: 0 month: ((pos > 0) ? 0 : 1)
|
||||
day: 0 hour: 0 minute: 0
|
||||
tmpDate = [tmpDate addYear: 0
|
||||
month: ((pos > 0) ? 0 : 1)
|
||||
day: 0
|
||||
hour: 0
|
||||
minute: 0
|
||||
second: 0];
|
||||
|
||||
/* If the day of the time change is "-XSU", we need to determine whether the
|
||||
|
@ -197,12 +200,41 @@
|
|||
END:STANDARD
|
||||
END:VTIMEZONE
|
||||
|
||||
The time changes occure on a Sunday, but in March, the 1st is a Sunday itself and in November
|
||||
The time changes occur on a Sunday, but in March, the 1st is a Sunday itself and in November
|
||||
the 1st is also a Sunday. If we don't decrement "pos" by one, tmpDate (which is set to March or November 1st
|
||||
because of "day: 1" will have 14 more days added for March and 7 more days added for November - which will
|
||||
effectively shift the time change by a whole week.
|
||||
|
||||
In Europe/Berlin, we have a different use-case for November. In 2015, November 1st is a Sunday.
|
||||
The time change in November must occur on October 25th but since tmpDate will be November 1st,
|
||||
so a Sunday, dateDayOfWeek will be 0 and dayOfWeek will also be 0 we would decrement tmpDate by 14 days,
|
||||
which is incorrect because it would shift the timezone change one week earlier. We take care about this
|
||||
one with check if pos is greater or equal than 0 and if so, we don't decrement it.
|
||||
|
||||
BEGIN:VCALENDAR
|
||||
PRODID:-//Inverse inc.//NONSGML Olson 2014g//EN
|
||||
VERSION:2.0
|
||||
BEGIN:VTIMEZONE
|
||||
TZID:Europe/Berlin
|
||||
X-LIC-LOCATION:Europe/Berlin
|
||||
BEGIN:DAYLIGHT
|
||||
TZOFFSETFROM:+0100
|
||||
TZOFFSETTO:+0200
|
||||
TZNAME:CEST
|
||||
DTSTART:19700329T020000
|
||||
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
|
||||
END:DAYLIGHT
|
||||
BEGIN:STANDARD
|
||||
TZOFFSETFROM:+0200
|
||||
TZOFFSETTO:+0100
|
||||
TZNAME:CET
|
||||
DTSTART:19701025T030000
|
||||
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
|
||||
END:STANDARD
|
||||
END:VTIMEZONE
|
||||
END:VCALENDAR
|
||||
*/
|
||||
if (dayOfWeek == dateDayOfWeek)
|
||||
if (dayOfWeek == dateDayOfWeek && pos >= 0)
|
||||
pos--;
|
||||
|
||||
offset = (dayOfWeek - dateDayOfWeek) + (pos * 7);
|
||||
|
|
|
@ -57,7 +57,7 @@
|
|||
- (NSDictionary *) asDictionary
|
||||
{
|
||||
NSDictionary *data;
|
||||
NSString *duration, *relation, *reference, *quantity, *unit;
|
||||
NSString *duration, *relation, *reference, *quantity=@"", *unit=@"";
|
||||
NSUInteger i;
|
||||
unichar c;
|
||||
|
||||
|
|
|
@ -1045,7 +1045,7 @@ static NSCharacterSet *whitespaceCharSet = nil;
|
|||
if (debugOn)
|
||||
{
|
||||
NSLog(@"%s: trying to decode data (0x%p,len=%d) ...",
|
||||
__PRETTY_FUNCTION__, _data, [_data length]);
|
||||
__PRETTY_FUNCTION__, _data, (int)[_data length]);
|
||||
}
|
||||
|
||||
if ((len = [_data length]) == 0)
|
||||
|
@ -1164,7 +1164,7 @@ static NSCharacterSet *whitespaceCharSet = nil;
|
|||
if (debugOn)
|
||||
{
|
||||
NSLog(@"%s: trying to parse string (0x%p,len=%d) ...",
|
||||
__PRETTY_FUNCTION__, _source, [_source length]);
|
||||
__PRETTY_FUNCTION__, _source, (int)[_source length]);
|
||||
}
|
||||
if (!_sysId) _sysId = @"<string>";
|
||||
[self _parseString: _source];
|
||||
|
|
|
@ -1,309 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import getopt
|
||||
import imaplib
|
||||
import ldb
|
||||
import plistlib
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from samba.param import LoadParm
|
||||
|
||||
imaphost = '127.0.0.1'
|
||||
imapport = 143
|
||||
|
||||
samba_lp = LoadParm()
|
||||
sambaprivate = samba_lp.get("private dir")
|
||||
mapistorefolder = samba_lp.private_path("mapistore")
|
||||
sogoSysDefaultsFile = "/etc/sogo/sogo.conf"
|
||||
sogoUserDefaultsFile = os.path.expanduser("~sogo/GNUstep/Defaults/.GNUstepDefaults")
|
||||
|
||||
# - takes a username and optionally its password
|
||||
# - removes the entry in samba's ldap tree via ldbedit (NOTYET)
|
||||
# - remove the user's directory under mapistore/ and mapistore/SOGo
|
||||
# - cleanup Junk Folders and Sync Issues imap folders
|
||||
# - Delete the sogo_cache_folder_ table for the username.
|
||||
|
||||
def usage():
|
||||
print """
|
||||
%s [-i imaphost] ] [-p imapport] [-s sambaprivate] username [password]
|
||||
-i imaphost IMAP host to connect to [%s]
|
||||
-p imappost IMAP port to use [%d]
|
||||
-s sambaprivate samba private directory [%s]
|
||||
""" % (os.path.basename(sys.argv[0]), imaphost, imapport, sambaprivate)
|
||||
|
||||
def main():
|
||||
global sambaprivate
|
||||
global mapistorefolder
|
||||
global imaphost
|
||||
global imapport
|
||||
try:
|
||||
opts, args = getopt.getopt(sys.argv[1:], "i:p:s:")
|
||||
except getopt.GetoptError, err:
|
||||
print str(err)
|
||||
usage()
|
||||
sys.exit(2)
|
||||
|
||||
for o, a in opts:
|
||||
if o == "-i":
|
||||
imaphost = a
|
||||
elif o == "-p":
|
||||
imapport = a
|
||||
elif o == "-s":
|
||||
sambaprivate = a
|
||||
mapistorefolder = "%s/mapistore" % (sambaprivate)
|
||||
else:
|
||||
assert False, "unhandled option"
|
||||
|
||||
argslen = len(args)
|
||||
if (argslen == 2):
|
||||
username = args[0]
|
||||
userpass = args[1]
|
||||
elif (argslen == 1):
|
||||
username = args[0]
|
||||
userpass = username
|
||||
print "Using username as password"
|
||||
else:
|
||||
usage()
|
||||
print "Specify a user (and optionally the password)"
|
||||
sys.exit(2)
|
||||
|
||||
# cleanup starts here
|
||||
try:
|
||||
imapCleanup(imaphost, imapport, username, userpass)
|
||||
except Exception as e:
|
||||
print "Error during imapCleanup, continuing: %s" % str(e)
|
||||
|
||||
try:
|
||||
mapistoreCleanup(mapistorefolder, username)
|
||||
except (shutil.Error, OSError) as e:
|
||||
print "Error during mapistoreCleanup, continuing: %s" % str(e)
|
||||
|
||||
try:
|
||||
ldbCleanup(sambaprivate, username)
|
||||
except ldb.LdbError as e:
|
||||
print "Error during ldbCleanup, continuing: %s" % str(e)
|
||||
|
||||
try:
|
||||
sqlCleanup(username)
|
||||
except Exception as e:
|
||||
print "Error during sqlCleanup, continuing: %s" % str(e)
|
||||
|
||||
def getsep(client):
|
||||
seq = None
|
||||
(code, data) = client.list("", "")
|
||||
if code == "OK" and data is not None:
|
||||
# yes this is ugly but it works on cyrus and dovecot.
|
||||
# (\\Noinferiors \\HasNoChildren) "/" INBOX
|
||||
m = re.search(".*\s+[\"\']?(.)[\"\']?\s+[\"\']?.*[\"\']?$", data[0])
|
||||
sep = m.group(1)
|
||||
return sep
|
||||
|
||||
def extractmb(si):
|
||||
inparen = False
|
||||
inquote = False
|
||||
part = []
|
||||
parts = []
|
||||
|
||||
for char in si:
|
||||
if inparen:
|
||||
if char == ")":
|
||||
inparen = False
|
||||
parts.append("".join(part))
|
||||
else:
|
||||
part.append(char)
|
||||
elif inquote:
|
||||
if char == "\"":
|
||||
inquote = False
|
||||
parts.append("".join(part))
|
||||
else:
|
||||
part.append(char)
|
||||
else:
|
||||
if char == "(":
|
||||
inparen = True
|
||||
elif char == "\"":
|
||||
inquote = True
|
||||
elif char == " ":
|
||||
part = []
|
||||
else:
|
||||
part.append(char)
|
||||
|
||||
return parts[-1]
|
||||
|
||||
def cleanupmb(mb, sep, client):
|
||||
(code, data) = client.list("%s%s" % (mb, sep), "%")
|
||||
if code == "OK":
|
||||
for si in data:
|
||||
if si is not None:
|
||||
submb = extractmb(si)
|
||||
cleanupmb(submb, sep, client)
|
||||
else:
|
||||
raise Exception, "Failure while cleaning up '%s'" % mb
|
||||
client.unsubscribe(mb)
|
||||
(code, data) = client.delete(mb)
|
||||
if code == "OK":
|
||||
print "mailbox '%s' deleted" % mb
|
||||
else:
|
||||
print "mailbox '%s' coult NOT be deleted (code = '%s')" % (mb, code)
|
||||
|
||||
def imapCleanup(imaphost, imapport, username, userpass):
|
||||
print "Starting IMAP cleanup"
|
||||
client = imaplib.IMAP4(imaphost, imapport)
|
||||
(code, data) = client.login(username, userpass)
|
||||
if code != "OK":
|
||||
raise Exception, "Login failure"
|
||||
|
||||
print "Logged in as '%s'" % username
|
||||
|
||||
sep = getsep(client)
|
||||
if not sep:
|
||||
client.logout()
|
||||
return
|
||||
|
||||
for foldername in ("Sync Issues", "Junk E-mail",
|
||||
"INBOX%sSync Issues" % sep, "INBOX%sJunk E-mail" % sep,
|
||||
"Probl&AOg-mes de synchronisation"):
|
||||
(code, data) = client.list(foldername, "%")
|
||||
if code == "OK":
|
||||
for si in data:
|
||||
if si is not None:
|
||||
mb = extractmb(si)
|
||||
cleanupmb(mb, sep, client)
|
||||
client.logout()
|
||||
|
||||
def mapistoreCleanup(mapistorefolder, username):
|
||||
print "Starting MAPIstore cleanup"
|
||||
|
||||
# delete the user's folder under the mapistore and under mapistore/SOGo
|
||||
mapistoreUserDir = "%s/%s" % (mapistorefolder, username)
|
||||
for dirpath, dirnames, filenames in os.walk(mapistoreUserDir):
|
||||
for f in filenames:
|
||||
if f != "password":
|
||||
os.unlink("%s/%s" % (dirpath,f))
|
||||
break #one level only
|
||||
|
||||
shutil.rmtree("%s/SOGo/%s" % (mapistorefolder, username), ignore_errors=True)
|
||||
|
||||
def ldbCleanup(sambaprivate, username):
|
||||
conn = ldb.Ldb("%s/openchange.ldb" % (sambaprivate))
|
||||
# find the DN of the user
|
||||
entries = conn.search(None, expression="(cn=%s)" % (username), scope=ldb.SCOPE_SUBTREE)
|
||||
if not entries:
|
||||
print "cn = %s not found in openchange.ldb" %(username)
|
||||
return
|
||||
|
||||
for entry in entries:
|
||||
# search again, but with the user's DN as a base
|
||||
subentries = conn.search(entry.dn.extended_str(), expression="(distinguishedName=*)", scope=ldb.SCOPE_SUBTREE)
|
||||
for subentry in subentries:
|
||||
print "Deleting %s" % (subentry.dn)
|
||||
conn.delete(subentry.dn)
|
||||
|
||||
def mysqlCleanup(dbhost, dbport, dbuser, dbpass, dbname, username):
|
||||
try:
|
||||
import MySQLdb
|
||||
except ImportError:
|
||||
msg ="""The python 'MySQLdb' module is not available
|
||||
On Debian based distro, install it using 'apt-get install python-mysqlbd'
|
||||
On RHEL, install it using 'yum install MySQL-python'"""
|
||||
raise Exception(msg)
|
||||
|
||||
conn = MySQLdb.connect(host=dbhost, port=int(dbport), user=dbuser, passwd=dbpass, db=dbname)
|
||||
c=conn.cursor()
|
||||
tablename="sogo_cache_folder_%s" % (username)
|
||||
c.execute("TRUNCATE TABLE %s" % tablename)
|
||||
print "Table %s emptied" % tablename
|
||||
|
||||
|
||||
def postgresqlCleanup(dbhost, dbport, dbuser, dbpass, dbname, username):
|
||||
try:
|
||||
import pg
|
||||
except ImportError:
|
||||
msg ="""The python 'pg' module is not available
|
||||
On Debian based distro, install it using 'apt-get install python-pygresql'
|
||||
On RHEL, install it using 'yum install python-pgsql'"""
|
||||
raise Exception(msg)
|
||||
|
||||
conn = pg.connect(host=dbhost, port=int(dbport), user=dbuser, passwd=dbpass, dbname=dbname)
|
||||
tablename = "sogo_cache_folder_%s" % username
|
||||
conn.query("DELETE FROM %s" % tablename)
|
||||
print "Table '%s' emptied" % tablename
|
||||
|
||||
def getOCSFolderInfoURL():
|
||||
global sogoSysDefaultsFile, sogoUserDefaultsFile
|
||||
|
||||
OCSFolderInfoURL = ""
|
||||
|
||||
# read defaults from defaults files
|
||||
# order is important, user defaults must have precedence
|
||||
for f in [sogoSysDefaultsFile, sogoUserDefaultsFile]:
|
||||
if os.path.exists(f):
|
||||
# FIXME: this is ugly, we should have a python plist parser
|
||||
# plistlib only supports XML plists.
|
||||
# the following magic replaces this shell pipeline:
|
||||
# sogo-tool dump-defaults -f %s | awk -F\\" '/ OCSFolderInfoURL =/ {print $2}'
|
||||
p1 = subprocess.Popen(["sogo-tool", "dump-defaults", "-f", f], stdout=subprocess.PIPE)
|
||||
p2 = subprocess.Popen(["awk", "-F\"", "/ OCSFolderInfoURL =/ {print $2}"], stdin=p1.stdout, stdout=subprocess.PIPE)
|
||||
p1.stdout.close() # Allow p1 to receive a SIGPIPE if p2 exits.
|
||||
tmp = p2.communicate()[0]
|
||||
|
||||
if tmp: OCSFolderInfoURL = tmp
|
||||
|
||||
return OCSFolderInfoURL
|
||||
|
||||
def asCSSIdentifier(inputString):
|
||||
cssEscapingCharMap = {"_" : "_U_",
|
||||
"." : "_D_",
|
||||
"#" : "_H_",
|
||||
"@" : "_A_",
|
||||
"*" : "_S_",
|
||||
":" : "_C_",
|
||||
"," : "_CO_",
|
||||
" " : "_SP_",
|
||||
"'" : "_SQ_",
|
||||
"&" : "_AM_",
|
||||
"+" : "_P_"}
|
||||
|
||||
newChars = []
|
||||
|
||||
for c in inputString:
|
||||
if c in cssEscapingCharMap:
|
||||
newChars.append(cssEscapingCharMap[c])
|
||||
else:
|
||||
newChars.append(c)
|
||||
|
||||
return "".join(newChars)
|
||||
|
||||
def sqlCleanup(username):
|
||||
print "Starting SQL cleanup"
|
||||
OCSFolderInfoURL = getOCSFolderInfoURL()
|
||||
if OCSFolderInfoURL is None:
|
||||
raise Exception("Couldn't fetch OCSFolderInfoURL or it is not set. the sogo_cache_folder_%s table should be truncated manually" % (username))
|
||||
|
||||
# postgresql://sogo:sogo@127.0.0.1:5432/sogo/sogo_folder_info
|
||||
m = re.search("(.+)://(.+):(.+)@(.+):(\d+)/(.+)/(.+)", OCSFolderInfoURL)
|
||||
|
||||
if not m:
|
||||
raise Exception("Unable to parse OCSFolderInfoURL: %s" % OCSFolderInfoURL)
|
||||
|
||||
proto = m.group(1)
|
||||
dbuser = m.group(2)
|
||||
dbpass = m.group(3)
|
||||
dbhost = m.group(4)
|
||||
dbport = m.group(5)
|
||||
dbname = m.group(6)
|
||||
# 7 is folderinfo table
|
||||
|
||||
encodedUserName = asCSSIdentifier(username)
|
||||
|
||||
if (proto == "postgresql"):
|
||||
postgresqlCleanup(dbhost, dbport, dbuser, dbpass, dbname, encodedUserName)
|
||||
elif (proto == "mysql"):
|
||||
mysqlCleanup(dbhost, dbport, dbuser, dbpass, dbname, encodedUserName)
|
||||
else:
|
||||
raise Exception("Unknown sql proto: %s" % (proto))
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -344,7 +344,7 @@ size_t curl_body_function_freebusy(void *ptr, size_t size, size_t nmemb, void *i
|
|||
NSMutableString *s;
|
||||
|
||||
s = [NSMutableString stringWithCapacity: 64];
|
||||
[s appendFormat:@"<0x%08X[%@]:", self, NSStringFromClass([self class])];
|
||||
[s appendFormat:@"<0x%08X[%@]:", (unsigned int)self, NSStringFromClass([self class])];
|
||||
if (freeBusyViewType)
|
||||
[s appendFormat:@" freeBusyViewType='%@'", freeBusyViewType];
|
||||
if (mergedFreeBusy)
|
||||
|
|
|
@ -84,7 +84,7 @@
|
|||
#import "SOGoAppointmentFolders.h"
|
||||
#import "SOGoFreeBusyObject.h"
|
||||
#import "SOGoTaskObject.h"
|
||||
#import "SOGoWebAppointmentFolder.h";
|
||||
#import "SOGoWebAppointmentFolder.h"
|
||||
|
||||
#import "SOGoAppointmentFolder.h"
|
||||
|
||||
|
@ -1013,7 +1013,7 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
|||
{
|
||||
if ([dateRange containsDate: [component startDate]])
|
||||
{
|
||||
// We must pass nill to :container here in order to avoid re-entrancy issues.
|
||||
// We must pass nil to :container here in order to avoid re-entrancy issues.
|
||||
newRecord = [self _fixupRecord: [component quickRecordFromContent: nil container: nil]];
|
||||
[ma replaceObjectAtIndex: recordIndex withObject: newRecord];
|
||||
}
|
||||
|
@ -1030,8 +1030,9 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
|||
{
|
||||
// The recurrence id of the exception is outside the date range;
|
||||
// simply add the exception to the records array.
|
||||
// We must pass nill to :container here in order to avoid re-entrancy issues.
|
||||
// We must pass nil to :container here in order to avoid re-entrancy issues.
|
||||
newRecord = [self _fixupRecord: [component quickRecordFromContent: nil container: nil]];
|
||||
if ([newRecord objectForKey: @"startDate"] && [newRecord objectForKey: @"endDate"]) {
|
||||
newRecordRange = [NGCalendarDateRange
|
||||
calendarDateRangeWithStartDate: [newRecord objectForKey: @"startDate"]
|
||||
endDate: [newRecord objectForKey: @"endDate"]];
|
||||
|
@ -1039,6 +1040,10 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
|||
[ma addObject: newRecord];
|
||||
else
|
||||
newRecord = nil;
|
||||
} else {
|
||||
[self warnWithFormat: @"Recurrence %@ without dtstart or dtend. Ignoring", recurrenceId];
|
||||
newRecord = nil;
|
||||
}
|
||||
}
|
||||
|
||||
if (newRecord)
|
||||
|
@ -2337,7 +2342,7 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
|||
request = [context request];
|
||||
if (!([request isIPhone] || [request isICal4]))
|
||||
{
|
||||
gdRT = [self groupDavResourceType];
|
||||
gdRT = (NSArray *) [self groupDavResourceType];
|
||||
gdVEventCol = [NSArray arrayWithObjects: [gdRT objectAtIndex: 0],
|
||||
XMLNS_GROUPDAV, nil];
|
||||
[colType addObject: gdVEventCol];
|
||||
|
@ -2989,6 +2994,9 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
|||
theUser = [SOGoUser userWithLogin: theUID];
|
||||
aParent = [theUser calendarsFolderInContext: context];
|
||||
|
||||
if ([aParent isKindOfClass: [NSException class]])
|
||||
return nil;
|
||||
|
||||
aFolders = [aParent subFolders];
|
||||
e = [aFolders objectEnumerator];
|
||||
while ((aFolder = [e nextObject]))
|
||||
|
@ -3152,10 +3160,19 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
|||
NSMutableString *content;
|
||||
NSString *uid;
|
||||
|
||||
// We first look if there's an event with the same UID in our calendar. If not,
|
||||
// let's reuse what is in the event, otherwise generate a new GUID and use it.
|
||||
// We first look if the event has any / or + in its UID. If that's the case
|
||||
// we generate a new UID based on a GUID
|
||||
uid = [event uid];
|
||||
|
||||
if ([uid rangeOfCharacterFromSet: [NSCharacterSet characterSetWithCharactersInString: @"+/"]].location != NSNotFound)
|
||||
{
|
||||
uid = [self globallyUniqueObjectId];
|
||||
[event setUid: uid];
|
||||
}
|
||||
else
|
||||
{
|
||||
// We also look if there's an event with the same UID in our calendar. If not,
|
||||
// let's reuse what is in the event, otherwise generate a new GUID and use it.
|
||||
object = [self lookupName: uid
|
||||
inContext: context
|
||||
acquire: NO];
|
||||
|
@ -3165,6 +3182,7 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
|||
uid = [self globallyUniqueObjectId];
|
||||
[event setUid: uid];
|
||||
}
|
||||
}
|
||||
|
||||
object = [SOGoAppointmentObject objectWithName: uid
|
||||
inContainer: self];
|
||||
|
|
|
@ -46,6 +46,7 @@
|
|||
#import <NGCards/iCalDateTime.h>
|
||||
#import <NGCards/iCalTimeZone.h>
|
||||
#import <NGCards/iCalTimeZonePeriod.h>
|
||||
#import <NGCards/iCalToDo.h>
|
||||
#import <NGCards/NSString+NGCards.h>
|
||||
|
||||
#import <SOGo/SOGoConstants.h>
|
||||
|
@ -748,13 +749,16 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
|
|||
{
|
||||
e = [events objectAtIndex: i];
|
||||
if ([e recurrenceId])
|
||||
for (j = 0; j < [theAttendees count]; j++)
|
||||
if (shouldAdd)
|
||||
for (j = 0; j < [theAttendees count]; j++) {
|
||||
if (shouldAdd) {
|
||||
[e addToAttendees: [theAttendees objectAtIndex: j]];
|
||||
else
|
||||
}
|
||||
else {
|
||||
[e removeFromAttendees: [theAttendees objectAtIndex: j]];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
|
@ -1038,7 +1042,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
|
|||
if ([delegateEmail length])
|
||||
otherDelegate = [event findAttendeeWithEmail: delegateEmail];
|
||||
else
|
||||
otherDelegate = NO;
|
||||
otherDelegate = nil;
|
||||
|
||||
/* we handle the addition/deletion of delegate users */
|
||||
addDelegate = NO;
|
||||
|
@ -1076,7 +1080,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
|
|||
if ([delegateEmail length])
|
||||
otherDelegate = [event findAttendeeWithEmail: delegateEmail];
|
||||
else
|
||||
otherDelegate = NO;
|
||||
otherDelegate = nil;
|
||||
}
|
||||
}
|
||||
if (addDelegate)
|
||||
|
@ -1233,7 +1237,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
|
|||
if ([delegateEmail length])
|
||||
otherDelegate = [event findAttendeeWithEmail: delegateEmail];
|
||||
else
|
||||
otherDelegate = NO;
|
||||
otherDelegate = nil;
|
||||
}
|
||||
|
||||
[self sendEMailUsingTemplateNamed: @"Deletion"
|
||||
|
@ -1992,7 +1996,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
|
|||
if ([container resourceNameForEventUID: eventUID])
|
||||
{
|
||||
return [NSException exceptionWithHTTPStatus: 403
|
||||
reason: [NSString stringWithFormat: @"Event UID already in use. (%s)", eventUID]];
|
||||
reason: [NSString stringWithFormat: @"Event UID already in use. (%@)", eventUID]];
|
||||
}
|
||||
|
||||
//
|
||||
|
|
|
@ -140,6 +140,14 @@
|
|||
return aclManager;
|
||||
}
|
||||
|
||||
- (NSException *) changeParticipationStatus: (NSString *) newPartStat
|
||||
withDelegate: (iCalPerson *) delegate
|
||||
alarm: (iCalAlarm *) alarm
|
||||
{
|
||||
// Required for protocol <SOGoComponentOccurence>
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (id) init
|
||||
{
|
||||
if ((self = [super init]))
|
||||
|
@ -171,7 +179,7 @@
|
|||
|
||||
- (Class *) parsingClass
|
||||
{
|
||||
return [iCalCalendar class];
|
||||
return (Class *)[iCalCalendar class];
|
||||
}
|
||||
|
||||
- (NSString *) davContentType
|
||||
|
|
|
@ -102,9 +102,9 @@
|
|||
}
|
||||
|
||||
if ([reminderReference caseInsensitiveCompare: @"BEFORE"] == NSOrderedSame)
|
||||
aValue = [NSString stringWithString: @"-P"];
|
||||
aValue = (NSString *) @"-P";
|
||||
else
|
||||
aValue = [NSString stringWithString: @"P"];
|
||||
aValue = (NSString *) @"P";
|
||||
|
||||
if ([reminderUnit caseInsensitiveCompare: @"MINUTES"] == NSOrderedSame ||
|
||||
[reminderUnit caseInsensitiveCompare: @"HOURS"] == NSOrderedSame)
|
||||
|
|
|
@ -267,7 +267,7 @@
|
|||
|
||||
- (NSTimeInterval) occurenceInterval
|
||||
{
|
||||
return [[self endDate] timeIntervalSinceDate: [self startDate]];
|
||||
return (NSTimeInterval) [[self endDate] timeIntervalSinceDate: [self startDate]];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -328,30 +328,10 @@
|
|||
*/
|
||||
- (NSDictionary *) attributesInContext: (WOContext *) context
|
||||
{
|
||||
BOOL isAllDay;
|
||||
NSCalendarDate *eventStartDate, *eventEndDate;
|
||||
NSMutableDictionary *data;
|
||||
NSTimeZone *timeZone;
|
||||
SOGoUserDefaults *ud;
|
||||
|
||||
isAllDay = [self isAllDay];
|
||||
ud = [[context activeUser] userDefaults];
|
||||
timeZone = [ud timeZone];
|
||||
eventStartDate = [self startDate];
|
||||
eventEndDate = [self endDate];
|
||||
|
||||
if (!isAllDay)
|
||||
{
|
||||
[eventStartDate setTimeZone: timeZone];
|
||||
[eventEndDate setTimeZone: timeZone];
|
||||
}
|
||||
|
||||
data = [NSMutableDictionary dictionaryWithDictionary: [super attributesInContext: context]];
|
||||
|
||||
[data setObject: [eventStartDate iso8601DateString] forKey: @"startDate"];
|
||||
[data setObject: [eventEndDate iso8601DateString] forKey: @"endDate"];
|
||||
|
||||
[data setObject: [NSNumber numberWithBool: isAllDay] forKey: @"isAllDay"];
|
||||
[data setObject: [NSNumber numberWithBool: ![self isOpaque]] forKey: @"isTransparent"];
|
||||
|
||||
return data;
|
||||
|
@ -383,18 +363,10 @@
|
|||
if ([o isKindOfClass: [NSString class]] && [o length])
|
||||
aptStartDate = [self dateFromString: o inContext: context];
|
||||
|
||||
o = [data objectForKey: @"startTime"];
|
||||
if ([o isKindOfClass: [NSString class]] && [o length])
|
||||
[self adjustDate: &aptStartDate withTimeString: o inContext: context];
|
||||
|
||||
o = [data objectForKey: @"endDate"];
|
||||
if ([o isKindOfClass: [NSString class]] && [o length])
|
||||
aptEndDate = [self dateFromString: o inContext: context];
|
||||
|
||||
o = [data objectForKey: @"endTime"];
|
||||
if ([o isKindOfClass: [NSString class]] && [o length])
|
||||
[self adjustDate: &aptEndDate withTimeString: o inContext: context];
|
||||
|
||||
o = [data objectForKey: @"isTransparent"];
|
||||
if ([o isKindOfClass: [NSNumber class]])
|
||||
[self setTransparency: ([o boolValue]? @"TRANSPARENT" : @"OPAQUE")];
|
||||
|
@ -418,6 +390,14 @@
|
|||
}
|
||||
else
|
||||
{
|
||||
o = [data objectForKey: @"startTime"];
|
||||
if ([o isKindOfClass: [NSString class]] && [o length])
|
||||
[self adjustDate: &aptStartDate withTimeString: o inContext: context];
|
||||
|
||||
o = [data objectForKey: @"endTime"];
|
||||
if ([o isKindOfClass: [NSString class]] && [o length])
|
||||
[self adjustDate: &aptEndDate withTimeString: o inContext: context];
|
||||
|
||||
[self setStartDate: aptStartDate];
|
||||
[self setEndDate: aptEndDate];
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@
|
|||
#import <SOGo/NSCalendarDate+SOGo.h>
|
||||
|
||||
#import "iCalRepeatableEntityObject+SOGo.h"
|
||||
#import "iCalCalendar+SOGo.h"
|
||||
|
||||
@implementation iCalRepeatableEntityObject (SOGoExtensions)
|
||||
|
||||
|
@ -162,7 +163,7 @@
|
|||
rule = [iCalRecurrenceRule new];
|
||||
[rule setInterval: @"1"];
|
||||
|
||||
frequency = NSNotFound;
|
||||
frequency = (int)NSNotFound;
|
||||
o = [repeat objectForKey: @"frequency"];
|
||||
if ([o isKindOfClass: [NSString class]])
|
||||
{
|
||||
|
|
|
@ -214,7 +214,7 @@
|
|||
{
|
||||
percent = [o intValue];
|
||||
if (percent >= 0 && percent <= 100)
|
||||
[self setPercentComplete: [NSString stringWithFormat: @"%i", percent]];
|
||||
[self setPercentComplete: [NSString stringWithFormat: @"%i", (int)percent]];
|
||||
}
|
||||
else
|
||||
[self setPercentComplete: @""];
|
||||
|
|
|
@ -327,7 +327,7 @@ convention:
|
|||
|
||||
if (year && month && day)
|
||||
[self setBday: [NSString stringWithFormat: @"%.4d-%.2d-%.2d",
|
||||
year, month, day]];
|
||||
(int)year, (int)month, (int)day]];
|
||||
else
|
||||
[self setBday: @""];
|
||||
|
||||
|
@ -644,11 +644,11 @@ convention:
|
|||
birthDay = [[self bday] asCalendarDate];
|
||||
if (birthDay)
|
||||
{
|
||||
stringValue = [NSString stringWithFormat: @"%.4d", [birthDay yearOfCommonEra]];
|
||||
stringValue = [NSString stringWithFormat: @"%.4d", (int)[birthDay yearOfCommonEra]];
|
||||
[self _setValue: @"birthyear" to: stringValue inLDIFRecord: ldifRecord];
|
||||
stringValue = [NSString stringWithFormat: @"%.2d", [birthDay monthOfYear]];
|
||||
stringValue = [NSString stringWithFormat: @"%.2d", (int)[birthDay monthOfYear]];
|
||||
[self _setValue: @"birthmonth" to: stringValue inLDIFRecord: ldifRecord];
|
||||
stringValue = [NSString stringWithFormat: @"%.2d", [birthDay dayOfMonth]];
|
||||
stringValue = [NSString stringWithFormat: @"%.2d", (int)[birthDay dayOfMonth]];
|
||||
[self _setValue: @"birthday" to: stringValue inLDIFRecord: ldifRecord];
|
||||
}
|
||||
[self _setValue: @"description" to: [self note] inLDIFRecord: ldifRecord];
|
||||
|
|
|
@ -59,7 +59,7 @@
|
|||
|
||||
[response setHeader: [self davContentType] forKey: @"content-type"];
|
||||
[response setHeader: [NSString stringWithFormat:@" %d",
|
||||
[data length]]
|
||||
(int)[data length]]
|
||||
forKey: @"content-length"];
|
||||
[response setContent: data];
|
||||
}
|
||||
|
|
|
@ -268,9 +268,9 @@ Class SOGoContactSourceFolderK;
|
|||
SOGoUser *currentUser;
|
||||
id <SOGoSource> source;
|
||||
|
||||
if ([sourceID isEqualToString: @"personal"])
|
||||
result = [NSException exceptionWithHTTPStatus: 403
|
||||
reason: (@"folder '%@' cannot be deleted", sourceID)];
|
||||
if ([sourceID isEqualToString: @"personal"]){
|
||||
result = [NSException exceptionWithHTTPStatus: 403 reason: [NSString stringWithFormat: (@"folder '%@' cannot be deleted"), sourceID]];
|
||||
}
|
||||
else
|
||||
{
|
||||
result = nil;
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
|
||||
- (Class *) parsingClass
|
||||
{
|
||||
return [NGVCard class];
|
||||
return (Class *)[NGVCard class];
|
||||
}
|
||||
|
||||
/* content */
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
|
||||
- (Class *) parsingClass
|
||||
{
|
||||
return [NGVList class];
|
||||
return (Class *)[NGVList class];
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -47,7 +47,9 @@
|
|||
#import <SOGo/NSObject+DAV.h>
|
||||
#import <SOGo/SOGoPermissions.h>
|
||||
#import <SOGo/SOGoSource.h>
|
||||
#import <SOGo/SOGoUserManager.h>
|
||||
#import <SOGo/SOGoUserSettings.h>
|
||||
#import <SOGo/SOGoSystemDefaults.h>
|
||||
#import <SOGo/WORequest+SOGo.h>
|
||||
#import <SOGo/WOResponse+SOGo.h>
|
||||
|
||||
|
@ -94,7 +96,7 @@
|
|||
{
|
||||
if (![newDisplayName length])
|
||||
newDisplayName = newName;
|
||||
ASSIGN (displayName, newDisplayName);
|
||||
ASSIGN (displayName, (NSMutableString *)newDisplayName);
|
||||
}
|
||||
|
||||
return self;
|
||||
|
@ -248,6 +250,13 @@
|
|||
data = @"";
|
||||
[newRecord setObject: data forKey: @"c_cn"];
|
||||
|
||||
if ([[SOGoSystemDefaults sharedSystemDefaults] enableDomainBasedUID])
|
||||
{
|
||||
data = [oldRecord objectForKey: @"c_domain"];
|
||||
if (data)
|
||||
[newRecord setObject: data forKey: @"c_domain"];
|
||||
}
|
||||
|
||||
// mail => emails[]
|
||||
data = [oldRecord objectForKey: @"c_emails"];
|
||||
if (data)
|
||||
|
@ -697,7 +706,7 @@
|
|||
BOOL otherIsPersonal;
|
||||
|
||||
otherIsPersonal = ([otherFolder isKindOfClass: [SOGoContactGCSFolder class]]
|
||||
|| ([otherFolder isKindOfClass: isa] && [otherFolder isPersonalSource]));
|
||||
|| ([otherFolder isKindOfClass: object_getClass(self)] && [otherFolder isPersonalSource]));
|
||||
|
||||
if (isPersonalSource)
|
||||
{
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
/* NSObject+CardDAV.h - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2007 Inverse inc.
|
||||
*
|
||||
* Author: Ludovic Marcotte <ludovic@inverse.ca>
|
||||
* Copyright (C) 2007-2015 Inverse inc.
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
/* NSObject+CardDAV.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2007-2011 Inverse inc.
|
||||
*
|
||||
* Author: Ludovic Marcotte <ludovic@inverse.ca>
|
||||
* Copyright (C) 2007-2015 Inverse inc.
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -187,6 +185,10 @@
|
|||
[filters addObject: filter];
|
||||
}
|
||||
|
||||
// If no filters are provided, we return everything.
|
||||
if (![filters count])
|
||||
[filters addObject: [NSDictionary dictionaryWithObject: @"." forKey: @"mail"]];
|
||||
|
||||
return filters;
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
|
||||
#import <Foundation/NSDictionary.h>
|
||||
#import <Foundation/NSString.h>
|
||||
#import <NGExtensions/NSString+misc.h>
|
||||
|
||||
#import "NSDictionary+Mail.h"
|
||||
|
||||
|
|
|
@ -533,7 +533,7 @@
|
|||
messageID = [NSMutableString string];
|
||||
[messageID appendFormat: @"<%@", [SOGoObject globallyUniqueObjectId]];
|
||||
pGUID = [[NSProcessInfo processInfo] globallyUniqueString];
|
||||
[messageID appendFormat: @"@%u>", [pGUID hash]];
|
||||
[messageID appendFormat: @"@%u>", (unsigned int)[pGUID hash]];
|
||||
|
||||
return [messageID lowercaseString];
|
||||
}
|
||||
|
|
|
@ -181,6 +181,7 @@ static NSString *headerKeys[] = {@"subject", @"to", @"cc", @"bcc",
|
|||
|
||||
static NGMimeType *MultiMixedType = nil;
|
||||
static NGMimeType *MultiAlternativeType = nil;
|
||||
static NGMimeType *MultiRelatedType = nil;
|
||||
static NSString *userAgent = nil;
|
||||
|
||||
+ (void) initialize
|
||||
|
@ -191,6 +192,9 @@ static NSString *userAgent = nil;
|
|||
MultiAlternativeType = [NGMimeType mimeType: @"multipart" subType: @"alternative"];
|
||||
[MultiAlternativeType retain];
|
||||
|
||||
MultiRelatedType = [NGMimeType mimeType: @"multipart" subType: @"related"];
|
||||
[MultiRelatedType retain];
|
||||
|
||||
userAgent = [NSString stringWithFormat: @"SOGoMail %@",
|
||||
SOGoVersion];
|
||||
[userAgent retain];
|
||||
|
@ -700,7 +704,8 @@ static NSString *userAgent = nil;
|
|||
for (count = max - 1; count >= 0; count--)
|
||||
{
|
||||
currentAddress = [addresses objectAtIndex: count];
|
||||
if ([currentRecipient
|
||||
if (![currentAddress baseEMail] ||
|
||||
[currentRecipient
|
||||
caseInsensitiveCompare: [currentAddress baseEMail]]
|
||||
== NSOrderedSame)
|
||||
{
|
||||
|
@ -1676,16 +1681,20 @@ static NSString *userAgent = nil;
|
|||
NGMimeMessage *message;
|
||||
NGMutableHashMap *map;
|
||||
NSString *newText;
|
||||
BOOL has_inline_images;
|
||||
|
||||
message = nil;
|
||||
|
||||
has_inline_images = NO;
|
||||
bodyParts = [NSMutableArray array];
|
||||
|
||||
if (_extractImages)
|
||||
{
|
||||
newText = [text htmlByExtractingImages: bodyParts];
|
||||
if ([bodyParts count])
|
||||
{
|
||||
[self setText: newText];
|
||||
has_inline_images = YES;
|
||||
}
|
||||
}
|
||||
|
||||
map = [self mimeHeaderMapWithHeaders: _headers
|
||||
|
@ -1702,11 +1711,21 @@ static NSString *userAgent = nil;
|
|||
/* no attachments */
|
||||
message = [self mimeMessageForContentWithHeaderMap: map];
|
||||
else
|
||||
/* attachments, create multipart/mixed */
|
||||
{
|
||||
// attachments, create multipart/mixed or multipart/related if
|
||||
// we have inline image to avoid Thunderbird bug #61815 (https://bugzilla.mozilla.org/show_bug.cgi?id=61815)
|
||||
if (has_inline_images)
|
||||
{
|
||||
[map removeAllObjectsForKey: @"content-type"];
|
||||
[map addObject: MultiRelatedType forKey: @"content-type"];
|
||||
}
|
||||
|
||||
message = [self mimeMultiPartMessageWithHeaderMap: map
|
||||
andBodyParts: bodyParts];
|
||||
|
||||
//[self debugWithFormat: @"message: %@", message];
|
||||
}
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
|
|
@ -206,8 +206,8 @@ static NSString *inboxFolderName = @"INBOX";
|
|||
[folders removeObjectsInArray: nss];
|
||||
}
|
||||
|
||||
return [[folders stringsWithFormat: @"folder%@"]
|
||||
resultsOfSelector: @selector (asCSSIdentifier)];
|
||||
return [[folders resultsOfSelector: @selector (asCSSIdentifier)]
|
||||
stringsWithFormat: @"folder%@"];
|
||||
}
|
||||
|
||||
- (NSArray *) toManyRelationshipKeys
|
||||
|
|
|
@ -32,9 +32,11 @@
|
|||
#import <NGExtensions/NSString+misc.h>
|
||||
#import <NGExtensions/NSURL+misc.h>
|
||||
#import <NGImap4/NGImap4Connection.h>
|
||||
#import <NGImap4/NGImap4Client.h>
|
||||
|
||||
#import <SOGo/SOGoCache.h>
|
||||
#import <SOGo/SOGoUser.h>
|
||||
#import <SOGo/WORequest+SOGo.h>
|
||||
|
||||
#import "SOGoMailAccount.h"
|
||||
#import "SOGoMailManager.h"
|
||||
|
|
|
@ -396,7 +396,7 @@ static BOOL debugOn = NO;
|
|||
mimeType = @"application/octet-stream";
|
||||
|
||||
[response setHeader: mimeType forKey: @"content-type"];
|
||||
[response setHeader: [NSString stringWithFormat:@"%d", [data length]]
|
||||
[response setHeader: [NSString stringWithFormat:@"%d", (int)[data length]]
|
||||
forKey: @"content-length"];
|
||||
|
||||
if (asAttachment)
|
||||
|
|
|
@ -97,10 +97,10 @@
|
|||
|
||||
- (NSString *) davCollectionTag;
|
||||
|
||||
- (NSArray *) syncTokenFieldsWithProperties: (NSArray *) theProperties
|
||||
matchingSyncToken: (NSString *) theSyncToken
|
||||
fromDate: (NSCalendarDate *) theStartDate;
|
||||
|
||||
- (NSArray *) syncTokenFieldsWithProperties: (NSDictionary *) properties
|
||||
matchingSyncToken: (NSString *) syncToken
|
||||
fromDate: (NSCalendarDate *) theStartDate
|
||||
initialLoad: (BOOL) initialLoadInProgress;
|
||||
/* flags */
|
||||
|
||||
- (NSException *) addFlagsToAllMessages: (id) _f;
|
||||
|
|
|
@ -199,9 +199,9 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
|||
{
|
||||
NSArray *subfolders;
|
||||
|
||||
subfolders = [[self subfolders] stringsWithFormat: @"folder%@"];
|
||||
subfolders = [[self subfolders] resultsOfSelector: @selector (asCSSIdentifier)];
|
||||
|
||||
return [subfolders resultsOfSelector: @selector (asCSSIdentifier)];
|
||||
return [subfolders stringsWithFormat: @"folder%@"];
|
||||
}
|
||||
|
||||
- (NSArray *) subfolders
|
||||
|
@ -634,7 +634,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
|||
inContext: (id) localContext
|
||||
{
|
||||
NSArray *folders;
|
||||
NSString *currentFolderName, *currentAccountName;
|
||||
NSString *currentFolderName, *currentAccountName, *destinationAccountName;
|
||||
NSMutableString *imapDestinationFolder;
|
||||
NGImap4Client *client;
|
||||
id result;
|
||||
|
@ -642,24 +642,24 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
|||
|
||||
#warning this code will fail on implementation using something else than '/' as delimiter
|
||||
imapDestinationFolder = [NSMutableString string];
|
||||
folders = [[destinationFolder componentsSeparatedByString: @"/"]
|
||||
resultsOfSelector: @selector (fromCSSIdentifier)];
|
||||
folders = [destinationFolder componentsSeparatedByString: @"/"];
|
||||
max = [folders count];
|
||||
if (max > 1)
|
||||
{
|
||||
currentAccountName = [[self mailAccountFolder] nameInContainer];
|
||||
client = [[self imap4Connection] client];
|
||||
[imap4 selectFolder: [self imap4URL]];
|
||||
destinationAccountName = [[folders objectAtIndex: 1] fromCSSIdentifier];
|
||||
|
||||
for (count = 2; count < max; count++)
|
||||
{
|
||||
currentFolderName = [[folders objectAtIndex: count] substringFromIndex: 6];
|
||||
currentFolderName = [[[folders objectAtIndex: count] substringFromIndex: 6] fromCSSIdentifier];
|
||||
[imapDestinationFolder appendFormat: @"/%@", currentFolderName];
|
||||
}
|
||||
|
||||
if (client)
|
||||
{
|
||||
if ([[folders objectAtIndex: 1] isEqualToString: currentAccountName])
|
||||
if ([destinationAccountName isEqualToString: currentAccountName])
|
||||
{
|
||||
// We make sure the destination IMAP folder exist, if not, we create it.
|
||||
result = [[client status: imapDestinationFolder
|
||||
|
@ -688,7 +688,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
|||
|
||||
userFolder = [[context activeUser] homeFolderInContext: context];
|
||||
accounts = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
|
||||
account = [accounts lookupName: [folders objectAtIndex: 1] inContext: localContext acquire: NO];
|
||||
account = [accounts lookupName: destinationAccountName inContext: localContext acquire: NO];
|
||||
|
||||
if ([account isKindOfClass: [NSException class]])
|
||||
{
|
||||
|
@ -1674,7 +1674,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
|||
|
||||
sortOrderings = [NSMutableArray array];
|
||||
|
||||
if ([self _sortElementIsAscending: sortElement])
|
||||
if ([self _sortElementIsAscending: (NGDOMNodeWithChildren <DOMElement> *)sortElement])
|
||||
sortOrderingOrder = EOCompareAscending;
|
||||
else
|
||||
sortOrderingOrder = EOCompareDescending;
|
||||
|
@ -2106,7 +2106,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
|||
// * 5 FETCH (UID 559 MODSEQ (17))
|
||||
// * 6 FETCH (UID 560 MODSEQ (18))
|
||||
// * 7 FETCH (UID 561 MODSEQ (19))
|
||||
|
||||
//
|
||||
//
|
||||
// fetchUIDsOfVanishedItems ..
|
||||
//
|
||||
|
@ -2119,6 +2119,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
|||
- (NSArray *) syncTokenFieldsWithProperties: (NSArray *) theProperties
|
||||
matchingSyncToken: (NSString *) theSyncToken
|
||||
fromDate: (NSCalendarDate *) theStartDate
|
||||
initialLoad: (BOOL) initialLoadInProgress
|
||||
{
|
||||
EOQualifier *searchQualifier;
|
||||
NSMutableArray *allTokens;
|
||||
|
@ -2195,6 +2196,9 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
|||
|
||||
for (i = 0; i < [fetchResults count]; i++)
|
||||
{
|
||||
if ([[[fetchResults objectAtIndex: i] objectForKey: @"flags"] containsObject: @"deleted"] && initialLoadInProgress)
|
||||
continue;
|
||||
|
||||
d = [NSDictionary dictionaryWithObject: ([[[fetchResults objectAtIndex: i] objectForKey: @"flags"] containsObject: @"deleted"]) ? [NSNull null] : [[fetchResults objectAtIndex: i] objectForKey: @"modseq"]
|
||||
forKey: [[[fetchResults objectAtIndex: i] objectForKey: @"uid"] stringValue]];
|
||||
[allTokens addObject: d];
|
||||
|
@ -2205,7 +2209,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
|||
if (highestmodseq == 0)
|
||||
highestmodseq = 1;
|
||||
|
||||
if (highestmodseq > 0)
|
||||
if (highestmodseq > 0 && !initialLoadInProgress)
|
||||
{
|
||||
id uid;
|
||||
|
||||
|
|
|
@ -73,10 +73,10 @@
|
|||
|
||||
- (NSString *) newLine
|
||||
{
|
||||
NSString *rc = [NSString stringWithString: @" "];
|
||||
NSString *rc = @" ";
|
||||
|
||||
if (htmlComposition)
|
||||
rc = [NSString stringWithString: @"<br/>"];
|
||||
rc = @"<br/>";
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
|
|
@ -74,7 +74,7 @@
|
|||
int i;
|
||||
|
||||
allLabels = [NSMutableArray array];
|
||||
allKeys = [[theDefaults allKeys] sortedArrayUsingSelector: @selector (caseInsensitiveCompare:)];
|
||||
allKeys = (NSMutableArray *)[[theDefaults allKeys] sortedArrayUsingSelector: @selector (caseInsensitiveCompare:)];
|
||||
|
||||
for (i = 0; i < [allKeys count]; i++)
|
||||
{
|
||||
|
|
|
@ -389,6 +389,13 @@ static BOOL debugSoParts = NO;
|
|||
[[[info valueForKey: @"subtype"] lowercaseString] isEqualToString: @"calendar"])
|
||||
return info;
|
||||
|
||||
// deal with mails that contain only an attachment, for example:
|
||||
// application/pkcs7-mime
|
||||
// application/pdf
|
||||
// etc.
|
||||
if ([[[info valueForKey: @"type"] lowercaseString] isEqualToString: @"application"])
|
||||
return info;
|
||||
|
||||
/*
|
||||
For each path component, eg 1,1,3
|
||||
|
||||
|
@ -775,10 +782,15 @@ static BOOL debugSoParts = NO;
|
|||
[mimeType hasPrefix: @"audio/"] ||
|
||||
[mimeType hasPrefix: @"image/"] ||
|
||||
[mimeType hasPrefix: @"video/"])
|
||||
{
|
||||
filename = [NSString stringWithFormat: @"unknown_%@", path];
|
||||
else if ([mimeType isEqualToString: @"message/rfc822"])
|
||||
}
|
||||
else
|
||||
{
|
||||
if ([mimeType isEqualToString: @"message/rfc822"])
|
||||
filename = [NSString stringWithFormat: @"email_%@.eml", path];
|
||||
}
|
||||
}
|
||||
|
||||
if (filename)
|
||||
{
|
||||
|
@ -810,7 +822,7 @@ static BOOL debugSoParts = NO;
|
|||
NSMutableDictionary *currentPart;
|
||||
NSString *newPath;
|
||||
NSArray *subparts;
|
||||
NSString *type;
|
||||
NSString *type, *subtype;
|
||||
NSUInteger i;
|
||||
|
||||
type = [[part objectForKey: @"type"] lowercaseString];
|
||||
|
@ -821,19 +833,27 @@ static BOOL debugSoParts = NO;
|
|||
{
|
||||
currentPart = [subparts objectAtIndex: i-1];
|
||||
if (path)
|
||||
newPath = [NSString stringWithFormat: @"%@.%d", path, i];
|
||||
newPath = [NSString stringWithFormat: @"%@.%d", path, (int)i];
|
||||
else
|
||||
newPath = [NSString stringWithFormat: @"%d", i];
|
||||
newPath = [NSString stringWithFormat: @"%d", (int)i];
|
||||
[self _fetchFileAttachmentKeysInPart: currentPart
|
||||
intoArray: keys
|
||||
withPath: newPath
|
||||
andPrefix: [NSString stringWithFormat: @"%@/%i", prefix, i]];
|
||||
andPrefix: [NSString stringWithFormat: @"%@/%i", prefix, (int)i]];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!path)
|
||||
{
|
||||
path = @"1";
|
||||
|
||||
// We set the path to 0 in case of a smime mail if not provided.
|
||||
subtype = [[part objectForKey: @"subtype"] lowercaseString];
|
||||
if ([subtype isEqualToString: @"pkcs7-mime"])
|
||||
path = @"0";
|
||||
}
|
||||
|
||||
[self _fetchFileAttachmentKey: part
|
||||
intoArray: keys
|
||||
withPath: path
|
||||
|
@ -1027,6 +1047,14 @@ static BOOL debugSoParts = NO;
|
|||
return obj;
|
||||
}
|
||||
}
|
||||
// Handles cases where the email is itself an attachment, so its Content-Type
|
||||
// is application/*, image/* etc.
|
||||
else if ([_key isEqualToString: @"asAttachment"] &&
|
||||
(obj = [self lookupImap4BodyPartKey: @"0" inContext:_ctx]) != nil)
|
||||
{
|
||||
[obj setAsAttachment];
|
||||
return obj;
|
||||
}
|
||||
|
||||
/* return 404 to stop acquisition */
|
||||
return [NSException exceptionWithHTTPStatus:404 /* Not Found */
|
||||
|
|
|
@ -310,7 +310,7 @@ static NSDictionary *BSONTypes()
|
|||
|
||||
case 'q': return 0x12;
|
||||
default:
|
||||
[NSException raise: NSInvalidArgumentException format: @"%@::%s - invalid encoding type '%c'", [self class], _cmd, encoding];
|
||||
[NSException raise: NSInvalidArgumentException format: @"%@::%@ - invalid encoding type '%c'", [self class], NSStringFromSelector(_cmd), encoding];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
@ -385,7 +385,7 @@ static NSDictionary *BSONTypes()
|
|||
}
|
||||
|
||||
|
||||
[NSException raise: NSInvalidArgumentException format: @"%@::%s - invalid encoding type '%c'", [self class], _cmd, encoding];
|
||||
[NSException raise: NSInvalidArgumentException format: @"%@::%@ - invalid encoding type '%c'", [self class], NSStringFromSelector(_cmd), encoding];
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
|
|
@ -1703,7 +1703,7 @@ _makeLDAPChanges (NGLdapConnection *ldapConnection,
|
|||
hostname: hostname
|
||||
port: [NSString stringWithFormat: @"%d", port]
|
||||
encryption: encryption
|
||||
bindAsCurrentUser: NO];
|
||||
bindAsCurrentUser: [NSString stringWithFormat: @"%d", NO]];
|
||||
[ab setBaseDN: [entry dn]
|
||||
IDField: @"cn"
|
||||
CNField: @"displayName"
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
/* NSArray+Utilities.h - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2006 Inverse inc.
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
* Copyright (C) 2006-2015 Inverse inc.
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
/* NSArray+Utilities.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2006-2011 Inverse inc.
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
* Copyright (C) 2006-2015 Inverse inc.
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
|
|
@ -90,9 +90,9 @@ static NSString *rfc822Months[] = {@"", @"Jan", @"Feb", @"Mar", @"Apr",
|
|||
NSString *str;
|
||||
|
||||
str = [NSString stringWithFormat: @"%.4d%.2d%.2d",
|
||||
[self yearOfCommonEra],
|
||||
[self monthOfYear],
|
||||
[self dayOfMonth]];
|
||||
(int)[self yearOfCommonEra],
|
||||
(int)[self monthOfYear],
|
||||
(int)[self dayOfMonth]];
|
||||
|
||||
return str;
|
||||
}
|
||||
|
@ -109,9 +109,9 @@ static NSString *rfc822Months[] = {@"", @"Jan", @"Feb", @"Mar", @"Apr",
|
|||
|
||||
return
|
||||
[NSString stringWithFormat: @"%@, %.2d %@ %d %.2d:%.2d:%.2d %+.4d",
|
||||
rfc822Days[[self dayOfWeek]], [self dayOfMonth],
|
||||
rfc822Months[[self monthOfYear]], [self yearOfCommonEra],
|
||||
[self hourOfDay], [self minuteOfHour], [self secondOfMinute],
|
||||
rfc822Days[[self dayOfWeek]], (int)[self dayOfMonth],
|
||||
rfc822Months[[self monthOfYear]], (int)[self yearOfCommonEra],
|
||||
(int)[self hourOfDay], (int)[self minuteOfHour], (int)[self secondOfMinute],
|
||||
timeZoneShift];
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
* Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#ifndef __OpenBSD__
|
||||
#if !defined(__OpenBSD__) && !defined(__FreeBSD__)
|
||||
#include <crypt.h>
|
||||
#endif
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
{
|
||||
NSString *newTag;
|
||||
|
||||
newTag = [NSString stringWithFormat: @"n%d", [namespaces count]];
|
||||
newTag = [NSString stringWithFormat: @"n%d", (int)[namespaces count]];
|
||||
[namespaces setObject: newTag forKey: newNS];
|
||||
|
||||
return newTag;
|
||||
|
|
|
@ -309,7 +309,7 @@ static int cssEscapingCount;
|
|||
c == 0xD ||
|
||||
(c >= 0x20 && c <= 0xD7FF) ||
|
||||
(c >= 0xE000 && c <= 0xFFFD) ||
|
||||
(c >= 0x10000 && c <= 0x10FFFF))
|
||||
(c >= (unichar)0x10000 && c <= (unichar)0x10FFFF))
|
||||
{
|
||||
*(start+j) = c;
|
||||
j++;
|
||||
|
@ -373,6 +373,7 @@ static int cssEscapingCount;
|
|||
|
||||
- (NSString *) asCSSIdentifier
|
||||
{
|
||||
NSCharacterSet *numericSet;
|
||||
NSMutableString *cssIdentifier;
|
||||
unichar currentChar;
|
||||
int count, max, idx;
|
||||
|
@ -381,10 +382,12 @@ static int cssEscapingCount;
|
|||
[self _setupCSSEscaping];
|
||||
|
||||
cssIdentifier = [NSMutableString string];
|
||||
numericSet = [NSCharacterSet decimalDigitCharacterSet];
|
||||
max = [self length];
|
||||
|
||||
if (max > 0)
|
||||
{
|
||||
if (isdigit([self characterAtIndex: 0]))
|
||||
if ([numericSet characterIsMember: [self characterAtIndex: 0]])
|
||||
// A CSS identifier can't start with a digit; we add an underscore
|
||||
[cssIdentifier appendString: @"_"];
|
||||
for (count = 0; count < max; count++)
|
||||
|
@ -415,6 +418,7 @@ static int cssEscapingCount;
|
|||
|
||||
- (NSString *) fromCSSIdentifier
|
||||
{
|
||||
NSCharacterSet *numericSet;
|
||||
NSMutableString *newString;
|
||||
NSString *currentString;
|
||||
int count, length, max, idx;
|
||||
|
@ -423,12 +427,14 @@ static int cssEscapingCount;
|
|||
if (!cssEscapingStrings)
|
||||
[self _setupCSSEscaping];
|
||||
|
||||
numericSet = [NSCharacterSet decimalDigitCharacterSet];
|
||||
newString = [NSMutableString string];
|
||||
max = [self length];
|
||||
count = 0;
|
||||
|
||||
if (max > 0
|
||||
&& [self characterAtIndex: 0] == '_'
|
||||
&& isdigit([self characterAtIndex: 1]))
|
||||
&& [numericSet characterIsMember: [self characterAtIndex: 1]])
|
||||
{
|
||||
/* If the identifier starts with an underscore followed by a digit,
|
||||
we remove the underscore */
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#ifndef SOGOCACHE_H
|
||||
#define SOGOCACHE_H
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <Foundation/NSObject.h>
|
||||
|
||||
#include <libmemcached/memcached.h>
|
||||
|
|
|
@ -40,6 +40,8 @@
|
|||
|
||||
- (NSMutableString *) pathForChild: (NSString *) childName;
|
||||
|
||||
- (void) addUserInAcls: (NSString *) user;
|
||||
|
||||
- (NSArray *) toOneRelationshipKeys;
|
||||
- (NSArray *) toManyRelationshipKeys;
|
||||
|
||||
|
|
|
@ -364,7 +364,7 @@ Class SOGoCacheGCSObjectK = Nil;
|
|||
if (record)
|
||||
{
|
||||
if ([[record objectForKey: @"c_type"] intValue] == MAPIFolderCacheObject)
|
||||
objectClass = isa;
|
||||
objectClass = object_getClass(self);
|
||||
else
|
||||
objectClass = SOGoCacheGCSObjectK;
|
||||
|
||||
|
|
|
@ -55,6 +55,9 @@ typedef enum {
|
|||
|
||||
- (void) reloadIfNeeded;
|
||||
- (void) save;
|
||||
- (NSException *) destroy;
|
||||
|
||||
+ (id) objectWithName: (NSString *) key inContainer: (id) theContainer useCache: (BOOL) useCache;
|
||||
|
||||
/* accessors */
|
||||
- (NSMutableString *) path; /* full filename */
|
||||
|
|
|
@ -87,7 +87,7 @@ static EOAttribute *textColumn = nil;
|
|||
{
|
||||
tableUrl = nil;
|
||||
initialized = NO;
|
||||
objectType = -1;
|
||||
objectType = (SOGoCacheObjectType) -1;
|
||||
deleted = NO;
|
||||
version = 0;
|
||||
}
|
||||
|
@ -103,11 +103,22 @@ static EOAttribute *textColumn = nil;
|
|||
}
|
||||
|
||||
+ (id) objectWithName: (NSString *) key inContainer: (id) theContainer
|
||||
{
|
||||
return [self objectWithName: key
|
||||
inContainer: theContainer
|
||||
useCache: YES];
|
||||
}
|
||||
|
||||
+ (id) objectWithName: (NSString *) key inContainer: (id) theContainer useCache: (BOOL) useCache
|
||||
{
|
||||
SOGoCache *cache;
|
||||
id o;
|
||||
|
||||
cache = [SOGoCache sharedCache];
|
||||
|
||||
if (!useCache)
|
||||
[cache unregisterObjectWithName: key inContainer: theContainer];
|
||||
|
||||
o = [cache objectNamed: key inContainer: theContainer];
|
||||
|
||||
if (!o)
|
||||
|
@ -383,7 +394,7 @@ static EOAttribute *textColumn = nil;
|
|||
@"SELECT * FROM %@ WHERE c_path = %@",
|
||||
tableName, pathValue];
|
||||
if (startVersion > -1)
|
||||
[sql appendFormat: @" AND c_version > %d", startVersion];
|
||||
[sql appendFormat: @" AND c_version > %d", (int)startVersion];
|
||||
|
||||
/* execution */
|
||||
records = [self performSQLQuery: sql];
|
||||
|
@ -411,15 +422,13 @@ static EOAttribute *textColumn = nil;
|
|||
|
||||
tableName = [self tableName];
|
||||
adaptor = [self tableChannelAdaptor];
|
||||
pathValue = [adaptor formatValue: [NSString stringWithFormat: @"/%@", deviceId]
|
||||
forAttribute: textColumn];
|
||||
|
||||
/* query */
|
||||
sql = [NSMutableString stringWithFormat:
|
||||
@"SELECT * FROM %@ WHERE c_type = %d AND c_deleted <> 1", tableName, objectType];
|
||||
|
||||
if (startVersion > -1)
|
||||
[sql appendFormat: @" AND c_version > %d", startVersion];
|
||||
[sql appendFormat: @" AND c_version > %d", (int)startVersion];
|
||||
|
||||
if (deviceId) {
|
||||
pathValue = [adaptor formatValue: [NSString stringWithFormat: @"/%@%", deviceId]
|
||||
|
@ -546,7 +555,7 @@ static EOAttribute *textColumn = nil;
|
|||
|
||||
lastModifiedValue = (NSInteger) [lastModified timeIntervalSince1970];
|
||||
|
||||
if (objectType == -1)
|
||||
if (objectType == (SOGoCacheObjectType) -1)
|
||||
[NSException raise: @"SOGoCacheIOException"
|
||||
format: @"object type has not been set for object '%@'",
|
||||
self];
|
||||
|
@ -576,7 +585,7 @@ static EOAttribute *textColumn = nil;
|
|||
@")"),
|
||||
tableName,
|
||||
pathValue, parentPathValue, objectType,
|
||||
creationDateValue, lastModifiedValue,
|
||||
(int)creationDateValue, (int)lastModifiedValue,
|
||||
propsValue];
|
||||
isNew = NO;
|
||||
}
|
||||
|
@ -590,7 +599,7 @@ static EOAttribute *textColumn = nil;
|
|||
@" c_version = %d, c_content = %@"
|
||||
@" WHERE c_path = %@"),
|
||||
tableName,
|
||||
lastModifiedValue, deletedValue, version, propsValue,
|
||||
(int)lastModifiedValue, (int)deletedValue, (int)version, propsValue,
|
||||
pathValue];
|
||||
}
|
||||
|
||||
|
|
|
@ -42,8 +42,6 @@
|
|||
- (NSCalendarDate *) creationDate;
|
||||
- (NSCalendarDate *) lastModified;
|
||||
|
||||
- (NSException *) destroy;
|
||||
|
||||
@end
|
||||
|
||||
#endif /* SOGOCACHEOBJECT_H */
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue