Merge branch 'master' into feature/addMissingStrings2

Conflicts:
	UI/AdministrationUI/English.lproj/Localizable.strings
	UI/PreferencesUI/English.lproj/Localizable.strings
	UI/Templates/PreferencesUI/UIxFilterEditor.wox
pull/101/head
Ludovic Marcotte 2015-12-03 11:16:17 -05:00
commit 9b0a2c5fae
390 changed files with 57472 additions and 27699 deletions

8
.gitignore vendored
View File

@ -9,6 +9,7 @@
*/*/obj/ */*/obj/
*/obj/ */obj/
.scss-lint-config.yml_ .scss-lint-config.yml_
ActiveSync/ActiveSync.SOGo
Documentation/*.docbook Documentation/*.docbook
Documentation/*.pdf Documentation/*.pdf
SoObjects/SOGo/SOGo.framework/ SoObjects/SOGo/SOGo.framework/
@ -20,11 +21,8 @@ UI/WebServerResources/bower_components/
UI/WebServerResources/css/ UI/WebServerResources/css/
UI/WebServerResources/css/styles.css UI/WebServerResources/css/styles.css
UI/WebServerResources/css/styles.css.map UI/WebServerResources/css/styles.css.map
UI/WebServerResources/js/Common.js* UI/WebServerResources/js/*.js
UI/WebServerResources/js/Contacts.js* UI/WebServerResources/js/*.js.map
UI/WebServerResources/js/Mailer.js*
UI/WebServerResources/js/Preferences.js*
UI/WebServerResources/js/Scheduler.js*
UI/WebServerResources/js/vendor/ UI/WebServerResources/js/vendor/
UI/WebServerResources/node_modules/ UI/WebServerResources/node_modules/
UI/WebServerResources/scss/.sass-cache/ UI/WebServerResources/scss/.sass-cache/

View File

@ -1 +1,4 @@
# compilation settings ifeq ($(HAS_LIBRARY_ssl),yes)
ADDITIONAL_CPPFLAGS += -DHAVE_OPENSSL=1
BUNDLE_LIBS += -lcrypto
endif

View File

@ -101,7 +101,7 @@ static NSArray *asElementArray = nil;
int i, count; int i, count;
if (!asElementArray) if (!asElementArray)
asElementArray = [[NSArray alloc] initWithObjects: @"Attendee", @"Category", nil]; asElementArray = [[NSArray alloc] initWithObjects: @"Attendee", @"Category", @"Exception", nil];
data = [NSMutableDictionary dictionary]; data = [NSMutableDictionary dictionary];

View File

@ -48,11 +48,42 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@implementation NGVCard (ActiveSync) @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 - (NSString *) activeSyncRepresentationInContext: (WOContext *) context
{ {
NSArray *emails, *addresses, *categories, *elements; NSArray *emails, *addresses, *categories, *elements;
CardElement *n, *homeAdr, *workAdr; CardElement *n, *homeAdr, *workAdr;
NSMutableString *s; NSMutableString *s, *a;
NSString *url; NSString *url;
id o; id o;
@ -63,7 +94,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if ((o = [n flattenedValueAtIndex: 0 forKey: @""])) if ((o = [n flattenedValueAtIndex: 0 forKey: @""]))
[s appendFormat: @"<LastName xmlns=\"Contacts:\">%@</LastName>", [o activeSyncRepresentationInContext: context]]; [s appendFormat: @"<LastName xmlns=\"Contacts:\">%@</LastName>", [o activeSyncRepresentationInContext: context]];
if ((o = [n flattenedValueAtIndex: 1 forKey: @""])) if ((o = [n flattenedValueAtIndex: 1 forKey: @""]))
[s appendFormat: @"<FirstName xmlns=\"Contacts:\">%@</FirstName>", [o activeSyncRepresentationInContext: context]]; [s appendFormat: @"<FirstName xmlns=\"Contacts:\">%@</FirstName>", [o activeSyncRepresentationInContext: context]];
@ -146,16 +177,22 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if ([addresses count]) if ([addresses count])
{ {
homeAdr = [addresses objectAtIndex: 0]; homeAdr = [addresses objectAtIndex: 0];
a = [NSMutableString string];
if ((o = [homeAdr flattenedValueAtIndex: 2 forKey: @""])) 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: @""])) if ((o = [homeAdr flattenedValueAtIndex: 3 forKey: @""]))
[s appendFormat: @"<HomeCity xmlns=\"Contacts:\">%@</HomeCity>", [o activeSyncRepresentationInContext: context]]; [s appendFormat: @"<HomeCity xmlns=\"Contacts:\">%@</HomeCity>", [o activeSyncRepresentationInContext: context]];
if ((o = [homeAdr flattenedValueAtIndex: 4 forKey: @""])) if ((o = [homeAdr flattenedValueAtIndex: 4 forKey: @""]))
[s appendFormat: @"<HomeState xmlns=\"Contacts:\">%@</HomeState>", [o activeSyncRepresentationInContext: context]]; [s appendFormat: @"<HomeState xmlns=\"Contacts:\">%@</HomeState>", [o activeSyncRepresentationInContext: context]];
if ((o = [homeAdr flattenedValueAtIndex: 5 forKey: @""])) if ((o = [homeAdr flattenedValueAtIndex: 5 forKey: @""]))
[s appendFormat: @"<HomePostalCode xmlns=\"Contacts:\">%@</HomePostalCode>", [o activeSyncRepresentationInContext: context]]; [s appendFormat: @"<HomePostalCode xmlns=\"Contacts:\">%@</HomePostalCode>", [o activeSyncRepresentationInContext: context]];
@ -171,9 +208,15 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if ([addresses count]) if ([addresses count])
{ {
workAdr = [addresses objectAtIndex: 0]; workAdr = [addresses objectAtIndex: 0];
a = [NSMutableString string];
if ((o = [workAdr flattenedValueAtIndex: 2 forKey: @""])) 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: @""])) if ((o = [workAdr flattenedValueAtIndex: 3 forKey: @""]))
[s appendFormat: @"<BusinessCity xmlns=\"Contacts:\">%@</BusinessCity>", [o activeSyncRepresentationInContext: context]]; [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 inContext: (WOContext *) context
{ {
CardElement *element; CardElement *element;
NSMutableArray *addressLines;
id o; id o;
// Contact's note // Contact's note
@ -226,6 +270,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Categories // Categories
if ((o = [theValues objectForKey: @"Categories"]) && [o length]) if ((o = [theValues objectForKey: @"Categories"]) && [o length])
[self setCategories: o]; [self setCategories: o];
else
[[self children] removeObjectsInArray: [self childrenWithTag: @"Categories"]];
// Birthday // Birthday
if ((o = [theValues objectForKey: @"Birthday"])) if ((o = [theValues objectForKey: @"Birthday"]))
@ -233,6 +279,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
o = [o calendarDate]; o = [o calendarDate];
[self setBday: [o descriptionWithCalendarFormat: @"%Y-%m-%d" timeZone: nil locale: nil]]; [self setBday: [o descriptionWithCalendarFormat: @"%Y-%m-%d" timeZone: nil locale: nil]];
} }
else if (![self _isGhosted: @"Birthday" inContext: context])
{
[self setBday: @""];
}
// //
// Business address information // Business address information
@ -244,18 +295,48 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// BusinessCountry // BusinessCountry
// //
element = [self elementWithTag: @"adr" ofType: @"work"]; element = [self elementWithTag: @"adr" ofType: @"work"];
[element setSingleValue: @""
atIndex: 1 forKey: @""]; if ((o = [theValues objectForKey: @"BusinessStreet"]) || ![self _isGhosted: @"BusinessStreet" inContext: context])
[element setSingleValue: [theValues objectForKey: @"BusinessStreet"] {
atIndex: 2 forKey: @""]; addressLines = [NSMutableArray arrayWithArray: [o componentsSeparatedByString: @"\n"]];
[element setSingleValue: [theValues objectForKey: @"BusinessCity"]
atIndex: 3 forKey: @""]; [element setSingleValue: @""
[element setSingleValue: [theValues objectForKey: @"BusinessState"] atIndex: 1 forKey: @""];
atIndex: 4 forKey: @""]; [element setSingleValue: [addressLines count] ? [addressLines objectAtIndex: 0] : @""
[element setSingleValue: [theValues objectForKey: @"BusinessPostalCode"] atIndex: 2 forKey: @""];
atIndex: 5 forKey: @""];
[element setSingleValue: [theValues objectForKey: @"BusinessCountry"] // Extended address line. If there are more than 2 address lines we add them to the extended address line.
atIndex: 6 forKey: @""]; 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 // Home address information
@ -267,35 +348,69 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// HomeCountry // HomeCountry
// //
element = [self elementWithTag: @"adr" ofType: @"home"]; element = [self elementWithTag: @"adr" ofType: @"home"];
[element setSingleValue: @""
atIndex: 1 forKey: @""]; if ((o = [theValues objectForKey: @"HomeStreet"]) || ![self _isGhosted: @"HomeStreet" inContext: context])
[element setSingleValue: [theValues objectForKey: @"HomeStreet"] {
atIndex: 2 forKey: @""]; addressLines = [NSMutableArray arrayWithArray: [o componentsSeparatedByString: @"\n"]];
[element setSingleValue: [theValues objectForKey: @"HomeCity"]
atIndex: 3 forKey: @""]; [element setSingleValue: @""
[element setSingleValue: [theValues objectForKey: @"HomeState"] atIndex: 1 forKey: @""];
atIndex: 4 forKey: @""]; [element setSingleValue: [addressLines count] ? [addressLines objectAtIndex: 0] : @""
[element setSingleValue: [theValues objectForKey: @"HomePostalCode"] atIndex: 2 forKey: @""];
atIndex: 5 forKey: @""];
[element setSingleValue: [theValues objectForKey: @"HomeCountry"] // Extended address line. If there are more then 2 address lines we add them to the extended address line.
atIndex: 6 forKey: @""]; 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 // Company's name
if ((o = [theValues objectForKey: @"CompanyName"])) if ((o = [theValues objectForKey: @"CompanyName"]))
[self setOrg: o units: nil]; [self setOrg: o units: nil];
else if (![self _isGhosted: @"CompanyName" inContext: context])
[self setOrg: @"" units: nil];
// Department // Department
if ((o = [theValues objectForKey: @"Department"])) if ((o = [theValues objectForKey: @"Department"]))
[self setOrg: nil units: [NSArray arrayWithObjects:o,nil]]; [self setOrg: nil units: [NSArray arrayWithObjects:o,nil]];
else if (![self _isGhosted: @"Department" inContext: context])
[self setOrg: nil units: [NSArray arrayWithObjects:@"",nil]];
// Email addresses // Email addresses
if ((o = [theValues objectForKey: @"Email1Address"])) if ((o = [theValues objectForKey: @"Email1Address"]) || ![self _isGhosted: @"Email1Address" inContext: context])
{ {
element = [self elementWithTag: @"email" ofType: @"work"]; element = [self elementWithTag: @"email" ofType: @"work"];
[element setSingleValue: [o pureEMailAddress] forKey: @""]; [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 = [self elementWithTag: @"email" ofType: @"home"];
[element setSingleValue: [o pureEMailAddress] forKey: @""]; [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 // SOGo currently only supports 2 email addresses ... but AS clients might send 3
// FIXME: revise this when the GUI revamp is done in SOGo // 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 = [self elementWithTag: @"email" ofType: @"three"];
[element setSingleValue: [o pureEMailAddress] forKey: @""]; [element setSingleValue: [o pureEMailAddress] forKey: @""];
@ -313,45 +428,62 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// MiddleName // MiddleName
// Suffix (II) // Suffix (II)
// Title (Mr.) // Title (Mr.)
[self setFn: [theValues objectForKey: @"FileAs"]]; if ((o = [theValues objectForKey: @"FileAs"]) || ![self _isGhosted: @"FileAs" inContext: context])
[self setFn: [theValues objectForKey: @"FileAs"]];
[self setNWithFamily: [theValues objectForKey: @"LastName"] [self setNWithFamily: [theValues objectForKey: @"LastName"]
given: [theValues objectForKey: @"FirstName"] given: [theValues objectForKey: @"FirstName"]
additional: nil prefixes: nil suffixes: nil]; additional: nil prefixes: nil suffixes: nil];
// IM information // IM information
[[self uniqueChildWithTag: @"x-aim"] if ((o = [theValues objectForKey: @"IMAddress"]) || ![self _isGhosted: @"IMAddress" inContext: context])
setSingleValue: [theValues objectForKey: @"IMAddress"] [[self uniqueChildWithTag: @"x-aim"]
forKey: @""]; setSingleValue: [theValues objectForKey: @"IMAddress"]
forKey: @""];
// //
// Phone numbrrs // Phone numbrrs
// //
element = [self elementWithTag: @"tel" ofType: @"work"]; if ((o = [theValues objectForKey: @"BusinessPhoneNumber"]) || ![self _isGhosted: @"BusinessPhoneNumber" inContext: context])
[element setSingleValue: [theValues objectForKey: @"BusinessPhoneNumber"] forKey: @""]; {
element = [self elementWithTag: @"tel" ofType: @"work"];
[element setSingleValue: [theValues objectForKey: @"BusinessPhoneNumber"] forKey: @""];
}
element = [self elementWithTag: @"tel" ofType: @"home"]; if ((o = [theValues objectForKey: @"HomePhoneNumber"]) || ![self _isGhosted: @"HomePhoneNumber" inContext: context])
[element setSingleValue: [theValues objectForKey: @"HomePhoneNumber"] forKey: @""]; {
element = [self elementWithTag: @"tel" ofType: @"home"];
[element setSingleValue: [theValues objectForKey: @"HomePhoneNumber"] forKey: @""];
}
element = [self elementWithTag: @"tel" ofType: @"cell"]; if ((o = [theValues objectForKey: @"MobilePhoneNumber"]) || ![self _isGhosted: @"MobilePhoneNumber" inContext: context])
[element setSingleValue: [theValues objectForKey: @"MobilePhoneNumber"] forKey: @""]; {
element = [self elementWithTag: @"tel" ofType: @"cell"];
[element setSingleValue: [theValues objectForKey: @"MobilePhoneNumber"] forKey: @""];
}
element = [self elementWithTag: @"tel" ofType: @"fax"]; if ((o = [theValues objectForKey: @"BusinessFaxNumber"]) || ![self _isGhosted: @"BusinessFaxNumber" inContext: context])
[element setSingleValue: [theValues objectForKey: @"BusinessFaxNumber"] forKey: @""]; {
element = [self elementWithTag: @"tel" ofType: @"fax"];
[element setSingleValue: [theValues objectForKey: @"BusinessFaxNumber"] forKey: @""];
}
element = [self elementWithTag: @"tel" ofType: @"pager"]; if ((o = [theValues objectForKey: @"PagerNumber"]) || ![self _isGhosted: @"PagerNumber" inContext: context])
[element setSingleValue: [theValues objectForKey: @"PagerNumber"] forKey: @""]; {
element = [self elementWithTag: @"tel" ofType: @"pager"];
[element setSingleValue: [theValues objectForKey: @"PagerNumber"] forKey: @""];
}
// Job's title // Job's title
if ((o = [theValues objectForKey: @"JobTitle"])) if ((o = [theValues objectForKey: @"JobTitle"]) || ![self _isGhosted: @"JobTitle" inContext: context])
[self setTitle: o]; [self setTitle: o];
// WebPage (work) // WebPage (work)
if ((o = [theValues objectForKey: @"WebPage"])) if ((o = [theValues objectForKey: @"WebPage"]) || ![self _isGhosted: @"WebPage" inContext: context])
[[self elementWithTag: @"url" ofType: @"work"] [[self elementWithTag: @"url" ofType: @"work"]
setSingleValue: o forKey: @""]; setSingleValue: o forKey: @""];
if ((o = [theValues objectForKey: @"NickName"])) if ((o = [theValues objectForKey: @"NickName"]) || ![self _isGhosted: @"NickName" inContext: context])
[self setNickname: o]; [self setNickname: o];
if ((o = [theValues objectForKey: @"Picture"])) if ((o = [theValues objectForKey: @"Picture"]))

View File

@ -112,13 +112,73 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@implementation SOGoActiveSyncDispatcher (Sync) @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 - (void) _setFolderMetadata: (NSDictionary *) theFolderMetadata
forKey: (NSString *) theFolderKey forKey: (NSString *) theFolderKey
{ {
NSNumber *processIdentifier, *processIdentifierInCache;
SOGoCacheGCSObject *o; SOGoCacheGCSObject *o;
NSDictionary *values; NSDictionary *values;
NSString *key; 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]; key = [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], theFolderKey];
values = [theFolderMetadata copy]; values = [theFolderMetadata copy];
@ -132,7 +192,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[[o properties] removeObjectForKey: @"DateCache"]; [[o properties] removeObjectForKey: @"DateCache"];
[[o properties] removeObjectForKey: @"MoreAvailable"]; [[o properties] removeObjectForKey: @"MoreAvailable"];
[[o properties] removeObjectForKey: @"BodyPreferenceType"]; [[o properties] removeObjectForKey: @"BodyPreferenceType"];
[[o properties] removeObjectForKey: @"SupportedElements"];
[[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"]; [[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"];
[[o properties] removeObjectForKey: @"InitialLoadSequence"];
[[o properties] addEntriesFromDictionary: values]; [[o properties] addEntriesFromDictionary: values];
[o save]; [o save];
@ -159,7 +221,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
NSString *nameInCache; NSString *nameInCache;
if (theFolderType == ActiveSyncMailFolder) if (theFolderType == ActiveSyncMailFolder)
nameInCache= [[[theCollection mailAccountFolder] imapFolderGUIDs] objectForKey: [theCollection nameInContainer]]; nameInCache = [imapFolderGUIDS objectForKey: [theCollection nameInContainer]];
else else
{ {
NSString *component_name; NSString *component_name;
@ -170,7 +232,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
else else
component_name = @"vtodo"; component_name = @"vtodo";
nameInCache= [NSString stringWithFormat: @"%@/%@", component_name, [theCollection nameInContainer]]; nameInCache = [NSString stringWithFormat: @"%@/%@", component_name, [theCollection nameInContainer]];
} }
return nameInCache; return nameInCache;
@ -403,8 +465,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[o takeActiveSyncValues: allChanges inContext: context]; [o takeActiveSyncValues: allChanges inContext: context];
[sogoObject saveComponent: o]; [sogoObject saveComponent: o];
[syncCache setObject: [NSString stringWithFormat:@"%f", [[sogoObject lastModified] timeIntervalSince1970]] forKey: serverId]; if ([syncCache objectForKey: serverId])
[syncCache setObject: [NSString stringWithFormat:@"%f", [[sogoObject lastModified] timeIntervalSince1970]] forKey: serverId];
} }
break; break;
case ActiveSyncEventFolder: case ActiveSyncEventFolder:
@ -414,27 +476,27 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[o takeActiveSyncValues: allChanges inContext: context]; [o takeActiveSyncValues: allChanges inContext: context];
[sogoObject saveComponent: o]; [sogoObject saveComponent: o];
[syncCache setObject: [NSString stringWithFormat:@"%f", [[sogoObject lastModified] timeIntervalSince1970]] forKey: serverId]; if ([syncCache objectForKey: serverId])
[syncCache setObject: [NSString stringWithFormat:@"%f", [[sogoObject lastModified] timeIntervalSince1970]] forKey: serverId];
} }
break; break;
case ActiveSyncMailFolder: case ActiveSyncMailFolder:
default: default:
{ {
NSDictionary *result; NSDictionary *result;
NSString *modseq; NSNumber *modseq;
[sogoObject takeActiveSyncValues: allChanges inContext: context]; [sogoObject takeActiveSyncValues: allChanges inContext: context];
result = [sogoObject fetchParts: [NSArray arrayWithObject: @"MODSEQ"]]; result = [sogoObject fetchParts: [NSArray arrayWithObject: @"MODSEQ"]];
modseq = [[[result objectForKey: @"RawResponse"] objectForKey: @"fetch"] objectForKey: @"modseq"]; modseq = [[[result objectForKey: @"RawResponse"] objectForKey: @"fetch"] objectForKey: @"modseq"];
if (modseq) if (modseq && [syncCache objectForKey: serverId])
[syncCache setObject: modseq forKey: serverId]; [syncCache setObject: [modseq stringValue] forKey: serverId];
} }
} }
[self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: theCollection withType: theFolderType]]; [self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: theCollection withType: theFolderType]];
[theBuffer appendString: @"<Change>"]; [theBuffer appendString: @"<Change>"];
@ -577,36 +639,44 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
withFilterType: (NSCalendarDate *) theFilterType withFilterType: (NSCalendarDate *) theFilterType
inBuffer: (NSMutableString *) theBuffer inBuffer: (NSMutableString *) theBuffer
lastServerKey: (NSString **) theLastServerKey lastServerKey: (NSString **) theLastServerKey
defaultInterval: (unsigned int) theDefaultInterval
{ {
NSMutableDictionary *folderMetadata, *dateCache, *syncCache; NSMutableDictionary *folderMetadata, *dateCache, *syncCache;
NSString *davCollectionTagToStore; NSString *davCollectionTagToStore;
NSAutoreleasePool *pool; NSAutoreleasePool *pool;
NSMutableString *s; NSMutableString *s;
BOOL more_available; BOOL cleanup_needed, more_available;
int i, max; int i, max;
s = [NSMutableString string]; s = [NSMutableString string];
cleanup_needed = more_available = NO;
more_available = NO;
folderMetadata = [self _folderMetadataForKey: [self _getNameInCache: theCollection withType: theFolderType]]; 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"]) if ([theSyncKey isEqualToString: @"-1"])
{ {
[folderMetadata setObject: [NSMutableDictionary dictionary] forKey: @"SyncCache"]; [folderMetadata setObject: [NSMutableDictionary dictionary] forKey: @"SyncCache"];
[folderMetadata setObject: [NSMutableDictionary dictionary] forKey: @"DateCache"]; [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"]; syncCache = [folderMetadata objectForKey: @"SyncCache"];
dateCache = [folderMetadata objectForKey: @"DateCache"]; dateCache = [folderMetadata objectForKey: @"DateCache"];
if ((theFolderType == ActiveSyncMailFolder || theFolderType == ActiveSyncEventFolder || theFolderType == ActiveSyncTaskFolder) && if ((theFolderType == ActiveSyncMailFolder || theFolderType == ActiveSyncEventFolder || theFolderType == ActiveSyncTaskFolder) &&
!([folderMetadata objectForKey: @"MoreAvailable"]) && // previous sync operation reached the windowSize or maximumSyncReponseSize (cleanup_needed ||
!([theSyncKey isEqualToString: @"-1"]) && // new sync operation ( !([folderMetadata objectForKey: @"MoreAvailable"]) && // previous sync operation reached the windowSize or maximumSyncReponseSize
theFilterType) !([folderMetadata objectForKey: @"InitialLoadSequence"]))) &&
theFilterType
)
{ {
NSArray *allKeys; NSArray *allKeys;
NSString *key; NSString *key;
@ -622,14 +692,36 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if ([[dateCache objectForKey:key] compare: theFilterType] == NSOrderedAscending) if ([[dateCache objectForKey:key] compare: theFilterType] == NSOrderedAscending)
{ {
[s appendString: @"<SoftDelete xmlns=\"AirSync:\">"]; if ([syncCache objectForKey:key])
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", key]; {
[s appendString: @"</SoftDelete>"]; if (debugOn)
[self logWithFormat: @"EAS - SoftDelete %@", key];
[syncCache removeObjectForKey: key]; [s appendString: @"<SoftDelete xmlns=\"AirSync:\">"];
[dateCache removeObjectForKey: key]; [s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", key];
[s appendString: @"</SoftDelete>"];
[syncCache removeObjectForKey: key];
//[dateCache removeObjectForKey: key];
softdelete_count++; 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)) if (softdelete_count >= theWindowSize || (theMaxSyncResponseSize > 0 && [s length] >= theMaxSyncResponseSize))
@ -656,8 +748,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// //
if ([theSyncKey isEqualToString: [theCollection davCollectionTag]] && !([s length])) if ([theSyncKey isEqualToString: [theCollection davCollectionTag]] && !([s length]))
return; return;
more_available = NO;
davCollectionTagToStore = [theCollection davCollectionTag]; davCollectionTagToStore = [theCollection davCollectionTag];
@ -673,7 +763,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
NSDictionary *component; NSDictionary *component;
NSArray *allComponents; NSArray *allComponents;
BOOL updated; BOOL updated, initialLoadInProgress;
int deleted, return_count; int deleted, return_count;
if (theFolderType == ActiveSyncContactFolder) if (theFolderType == ActiveSyncContactFolder)
@ -683,13 +773,76 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
else else
component_name = @"vtodo"; component_name = @"vtodo";
allComponents = [theCollection syncTokenFieldsWithProperties: nil matchingSyncToken: theSyncKey fromDate: theFilterType]; initialLoadInProgress = NO;
allComponents = [allComponents sortedArrayUsingDescriptors: [NSArray arrayWithObjects: [[NSSortDescriptor alloc] initWithKey: @"c_lastmodified" ascending:YES], nil]];
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 // Check for the WindowSize
max = [allComponents count]; 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; return_count = 0;
for (i = 0; i < max; i++) for (i = 0; i < max; i++)
@ -847,12 +1000,38 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
NSMutableArray *allCacheObjects, *sortedBySequence; NSMutableArray *allCacheObjects, *sortedBySequence;
SOGoMailObject *mailObject; SOGoMailObject *mailObject;
NSArray *allMessages; NSArray *allMessages, *a;
NSString *firstUIDAdded;
int j, k, return_count; int j, k, return_count, highestmodseq;
BOOL found_in_cache; 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]; max = [allMessages count];
allCacheObjects = [NSMutableArray array]; allCacheObjects = [NSMutableArray array];
@ -860,7 +1039,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
for (i = 0; i < max; i++) for (i = 0; i < max; i++)
{ {
[allCacheObjects addObject: [SOGoSyncCacheObject syncCacheObjectWithUID: [[[allMessages objectAtIndex: i] allKeys] lastObject] [allCacheObjects addObject: [SOGoSyncCacheObject syncCacheObjectWithUID: [[[allMessages objectAtIndex: i] allKeys] lastObject]
sequence: [[[allMessages objectAtIndex: i] allValues] lastObject]]]; sequence: [[[allMessages objectAtIndex: i] allValues] lastObject]]];
} }
sortedBySequence = [[NSMutableArray alloc] initWithDictionary: syncCache]; sortedBySequence = [[NSMutableArray alloc] initWithDictionary: syncCache];
@ -869,15 +1048,82 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[allCacheObjects sortUsingSelector: @selector(compareSequence:)]; [allCacheObjects sortUsingSelector: @selector(compareSequence:)];
//NSLog(@"sortedBySequence (%d) - lastObject: %@", [sortedBySequence count], [sortedBySequence lastObject]); if (debugOn)
//NSLog(@"allCacheObjects (%d) - lastObject: %@", [allCacheObjects count], [allCacheObjects lastObject]); {
[self logWithFormat: @"EAS - sortedBySequence (%d) - lastObject: %@", [sortedBySequence count], [sortedBySequence lastObject]];
[self logWithFormat: @"EAS - allCacheObjects (%d) - lastObject: %@", [allCacheObjects count], [allCacheObjects lastObject]];
}
lastCacheObject = [sortedBySequence lastObject]; lastCacheObject = [sortedBySequence lastObject];
//
// 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 ([folderMetadata objectForKey: @"MoreAvailable"] && lastCacheObject) 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++) 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]]) if ([[lastCacheObject uid] isEqual: [[allCacheObjects objectAtIndex: j] uid]])
{ {
// Found out where we're at, let's continue from there... // 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) if (found_in_cache)
k = j+1; k = j+1;
else else
{ j = k = 0;
k = 0;
j = 0; if (debugOn)
} [self logWithFormat: @"EAS - found in cache: %d k = %d", found_in_cache, k];
//NSLog(@"found in cache: %d k = %d", found_in_cache, k);
return_count = 0; return_count = 0;
@ -911,20 +1155,35 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
NSString *lastSequence; NSString *lastSequence;
more_available = YES; more_available = YES;
lastSequence = ([[aCacheObject sequence] isEqual: [NSNull null]] ? @"1" : [aCacheObject sequence]); if (!firstUIDAdded)
*theLastServerKey = [[NSString alloc] initWithFormat: @"%@-%@", [aCacheObject uid], lastSequence]; {
//NSLog(@"Reached windowSize - lastUID will be: %@", *theLastServerKey); 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); DESTROY(pool);
break; break;
} }
aCacheObject = [allCacheObjects objectAtIndex: k]; 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 ([syncCache objectForKey: [aCacheObject uid]])
{ {
if ([[aCacheObject sequence] isEqual: [NSNull null]]) if ([[aCacheObject sequence] isEqual: [NSNull null]])
{ {
if (debugOn)
[self logWithFormat: @"EAS - DELETE!"];
// Deleted // Deleted
[s appendString: @"<Delete xmlns=\"AirSync:\">"]; [s appendString: @"<Delete xmlns=\"AirSync:\">"];
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", [aCacheObject uid]]; [s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", [aCacheObject uid]];
@ -942,9 +1201,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
mailObject = [theCollection lookupName: [aCacheObject uid] mailObject = [theCollection lookupName: [aCacheObject uid]
inContext: context inContext: context
acquire: 0]; acquire: 0];
if (![[aCacheObject sequence] isEqual: [syncCache objectForKey: [aCacheObject uid]]]) if (![[aCacheObject sequence] isEqual: [syncCache objectForKey: [aCacheObject uid]]])
{ {
if (debugOn)
[self logWithFormat: @"EAS - CHANGE!"];
[s appendString: @"<Change xmlns=\"AirSync:\">"]; [s appendString: @"<Change xmlns=\"AirSync:\">"];
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", [aCacheObject uid]]; [s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", [aCacheObject uid]];
[s appendString: @"<ApplicationData xmlns=\"AirSync:\">"]; [s appendString: @"<ApplicationData xmlns=\"AirSync:\">"];
@ -960,6 +1222,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
} }
else else
{ {
if (debugOn)
[self logWithFormat: @"EAS - ADD!"];
// Added // Added
if (![[aCacheObject sequence] isEqual: [NSNull null]]) 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]]; [syncCache setObject: [aCacheObject sequence] forKey: [aCacheObject uid]];
[dateCache setObject: [NSCalendarDate date] 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++; return_count++;
} }
else 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) if (more_available)
{ {
[folderMetadata setObject: [NSNumber numberWithBool: YES] forKey: @"MoreAvailable"]; [folderMetadata setObject: [NSNumber numberWithInt: YES] forKey: @"MoreAvailable"];
[folderMetadata setObject: *theLastServerKey forKey: @"SyncKey"]; [folderMetadata setObject: *theLastServerKey forKey: @"SyncKey"];
} }
else else
{ {
[folderMetadata removeObjectForKey: @"MoreAvailable"]; [folderMetadata removeObjectForKey: @"MoreAvailable"];
[folderMetadata setObject: davCollectionTagToStore forKey: @"SyncKey"];
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]]; [self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: theCollection withType: theFolderType]];
RELEASE(*theLastServerKey); RELEASE(*theLastServerKey);
} // default: } // default:
@ -1113,15 +1398,16 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
changeDetected: (BOOL *) changeDetected changeDetected: (BOOL *) changeDetected
maxSyncResponseSize: (int) theMaxSyncResponseSize maxSyncResponseSize: (int) theMaxSyncResponseSize
{ {
NSString *collectionId, *realCollectionId, *syncKey, *davCollectionTag, *bodyPreferenceType, *mimeSupport, *lastServerKey, *syncKeyInCache; NSString *collectionId, *realCollectionId, *syncKey, *davCollectionTag, *bodyPreferenceType, *mimeSupport, *lastServerKey, *syncKeyInCache, *folderKey;
SOGoMicrosoftActiveSyncFolderType folderType;
id collection, value;
NSMutableString *changeBuffer, *commandsBuffer;
BOOL getChanges, first_sync;
unsigned int windowSize, v, status;
NSMutableDictionary *folderMetadata, *folderOptions; NSMutableDictionary *folderMetadata, *folderOptions;
NSMutableArray *supportedElements, *supportedElementNames;
NSMutableString *changeBuffer, *commandsBuffer;
id collection, value;
SOGoMicrosoftActiveSyncFolderType folderType;
unsigned int windowSize, v, status, i;
BOOL getChanges, first_sync;
changeBuffer = [NSMutableString string]; changeBuffer = [NSMutableString string];
commandsBuffer = [NSMutableString string]; commandsBuffer = [NSMutableString string];
@ -1144,7 +1430,15 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//[theBuffer appendString: @"</Collection>"]; //[theBuffer appendString: @"</Collection>"];
return; 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 // We check for a window size, default to 100 if not specfied or out of bounds
windowSize = [[[(id)[theDocumentElement getElementsByTagName: @"WindowSize"] lastObject] textValue] intValue]; 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; first_sync = NO;
folderMetadata = [self _folderMetadataForKey: [self _getNameInCache: collection withType: folderType]];
if ([syncKey isEqualToString: @"0"]) if ([syncKey isEqualToString: @"0"])
{ {
davCollectionTag = @"-1"; davCollectionTag = @"-1";
first_sync = YES; first_sync = YES;
*changeDetected = 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"])) 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. // By default, send MIME mails. See #3146 for details.
if (!bodyPreferenceType) if (!bodyPreferenceType)
bodyPreferenceType = @"4"; bodyPreferenceType = @"4";
mimeSupport = [[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"MIMESupport"];
if (!mimeSupport)
mimeSupport = @"1";
} }
else else
{ {
@ -1225,11 +1546,13 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{ {
folderOptions = [[NSDictionary alloc] initWithObjectsAndKeys: mimeSupport, @"MIMESupport", bodyPreferenceType, @"BodyPreferenceType", nil]; folderOptions = [[NSDictionary alloc] initWithObjectsAndKeys: mimeSupport, @"MIMESupport", bodyPreferenceType, @"BodyPreferenceType", nil];
[folderMetadata setObject: folderOptions forKey: @"FolderOptions"]; [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: bodyPreferenceType forKey: @"BodyPreferenceType"];
[context setObject: mimeSupport forKey: @"MIMESupport"];
[context setObject: [folderMetadata objectForKey: @"SupportedElements"] forKey: @"SupportedElements"];
// //
// We process the commands from the request // We process the commands from the request
@ -1268,10 +1591,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
withFolderType: folderType withFolderType: folderType
withFilterType: [NSCalendarDate dateFromFilterType: [[(id)[theDocumentElement getElementsByTagName: @"FilterType"] lastObject] textValue]] withFilterType: [NSCalendarDate dateFromFilterType: [[(id)[theDocumentElement getElementsByTagName: @"FilterType"] lastObject] textValue]]
inBuffer: changeBuffer 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 // If we got any changes or if we have applied any commands
// let's regenerate our SyncKey based on the collection tag. // 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; SOGoSystemDefaults *defaults;
id <DOMElement> aCollection; id <DOMElement> aCollection;
NSMutableString *output, *s; NSMutableString *output, *s;
NSMutableDictionary *globalMetadata;
NSNumber *syncRequestInCache, *processIdentifier;
NSString *key;
NSArray *allCollections; NSArray *allCollections;
NSData *d; NSData *d;
int i, j, defaultInterval, heartbeatInterval, internalInterval, maxSyncResponseSize; int i, j, defaultInterval, heartbeatInterval, internalInterval, maxSyncResponseSize, total_sleep;
BOOL changeDetected; BOOL changeDetected;
changeDetected = NO;
maxSyncResponseSize = [[SOGoSystemDefaults sharedSystemDefaults] maximumSyncResponseSize];
// We initialize our output buffer // We initialize our output buffer
output = [[NSMutableString alloc] init]; 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: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
[output appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"]; [output appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
[output appendString: @"<Sync xmlns=\"AirSync:\">"]; [output appendString: @"<Sync xmlns=\"AirSync:\">"];
@ -1457,12 +1786,16 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[output appendString: @"</Sync>"]; [output appendString: @"</Sync>"];
d = [[output dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml]; d = [[output dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
[theResponse setContent: d]; [theResponse setContent: d];
RELEASE(output);
return; return;
} }
// Let other requests know about the collections we are dealing with.
[self _setOrUnsetSyncRequest: YES collections: allCollections];
defaults = [SOGoSystemDefaults sharedSystemDefaults]; changeDetected = NO;
maxSyncResponseSize = [[SOGoSystemDefaults sharedSystemDefaults] maximumSyncResponseSize];
heartbeatInterval = [[[(id)[theDocumentElement getElementsByTagName: @"HeartbeatInterval"] lastObject] textValue] intValue]; heartbeatInterval = [[[(id)[theDocumentElement getElementsByTagName: @"HeartbeatInterval"] lastObject] textValue] intValue];
defaultInterval = [defaults maximumSyncInterval];
internalInterval = [defaults internalSyncInterval]; internalInterval = [defaults internalSyncInterval];
// If the request doesn't contain "HeartbeatInterval" there is no reason to delay the response. // If the request doesn't contain "HeartbeatInterval" there is no reason to delay the response.
@ -1472,17 +1805,20 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// We check to see if our heartbeat interval falls into the supported ranges. // We check to see if our heartbeat interval falls into the supported ranges.
if (heartbeatInterval > defaultInterval || heartbeatInterval < 1) if (heartbeatInterval > defaultInterval || heartbeatInterval < 1)
{ {
int limit;
// Interval is too long, inform the client. // Interval is too long, inform the client.
heartbeatInterval = defaultInterval; heartbeatInterval = defaultInterval;
// Outlook doesn't like this... // When Status = 14, the Wait interval is specified in minutes while
//[output appendFormat: @"<Limit>%d</Limit>", defaultInterval]; // 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 appendFormat: @"<Status>%d</Status>", 14];
} }
[output appendString: @"<Collections>"]; [output appendString: @"<Collections>"];
allCollections = (id)[theDocumentElement getElementsByTagName: @"Collection"];
// We enter our loop detection change // We enter our loop detection change
for (i = 0; i < (heartbeatInterval/internalInterval); i++) for (i = 0; i < (heartbeatInterval/internalInterval); i++)
@ -1492,25 +1828,62 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
for (j = 0; j < [allCollections count]; j++) for (j = 0; j < [allCollections count]; j++)
{ {
aCollection = [allCollections objectAtIndex: j]; aCollection = [allCollections objectAtIndex: j];
[self processSyncCollection: aCollection [self processSyncCollection: aCollection
inBuffer: s inBuffer: s
changeDetected: &changeDetected changeDetected: &changeDetected
maxSyncResponseSize: maxSyncResponseSize]; 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; break;
} }
if (changeDetected) if (changeDetected)
{ {
[self logWithFormat: @"Change detected, we push the content."]; [self logWithFormat: @"Change detected during Sync, we push the content."];
break; break;
} }
else if (heartbeatInterval > 1) else if (heartbeatInterval > 1)
{ {
[self logWithFormat: @"Sleeping %d seconds while detecting changes...", internalInterval]; total_sleep = 0;
sleep(internalInterval);
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 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"]) 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. // We always return the last generated response.

View File

@ -1,6 +1,6 @@
/* /*
Copyright (c) 2014, Inverse inc. Copyright (c) 2014-2015, Inverse inc.
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without 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" #include "SOGoActiveSyncConstants.h"
@class NSCalendarDate;
@class NSException; @class NSException;
@class NSMutableDictionary;
@class NSURL; @class NSURL;
@class NSNumber;
@interface SOGoActiveSyncDispatcher : NSObject @interface SOGoActiveSyncDispatcher : NSObject
{ {
NSURL *folderTableURL; NSURL *folderTableURL;
NSDictionary *imapFolderGUIDS;
id context; id context;
NSNumber *syncRequest;
BOOL debugOn;
} }
- (NSMutableDictionary *) globalMetadataForDevice;
- (id) collectionFromId: (NSString *) theCollectionId - (id) collectionFromId: (NSString *) theCollectionId
type: (SOGoMicrosoftActiveSyncFolderType) theFolderType; type: (SOGoMicrosoftActiveSyncFolderType) theFolderType;

View File

@ -30,8 +30,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "SOGoActiveSyncDispatcher.h" #include "SOGoActiveSyncDispatcher.h"
#import <Foundation/NSArray.h> #import <Foundation/NSArray.h>
#import <Foundation/NSData.h>
#import <Foundation/NSAutoreleasePool.h> #import <Foundation/NSAutoreleasePool.h>
#import <Foundation/NSCalendarDate.h> #import <Foundation/NSCalendarDate.h>
#if GNUSTEP_BASE_MINOR_VERSION >= 21
#import <Foundation/NSLocale.h>
#endif
#import <Foundation/NSProcessInfo.h> #import <Foundation/NSProcessInfo.h>
#import <Foundation/NSTimeZone.h> #import <Foundation/NSTimeZone.h>
#import <Foundation/NSURL.h> #import <Foundation/NSURL.h>
@ -134,6 +138,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <unistd.h> #include <unistd.h>
#ifdef HAVE_OPENSSL
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/x509.h>
#endif
@interface SOGoActiveSyncDispatcher (Sync) @interface SOGoActiveSyncDispatcher (Sync)
- (NSMutableDictionary *) _folderMetadataForKey: (NSString *) theFolderKey; - (NSMutableDictionary *) _folderMetadataForKey: (NSString *) theFolderKey;
@ -143,20 +153,22 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@implementation SOGoActiveSyncDispatcher @implementation SOGoActiveSyncDispatcher
static BOOL debugOn = NO;
- (id) init - (id) init
{ {
[super init]; [super init];
debugOn = [[SOGoSystemDefaults sharedSystemDefaults] easDebugEnabled]; debugOn = [[SOGoSystemDefaults sharedSystemDefaults] easDebugEnabled];
folderTableURL = nil; folderTableURL = nil;
imapFolderGUIDS = nil;
syncRequest = nil;
return self; return self;
} }
- (void) dealloc - (void) dealloc
{ {
RELEASE(folderTableURL); RELEASE(folderTableURL);
RELEASE(imapFolderGUIDS);
RELEASE(syncRequest);
[super dealloc]; [super dealloc];
} }
@ -169,16 +181,16 @@ static BOOL debugOn = NO;
[o setTableUrl: [self folderTableURL]]; [o setTableUrl: [self folderTableURL]];
[o reloadIfNeeded]; [o reloadIfNeeded];
[[o properties] removeAllObjects]; [[o properties] setObject: theSyncKey
[[o properties] addEntriesFromDictionary: [NSDictionary dictionaryWithObject: theSyncKey forKey: @"FolderSyncKey"]]; forKey: @"FolderSyncKey"];
[o save]; [o save];
} }
- (NSMutableDictionary *) _globalMetadataForDevice - (NSMutableDictionary *) globalMetadataForDevice
{ {
SOGoCacheGCSObject *o; SOGoCacheGCSObject *o;
o = [SOGoCacheGCSObject objectWithName: [context objectForKey: @"DeviceId"] inContainer: nil]; o = [SOGoCacheGCSObject objectWithName: [context objectForKey: @"DeviceId"] inContainer: nil useCache: NO];
[o setObjectType: ActiveSyncGlobalCacheObject]; [o setObjectType: ActiveSyncGlobalCacheObject];
[o setTableUrl: [self folderTableURL]]; [o setTableUrl: [self folderTableURL]];
[o reloadIfNeeded]; [o reloadIfNeeded];
@ -202,7 +214,7 @@ static BOOL debugOn = NO;
if (theFilter) if (theFilter)
{ {
o = [SOGoCacheGCSObject objectWithName: [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], theCollectionId] inContainer: nil]; o = [SOGoCacheGCSObject objectWithName: [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], theCollectionId] inContainer: nil];
[o setObjectType: ActiveSyncGlobalCacheObject]; [o setObjectType: ActiveSyncFolderCacheObject];
[o setTableUrl: [self folderTableURL]]; [o setTableUrl: [self folderTableURL]];
[o reloadIfNeeded]; [o reloadIfNeeded];
@ -229,23 +241,27 @@ static BOOL debugOn = NO;
SOGoMailAccounts *accountsFolder; SOGoMailAccounts *accountsFolder;
SOGoMailAccount *accountFolder; SOGoMailAccount *accountFolder;
SOGoUserFolder *userFolder; SOGoUserFolder *userFolder;
NSDictionary *imapGUIDs;
userFolder = [[context activeUser] homeFolderInContext: context]; if (!imapFolderGUIDS)
accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO]; {
accountFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO]; userFolder = [[context activeUser] homeFolderInContext: context];
accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
// Get the GUID of the IMAP folder accountFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO];
imapGUIDs = [accountFolder imapFolderGUIDs];
// Get the GUID of the IMAP folder
//return [[imapGUIDs allKeysForObject: theIdToTranslate] objectAtIndex: 0]; imapFolderGUIDS = [accountFolder imapFolderGUIDs];
return [[[imapGUIDs allKeysForObject: [NSString stringWithFormat: @"folder%@", theIdToTranslate]] objectAtIndex: 0] substringFromIndex: 6] ; [imapFolderGUIDS retain];
}
return [[[imapFolderGUIDS allKeysForObject: [NSString stringWithFormat: @"folder%@", theIdToTranslate]] objectAtIndex: 0] substringFromIndex: 6] ;
} }
return theIdToTranslate; return theIdToTranslate;
} }
// //
// //
// //
@ -691,26 +707,25 @@ static BOOL debugOn = NO;
- (void) processFolderSync: (id <DOMElement>) theDocumentElement - (void) processFolderSync: (id <DOMElement>) theDocumentElement
inResponse: (WOResponse *) theResponse 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; NSDictionary *folderMetadata, *imapGUIDs;
NSArray *allFoldersMetadata, *allKeys; NSArray *allFoldersMetadata, *allKeys;
NSMutableDictionary *cachedGUIDs, *metadata;
SOGoMailAccounts *accountsFolder; SOGoMailAccounts *accountsFolder;
SOGoMailAccount *accountFolder; SOGoMailAccount *accountFolder;
NSMutableString *s, *commands; NSMutableString *s, *commands;
SOGoUserFolder *userFolder; SOGoUserFolder *userFolder;
NSMutableArray *folders, *processedFolders;
SoSecurityManager *sm; SoSecurityManager *sm;
SOGoCacheGCSObject *o; SOGoCacheGCSObject *o;
id currentFolder; id currentFolder;
NSData *d; NSData *d;
int status, command_count, i, type, fi, count; int status, command_count, i, type, fi, count;
BOOL first_sync; BOOL first_sync;
sm = [SoSecurityManager sharedSecurityManager]; sm = [SoSecurityManager sharedSecurityManager];
metadata = [self _globalMetadataForDevice]; metadata = [self globalMetadataForDevice];
syncKey = [[(id)[theDocumentElement getElementsByTagName: @"SyncKey"] lastObject] textValue]; syncKey = [[(id)[theDocumentElement getElementsByTagName: @"SyncKey"] lastObject] textValue];
s = [NSMutableString string]; s = [NSMutableString string];
@ -824,32 +839,38 @@ static BOOL debugOn = NO;
} }
else else
{ {
if ([cKey rangeOfString: @"vevent" options: NSCaseInsensitiveSearch].location != NSNotFound || if ([cKey rangeOfString: @"vevent" options: NSCaseInsensitiveSearch].location != NSNotFound ||
[cKey rangeOfString: @"vtodo" options: NSCaseInsensitiveSearch].location != NSNotFound) [cKey rangeOfString: @"vtodo" options: NSCaseInsensitiveSearch].location != NSNotFound)
folderType = @"Calendar"; folderType = @"Calendar";
else else
folderType = @"Contacts"; folderType = @"Contacts";
if ([ cKey rangeOfString: @"/"].location != NSNotFound) if ([ cKey rangeOfString: @"/"].location != NSNotFound)
currentFolder = [[[[context activeUser] homeFolderInContext: context] lookupName: folderType inContext: context acquire: NO] currentFolder = [[[[context activeUser] homeFolderInContext: context] lookupName: folderType inContext: context acquire: NO]
lookupName: [cKey substringFromIndex: [cKey rangeOfString: @"/"].location+1] 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 || if ([currentFolder isKindOfClass: [SOGoGCSFolder class]] &&
[sm validatePermission: SoPerm_DeleteObjects [[currentFolder nameInContainer] isEqualToString: @"personal"])
onObject: currentFolder continue;
inContext: context] ||
[sm validatePermission: SoPerm_AddDocumentsImagesAndFiles // 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
onObject: currentFolder if (!currentFolder ||
inContext: context]) ![currentFolder synchronize] ||
{ [sm validatePermission: SoPerm_DeleteObjects
[commands appendFormat: @"<Delete><ServerId>%@</ServerId></Delete>", [cKey stringByEscapingURL] ]; onObject: currentFolder
command_count++; inContext: context] ||
[o destroy]; [sm validatePermission: SoPerm_AddDocumentsImagesAndFiles
} onObject: currentFolder
} inContext: context])
} {
} [commands appendFormat: @"<Delete><ServerId>%@</ServerId></Delete>", [cKey stringByEscapingURL] ];
command_count++;
[o destroy];
}
}
}
}
// Handle addition and changes // Handle addition and changes
for (i = 0; i < [allFoldersMetadata count]; i++) for (i = 0; i < [allFoldersMetadata count]; i++)
@ -937,7 +958,9 @@ static BOOL debugOn = NO;
[[o properties] removeObjectForKey: @"DateCache"]; [[o properties] removeObjectForKey: @"DateCache"];
[[o properties] removeObjectForKey: @"MoreAvailable"]; [[o properties] removeObjectForKey: @"MoreAvailable"];
[[o properties] removeObjectForKey: @"BodyPreferenceType"]; [[o properties] removeObjectForKey: @"BodyPreferenceType"];
[[o properties] removeObjectForKey: @"SupportedElements"];
[[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"]; [[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"];
[[o properties] removeObjectForKey: @"InitialLoadSequence"];
[o save]; [o save];
command_count++; command_count++;
@ -946,17 +969,29 @@ static BOOL debugOn = NO;
personalFolderName = [[[context activeUser] personalCalendarFolderInContext: context] nameInContainer]; personalFolderName = [[[context activeUser] personalCalendarFolderInContext: context] nameInContainer];
folders = [[[[[context activeUser] homeFolderInContext: context] lookupName: @"Calendar" inContext: context acquire: NO] subFolders] mutableCopy]; 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]]; [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; count = [folders count]-1;
for (; count >= 0; count--) for (; count >= 0; count--)
{ {
if ([sm validatePermission: SoPerm_DeleteObjects currentFolder = [folders objectAtIndex: count];
onObject: [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] || inContext: context] ||
[sm validatePermission: SoPerm_AddDocumentsImagesAndFiles [sm validatePermission: SoPerm_AddDocumentsImagesAndFiles
onObject: [folders objectAtIndex: count] onObject: currentFolder
inContext: context]) inContext: context])
{ {
[folders removeObjectAtIndex: count]; [folders removeObjectAtIndex: count];
@ -964,7 +999,6 @@ static BOOL debugOn = NO;
} }
count = [folders count]-1; count = [folders count]-1;
NSString *operation;
for (fi = 0; fi <= count ; fi++) for (fi = 0; fi <= count ; fi++)
{ {
@ -1022,7 +1056,9 @@ static BOOL debugOn = NO;
[[o properties] removeObjectForKey: @"DateCache"]; [[o properties] removeObjectForKey: @"DateCache"];
[[o properties] removeObjectForKey: @"MoreAvailable"]; [[o properties] removeObjectForKey: @"MoreAvailable"];
[[o properties] removeObjectForKey: @"BodyPreferenceType"]; [[o properties] removeObjectForKey: @"BodyPreferenceType"];
[[o properties] removeObjectForKey: @"SupportedElements"];
[[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"]; [[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"];
[[o properties] removeObjectForKey: @"InitialLoadSequence"];
} }
[o save]; [o save];
@ -1045,7 +1081,9 @@ static BOOL debugOn = NO;
[[o properties] removeObjectForKey: @"DateCache"]; [[o properties] removeObjectForKey: @"DateCache"];
[[o properties] removeObjectForKey: @"MoreAvailable"]; [[o properties] removeObjectForKey: @"MoreAvailable"];
[[o properties] removeObjectForKey: @"BodyPreferenceType"]; [[o properties] removeObjectForKey: @"BodyPreferenceType"];
[[o properties] removeObjectForKey: @"SupportedElements"];
[[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"]; [[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"];
[[o properties] removeObjectForKey: @"InitialLoadSequence"];
} }
[o save]; [o save];
@ -1204,7 +1242,7 @@ static BOOL debugOn = NO;
filter = [NSCalendarDate dateFromFilterType: [[(id)[[allCollections objectAtIndex: j] getElementsByTagName: @"FilterType"] lastObject] textValue]]; filter = [NSCalendarDate dateFromFilterType: [[(id)[[allCollections objectAtIndex: j] getElementsByTagName: @"FilterType"] lastObject] textValue]];
syncKey = [[(id)[[allCollections objectAtIndex: j] getElementsByTagName: @"SyncKey"] 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]; count = [allMessages count];
@ -1254,7 +1292,7 @@ static BOOL debugOn = NO;
- (void) processItemOperations: (id <DOMElement>) theDocumentElement - (void) processItemOperations: (id <DOMElement>) theDocumentElement
inResponse: (WOResponse *) theResponse inResponse: (WOResponse *) theResponse
{ {
NSString *fileReference, *realCollectionId, *serverId, *bodyPreferenceType, *collectionId; NSString *fileReference, *realCollectionId, *serverId, *bodyPreferenceType, *mimeSupport, *collectionId;
NSMutableString *s; NSMutableString *s;
NSArray *fetchRequests; NSArray *fetchRequests;
id aFetch; id aFetch;
@ -1367,6 +1405,8 @@ static BOOL debugOn = NO;
serverId = [[(id)[theDocumentElement getElementsByTagName: @"ServerId"] lastObject] textValue]; serverId = [[(id)[theDocumentElement getElementsByTagName: @"ServerId"] lastObject] textValue];
bodyPreferenceType = [[(id)[[(id)[theDocumentElement getElementsByTagName: @"BodyPreference"] lastObject] getElementsByTagName: @"Type"] lastObject] textValue]; bodyPreferenceType = [[(id)[[(id)[theDocumentElement getElementsByTagName: @"BodyPreference"] lastObject] getElementsByTagName: @"Type"] lastObject] textValue];
[context setObject: bodyPreferenceType forKey: @"BodyPreferenceType"]; [context setObject: bodyPreferenceType forKey: @"BodyPreferenceType"];
mimeSupport = [[(id)[theDocumentElement getElementsByTagName: @"MIMESupport"] lastObject] textValue];
[context setObject: mimeSupport forKey: @"MIMESupport"];
currentCollection = [self collectionFromId: realCollectionId type: folderType]; currentCollection = [self collectionFromId: realCollectionId type: folderType];
@ -2024,13 +2064,13 @@ static BOOL debugOn = NO;
if ([foldersWithChanges count]) 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; status = 2;
break; break;
} }
else else
{ {
[self logWithFormat: @"Sleeping %d seconds while detecting changes...", internalInterval]; [self logWithFormat: @"Sleeping %d seconds while detecting changes in Ping...", internalInterval];
sleep(internalInterval); sleep(internalInterval);
} }
} }
@ -2094,6 +2134,123 @@ static BOOL debugOn = NO;
[theResponse setContent: d]; [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"?> // <?xml version="1.0"?>
// <!DOCTYPE ActiveSync PUBLIC "-//MICROSOFT//DTD ActiveSync//EN" "http://www.microsoft.com/"> // <!DOCTYPE ActiveSync PUBLIC "-//MICROSOFT//DTD ActiveSync//EN" "http://www.microsoft.com/">
@ -2410,39 +2567,100 @@ static BOOL debugOn = NO;
NGMimeMessageParser *parser; NGMimeMessageParser *parser;
NGMimeMessage *message; NGMimeMessage *message;
NSException *error; NSException *error;
NSData *data; NSMutableData *data;
NGMutableHashMap *map; NSData *new_from_header;
NGMimeMessage *messageToSend;
NGMimeMessageGenerator *generator;
NSDictionary *identity; NSDictionary *identity;
NSString *fullName, *email; NSString *fullName, *email;
const char *bytes;
int i, e, len;
BOOL found_header;
// We get the mail's data // 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 // We extract the recipients
parser = [[NGMimeMessageParser alloc] init]; parser = [[NGMimeMessageParser alloc] init];
message = [parser parsePartFromData: data]; message = [parser parsePartFromData: data];
RELEASE(parser); RELEASE(parser);
map = [NGHashMap hashMapWithDictionary: [message headers]];
identity = [[context activeUser] primaryIdentity]; identity = [[context activeUser] primaryIdentity];
fullName = [identity objectForKey: @"fullName"]; fullName = [identity objectForKey: @"fullName"];
email = [identity objectForKey: @"email"]; email = [identity objectForKey: @"email"];
if ([fullName length]) 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 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;
}
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]];
}
generator = [[[NGMimeMessageGenerator alloc] init] autorelease];
data = [generator generateMimeFromPart: messageToSend];
error = [self _sendMail: data error = [self _sendMail: data
recipients: [message allRecipients] recipients: [message allRecipients]
saveInSentItems: ([(id)[theDocumentElement getElementsByTagName: @"SaveInSentItems"] count] ? YES : NO)]; saveInSentItems: ([(id)[theDocumentElement getElementsByTagName: @"SaveInSentItems"] count] ? YES : NO)];
@ -2619,6 +2837,11 @@ static BOOL debugOn = NO;
[map setObject: [mailObject messageId] forKey: @"in-reply-to"]; [map setObject: [mailObject messageId] forKey: @"in-reply-to"];
references = [[[[[mailObject mailHeaders] objectForKey: @"references"] componentsSeparatedByString: @" "] mutableCopy] autorelease]; 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 ([references count] > 0)
{ {
// If there are more than ten identifiers listed, we eliminate the second one. // 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) options: NSCaseInsensitiveSearch].location == NSNotFound)
{ {
NSString *value; 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]; value = [[NSDate date] descriptionWithCalendarFormat: @"%a, %d %b %Y %H:%M:%S %z"
s = [NSString stringWithFormat: @"Date: %@\n%@", value, [theRequest contentAsString]]; 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]];
#endif
s = [NSString stringWithFormat: @"Date: %@\r\n%@", value, [theRequest contentAsString]];
} }
else else
{ {
@ -3026,8 +3265,7 @@ static BOOL debugOn = NO;
return nil; return nil;
urlString = [[user domainDefaults] folderInfoURL]; urlString = [[user domainDefaults] folderInfoURL];
parts = [[urlString componentsSeparatedByString: @"/"] parts = [[urlString componentsSeparatedByString: @"/"] mutableCopy];
mutableCopy];
[parts autorelease]; [parts autorelease];
if ([parts count] == 5) if ([parts count] == 5)
{ {

View File

@ -472,18 +472,46 @@ struct GlobalObjectId {
return d; 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 - (NSData *) _preferredBodyDataUsingType: (int) theType
mimeSupport: (int) theMimeSupport
nativeType: (int *) theNativeType nativeType: (int *) theNativeType
{ {
NSString *type, *subtype, *encoding; NSString *type, *subtype, *encoding;
NSData *d; NSData *d;
BOOL isSMIME, sanitinizeNeeded;
type = [[[self bodyStructure] valueForKey: @"type"] lowercaseString]; type = [[[self bodyStructure] valueForKey: @"type"] lowercaseString];
subtype = [[[self bodyStructure] valueForKey: @"subtype"] lowercaseString]; subtype = [[[self bodyStructure] valueForKey: @"subtype"] lowercaseString];
isSMIME = NO;
d = nil; d = nil;
// We determine the native type // We determine the native type
@ -494,8 +522,14 @@ struct GlobalObjectId {
else if ([type isEqualToString: @"multipart"]) else if ([type isEqualToString: @"multipart"])
*theNativeType = 4; *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 // 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"]) if ([type isEqualToString: @"text"] && ![subtype isEqualToString: @"calendar"])
{ {
@ -536,12 +570,12 @@ struct GlobalObjectId {
d = [self _preferredBodyDataInMultipartUsingType: theType nativeTypeFound: theNativeType]; 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 // We sanitize the content if the content-transfer-encoding is 8bit and charset is not utf-8 or us-ascii.
// with quoted-printable/base64 encoded text parts. It just doesn't decode them. sanitinizeNeeded = [self _sanitinizeNeeded: [NSArray arrayWithObject: [self bodyStructure]]];
encoding = [[self lookupInfoForBodyPart: @""] objectForKey: @"encoding"];
if ([[context objectForKey: @"DeviceType"] isEqualToString: @"WindowsOutlook15"] || ([encoding caseInsensitiveCompare: @"8bit"] == NSOrderedSame)) if (sanitinizeNeeded && !isSMIME)
d = [self _sanitizedMIMEMessage]; d = [self _sanitizedMIMEMessage];
else else
d = [self content]; d = [self content];
@ -656,16 +690,18 @@ struct GlobalObjectId {
{ {
NSData *d, *globalObjId; NSData *d, *globalObjId;
NSArray *attachmentKeys; NSArray *attachmentKeys;
NSMutableString *s;
uint32_t v;
NSString *p;
id value;
iCalCalendar *calendar; iCalCalendar *calendar;
NSString *p, *subtype;
NSMutableString *s;
id value;
int preferredBodyType, nativeBodyType; int preferredBodyType, mimeSupport, nativeBodyType;
uint32_t v;
subtype = [[[self bodyStructure] valueForKey: @"subtype"] lowercaseString];
preferredBodyType = [[context objectForKey: @"BodyPreferenceType"] intValue];
mimeSupport = [[context objectForKey: @"MIMESupport"] intValue];
s = [NSMutableString string]; s = [NSMutableString string];
@ -862,7 +898,12 @@ struct GlobalObjectId {
else else
{ {
// MesssageClass and ContentClass // MesssageClass and ContentClass
[s appendFormat: @"<MessageClass xmlns=\"Email:\">%@</MessageClass>", @"IPM.Note"]; 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"]; [s appendFormat: @"<ContentClass xmlns=\"Email:\">%@</ContentClass>", @"urn:content-classes:message"];
} }
@ -876,10 +917,8 @@ struct GlobalObjectId {
[s appendFormat: @"<InternetCPID xmlns=\"Email:\">%@</InternetCPID>", @"65001"]; [s appendFormat: @"<InternetCPID xmlns=\"Email:\">%@</InternetCPID>", @"65001"];
// Body - namespace 17 // Body - namespace 17
preferredBodyType = [[context objectForKey: @"BodyPreferenceType"] intValue];
nativeBodyType = 1; nativeBodyType = 1;
d = [self _preferredBodyDataUsingType: preferredBodyType nativeType: &nativeBodyType]; d = [self _preferredBodyDataUsingType: preferredBodyType mimeSupport: mimeSupport nativeType: &nativeBodyType];
if (calendar && !d) if (calendar && !d)
{ {
@ -981,9 +1020,12 @@ struct GlobalObjectId {
{ {
[s appendString: @"<Body xmlns=\"AirSyncBase:\">"]; [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) if (preferredBodyType == 2 && nativeBodyType == 1)
[s appendString: @"<Type>1</Type>"]; [s appendString: @"<Type>1</Type>"];
else if (([subtype isEqualToString: @"signed"] || [subtype isEqualToString: @"pkcs7-mime"] ) && mimeSupport > 0)
[s appendString: @"<Type>4</Type>"];
else else
[s appendFormat: @"<Type>%d</Type>", preferredBodyType]; [s appendFormat: @"<Type>%d</Type>", preferredBodyType];
@ -1001,7 +1043,8 @@ struct GlobalObjectId {
// Attachments -namespace 16 // Attachments -namespace 16
attachmentKeys = [self fetchFileAttachmentKeys]; attachmentKeys = [self fetchFileAttachmentKeys];
if ([attachmentKeys count])
if ([attachmentKeys count] && !([subtype isEqualToString: @"signed"]))
{ {
int i; int i;
@ -1071,9 +1114,15 @@ struct GlobalObjectId {
if ([[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"14.0"] || if ([[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"14.0"] ||
[[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"14.1"]) [[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"14.1"])
{ {
id value;
NSString *reference; 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) if ([reference length] > 0)
[s appendFormat: @"<ConversationId xmlns=\"Email2:\">%@</ConversationId>", [[reference dataUsingEncoding: NSUTF8StringEncoding] activeSyncRepresentationInContext: context]]; [s appendFormat: @"<ConversationId xmlns=\"Email2:\">%@</ConversationId>", [[reference dataUsingEncoding: NSUTF8StringEncoding] activeSyncRepresentationInContext: context]];

View File

@ -37,6 +37,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@interface SOGoSyncCacheObject : NSObject @interface SOGoSyncCacheObject : NSObject
{ {
@public
id _uid; id _uid;
id _sequence; id _sequence;
} }

View File

@ -32,9 +32,17 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <Foundation/NSNull.h> #import <Foundation/NSNull.h>
#import <Foundation/NSString.h> #import <Foundation/NSString.h>
#import <Foundation/NSValue.h>
static Class NSNullK;
@implementation SOGoSyncCacheObject @implementation SOGoSyncCacheObject
+ (void) initialize
{
NSNullK = [NSNull class];
}
- (id) init - (id) init
{ {
if ((self = [super init])) if ((self = [super init]))
@ -46,14 +54,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
return self; return self;
} }
+ (id) syncCacheObjectWithUID: (id) theUID sequence: (id) theSequence; + (id) syncCacheObjectWithUID: (id) theUID sequence: (id) theSequence
{ {
id o; id o;
o = [[self alloc] init]; o = [[self alloc] init];
[o setUID: theUID]; [o setUID: [NSNumber numberWithInt: [theUID intValue]]];
[o setSequence: theSequence]; [o setSequence: ([theSequence isKindOfClass: NSNullK] ? theSequence : [NSNumber numberWithInt: [theSequence intValue]])];
return [o autorelease]; return [o autorelease];
} }
@ -67,7 +75,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- (id) uid - (id) uid
{ {
return _uid; return [_uid description];
} }
- (void) setUID: (id) theUID - (void) setUID: (id) theUID
@ -77,7 +85,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- (id) sequence - (id) sequence
{ {
return _sequence; return ([_sequence isKindOfClass: NSNullK] ? _sequence : [_sequence description]);
} }
- (void) setSequence: (id) theSequence - (void) setSequence: (id) theSequence
@ -88,7 +96,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- (NSComparisonResult) compareUID: (SOGoSyncCacheObject *) theSyncCacheObject - (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 - (NSComparisonResult) compareSequence: (SOGoSyncCacheObject *) theSyncCacheObject
{ {
if ([[self sequence] isEqual: [NSNull null]] && if ([self->_sequence isEqual: [NSNull null]] &&
[[theSyncCacheObject sequence] isEqual: [NSNull null]]) [theSyncCacheObject->_sequence isEqual: [NSNull null]])
return [self compareUID: theSyncCacheObject]; 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; 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; return NSOrderedAscending;
// Must check this here, to avoid comparing NSNull objects // 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 compareUID: theSyncCacheObject];
return [[self sequence] compare: [theSyncCacheObject sequence]]; return [self->_sequence compare: theSyncCacheObject->_sequence];
} }
- (NSString *) description - (NSString *) description
@ -120,3 +128,4 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
} }
@end @end

View File

@ -58,7 +58,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// don't send negative reminder - not supported // don't send negative reminder - not supported
if (delta > 0) if (delta > 0)
[s appendFormat: @"<Reminder xmlns=\"Calendar:\">%d</Reminder>", delta]; [s appendFormat: @"<Reminder xmlns=\"Calendar:\">%d</Reminder>", (int)delta];
} }
return s; return s;

View File

@ -38,6 +38,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <Foundation/NSTimeZone.h> #import <Foundation/NSTimeZone.h>
#import <NGExtensions/NSString+misc.h> #import <NGExtensions/NSString+misc.h>
#import <NGExtensions/NSCalendarDate+misc.h>
#import <NGExtensions/NSObject+Logs.h>
#import <NGObjWeb/WOContext.h> #import <NGObjWeb/WOContext.h>
#import <NGObjWeb/WOContext+SoObjects.h> #import <NGObjWeb/WOContext+SoObjects.h>
#import <NGObjWeb/WORequest.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/iCalCalendar.h>
#import <NGCards/iCalDateTime.h> #import <NGCards/iCalDateTime.h>
#import <NGCards/iCalPerson.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/SOGoUser.h>
#import <SOGo/SOGoUserDefaults.h> #import <SOGo/SOGoUserDefaults.h>
#import <SOGo/WORequest+SOGo.h>
#import <Appointments/iCalEntityObject+SOGo.h> #import <Appointments/iCalEntityObject+SOGo.h>
#import <Appointments/iCalRepeatableEntityObject+SOGo.h>
#include "iCalAlarm+ActiveSync.h" #include "iCalAlarm+ActiveSync.h"
#include "iCalRecurrenceRule+ActiveSync.h" #include "iCalRecurrenceRule+ActiveSync.h"
@ -98,10 +105,13 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
else if ([self created]) else if ([self created])
[s appendFormat: @"<DTStamp xmlns=\"Calendar:\">%@</DTStamp>", [[self created] activeSyncRepresentationWithoutSeparatorsInContext: context]]; [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 // StartTime -- http://msdn.microsoft.com/en-us/library/ee157132(v=exchg.80).aspx
if ([self startDate]) if ([self startDate])
{ {
if ([self isAllDay]) if ([self isAllDay] && !tz)
[s appendFormat: @"<StartTime xmlns=\"Calendar:\">%@</StartTime>", [s appendFormat: @"<StartTime xmlns=\"Calendar:\">%@</StartTime>",
[[[self startDate] dateByAddingYears: 0 months: 0 days: 0 [[[self startDate] dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 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 // EndTime -- http://msdn.microsoft.com/en-us/library/ee157945(v=exchg.80).aspx
if ([self endDate]) if ([self endDate])
{ {
if ([self isAllDay]) if ([self isAllDay] && !tz)
[s appendFormat: @"<EndTime xmlns=\"Calendar:\">%@</EndTime>", [s appendFormat: @"<EndTime xmlns=\"Calendar:\">%@</EndTime>",
[[[self endDate] dateByAddingYears: 0 months: 0 days: 0 [[[self endDate] dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 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]]; [s appendFormat: @"<EndTime xmlns=\"Calendar:\">%@</EndTime>", [[self endDate] activeSyncRepresentationWithoutSeparatorsInContext: context]];
} }
// Timezone
tz = [(iCalDateTime *)[self firstChildWithTag: @"dtstart"] timeZone];
if (!tz) if (!tz)
tz = [iCalTimeZone timeZoneForName: [userTimeZone name]]; tz = [iCalTimeZone timeZoneForName: [userTimeZone name]];
@ -226,9 +233,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//[s appendFormat: @"<Importance xmlns=\"Calendar:\">%d</Importance>", v]; //[s appendFormat: @"<Importance xmlns=\"Calendar:\">%d</Importance>", v];
// UID -- http://msdn.microsoft.com/en-us/library/ee159919(v=exchg.80).aspx // 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]]; [s appendFormat: @"<UID xmlns=\"Calendar:\">%@</UID>", [self uid]];
// Sensitivity // Sensitivity
if ([[self accessClass] isEqualToString: @"PRIVATE"]) if ([[self accessClass] isEqualToString: @"PRIVATE"])
v = 2; v = 2;
@ -251,7 +258,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[s appendFormat: @"</Categories>"]; [s appendFormat: @"</Categories>"];
} }
// Reminder -- http://msdn.microsoft.com/en-us/library/ee219691(v=exchg.80).aspx // Reminder -- http://msdn.microsoft.com/en-us/library/ee219691(v=exchg.80).aspx
// TODO: improve this to handle more alarm types // TODO: improve this to handle more alarm types
if ([self hasAlarms]) if ([self hasAlarms])
@ -265,7 +271,74 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Recurrence rules // Recurrence rules
if ([self isRecurrent]) if ([self isRecurrent])
{ {
NSMutableArray *components, *exdates;
iCalEvent *current_component;
NSString *recurrence_id;
unsigned int count, max, i;
[s appendString: [[[self recurrenceRules] lastObject] activeSyncRepresentationInContext: context]]; [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 // Comment
@ -285,13 +358,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{ {
[s appendString: @"<Body xmlns=\"AirSyncBase:\">"]; [s appendString: @"<Body xmlns=\"AirSyncBase:\">"];
[s appendFormat: @"<Type>%d</Type>", 1]; [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 appendFormat: @"<Data>%@</Data>", o];
[s appendString: @"</Body>"]; [s appendString: @"</Body>"];
} }
} }
[s appendFormat: @"<NativeBodyType xmlns=\"AirSyncBase:\">%d</NativeBodyType>", 1]; if (![self recurrenceId])
[s appendFormat: @"<NativeBodyType xmlns=\"AirSyncBase:\">%d</NativeBodyType>", 1];
return s; return s;
} }
@ -337,13 +411,19 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- (void) takeActiveSyncValues: (NSDictionary *) theValues - (void) takeActiveSyncValues: (NSDictionary *) theValues
inContext: (WOContext *) context inContext: (WOContext *) context
{ {
iCalDateTime *start, *end; iCalDateTime *start, *end; //, *oldstart;
NSCalendarDate *oldstart;
NSTimeZone *userTimeZone; NSTimeZone *userTimeZone;
iCalTimeZone *tz; iCalTimeZone *tz;
id o; id o;
int deltasecs;
BOOL isAllDay; BOOL isAllDay;
NSMutableArray *occurences;
occurences = [NSMutableArray arrayWithArray: [[self parent] events]];
if ((o = [theValues objectForKey: @"UID"])) if ((o = [theValues objectForKey: @"UID"]))
[self setUid: o]; [self setUid: o];
@ -356,6 +436,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{ {
isAllDay = YES; 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 // 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]; o = [o calendarDate];
start = (iCalDateTime *) [self uniqueChildWithTag: @"dtstart"]; start = (iCalDateTime *) [self uniqueChildWithTag: @"dtstart"];
oldstart = [start dateTime];
[start setTimeZone: tz]; [start setTimeZone: tz];
if (isAllDay) if (isAllDay)
@ -433,6 +519,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{ {
[start setDateTime: o]; [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"])) if ((o = [theValues objectForKey: @"EndTime"]))
@ -491,6 +583,177 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
RELEASE(rule); RELEASE(rule);
[rule takeActiveSyncValues: o inContext: context]; [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 // Organizer - we don't touch the value unless we're the organizer
@ -498,7 +761,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
([self userIsOrganizer: [context activeUser]] || [[context activeUser] hasEmail: o])) ([self userIsOrganizer: [context activeUser]] || [[context activeUser] hasEmail: o]))
{ {
iCalPerson *person; iCalPerson *person;
person = [iCalPerson elementWithTag: @"organizer"]; person = [iCalPerson elementWithTag: @"organizer"];
[person setEmail: o]; [person setEmail: o];
[person setCn: [theValues objectForKey: @"Organizer_Name"]]; [person setCn: [theValues objectForKey: @"Organizer_Name"]];

View File

@ -140,7 +140,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Simple reccurrence rule of type "Monthly" // Simple reccurrence rule of type "Monthly"
type = 2; type = 2;
[s appendFormat: @"<Recurrence_DayOfMonth xmlns=\"Calendar:\">%d</Recurrence_DayOfMonth>", [s appendFormat: @"<Recurrence_DayOfMonth xmlns=\"Calendar:\">%d</Recurrence_DayOfMonth>",
[[[self parent] startDate] dayOfMonth]]; (int)[[[self parent] startDate] dayOfMonth]];
} }
} }
else if ([self frequency] == iCalRecurrenceFrequenceYearly) else if ([self frequency] == iCalRecurrenceFrequenceYearly)

View File

@ -140,7 +140,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{ {
[s appendString: @"<Body xmlns=\"AirSyncBase:\">"]; [s appendString: @"<Body xmlns=\"AirSyncBase:\">"];
[s appendFormat: @"<Type>%d</Type>", 1]; [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 appendFormat: @"<Data>%@</Data>", o];
[s appendString: @"</Body>"]; [s appendString: @"</Body>"];
} }

13506
ChangeLog

File diff suppressed because it is too large Load Diff

View File

@ -170,7 +170,7 @@ supported by SOGo:
* Red Hat Enterprise Linux (RHEL) Server 5, 6 and 7 * Red Hat Enterprise Linux (RHEL) Server 5, 6 and 7
* Community ENTerprise Operating System (CentOS) 5, 6 and 7 * Community ENTerprise Operating System (CentOS) 5, 6 and 7
* Debian GNU/Linux 6.0 (Squeeze) to 8.0 (Jessie) * 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 Make sure the required components are started automatically at boot time
and that they are running before proceeding with the SOGo configuration. and that they are running before proceeding with the SOGo configuration.

View File

@ -233,6 +233,7 @@ Installation
This section will guide you through the installation of the native This section will guide you through the installation of the native
Microsoft Outlook compatibility layer SOGo offers. Microsoft Outlook compatibility layer SOGo offers.
////
Red Hat Enterprise Linux v6 x86_64 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 On Ubuntu 12.04, the Samba init scripts need to be modified to
disable the upstart check. For more details, refer to: disable the upstart check. For more details, refer to:
https://wiki.samba.org/index.php/Samba4/InitScript https://wiki.samba.org/index.php/Samba4/InitScript
////
Debian 8 (Jessie) and Ubuntu 14.04 (Trusty Tahr) 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 \ samba-tool domain provision --realm=example.com \
--domain=OPENCHANGE \ --domain=EXAMPLE \
--adminpass='%1OpenChange' \ --adminpass='%1OpenChange' \
--server-role='domain controller' --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 You might consider changing the realm and domain used, to suit your
enviroment. environment.
You might also have to You might also have to
remove `/etc/samba/smb.conf` prior running this command. 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 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: 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 mapiproxy:openchangedb = mysql://openchange-user:openchange$123@localhost/openchange
---- ----
////
On RHEL, make sure SELinux is disabled: On RHEL, make sure SELinux is disabled:
setenforce 0 setenforce 0
////
Next, you can start Samba using the usual command: 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 While HTTPS is not required to access this service, it is strongly
recommended. recommended.
////
On RHEL-based distributions, the apache configuration required by these On RHEL-based distributions, the apache configuration required by these
services can be found in `/etc/httpd/conf.d/ocsmanager.conf` and services can be found in `/etc/httpd/conf.d/ocsmanager.conf` and
`/etc/httpd/conf.d/rpcproxy.conf`. `/etc/httpd/conf.d/rpcproxy.conf`.
////
For Debian-based distributions, these files can be found For Debian-based distributions, these files can be found
in `/etc/apache2/conf.d/` or `/etc/apache2/conf-available`. 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 ocsmanager
a2enconf rpcproxy a2enconf rpcproxy
////
On RHEL-based distributions, make sure the `LoadModule` directive is On RHEL-based distributions, make sure the `LoadModule` directive is
uncommented in `/etc/httpd/conf.d/wsgi.conf`. uncommented in `/etc/httpd/conf.d/wsgi.conf`.
////
The _reqtimeout_ apache module is known to cause problems when using the The _reqtimeout_ apache module is known to cause problems when using the
default configuration shipped with Debian-based systems. On such 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 a2dismod reqtimeout
You should now restart the Apache service and make sure it will start on 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 chkconfig httpd on && /etc/init.d/httpd restart
////
On Debian-based distributions, do: On Debian-based distributions, do:
update-rc.d apache2 defaults && /etc/init.d/apache2 restart 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 remove any data associated with the user from the SOGo server and
recreate a Microsoft Outlook profile. recreate a Microsoft Outlook profile.
To remove any data associated to a user, use To remove any data associated to a user, use
the `openchange_user_cleanup` script distributed with SOGo. The script the `openchange_user_cleanup` script distributed with OpenChange. The script
can be found in `/usr/share/doc/sogo/` (`/usr/share/sogo-VERSION/` on can be found in `/usr/share/openchange/`.
RHEL).
To reset a user, run the script as root: 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 * The "Out of Office Assistant" will not currently work. This feature
has not been implemented. has not been implemented.
* Creating folders below INBOX (when not normally permitted by the IMAP * Creating folders below INBOX (when not normally permitted by the IMAP

View File

@ -267,7 +267,7 @@
NSObject <DOMNodeList> *list; NSObject <DOMNodeList> *list;
NSObject <DOMNode> *valueNode; NSObject <DOMNode> *valueNode;
NSArray *elements; NSArray *elements;
NSString *property, *match; NSString *property=nil, *match=nil;
list = [searchElement getElementsByTagName: @"prop"]; list = [searchElement getElementsByTagName: @"prop"];
if ([list length]) if ([list length])

88
NEWS
View File

@ -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 Bug fixes
- fixed display of whitelisted attendees in Preferences window on Firefox (#3285) - fixed display of whitelisted attendees in Preferences window on Firefox (#3285)
- Non-latin subfolder names are displayed correctly on Outlook (Zentyal) - non-latin subfolder names are displayed correctly on Outlook (Zentyal)
- Fixed several sync issues on environments with multiple users (Zentyal) - fixed several sync issues on environments with multiple users (Zentyal)
- Folders from other users will no longer appear on your Outlook (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) - use right auth in multidomain environments in contacts and calendar from Outlook (Zentyal)
- Session fix when SOGoEnableDomainBasedUID is enabled but logins are domain-less - 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) 2.3.1 (2015-07-23)
------------------ ------------------
@ -20,6 +95,7 @@ Enhancements
- added create-folder subcommand to sogo-tool to create contact and calendar folders - added create-folder subcommand to sogo-tool to create contact and calendar folders
- group mail addresses can be used as recipient in Outlook - group mail addresses can be used as recipient in Outlook
- added 'ActiveSync' module constraints - added 'ActiveSync' module constraints
- updated CKEditor to version 4.5.1
- added Slovenian translation - thanks to Jens Riecken - added Slovenian translation - thanks to Jens Riecken
- added Chinese (Taiwan) translation - added Chinese (Taiwan) translation

View File

@ -46,6 +46,9 @@
forURL: (NSURL *) server forURL: (NSURL *) server
forceRenew: (BOOL) renew; forceRenew: (BOOL) renew;
- (NSString *) passwordInContext: (WOContext *) context;
@end @end
#endif /* MAPISTOREAUTHENTICATOR_H */ #endif /* MAPISTOREAUTHENTICATOR_H */

