Revert merge from inverse

https://github.com/Zentyal/sogo/pull/150

Because the login on web with the use of outlook is broken after
including the DomainLessLogin feature
This commit is contained in:
Jesús García Sáez 2015-07-28 12:37:33 +02:00
parent 72cbd9a45f
commit 3dc8cc78ea
41 changed files with 284 additions and 767 deletions

View file

@ -66,33 +66,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// //
- (NSString *) activeSyncRepresentationInContext: (WOContext *) context - (NSString *) activeSyncRepresentationInContext: (WOContext *) context
{ {
NSString *tmp, *s; return [[self stringByEncodingBase64] stringByReplacingString: @"\n" withString: @""];
unichar *buf, *start, c;
int len, i, j;
tmp = [self stringByEncodingBase64] ;
len = [tmp length];
start = buf = (unichar *)malloc(len*sizeof(unichar));
[tmp getCharacters: buf range: NSMakeRange(0, len)];
for (i = 0, j = 0; i < len; i++)
{
c = *buf;
if (!(c == 0xA))
{
*(start+j) = c;
j++;
}
buf++;
}
s = [[NSString alloc] initWithCharactersNoCopy: start length: j freeWhenDone: YES];
return AUTORELEASE(s);
} }
- (NSData *) wbxml2xml - (NSData *) wbxml2xml

View file

