1/* 2 Copyright (C) 2006-2021 Inverse inc. 3 4 This file is part of SOGo 5 6 SOGo is free software; you can redistribute it and/or modify it under 7 the terms of the GNU Lesser General Public License as published by the 8 Free Software Foundation; either version 2, or (at your option) any 9 later version. 10 11 SOGo is distributed in the hope that it will be useful, but WITHOUT ANY 12 WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 14 License for more details. 15 16 You should have received a copy of the GNU Lesser General Public 17 License along with OGo; see the file COPYING. If not, write to the 18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 19 02111-1307, USA. 20*/ 21 22#import <Foundation/Foundation.h> 23#import <SoObjects/SOGo/NSArray+Utilities.h> 24#import <SoObjects/SOGo/NSDictionary+Utilities.h> 25#import <SoObjects/SOGo/NSString+Utilities.h> 26 27#import <NGObjWeb/NSException+HTTP.h> 28#import <NGObjWeb/WOResponse.h> 29#define COMPILING_NGOBJWEB 1 /* we want httpRequest for parsing multi-part 30 form data */ 31#import <NGObjWeb/WORequest.h> 32#undef COMPILING_NGOBJWEB 33#import <NGExtensions/NSString+misc.h> 34#import <NGExtensions/NSNull+misc.h> 35#import <NGExtensions/NGBase64Coding.h> 36#import <NGHttp/NGHttpRequest.h> 37#import <NGMime/NGMimeMultipartBody.h> 38 39#import <GDLAccess/EOAdaptorChannel.h> 40#import <GDLAccess/EOAdaptorContext.h> 41#import <GDLContentStore/GCSFolder.h> 42 43#import <Contacts/NSDictionary+LDIF.h> 44 45#import <SoObjects/Contacts/NGVCard+SOGo.h> 46#import <SoObjects/Contacts/NGVList+SOGo.h> 47#import <SoObjects/Contacts/SOGoContactGCSEntry.h> 48#import <SoObjects/Contacts/SOGoContactLDIFEntry.h> 49#import <SoObjects/Contacts/SOGoContactGCSList.h> 50#import <SoObjects/Contacts/SOGoContactGCSFolder.h> 51 52#import <SOGo/NSString+Utilities.h> 53 54#import "UIxContactFolderActions.h" 55 56static NSArray *photoTags = nil; 57 58@implementation UIxContactFolderActions 59 60+ (void) initialize 61{ 62 if (!photoTags) 63 { 64 photoTags = [[NSArray alloc] initWithObjects: @"jpegphoto", @"photo", @"thumbnailphoto", nil]; 65 } 66} 67 68/* actions */ 69 70- (id <WOActionResults>) exportAction 71{ 72 WOResponse *response; 73 NSArray *contactsId; 74 NSEnumerator *uids; 75 NSString *uid, *filename, *disposition; 76 id currentChild; 77 SOGoContactGCSFolder *sourceFolder; 78 NSMutableString *content; 79 80 content = [NSMutableString string]; 81 sourceFolder = [self clientObject]; 82 contactsId = [[[[context request] contentAsString] objectFromJSONString] objectForKey: @"uids"]; 83 84 if (!contactsId) 85 contactsId = [sourceFolder toOneRelationshipKeys]; 86 87 uids = [contactsId objectEnumerator]; 88 while ((uid = [uids nextObject])) 89 { 90 currentChild = [sourceFolder lookupName: uid 91 inContext: [self context] 92 acquire: NO]; 93 if ([currentChild respondsToSelector: @selector (vCard)]) 94 [content appendFormat: @"%@", [[currentChild ldifRecord] ldifRecordAsString]]; 95 else if ([currentChild respondsToSelector: @selector (vList)]) 96 [content appendFormat: @"%@", [[currentChild vList] ldifString]]; 97 [content appendString: @"\n"]; 98 } 99 100 response = [context response]; 101 [response setHeader: @"application/directory; charset=utf-8" 102 forKey: @"content-type"]; 103 filename = [NSString stringWithFormat: @"%@.ldif", 104 [[sourceFolder displayName] asQPSubjectString: @"utf-8"]]; 105 disposition = [NSString stringWithFormat: @"attachment; filename=\"%@\"", filename]; 106 [response setHeader: disposition forKey: @"Content-Disposition"]; 107 [response setContent: [content dataUsingEncoding: NSUTF8StringEncoding]]; 108 109 return response; 110} 111 112- (id <WOActionResults>) importAction 113{ 114 WORequest *request; 115 WOResponse *response; 116 id data; 117 NSMutableDictionary *rc; 118 NSString *fileContent; 119 int imported = 0; 120 121 122 request = [context request]; 123 rc = [NSMutableDictionary dictionary]; 124 data = [[request httpRequest] body]; 125 126 // We got an exception, that means the file upload limit 127 // has been reached. 128 if ([data isKindOfClass: [NSException class]]) 129 { 130 response = [self responseWithStatus: 507]; 131 return response; 132 } 133 134 data = [[[data parts] lastObject] body]; 135 136 fileContent = [[NSString alloc] initWithData: (NSData *) data 137 encoding: NSUTF8StringEncoding]; 138 [fileContent autorelease]; 139 140 if (fileContent && [fileContent length]) 141 { 142 if ([fileContent hasPrefix: @"dn:"]) 143 imported = [self importLdifData: fileContent]; 144 else if ([fileContent hasPrefix: @"BEGIN:"]) 145 imported = [self importVcardData: fileContent]; 146 else 147 imported = 0; 148 } 149 150 [rc setObject: [NSNumber numberWithInt: imported] forKey: @"imported"]; 151 152 response = [self responseWithStatus: 200]; 153 [response setHeader: @"text/html" forKey: @"content-type"]; 154 [(WOResponse*)response appendContentString: [rc jsonRepresentation]]; 155 156 return response; 157} 158 159- (int) importLdifData: (NSString *) ldifData 160{ 161 NSMutableArray *ldifListEntries; 162 NSMutableDictionary *entry, *encodedEntry; 163 SOGoContactLDIFEntry *ldifEntry; 164 NSArray *ldifContacts, *lines; 165 SOGoContactGCSFolder *folder; 166 NSEnumerator *keyEnumerator; 167 NSString *key, *uid, *line; 168 NGVCard *vCard; 169 NGVList *vList; 170 id value, values; 171 172 NSRange r; 173 int i, j, count, linesCount, len; 174 int rc; 175 176 folder = [self clientObject]; 177 ldifListEntries = [NSMutableArray array]; 178 ldifContacts = [ldifData componentsSeparatedByString: @"\ndn"]; 179 count = [ldifContacts count]; 180 rc = 0; 181 182 for (i = 0; i < count; i++) 183 { 184 encodedEntry = [NSMutableDictionary dictionary]; 185 lines = [[ldifContacts objectAtIndex: i] 186 componentsSeparatedByString: @"\n"]; 187 188 key = NULL; 189 linesCount = [lines count]; 190 for (j = 0; j < linesCount; j++) 191 { 192 line = [lines objectAtIndex: j]; 193 len = [line length]; 194 195 /* we check for trailing \r and we strip them */ 196 if (len && [line characterAtIndex: len-1] == '\r') 197 line = [line substringToIndex: len-1]; 198 199 /* skip embedded comment lines */ 200 if ([line hasPrefix: @"#"]) 201 { 202 key = NULL; 203 continue; 204 } 205 206 /* handle continuation lines */ 207 if ([line hasPrefix: @" "]) 208 { 209 if (key != NULL) 210 { 211 values = [encodedEntry objectForKey: key]; 212 if ([values isKindOfClass: [NSArray class]]) 213 { 214 // Multiple values for key 215 value = [[values lastObject] stringByAppendingString: [line substringFromIndex: 1]]; 216 [values replaceObjectAtIndex: [values count] - 1 217 withObject: value]; 218 } 219 else 220 { 221 // Single value for key 222 value = [values stringByAppendingString: [line substringFromIndex: 1]]; 223 [encodedEntry setValue: value forKey: key]; 224 } 225 } 226 continue; 227 } 228 229 r = [line rangeOfString: @": "]; 230 if (r.location != NSNotFound) 231 { 232 key = [[line substringToIndex: r.location] lowercaseString]; 233 value = [line substringFromIndex: NSMaxRange(r)]; 234 235 if ([key length] == 0) 236 key = @"dn"; 237 238 if ((values = [encodedEntry objectForKey: key])) 239 { 240 if (![values isKindOfClass: [NSArray class]]) 241 values = [NSMutableArray arrayWithObject: values]; 242 [values addObject: value]; 243 [encodedEntry setValue: values forKey: key]; 244 } 245 else 246 [encodedEntry setValue: value forKey: key]; 247 } 248 else 249 { 250 break; 251 } 252 } 253 254 /* decode Base64-encoded attributes */ 255 entry = [NSMutableDictionary dictionary]; 256 keyEnumerator = [encodedEntry keyEnumerator]; 257 while ((key = [keyEnumerator nextObject])) 258 { 259 values = [encodedEntry valueForKey: key]; 260 if ([key hasSuffix: @":"]) 261 { 262 key = [key substringToIndex: [key length] - 1]; 263 if ([photoTags containsObject: key]) 264 values = [values dataByDecodingBase64]; 265 else if ([values isKindOfClass: [NSArray class]]) 266 { 267 for (j = 0; j < [values count]; j++) 268 { 269 value = [values objectAtIndex: j]; 270 value = [value stringByDecodingBase64]; 271 [values replaceObjectAtIndex: j 272 withObject: value]; 273 } 274 } 275 else 276 values = [values stringByDecodingBase64]; 277 } 278 279 // Standard key recognized in NGCards 280 if ([photoTags containsObject: key]) 281 key = @"photo"; 282 283 [entry setValue: values forKey: key]; 284 } 285 286 uid = [folder globallyUniqueObjectId]; 287 ldifEntry = [SOGoContactLDIFEntry contactEntryWithName: uid 288 withLDIFEntry: entry 289 inContainer: folder]; 290 if (ldifEntry) 291 { 292 if ([ldifEntry isList]) 293 { 294 // Postpone importation of lists 295 [ldifListEntries addObject: ldifEntry]; 296 } 297 else 298 { 299 vCard = [ldifEntry vCard]; 300 if ([self importVcard: vCard]) 301 { 302 rc++; 303 } 304 } 305 306 } 307 } 308 309 // Force update of quick table 310 [[[[[self clientObject] ocsFolder] acquireQuickChannel] adaptorContext] commitTransaction]; 311 312 // Convert groups to vLists 313 count = [ldifListEntries count]; 314 for (i = 0; i < count; i++) 315 { 316 vList = [[ldifListEntries objectAtIndex: i] vList]; 317 if ([self importVlist: vList]) 318 rc++; 319 } 320 321 return rc; 322} 323 324- (int) importVcardData: (NSString *) vcardData 325{ 326 NSAutoreleasePool *pool; 327 NSArray *allCards; 328 int rc, count; 329 330 rc = 0; 331 332 pool = [[NSAutoreleasePool alloc] init]; 333 allCards = [NGVCard parseFromSource: vcardData]; 334 335 count = [allCards count]; 336 if (allCards && count) 337 { 338 int i; 339 340 for (i = 0; i < count; i++) 341 { 342 if (![self importVcard: [allCards objectAtIndex: i]]) 343 { 344 rc = 0; 345 break; 346 } 347 else 348 rc++; 349 } 350 } 351 352 RELEASE(pool); 353 354 return rc; 355} 356 357- (BOOL) importVcard: (NGVCard *) card 358{ 359 SOGoContactGCSFolder *folder; 360 SOGoContactGCSEntry *contact; 361 NSAutoreleasePool *pool; 362 NSString *uid; 363 364 BOOL rc = NO; 365 366 if (card) 367 { 368 pool = [[NSAutoreleasePool alloc] init]; 369 folder = [self clientObject]; 370 371 // TODO: shall we add .vcf as in [SOGoContactGCSEntry copyToFolder:] 372 uid = [card uid]; 373 if (![uid length]) 374 { 375 uid = [folder globallyUniqueObjectId]; 376 [card setUid: uid]; 377 } 378 contact = [SOGoContactGCSEntry objectWithName: uid 379 inContainer: folder]; 380 [contact setIsNew: YES]; 381 [contact saveComponent: card]; 382 383 rc = YES; 384 RELEASE(pool); 385 } 386 387 return rc; 388} 389 390 - (BOOL) importVlist: (NGVList *) list 391{ 392 SOGoContactGCSFolder *folder; 393 SOGoContactGCSList *contact; 394 NSAutoreleasePool *pool; 395 NSString *uid; 396 397 BOOL rc = NO; 398 399 if (list) 400 { 401 pool = [[NSAutoreleasePool alloc] init]; 402 folder = [self clientObject]; 403 404 // TODO: shall we add .vcf as in [SOGoContactGCSEntry copyToFolder:] 405 uid = [list uid]; 406 contact = [SOGoContactGCSList objectWithName: uid 407 inContainer: folder]; 408 [contact setIsNew: YES]; 409 [contact saveComponent: list]; 410 411 rc = YES; 412 RELEASE(pool); 413 } 414 415 return rc; 416} 417 418@end /* UIxContactFolderActions */ 419