View File

@ -72,4 +72,8 @@
return imapPassword; return imapPassword;
} }
- (NSString *) passwordInContext: (WOContext *) context
{
return password;
}
@end @end

View File

@ -671,7 +671,7 @@ static Class NSArrayK, MAPIStoreAppointmentWrapperK;
return newAttachment; return newAttachment;
} }
- (int) setReadFlag: (uint8_t) flag - (enum mapistore_error) setReadFlag: (uint8_t) flag
{ {
return MAPISTORE_SUCCESS; return MAPISTORE_SUCCESS;
} }

View File

@ -22,6 +22,7 @@
#import <Foundation/NSArray.h> #import <Foundation/NSArray.h>
#import <Foundation/NSCalendarDate.h> #import <Foundation/NSCalendarDate.h>
#import <Foundation/NSData.h>
#import <Foundation/NSDictionary.h> #import <Foundation/NSDictionary.h>
#import <Foundation/NSString.h> #import <Foundation/NSString.h>
#import <Foundation/NSValue.h> #import <Foundation/NSValue.h>
@ -34,6 +35,7 @@
#import "MAPIStoreDBFolder.h" #import "MAPIStoreDBFolder.h"
#import "MAPIStoreDBMessage.h" #import "MAPIStoreDBMessage.h"
#import "MAPIStoreTypes.h" #import "MAPIStoreTypes.h"
#import "NSData+MAPIStore.h"
#import "NSObject+MAPIStore.h" #import "NSObject+MAPIStore.h"
#import "NSString+MAPIStore.h" #import "NSString+MAPIStore.h"
@ -43,16 +45,28 @@
@implementation MAPIStoreDBMessage @implementation MAPIStoreDBMessage
+ (int) getAvailableProperties: (struct SPropTagArray **) propertiesP + (enum mapistore_error) getAvailableProperties: (struct SPropTagArray **) propertiesP
inMemCtx: (TALLOC_CTX *) memCtx inMemCtx: (TALLOC_CTX *) memCtx
{ {
struct SPropTagArray *properties; struct SPropTagArray *properties;
NSUInteger count; NSUInteger count;
enum MAPITAGS faiProperties[] = { 0x68350102, 0x683c0102, 0x683e0102,
0x683f0102, 0x68410003, 0x68420102, enum MAPITAGS faiProperties[] = {
0x68450102, 0x68460003, 0x68330048, /* PR_VIEW_CLSID */
// PR_VD_NAME_W, PR_VD_FLAGS, PR_VD_VERSION, PR_VIEW_CLSID 0x68350102, /* PR_VIEW_STATE */
0x7006001F, 0x70030003, 0x70070003, 0x68330048 }; 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); size_t faiSize = sizeof(faiProperties) / sizeof(enum MAPITAGS);
properties = talloc_zero (memCtx, struct SPropTagArray); properties = talloc_zero (memCtx, struct SPropTagArray);
@ -74,6 +88,13 @@
return MAPISTORE_SUCCESS; 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 - (id) initWithSOGoObject: (id) newSOGoObject
inContainer: (MAPIStoreObject *) newContainer inContainer: (MAPIStoreObject *) newContainer
{ {
@ -104,6 +125,105 @@
return objectVersion; 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, &currentChangeKey->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? // FIXME: how this can happen?
// //
@ -166,6 +286,9 @@
[properties setObject: [NSNumber numberWithUnsignedLongLong: newVersion] [properties setObject: [NSNumber numberWithUnsignedLongLong: newVersion]
forKey: @"version"]; forKey: @"version"];
/* Update PredecessorChangeList accordingly */
[self _updatePredecessorChangeList];
[self logWithFormat: @"%d props in dict", [properties count]]; [self logWithFormat: @"%d props in dict", [properties count]];
[sogoObject save]; [sogoObject save];
@ -209,4 +332,36 @@
return [sogoObject lastModified]; 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 @end

View File

@ -121,6 +121,7 @@
fromFolder: (MAPIStoreFolder *) sourceFolder fromFolder: (MAPIStoreFolder *) sourceFolder
withMIDs: (uint64_t *) targetMids withMIDs: (uint64_t *) targetMids
andChangeKeys: (struct Binary_r **) targetChangeKeys andChangeKeys: (struct Binary_r **) targetChangeKeys
andPredecessorChangeLists: (struct Binary_r **) targetPredecessorChangeLists
wantCopy: (uint8_t) want_copy wantCopy: (uint8_t) want_copy
inMemCtx: (TALLOC_CTX *) memCtx; inMemCtx: (TALLOC_CTX *) memCtx;

View File

@ -642,6 +642,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
fromFolder: (MAPIStoreFolder *) sourceFolder fromFolder: (MAPIStoreFolder *) sourceFolder
withMID: (uint64_t) targetMid withMID: (uint64_t) targetMid
andChangeKey: (struct Binary_r *) targetChangeKey andChangeKey: (struct Binary_r *) targetChangeKey
andPredecessorChangeList: (struct Binary_r *) targetPredecessorChangeList
wantCopy: (uint8_t) wantCopy wantCopy: (uint8_t) wantCopy
inMemCtx: (TALLOC_CTX *) memCtx inMemCtx: (TALLOC_CTX *) memCtx
{ {
@ -669,15 +670,18 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
[sourceMsg copyToMessage: destMsg inMemCtx: memCtx]; [sourceMsg copyToMessage: destMsg inMemCtx: memCtx];
if (targetChangeKey) if (targetPredecessorChangeList)
{ {
property.ulPropTag = PidTagChangeKey; property.ulPropTag = PidTagPredecessorChangeList;
property.value.bin = *targetChangeKey; property.value.bin = *targetPredecessorChangeList;
aRow.cValues = 1; aRow.cValues = 1;
aRow.lpProps = &property; aRow.lpProps = &property;
rc = [destMsg addPropertiesFromRow: &aRow]; rc = [destMsg addPropertiesFromRow: &aRow];
if (rc != MAPISTORE_SUCCESS) if (rc != MAPISTORE_SUCCESS)
goto end; {
[self errorWithFormat: @"Cannot add PredecessorChangeList on move"];
goto end;
}
} }
[destMsg save: memCtx]; [destMsg save: memCtx];
if (!wantCopy) if (!wantCopy)
@ -696,6 +700,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
fromFolder: (MAPIStoreFolder *) sourceFolder fromFolder: (MAPIStoreFolder *) sourceFolder
withMIDs: (uint64_t *) targetMids withMIDs: (uint64_t *) targetMids
andChangeKeys: (struct Binary_r **) targetChangeKeys andChangeKeys: (struct Binary_r **) targetChangeKeys
andPredecessorChangeLists: (struct Binary_r **) targetPredecessorChangeLists
wantCopy: (uint8_t) wantCopy wantCopy: (uint8_t) wantCopy
inMemCtx: (TALLOC_CTX *) memCtx inMemCtx: (TALLOC_CTX *) memCtx
{ {
@ -705,7 +710,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
NSString *oldMessageURL; NSString *oldMessageURL;
MAPIStoreMapping *mapping; MAPIStoreMapping *mapping;
SOGoUser *ownerUser; SOGoUser *ownerUser;
struct Binary_r *targetChangeKey; struct Binary_r *targetChangeKey, *targetPredecessorChangeList;
//TALLOC_CTX *memCtx; //TALLOC_CTX *memCtx;
//memCtx = talloc_zero (NULL, TALLOC_CTX); //memCtx = talloc_zero (NULL, TALLOC_CTX);
@ -726,14 +731,21 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
if (oldMessageURL) if (oldMessageURL)
{ {
[oldMessageURLs addObject: oldMessageURL]; [oldMessageURLs addObject: oldMessageURL];
if (targetChangeKeys) if (targetChangeKeys && targetPredecessorChangeList)
targetChangeKey = targetChangeKeys[count]; {
targetChangeKey = targetChangeKeys[count];
targetPredecessorChangeList = targetPredecessorChangeLists[count];
}
else else
targetChangeKey = NULL; {
targetChangeKey = NULL;
targetPredecessorChangeList = NULL;
}
rc = [self _moveCopyMessageWithMID: srcMids[count] rc = [self _moveCopyMessageWithMID: srcMids[count]
fromFolder: sourceFolder fromFolder: sourceFolder
withMID: targetMids[count] withMID: targetMids[count]
andChangeKey: targetChangeKey andChangeKey: targetChangeKey
andPredecessorChangeList: targetPredecessorChangeList
wantCopy: wantCopy wantCopy: wantCopy
inMemCtx: memCtx]; inMemCtx: memCtx];
} }

View File

@ -41,8 +41,10 @@
/* synchronisation */ /* synchronisation */
- (BOOL) synchroniseCache; - (BOOL) synchroniseCache;
- (BOOL) synchroniseCacheFor: (NSString *) nameInContainer;
- (void) updateVersionsForMessageWithKey: (NSString *) messageKey - (void) updateVersionsForMessageWithKey: (NSString *) messageKey
withChangeKey: (NSData *) newChangeKey; withChangeKey: (NSData *) oldChangeKey
andPredecessorChangeList: (NSData *) pcl;
- (NSNumber *) lastModifiedFromMessageChangeNumber: (NSString *) changeNumber; - (NSNumber *) lastModifiedFromMessageChangeNumber: (NSString *) changeNumber;
- (NSString *) changeNumberForMessageWithKey: (NSString *) messageKey; - (NSString *) changeNumberForMessageWithKey: (NSString *) messageKey;
- (NSData *) changeKeyForMessageWithKey: (NSString *) messageKey; - (NSData *) changeKeyForMessageWithKey: (NSString *) messageKey;

View File

@ -75,6 +75,7 @@ static Class NSNumberK;
[SOGoMAPIDBMessage objectWithName: @"versions.plist" [SOGoMAPIDBMessage objectWithName: @"versions.plist"
inContainer: dbFolder]); inContainer: dbFolder]);
[versionsMessage setObjectType: MAPIInternalCacheObject]; [versionsMessage setObjectType: MAPIInternalCacheObject];
[versionsMessage reloadIfNeeded];
} }
- (void) dealloc - (void) dealloc
@ -261,7 +262,6 @@ static Class NSNumberK;
*/ */
- (void) _setChangeKey: (NSData *) changeKey - (void) _setChangeKey: (NSData *) changeKey
forMessageEntry: (NSMutableDictionary *) messageEntry forMessageEntry: (NSMutableDictionary *) messageEntry
inChangeListOnly: (BOOL) inChangeListOnly
{ {
struct XID *xid; struct XID *xid;
NSString *guid; NSString *guid;
@ -270,19 +270,15 @@ static Class NSNumberK;
NSMutableDictionary *changeList; NSMutableDictionary *changeList;
xid = [changeKey asXIDInMemCtx: NULL]; xid = [changeKey asXIDInMemCtx: NULL];
guid = [NSString stringWithGUID: &xid->GUID]; guid = [NSString stringWithGUID: &xid->NameSpaceGuid];
globCnt = [NSData dataWithBytes: xid->Data length: xid->Size]; globCnt = [NSData dataWithBytes: xid->LocalId.data length: xid->LocalId.length];
talloc_free (xid); talloc_free (xid);
if (!inChangeListOnly) /* 1. set change key association */
{ changeKeyDict = [NSDictionary dictionaryWithObjectsAndKeys: guid, @"GUID",
/* 1. set change key association */ globCnt, @"LocalId",
changeKeyDict = [NSDictionary dictionaryWithObjectsAndKeys: nil];
guid, @"GUID", [messageEntry setObject: changeKeyDict forKey: @"ChangeKey"];
globCnt, @"LocalId",
nil];
[messageEntry setObject: changeKeyDict forKey: @"ChangeKey"];
}
/* 2. append/update predecessor change list */ /* 2. append/update predecessor change list */
changeList = [messageEntry objectForKey: @"PredecessorChangeList"]; changeList = [messageEntry objectForKey: @"PredecessorChangeList"];
@ -296,6 +292,77 @@ static Class NSNumberK;
[changeList setObject: globCnt forKey: guid]; [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 - (EOQualifier *) componentQualifier
{ {
if (!componentQualifier) if (!componentQualifier)
@ -465,8 +532,7 @@ static Class NSNumberK;
// A GLOBCNT structure is a 6-byte global namespace counter, // A GLOBCNT structure is a 6-byte global namespace counter,
// we strip the first 2 bytes. The first two bytes is the ReplicaId // we strip the first 2 bytes. The first two bytes is the ReplicaId
changeKey = [self getReplicaKeyFromGlobCnt: newChangeNum >> 16]; changeKey = [self getReplicaKeyFromGlobCnt: newChangeNum >> 16];
[self _setChangeKey: changeKey forMessageEntry: messageEntry [self _setChangeKey: changeKey forMessageEntry: messageEntry];
inChangeListOnly: NO];
} }
now = [NSCalendarDate date]; now = [NSCalendarDate date];
@ -482,13 +548,123 @@ static Class NSNumberK;
return rc; 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 - (void) updateVersionsForMessageWithKey: (NSString *) messageKey
withChangeKey: (NSData *) newChangeKey withChangeKey: (NSData *) oldChangeKey
andPredecessorChangeList: (NSData *) pcl
{ {
NSMutableDictionary *messages, *messageEntry; NSMutableDictionary *messages, *messageEntry;
[self synchroniseCache]; [self synchroniseCache];
if (newChangeKey) if (oldChangeKey || pcl)
{ {
messages = [[versionsMessage properties] objectForKey: @"Messages"]; messages = [[versionsMessage properties] objectForKey: @"Messages"];
messageEntry = [messages objectForKey: messageKey]; messageEntry = [messages objectForKey: messageKey];
@ -496,8 +672,8 @@ static Class NSNumberK;
[NSException raise: @"MAPIStoreIOException" [NSException raise: @"MAPIStoreIOException"
format: @"no version record found for message '%@'", format: @"no version record found for message '%@'",
messageKey]; messageKey];
[self _setChangeKey: newChangeKey forMessageEntry: messageEntry [self _updatePredecessorChangeList: pcl forMessageEntry: messageEntry
inChangeListOnly: YES]; withOldChangeKey: oldChangeKey];
[versionsMessage save]; [versionsMessage save];
} }
} }

View File

@ -180,7 +180,8 @@
{ {
uint64_t version = ULLONG_MAX; uint64_t version = ULLONG_MAX;
NSString *changeNumber; NSString *changeNumber;
BOOL synced;
if (!isNew) if (!isNew)
{ {
changeNumber = [(MAPIStoreGCSFolder *) container changeNumber = [(MAPIStoreGCSFolder *) container
@ -189,16 +190,28 @@
{ {
[self warnWithFormat: @"attempting to get change number" [self warnWithFormat: @"attempting to get change number"
@" by synchronising folder..."]; @" by synchronising folder..."];
[(MAPIStoreGCSFolder *) container synchroniseCache]; synced = [(MAPIStoreGCSFolder *) container synchroniseCache];
changeNumber = [(MAPIStoreGCSFolder *) container if (synced)
changeNumberForMessageWithKey: [self nameInContainer]];
if (changeNumber)
[self logWithFormat: @"got one"];
else
{ {
[self errorWithFormat: @"still nothing. We crash!"]; changeNumber = [(MAPIStoreGCSFolder *) container
abort(); changeNumberForMessageWithKey: [self nameInContainer]];
}
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; version = [changeNumber unsignedLongLongValue] >> 16;
@ -209,13 +222,16 @@
- (void) updateVersions - (void) updateVersions
{ {
NSData *newChangeKey; /* Update ChangeKey and PredecessorChangeList on message's save */
NSData *newChangeKey, *predecessorChangeList;
newChangeKey = [properties objectForKey: MAPIPropertyKey (PR_CHANGE_KEY)]; newChangeKey = [properties objectForKey: MAPIPropertyKey (PR_CHANGE_KEY)];
predecessorChangeList = [properties objectForKey: MAPIPropertyKey (PR_PREDECESSOR_CHANGE_LIST)];
[(MAPIStoreGCSFolder *) container [(MAPIStoreGCSFolder *) container
updateVersionsForMessageWithKey: [self nameInContainer] updateVersionsForMessageWithKey: [self nameInContainer]
withChangeKey: newChangeKey]; withChangeKey: newChangeKey
andPredecessorChangeList: predecessorChangeList];
} }
@end @end

View File

@ -30,6 +30,7 @@
#import <SOGo/NSArray+Utilities.h> #import <SOGo/NSArray+Utilities.h>
#import <Mailer/SOGoMailBodyPart.h> #import <Mailer/SOGoMailBodyPart.h>
#import <Mailer/SOGoMailObject.h> #import <Mailer/SOGoMailObject.h>
#import <Mailer/NSDictionary+Mail.h>
#import "MAPIStoreTypes.h" #import "MAPIStoreTypes.h"
#import "MAPIStoreMailMessage.h" #import "MAPIStoreMailMessage.h"
@ -108,7 +109,7 @@
static char recordBytes[] = {0xd9, 0xd8, 0x11, 0xa3, 0xe2, 0x90, 0x18, 0x41, static char recordBytes[] = {0xd9, 0xd8, 0x11, 0xa3, 0xe2, 0x90, 0x18, 0x41,
0x9e, 0x04, 0x58, 0x46, 0x9d, 0x6d, 0x1b, 0x9e, 0x04, 0x58, 0x46, 0x9d, 0x6d, 0x1b,
0x68}; 0x68};
*data = [[NSData dataWithBytes: recordBytes length: 16] *data = [[NSData dataWithBytes: recordBytes length: 16]
asBinaryInMemCtx: memCtx]; asBinaryInMemCtx: memCtx];
@ -117,19 +118,7 @@
- (NSString *) _fileName - (NSString *) _fileName
{ {
NSString *fileName; return [bodyInfo filename];
NSDictionary *parameters;
fileName = [[bodyInfo objectForKey: @"parameterList"]
objectForKey: @"name"];
if (!fileName)
{
parameters = [[bodyInfo objectForKey: @"disposition"]
objectForKey: @"parameterList"];
fileName = [parameters objectForKey: @"filename"];
}
return fileName;
} }
- (int) getPidTagAttachLongFilename: (void **) data - (int) getPidTagAttachLongFilename: (void **) data
@ -178,7 +167,7 @@
- (int) getPidTagAttachContentId: (void **) data - (int) getPidTagAttachContentId: (void **) data
inMemCtx: (TALLOC_CTX *) memCtx inMemCtx: (TALLOC_CTX *) memCtx
{ {
*data = [[bodyInfo objectForKey: @"bodyId"] *data = [[bodyInfo objectForKey: @"bodyId"]
asUnicodeInMemCtx: memCtx]; asUnicodeInMemCtx: memCtx];

View File

@ -160,23 +160,25 @@ MakeDisplayFolderName (NSString *folderName)
for (count = 0; count < max; count++) for (count = 0; count < max; count++)
{ {
context = talloc_zero (memCtx, struct mapistore_contexts_list); context = talloc_zero (memCtx, struct mapistore_contexts_list);
// secondaryFolders has the names (1) Imap4Encoded and (2) asCSSIdentifier // secondaryFolders has the names (1) Imap4Encoded ,(2) asCSSIdentifier and (3) "folder"-prefixed
// e.g.: Probl&AOg-mes_SP_de_SP_synchronisation // e.g.: folderProbl&AOg-mes_SP_de_SP_synchronisation
currentName = [secondaryFolders objectAtIndex: count]; currentName = [secondaryFolders objectAtIndex: count];
// To get the real name we have to revert that (applying the decode functions) // To get the real name we have to revert that (applying the decode functions
// in reverse order // in reverse order)
// e.g.: Problèmes de synchronisation // e.g.: Problèmes de synchronisation
realName = [[currentName fromCSSIdentifier] realName = [[[currentName substringFromIndex: 6]
stringByDecodingImap4FolderName]; fromCSSIdentifier]
stringByDecodingImap4FolderName];
// And finally to represent that as URI we have to (1) asCSSIdentifier, // And finally to represent that as URI we have to (1) asCSSIdentifier,
// (2) Imap4Encode and (3) AddPercentEscapes // (2) Imap4Encode (3) AddPercentEscapes and (4) add the "folder" prefix
// e.g.: Probl&AOg-mes_SP_de_SP_synchronisation // e.g.: folderProbl&AOg-mes_SP_de_SP_synchronisation
// In the example there are no percent escapes added because is already ok // In the example there are no percent escapes added because is already ok
stringData = [[[realName asCSSIdentifier] stringData = [[[realName asCSSIdentifier]
stringByEncodingImap4FolderName] stringByEncodingImap4FolderName]
stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]; stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding];
stringData = [NSString stringWithFormat: @"folder%@", stringData];
context->url = [[NSString stringWithFormat: @"%@%@", urlBase, stringData] asUnicodeInMemCtx: context]; 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->main_folder = false;
context->role = MAPISTORE_MAIL_ROLE; context->role = MAPISTORE_MAIL_ROLE;
context->tag = "tag"; context->tag = "tag";
@ -200,7 +202,7 @@ MakeDisplayFolderName (NSString *folderName)
andTDBIndexing: NULL]; andTDBIndexing: NULL];
accountFolder = [[userContext rootFolders] objectForKey: @"mail"]; accountFolder = [[userContext rootFolders] objectForKey: @"mail"];
folderName = [NSString stringWithFormat: @"folder%@", folderName = [NSString stringWithFormat: @"folder%@",
[newFolderName asCSSIdentifier]]; [[newFolderName stringByEncodingImap4FolderName] asCSSIdentifier]];
newFolder = [SOGoMailFolder objectWithName: folderName newFolder = [SOGoMailFolder objectWithName: folderName
inContainer: accountFolder]; inContainer: accountFolder];
if ([newFolder create]) if ([newFolder create])
@ -209,7 +211,7 @@ MakeDisplayFolderName (NSString *folderName)
withString: @"%40"], withString: @"%40"],
[userName stringByReplacingOccurrencesOfString: @"@" [userName stringByReplacingOccurrencesOfString: @"@"
withString: @"%40"], withString: @"%40"],
[[folderName stringByEncodingImap4FolderName] stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]]; [folderName stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]];
else else
mapistoreURI = nil; mapistoreURI = nil;

View File

@ -52,6 +52,8 @@
- (NSString *) changeNumberForMessageUID: (NSString *) messageUid; - (NSString *) changeNumberForMessageUID: (NSString *) messageUid;
- (void) setChangeKey: (NSData *) changeKey - (void) setChangeKey: (NSData *) changeKey
forMessageWithKey: (NSString *) messageKey; forMessageWithKey: (NSString *) messageKey;
- (BOOL) updatePredecessorChangeListWith: (NSData *) changeKey
forMessageWithKey: (NSString *) messageKey;
- (NSData *) changeKeyForMessageWithKey: (NSString *) messageKey; - (NSData *) changeKeyForMessageWithKey: (NSString *) messageKey;
- (NSData *) predecessorChangeListForMessageWithKey: (NSString *) messageKey; - (NSData *) predecessorChangeListForMessageWithKey: (NSString *) messageKey;

View File

@ -68,6 +68,8 @@
static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK; static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK;
#include <gen_ndr/exchange.h>
#undef DEBUG #undef DEBUG
#include <util/attr.h> #include <util/attr.h>
#include <libmapi/libmapi.h> #include <libmapi/libmapi.h>
@ -109,6 +111,7 @@ static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK;
[SOGoMAPIDBMessage objectWithName: @"versions.plist" [SOGoMAPIDBMessage objectWithName: @"versions.plist"
inContainer: dbFolder]); inContainer: dbFolder]);
[versionsMessage setObjectType: MAPIInternalCacheObject]; [versionsMessage setObjectType: MAPIInternalCacheObject];
[versionsMessage reloadIfNeeded];
} }
- (BOOL) ensureFolderExists - (BOOL) ensureFolderExists
@ -516,6 +519,44 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
return [modseq1 compare: modseq2]; 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 - (void) _setChangeKey: (NSData *) changeKey
forMessageEntry: (NSMutableDictionary *) messageEntry forMessageEntry: (NSMutableDictionary *) messageEntry
{ {
@ -526,8 +567,8 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
NSMutableDictionary *changeList; NSMutableDictionary *changeList;
xid = [changeKey asXIDInMemCtx: NULL]; xid = [changeKey asXIDInMemCtx: NULL];
guid = [NSString stringWithGUID: &xid->GUID]; guid = [NSString stringWithGUID: &xid->NameSpaceGuid];
globCnt = [NSData dataWithBytes: xid->Data length: xid->Size]; globCnt = [NSData dataWithBytes: xid->LocalId.data length: xid->LocalId.length];
talloc_free (xid); talloc_free (xid);
/* 1. set change key association */ /* 1. set change key association */
@ -553,8 +594,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
{ {
BOOL rc = YES; BOOL rc = YES;
uint64_t newChangeNum; uint64_t newChangeNum;
NSNumber *ti, *modseq, *initialLastModseq, *lastModseq, NSNumber *ti, *modseq, *lastModseq, *nextModseq;
*nextModseq;
NSString *changeNumber, *uid, *messageKey; NSString *changeNumber, *uid, *messageKey;
uint64_t lastModseqNbr; uint64_t lastModseqNbr;
EOQualifier *searchQualifier; EOQualifier *searchQualifier;
@ -593,7 +633,6 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
} }
lastModseq = [currentProperties objectForKey: @"SyncLastModseq"]; lastModseq = [currentProperties objectForKey: @"SyncLastModseq"];
initialLastModseq = lastModseq;
if (lastModseq) if (lastModseq)
{ {
lastModseqNbr = [lastModseq unsignedLongLongValue]; lastModseqNbr = [lastModseq unsignedLongLongValue];
@ -677,40 +716,37 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
} }
/* 2. we synchronise expunged UIDs */ /* 2. we synchronise expunged UIDs */
if (initialLastModseq) fetchResults = [(SOGoMailFolder *) sogoObject
fetchUIDsOfVanishedItems: lastModseqNbr];
max = [fetchResults count];
changeNumber = nil;
for (count = 0; count < max; count++)
{ {
fetchResults = [(SOGoMailFolder *) sogoObject uid = [[fetchResults objectAtIndex: count] stringValue];
fetchUIDsOfVanishedItems: lastModseqNbr]; if ([messages objectForKey: uid])
max = [fetchResults count];
changeNumber = nil;
for (count = 0; count < max; count++)
{ {
uid = [[fetchResults objectAtIndex: count] stringValue]; if (!changeNumber)
if ([messages objectForKey: uid])
{ {
if (!changeNumber) newChangeNum = [[self context] getNewChangeNumber];
{ changeNumber = [NSString stringWithUnsignedLongLong: newChangeNum];
newChangeNum = [[self context] getNewChangeNumber];
changeNumber = [NSString stringWithUnsignedLongLong: newChangeNum];
}
[messages removeObjectForKey: uid];
[self logWithFormat: @"Removed message entry for UID %@", uid];
}
else
{
[self logWithFormat:@"Message entry not found for UID %@", uid];
} }
[messages removeObjectForKey: uid];
[self logWithFormat: @"Removed message entry for UID %@", uid];
} }
if (changeNumber) else
{ {
[currentProperties setObject: changeNumber [self logWithFormat:@"Message entry not found for UID %@", uid];
forKey: @"SyncLastDeleteChangeNumber"];
[mapping setObject: lastModseq forKey: changeNumber];
foundChange = YES;
} }
} }
if (changeNumber)
{
[currentProperties setObject: changeNumber
forKey: @"SyncLastDeleteChangeNumber"];
[mapping setObject: lastModseq forKey: changeNumber];
foundChange = YES;
}
if (foundChange) if (foundChange)
{ {
@ -924,8 +960,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
return changeNumber; return changeNumber;
} }
- (void) setChangeKey: (NSData *) changeKey - (NSMutableDictionary *) _messageEntryFromMessageKey: (NSString *) messageKey
forMessageWithKey: (NSString *) messageKey
{ {
NSMutableDictionary *messages, *messageEntry; NSMutableDictionary *messages, *messageEntry;
NSString *messageUid; NSString *messageUid;
@ -936,7 +971,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
messageEntry = [messages objectForKey: messageUid]; messageEntry = [messages objectForKey: messageUid];
if (!messageEntry) 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]; @"this message %@", messageKey];
synced = [self synchroniseCacheForUID: messageUid]; synced = [self synchroniseCacheForUID: messageUid];
if (synced) if (synced)
@ -947,11 +982,57 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
abort (); abort ();
} }
} }
[self _setChangeKey: changeKey forMessageEntry: messageEntry];
return messageEntry;
}
- (void) setChangeKey: (NSData *) changeKey
forMessageWithKey: (NSString *) messageKey
{
[self _setChangeKey: changeKey
forMessageEntry: [self _messageEntryFromMessageKey: messageKey]];
[versionsMessage save]; [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 - (NSData *) changeKeyForMessageWithKey: (NSString *) messageKey
{ {
NSDictionary *messages, *changeKeyDict; NSDictionary *messages, *changeKeyDict;
@ -1217,6 +1298,7 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP)
fromFolder: (MAPIStoreFolder *) sourceFolder fromFolder: (MAPIStoreFolder *) sourceFolder
withMIDs: (uint64_t *) targetMids withMIDs: (uint64_t *) targetMids
andChangeKeys: (struct Binary_r **) targetChangeKeys andChangeKeys: (struct Binary_r **) targetChangeKeys
andPredecessorChangeLists: (struct Binary_r **) targetPredecessorChangeLists
wantCopy: (uint8_t) wantCopy wantCopy: (uint8_t) wantCopy
inMemCtx: (TALLOC_CTX *) memCtx inMemCtx: (TALLOC_CTX *) memCtx
@ -1231,12 +1313,13 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP)
NSDictionary *result; NSDictionary *result;
NSUInteger count; NSUInteger count;
NSArray *a; NSArray *a;
NSData *changeKey; NSData *changeList;
if (![sourceFolder isKindOfClass: [MAPIStoreMailFolder class]]) if (![sourceFolder isKindOfClass: [MAPIStoreMailFolder class]])
return [super moveCopyMessagesWithMIDs: srcMids andCount: midCount return [super moveCopyMessagesWithMIDs: srcMids andCount: midCount
fromFolder: sourceFolder withMIDs: targetMids fromFolder: sourceFolder withMIDs: targetMids
andChangeKeys: targetChangeKeys andChangeKeys: targetChangeKeys
andPredecessorChangeLists: targetPredecessorChangeLists
wantCopy: wantCopy wantCopy: wantCopy
inMemCtx: memCtx]; inMemCtx: memCtx];
@ -1325,11 +1408,11 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP)
[self synchroniseCache]; [self synchroniseCache];
for (count = 0; count < midCount; count++) for (count = 0; count < midCount; count++)
{ {
changeKey = [NSData dataWithBinary: targetChangeKeys[count]]; changeList = [NSData dataWithBinary: targetPredecessorChangeLists[count]];
messageKey = [NSString stringWithFormat: @"%@.eml", messageKey = [NSString stringWithFormat: @"%@.eml",
[destUIDs objectAtIndex: count]]; [destUIDs objectAtIndex: count]];
[self setChangeKey: changeKey [self _updatePredecessorChangeListWith: changeList
forMessageWithKey: messageKey]; forMessageEntry: [self _messageEntryFromMessageKey: messageKey]];
} }
} }

View File

@ -40,6 +40,7 @@
#import <Mailer/NSData+Mail.h> #import <Mailer/NSData+Mail.h>
#import <Mailer/SOGoMailBodyPart.h> #import <Mailer/SOGoMailBodyPart.h>
#import <Mailer/SOGoMailObject.h> #import <Mailer/SOGoMailObject.h>
#import <Mailer/NSDictionary+Mail.h>
#import "Codepages.h" #import "Codepages.h"
#import "NSData+MAPIStore.h" #import "NSData+MAPIStore.h"
@ -196,7 +197,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
count1 = [keys indexOfObject: data1]; count1 = [keys indexOfObject: data1];
data2 = [entry2 objectForKey: @"mimeType"]; data2 = [entry2 objectForKey: @"mimeType"];
count2 = [keys indexOfObject: data2]; count2 = [keys indexOfObject: data2];
if (count1 == count2) if (count1 == count2)
{ {
data1 = [entry1 objectForKey: @"key"]; data1 = [entry1 objectForKey: @"key"];
@ -529,7 +530,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
else else
stringValue = @""; stringValue = @"";
*data = [stringValue asUnicodeInMemCtx: memCtx]; *data = [stringValue asUnicodeInMemCtx: memCtx];
return MAPISTORE_SUCCESS; return MAPISTORE_SUCCESS;
} }
@ -624,7 +625,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
NSDictionary *coreInfos; NSDictionary *coreInfos;
NSArray *flags; NSArray *flags;
unsigned int v = 0; unsigned int v = 0;
coreInfos = [sogoObject fetchCoreInfos]; coreInfos = [sogoObject fetchCoreInfos];
flags = [coreInfos objectForKey: @"flags"]; flags = [coreInfos objectForKey: @"flags"];
@ -636,7 +637,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
if ([[self attachmentKeys] if ([[self attachmentKeys]
count] > 0) count] > 0)
v |= MSGFLAG_HASATTACH; v |= MSGFLAG_HASATTACH;
*data = MAPILongValue (memCtx, v); *data = MAPILongValue (memCtx, v);
return MAPISTORE_SUCCESS; return MAPISTORE_SUCCESS;
@ -656,7 +657,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
v = 2; v = 2;
else else
v = 0; v = 0;
*data = MAPILongValue (memCtx, v); *data = MAPILongValue (memCtx, v);
return MAPISTORE_SUCCESS; return MAPISTORE_SUCCESS;
@ -668,15 +669,15 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
NSDictionary *coreInfos; NSDictionary *coreInfos;
NSArray *flags; NSArray *flags;
unsigned int v; unsigned int v;
coreInfos = [sogoObject fetchCoreInfos]; coreInfos = [sogoObject fetchCoreInfos];
flags = [coreInfos objectForKey: @"flags"]; flags = [coreInfos objectForKey: @"flags"];
if ([flags containsObject: @"flagged"]) if ([flags containsObject: @"flagged"])
v = 6; v = 6;
else else
v = 0; v = 0;
*data = MAPILongValue (memCtx, v); *data = MAPILongValue (memCtx, v);
return MAPISTORE_SUCCESS; return MAPISTORE_SUCCESS;
@ -755,7 +756,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
if ([ngAddress isKindOfClass: [NGMailAddress class]]) if ([ngAddress isKindOfClass: [NGMailAddress class]])
{ {
cn = [ngAddress displayName]; cn = [ngAddress displayName];
// If we don't have a displayName, we use the email address instead. This // If we don't have a displayName, we use the email address instead. This
// avoid bug #2119 - where Outlook won't display anything in the "From" field, // avoid bug #2119 - where Outlook won't display anything in the "From" field,
// nor in the recipient field if we reply to the email. // nor in the recipient field if we reply to the email.
@ -809,7 +810,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
entryId = MAPIStoreExternalEntryId (cn, email); entryId = MAPIStoreExternalEntryId (cn, email);
*data = [entryId asBinaryInMemCtx: memCtx]; *data = [entryId asBinaryInMemCtx: memCtx];
rc = MAPISTORE_SUCCESS; rc = MAPISTORE_SUCCESS;
} }
else else
@ -966,15 +967,15 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
{ {
uint32_t v; uint32_t v;
NSString *s; NSString *s;
s = [[sogoObject mailHeaders] objectForKey: @"x-priority"]; s = [[sogoObject mailHeaders] objectForKey: @"x-priority"];
v = 0x1; v = 0x1;
if ([s hasPrefix: @"1"]) v = 0x2; if ([s hasPrefix: @"1"]) v = 0x2;
else if ([s hasPrefix: @"2"]) v = 0x2; else if ([s hasPrefix: @"2"]) v = 0x2;
else if ([s hasPrefix: @"4"]) v = 0x0; else if ([s hasPrefix: @"4"]) v = 0x0;
else if ([s hasPrefix: @"5"]) v = 0x0; else if ([s hasPrefix: @"5"]) v = 0x0;
*data = MAPILongValue (memCtx, v); *data = MAPILongValue (memCtx, v);
return MAPISTORE_SUCCESS; return MAPISTORE_SUCCESS;
@ -1163,14 +1164,14 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
if (!headerSetup) if (!headerSetup)
[self _fetchHeaderData]; [self _fetchHeaderData];
if ([headerMimeType isEqualToString: @"text/plain"]) if ([headerMimeType isEqualToString: @"text/plain"])
format = EDITOR_FORMAT_PLAINTEXT; format = EDITOR_FORMAT_PLAINTEXT;
else if ([headerMimeType isEqualToString: @"text/html"]) else if ([headerMimeType isEqualToString: @"text/html"])
format = EDITOR_FORMAT_HTML; format = EDITOR_FORMAT_HTML;
else else
format = 0; /* EDITOR_FORMAT_DONTKNOW */ format = 0; /* EDITOR_FORMAT_DONTKNOW */
*data = MAPILongValue (memCtx, format); *data = MAPILongValue (memCtx, format);
return MAPISTORE_SUCCESS; return MAPISTORE_SUCCESS;
@ -1517,7 +1518,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
p = 0; p = 0;
recipient->data = talloc_array (msgData, void *, msgData->columns->cValues); recipient->data = talloc_array (msgData, void *, msgData->columns->cValues);
memset (recipient->data, 0, msgData->columns->cValues * sizeof (void *)); memset (recipient->data, 0, msgData->columns->cValues * sizeof (void *));
// PR_OBJECT_TYPE = MAPI_MAILUSER (see MAPI_OBJTYPE) // PR_OBJECT_TYPE = MAPI_MAILUSER (see MAPI_OBJTYPE)
recipient->data[p] = MAPILongValue (msgData, MAPI_MAILUSER); recipient->data[p] = MAPILongValue (msgData, MAPI_MAILUSER);
p++; p++;
@ -1525,7 +1526,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
// PR_DISPLAY_TYPE = DT_MAILUSER (see MS-NSPI) // PR_DISPLAY_TYPE = DT_MAILUSER (see MS-NSPI)
recipient->data[p] = MAPILongValue (msgData, 0); recipient->data[p] = MAPILongValue (msgData, 0);
p++; p++;
// PR_7BIT_DISPLAY_NAME_UNICODE // PR_7BIT_DISPLAY_NAME_UNICODE
recipient->data[p] = [cn asUnicodeInMemCtx: msgData]; recipient->data[p] = [cn asUnicodeInMemCtx: msgData];
p++; p++;
@ -1533,7 +1534,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
// PR_SMTP_ADDRESS_UNICODE // PR_SMTP_ADDRESS_UNICODE
recipient->data[p] = [email asUnicodeInMemCtx: msgData]; recipient->data[p] = [email asUnicodeInMemCtx: msgData];
p++; p++;
// PR_SEND_INTERNET_ENCODING = 0x00060000 (plain text, see OXCMAIL) // PR_SEND_INTERNET_ENCODING = 0x00060000 (plain text, see OXCMAIL)
recipient->data[p] = MAPILongValue (msgData, 0x00060000); recipient->data[p] = MAPILongValue (msgData, 0x00060000);
p++; p++;
@ -1566,12 +1567,9 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
withPrefix: (NSString *) keyPrefix withPrefix: (NSString *) keyPrefix
{ {
NSArray *parts; NSArray *parts;
NSDictionary *parameters;
NSUInteger count, max; NSUInteger count, max;
parameters = [[bodyInfo objectForKey: @"disposition"] if ([[bodyInfo filename] length] > 0)
objectForKey: @"parameterList"];
if ([[parameters objectForKey: @"filename"] length] > 0)
{ {
if ([keyPrefix length] == 0) if ([keyPrefix length] == 0)
keyPrefix = @"0"; keyPrefix = @"0";
@ -1645,15 +1643,13 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
return attachment; 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 */ /* TODO: notifications should probably be emitted from here */
if (flag & CLEAR_READ_FLAG) if (flag & CLEAR_READ_FLAG)
[sogoObject removeFlags: imapFlag]; [properties setObject: [NSNumber numberWithBool: NO] forKey: @"read_flag_set"];
else else
[sogoObject addFlags: imapFlag]; [properties setObject: [NSNumber numberWithBool: YES] forKey: @"read_flag_set"];
return MAPISTORE_SUCCESS; return MAPISTORE_SUCCESS;
} }
@ -1694,9 +1690,12 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
return nil; return nil;
} }
- (void) save: (TALLOC_CTX *) memCtx - (void) save: (TALLOC_CTX *) memCtx
{ {
BOOL modified = NO;
BOOL seen, storedSeenFlag;
NSNumber *value; NSNumber *value;
NSString *imapFlag = @"\\Seen";
value = [properties objectForKey: MAPIPropertyKey (PR_FLAG_STATUS)]; value = [properties objectForKey: MAPIPropertyKey (PR_FLAG_STATUS)];
if (value) if (value)
@ -1706,8 +1705,35 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
[sogoObject addFlags: @"\\Flagged"]; [sogoObject addFlags: @"\\Flagged"];
else /* 0: unflagged, 1: follow up complete */ else /* 0: unflagged, 1: follow up complete */
[sogoObject removeFlags: @"\\Flagged"]; [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) if (mailIsSharingObject)
[[self _sharingObject] saveWithMessage: self [[self _sharingObject] saveWithMessage: self
andSOGoObject: sogoObject]; andSOGoObject: sogoObject];

View File

@ -161,15 +161,30 @@ static Class MAPIStoreMailMessageK, NSDataK, NSStringK;
//[self logWithFormat: @"change number from oxcfxics: %.16lx", [value unsignedLongLongValue]]; //[self logWithFormat: @"change number from oxcfxics: %.16lx", [value unsignedLongLongValue]];
//[self logWithFormat: @" modseq: %.16lx", [modseq unsignedLongLongValue]]; //[self logWithFormat: @" modseq: %.16lx", [modseq unsignedLongLongValue]];
if (modseq) if (modseq)
modseq = [NSNumber numberWithUnsignedLongLong: {
[modseq unsignedLongLongValue] + 1]; if (res->relop == RELOP_GT)
modseq = [NSNumber numberWithUnsignedLongLong:
[modseq unsignedLongLongValue] + 1];
}
else else
modseq = [NSNumber numberWithUnsignedLongLong: 0]; modseq = [NSNumber numberWithUnsignedLongLong: 0];
*qualifier = [[EOKeyValueQualifier alloc] initWithKey: @"MODSEQ"
operatorSelector: EOQualifierOperatorGreaterThanOrEqualTo if (res->relop == RELOP_GT || res->relop == RELOP_GE)
value: modseq]; {
[*qualifier autorelease]; *qualifier = [[EOKeyValueQualifier alloc] initWithKey: @"MODSEQ"
rc = MAPIRestrictionStateNeedsEval; 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; break;

View File

@ -34,6 +34,7 @@
#import <NGExtensions/NGBase64Coding.h> #import <NGExtensions/NGBase64Coding.h>
#import <NGExtensions/NGHashMap.h> #import <NGExtensions/NGHashMap.h>
#import <NGExtensions/NSObject+Logs.h> #import <NGExtensions/NSObject+Logs.h>
#import <NGExtensions/NSObject+Values.h>
#import <NGExtensions/NSString+Encoding.h> #import <NGExtensions/NSString+Encoding.h>
#import <NGMime/NGMimeBodyPart.h> #import <NGMime/NGMimeBodyPart.h>
#import <NGMime/NGMimeMultipartBody.h> #import <NGMime/NGMimeMultipartBody.h>
@ -166,9 +167,9 @@ static NSString *recTypes[] = { @"orig", @"to", @"cc", @"bcc" };
- (BOOL) hasContentId - (BOOL) hasContentId
{ {
return ([properties NSString *contentId = [properties
objectForKey: MAPIPropertyKey (PR_ATTACH_CONTENT_ID_UNICODE)] objectForKey: MAPIPropertyKey (PR_ATTACH_CONTENT_ID_UNICODE)];
!= nil); return contentId && [contentId length] > 0;
} }
- (NGMimeBodyPart *) asMIMEBodyPart - (NGMimeBodyPart *) asMIMEBodyPart
@ -232,7 +233,7 @@ static NSString *recTypes[] = { @"orig", @"to", @"cc", @"bcc" };
[map addObject: contentDisposition forKey: @"content-disposition"]; [map addObject: contentDisposition forKey: @"content-disposition"];
contentId = [properties contentId = [properties
objectForKey: MAPIPropertyKey (PR_ATTACH_CONTENT_ID_UNICODE)]; objectForKey: MAPIPropertyKey (PR_ATTACH_CONTENT_ID_UNICODE)];
if (contentId) if (contentId && [contentId length] > 0)
[map setObject: [NSString stringWithFormat: @"<%@>", contentId] [map setObject: [NSString stringWithFormat: @"<%@>", contentId]
forKey: @"content-id"]; forKey: @"content-id"];
bodyPart = [NGMimeBodyPart bodyPartWithHeader: map]; bodyPart = [NGMimeBodyPart bodyPartWithHeader: map];
@ -285,7 +286,7 @@ static NSString *recTypes[] = { @"orig", @"to", @"cc", @"bcc" };
version = [properties objectForKey: @"version"]; version = [properties objectForKey: @"version"];
return (version return (version
? exchange_globcnt ([version unsignedLongLongValue]) ? [version unsignedLongLongValue]
: ULLONG_MAX); : ULLONG_MAX);
} }
@ -1110,7 +1111,8 @@ MakeMessageBody (NSDictionary *mailProperties, NSDictionary *attachmentParts, NS
- (void) save: (TALLOC_CTX *) memCtx - (void) save: (TALLOC_CTX *) memCtx
{ {
NSString *folderName, *flag, *newIdString, *messageKey; BOOL updatedMetadata;
NSString *folderName, *flag, *newIdString, *messageKey, *changeNumber;
NSData *changeKey, *messageData; NSData *changeKey, *messageData;
NGImap4Connection *connection; NGImap4Connection *connection;
NGImap4Client *client; NGImap4Client *client;
@ -1146,21 +1148,33 @@ MakeMessageBody (NSDictionary *mailProperties, NSDictionary *attachmentParts, NS
[sogoObject setNameInContainer: messageKey]; [sogoObject setNameInContainer: messageKey];
[mapping registerURL: [self url] withID: mid]; [mapping registerURL: [self url] withID: mid];
/* synchronise the cache and update the change key with the one provided /* synchronise the cache and update the predecessor change list
by the client. Before doing this, lets issue a unselect/select combo with the change key provided by the client. Before doing
because of timing issues with Dovecot in obtaining the latest modseq. this, lets issue a unselect/select combo because of timing
Sometimes, Dovecot doesn't return the newly appended UID if we do issues with Dovecot in obtaining the latest modseq.
a "UID SORT (DATE) UTF-8 (MODSEQ XYZ) (NOT DELETED)" command (where Sometimes, Dovecot doesn't return the newly appended UID if
XYZ is the HIGHESTMODSEQ+1) immediately after IMAP APPEND */ 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 unselect];
[client select: folderName]; [client select: folderName];
[(MAPIStoreMailFolder *) container synchroniseCache]; [(MAPIStoreMailFolder *) container synchroniseCache];
changeKey = [properties objectForKey: MAPIPropertyKey (PR_CHANGE_KEY)]; changeKey = [properties objectForKey: MAPIPropertyKey (PR_CHANGE_KEY)];
if (changeKey) if (changeKey)
[(MAPIStoreMailFolder *) container {
setChangeKey: changeKey updatedMetadata = [(MAPIStoreMailFolder *) container updatePredecessorChangeListWith: changeKey
forMessageWithKey: messageKey]; 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"];
} }
} }

View File

@ -63,7 +63,7 @@
withAID: (uint32_t) aid; withAID: (uint32_t) aid;
- (int) getAttachmentTable: (MAPIStoreAttachmentTable **) tablePtr - (int) getAttachmentTable: (MAPIStoreAttachmentTable **) tablePtr
andRowCount: (uint32_t *) countPtr; andRowCount: (uint32_t *) countPtr;
- (int) setReadFlag: (uint8_t) flag; - (enum mapistore_error) setReadFlag: (uint8_t) flag;
- (enum mapistore_error) saveMessage: (TALLOC_CTX *) memCtx; - (enum mapistore_error) saveMessage: (TALLOC_CTX *) memCtx;
- (NSArray *) activeContainerMessageTables; - (NSArray *) activeContainerMessageTables;

View File

@ -549,11 +549,12 @@ rtf2html (NSData *compressedRTF)
} }
[self save: memCtx]; [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 properties dictionary, to make sure that related methods will be
invoked the next time they are requested. */ invoked the next time they are requested. */
[properties removeObjectForKey: MAPIPropertyKey (PidTagChangeKey)]; [properties removeObjectForKey: MAPIPropertyKey (PidTagChangeKey)];
[properties removeObjectForKey: MAPIPropertyKey (PidTagChangeNumber)]; [properties removeObjectForKey: MAPIPropertyKey (PidTagChangeNumber)];
[properties removeObjectForKey: MAPIPropertyKey (PidTagPredecessorChangeList)];
if ([container isKindOfClass: MAPIStoreFolderK]) if ([container isKindOfClass: MAPIStoreFolderK])
{ {
@ -918,7 +919,7 @@ rtf2html (NSData *compressedRTF)
return [self getNo: data inMemCtx: memCtx];; return [self getNo: data inMemCtx: memCtx];;
} }
- (int) setReadFlag: (uint8_t) flag - (enum mapistore_error) setReadFlag: (uint8_t) flag
{ {
// [self subclassResponsibility: _cmd]; // [self subclassResponsibility: _cmd];

View File

@ -674,6 +674,7 @@ sogo_folder_move_copy_messages(void *folder_object,
uint32_t mid_count, uint32_t mid_count,
uint64_t *src_mids, uint64_t *t_mids, uint64_t *src_mids, uint64_t *t_mids,
struct Binary_r **target_change_keys, struct Binary_r **target_change_keys,
struct Binary_r **target_predecessor_change_lists,
uint8_t want_copy) uint8_t want_copy)
{ {
MAPIStoreFolder *sourceFolder, *targetFolder; MAPIStoreFolder *sourceFolder, *targetFolder;
@ -698,6 +699,7 @@ sogo_folder_move_copy_messages(void *folder_object,
fromFolder: sourceFolder fromFolder: sourceFolder
withMIDs: t_mids withMIDs: t_mids
andChangeKeys: target_change_keys andChangeKeys: target_change_keys
andPredecessorChangeLists: target_predecessor_change_lists
wantCopy: want_copy wantCopy: want_copy
inMemCtx: mem_ctx]; inMemCtx: mem_ctx];
TRYCATCH_END(pool) TRYCATCH_END(pool)
@ -1118,7 +1120,7 @@ sogo_message_set_read_flag (void *message_object, uint8_t flag)
struct MAPIStoreTallocWrapper *wrapper; struct MAPIStoreTallocWrapper *wrapper;
NSAutoreleasePool *pool; NSAutoreleasePool *pool;
MAPIStoreMessage *message; MAPIStoreMessage *message;
int rc; enum mapistore_error rc;
if (message_object) if (message_object)
{ {

View File

@ -531,7 +531,7 @@
} }
[vToDo setTimeStampAsDate: now]; [vToDo setTimeStampAsDate: now];
[sogoObject saveComponent: vCalendar]; [sogoObject saveCalendar: vCalendar];
[self updateVersions]; [self updateVersions];
} }

View File

@ -41,6 +41,8 @@
+ (id) dataWithXID: (const struct XID *) xid; + (id) dataWithXID: (const struct XID *) xid;
- (struct XID *) asXIDInMemCtx: (void *) memCtx; - (struct XID *) asXIDInMemCtx: (void *) memCtx;
- (struct SizedXid *) asSizedXidArrayInMemCtx: (void *) memCtx
with: (uint32_t *) length;
+ (id) dataWithChangeKeyGUID: (NSString *) guidString + (id) dataWithChangeKeyGUID: (NSString *) guidString
andCnt: (NSData *) globCnt; andCnt: (NSData *) globCnt;

View File

@ -22,6 +22,7 @@
#import <NGExtensions/NSObject+Logs.h> #import <NGExtensions/NSObject+Logs.h>
#import "MAPIStoreTypes.h"
#import "NSObject+MAPIStore.h" #import "NSObject+MAPIStore.h"
#import "NSString+MAPIStore.h" #import "NSString+MAPIStore.h"
@ -29,6 +30,7 @@
#undef DEBUG #undef DEBUG
#include <stdbool.h> #include <stdbool.h>
#include <libmapi/libmapi.h>
#include <talloc.h> #include <talloc.h>
#include <util/time.h> #include <util/time.h>
#include <gen_ndr/exchange.h> #include <gen_ndr/exchange.h>
@ -136,11 +138,11 @@ static void _fillFlatUIDWithGUID (struct FlatUID_r *flatUID, const struct GUID *
NSMutableData *xidData; NSMutableData *xidData;
struct FlatUID_r flatUID; 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: flatUID.ab length: 16];
[xidData appendBytes: xid->Data length: xid->Size]; [xidData appendBytes: xid->LocalId.data length: xid->LocalId.length];
return xidData; return xidData;
} }
@ -156,12 +158,12 @@ static void _fillFlatUIDWithGUID (struct FlatUID_r *flatUID, const struct GUID *
{ {
xid = talloc_zero (memCtx, struct XID); 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]; 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 else
{ {
@ -172,6 +174,38 @@ static void _fillFlatUIDWithGUID (struct FlatUID_r *flatUID, const struct GUID *
return xid; 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 + (id) dataWithChangeKeyGUID: (NSString *) guidString
andCnt: (NSData *) globCnt; andCnt: (NSData *) globCnt;
{ {

View File

@ -500,7 +500,7 @@ static NSTimeInterval ChannelCollectionTimer = 5 * 60;
ms = [NSMutableString stringWithCapacity: 256]; ms = [NSMutableString stringWithCapacity: 256];
[ms appendFormat: @"<0x%p[%@]: ", self, NSStringFromClass ([self class])]; [ms appendFormat: @"<0x%p[%@]: ", self, NSStringFromClass ([self class])];
[ms appendFormat: @" #adaptors=%d", [urlToAdaptor count]]; [ms appendFormat: @" #adaptors=%d", (int)[urlToAdaptor count]];
[ms appendString: @">"]; [ms appendString: @">"];
return ms; return ms;

View File

@ -414,7 +414,7 @@
if (group) if (group)
[str appendFormat: @"%@ (group: %@)\n", tag, group]; [str appendFormat: @"%@ (group: %@)\n", tag, group];
else else
[str appendFormat: @"%@\n", tag, group]; [str appendFormat: @"%@\n", tag];
[str appendString: [self versitString]]; [str appendString: [self versitString]];

View File

@ -409,7 +409,7 @@ static NGCardsSaxHandler *sax = nil;
max = [children count]; max = [children count];
if (max > 0) 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++) for (count = 0; count < max; count++)
[str appendFormat: @" %@\n", [str appendFormat: @" %@\n",
[[children objectAtIndex: count] description]]; [[children objectAtIndex: count] description]];

View File

@ -32,16 +32,16 @@
- (NSString *) iCalFormattedDateTimeString - (NSString *) iCalFormattedDateTimeString
{ {
return [NSString stringWithFormat: @"%.4d%.2d%.2dT%.2d%.2d%.2d", return [NSString stringWithFormat: @"%.4d%.2d%.2dT%.2d%.2d%.2d",
[self yearOfCommonEra], [self monthOfYear], (int)[self yearOfCommonEra], (int)[self monthOfYear],
[self dayOfMonth], [self hourOfDay], (int)[self dayOfMonth], (int)[self hourOfDay],
[self minuteOfHour], [self secondOfMinute]]; (int)[self minuteOfHour], (int)[self secondOfMinute]];
} }
- (NSString *) iCalFormattedDateString - (NSString *) iCalFormattedDateString
{ {
return [NSString stringWithFormat: @"%.4d%.2d%.2d", return [NSString stringWithFormat: @"%.4d%.2d%.2d",
[self yearOfCommonEra], [self monthOfYear], (int)[self yearOfCommonEra], (int)[self monthOfYear],
[self dayOfMonth]]; (int)[self dayOfMonth]];
} }
@end @end

View File

@ -158,16 +158,31 @@
NSMutableString *string; NSMutableString *string;
unsigned int len, i; unsigned int len, i;
unichar c; unichar c;
BOOL isQuoted;
len = [self length]; len = [self length];
string = [NSMutableString stringWithCapacity: len * 1.5]; string = [NSMutableString stringWithCapacity: len * 1.5];
isQuoted = NO;
for (i = 0; i < len; i++) for (i = 0; i < len; i++)
{ {
c = [self characterAtIndex: i]; c = [self characterAtIndex: i];
if (isQuoted)
{
if (c == '"')
isQuoted = NO;
[string appendFormat: @"%C", c];
continue;
}
switch (c) switch (c)
{ {
case '"':
isQuoted = YES;
[string appendFormat: @"%C", c];
break;
case '\\': case '\\':
[string appendString: @"\\\\"]; [string appendString: @"\\\\"];
break; break;

View File

@ -27,6 +27,7 @@
#import "NSCalendarDate+NGCards.h" #import "NSCalendarDate+NGCards.h"
#import "iCalAlarm.h" #import "iCalAlarm.h"
#import "iCalCalendar.h"
#import "iCalDateTime.h" #import "iCalDateTime.h"
#import "iCalEntityObject.h" #import "iCalEntityObject.h"
#import "iCalEvent.h" #import "iCalEvent.h"
@ -268,9 +269,12 @@
- (void) setRecurrenceId: (NSCalendarDate *) newRecId - (void) setRecurrenceId: (NSCalendarDate *) newRecId
{ {
iCalDateTime* recurrenceId; iCalDateTime* recurrenceId;
BOOL isMasterAllDay;
isMasterAllDay = [[[[self parent] events] objectAtIndex: 0] isAllDay];
recurrenceId = (iCalDateTime *) [self uniqueChildWithTag: @"recurrence-id"]; recurrenceId = (iCalDateTime *) [self uniqueChildWithTag: @"recurrence-id"];
if ([self isKindOfClass: [iCalEvent class]] && [(iCalEvent *)self isAllDay]) if ([self isKindOfClass: [iCalEvent class]] && isMasterAllDay)
[recurrenceId setDate: newRecId]; [recurrenceId setDate: newRecId];
else else
[recurrenceId setDateTime: newRecId]; [recurrenceId setDateTime: newRecId];

View File

@ -348,7 +348,7 @@ static inline unsigned iCalDoWForNSDoW (int dow)
if ([byDayMask occursOnDay: currentWeekDay]) if ([byDayMask occursOnDay: currentWeekDay])
{ {
if ([bySetPos containsObject: if ([bySetPos containsObject:
[NSString stringWithFormat: @"%d", currentPos]]) [NSString stringWithFormat: @"%d", (int)currentPos]])
monthDays[monthDay+1] = YES; monthDays[monthDay+1] = YES;
currentPos++; currentPos++;
} }
@ -362,7 +362,7 @@ static inline unsigned iCalDoWForNSDoW (int dow)
if ([byDayMask occursOnDay: currentWeekDay]) if ([byDayMask occursOnDay: currentWeekDay])
{ {
if ([bySetPos containsObject: if ([bySetPos containsObject:
[NSString stringWithFormat: @"%d", currentPos]]) [NSString stringWithFormat: @"%d", (int)currentPos]])
monthDays[monthDay] = YES; monthDays[monthDay] = YES;
currentPos--; currentPos--;
} }

View File

@ -267,7 +267,7 @@
- (BOOL)isEqual:(id)_other { - (BOOL)isEqual:(id)_other {
if(_other == nil) if(_other == nil)
return NO; return NO;
if([_other class] != self->isa) if([_other class] != object_getClass(self))
return NO; return NO;
if([_other hash] != [self hash]) if([_other hash] != [self hash])
return NO; return NO;

View File

@ -309,10 +309,10 @@ NSString *iCalWeekDayString[] = { @"SU", @"MO", @"TU", @"WE", @"TH", @"FR",
else if ([frequency isEqualToString:@"SECONDLY"]) else if ([frequency isEqualToString:@"SECONDLY"])
freq = iCalRecurrenceFrequenceSecondly; freq = iCalRecurrenceFrequenceSecondly;
else else
freq = NSNotFound; freq = (iCalRecurrenceFrequency) NSNotFound;
} }
else else
freq = NSNotFound; freq = (iCalRecurrenceFrequency) NSNotFound;
return freq; return freq;
} }

View File

@ -161,8 +161,11 @@
minute: [tzStart minuteOfHour] second: 0 minute: [tzStart minuteOfHour] second: 0
timeZone: [NSTimeZone timeZoneWithName: @"GMT"]]; timeZone: [NSTimeZone timeZoneWithName: @"GMT"]];
tmpDate = [tmpDate addYear: 0 month: ((pos > 0) ? 0 : 1) tmpDate = [tmpDate addYear: 0
day: 0 hour: 0 minute: 0 month: ((pos > 0) ? 0 : 1)
day: 0
hour: 0
minute: 0
second: 0]; second: 0];
/* If the day of the time change is "-XSU", we need to determine whether the /* If the day of the time change is "-XSU", we need to determine whether the
@ -197,12 +200,41 @@
END:STANDARD END:STANDARD
END:VTIMEZONE 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 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 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. 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--; pos--;
offset = (dayOfWeek - dateDayOfWeek) + (pos * 7); offset = (dayOfWeek - dateDayOfWeek) + (pos * 7);

View File

@ -57,7 +57,7 @@
- (NSDictionary *) asDictionary - (NSDictionary *) asDictionary
{ {
NSDictionary *data; NSDictionary *data;
NSString *duration, *relation, *reference, *quantity, *unit; NSString *duration, *relation, *reference, *quantity=@"", *unit=@"";
NSUInteger i; NSUInteger i;
unichar c; unichar c;

View File

@ -1045,7 +1045,7 @@ static NSCharacterSet *whitespaceCharSet = nil;
if (debugOn) if (debugOn)
{ {
NSLog(@"%s: trying to decode data (0x%p,len=%d) ...", 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) if ((len = [_data length]) == 0)
@ -1164,7 +1164,7 @@ static NSCharacterSet *whitespaceCharSet = nil;
if (debugOn) if (debugOn)
{ {
NSLog(@"%s: trying to parse string (0x%p,len=%d) ...", 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>"; if (!_sysId) _sysId = @"<string>";
[self _parseString: _source]; [self _parseString: _source];

View File

@ -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()

View File

View File

View File

View File

0
Scripts/sql-update-20080303.sh 100644 → 100755
View File

View File

@ -344,7 +344,7 @@ size_t curl_body_function_freebusy(void *ptr, size_t size, size_t nmemb, void *i
NSMutableString *s; NSMutableString *s;
s = [NSMutableString stringWithCapacity: 64]; s = [NSMutableString stringWithCapacity: 64];
[s appendFormat:@"<0x%08X[%@]:", self, NSStringFromClass([self class])]; [s appendFormat:@"<0x%08X[%@]:", (unsigned int)self, NSStringFromClass([self class])];
if (freeBusyViewType) if (freeBusyViewType)
[s appendFormat:@" freeBusyViewType='%@'", freeBusyViewType]; [s appendFormat:@" freeBusyViewType='%@'", freeBusyViewType];
if (mergedFreeBusy) if (mergedFreeBusy)

View File

@ -84,7 +84,7 @@
#import "SOGoAppointmentFolders.h" #import "SOGoAppointmentFolders.h"
#import "SOGoFreeBusyObject.h" #import "SOGoFreeBusyObject.h"
#import "SOGoTaskObject.h" #import "SOGoTaskObject.h"
#import "SOGoWebAppointmentFolder.h"; #import "SOGoWebAppointmentFolder.h"
#import "SOGoAppointmentFolder.h" #import "SOGoAppointmentFolder.h"
@ -1013,7 +1013,7 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
{ {
if ([dateRange containsDate: [component startDate]]) 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]]; newRecord = [self _fixupRecord: [component quickRecordFromContent: nil container: nil]];
[ma replaceObjectAtIndex: recordIndex withObject: newRecord]; [ma replaceObjectAtIndex: recordIndex withObject: newRecord];
} }
@ -1030,15 +1030,20 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
{ {
// The recurrence id of the exception is outside the date range; // The recurrence id of the exception is outside the date range;
// simply add the exception to the records array. // 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]]; newRecord = [self _fixupRecord: [component quickRecordFromContent: nil container: nil]];
newRecordRange = [NGCalendarDateRange if ([newRecord objectForKey: @"startDate"] && [newRecord objectForKey: @"endDate"]) {
calendarDateRangeWithStartDate: [newRecord objectForKey: @"startDate"] newRecordRange = [NGCalendarDateRange
endDate: [newRecord objectForKey: @"endDate"]]; calendarDateRangeWithStartDate: [newRecord objectForKey: @"startDate"]
if ([dateRange doesIntersectWithDateRange: newRecordRange]) endDate: [newRecord objectForKey: @"endDate"]];
if ([dateRange doesIntersectWithDateRange: newRecordRange])
[ma addObject: newRecord]; [ma addObject: newRecord];
else else
newRecord = nil;
} else {
[self warnWithFormat: @"Recurrence %@ without dtstart or dtend. Ignoring", recurrenceId];
newRecord = nil; newRecord = nil;
}
} }
if (newRecord) if (newRecord)
@ -2337,7 +2342,7 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
request = [context request]; request = [context request];
if (!([request isIPhone] || [request isICal4])) if (!([request isIPhone] || [request isICal4]))
{ {
gdRT = [self groupDavResourceType]; gdRT = (NSArray *) [self groupDavResourceType];
gdVEventCol = [NSArray arrayWithObjects: [gdRT objectAtIndex: 0], gdVEventCol = [NSArray arrayWithObjects: [gdRT objectAtIndex: 0],
XMLNS_GROUPDAV, nil]; XMLNS_GROUPDAV, nil];
[colType addObject: gdVEventCol]; [colType addObject: gdVEventCol];
@ -2988,7 +2993,10 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
theUser = [SOGoUser userWithLogin: theUID]; theUser = [SOGoUser userWithLogin: theUID];
aParent = [theUser calendarsFolderInContext: context]; aParent = [theUser calendarsFolderInContext: context];
if ([aParent isKindOfClass: [NSException class]])
return nil;
aFolders = [aParent subFolders]; aFolders = [aParent subFolders];
e = [aFolders objectEnumerator]; e = [aFolders objectEnumerator];
while ((aFolder = [e nextObject])) while ((aFolder = [e nextObject]))
@ -3152,19 +3160,29 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
NSMutableString *content; NSMutableString *content;
NSString *uid; NSString *uid;
// We first look if there's an event with the same UID in our calendar. If not, // We first look if the event has any / or + in its UID. If that's the case
// let's reuse what is in the event, otherwise generate a new GUID and use it. // we generate a new UID based on a GUID
uid = [event uid]; uid = [event uid];
object = [self lookupName: uid if ([uid rangeOfCharacterFromSet: [NSCharacterSet characterSetWithCharactersInString: @"+/"]].location != NSNotFound)
inContext: context
acquire: NO];
if (object && ![object isKindOfClass: [NSException class]])
{ {
uid = [self globallyUniqueObjectId]; uid = [self globallyUniqueObjectId];
[event setUid: uid]; [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];
if (object && ![object isKindOfClass: [NSException class]])
{
uid = [self globallyUniqueObjectId];
[event setUid: uid];
}
}
object = [SOGoAppointmentObject objectWithName: uid object = [SOGoAppointmentObject objectWithName: uid
inContainer: self]; inContainer: self];

View File

@ -46,6 +46,7 @@
#import <NGCards/iCalDateTime.h> #import <NGCards/iCalDateTime.h>
#import <NGCards/iCalTimeZone.h> #import <NGCards/iCalTimeZone.h>
#import <NGCards/iCalTimeZonePeriod.h> #import <NGCards/iCalTimeZonePeriod.h>
#import <NGCards/iCalToDo.h>
#import <NGCards/NSString+NGCards.h> #import <NGCards/NSString+NGCards.h>
#import <SOGo/SOGoConstants.h> #import <SOGo/SOGoConstants.h>
@ -748,11 +749,14 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
{ {
e = [events objectAtIndex: i]; e = [events objectAtIndex: i];
if ([e recurrenceId]) if ([e recurrenceId])
for (j = 0; j < [theAttendees count]; j++) for (j = 0; j < [theAttendees count]; j++) {
if (shouldAdd) if (shouldAdd) {
[e addToAttendees: [theAttendees objectAtIndex: j]]; [e addToAttendees: [theAttendees objectAtIndex: j]];
else }
else {
[e removeFromAttendees: [theAttendees objectAtIndex: j]]; [e removeFromAttendees: [theAttendees objectAtIndex: j]];
}
}
} }
} }
@ -1038,7 +1042,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
if ([delegateEmail length]) if ([delegateEmail length])
otherDelegate = [event findAttendeeWithEmail: delegateEmail]; otherDelegate = [event findAttendeeWithEmail: delegateEmail];
else else
otherDelegate = NO; otherDelegate = nil;
/* we handle the addition/deletion of delegate users */ /* we handle the addition/deletion of delegate users */
addDelegate = NO; addDelegate = NO;
@ -1076,7 +1080,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
if ([delegateEmail length]) if ([delegateEmail length])
otherDelegate = [event findAttendeeWithEmail: delegateEmail]; otherDelegate = [event findAttendeeWithEmail: delegateEmail];
else else
otherDelegate = NO; otherDelegate = nil;
} }
} }
if (addDelegate) if (addDelegate)
@ -1233,7 +1237,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
if ([delegateEmail length]) if ([delegateEmail length])
otherDelegate = [event findAttendeeWithEmail: delegateEmail]; otherDelegate = [event findAttendeeWithEmail: delegateEmail];
else else
otherDelegate = NO; otherDelegate = nil;
} }
[self sendEMailUsingTemplateNamed: @"Deletion" [self sendEMailUsingTemplateNamed: @"Deletion"
@ -1992,7 +1996,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
if ([container resourceNameForEventUID: eventUID]) if ([container resourceNameForEventUID: eventUID])
{ {
return [NSException exceptionWithHTTPStatus: 403 return [NSException exceptionWithHTTPStatus: 403
reason: [NSString stringWithFormat: @"Event UID already in use. (%s)", eventUID]]; reason: [NSString stringWithFormat: @"Event UID already in use. (%@)", eventUID]];
} }
// //

View File

@ -140,6 +140,14 @@
return aclManager; return aclManager;
} }
- (NSException *) changeParticipationStatus: (NSString *) newPartStat
withDelegate: (iCalPerson *) delegate
alarm: (iCalAlarm *) alarm
{
// Required for protocol <SOGoComponentOccurence>
return nil;
}
- (id) init - (id) init
{ {
if ((self = [super init])) if ((self = [super init]))
@ -171,7 +179,7 @@
- (Class *) parsingClass - (Class *) parsingClass
{ {
return [iCalCalendar class]; return (Class *)[iCalCalendar class];
} }
- (NSString *) davContentType - (NSString *) davContentType

View File

@ -102,9 +102,9 @@
} }
if ([reminderReference caseInsensitiveCompare: @"BEFORE"] == NSOrderedSame) if ([reminderReference caseInsensitiveCompare: @"BEFORE"] == NSOrderedSame)
aValue = [NSString stringWithString: @"-P"]; aValue = (NSString *) @"-P";
else else
aValue = [NSString stringWithString: @"P"]; aValue = (NSString *) @"P";
if ([reminderUnit caseInsensitiveCompare: @"MINUTES"] == NSOrderedSame || if ([reminderUnit caseInsensitiveCompare: @"MINUTES"] == NSOrderedSame ||
[reminderUnit caseInsensitiveCompare: @"HOURS"] == NSOrderedSame) [reminderUnit caseInsensitiveCompare: @"HOURS"] == NSOrderedSame)

View File

@ -267,7 +267,7 @@
- (NSTimeInterval) occurenceInterval - (NSTimeInterval) occurenceInterval
{ {
return [[self endDate] timeIntervalSinceDate: [self startDate]]; return (NSTimeInterval) [[self endDate] timeIntervalSinceDate: [self startDate]];
} }
/** /**
@ -328,30 +328,10 @@
*/ */
- (NSDictionary *) attributesInContext: (WOContext *) context - (NSDictionary *) attributesInContext: (WOContext *) context
{ {
BOOL isAllDay;
NSCalendarDate *eventStartDate, *eventEndDate;
NSMutableDictionary *data; 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 = [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"]; [data setObject: [NSNumber numberWithBool: ![self isOpaque]] forKey: @"isTransparent"];
return data; return data;
@ -383,18 +363,10 @@
if ([o isKindOfClass: [NSString class]] && [o length]) if ([o isKindOfClass: [NSString class]] && [o length])
aptStartDate = [self dateFromString: o inContext: context]; 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"]; o = [data objectForKey: @"endDate"];
if ([o isKindOfClass: [NSString class]] && [o length]) if ([o isKindOfClass: [NSString class]] && [o length])
aptEndDate = [self dateFromString: o inContext: context]; 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"]; o = [data objectForKey: @"isTransparent"];
if ([o isKindOfClass: [NSNumber class]]) if ([o isKindOfClass: [NSNumber class]])
[self setTransparency: ([o boolValue]? @"TRANSPARENT" : @"OPAQUE")]; [self setTransparency: ([o boolValue]? @"TRANSPARENT" : @"OPAQUE")];
@ -418,6 +390,14 @@
} }
else 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 setStartDate: aptStartDate];
[self setEndDate: aptEndDate]; [self setEndDate: aptEndDate];
} }

View File

@ -44,6 +44,7 @@
#import <SOGo/NSCalendarDate+SOGo.h> #import <SOGo/NSCalendarDate+SOGo.h>
#import "iCalRepeatableEntityObject+SOGo.h" #import "iCalRepeatableEntityObject+SOGo.h"
#import "iCalCalendar+SOGo.h"
@implementation iCalRepeatableEntityObject (SOGoExtensions) @implementation iCalRepeatableEntityObject (SOGoExtensions)
@ -162,7 +163,7 @@
rule = [iCalRecurrenceRule new]; rule = [iCalRecurrenceRule new];
[rule setInterval: @"1"]; [rule setInterval: @"1"];
frequency = NSNotFound; frequency = (int)NSNotFound;
o = [repeat objectForKey: @"frequency"]; o = [repeat objectForKey: @"frequency"];
if ([o isKindOfClass: [NSString class]]) if ([o isKindOfClass: [NSString class]])
{ {

View File

@ -214,7 +214,7 @@
{ {
percent = [o intValue]; percent = [o intValue];
if (percent >= 0 && percent <= 100) if (percent >= 0 && percent <= 100)
[self setPercentComplete: [NSString stringWithFormat: @"%i", percent]]; [self setPercentComplete: [NSString stringWithFormat: @"%i", (int)percent]];
} }
else else
[self setPercentComplete: @""]; [self setPercentComplete: @""];

View File

@ -327,7 +327,7 @@ convention:
if (year && month && day) if (year && month && day)
[self setBday: [NSString stringWithFormat: @"%.4d-%.2d-%.2d", [self setBday: [NSString stringWithFormat: @"%.4d-%.2d-%.2d",
year, month, day]]; (int)year, (int)month, (int)day]];
else else
[self setBday: @""]; [self setBday: @""];
@ -644,11 +644,11 @@ convention:
birthDay = [[self bday] asCalendarDate]; birthDay = [[self bday] asCalendarDate];
if (birthDay) if (birthDay)
{ {
stringValue = [NSString stringWithFormat: @"%.4d", [birthDay yearOfCommonEra]]; stringValue = [NSString stringWithFormat: @"%.4d", (int)[birthDay yearOfCommonEra]];
[self _setValue: @"birthyear" to: stringValue inLDIFRecord: ldifRecord]; [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]; [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: @"birthday" to: stringValue inLDIFRecord: ldifRecord];
} }
[self _setValue: @"description" to: [self note] inLDIFRecord: ldifRecord]; [self _setValue: @"description" to: [self note] inLDIFRecord: ldifRecord];

View File

@ -59,7 +59,7 @@
[response setHeader: [self davContentType] forKey: @"content-type"]; [response setHeader: [self davContentType] forKey: @"content-type"];
[response setHeader: [NSString stringWithFormat:@" %d", [response setHeader: [NSString stringWithFormat:@" %d",
[data length]] (int)[data length]]
forKey: @"content-length"]; forKey: @"content-length"];
[response setContent: data]; [response setContent: data];
} }

View File

@ -268,9 +268,9 @@ Class SOGoContactSourceFolderK;
SOGoUser *currentUser; SOGoUser *currentUser;
id <SOGoSource> source; id <SOGoSource> source;
if ([sourceID isEqualToString: @"personal"]) if ([sourceID isEqualToString: @"personal"]){
result = [NSException exceptionWithHTTPStatus: 403 result = [NSException exceptionWithHTTPStatus: 403 reason: [NSString stringWithFormat: (@"folder '%@' cannot be deleted"), sourceID]];
reason: (@"folder '%@' cannot be deleted", sourceID)]; }
else else
{ {
result = nil; result = nil;

View File

@ -50,7 +50,7 @@
- (Class *) parsingClass - (Class *) parsingClass
{ {
return [NGVCard class]; return (Class *)[NGVCard class];
} }
/* content */ /* content */

View File

@ -45,7 +45,7 @@
- (Class *) parsingClass - (Class *) parsingClass
{ {
return [NGVList class]; return (Class *)[NGVList class];
} }

View File

@ -47,7 +47,9 @@
#import <SOGo/NSObject+DAV.h> #import <SOGo/NSObject+DAV.h>
#import <SOGo/SOGoPermissions.h> #import <SOGo/SOGoPermissions.h>
#import <SOGo/SOGoSource.h> #import <SOGo/SOGoSource.h>
#import <SOGo/SOGoUserManager.h>
#import <SOGo/SOGoUserSettings.h> #import <SOGo/SOGoUserSettings.h>
#import <SOGo/SOGoSystemDefaults.h>
#import <SOGo/WORequest+SOGo.h> #import <SOGo/WORequest+SOGo.h>
#import <SOGo/WOResponse+SOGo.h> #import <SOGo/WOResponse+SOGo.h>
@ -94,7 +96,7 @@
{ {
if (![newDisplayName length]) if (![newDisplayName length])
newDisplayName = newName; newDisplayName = newName;
ASSIGN (displayName, newDisplayName); ASSIGN (displayName, (NSMutableString *)newDisplayName);
} }
return self; return self;
@ -248,6 +250,13 @@
data = @""; data = @"";
[newRecord setObject: data forKey: @"c_cn"]; [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[] // mail => emails[]
data = [oldRecord objectForKey: @"c_emails"]; data = [oldRecord objectForKey: @"c_emails"];
if (data) if (data)
@ -697,7 +706,7 @@
BOOL otherIsPersonal; BOOL otherIsPersonal;
otherIsPersonal = ([otherFolder isKindOfClass: [SOGoContactGCSFolder class]] otherIsPersonal = ([otherFolder isKindOfClass: [SOGoContactGCSFolder class]]
|| ([otherFolder isKindOfClass: isa] && [otherFolder isPersonalSource])); || ([otherFolder isKindOfClass: object_getClass(self)] && [otherFolder isPersonalSource]));
if (isPersonalSource) if (isPersonalSource)
{ {

View File

@ -1,8 +1,6 @@
/* NSObject+CardDAV.h - this file is part of SOGo /* NSObject+CardDAV.h - this file is part of SOGo
* *
* Copyright (C) 2007 Inverse inc. * Copyright (C) 2007-2015 Inverse inc.
*
* Author: Ludovic Marcotte <ludovic@inverse.ca>
* *
* This file is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by

View File

@ -1,8 +1,6 @@
/* NSObject+CardDAV.m - this file is part of SOGo /* NSObject+CardDAV.m - this file is part of SOGo
* *
* Copyright (C) 2007-2011 Inverse inc. * Copyright (C) 2007-2015 Inverse inc.
*
* Author: Ludovic Marcotte <ludovic@inverse.ca>
* *
* This file is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -187,6 +185,10 @@
[filters addObject: filter]; [filters addObject: filter];
} }
// If no filters are provided, we return everything.
if (![filters count])
[filters addObject: [NSDictionary dictionaryWithObject: @"." forKey: @"mail"]];
return filters; return filters;
} }

View File

@ -20,6 +20,7 @@
#import <Foundation/NSDictionary.h> #import <Foundation/NSDictionary.h>
#import <Foundation/NSString.h> #import <Foundation/NSString.h>
#import <NGExtensions/NSString+misc.h>
#import "NSDictionary+Mail.h" #import "NSDictionary+Mail.h"

View File

@ -533,7 +533,7 @@
messageID = [NSMutableString string]; messageID = [NSMutableString string];
[messageID appendFormat: @"<%@", [SOGoObject globallyUniqueObjectId]]; [messageID appendFormat: @"<%@", [SOGoObject globallyUniqueObjectId]];
pGUID = [[NSProcessInfo processInfo] globallyUniqueString]; pGUID = [[NSProcessInfo processInfo] globallyUniqueString];
[messageID appendFormat: @"@%u>", [pGUID hash]]; [messageID appendFormat: @"@%u>", (unsigned int)[pGUID hash]];
return [messageID lowercaseString]; return [messageID lowercaseString];
} }

View File

@ -181,6 +181,7 @@ static NSString *headerKeys[] = {@"subject", @"to", @"cc", @"bcc",
static NGMimeType *MultiMixedType = nil; static NGMimeType *MultiMixedType = nil;
static NGMimeType *MultiAlternativeType = nil; static NGMimeType *MultiAlternativeType = nil;
static NGMimeType *MultiRelatedType = nil;
static NSString *userAgent = nil; static NSString *userAgent = nil;
+ (void) initialize + (void) initialize
@ -191,6 +192,9 @@ static NSString *userAgent = nil;
MultiAlternativeType = [NGMimeType mimeType: @"multipart" subType: @"alternative"]; MultiAlternativeType = [NGMimeType mimeType: @"multipart" subType: @"alternative"];
[MultiAlternativeType retain]; [MultiAlternativeType retain];
MultiRelatedType = [NGMimeType mimeType: @"multipart" subType: @"related"];
[MultiRelatedType retain];
userAgent = [NSString stringWithFormat: @"SOGoMail %@", userAgent = [NSString stringWithFormat: @"SOGoMail %@",
SOGoVersion]; SOGoVersion];
[userAgent retain]; [userAgent retain];
@ -700,7 +704,8 @@ static NSString *userAgent = nil;
for (count = max - 1; count >= 0; count--) for (count = max - 1; count >= 0; count--)
{ {
currentAddress = [addresses objectAtIndex: count]; currentAddress = [addresses objectAtIndex: count];
if ([currentRecipient if (![currentAddress baseEMail] ||
[currentRecipient
caseInsensitiveCompare: [currentAddress baseEMail]] caseInsensitiveCompare: [currentAddress baseEMail]]
== NSOrderedSame) == NSOrderedSame)
{ {
@ -1676,16 +1681,20 @@ static NSString *userAgent = nil;
NGMimeMessage *message; NGMimeMessage *message;
NGMutableHashMap *map; NGMutableHashMap *map;
NSString *newText; NSString *newText;
BOOL has_inline_images;
message = nil; message = nil;
has_inline_images = NO;
bodyParts = [NSMutableArray array]; bodyParts = [NSMutableArray array];
if (_extractImages) if (_extractImages)
{ {
newText = [text htmlByExtractingImages: bodyParts]; newText = [text htmlByExtractingImages: bodyParts];
if ([bodyParts count]) if ([bodyParts count])
[self setText: newText]; {
[self setText: newText];
has_inline_images = YES;
}
} }
map = [self mimeHeaderMapWithHeaders: _headers map = [self mimeHeaderMapWithHeaders: _headers
@ -1702,10 +1711,20 @@ static NSString *userAgent = nil;
/* no attachments */ /* no attachments */
message = [self mimeMessageForContentWithHeaderMap: map]; message = [self mimeMessageForContentWithHeaderMap: map];
else else
/* attachments, create multipart/mixed */ {
message = [self mimeMultiPartMessageWithHeaderMap: map // attachments, create multipart/mixed or multipart/related if
andBodyParts: bodyParts]; // we have inline image to avoid Thunderbird bug #61815 (https://bugzilla.mozilla.org/show_bug.cgi?id=61815)
//[self debugWithFormat: @"message: %@", message]; 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; return message;

View File

@ -206,8 +206,8 @@ static NSString *inboxFolderName = @"INBOX";
[folders removeObjectsInArray: nss]; [folders removeObjectsInArray: nss];
} }
return [[folders stringsWithFormat: @"folder%@"] return [[folders resultsOfSelector: @selector (asCSSIdentifier)]
resultsOfSelector: @selector (asCSSIdentifier)]; stringsWithFormat: @"folder%@"];
} }
- (NSArray *) toManyRelationshipKeys - (NSArray *) toManyRelationshipKeys

View File

@ -32,9 +32,11 @@
#import <NGExtensions/NSString+misc.h> #import <NGExtensions/NSString+misc.h>
#import <NGExtensions/NSURL+misc.h> #import <NGExtensions/NSURL+misc.h>
#import <NGImap4/NGImap4Connection.h> #import <NGImap4/NGImap4Connection.h>
#import <NGImap4/NGImap4Client.h>
#import <SOGo/SOGoCache.h> #import <SOGo/SOGoCache.h>
#import <SOGo/SOGoUser.h> #import <SOGo/SOGoUser.h>
#import <SOGo/WORequest+SOGo.h>
#import "SOGoMailAccount.h" #import "SOGoMailAccount.h"
#import "SOGoMailManager.h" #import "SOGoMailManager.h"

View File

@ -396,7 +396,7 @@ static BOOL debugOn = NO;
mimeType = @"application/octet-stream"; mimeType = @"application/octet-stream";
[response setHeader: mimeType forKey: @"content-type"]; [response setHeader: mimeType forKey: @"content-type"];
[response setHeader: [NSString stringWithFormat:@"%d", [data length]] [response setHeader: [NSString stringWithFormat:@"%d", (int)[data length]]
forKey: @"content-length"]; forKey: @"content-length"];
if (asAttachment) if (asAttachment)

View File

@ -97,10 +97,10 @@
- (NSString *) davCollectionTag; - (NSString *) davCollectionTag;
- (NSArray *) syncTokenFieldsWithProperties: (NSArray *) theProperties - (NSArray *) syncTokenFieldsWithProperties: (NSDictionary *) properties
matchingSyncToken: (NSString *) theSyncToken matchingSyncToken: (NSString *) syncToken
fromDate: (NSCalendarDate *) theStartDate; fromDate: (NSCalendarDate *) theStartDate
initialLoad: (BOOL) initialLoadInProgress;
/* flags */ /* flags */
- (NSException *) addFlagsToAllMessages: (id) _f; - (NSException *) addFlagsToAllMessages: (id) _f;

View File

@ -199,9 +199,9 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
{ {
NSArray *subfolders; NSArray *subfolders;
subfolders = [[self subfolders] stringsWithFormat: @"folder%@"]; subfolders = [[self subfolders] resultsOfSelector: @selector (asCSSIdentifier)];
return [subfolders resultsOfSelector: @selector (asCSSIdentifier)]; return [subfolders stringsWithFormat: @"folder%@"];
} }
- (NSArray *) subfolders - (NSArray *) subfolders
@ -634,7 +634,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
inContext: (id) localContext inContext: (id) localContext
{ {
NSArray *folders; NSArray *folders;
NSString *currentFolderName, *currentAccountName; NSString *currentFolderName, *currentAccountName, *destinationAccountName;
NSMutableString *imapDestinationFolder; NSMutableString *imapDestinationFolder;
NGImap4Client *client; NGImap4Client *client;
id result; 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 #warning this code will fail on implementation using something else than '/' as delimiter
imapDestinationFolder = [NSMutableString string]; imapDestinationFolder = [NSMutableString string];
folders = [[destinationFolder componentsSeparatedByString: @"/"] folders = [destinationFolder componentsSeparatedByString: @"/"];
resultsOfSelector: @selector (fromCSSIdentifier)];
max = [folders count]; max = [folders count];
if (max > 1) if (max > 1)
{ {
currentAccountName = [[self mailAccountFolder] nameInContainer]; currentAccountName = [[self mailAccountFolder] nameInContainer];
client = [[self imap4Connection] client]; client = [[self imap4Connection] client];
[imap4 selectFolder: [self imap4URL]]; [imap4 selectFolder: [self imap4URL]];
destinationAccountName = [[folders objectAtIndex: 1] fromCSSIdentifier];
for (count = 2; count < max; count++) for (count = 2; count < max; count++)
{ {
currentFolderName = [[folders objectAtIndex: count] substringFromIndex: 6]; currentFolderName = [[[folders objectAtIndex: count] substringFromIndex: 6] fromCSSIdentifier];
[imapDestinationFolder appendFormat: @"/%@", currentFolderName]; [imapDestinationFolder appendFormat: @"/%@", currentFolderName];
} }
if (client) 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. // We make sure the destination IMAP folder exist, if not, we create it.
result = [[client status: imapDestinationFolder result = [[client status: imapDestinationFolder
@ -688,7 +688,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
userFolder = [[context activeUser] homeFolderInContext: context]; userFolder = [[context activeUser] homeFolderInContext: context];
accounts = [userFolder lookupName: @"Mail" inContext: context acquire: NO]; 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]]) if ([account isKindOfClass: [NSException class]])
{ {
@ -1674,7 +1674,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
sortOrderings = [NSMutableArray array]; sortOrderings = [NSMutableArray array];
if ([self _sortElementIsAscending: sortElement]) if ([self _sortElementIsAscending: (NGDOMNodeWithChildren <DOMElement> *)sortElement])
sortOrderingOrder = EOCompareAscending; sortOrderingOrder = EOCompareAscending;
else else
sortOrderingOrder = EOCompareDescending; sortOrderingOrder = EOCompareDescending;
@ -2106,7 +2106,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
// * 5 FETCH (UID 559 MODSEQ (17)) // * 5 FETCH (UID 559 MODSEQ (17))
// * 6 FETCH (UID 560 MODSEQ (18)) // * 6 FETCH (UID 560 MODSEQ (18))
// * 7 FETCH (UID 561 MODSEQ (19)) // * 7 FETCH (UID 561 MODSEQ (19))
//
// //
// fetchUIDsOfVanishedItems .. // fetchUIDsOfVanishedItems ..
// //
@ -2119,6 +2119,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
- (NSArray *) syncTokenFieldsWithProperties: (NSArray *) theProperties - (NSArray *) syncTokenFieldsWithProperties: (NSArray *) theProperties
matchingSyncToken: (NSString *) theSyncToken matchingSyncToken: (NSString *) theSyncToken
fromDate: (NSCalendarDate *) theStartDate fromDate: (NSCalendarDate *) theStartDate
initialLoad: (BOOL) initialLoadInProgress
{ {
EOQualifier *searchQualifier; EOQualifier *searchQualifier;
NSMutableArray *allTokens; NSMutableArray *allTokens;
@ -2195,6 +2196,9 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
for (i = 0; i < [fetchResults count]; i++) 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"] d = [NSDictionary dictionaryWithObject: ([[[fetchResults objectAtIndex: i] objectForKey: @"flags"] containsObject: @"deleted"]) ? [NSNull null] : [[fetchResults objectAtIndex: i] objectForKey: @"modseq"]
forKey: [[[fetchResults objectAtIndex: i] objectForKey: @"uid"] stringValue]]; forKey: [[[fetchResults objectAtIndex: i] objectForKey: @"uid"] stringValue]];
[allTokens addObject: d]; [allTokens addObject: d];
@ -2205,7 +2209,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
if (highestmodseq == 0) if (highestmodseq == 0)
highestmodseq = 1; highestmodseq = 1;
if (highestmodseq > 0) if (highestmodseq > 0 && !initialLoadInProgress)
{ {
id uid; id uid;

View File

@ -73,10 +73,10 @@
- (NSString *) newLine - (NSString *) newLine
{ {
NSString *rc = [NSString stringWithString: @" "]; NSString *rc = @" ";
if (htmlComposition) if (htmlComposition)
rc = [NSString stringWithString: @"<br/>"]; rc = @"<br/>";
return rc; return rc;
} }

View File

@ -74,7 +74,7 @@
int i; int i;
allLabels = [NSMutableArray array]; allLabels = [NSMutableArray array];
allKeys = [[theDefaults allKeys] sortedArrayUsingSelector: @selector (caseInsensitiveCompare:)]; allKeys = (NSMutableArray *)[[theDefaults allKeys] sortedArrayUsingSelector: @selector (caseInsensitiveCompare:)];
for (i = 0; i < [allKeys count]; i++) for (i = 0; i < [allKeys count]; i++)
{ {

View File

@ -389,6 +389,13 @@ static BOOL debugSoParts = NO;
[[[info valueForKey: @"subtype"] lowercaseString] isEqualToString: @"calendar"]) [[[info valueForKey: @"subtype"] lowercaseString] isEqualToString: @"calendar"])
return info; 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 For each path component, eg 1,1,3
@ -775,9 +782,14 @@ static BOOL debugSoParts = NO;
[mimeType hasPrefix: @"audio/"] || [mimeType hasPrefix: @"audio/"] ||
[mimeType hasPrefix: @"image/"] || [mimeType hasPrefix: @"image/"] ||
[mimeType hasPrefix: @"video/"]) [mimeType hasPrefix: @"video/"])
{
filename = [NSString stringWithFormat: @"unknown_%@", path]; filename = [NSString stringWithFormat: @"unknown_%@", path];
else if ([mimeType isEqualToString: @"message/rfc822"]) }
filename = [NSString stringWithFormat: @"email_%@.eml", path]; else
{
if ([mimeType isEqualToString: @"message/rfc822"])
filename = [NSString stringWithFormat: @"email_%@.eml", path];
}
} }
if (filename) if (filename)
@ -810,7 +822,7 @@ static BOOL debugSoParts = NO;
NSMutableDictionary *currentPart; NSMutableDictionary *currentPart;
NSString *newPath; NSString *newPath;
NSArray *subparts; NSArray *subparts;
NSString *type; NSString *type, *subtype;
NSUInteger i; NSUInteger i;
type = [[part objectForKey: @"type"] lowercaseString]; type = [[part objectForKey: @"type"] lowercaseString];
@ -821,19 +833,27 @@ static BOOL debugSoParts = NO;
{ {
currentPart = [subparts objectAtIndex: i-1]; currentPart = [subparts objectAtIndex: i-1];
if (path) if (path)
newPath = [NSString stringWithFormat: @"%@.%d", path, i]; newPath = [NSString stringWithFormat: @"%@.%d", path, (int)i];
else else
newPath = [NSString stringWithFormat: @"%d", i]; newPath = [NSString stringWithFormat: @"%d", (int)i];
[self _fetchFileAttachmentKeysInPart: currentPart [self _fetchFileAttachmentKeysInPart: currentPart
intoArray: keys intoArray: keys
withPath: newPath withPath: newPath
andPrefix: [NSString stringWithFormat: @"%@/%i", prefix, i]]; andPrefix: [NSString stringWithFormat: @"%@/%i", prefix, (int)i]];
} }
} }
else else
{ {
if (!path) if (!path)
path = @"1"; {
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 [self _fetchFileAttachmentKey: part
intoArray: keys intoArray: keys
withPath: path withPath: path
@ -1027,6 +1047,14 @@ static BOOL debugSoParts = NO;
return obj; 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 404 to stop acquisition */
return [NSException exceptionWithHTTPStatus:404 /* Not Found */ return [NSException exceptionWithHTTPStatus:404 /* Not Found */

View File

@ -310,7 +310,7 @@ static NSDictionary *BSONTypes()
case 'q': return 0x12; case 'q': return 0x12;
default: 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; 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; return nil;
} }

View File

@ -1703,7 +1703,7 @@ _makeLDAPChanges (NGLdapConnection *ldapConnection,
hostname: hostname hostname: hostname
port: [NSString stringWithFormat: @"%d", port] port: [NSString stringWithFormat: @"%d", port]
encryption: encryption encryption: encryption
bindAsCurrentUser: NO]; bindAsCurrentUser: [NSString stringWithFormat: @"%d", NO]];
[ab setBaseDN: [entry dn] [ab setBaseDN: [entry dn]
IDField: @"cn" IDField: @"cn"
CNField: @"displayName" CNField: @"displayName"

View File

@ -1,8 +1,6 @@
/* NSArray+Utilities.h - this file is part of SOGo /* NSArray+Utilities.h - this file is part of SOGo
* *
* Copyright (C) 2006 Inverse inc. * Copyright (C) 2006-2015 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* *
* This file is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by

View File

@ -1,8 +1,6 @@
/* NSArray+Utilities.m - this file is part of SOGo /* NSArray+Utilities.m - this file is part of SOGo
* *
* Copyright (C) 2006-2011 Inverse inc. * Copyright (C) 2006-2015 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* *
* This file is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by

View File

@ -90,9 +90,9 @@ static NSString *rfc822Months[] = {@"", @"Jan", @"Feb", @"Mar", @"Apr",
NSString *str; NSString *str;
str = [NSString stringWithFormat: @"%.4d%.2d%.2d", str = [NSString stringWithFormat: @"%.4d%.2d%.2d",
[self yearOfCommonEra], (int)[self yearOfCommonEra],
[self monthOfYear], (int)[self monthOfYear],
[self dayOfMonth]]; (int)[self dayOfMonth]];
return str; return str;
} }
@ -109,9 +109,9 @@ static NSString *rfc822Months[] = {@"", @"Jan", @"Feb", @"Mar", @"Apr",
return return
[NSString stringWithFormat: @"%@, %.2d %@ %d %.2d:%.2d:%.2d %+.4d", [NSString stringWithFormat: @"%@, %.2d %@ %d %.2d:%.2d:%.2d %+.4d",
rfc822Days[[self dayOfWeek]], [self dayOfMonth], rfc822Days[[self dayOfWeek]], (int)[self dayOfMonth],
rfc822Months[[self monthOfYear]], [self yearOfCommonEra], rfc822Months[[self monthOfYear]], (int)[self yearOfCommonEra],
[self hourOfDay], [self minuteOfHour], [self secondOfMinute], (int)[self hourOfDay], (int)[self minuteOfHour], (int)[self secondOfMinute],
timeZoneShift]; timeZoneShift];
} }

View File

@ -23,7 +23,7 @@
* Boston, MA 02111-1307, USA. * Boston, MA 02111-1307, USA.
*/ */
#ifndef __OpenBSD__ #if !defined(__OpenBSD__) && !defined(__FreeBSD__)
#include <crypt.h> #include <crypt.h>
#endif #endif

View File

@ -46,7 +46,7 @@
{ {
NSString *newTag; NSString *newTag;
newTag = [NSString stringWithFormat: @"n%d", [namespaces count]]; newTag = [NSString stringWithFormat: @"n%d", (int)[namespaces count]];
[namespaces setObject: newTag forKey: newNS]; [namespaces setObject: newTag forKey: newNS];
return newTag; return newTag;

View File

@ -309,7 +309,7 @@ static int cssEscapingCount;
c == 0xD || c == 0xD ||
(c >= 0x20 && c <= 0xD7FF) || (c >= 0x20 && c <= 0xD7FF) ||
(c >= 0xE000 && c <= 0xFFFD) || (c >= 0xE000 && c <= 0xFFFD) ||
(c >= 0x10000 && c <= 0x10FFFF)) (c >= (unichar)0x10000 && c <= (unichar)0x10FFFF))
{ {
*(start+j) = c; *(start+j) = c;
j++; j++;
@ -373,6 +373,7 @@ static int cssEscapingCount;
- (NSString *) asCSSIdentifier - (NSString *) asCSSIdentifier
{ {
NSCharacterSet *numericSet;
NSMutableString *cssIdentifier; NSMutableString *cssIdentifier;
unichar currentChar; unichar currentChar;
int count, max, idx; int count, max, idx;
@ -381,10 +382,12 @@ static int cssEscapingCount;
[self _setupCSSEscaping]; [self _setupCSSEscaping];
cssIdentifier = [NSMutableString string]; cssIdentifier = [NSMutableString string];
numericSet = [NSCharacterSet decimalDigitCharacterSet];
max = [self length]; max = [self length];
if (max > 0) 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 // A CSS identifier can't start with a digit; we add an underscore
[cssIdentifier appendString: @"_"]; [cssIdentifier appendString: @"_"];
for (count = 0; count < max; count++) for (count = 0; count < max; count++)
@ -415,6 +418,7 @@ static int cssEscapingCount;
- (NSString *) fromCSSIdentifier - (NSString *) fromCSSIdentifier
{ {
NSCharacterSet *numericSet;
NSMutableString *newString; NSMutableString *newString;
NSString *currentString; NSString *currentString;
int count, length, max, idx; int count, length, max, idx;
@ -423,12 +427,14 @@ static int cssEscapingCount;
if (!cssEscapingStrings) if (!cssEscapingStrings)
[self _setupCSSEscaping]; [self _setupCSSEscaping];
numericSet = [NSCharacterSet decimalDigitCharacterSet];
newString = [NSMutableString string]; newString = [NSMutableString string];
max = [self length]; max = [self length];
count = 0; count = 0;
if (max > 0 if (max > 0
&& [self characterAtIndex: 0] == '_' && [self characterAtIndex: 0] == '_'
&& isdigit([self characterAtIndex: 1])) && [numericSet characterIsMember: [self characterAtIndex: 1]])
{ {
/* If the identifier starts with an underscore followed by a digit, /* If the identifier starts with an underscore followed by a digit,
we remove the underscore */ we remove the underscore */

View File

@ -21,6 +21,7 @@
#ifndef SOGOCACHE_H #ifndef SOGOCACHE_H
#define SOGOCACHE_H #define SOGOCACHE_H
#import <Foundation/Foundation.h>
#import <Foundation/NSObject.h> #import <Foundation/NSObject.h>
#include <libmemcached/memcached.h> #include <libmemcached/memcached.h>

View File

@ -40,6 +40,8 @@
- (NSMutableString *) pathForChild: (NSString *) childName; - (NSMutableString *) pathForChild: (NSString *) childName;
- (void) addUserInAcls: (NSString *) user;
- (NSArray *) toOneRelationshipKeys; - (NSArray *) toOneRelationshipKeys;
- (NSArray *) toManyRelationshipKeys; - (NSArray *) toManyRelationshipKeys;

View File

@ -364,7 +364,7 @@ Class SOGoCacheGCSObjectK = Nil;
if (record) if (record)
{ {
if ([[record objectForKey: @"c_type"] intValue] == MAPIFolderCacheObject) if ([[record objectForKey: @"c_type"] intValue] == MAPIFolderCacheObject)
objectClass = isa; objectClass = object_getClass(self);
else else
objectClass = SOGoCacheGCSObjectK; objectClass = SOGoCacheGCSObjectK;

View File

@ -55,6 +55,9 @@ typedef enum {
- (void) reloadIfNeeded; - (void) reloadIfNeeded;
- (void) save; - (void) save;
- (NSException *) destroy;
+ (id) objectWithName: (NSString *) key inContainer: (id) theContainer useCache: (BOOL) useCache;
/* accessors */ /* accessors */
- (NSMutableString *) path; /* full filename */ - (NSMutableString *) path; /* full filename */

View File

@ -87,7 +87,7 @@ static EOAttribute *textColumn = nil;
{ {
tableUrl = nil; tableUrl = nil;
initialized = NO; initialized = NO;
objectType = -1; objectType = (SOGoCacheObjectType) -1;
deleted = NO; deleted = NO;
version = 0; version = 0;
} }
@ -103,11 +103,22 @@ static EOAttribute *textColumn = nil;
} }
+ (id) objectWithName: (NSString *) key inContainer: (id) theContainer + (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; SOGoCache *cache;
id o; id o;
cache = [SOGoCache sharedCache]; cache = [SOGoCache sharedCache];
if (!useCache)
[cache unregisterObjectWithName: key inContainer: theContainer];
o = [cache objectNamed: key inContainer: theContainer]; o = [cache objectNamed: key inContainer: theContainer];
if (!o) if (!o)
@ -383,7 +394,7 @@ static EOAttribute *textColumn = nil;
@"SELECT * FROM %@ WHERE c_path = %@", @"SELECT * FROM %@ WHERE c_path = %@",
tableName, pathValue]; tableName, pathValue];
if (startVersion > -1) if (startVersion > -1)
[sql appendFormat: @" AND c_version > %d", startVersion]; [sql appendFormat: @" AND c_version > %d", (int)startVersion];
/* execution */ /* execution */
records = [self performSQLQuery: sql]; records = [self performSQLQuery: sql];
@ -411,15 +422,13 @@ static EOAttribute *textColumn = nil;
tableName = [self tableName]; tableName = [self tableName];
adaptor = [self tableChannelAdaptor]; adaptor = [self tableChannelAdaptor];
pathValue = [adaptor formatValue: [NSString stringWithFormat: @"/%@", deviceId]
forAttribute: textColumn];
/* query */ /* query */
sql = [NSMutableString stringWithFormat: sql = [NSMutableString stringWithFormat:
@"SELECT * FROM %@ WHERE c_type = %d AND c_deleted <> 1", tableName, objectType]; @"SELECT * FROM %@ WHERE c_type = %d AND c_deleted <> 1", tableName, objectType];
if (startVersion > -1) if (startVersion > -1)
[sql appendFormat: @" AND c_version > %d", startVersion]; [sql appendFormat: @" AND c_version > %d", (int)startVersion];
if (deviceId) { if (deviceId) {
pathValue = [adaptor formatValue: [NSString stringWithFormat: @"/%@%", deviceId] pathValue = [adaptor formatValue: [NSString stringWithFormat: @"/%@%", deviceId]
@ -546,7 +555,7 @@ static EOAttribute *textColumn = nil;
lastModifiedValue = (NSInteger) [lastModified timeIntervalSince1970]; lastModifiedValue = (NSInteger) [lastModified timeIntervalSince1970];
if (objectType == -1) if (objectType == (SOGoCacheObjectType) -1)
[NSException raise: @"SOGoCacheIOException" [NSException raise: @"SOGoCacheIOException"
format: @"object type has not been set for object '%@'", format: @"object type has not been set for object '%@'",
self]; self];
@ -576,7 +585,7 @@ static EOAttribute *textColumn = nil;
@")"), @")"),
tableName, tableName,
pathValue, parentPathValue, objectType, pathValue, parentPathValue, objectType,
creationDateValue, lastModifiedValue, (int)creationDateValue, (int)lastModifiedValue,
propsValue]; propsValue];
isNew = NO; isNew = NO;
} }
@ -590,7 +599,7 @@ static EOAttribute *textColumn = nil;
@" c_version = %d, c_content = %@" @" c_version = %d, c_content = %@"
@" WHERE c_path = %@"), @" WHERE c_path = %@"),
tableName, tableName,
lastModifiedValue, deletedValue, version, propsValue, (int)lastModifiedValue, (int)deletedValue, (int)version, propsValue,
pathValue]; pathValue];
} }

View File

@ -42,8 +42,6 @@
- (NSCalendarDate *) creationDate; - (NSCalendarDate *) creationDate;
- (NSCalendarDate *) lastModified; - (NSCalendarDate *) lastModified;
- (NSException *) destroy;
@end @end
#endif /* SOGOCACHEOBJECT_H */ #endif /* SOGOCACHEOBJECT_H */

Some files were not shown because too many files have changed in this diff Show More