@ -46,99 +46,6 @@ static NSArray *easCommandParameters = nil;
@implementation NSString (ActiveSync) @implementation NSString (ActiveSync)
//
// This is a copy from NSString+XMLEscaping.m from SOPE.
// The difference here is that we use wchar_t instead of unichar.
// This is needed to get the rigth numeric character reference.
// e.g. SMILING FACE WITH OPEN MOUTH
// ok: wchar_t -> &#128515; wrong: unichar -> &#55357; &#56835;
//
// We avoir naming it like the one in SOPE since if the ActiveSync
// bundle is loaded, it'll overwrite the one provided by SOPE.
//
- (NSString *) _stringByEscapingXMLStringUsingCharacters {
register unsigned i, len, j;
register wchar_t *buf;
const wchar_t *chars;
unsigned escapeCount;
if ([self length] == 0) return @"";
NSData *data = [self dataUsingEncoding:NSUTF32StringEncoding];
chars = [data bytes];
len = [data length]/4;
/* check for characters to escape ... */
for (i = 0, escapeCount = 0; i < len; i++) {
switch (chars[i]) {
case '&': case '"': case '<': case '>': case '\r':
escapeCount++;
break;
default:
if (chars[i] > 127)
escapeCount++;
break;
}
}
if (escapeCount == 0 ) {
/* nothing to escape ... */
return [[self copy] autorelease];
}
buf = calloc((len + 5) + (escapeCount * 16), sizeof(wchar_t));
for (i = 0, j = 0; i < len; i++) {
switch (chars[i]) {
/* escape special chars */
case '\r':
buf[j] = '&'; j++; buf[j] = '#'; j++; buf[j] = '1'; j++;
buf[j] = '3'; j++; buf[j] = ';'; j++;
break;
case '&':
buf[j] = '&'; j++; buf[j] = 'a'; j++; buf[j] = 'm'; j++;
buf[j] = 'p'; j++; buf[j] = ';'; j++;
break;
case '"':
buf[j] = '&'; j++; buf[j] = 'q'; j++; buf[j] = 'u'; j++;
buf[j] = 'o'; j++; buf[j] = 't'; j++; buf[j] = ';'; j++;
break;
case '<':
buf[j] = '&'; j++; buf[j] = 'l'; j++; buf[j] = 't'; j++;
buf[j] = ';'; j++;
break;
case '>':
buf[j] = '&'; j++; buf[j] = 'g'; j++; buf[j] = 't'; j++;
buf[j] = ';'; j++;
break;
default:
/* escape big chars */
if (chars[i] > 127) {
unsigned char nbuf[32];
unsigned int k;
sprintf((char *)nbuf, "&#%i;", (int)chars[i]);
for (k = 0; nbuf[k] != '\0'; k++) {
buf[j] = nbuf[k];
j++;
}
}
else if (chars[i] == 0x9 || chars[i] == 0xA || chars[i] == 0xD || chars[i] >= 0x20) { // ignore any unsupported control character
/* nothing to escape */
buf[j] = chars[i];
j++;
}
break;
}
}
self = [[NSString alloc] initWithBytesNoCopy: buf
length: (j*sizeof(wchar_t))
encoding: NSUTF32StringEncoding
freeWhenDone: YES];
return [self autorelease];
}
- (NSString *) sanitizedServerIdWithType: (SOGoMicrosoftActiveSyncFolderType) folderType - (NSString *) sanitizedServerIdWithType: (SOGoMicrosoftActiveSyncFolderType) folderType
{ {
if (folderType == ActiveSyncEventFolder) if (folderType == ActiveSyncEventFolder)
@ -158,7 +65,11 @@ static NSArray *easCommandParameters = nil;
- (NSString *) activeSyncRepresentationInContext: (WOContext *) context - (NSString *) activeSyncRepresentationInContext: (WOContext *) context
{ {
return [self _stringByEscapingXMLStringUsingCharacters]; NSString *s;
s = [self safeString];
return [s stringByEscapingHTMLString];
} }
- (int) activeSyncFolderType - (int) activeSyncFolderType

View file

@ -453,27 +453,16 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
withType: (SOGoMicrosoftActiveSyncFolderType) theFolderType withType: (SOGoMicrosoftActiveSyncFolderType) theFolderType
inBuffer: (NSMutableString *) theBuffer inBuffer: (NSMutableString *) theBuffer
{ {
id aDelete, sogoObject, value;
NSArray *deletions; NSArray *deletions;
NSString *serverId; NSString *serverId;
BOOL deletesAsMoves, useTrash; id aDelete, sogoObject;
int i; int i;
deletions = (id)[theDocumentElement getElementsByTagName: @"Delete"]; deletions = (id)[theDocumentElement getElementsByTagName: @"Delete"];
if ([deletions count]) if ([deletions count])
{ {
// From the documention, if DeletesAsMoves is missing, we must assume it's a YES.
// See https://msdn.microsoft.com/en-us/library/gg675480(v=exchg.80).aspx for all details.
value = [theDocumentElement getElementsByTagName: @"DeletesAsMoves"];
deletesAsMoves = YES;
useTrash = YES;
if ([value count] && [[[value lastObject] textValue] length])
deletesAsMoves = [[[value lastObject] textValue] boolValue];
for (i = 0; i < [deletions count]; i++) for (i = 0; i < [deletions count]; i++)
{ {
aDelete = [deletions objectAtIndex: i]; aDelete = [deletions objectAtIndex: i];
@ -485,13 +474,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
acquire: NO]; acquire: NO];
if (![sogoObject isKindOfClass: [NSException class]]) if (![sogoObject isKindOfClass: [NSException class]])
{ [sogoObject delete];
// FIXME: handle errors here
if (deletesAsMoves)
[(SOGoMailFolder *)[sogoObject container] deleteUIDs: [NSArray arrayWithObjects: serverId, nil] useTrashFolder: &useTrash inContext: context];
else
[sogoObject delete];
}
[theBuffer appendString: @"<Delete>"]; [theBuffer appendString: @"<Delete>"];
[theBuffer appendFormat: @"<ServerId>%@</ServerId>", serverId]; [theBuffer appendFormat: @"<ServerId>%@</ServerId>", serverId];

View file

@ -453,7 +453,7 @@ static BOOL debugOn = NO;
[s appendString: @"<FolderCreate xmlns=\"FolderHierarchy:\">"]; [s appendString: @"<FolderCreate xmlns=\"FolderHierarchy:\">"];
[s appendFormat: @"<Status>%d</Status>", 1]; [s appendFormat: @"<Status>%d</Status>", 1];
[s appendFormat: @"<SyncKey>%@</SyncKey>", syncKey]; [s appendFormat: @"<SyncKey>%@</SyncKey>", syncKey];
[s appendFormat: @"<ServerId>%@</ServerId>", [nameInContainer stringByEscapingURL]]; [s appendFormat: @"<ServerId>%@</ServerId>", nameInContainer];
[s appendString: @"</FolderCreate>"]; [s appendString: @"</FolderCreate>"];
d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml]; d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
@ -1084,8 +1084,6 @@ static BOOL debugOn = NO;
SOGoMailAccounts *accountsFolder; SOGoMailAccounts *accountsFolder;
SOGoUserFolder *userFolder; SOGoUserFolder *userFolder;
SOGoMailObject *mailObject; SOGoMailObject *mailObject;
NSArray *partKeys;
int p;
NSRange r1, r2; NSRange r1, r2;
@ -1105,14 +1103,7 @@ static BOOL debugOn = NO;
acquire: NO]; acquire: NO];
mailObject = [currentCollection lookupName: messageName inContext: context acquire: NO]; mailObject = [currentCollection lookupName: messageName inContext: context acquire: NO];
currentBodyPart = [mailObject lookupImap4BodyPartKey: pathToPart inContext: context];
partKeys = [pathToPart componentsSeparatedByString: @"."];
currentBodyPart = [mailObject lookupImap4BodyPartKey: [partKeys objectAtIndex:0] inContext: context];
for (p = 1; p < [partKeys count]; p++)
{
currentBodyPart = [currentBodyPart lookupImap4BodyPartKey: [partKeys objectAtIndex:p] inContext: context];
}
[theResponse setHeader: [NSString stringWithFormat: @"%@/%@", [[currentBodyPart partInfo] objectForKey: @"type"], [[currentBodyPart partInfo] objectForKey: @"subtype"]] [theResponse setHeader: [NSString stringWithFormat: @"%@/%@", [[currentBodyPart partInfo] objectForKey: @"type"], [[currentBodyPart partInfo] objectForKey: @"subtype"]]
forKey: @"Content-Type"]; forKey: @"Content-Type"];
@ -1167,11 +1158,11 @@ static BOOL debugOn = NO;
{ {
collectionId = [[(id)[[allCollections objectAtIndex: j] getElementsByTagName: @"CollectionId"] lastObject] textValue]; collectionId = [[(id)[[allCollections objectAtIndex: j] getElementsByTagName: @"CollectionId"] lastObject] textValue];
realCollectionId = [collectionId realCollectionIdWithFolderType: &folderType]; realCollectionId = [collectionId realCollectionIdWithFolderType: &folderType];
if (folderType == ActiveSyncMailFolder) if (folderType == ActiveSyncMailFolder)
nameInCache = [NSString stringWithFormat: @"folder%@", realCollectionId]; nameInCache = [NSString stringWithFormat: @"folder%@", realCollectionId];
else else
nameInCache = collectionId; nameInCache = collectionId;
realCollectionId = [self globallyUniqueIDToIMAPFolderName: realCollectionId type: folderType]; realCollectionId = [self globallyUniqueIDToIMAPFolderName: realCollectionId type: folderType];
@ -1243,11 +1234,10 @@ 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;
NSMutableString *s; NSMutableString *s;
NSArray *fetchRequests; NSArray *fetchRequests;
id aFetch; id aFetch;
NSData *d;
int i; int i;
SOGoMicrosoftActiveSyncFolderType folderType; SOGoMicrosoftActiveSyncFolderType folderType;
@ -1257,6 +1247,8 @@ static BOOL debugOn = NO;
[s appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"]; [s appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
[s appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"]; [s appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
[s appendString: @"<ItemOperations xmlns=\"ItemOperations:\">"]; [s appendString: @"<ItemOperations xmlns=\"ItemOperations:\">"];
[s appendString: @"<Status>1</Status>"];
[s appendString: @"<Response>"];
fetchRequests = (id)[theDocumentElement getElementsByTagName: @"Fetch"]; fetchRequests = (id)[theDocumentElement getElementsByTagName: @"Fetch"];
@ -1264,25 +1256,17 @@ static BOOL debugOn = NO;
{ {
NSMutableData *bytes, *parts; NSMutableData *bytes, *parts;
NSMutableArray *partLength; NSMutableArray *partLength;
NSData *d;
bytes = [NSMutableData data]; bytes = [NSMutableData data];
parts = [NSMutableData data]; parts = [NSMutableData data];
partLength = [NSMutableArray array]; partLength = [NSMutableArray array];
[s appendString: @"<Status>1</Status>"];
[s appendString: @"<Response>"];
for (i = 0; i < [fetchRequests count]; i++) for (i = 0; i < [fetchRequests count]; i++)
{ {
aFetch = [fetchRequests objectAtIndex: i]; aFetch = [fetchRequests objectAtIndex: i];
fileReference = [[[(id)[aFetch getElementsByTagName: @"FileReference"] lastObject] textValue] stringByUnescapingURL]; fileReference = [[[(id)[aFetch getElementsByTagName: @"FileReference"] lastObject] textValue] stringByUnescapingURL];
collectionId = [[(id)[theDocumentElement getElementsByTagName: @"CollectionId"] lastObject] textValue]; realCollectionId = [fileReference realCollectionIdWithFolderType: &folderType];
// its either a itemOperation to fetch an attachment or an email
if ([fileReference length])
realCollectionId = [fileReference realCollectionIdWithFolderType: &folderType];
else
realCollectionId = [collectionId realCollectionIdWithFolderType: &folderType];
if (folderType == ActiveSyncMailFolder) if (folderType == ActiveSyncMailFolder)
{ {
@ -1292,80 +1276,43 @@ static BOOL debugOn = NO;
SOGoUserFolder *userFolder; SOGoUserFolder *userFolder;
SOGoMailObject *mailObject; SOGoMailObject *mailObject;
if ([fileReference length]) NSRange r1, r2;
{
// fetch attachment
NSRange r1, r2;
NSArray *partKeys;
int p;
r1 = [realCollectionId rangeOfString: @"/"]; r1 = [realCollectionId rangeOfString: @"/"];
r2 = [realCollectionId rangeOfString: @"/" options: 0 range: NSMakeRange(NSMaxRange(r1)+1, [realCollectionId length]-NSMaxRange(r1)-1)]; r2 = [realCollectionId rangeOfString: @"/" options: 0 range: NSMakeRange(NSMaxRange(r1)+1, [realCollectionId length]-NSMaxRange(r1)-1)];
folderName = [realCollectionId substringToIndex: r1.location]; folderName = [realCollectionId substringToIndex: r1.location];
messageName = [realCollectionId substringWithRange: NSMakeRange(NSMaxRange(r1), r2.location-r1.location-1)]; messageName = [realCollectionId substringWithRange: NSMakeRange(NSMaxRange(r1), r2.location-r1.location-1)];
pathToPart = [realCollectionId substringFromIndex: r2.location+1]; pathToPart = [realCollectionId substringFromIndex: r2.location+1];
userFolder = [[context activeUser] homeFolderInContext: context]; userFolder = [[context activeUser] homeFolderInContext: context];
accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO]; accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
currentFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO]; currentFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO];
currentCollection = [currentFolder lookupName: [NSString stringWithFormat: @"folder%@", folderName] currentCollection = [currentFolder lookupName: [NSString stringWithFormat: @"folder%@", folderName]
inContext: context inContext: context
acquire: NO]; acquire: NO];
mailObject = [currentCollection lookupName: messageName inContext: context acquire: NO]; mailObject = [currentCollection lookupName: messageName inContext: context acquire: NO];
currentBodyPart = [mailObject lookupImap4BodyPartKey: pathToPart inContext: context];
partKeys = [pathToPart componentsSeparatedByString: @"."]; [s appendString: @"<Fetch>"];
[s appendString: @"<Status>1</Status>"];
[s appendFormat: @"<FileReference xmlns=\"AirSyncBase:\">%@</FileReference>", [fileReference stringByEscapingURL]];
[s appendString: @"<Properties>"];
currentBodyPart = [mailObject lookupImap4BodyPartKey: [partKeys objectAtIndex:0] inContext: context]; [s appendFormat: @"<ContentType xmlns=\"AirSyncBase:\">%@/%@</ContentType>", [[currentBodyPart partInfo] objectForKey: @"type"], [[currentBodyPart partInfo] objectForKey: @"subtype"]];
for (p = 1; p < [partKeys count]; p++)
{
currentBodyPart = [currentBodyPart lookupImap4BodyPartKey: [partKeys objectAtIndex:p] inContext: context];
}
[s appendString: @"<Fetch>"];
[s appendString: @"<Status>1</Status>"];
[s appendFormat: @"<FileReference xmlns=\"AirSyncBase:\">%@</FileReference>", [fileReference stringByEscapingURL]];
[s appendString: @"<Properties>"];
[s appendFormat: @"<ContentType xmlns=\"AirSyncBase:\">%@/%@</ContentType>", [[currentBodyPart partInfo] objectForKey: @"type"], [[currentBodyPart partInfo] objectForKey: @"subtype"]]; if ([[theResponse headerForKey: @"Content-Type"] isEqualToString:@"application/vnd.ms-sync.multipart"])
{
if ([[theResponse headerForKey: @"Content-Type"] isEqualToString:@"application/vnd.ms-sync.multipart"]) [s appendFormat: @"<Part>%d</Part>", i+1];
{ [partLength addObject: [NSNumber numberWithInteger: [[currentBodyPart fetchBLOB] length]]];
NSData *d; [parts appendData:[currentBodyPart fetchBLOB]];
d = [currentBodyPart fetchBLOB];
[s appendFormat: @"<Part>%d</Part>", i+1];
[partLength addObject: [NSNumber numberWithInteger: [d length]]];
[parts appendData: d];
}
else
{
NSString *a;
a = [[currentBodyPart fetchBLOB] activeSyncRepresentationInContext: context];
[s appendFormat: @"<Range>0-%d</Range>", [a length]-1];
[s appendFormat: @"<Data>%@</Data>", a];
}
} }
else else
{ {
// fetch mail [s appendFormat: @"<Range>0-%d</Range>", [[[currentBodyPart fetchBLOB] activeSyncRepresentationInContext: context] length]-1];
realCollectionId = [self globallyUniqueIDToIMAPFolderName: realCollectionId type: folderType]; [s appendFormat: @"<Data>%@</Data>", [[currentBodyPart fetchBLOB] activeSyncRepresentationInContext: context]];
serverId = [[(id)[theDocumentElement getElementsByTagName: @"ServerId"] lastObject] textValue];
bodyPreferenceType = [[(id)[[(id)[theDocumentElement getElementsByTagName: @"BodyPreference"] lastObject] getElementsByTagName: @"Type"] lastObject] textValue];
[context setObject: bodyPreferenceType forKey: @"BodyPreferenceType"];
currentCollection = [self collectionFromId: realCollectionId type: folderType];
mailObject = [currentCollection lookupName: serverId inContext: context acquire: NO];
[s appendString: @"<Fetch>"];
[s appendString: @"<Status>1</Status>"];
[s appendFormat: @"<CollectionId xmlns=\"AirSyncBase:\">%@</CollectionId>", collectionId];
[s appendFormat: @"<ServerId xmlns=\"AirSyncBase:\">%@</ServerId>", serverId];
[s appendString: @"<Properties>"];
[s appendString: [mailObject activeSyncRepresentationInContext: context]];
} }
[s appendString: @"</Properties>"]; [s appendString: @"</Properties>"];
@ -1419,62 +1366,6 @@ static BOOL debugOn = NO;
{ {
[theResponse setContent: d]; [theResponse setContent: d];
} }
}
else if ([theDocumentElement getElementsByTagName: @"EmptyFolderContents"])
{
NGImap4Connection *connection;
NSEnumerator *subfolders;
NSException *error;
NSURL *currentURL;
id co;
collectionId = [[(id)[theDocumentElement getElementsByTagName: @"CollectionId"] lastObject] textValue];
realCollectionId = [collectionId realCollectionIdWithFolderType: &folderType];
realCollectionId = [self globallyUniqueIDToIMAPFolderName: realCollectionId type: folderType];
if (folderType == ActiveSyncMailFolder)
{
co = [self collectionFromId: realCollectionId type: folderType];
error = [co addFlagsToAllMessages: @"deleted"];
if (!error)
error = [(SOGoMailFolder *)co expunge];
if (!error)
{
[co flushMailCaches];
if ([theDocumentElement getElementsByTagName: @"DeleteSubFolders"])
{
// Delete sub-folders
connection = [co imap4Connection];
subfolders = [[co allFolderURLs] objectEnumerator];
while ((currentURL = [subfolders nextObject]))
{
[[connection client] unsubscribe: [currentURL path]];
[connection deleteMailboxAtURL: currentURL];
}
}
[s appendString: @"<Status>1</Status>"];
[s appendString: @"</ItemOperations>"];
}
if (error)
{
[s appendString: @"<Status>3</Status>"];
[s appendString: @"</ItemOperations>"];
}
d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
[theResponse setContent: d];
}
else
{
[theResponse setStatus: 500];
return;
}
} }
} }

View file

@ -381,6 +381,11 @@ struct GlobalObjectId {
if (s) if (s)
{ {
// We sanitize the content immediately, in case we have non-UNICODE safe
// characters that would be re-encoded later in HTML entities and thus,
// ignore afterwards.
s = [s safeString];
body = [s dataUsingEncoding: NSUTF8StringEncoding]; body = [s dataUsingEncoding: NSUTF8StringEncoding];
} }
@ -861,41 +866,10 @@ struct GlobalObjectId {
if (d) if (d)
{ {
NSMutableData *sanitizedData;
NSString *content; NSString *content;
int len, truncated; int len, truncated;
// Outlook fails to decode quoted-printable (see #3082) if lines are not termined by CRLF. content = [[NSString alloc] initWithData: d encoding: NSUTF8StringEncoding];
const char *bytes;
char *mbytes;
int mlen;
len = [d length];
mlen = 0;
sanitizedData = [NSMutableData dataWithLength: len*2];
bytes = [d bytes];
mbytes = [sanitizedData mutableBytes];
while (len > 0)
{
if (*bytes == '\n' && *(bytes-1) != '\r' && mlen > 0)
{
*mbytes = '\r';
mbytes++;
mlen++;
}
*mbytes = *bytes;
mbytes++; bytes++;
len--;
mlen++;
}
[sanitizedData setLength: mlen];
content = [[NSString alloc] initWithData: sanitizedData encoding: NSUTF8StringEncoding];
// FIXME: This is a hack. We should normally avoid doing this as we might get // FIXME: This is a hack. We should normally avoid doing this as we might get
// broken encodings. We should rather tell that the data was truncated and expect // broken encodings. We should rather tell that the data was truncated and expect
@ -905,15 +879,15 @@ struct GlobalObjectId {
// for an "interesting" discussion around this. // for an "interesting" discussion around this.
// //
if (!content) if (!content)
content = [[NSString alloc] initWithData: sanitizedData encoding: NSISOLatin1StringEncoding]; content = [[NSString alloc] initWithData: d encoding: NSISOLatin1StringEncoding];
AUTORELEASE(content); AUTORELEASE(content);
content = [content activeSyncRepresentationInContext: context]; content = [content activeSyncRepresentationInContext: context];
truncated = 0; truncated = 0;
len = [content length]; len = [content length];
if ([[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"2.5"]) if ([[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"2.5"])
{ {
[s appendFormat: @"<Body xmlns=\"Email:\">%@</Body>", content]; [s appendFormat: @"<Body xmlns=\"Email:\">%@</Body>", content];

View file

@ -506,14 +506,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[self setOrganizer: person]; [self setOrganizer: person];
} }
//
// iOS is plain stupid here. It seends event invitations with no Organizer.
// We check this corner-case and if MeetingStatus == 1 (see http://msdn.microsoft.com/en-us/library/ee219342(v=exchg.80).aspx or details)
// and there's no organizer, we fake one.
//
if ((o = [theValues objectForKey: @"MeetingStatus"])) if ((o = [theValues objectForKey: @"MeetingStatus"]))
{ {
// if ([o intValue] == 1 && ![theValues objectForKey: @"Organizer_Email"])
// iOS is plain stupid here. It seends event invitations with no Organizer.
// We check this corner-case and if MeetingStatus == 1 (see http://msdn.microsoft.com/en-us/library/ee219342(v=exchg.80).aspx or details)
// and there's no organizer, we fake one.
//
if ([o intValue] == 1 && ![theValues objectForKey: @"Organizer_Email"] && ![[[self organizer] rfc822Email] length])
{ {
iCalPerson *person; iCalPerson *person;
@ -523,20 +523,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[person setPartStat: @"ACCEPTED"]; [person setPartStat: @"ACCEPTED"];
[self setOrganizer: person]; [self setOrganizer: person];
} }
//
// When MeetingResponse fails Outlook still sends a new calendar entry with MeetingStatus=3.
// Use the organizer from the request if the event has no organizer.
//
if ([o intValue] == 3 && [theValues objectForKey: @"Organizer_Email"] && ![[[self organizer] rfc822Email] length])
{
iCalPerson *person;
person = [iCalPerson elementWithTag: @"organizer"];
[person setEmail: [theValues objectForKey: @"Organizer_Email"]];
[person setCn: [theValues objectForKey: @"Organizer_Name"]];
[person setPartStat: @"ACCEPTED"];
[self setOrganizer: person];
}
} }

View file

@ -1920,11 +1920,10 @@ events. This parameter is an array of arbitrary strings.
Defaults to a list that depends on the language. Defaults to a list that depends on the language.
|U |SOGoCalendarCategoriesColors |U |SOGoCalendarDefaultCategoryColor
|Parameter used to define the colour of categories. This parameter |Parameter used to define the default colour of categories.
is a dictionary of category name/color.
Defaults to `#F0F0F0` for all categories when unset. Defaults to `#F0F0F0` when unset.
|U |SOGoCalendarEventsDefaultClassification |U |SOGoCalendarEventsDefaultClassification
|Parameter used to defined the default classification for new events. |Parameter used to defined the default classification for new events.
@ -2137,8 +2136,7 @@ Multi-domains Configuration
~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you want your installation to isolate two groups of users, you must If you want your installation to isolate two groups of users, you must
define a distinct authentication source for each _domain_. Your domain keys define a distinct authentication source for each _domain_. Following is
must have the same value as your email domain you want to add. Following is
the same configuration that now includes two domains (acme.com and the same configuration that now includes two domains (acme.com and
coyote.com): coyote.com):
@ -2146,7 +2144,7 @@ coyote.com):
{ {
... ...
domains = { domains = {
acme.com = { acme = {
SOGoMailDomain = acme.com; SOGoMailDomain = acme.com;
SOGoDraftsFolderName = Drafts; SOGoDraftsFolderName = Drafts;
SOGoUserSources = ( SOGoUserSources = (
@ -2167,7 +2165,7 @@ coyote.com):
} }
); );
}; };
coyote.com = { coyote = {
SOGoMailDomain = coyote.com; SOGoMailDomain = coyote.com;
SOGoIMAPServer = imap.coyote.com; SOGoIMAPServer = imap.coyote.com;
SOGoUserSources = ( SOGoUserSources = (
@ -2198,7 +2196,7 @@ domains.
[cols="3,47,50a"] [cols="3,47,50a"]
|======================================================================= |=======================================================================
|S |SOGoEnableDomainBasedUID |S |SOGoEnableDomainBasedUID
|Parameter used to enable user identification by domain. Users will be |Parameter used to activate user identification by domain. Users will be
able (without being required) to login using the form `username@domain`, able (without being required) to login using the form `username@domain`,
meaning that values of _UIDFieldName_ no longer have to be unique among meaning that values of _UIDFieldName_ no longer have to be unique among
all domains but only within the same domain. Internally, users will all domains but only within the same domain. Internally, users will
@ -2713,11 +2711,6 @@ current version of SOGo from the previous release.
[cols="100a"] [cols="100a"]
|======================================================================= |=======================================================================
h|2.3.1
|The SOGoCalendarDefaultCategoryColor default has been removed. If you
want to customize the color of calendar categories, use the
SOGoCalendarCategories and SOGoCalendarCategoriesColors defaults.
h|2.3.0 h|2.3.0
|Run the shell script `sql-update-2.2.17_to_2.3.0.sh` or |Run the shell script `sql-update-2.2.17_to_2.3.0.sh` or
`sql-update-2.2.17_to_2.3.0-mysql.sh` (if you use MySQL). `sql-update-2.2.17_to_2.3.0-mysql.sh` (if you use MySQL).
@ -2725,9 +2718,6 @@ h|2.3.0
This will grow the "participant states" field of calendar quick tables to a larger This will grow the "participant states" field of calendar quick tables to a larger
size and add the the "c_description" column to calendar quick tables. size and add the the "c_description" column to calendar quick tables.
Moreover, if you are using a multi-domain configuration, make sure the keys for
your domains match the email domains you have defined.
h|2.2.8 h|2.2.8
|The configuration configuration parameters were renamed: |The configuration configuration parameters were renamed:

20
NEWS
View file

@ -1,23 +1,3 @@
2.3.1 (2015-06-XX)
------------------
Enhancements
- improved EAS speed, especially when fetching big attachments
- now always enforce the organizer's default identity in appointments
- improved the handling of default calendar categories/colors (#3200)
- added support for DeletesAsMoves over EAS
Bug fixes
- EAS's GetItemEstimate/ItemOperations now support fetching mails and empty folders
- fixed some rare cornercases in multidomain configurations
- properly escape folder after creation using EAS (#3237)
- fixed potential organizer highjacking when using EAS (#3131)
- properly support big characters in EAS and fix encoding QP EAS error for Outlook (#3082)
- properly encode id of DOM elements in Address Book module (#3239, #3245)
- fixed multi-domain support for sogo-tool backup/restore (#2600)
- fixed data ordering in events list of Calendar module (#3261)
- fixed data ordering in tasks list of Calendar module (#3267)
2.3.0 (2015-06-01) 2.3.0 (2015-06-01)
------------------- -------------------

View file

@ -1,2 +0,0 @@
# SOGo needs directory in /var/run
d /var/run/sogo 0755 sogo sogo

View file

@ -49,7 +49,6 @@ function adjustSchema() {
echo "This script will ask for the sql password twice" >&2 echo "This script will ask for the sql password twice" >&2
echo "Converting c_partstates from VARCHAR(255) to mediumtext in calendar quick tables" >&2 echo "Converting c_partstates from VARCHAR(255) to mediumtext in calendar quick tables" >&2
echo "Adding c_description column as mediumtext in calendar quick tables" >&2
tables=`mysql -p -s -u $username -h $hostname $database -e "select SUBSTRING_INDEX(c_quick_location, '/', -1) from $indextable where c_path3 = 'Calendar';"` tables=`mysql -p -s -u $username -h $hostname $database -e "select SUBSTRING_INDEX(c_quick_location, '/', -1) from $indextable where c_path3 = 'Calendar';"`
for table in $tables; for table in $tables;

View file

@ -45,8 +45,7 @@ function adjustSchema() {
IFS="$oldIFS" IFS="$oldIFS"
} }
echo "Converting c_partstates from VARCHAR(255) to mediumtext in calendar quick tables" >&2 echo "Converting c_cycleinfo from VARCHAR(255) to TEXT in calendar quick tables" >&2
echo "Adding c_description column as mediumtext in calendar quick tables" >&2
tables=`psql -t -U $username -h $hostname $database -c "select split_part(c_quick_location, '/', 5) from $indextable where c_path3 = 'Calendar';"` tables=`psql -t -U $username -h $hostname $database -c "select split_part(c_quick_location, '/', 5) from $indextable where c_path3 = 'Calendar';"`
for table in $tables; for table in $tables;

View file

@ -3157,8 +3157,6 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
inContainer: self]; inContainer: self];
[object setIsNew: YES]; [object setIsNew: YES];
content = [NSMutableString stringWithString: @"BEGIN:VCALENDAR\n"]; content = [NSMutableString stringWithString: @"BEGIN:VCALENDAR\n"];
[content appendFormat: @"PRODID:-//Inverse inc./SOGo %@//EN\n", SOGoVersion];
if (timezone) if (timezone)
[content appendFormat: @"%@\n", [timezone versitString]]; [content appendFormat: @"%@\n", [timezone versitString]];
[content appendFormat: @"%@\nEND:VCALENDAR", [event versitString]]; [content appendFormat: @"%@\nEND:VCALENDAR", [event versitString]];

View file

@ -1820,28 +1820,9 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
[self warnWithFormat: @"Invalid event: no end date; setting duration to %@", [event duration]]; [self warnWithFormat: @"Invalid event: no end date; setting duration to %@", [event duration]];
} }
if ([event organizer]) if ([event organizer] && ![[[event organizer] cn] length])
{ {
NSString *uid; [[event organizer] setCn: [[event organizer] rfc822Email]];
if (![[[event organizer] cn] length])
{
[[event organizer] setCn: [[event organizer] rfc822Email]];
}
// We now make sure that the organizer, if managed by SOGo, is using
// its default email when creating events and inviting attendees.
uid = [[SOGoUserManager sharedUserManager] getUIDForEmail: [[event organizer] rfc822Email]];
if (uid)
{
NSDictionary *defaultIdentity;
SOGoUser *organizer;
organizer = [SOGoUser userWithLogin: uid];
defaultIdentity = [organizer defaultIdentity];
[[event organizer] setCn: [defaultIdentity objectForKey: @"fullName"]];
[[event organizer] setEmail: [defaultIdentity objectForKey: @"email"]];
}
} }
} }
} }

View file

@ -1,6 +1,6 @@
/* iCalAlarm+SOGo.h - this file is part of SOGo /* iCalAlarm+SOGo.h - this file is part of SOGo
* *
* Copyright (C) 2015 Inverse inc. * Copyright (C) 2014 Inverse inc.
* *
* 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
@ -20,11 +20,11 @@
#import <NGCards/iCalAlarm.h> #import <NGCards/iCalAlarm.h>
@class iCalEntityObject; @class iCalRepeatableEntityObject;
@interface iCalAlarm (SOGoExtensions) @interface iCalAlarm (SOGoExtensions)
+ (id) alarmForEvent: (iCalEntityObject *) theEntity + (id) alarmForEvent: (iCalRepeatableEntityObject *) theEntity
owner: (NSString *) theOwner owner: (NSString *) theOwner
action: (NSString *) reminderAction action: (NSString *) reminderAction
unit: (NSString *) reminderUnit unit: (NSString *) reminderUnit

View file

@ -1,6 +1,6 @@
/* iCalAlarm+SOGo.m - this file is part of SOGo /* iCalAlarm+SOGo.m - this file is part of SOGo
* *
* Copyright (C) 2015 Inverse inc. * Copyright (C) 2014 Inverse inc.
* *
* 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
@ -28,7 +28,6 @@
#import <NGCards/iCalPerson.h> #import <NGCards/iCalPerson.h>
#import <NGCards/iCalTrigger.h> #import <NGCards/iCalTrigger.h>
#import <NGCards/iCalEntityObject.h>
@implementation iCalAlarm (SOGoExtensions) @implementation iCalAlarm (SOGoExtensions)
@ -66,7 +65,7 @@
[alarm addChild: aAttendee]; [alarm addChild: aAttendee];
} }
+ (id) alarmForEvent: (iCalEntityObject *) theEntity + (id) alarmForEvent: (iCalRepeatableEntityObject *) theEntity
owner: (NSString *) theOwner owner: (NSString *) theOwner
action: (NSString *) reminderAction action: (NSString *) reminderAction
unit: (NSString *) reminderUnit unit: (NSString *) reminderUnit

View file

@ -777,6 +777,8 @@ static BOOL debugSoParts = NO;
filename = [NSString stringWithFormat: @"unknown_%@", path]; filename = [NSString stringWithFormat: @"unknown_%@", path];
else if ([mimeType isEqualToString: @"message/rfc822"]) else if ([mimeType isEqualToString: @"message/rfc822"])
filename = [NSString stringWithFormat: @"email_%@.eml", path]; filename = [NSString stringWithFormat: @"email_%@.eml", path];
else if ([mimeType isEqualToString: @"text/calendar"])
filename = [NSString stringWithFormat: @"calendar_%@.ics", path];
if (filename) if (filename)

View file

@ -1,6 +1,6 @@
/* NSString+Utilities.h - this file is part of SOGo /* NSString+Utilities.h - this file is part of SOGo
* *
* Copyright (C) 2006-2015 Inverse inc. * Copyright (C) 2006-2014 Inverse inc.
* *
* 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
@ -46,9 +46,6 @@
- (NSString *) asCSSIdentifier; - (NSString *) asCSSIdentifier;
- (NSString *) fromCSSIdentifier; - (NSString *) fromCSSIdentifier;
/* JavaScript safety */
- (NSString *) asSafeJSString;
/* SQL safety */ /* SQL safety */
- (NSString *) asSafeSQLString; - (NSString *) asSafeSQLString;

View file

@ -1,6 +1,6 @@
/* NSString+Utilities.m - this file is part of SOGo /* NSString+Utilities.m - this file is part of SOGo
* *
* Copyright (C) 2006-2015 Inverse inc. * Copyright (C) 2006-2014 Inverse inc.
* *
* 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
@ -257,7 +257,7 @@ static int cssEscapingCount;
return selfCopy; return selfCopy;
} }
- (NSString *) asSafeJSString - (NSString *) doubleQuotedString
{ {
NSMutableString *representation; NSMutableString *representation;
@ -270,12 +270,7 @@ static int cssEscapingCount;
[representation replaceString: @"\r" withString: @"\\r"]; [representation replaceString: @"\r" withString: @"\\r"];
[representation replaceString: @"\t" withString: @"\\t"]; [representation replaceString: @"\t" withString: @"\\t"];
return representation; return [NSString stringWithFormat: @"\"%@\"", representation];
}
- (NSString *) doubleQuotedString
{
return [NSString stringWithFormat: @"\"%@\"", [self asSafeJSString]];
} }
// //
@ -338,18 +333,12 @@ static int cssEscapingCount;
int count; int count;
strings = [NSArray arrayWithObjects: @"_U_", @"_D_", @"_H_", @"_A_", @"_S_", strings = [NSArray arrayWithObjects: @"_U_", @"_D_", @"_H_", @"_A_", @"_S_",
@"_C_", @"_SC_", @"_C_", @"_CO_", @"_SP_", @"_SQ_", @"_AM_", @"_P_", @"_DS_", nil];
@"_CO_", @"_SP_", @"_SQ_", @"_DQ_",
@"_LP_", @"_RP_", @"_LS_", @"_RS_", @"_LC_", @"_RC_",
@"_AM_", @"_P_", @"_DS_", nil];
[strings retain]; [strings retain];
cssEscapingStrings = [strings asPointersOfObjects]; cssEscapingStrings = [strings asPointersOfObjects];
characters = [NSArray arrayWithObjects: @"_", @".", @"#", @"@", @"*", characters = [NSArray arrayWithObjects: @"_", @".", @"#", @"@", @"*", @":",
@":", @";", @",", @" ", @"'", @"&", @"+", @"$", nil];
@",", @" ", @"'", @"\"",
@"(", @")", @"[", @"]", @"{", @"}",
@"&", @"+", @"$", nil];
cssEscapingCount = [strings count]; cssEscapingCount = [strings count];
cssEscapingCharacters = NSZoneMalloc (NULL, cssEscapingCharacters = NSZoneMalloc (NULL,
(cssEscapingCount + 1) (cssEscapingCount + 1)

View file

@ -70,6 +70,7 @@
SOGoMailAutoSave = "5"; SOGoMailAutoSave = "5";
SOGoCalendarDefaultCategoryColor = "#aaa";
SOGoCalendarShouldDisplayWeekend = YES; SOGoCalendarShouldDisplayWeekend = YES;
SOGoCalendarEventsDefaultClassification = "PUBLIC"; SOGoCalendarEventsDefaultClassification = "PUBLIC";
SOGoCalendarTasksDefaultClassification = "PUBLIC"; SOGoCalendarTasksDefaultClassification = "PUBLIC";
@ -86,10 +87,6 @@
$label4 = ("To Do", "#3333FF"); $label4 = ("To Do", "#3333FF");
$label5 = ("Later", "#993399"); $label5 = ("Later", "#993399");
}; };
SOGoCalendarCategories = ("Customer", "Calls", "Favorites", "Meeting", "Ideas", "Miscellaneous", "Birthday", "Anniversary", "Vacation", "Travel", "Projects", "Suppliers", "Gifts", "Clients", "Issues", "Business", "Holidays", "Personal", "Status", "Competition", "Follow up", "Public Holiday");
SOGoCalendarCategoriesColors = { "Customer" = "#F0F0F0"; "Calls" = "#F0F0F0"; "Favorites" = "#F0F0F0"; "Meeting" = "#F0F0F0"; "Ideas" = "#F0F0F0"; "Miscellaneous" = "#F0F0F0"; "Birthday" = "#F0F0F0"; "Anniversary" = "#F0F0F0"; "Vacation" = "#F0F0F0"; "Travel" = "#F0F0F0"; "Projects" = "#F0F0F0"; "Suppliers" = "#F0F0F0"; "Gifts" = "#F0F0F0"; "Clients" = "#F0F0F0"; "Issues" = "#F0F0F0"; "Business" = "#F0F0F0"; "Holidays" = "#F0F0F0"; "Personal" = "#F0F0F0"; "Status" = "#F0F0F0"; "Competition" = "#F0F0F0"; "Follow up" = "#F0F0F0"; "Public Holiday" = "#F0F0F0"; };
SOGoSubscriptionFolderFormat = "%{FolderName} (%{UserName} <%{Email}>)"; SOGoSubscriptionFolderFormat = "%{FolderName} (%{UserName} <%{Email}>)";
} }

View file

@ -68,6 +68,8 @@
- (NSArray *) refreshViewIntervals; - (NSArray *) refreshViewIntervals;
- (NSString *) subscriptionFolderFormat; - (NSString *) subscriptionFolderFormat;
- (NSString *) calendarDefaultCategoryColor;
- (NSArray *) freeBusyDefaultInterval; - (NSArray *) freeBusyDefaultInterval;
- (int) davCalendarStartTimeLimit; - (int) davCalendarStartTimeLimit;

View file

@ -294,6 +294,11 @@
return [self stringForKey: @"SOGoLDAPContactInfoAttribute"]; return [self stringForKey: @"SOGoLDAPContactInfoAttribute"];
} }
- (NSString *) calendarDefaultCategoryColor
{
return [self stringForKey: @"SOGoCalendarDefaultCategoryColor"];
}
- (NSArray *) freeBusyDefaultInterval - (NSArray *) freeBusyDefaultInterval
{ {
return [self arrayForKey: @"SOGoFreeBusyDefaultInterval"]; return [self arrayForKey: @"SOGoFreeBusyDefaultInterval"];

View file

@ -165,9 +165,10 @@
// The domain is probably appended to the username; // The domain is probably appended to the username;
// make sure it is defined as a domain in the configuration. // make sure it is defined as a domain in the configuration.
domain = [newLogin substringFromIndex: (r.location + r.length)]; domain = [newLogin substringFromIndex: (r.location + r.length)];
if ([[sd domainIds] containsObject: domain] && if ([[sd domainIds] containsObject: domain])
![sd enableDomainBasedUID])
newLogin = [newLogin substringToIndex: r.location]; newLogin = [newLogin substringToIndex: r.location];
else
domain = nil;
if (domain != nil && ![sd enableDomainBasedUID]) if (domain != nil && ![sd enableDomainBasedUID])
// Login domains are enabled (SOGoLoginDomains) but not // Login domains are enabled (SOGoLoginDomains) but not
@ -196,25 +197,8 @@
// When the user is associated to a domain, the [SOGoUser login] // When the user is associated to a domain, the [SOGoUser login]
// method returns the combination login@domain while // method returns the combination login@domain while
// [SOGoUser loginInDomain] only returns the login. // [SOGoUser loginInDomain] only returns the login.
r = [realUID rangeOfString: domain options: NSBackwardsSearch|NSCaseInsensitiveSearch]; uid = [NSString stringWithString: realUID];
realUID = [NSString stringWithFormat: @"%@@%@", realUID, domain];
// Do NOT strip @domain.com if SOGoEnableDomainBasedUID is enabled since
// the real login most likely is the email address.
if (r.location != NSNotFound && ![sd enableDomainBasedUID])
uid = [realUID substringToIndex: r.location-1];
// If we don't have the domain in the UID but SOGoEnableDomainBasedUID is
// enabled, let's add it internally so so it becomes unique across
// all potential domains.
else if (r.location == NSNotFound && [sd enableDomainBasedUID])
{
uid = [NSString stringWithString: realUID];
realUID = [NSString stringWithFormat: @"%@@%@", realUID, domain];
}
// We found the domain and SOGoEnableDomainBasedUID is enabled,
// we keep realUID.. This would happen for example if the user
// authenticates with foo@bar.com and the UIDFieldName is also foo@bar.com
else if ([sd enableDomainBasedUID])
uid = [NSString stringWithString: realUID];
} }
} }

View file

@ -702,7 +702,7 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek";
- (NSDictionary *) calendarCategoriesColors - (NSDictionary *) calendarCategoriesColors
{ {
return [self objectForKey: @"SOGoCalendarCategoriesColors"]; return [self dictionaryForKey: @"SOGoCalendarCategoriesColors"];
} }
- (void) setCalendarShouldDisplayWeekend: (BOOL) newValue - (void) setCalendarShouldDisplayWeekend: (BOOL) newValue

View file

@ -494,10 +494,10 @@ static Class NSNullK;
NSMutableDictionary *currentUser; NSMutableDictionary *currentUser;
NSDictionary *failedCount; NSDictionary *failedCount;
NSString *dictPassword, *username, *jsonUser; NSString *dictPassword, *username, *jsonUser;
SOGoSystemDefaults *sd; SOGoSystemDefaults *dd;
BOOL checkOK; BOOL checkOK;
sd = [SOGoSystemDefaults sharedSystemDefaults]; dd = [SOGoSystemDefaults sharedSystemDefaults];
username = _login; username = _login;
@ -517,9 +517,21 @@ static Class NSNullK;
if (r.location != NSNotFound) if (r.location != NSNotFound)
{ {
NSArray *allDomains;
int i;
*_domain = [username substringFromIndex: r.location+1]; *_domain = [username substringFromIndex: r.location+1];
if (![[[SOGoSystemDefaults sharedSystemDefaults] domainIds] containsObject: *_domain]) allDomains = [[dd dictionaryForKey: @"domains"] allValues];
for (i = 0; i < [allDomains count]; i++)
{
if ([*_domain isEqualToString: [[allDomains objectAtIndex: i] objectForKey: @"SOGoMailDomain"]])
break;
}
// We haven't found one
if (i == [allDomains count])
*_domain = nil; *_domain = nil;
} }
} }
@ -536,10 +548,10 @@ static Class NSNullK;
start_time = [[failedCount objectForKey: @"InitialDate"] unsignedIntValue]; start_time = [[failedCount objectForKey: @"InitialDate"] unsignedIntValue];
delta = current_time - start_time; delta = current_time - start_time;
block_time = [sd failedLoginBlockInterval]; block_time = [dd failedLoginBlockInterval];
if ([[failedCount objectForKey: @"FailedCount"] intValue] >= [sd maximumFailedLoginCount] && if ([[failedCount objectForKey: @"FailedCount"] intValue] >= [dd maximumFailedLoginCount] &&
delta >= [sd maximumFailedLoginInterval] && delta >= [dd maximumFailedLoginInterval] &&
delta <= block_time ) delta <= block_time )
{ {
*_perr = PolicyAccountLocked; *_perr = PolicyAccountLocked;
@ -558,28 +570,6 @@ static Class NSNullK;
// authentication source and try to validate there, then cache it. // authentication source and try to validate there, then cache it.
jsonUser = [[SOGoCache sharedCache] userAttributesForLogin: username]; jsonUser = [[SOGoCache sharedCache] userAttributesForLogin: username];
currentUser = [jsonUser objectFromJSONString]; currentUser = [jsonUser objectFromJSONString];
//
// If we are using multidomain and the UIDFieldName is not part of the email address
// we must bind without the domain part since internally, SOGo will use
// UIDFieldName @ domain as its unique identifier if the UIDFieldName is used to
// authenticate. This can happen for example of one has in LDAP:
//
// dn: uid=foo,dc=example,dc=com
// uid: foo
// mail: broccoli@example.com
//
// and authenticates with "foo", using bindFields = (uid, mail) and SOGoEnableDomainBasedUID = YES;
// Otherwise, -_sourceCheckLogin:... would have failed because SOGo would try to bind using: foo@example.com
//
if ([[currentUser objectForKey: @"DomainLessLogin"] boolValue])
{
NSRange r;
r = [_login rangeOfString: [NSString stringWithFormat: @"@%@", *_domain]];
_login = [_login substringToIndex: r.location];
}
dictPassword = [currentUser objectForKey: @"password"]; dictPassword = [currentUser objectForKey: @"password"];
if (useCache && currentUser && dictPassword) if (useCache && currentUser && dictPassword)
{ {
@ -599,18 +589,6 @@ static Class NSNullK;
currentUser = [NSMutableDictionary dictionary]; currentUser = [NSMutableDictionary dictionary];
} }
// Before caching user attributes, we must check if SOGoEnableDomainBasedUID is enabled
// but we don't have a domain. That would happen for example if the user authenticates
// without the domain part. We must also cache that information, since SOGo will try
// afterward to bind with UIDFieldName@domain, and it could potentially not exist
// in the authentication source. See the rationale in _sourceCheckLogin: ...
if ([sd enableDomainBasedUID] &&
[username rangeOfString: @"@"].location == NSNotFound)
{
username = [NSString stringWithFormat: @"%@@%@", username, *_domain];
[currentUser setObject: [NSNumber numberWithBool: YES] forKey: @"DomainLessLogin"];
}
// It's important to cache the password here as we might have cached the // It's important to cache the password here as we might have cached the
// user's entry in -contactInfosForUserWithUIDorEmail: and if we don't // user's entry in -contactInfosForUserWithUIDorEmail: and if we don't
// set the password and recache the entry, the password would never be // set the password and recache the entry, the password would never be
@ -624,7 +602,7 @@ static Class NSNullK;
else else
{ {
// If failed login "rate-limiting" is enabled, we adjust the stats // If failed login "rate-limiting" is enabled, we adjust the stats
if ([sd maximumFailedLoginCount]) if ([dd maximumFailedLoginCount])
{ {
[[SOGoCache sharedCache] setFailedCount: ([[failedCount objectForKey: @"FailedCount"] intValue] + 1) [[SOGoCache sharedCache] setFailedCount: ([[failedCount objectForKey: @"FailedCount"] intValue] + 1)
forLogin: username]; forLogin: username];
@ -732,9 +710,9 @@ static Class NSNullK;
// //
// //
// //
- (void) _fillContactInfosForUser: (NSMutableDictionary *) theCurrentUser - (void) _fillContactInfosForUser: (NSMutableDictionary *) currentUser
withUIDorEmail: (NSString *) theUID withUIDorEmail: (NSString *) uid
inDomain: (NSString *) theDomain inDomain: (NSString *) domain
{ {
NSString *sourceID, *cn, *c_domain, *c_uid, *c_imaphostname, *c_imaplogin, *c_sievehostname; NSString *sourceID, *cn, *c_domain, *c_uid, *c_imaphostname, *c_imaplogin, *c_sievehostname;
NSObject <SOGoSource> *currentSource; NSObject <SOGoSource> *currentSource;
@ -761,28 +739,19 @@ static Class NSNullK;
enumerator = [access_types_list objectEnumerator]; enumerator = [access_types_list objectEnumerator];
while ((access_type = [enumerator nextObject]) != nil) while ((access_type = [enumerator nextObject]) != nil)
[theCurrentUser setObject: [NSNumber numberWithBool: YES] [currentUser setObject: [NSNumber numberWithBool: YES]
forKey: access_type]; forKey: access_type];
if ([[theCurrentUser objectForKey: @"DomainLessLogin"] boolValue]) sogoSources = [[self authenticationSourceIDsInDomain: domain] objectEnumerator];
{
NSRange r;
r = [theUID rangeOfString: [NSString stringWithFormat: @"@%@", theDomain]];
theUID = [theUID substringToIndex: r.location];
}
sogoSources = [[self authenticationSourceIDsInDomain: theDomain] objectEnumerator];
userEntry = nil; userEntry = nil;
while (!userEntry && (sourceID = [sogoSources nextObject])) while (!userEntry && (sourceID = [sogoSources nextObject]))
{ {
currentSource = [_sources objectForKey: sourceID]; currentSource = [_sources objectForKey: sourceID];
userEntry = [currentSource lookupContactEntryWithUIDorEmail: uid
userEntry = [currentSource lookupContactEntryWithUIDorEmail: theUID inDomain: domain];
inDomain: theDomain];
if (userEntry) if (userEntry)
{ {
[theCurrentUser setObject: sourceID forKey: @"SOGoSource"]; [currentUser setObject: sourceID forKey: @"SOGoSource"];
if (!cn) if (!cn)
cn = [userEntry objectForKey: @"c_cn"]; cn = [userEntry objectForKey: @"c_cn"];
if (!c_uid) if (!c_uid)
@ -804,27 +773,27 @@ static Class NSNullK;
{ {
access = [[userEntry objectForKey: access_type] boolValue]; access = [[userEntry objectForKey: access_type] boolValue];
if (!access) if (!access)
[theCurrentUser setObject: [NSNumber numberWithBool: NO] [currentUser setObject: [NSNumber numberWithBool: NO]
forKey: access_type]; forKey: access_type];
} }
// We check if it's a group // We check if it's a group
isGroup = [userEntry objectForKey: @"isGroup"]; isGroup = [userEntry objectForKey: @"isGroup"];
if (isGroup) if (isGroup)
[theCurrentUser setObject: isGroup forKey: @"isGroup"]; [currentUser setObject: isGroup forKey: @"isGroup"];
// We also fill the resource attributes, if any // We also fill the resource attributes, if any
if ([userEntry objectForKey: @"isResource"]) if ([userEntry objectForKey: @"isResource"])
[theCurrentUser setObject: [userEntry objectForKey: @"isResource"] [currentUser setObject: [userEntry objectForKey: @"isResource"]
forKey: @"isResource"]; forKey: @"isResource"];
if ([userEntry objectForKey: @"numberOfSimultaneousBookings"]) if ([userEntry objectForKey: @"numberOfSimultaneousBookings"])
[theCurrentUser setObject: [userEntry objectForKey: @"numberOfSimultaneousBookings"] [currentUser setObject: [userEntry objectForKey: @"numberOfSimultaneousBookings"]
forKey: @"numberOfSimultaneousBookings"]; forKey: @"numberOfSimultaneousBookings"];
// This is Active Directory specific attribute (needed on OpenChange/* layer) // This is Active Directory specific attribute (needed on OpenChange/* layer)
if ([userEntry objectForKey: @"samaccountname"]) if ([userEntry objectForKey: @"samaccountname"])
[theCurrentUser setObject: [userEntry objectForKey: @"samaccountname"] [currentUser setObject: [userEntry objectForKey: @"samaccountname"]
forKey: @"sAMAccountName"]; forKey: @"sAMAccountName"];
} }
} }
@ -836,20 +805,20 @@ static Class NSNullK;
c_domain = @""; c_domain = @"";
if (c_imaphostname) if (c_imaphostname)
[theCurrentUser setObject: c_imaphostname forKey: @"c_imaphostname"]; [currentUser setObject: c_imaphostname forKey: @"c_imaphostname"];
if (c_imaplogin) if (c_imaplogin)
[theCurrentUser setObject: c_imaplogin forKey: @"c_imaplogin"]; [currentUser setObject: c_imaplogin forKey: @"c_imaplogin"];
if (c_sievehostname) if (c_sievehostname)
[theCurrentUser setObject: c_sievehostname forKey: @"c_sievehostname"]; [currentUser setObject: c_sievehostname forKey: @"c_sievehostname"];
[theCurrentUser setObject: emails forKey: @"emails"]; [currentUser setObject: emails forKey: @"emails"];
[theCurrentUser setObject: cn forKey: @"cn"]; [currentUser setObject: cn forKey: @"cn"];
[theCurrentUser setObject: c_uid forKey: @"c_uid"]; [currentUser setObject: c_uid forKey: @"c_uid"];
[theCurrentUser setObject: c_domain forKey: @"c_domain"]; [currentUser setObject: c_domain forKey: @"c_domain"];
// If our LDAP queries gave us nothing, we add at least one default // If our LDAP queries gave us nothing, we add at least one default
// email address based on the default domain. // email address based on the default domain.
[self _fillContactMailRecords: theCurrentUser]; [self _fillContactMailRecords: currentUser];
} }
// //
@ -943,9 +912,8 @@ static Class NSNullK;
- (NSDictionary *) contactInfosForUserWithUIDorEmail: (NSString *) uid - (NSDictionary *) contactInfosForUserWithUIDorEmail: (NSString *) uid
inDomain: (NSString *) domain inDomain: (NSString *) domain
{ {
NSString *aUID, *cacheUid, *jsonUser;
NSMutableDictionary *currentUser; NSMutableDictionary *currentUser;
NSString *aUID, *cacheUid, *jsonUser;
BOOL newUser; BOOL newUser;
if ([uid isEqualToString: @"anonymous"]) if ([uid isEqualToString: @"anonymous"])
@ -954,14 +922,12 @@ static Class NSNullK;
{ {
// Remove the "@" prefix used to identified groups in the ACL tables. // Remove the "@" prefix used to identified groups in the ACL tables.
aUID = [uid hasPrefix: @"@"] ? [uid substringFromIndex: 1] : uid; aUID = [uid hasPrefix: @"@"] ? [uid substringFromIndex: 1] : uid;
if (domain && [aUID rangeOfString: @"@"].location == NSNotFound) if (domain)
cacheUid = [NSString stringWithFormat: @"%@@%@", aUID, domain]; cacheUid = [NSString stringWithFormat: @"%@@%@", aUID, domain];
else else
cacheUid = aUID; cacheUid = aUID;
jsonUser = [[SOGoCache sharedCache] userAttributesForLogin: cacheUid]; jsonUser = [[SOGoCache sharedCache] userAttributesForLogin: cacheUid];
currentUser = [jsonUser objectFromJSONString]; currentUser = [jsonUser objectFromJSONString];
if ([currentUser isKindOfClass: NSNullK]) if ([currentUser isKindOfClass: NSNullK])
currentUser = nil; currentUser = nil;
else if (!([currentUser objectForKey: @"emails"] else if (!([currentUser objectForKey: @"emails"]
@ -971,10 +937,8 @@ static Class NSNullK;
// that we have an occurence with only a cached password. In the // that we have an occurence with only a cached password. In the
// latter case, we update the entry with the remaining information // latter case, we update the entry with the remaining information
// and recache the value. // and recache the value.
if (!currentUser || if (!currentUser || ([currentUser count] == 1 && [currentUser objectForKey: @"password"]))
([currentUser count] == 1 && [currentUser objectForKey: @"password"]) || {
([currentUser count] == 2 && [currentUser objectForKey: @"password"] && [currentUser objectForKey: @"DomainLessLogin"]))
{
newUser = YES; newUser = YES;
if (!currentUser) if (!currentUser)
@ -994,22 +958,9 @@ static Class NSNullK;
currentUser = nil; currentUser = nil;
} }
else else
{ [self _retainUser: currentUser
SOGoSystemDefaults *sd; withLogin: cacheUid];
}
sd = [SOGoSystemDefaults sharedSystemDefaults];
// SOGoEnableDomainBasedUID is set to YES but we don't have a domain part. This happens in
// multi-domain environments authenticating only with the UIDFieldName
if ([sd enableDomainBasedUID] && !domain)
{
cacheUid = [NSString stringWithFormat: @"%@@%@", cacheUid, [currentUser objectForKey: @"c_domain"]];
[currentUser setObject: [NSNumber numberWithBool: YES] forKey: @"DomainLessLogin"];
}
[self _retainUser: currentUser withLogin: cacheUid];
}
}
} }
} }
else else

View file

@ -1,6 +1,9 @@
/* SOGoToolBackup.m - this file is part of SOGo /* SOGoToolBackup.m - this file is part of SOGo
* *
* Copyright (C) 2009-2015 Inverse inc. * Copyright (C) 2009-2011 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* Francis Lachapelle <flachapelle@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
@ -39,7 +42,6 @@
#import <SOGo/SOGoUserDefaults.h> #import <SOGo/SOGoUserDefaults.h>
#import <SOGo/SOGoUserProfile.h> #import <SOGo/SOGoUserProfile.h>
#import <SOGo/SOGoUserSettings.h> #import <SOGo/SOGoUserSettings.h>
#import <SOGo/SOGoSystemDefaults.h>
#import <Contacts/NSDictionary+LDIF.h> #import <Contacts/NSDictionary+LDIF.h>
#import "SOGoTool.h" #import "SOGoTool.h"
@ -53,7 +55,7 @@
@interface SOGoToolBackup : SOGoTool @interface SOGoToolBackup : SOGoTool
{ {
NSString *directory; NSString *directory;
NSArray *usersToBackup; NSArray *userIDs;
} }
@end @end
@ -81,7 +83,7 @@
if ((self = [super init])) if ((self = [super init]))
{ {
directory = nil; directory = nil;
usersToBackup = nil; userIDs = nil;
} }
return self; return self;
@ -90,7 +92,7 @@
- (void) dealloc - (void) dealloc
{ {
[directory release]; [directory release];
[usersToBackup release]; [userIDs release];
[super dealloc]; [super dealloc];
} }
@ -141,7 +143,6 @@
lm = [SOGoUserManager sharedUserManager]; lm = [SOGoUserManager sharedUserManager];
pool = [[NSAutoreleasePool alloc] init]; pool = [[NSAutoreleasePool alloc] init];
max = [users count]; max = [users count];
user = [users objectAtIndex: 0]; user = [users objectAtIndex: 0];
@ -197,11 +198,11 @@
NSLog (@"user '%@' unknown", user); NSLog (@"user '%@' unknown", user);
} }
[allUsers autorelease]; [allUsers autorelease];
ASSIGN (usersToBackup, allUsers); ASSIGN (userIDs, [allUsers objectsForKey: @"c_uid" notFoundMarker: nil]);
DESTROY(pool); DESTROY(pool);
return ([usersToBackup count] > 0); return ([userIDs count] > 0);
} }
- (BOOL) parseArguments - (BOOL) parseArguments
@ -409,29 +410,19 @@
return YES; return YES;
} }
- (BOOL) exportUser: (NSDictionary *) theUser - (BOOL) exportUser: (NSString *) uid
{ {
NSString *exportPath, *gcsUID, *ldapUID;
NSMutableDictionary *userRecord; NSMutableDictionary *userRecord;
SOGoSystemDefaults *sd; NSString *exportPath;
sd = [SOGoSystemDefaults sharedSystemDefaults];
userRecord = [NSMutableDictionary dictionary]; userRecord = [NSMutableDictionary dictionary];
exportPath = [directory stringByAppendingPathComponent: uid];
ldapUID = [theUser objectForKey: @"c_uid"]; return ([self extractUserFolders: uid
exportPath = [directory stringByAppendingPathComponent: ldapUID];
gcsUID = [theUser objectForKey: @"c_uid"];
if ([sd enableDomainBasedUID] && [gcsUID rangeOfString: @"@"].location == NSNotFound)
gcsUID = [NSString stringWithFormat: @"%@@%@", gcsUID, [theUser objectForKey: @"c_domain"]];
return ([self extractUserFolders: gcsUID
intoRecord: userRecord] intoRecord: userRecord]
&& [self extractUserLDIFRecord: ldapUID && [self extractUserLDIFRecord: uid
intoRecord: userRecord] intoRecord: userRecord]
&& [self extractUserPreferences: gcsUID && [self extractUserPreferences: uid
intoRecord: userRecord] intoRecord: userRecord]
&& [userRecord writeToFile: exportPath && [userRecord writeToFile: exportPath
atomically: NO]); atomically: NO]);
@ -447,10 +438,10 @@
pool = [NSAutoreleasePool new]; pool = [NSAutoreleasePool new];
max = [usersToBackup count]; max = [userIDs count];
for (count = 0; rc && count < max; count++) for (count = 0; rc && count < max; count++)
{ {
rc = [self exportUser: [usersToBackup objectAtIndex: count]]; rc = [self exportUser: [userIDs objectAtIndex: count]];
if ((count % 10) == 0) if ((count % 10) == 0)
[pool emptyPool]; [pool emptyPool];
} }

View file

@ -33,7 +33,6 @@ typedef enum SOGoToolRestoreMode {
{ {
NSString *directory; NSString *directory;
NSString *userID; NSString *userID;
NSString *filename;
NSString *restoreFolder; NSString *restoreFolder;
BOOL destructive; /* destructive mode not handled */ BOOL destructive; /* destructive mode not handled */
SOGoToolRestoreMode restoreMode; SOGoToolRestoreMode restoreMode;

View file

@ -1,6 +1,6 @@
/* SOGoToolRestore.m - this file is part of SOGo /* SOGoToolRestore.m - this file is part of SOGo
* *
* Copyright (C) 2009-2015 Inverse inc. * Copyright (C) 2009-2014 Inverse inc.
* *
* 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
@ -73,7 +73,6 @@
{ {
directory = nil; directory = nil;
userID = nil; userID = nil;
filename = nil;
restoreFolder = nil; restoreFolder = nil;
destructive = NO; destructive = NO;
} }
@ -85,7 +84,6 @@
{ {
[directory release]; [directory release];
[userID release]; [userID release];
[filename release];
[restoreFolder release]; [restoreFolder release];
[super dealloc]; [super dealloc];
} }
@ -154,35 +152,25 @@
- (BOOL) fetchUserID: (NSString *) identifier - (BOOL) fetchUserID: (NSString *) identifier
{ {
SOGoSystemDefaults *sd; BOOL rc;
SOGoUserManager *lm; SOGoUserManager *lm;
NSDictionary *infos; NSDictionary *infos;
SOGoSystemDefaults *sd;
NSString *uid = nil; NSString *uid = nil;
BOOL rc;
lm = [SOGoUserManager sharedUserManager]; lm = [SOGoUserManager sharedUserManager];
infos = [lm contactInfosForUserWithUIDorEmail: identifier]; infos = [lm contactInfosForUserWithUIDorEmail: identifier];
uid = nil;
if (infos) if (infos)
{ {
sd = [SOGoSystemDefaults sharedSystemDefaults]; sd = [SOGoSystemDefaults sharedSystemDefaults];
uid = [infos objectForKey: @"c_uid"]; if ([sd enableDomainBasedUID])
if ([sd enableDomainBasedUID] && [uid rangeOfString: @"@"].location == NSNotFound)
uid = [NSString stringWithFormat: @"%@@%@", uid = [NSString stringWithFormat: @"%@@%@",
[infos objectForKey: @"c_uid"], [infos objectForKey: @"c_uid"],
[infos objectForKey: @"c_domain"]]; [infos objectForKey: @"c_domain"]];
if ([[infos objectForKey: @"DomainLessLogin"] boolValue])
ASSIGN(filename, [infos objectForKey: @"c_uid"]);
else else
ASSIGN(filename, uid); uid = [infos objectForKey: @"c_uid"];
} }
ASSIGN (userID, uid); ASSIGN (userID, uid);
if (userID) if (userID)
rc = YES; rc = YES;
else else
@ -620,7 +608,7 @@
NSString *importPath; NSString *importPath;
BOOL rc; BOOL rc;
importPath = [directory stringByAppendingPathComponent: filename]; importPath = [directory stringByAppendingPathComponent: userID];
userRecord = [NSDictionary dictionaryWithContentsOfFile: importPath]; userRecord = [NSDictionary dictionaryWithContentsOfFile: importPath];
if (userRecord) if (userRecord)
{ {
@ -638,7 +626,7 @@
else else
{ {
rc = NO; rc = NO;
NSLog(@"user backup (%@) file could not be loaded", importPath); NSLog (@"user backup file could not be loaded");
} }
return rc; return rc;

View file

@ -1,6 +1,8 @@
/* sogo-tool.m - this file is part of SOGo /* sogo-tool.m - this file is part of SOGo
* *
* Copyright (C) 2009-2015 Inverse inc. * Copyright (C) 2009 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,6 +1,6 @@
/* /*
Copyright (C) 2004-2005 SKYRIX Software AG Copyright (C) 2004-2005 SKYRIX Software AG
Copyright (C) 2005-2015 Inverse inc. Copyright (C) 2005-2010 Inverse inc.
This file is part of SOGo This file is part of SOGo
@ -378,8 +378,9 @@ static Class SOGoContactGCSEntryK = Nil;
result = [self redirectToLocation: [self modulePath]]; result = [self redirectToLocation: [self modulePath]];
else else
{ {
jsRefreshMethod = [NSString stringWithFormat: @"refreshContacts('%@')", jsRefreshMethod
[contact nameInContainer]]; = [NSString stringWithFormat: @"refreshContacts(\"%@\")",
[contact nameInContainer]];
result = [self jsCloseWithRefreshMethod: jsRefreshMethod]; result = [self jsCloseWithRefreshMethod: jsRefreshMethod];
} }

View file

@ -1,6 +1,6 @@
/* /*
Copyright (C) 2004 SKYRIX Software AG Copyright (C) 2004 SKYRIX Software AG
Copyright (C) 2005-2015 Inverse inc. Copyright (C) 2005-2014 Inverse inc.
This file is part of SOGo. This file is part of SOGo.
@ -138,13 +138,9 @@
if ([email length] > 0) if ([email length] > 0)
{ {
fn = [card fn]; fn = [card fn];
if ([fn length] > 0) fn = [fn stringByReplacingString: @"\"" withString: @""];
attrs = [NSString stringWithFormat: @"%@ <%@>", fn, email]; fn = [fn stringByReplacingString: @"'" withString: @"\\\'"];
else attrs = [NSString stringWithFormat: @"onclick=\"return openMailTo('%@ <%@>');\"", fn, email];
attrs = email;
attrs = [attrs stringByReplacingString: @"'" withString: @"\\'"];
attrs = [attrs stringByReplacingString: @"\"" withString: @"\\\""];
attrs = [NSString stringWithFormat: @"onclick=\"return openMailTo('%@');\"", attrs];
} }
else else
{ {
@ -185,23 +181,16 @@
for (i = 0; i < [emails count]; i++) for (i = 0; i < [emails count]; i++)
{ {
email = [[emails objectAtIndex: i] flattenedValuesForKey: @""]; email = [[emails objectAtIndex: i] flattenedValuesForKey: @""];
if ([email length]) fn = [card fn];
{ fn = [fn stringByReplacingString: @"\"" withString: @""];
fn = [card fn]; fn = [fn stringByReplacingString: @"'" withString: @"\\\'"];
if ([fn length]) attrs = [NSString stringWithFormat: @"onclick=\"return openMailTo('%@ <%@>');\"", fn, email];
attrs = [NSString stringWithFormat: @"%@ <%@>", fn, email];
else [secondaryEmails addObject: [self _cardStringWithLabel: nil
attrs = email; value: email
attrs = [attrs stringByReplacingString: @"'" withString: @"\\'"]; byEscapingHTMLString: YES
attrs = [attrs stringByReplacingString: @"\"" withString: @"\\\""]; asLinkScheme: @"mailto:"
attrs = [NSString stringWithFormat: @"onclick=\"return openMailTo('%@');\"", attrs]; withLinkAttributes: attrs]];
[secondaryEmails addObject: [self _cardStringWithLabel: nil
value: email
byEscapingHTMLString: YES
asLinkScheme: @"mailto:"
withLinkAttributes: attrs]];
}
} }
} }
else else

View file

@ -1,6 +1,6 @@
/* UIxListEditor.m - this file is part of SOGo /* UIxListEditor.m - this file is part of SOGo
* *
* Copyright (C) 2008-2015 Inverse inc. * Copyright (C) 2008-2014 Inverse inc.
* *
* 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
@ -29,8 +29,6 @@
#import <NGCards/NGVCardReference.h> #import <NGCards/NGVCardReference.h>
#import <NGCards/NGVList.h> #import <NGCards/NGVList.h>
#import <SOGo/NSString+Utilities.h>
#import <Contacts/SOGoContactGCSEntry.h> #import <Contacts/SOGoContactGCSEntry.h>
#import <Contacts/SOGoContactGCSFolder.h> #import <Contacts/SOGoContactGCSFolder.h>
#import <Contacts/SOGoContactGCSList.h> #import <Contacts/SOGoContactGCSList.h>
@ -291,8 +289,9 @@
result = [self redirectToLocation: [self modulePath]]; result = [self redirectToLocation: [self modulePath]];
else else
{ {
jsRefreshMethod = [NSString stringWithFormat: @"refreshContacts('%@')", jsRefreshMethod
[co nameInContainer]]; = [NSString stringWithFormat: @"refreshContacts(\"%@\")",
[co nameInContainer]];
result = [self jsCloseWithRefreshMethod: jsRefreshMethod]; result = [self jsCloseWithRefreshMethod: jsRefreshMethod];
} }
} }

View file

@ -463,7 +463,7 @@
if (!activeUserIsInDomain || ![uid isEqualToString: login]) if (!activeUserIsInDomain || ![uid isEqualToString: login])
{ {
jsonLine = [NSMutableArray arrayWithCapacity: 4]; jsonLine = [NSMutableArray arrayWithCapacity: 4];
if ([domain length] && [uid rangeOfString: @"@"].location == NSNotFound) if ([domain length])
uid = [NSString stringWithFormat: @"%@@%@", uid, domain]; uid = [NSString stringWithFormat: @"%@@%@", uid, domain];
[jsonLine addObject: uid]; [jsonLine addObject: uid];
[jsonLine addObject: [contact objectForKey: @"cn"]]; [jsonLine addObject: [contact objectForKey: @"cn"]];

View file

@ -44,6 +44,7 @@
NSDictionary *calendarCategoriesColors; NSDictionary *calendarCategoriesColors;
NSArray *contactsCategories; NSArray *contactsCategories;
NSString *defaultCategoryColor;
NSCalendarDate *today; NSCalendarDate *today;
// Mail labels/tags // Mail labels/tags

View file

@ -128,6 +128,7 @@ static NSArray *reminderValues = nil;
calendarCategories = nil; calendarCategories = nil;
calendarCategoriesColors = nil; calendarCategoriesColors = nil;
defaultCategoryColor = nil;
category = nil; category = nil;
label = nil; label = nil;
@ -174,6 +175,7 @@ static NSArray *reminderValues = nil;
[vacationOptions release]; [vacationOptions release];
[calendarCategories release]; [calendarCategories release];
[calendarCategoriesColors release]; [calendarCategoriesColors release];
[defaultCategoryColor release];
[category release]; [category release];
[label release]; [label release];
[mailLabels release]; [mailLabels release];
@ -1519,6 +1521,15 @@ static NSArray *reminderValues = nil;
ASSIGN (calendarCategoriesColors, [userDefaults calendarCategoriesColors]); ASSIGN (calendarCategoriesColors, [userDefaults calendarCategoriesColors]);
categoryColor = [calendarCategoriesColors objectForKey: category]; categoryColor = [calendarCategoriesColors objectForKey: category];
if (!categoryColor)
{
if (!defaultCategoryColor)
{
dd = [[context activeUser] domainDefaults];
ASSIGN (defaultCategoryColor, [dd calendarDefaultCategoryColor]);
}
categoryColor = defaultCategoryColor;
}
return categoryColor; return categoryColor;
} }

View file

@ -1,5 +1,5 @@
/* /*
Copyright (C) 2007-2015 Inverse inc. Copyright (C) 2007-2012 Inverse inc.
Copyright (C) 2004 SKYRIX Software AG Copyright (C) 2004 SKYRIX Software AG
This file is part of SOGo This file is part of SOGo
@ -452,7 +452,7 @@ static SoProduct *commonProduct = nil;
jsClose = [UIxJSClose new]; jsClose = [UIxJSClose new];
[jsClose autorelease]; [jsClose autorelease];
[jsClose setRefreshMethod: [methodName doubleQuotedString]]; [jsClose setRefreshMethod: methodName];
return jsClose; return jsClose;
} }

View file

@ -149,11 +149,11 @@
<var:foreach list="personalContactInfos" item="currentContact"> <var:foreach list="personalContactInfos" item="currentContact">
<tr var:class="currentContactClasses" <tr var:class="currentContactClasses"
var:categories="currentContact.c_categories.asSafeJSString" var:categories="currentContact.c_categories"
var:id="currentContact.c_name.asCSSIdentifier" var:id="currentContact.c_name"
var:contactname="currentContact.c_cn.asSafeJSString"> var:contactname="currentContact.c_cn">
<td class="displayName" var:title="currentContact.c_cn.asSafeJSString"><var:string value="currentContact.c_cn" const:escapeHTML="YES" /></td> <td class="displayName" var:title="currentContact.c_cn"><var:string value="currentContact.c_cn" const:escapeHTML="YES" /></td>
<td var:title="currentContact.c_mail.asSafeJSString"><var:string value="currentContact.c_mail"/></td> <td var:title="currentContact.c_mail"><var:string value="currentContact.c_mail"/></td>
<td><var:string value="currentContact.c_screenname"/></td> <td><var:string value="currentContact.c_screenname"/></td>
<td><var:string value="currentContact.c_o"/></td> <td><var:string value="currentContact.c_o"/></td>
<td><var:string value="currentContact.c_telephonenumber"/></td> <td><var:string value="currentContact.c_telephonenumber"/></td>

View file

@ -17,7 +17,7 @@
><script type="text/javascript" ><script type="text/javascript"
><var:if condition="hasRefreshMethod"> ><var:if condition="hasRefreshMethod">
var p = <var:if condition="singleWindowModeEnabled">parent</var:if><var:if condition="singleWindowModeEnabled" const:negate="YES">window.opener</var:if>; var p = <var:if condition="singleWindowModeEnabled">parent</var:if><var:if condition="singleWindowModeEnabled" const:negate="YES">window.opener</var:if>;
if (p) p.setTimeout(<var:string value="refreshMethod" const:escapeHTML="NO" />, 50); if (p) p.setTimeout('<var:string value="refreshMethod" const:escapeHTML="NO" />;', 50);
</var:if> </var:if>
onCloseButtonClick(); onCloseButtonClick();
</script> </script>

View file

@ -31,7 +31,7 @@ function openContactsFolder(contactsFolder, reload, idx) {
var selection; var selection;
if (idx) { if (idx) {
selection = [idx.asCSSIdentifier()]; selection = [idx];
} }
else if (contactsFolder == Contact.currentAddressBook) { else if (contactsFolder == Contact.currentAddressBook) {
var contactsList = $("contactsList"); var contactsList = $("contactsList");
@ -74,7 +74,7 @@ function contactsListCallback(http) {
var contact = data[i]; var contact = data[i];
var row = rows[i]; var row = rows[i];
row.className = contact["c_component"]; row.className = contact["c_component"];
row.setAttribute("id", contact["c_name"].asCSSIdentifier()); row.setAttribute("id", contact["c_name"]);
row.setAttribute("categories", contact["c_categories"]); row.setAttribute("categories", contact["c_categories"]);
row.setAttribute("contactname", contact["c_cn"]); row.setAttribute("contactname", contact["c_cn"]);
var cells = row.getElementsByTagName("TD"); var cells = row.getElementsByTagName("TD");
@ -111,7 +111,7 @@ function contactsListCallback(http) {
for (var j = i; j < data.length; j++) { for (var j = i; j < data.length; j++) {
var contact = data[j]; var contact = data[j];
var row = createElement("tr", var row = createElement("tr",
contact["c_name"].asCSSIdentifier(), contact["c_name"],
contact["c_component"], contact["c_component"],
null, null,
{ categories: contact["c_categories"], { categories: contact["c_categories"],
@ -272,7 +272,7 @@ function _onContactMenuAction(folderItem, action, refresh) {
if (Object.isArray(document.menuTarget) && selectedFolders.length > 0) { if (Object.isArray(document.menuTarget) && selectedFolders.length > 0) {
var selectedFolderId = $(selectedFolders[0]).readAttribute("id"); var selectedFolderId = $(selectedFolders[0]).readAttribute("id");
var contactIds = $(document.menuTarget).collect(function(row) { var contactIds = $(document.menuTarget).collect(function(row) {
return row.getAttribute("id").fromCSSIdentifier(); return row.getAttribute("id");
}); });
for (var i = 0; i < contactIds.length; i++) { for (var i = 0; i < contactIds.length; i++) {
@ -283,7 +283,9 @@ function _onContactMenuAction(folderItem, action, refresh) {
} }
var url = ApplicationBaseURL + "/" + selectedFolderId + "/" + action; var url = ApplicationBaseURL + "/" + selectedFolderId + "/" + action;
var uids = contactIds.collect(encodeURIComponent).join('&uid='); var uids = contactIds.collect(function (s) {
return encodeURIComponent(s.unescapeHTML());
}).join('&uid=');
if (refresh) if (refresh)
triggerAjaxRequest(url, actionContactCallback, selectedFolderId, triggerAjaxRequest(url, actionContactCallback, selectedFolderId,
('folder='+ folderId + '&uid=' + uids), ('folder='+ folderId + '&uid=' + uids),
@ -310,22 +312,22 @@ function onMenuExportContact (event) {
if (canExport) { if (canExport) {
var selectedFolderId = $(selectedFolders[0]).readAttribute("id"); var selectedFolderId = $(selectedFolders[0]).readAttribute("id");
var contactIds = document.menuTarget.collect(function(row) { var contactIds = document.menuTarget.collect(function(row) {
return row.readAttribute("id").fromCSSIdentifier(); return row.readAttribute("id");
}); });
var url = ApplicationBaseURL + "/" + selectedFolderId + "/export" var url = ApplicationBaseURL + "/" + selectedFolderId + "/export"
+ "?uid=" + contactIds.collect(encodeURIComponent).join("&uid="); + "?uid=" + contactIds.join("&uid=");
window.location.href = url; window.location.href = url;
} }
} }
function onMenuRawContact (event) { function onMenuRawContact (event) {
var cname = document.menuTarget.collect(function(row) { var cname = document.menuTarget.collect(function(row) {
return row.readAttribute("id").fromCSSIdentifier(); return row.readAttribute("id");
}); });
$(function() { $(function() {
openGenericWindow(URLForFolderID(Contact.currentAddressBook) openGenericWindow(URLForFolderID(Contact.currentAddressBook)
+ "/" + encodeURIComponent(cname) + "/raw"); + "/" + cname + "/raw");
}).delay(0.1); }).delay(0.1);
} }
@ -348,22 +350,22 @@ function actionContactCallback(http) {
} }
} }
function loadContact(cname) { function loadContact(idx) {
if (document.contactAjaxRequest) { if (document.contactAjaxRequest) {
document.contactAjaxRequest.aborted = true; document.contactAjaxRequest.aborted = true;
document.contactAjaxRequest.abort(); document.contactAjaxRequest.abort();
} }
if (cachedContacts[Contact.currentAddressBook + "/" + cname]) { if (cachedContacts[Contact.currentAddressBook + "/" + idx]) {
var div = $('contactView'); var div = $('contactView');
Contact.currentContactId = cname; Contact.currentContactId = idx;
div.innerHTML = cachedContacts[Contact.currentAddressBook + "/" + cname]; div.innerHTML = cachedContacts[Contact.currentAddressBook + "/" + idx];
} }
else { else {
var url = (URLForFolderID(Contact.currentAddressBook) var url = (URLForFolderID(Contact.currentAddressBook)
+ "/" + encodeURIComponent(cname) + "/view?noframe=1"); + "/" + encodeURIComponent(idx.unescapeHTML()) + "/view?noframe=1");
document.contactAjaxRequest document.contactAjaxRequest
= triggerAjaxRequest(url, contactLoadCallback, cname); = triggerAjaxRequest(url, contactLoadCallback, idx);
} }
} }
@ -416,9 +418,8 @@ function moveTo(uri) {
/* contact menu entries */ /* contact menu entries */
function onContactRowDblClick(event) { function onContactRowDblClick(event) {
var t = getTarget(event); var t = getTarget(event);
var cname = t.parentNode.getAttribute('id').fromCSSIdentifier(); var cname = t.parentNode.getAttribute('id');
cname = encodeURIComponent(cname);
openContactWindow(URLForFolderID(Contact.currentAddressBook) openContactWindow(URLForFolderID(Contact.currentAddressBook)
+ "/" + cname + "/edit", cname); + "/" + cname + "/edit", cname);
@ -437,7 +438,7 @@ function onContactSelectionChange(event) {
if (rows.length == 1) { if (rows.length == 1) {
var node = $(rows[0]); var node = $(rows[0]);
loadContact(node.getAttribute('id').fromCSSIdentifier()); loadContact(node.getAttribute('id'));
} }
else if (rows.length > 1) { else if (rows.length > 1) {
$('contactView').update(); $('contactView').update();
@ -478,9 +479,8 @@ function onToolbarEditSelectedContacts(event) {
} }
for (var i = 0; i < rows.length; i++) { for (var i = 0; i < rows.length; i++) {
var id = encodeURIComponent(rows[i].fromCSSIdentifier());
openContactWindow(URLForFolderID(Contact.currentAddressBook) openContactWindow(URLForFolderID(Contact.currentAddressBook)
+ "/" + id + "/edit", rows[i]); + "/" + rows[i] + "/edit", rows[i]);
} }
return false; return false;
@ -488,17 +488,16 @@ function onToolbarEditSelectedContacts(event) {
function onToolbarWriteToSelectedContacts(event) { function onToolbarWriteToSelectedContacts(event) {
var contactsList = $('contactsList'); var contactsList = $('contactsList');
var rowIds = contactsList.getSelectedRowsId(); var rows = contactsList.getSelectedRowsId();
var rowsWithEmail = 0;
if (rowIds.length == 0) { if (rows.length == 0) {
showAlertDialog(_("Please select a contact.")); showAlertDialog(_("Please select a contact."));
} }
else { else {
openMailComposeWindow(ApplicationBaseURL + "/../Mail/compose" openMailComposeWindow(ApplicationBaseURL + "/../Mail/compose"
+ "?folder=" + Contact.currentAddressBook.substring(1) + "?folder=" + Contact.currentAddressBook.substring(1)
+ "&uid=" + rowIds.collect(function(id) { + "&uid=" + rows.join("&uid="));
return encodeURIComponent(id.fromCSSIdentifier());
}).join("&uid="));
if (document.body.hasClassName("popup")) if (document.body.hasClassName("popup"))
window.close(); window.close();
} }
@ -525,28 +524,26 @@ function onToolbarDeleteSelectedContactsConfirm(dialogId) {
var contactsList = $('contactsList'); var contactsList = $('contactsList');
var rowIds = contactsList.getSelectedRowsId(); var rowIds = contactsList.getSelectedRowsId();
var urlstr = (URLForFolderID(Contact.currentAddressBook) + "/batchDelete"); var urlstr = (URLForFolderID(Contact.currentAddressBook) + "/batchDelete");
for (var i = 0; i < rowIds.length; i++) for (var i = 0; i < rowIds.length; i++)
$(rowIds[i]).hide(); $(rowIds[i]).hide();
triggerAjaxRequest(urlstr, onContactDeleteEventCallback, rowIds, triggerAjaxRequest(urlstr, onContactDeleteEventCallback, rowIds,
('ids=' + rowIds.collect(function(id) { ('ids=' + rowIds.collect(function (s) {
return encodeURIComponent(id.fromCSSIdentifier()); return encodeURIComponent(s.unescapeHTML());
}).join(",")), }).join(",")),
{ "Content-type": "application/x-www-form-urlencoded" }); { "Content-type": "application/x-www-form-urlencoded" });
} }
function onContactDeleteEventCallback(http) { function onContactDeleteEventCallback(http) {
var rowIds = http.callbackData;
if (http.readyState == 4) { if (http.readyState == 4) {
if (isHttpStatus204(http.status)) { if (isHttpStatus204(http.status)) {
var rowIds = http.callbackData;
var row; var row;
var nextRow = null; var nextRow = null;
for (var i = 0; i < rowIds.length; i++) { for (var i = 0; i < rowIds.length; i++) {
var id = rowIds[i].fromCSSIdentifier(); delete cachedContacts[Contact.currentAddressBook + "/" + rowIds[i]];
delete cachedContacts[Contact.currentAddressBook + "/" + id];
row = $(rowIds[i]); row = $(rowIds[i]);
var displayName = row.readAttribute("contactname"); var displayName = row.readAttribute("contactname");
if (Contact.currentContactId == id) { if (Contact.currentContactId == row) {
Contact.currentContactId = null; Contact.currentContactId = null;
} }
var nextRow = row.next("tr"); var nextRow = row.next("tr");
@ -558,7 +555,7 @@ function onContactDeleteEventCallback(http) {
} }
} }
if (nextRow) { if (nextRow) {
Contact.currentContactId = nextRow.getAttribute("id").fromCSSIdentifier(); Contact.currentContactId = nextRow.getAttribute("id");
nextRow.selectElement(); nextRow.selectElement();
loadContact(Contact.currentContactId); loadContact(Contact.currentContactId);
} }
@ -673,7 +670,7 @@ function onConfirmContactSelection(event) {
var contactsList = $("contactsList"); var contactsList = $("contactsList");
var rows = contactsList.getSelectedRows(); var rows = contactsList.getSelectedRows();
for (i = 0; i < rows.length; i++) { for (i = 0; i < rows.length; i++) {
var cid = rows[i].getAttribute("id").fromCSSIdentifier(); var cid = rows[i].getAttribute("id");
if (cid.endsWith(".vlf")) { if (cid.endsWith(".vlf")) {
addListToOpener(tag, Contact.currentAddressBook, currentAddressBookName, cid); addListToOpener(tag, Contact.currentAddressBook, currentAddressBookName, cid);
} }
@ -1298,7 +1295,7 @@ function onDocumentKeydown(event) {
else if (keyCode == Event.KEY_DOWN || else if (keyCode == Event.KEY_DOWN ||
keyCode == Event.KEY_UP) { keyCode == Event.KEY_UP) {
if (Contact.currentContactId) { if (Contact.currentContactId) {
var row = $(Contact.currentContactId.asCSSIdentifier()); var row = $(Contact.currentContactId);
var nextRow; var nextRow;
if (keyCode == Event.KEY_DOWN) if (keyCode == Event.KEY_DOWN)
nextRow = row.next("tr"); nextRow = row.next("tr");
@ -1322,7 +1319,7 @@ function onDocumentKeydown(event) {
// Select and load the next message // Select and load the next message
nextRow.selectElement(); nextRow.selectElement();
loadContact(nextRow.readAttribute("id").fromCSSIdentifier()); loadContact(nextRow.readAttribute("id"));
} }
Event.stop(event); Event.stop(event);
} }
@ -1468,12 +1465,11 @@ function onCategoriesMenuItemClick() {
var rowIds = contactsList.getSelectedRowsId(); var rowIds = contactsList.getSelectedRowsId();
if (rowIds.length > 0) { if (rowIds.length > 0) {
for (var i = 0; i < rowIds.length; i++) { for (var i = 0; i < rowIds.length; i++) {
var id = rowIds[i].fromCSSIdentifier();
var url = (URLForFolderID(Contact.currentAddressBook) var url = (URLForFolderID(Contact.currentAddressBook)
+ "/" + encodeURIComponent(id) + "/" + method); + "/" + rowIds[i] + "/" + method);
url += "?category=" + encodeURIComponent(this.category); url += "?category=" + encodeURIComponent(this.category);
triggerAjaxRequest(url, onCategoriesMenuItemCallback, triggerAjaxRequest(url, onCategoriesMenuItemCallback,
{ 'addressBook' : Contact.currentAddressBook, 'id' : id }); { 'addressBook' : Contact.currentAddressBook, 'id' : rowIds[i] });
if (set) { if (set) {
setCategoryOnNode($(rowIds[i]), this.category); setCategoryOnNode($(rowIds[i]), this.category);
} }
@ -1501,7 +1497,7 @@ function onCategoriesMenuItemCallback(http) {
function setCategoryOnNode(contactNode, category) { function setCategoryOnNode(contactNode, category) {
var catList = contactNode.getAttribute("categories"); var catList = contactNode.getAttribute("categories");
var catsArray = catList? catList.split(",") : []; var catsArray = catList.split(",");
if (catsArray.indexOf(category) == -1) { if (catsArray.indexOf(category) == -1) {
catsArray.push(category); catsArray.push(category);
contactNode.setAttribute("categories", catsArray.join(",")); contactNode.setAttribute("categories", catsArray.join(","));
@ -1611,9 +1607,9 @@ function dropSelectedContacts(action, toId) {
if ((!currentFolderIsRemote() || action != "move") if ((!currentFolderIsRemote() || action != "move")
&& fromId.substring(1) != toId) { && fromId.substring(1) != toId) {
var url = ApplicationBaseURL + fromId + "/" + action; var url = ApplicationBaseURL + "/" + fromId + "/" + action;
var uids = contactIds.collect(function(id) { var uids = contactIds.collect(function (s) {
return encodeURIComponent(id.fromCSSIdentifier()); return encodeURIComponent(s.unescapeHTML());
}).join('&uid='); }).join('&uid=');
triggerAjaxRequest(url, actionContactCallback, fromId, triggerAjaxRequest(url, actionContactCallback, fromId,
('folder='+ toId + '&uid=' + uids), ('folder='+ toId + '&uid=' + uids),

View file

@ -73,10 +73,6 @@ String.prototype.decodeEntities = function() {
}); });
}; };
String.prototype.unescapeHTMLEntities = function() {
return this.unescapeHTML().replace(/&quot;/g,'"');
};
String.prototype.asDate = function () { String.prototype.asDate = function () {
var newDate; var newDate;
var date = this.split("/"); var date = this.split("/");
@ -98,39 +94,20 @@ String.prototype.asDate = function () {
return newDate; return newDate;
}; };
RegExp.escape = function(text) {
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
}
var css_invalid_characters = [ '_' , '.', '#' , '@' , '*', ':' , ';' , ',' , ' ',
'(', ')', '[', ']', '{', '}',
"'", '"', '&', '+' ];
var css_escape_characters = [ '_U_', '_D_', '_H_', '_A_', '_S_', '_C_', '_SC_', '_CO_', '_SP_',
'_LP_', '_RP_', '_LS_', '_RQ_', '_LC_', '_RC_',
'_SQ_', '_DQ_', '_AM_', '_P_' ];
String.prototype.asCSSIdentifier = function() { String.prototype.asCSSIdentifier = function() {
var characters = [ '_' , '\\.', '#' , '@' , '\\*', ':' , ',' , ' '
, "'", '&', '\\+' ];
var escapeds = [ '_U_', '_D_', '_H_', '_A_', '_S_', '_C_', '_CO_',
'_SP_', '_SQ_', '_AM_', '_P_' ];
var newString = this; var newString = this;
for (var i = 0; i < css_invalid_characters.length; i++) { for (var i = 0; i < characters.length; i++) {
var re = new RegExp(RegExp.escape(css_invalid_characters[i]), 'g'); var re = new RegExp(characters[i], 'g');
newString = newString.replace(re, css_escape_characters[i]); newString = newString.replace(re, escapeds[i]);
} }
if (/^\d/.test(newString)) if (/^\d+/.test(newString)) {
newString = '_' + newString; newString = '_' + newString;
return newString;
};
String.prototype.fromCSSIdentifier = function() {
var newString = this;
if (/^_\d/.test(newString))
newString = newString.substring(1);
for (var i = 0; i < css_escape_characters.length; i++) {
var re = new RegExp(css_escape_characters[i], 'g');
newString = newString.replace(re, css_invalid_characters[i]);
} }
return newString; return newString;

View file

@ -1015,32 +1015,6 @@ function eventsListCallback(http) {
if (http.responseText.length > 0) { if (http.responseText.length > 0) {
var data = http.responseText.evalJSON(true); var data = http.responseText.evalJSON(true);
// [0] Event ID
// [1] Calendar ID
// [2] Calendar name
// [3] Status
// [4] Title
// [5] Start date
// [6] End date
// [7] Location
// [8] Is all day?
// [9] Classification (0 = public, 1, = private, 2 = confidential)
// [10] Category
// [11] Participants email addresses
// [12] Participants states
// [13] Owner
// [14] Is cyclic?
// [15] Next alarm
// [16] recurrence-id
// [17] isException
// [18] Editable?
// [19] Erasable?
// [20] Owner is organizer?
// [21] Description
// [22] Formatted start date
// [23] Formatted end date
for (var i = 0; i < data.length; i++) { for (var i = 0; i < data.length; i++) {
var row = createElement("tr"); var row = createElement("tr");
table.tBodies[0].appendChild(row); table.tBodies[0].appendChild(row);
@ -1082,12 +1056,12 @@ function eventsListCallback(http) {
td = createElement("td"); td = createElement("td");
row.appendChild(td); row.appendChild(td);
td.observe("mousedown", listRowMouseDownHandler, true); td.observe("mousedown", listRowMouseDownHandler, true);
td.update(data[i][22]); // start date td.update(data[i][21]); // start date
td = createElement("td"); td = createElement("td");
row.appendChild(td); row.appendChild(td);
td.observe("mousedown", listRowMouseDownHandler, true); td.observe("mousedown", listRowMouseDownHandler, true);
td.update(data[i][23]); // end date td.update(data[i][22]); // end date
td = createElement("td"); td = createElement("td");
row.appendChild(td); row.appendChild(td);
@ -1191,9 +1165,8 @@ function tasksListCallback(http) {
// [12] Owner // [12] Owner
// [13] recurrence-id // [13] recurrence-id
// [14] isException // [14] isException
// [15] Description // [15] Status CSS class (duelater, completed, etc)
// [16] Status CSS class (duelater, completed, etc) // [16] Due date (formatted)
// [17] Due date (formatted)
for (var i = 0; i < data.length; i++) { for (var i = 0; i < data.length; i++) {
var row = createElement("tr"); var row = createElement("tr");
@ -1209,12 +1182,16 @@ function tasksListCallback(http) {
if (rTime) if (rTime)
id += "-" + escape(rTime); id += "-" + escape(rTime);
row.setAttribute("id", id); row.setAttribute("id", id);
//row.cname = escape(data[i][0]);
//row.calendar = calendar;
if (rTime) if (rTime)
row.recurrenceTime = escape(rTime); row.recurrenceTime = escape(rTime);
row.isException = data[i][14]; row.isException = data[i][14];
//row.setAttribute("id", calendar + "-" + cname);
//listItem.addClassName(data[i][5]); // Classification //listItem.addClassName(data[i][5]); // Classification
row.addClassName(data[i][16]); // status //row.addClassName(data[i][14]); // status
row.addClassName("taskRow"); row.addClassName("taskRow");
row.calendar = calendar; row.calendar = calendar;
row.cname = cname; row.cname = cname;
@ -1259,8 +1236,8 @@ function tasksListCallback(http) {
cell = createElement("td"); cell = createElement("td");
row.appendChild(cell); row.appendChild(cell);
if (data[i][17]) if (data[i][16])
cell.update(data[i][17]); // end date cell.update(data[i][16]); // end date
cell = createElement("td"); cell = createElement("td");
row.appendChild(cell); row.appendChild(cell);

View file

@ -253,9 +253,6 @@ cp Scripts/logrotate ${RPM_BUILD_ROOT}/etc/logrotate.d/sogo
%if 0%{?_with_systemd} %if 0%{?_with_systemd}
cp Scripts/sogo-systemd-redhat ${RPM_BUILD_ROOT}/usr/lib/systemd/system/sogod.service cp Scripts/sogo-systemd-redhat ${RPM_BUILD_ROOT}/usr/lib/systemd/system/sogod.service
chmod 644 ${RPM_BUILD_ROOT}/usr/lib/systemd/system/sogod.service chmod 644 ${RPM_BUILD_ROOT}/usr/lib/systemd/system/sogod.service
mkdir ${RPM_BUILD_ROOT}/etc/tmpfiles.d
cp Scripts/sogo-systemd.conf ${RPM_BUILD_ROOT}/etc/tmpfiles.d/sogo.conf
chmod 644 ${RPM_BUILD_ROOT}/etc/tmpfiles.d/sogo.conf
%else %else
cp Scripts/sogo-init.d-redhat ${RPM_BUILD_ROOT}/etc/init.d/sogod cp Scripts/sogo-init.d-redhat ${RPM_BUILD_ROOT}/etc/init.d/sogod
chmod 755 ${RPM_BUILD_ROOT}/etc/init.d/sogod chmod 755 ${RPM_BUILD_ROOT}/etc/init.d/sogod
@ -292,7 +289,6 @@ rm -fr ${RPM_BUILD_ROOT}
%if 0%{?_with_systemd} %if 0%{?_with_systemd}
/usr/lib/systemd/system/sogod.service /usr/lib/systemd/system/sogod.service
/etc/tmpfiles.d/sogo.conf
%else %else
/etc/init.d/sogod /etc/init.d/sogod
%endif %endif