/* Copyright (C) 2006-2021 Inverse inc. This file is part of SOGo SOGo is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. SOGo is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with OGo; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #import #import #import #import #import #import #define COMPILING_NGOBJWEB 1 /* we want httpRequest for parsing multi-part form data */ #import #undef COMPILING_NGOBJWEB #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import "UIxContactFolderActions.h" static NSArray *photoTags = nil; @implementation UIxContactFolderActions + (void) initialize { if (!photoTags) { photoTags = [[NSArray alloc] initWithObjects: @"jpegphoto", @"photo", @"thumbnailphoto", nil]; } } /* actions */ - (id ) exportAction { WOResponse *response; NSArray *contactsId; NSEnumerator *uids; NSString *uid, *filename, *disposition; id currentChild; SOGoContactGCSFolder *sourceFolder; NSMutableString *content; content = [NSMutableString string]; sourceFolder = [self clientObject]; contactsId = [[[[context request] contentAsString] objectFromJSONString] objectForKey: @"uids"]; if (!contactsId) contactsId = [sourceFolder toOneRelationshipKeys]; uids = [contactsId objectEnumerator]; while ((uid = [uids nextObject])) { currentChild = [sourceFolder lookupName: uid inContext: [self context] acquire: NO]; if ([currentChild respondsToSelector: @selector (vCard)]) [content appendFormat: @"%@", [[currentChild ldifRecord] ldifRecordAsString]]; else if ([currentChild respondsToSelector: @selector (vList)]) [content appendFormat: @"%@", [[currentChild vList] ldifString]]; [content appendString: @"\n"]; } response = [context response]; [response setHeader: @"application/directory; charset=utf-8" forKey: @"content-type"]; filename = [NSString stringWithFormat: @"%@.ldif", [[sourceFolder displayName] asQPSubjectString: @"utf-8"]]; disposition = [NSString stringWithFormat: @"attachment; filename=\"%@\"", filename]; [response setHeader: disposition forKey: @"Content-Disposition"]; [response setContent: [content dataUsingEncoding: NSUTF8StringEncoding]]; return response; } - (id ) importAction { WORequest *request; WOResponse *response; id data; NSMutableDictionary *rc; NSString *fileContent; int imported = 0; request = [context request]; rc = [NSMutableDictionary dictionary]; data = [[request httpRequest] body]; // We got an exception, that means the file upload limit // has been reached. if ([data isKindOfClass: [NSException class]]) { response = [self responseWithStatus: 507]; return response; } data = [[[data parts] lastObject] body]; fileContent = [[NSString alloc] initWithData: (NSData *) data encoding: NSUTF8StringEncoding]; [fileContent autorelease]; if (fileContent && [fileContent length]) { if ([fileContent hasPrefix: @"dn:"]) imported = [self importLdifData: fileContent]; else if ([fileContent hasPrefix: @"BEGIN:"]) imported = [self importVcardData: fileContent]; else imported = 0; } [rc setObject: [NSNumber numberWithInt: imported] forKey: @"imported"]; response = [self responseWithStatus: 200]; [response setHeader: @"text/html" forKey: @"content-type"]; [(WOResponse*)response appendContentString: [rc jsonRepresentation]]; return response; } - (int) importLdifData: (NSString *) ldifData { NSMutableArray *ldifListEntries; NSMutableDictionary *entry, *encodedEntry; SOGoContactLDIFEntry *ldifEntry; NSArray *ldifContacts, *lines; SOGoContactGCSFolder *folder; NSEnumerator *keyEnumerator; NSString *key, *uid, *line; NGVCard *vCard; NGVList *vList; id value, values; NSRange r; int i, j, count, linesCount, len; int rc; folder = [self clientObject]; ldifListEntries = [NSMutableArray array]; ldifContacts = [ldifData componentsSeparatedByString: @"\ndn"]; count = [ldifContacts count]; rc = 0; for (i = 0; i < count; i++) { encodedEntry = [NSMutableDictionary dictionary]; lines = [[ldifContacts objectAtIndex: i] componentsSeparatedByString: @"\n"]; key = NULL; linesCount = [lines count]; for (j = 0; j < linesCount; j++) { line = [lines objectAtIndex: j]; len = [line length]; /* we check for trailing \r and we strip them */ if (len && [line characterAtIndex: len-1] == '\r') line = [line substringToIndex: len-1]; /* skip embedded comment lines */ if ([line hasPrefix: @"#"]) { key = NULL; continue; } /* handle continuation lines */ if ([line hasPrefix: @" "]) { if (key != NULL) { values = [encodedEntry objectForKey: key]; if ([values isKindOfClass: [NSArray class]]) { // Multiple values for key value = [[values lastObject] stringByAppendingString: [line substringFromIndex: 1]]; [values replaceObjectAtIndex: [values count] - 1 withObject: value]; } else { // Single value for key value = [values stringByAppendingString: [line substringFromIndex: 1]]; [encodedEntry setValue: value forKey: key]; } } continue; } r = [line rangeOfString: @": "]; if (r.location != NSNotFound) { key = [[line substringToIndex: r.location] lowercaseString]; value = [line substringFromIndex: NSMaxRange(r)]; if ([key length] == 0) key = @"dn"; if ((values = [encodedEntry objectForKey: key])) { if (![values isKindOfClass: [NSArray class]]) values = [NSMutableArray arrayWithObject: values]; [values addObject: value]; [encodedEntry setValue: values forKey: key]; } else [encodedEntry setValue: value forKey: key]; } else { break; } } /* decode Base64-encoded attributes */ entry = [NSMutableDictionary dictionary]; keyEnumerator = [encodedEntry keyEnumerator]; while ((key = [keyEnumerator nextObject])) { values = [encodedEntry valueForKey: key]; if ([key hasSuffix: @":"]) { key = [key substringToIndex: [key length] - 1]; if ([photoTags containsObject: key]) values = [values dataByDecodingBase64]; else if ([values isKindOfClass: [NSArray class]]) { for (j = 0; j < [values count]; j++) { value = [values objectAtIndex: j]; value = [value stringByDecodingBase64]; [values replaceObjectAtIndex: j withObject: value]; } } else values = [values stringByDecodingBase64]; } // Standard key recognized in NGCards if ([photoTags containsObject: key]) key = @"photo"; [entry setValue: values forKey: key]; } uid = [folder globallyUniqueObjectId]; ldifEntry = [SOGoContactLDIFEntry contactEntryWithName: uid withLDIFEntry: entry inContainer: folder]; if (ldifEntry) { if ([ldifEntry isList]) { // Postpone importation of lists [ldifListEntries addObject: ldifEntry]; } else { vCard = [ldifEntry vCard]; if ([self importVcard: vCard]) { rc++; } } } } // Force update of quick table [[[[[self clientObject] ocsFolder] acquireQuickChannel] adaptorContext] commitTransaction]; // Convert groups to vLists count = [ldifListEntries count]; for (i = 0; i < count; i++) { vList = [[ldifListEntries objectAtIndex: i] vList]; if ([self importVlist: vList]) rc++; } return rc; } - (int) importVcardData: (NSString *) vcardData { NSAutoreleasePool *pool; NSArray *allCards; int rc, count; rc = 0; pool = [[NSAutoreleasePool alloc] init]; allCards = [NGVCard parseFromSource: vcardData]; count = [allCards count]; if (allCards && count) { int i; for (i = 0; i < count; i++) { if (![self importVcard: [allCards objectAtIndex: i]]) { rc = 0; break; } else rc++; } } RELEASE(pool); return rc; } - (BOOL) importVcard: (NGVCard *) card { SOGoContactGCSFolder *folder; SOGoContactGCSEntry *contact; NSAutoreleasePool *pool; NSString *uid; BOOL rc = NO; if (card) { pool = [[NSAutoreleasePool alloc] init]; folder = [self clientObject]; // TODO: shall we add .vcf as in [SOGoContactGCSEntry copyToFolder:] uid = [card uid]; if (![uid length]) { uid = [folder globallyUniqueObjectId]; [card setUid: uid]; } contact = [SOGoContactGCSEntry objectWithName: uid inContainer: folder]; [contact setIsNew: YES]; [contact saveComponent: card]; rc = YES; RELEASE(pool); } return rc; } - (BOOL) importVlist: (NGVList *) list { SOGoContactGCSFolder *folder; SOGoContactGCSList *contact; NSAutoreleasePool *pool; NSString *uid; BOOL rc = NO; if (list) { pool = [[NSAutoreleasePool alloc] init]; folder = [self clientObject]; // TODO: shall we add .vcf as in [SOGoContactGCSEntry copyToFolder:] uid = [list uid]; contact = [SOGoContactGCSList objectWithName: uid inContainer: folder]; [contact setIsNew: YES]; [contact saveComponent: list]; rc = YES; RELEASE(pool); } return rc; } @end /* UIxContactFolderActions */