1/* SOGo+DAV.m - this file is part of SOGo 2 * 3 * Copyright (C) 2010-2013 Inverse inc. 4 * 5 * This file is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 2, or (at your option) 8 * any later version. 9 * 10 * This file is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program; see the file COPYING. If not, write to 17 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, 18 * Boston, MA 02111-1307, USA. 19 */ 20 21#import <Foundation/NSDictionary.h> 22#import <Foundation/NSEnumerator.h> 23 24#import <NGObjWeb/NSException+HTTP.h> 25#import <NGObjWeb/SoObject+SoDAV.h> 26#import <NGObjWeb/WOContext+SoObjects.h> 27#import <NGObjWeb/WORequest.h> 28#import <NGExtensions/NSObject+Logs.h> 29 30#import <DOM/DOMDocument.h> 31#import <DOM/DOMNode.h> 32#import <DOM/DOMProtocols.h> 33#import <SaxObjC/XMLNamespaces.h> 34 35#import <SOGo/NSArray+Utilities.h> 36#import <SOGo/NSObject+DAV.h> 37#import <SOGo/NSObject+Utilities.h> 38#import <SOGo/NSString+DAV.h> 39#import <SOGo/WORequest+SOGo.h> 40#import <SOGo/WOResponse+SOGo.h> 41#import <SOGo/SOGoSource.h> 42#import <SOGo/SOGoPermissions.h> 43#import <SOGo/SOGoUser.h> 44#import <SOGo/SOGoUserFolder.h> 45#import <SOGo/SOGoUserManager.h> 46 47#import "SOGo+DAV.h" 48 49@implementation SOGo (SOGoWebDAVExtensions) 50 51- (NSArray *) davPrincipalURL 52{ 53 NSArray *principalURL; 54 NSString *classes; 55 WOContext *context; 56 57 context = [self context]; 58 if ([[context request] isICal4]) 59 { 60 classes = [[self davComplianceClassesInContext: context] 61 componentsJoinedByString: @", "]; 62 [[context response] setHeader: classes forKey: @"DAV"]; 63 } 64 65 principalURL = [NSArray arrayWithObjects: @"href", @"DAV:", @"D", 66 [self davURLAsString], nil]; 67 68 return [NSArray arrayWithObject: principalURL]; 69} 70 71- (WOResponse *) davPrincipalSearchPropertySet: (WOContext *) localContext 72{ 73 static NSDictionary *davResponse = nil; 74 NSMutableArray *propertySet; 75 NSDictionary *property, *prop, *description; 76 NSArray *currentValue, *properties, *descriptions, *namespaces; 77 int count, max; 78 WOResponse *r; 79 80 if (!davResponse) 81 { 82 properties = [NSArray arrayWithObjects: 83 @"calendar-user-type", 84 @"calendar-user-address-set", 85 @"calendar-user-type", 86 @"displayname", 87 @"first-name", 88 @"last-name", 89 @"email-address-set", 90 nil]; 91 descriptions 92 = [properties resultsOfSelector: @selector (asDAVPropertyDescription)]; 93 namespaces = [NSArray arrayWithObjects: 94 XMLNS_CALDAV, 95 XMLNS_CALDAV, 96 XMLNS_CALDAV, 97 XMLNS_WEBDAV, 98 XMLNS_CalendarServerOrg, 99 XMLNS_CalendarServerOrg, 100 XMLNS_CalendarServerOrg, 101 nil]; 102 103 max = [properties count]; 104 propertySet = [NSMutableArray arrayWithCapacity: max]; 105 for (count = 0; count < max; count++) 106 { 107 property = davElement([properties objectAtIndex: count], 108 [namespaces objectAtIndex: count]); 109 prop = davElementWithContent(@"prop", @"DAV:", 110 property); 111 description 112 = davElementWithContent(@"description", @"DAV:", 113 [descriptions objectAtIndex: count]); 114 currentValue = [NSArray arrayWithObjects: prop, description, nil]; 115 [propertySet 116 addObject: davElementWithContent(@"principal-search-property", 117 @"DAV:", 118 currentValue)]; 119 } 120 davResponse = davElementWithContent (@"principal-search-property-set", 121 @"DAV:", 122 propertySet); 123 [davResponse retain]; 124 } 125 126 r = [localContext response]; 127 [r prepareDAVResponse]; 128 [r appendContentString: [davResponse asWebDavStringWithNamespaces: nil]]; 129 130 return r; 131} 132 133#warning all REPORT method should be standardized... 134- (NSDictionary *) _formalizePrincipalMatchResponse: (NSArray *) hrefs 135{ 136 NSDictionary *multiStatus; 137 NSEnumerator *hrefList; 138 NSString *currentHref; 139 NSMutableArray *responses; 140 NSArray *responseElements; 141 142 responses = [NSMutableArray array]; 143 144 hrefList = [hrefs objectEnumerator]; 145 while ((currentHref = [hrefList nextObject])) 146 { 147 responseElements 148 = [NSArray arrayWithObjects: davElementWithContent (@"href", 149 XMLNS_WEBDAV, 150 currentHref), 151 davElementWithContent (@"status", XMLNS_WEBDAV, 152 @"HTTP/1.1 200 OK"), 153 nil]; 154 [responses addObject: davElementWithContent (@"response", XMLNS_WEBDAV, 155 responseElements)]; 156 } 157 158 multiStatus = davElementWithContent (@"multistatus", XMLNS_WEBDAV, 159 responses); 160 161 return multiStatus; 162} 163 164- (NSDictionary *) _handlePrincipalMatchSelfInContext: (WOContext *) localContext 165{ 166 NSString *davURL, *userLogin; 167 NSArray *principalURL; 168 169 davURL = [self davURLAsString]; 170 userLogin = [[localContext activeUser] login]; 171 principalURL 172 = [NSArray arrayWithObject: [NSString stringWithFormat: @"%@%@/", davURL, 173 userLogin]]; 174 return [self _formalizePrincipalMatchResponse: principalURL]; 175} 176 177- (void) _fillArrayWithPrincipalsOwnedBySelf: (NSMutableArray *) hrefs 178 inContext: (WOContext *) localContext 179{ 180 NSArray *roles; 181 182 roles = [[localContext activeUser] rolesForObject: self 183 inContext: localContext]; 184 if ([roles containsObject: SoRole_Owner]) 185 [hrefs addObject: [self davURLAsString]]; 186} 187 188- (NSDictionary *) 189 _handlePrincipalMatchPrincipalProperty: (id <DOMElement>) child 190 inContext: (WOContext *) localContext 191{ 192 NSMutableArray *hrefs; 193 NSDictionary *response; 194 195 hrefs = [NSMutableArray array]; 196 [self _fillArrayWithPrincipalsOwnedBySelf: hrefs 197 inContext: localContext]; 198 199 response = [self _formalizePrincipalMatchResponse: hrefs]; 200 201 return response; 202} 203 204- (NSDictionary *) _handlePrincipalMatchReport: (id <DOMDocument>) document 205 inContext: (WOContext *) localContext 206{ 207 NSDictionary *response; 208 id <DOMElement> documentElement, queryChild; 209 NSArray *children; 210 NSString *queryTag, *error; 211 212 documentElement = [document documentElement]; 213 children = [self domNode: documentElement 214 getChildNodesByType: DOM_ELEMENT_NODE]; 215 if ([children count] == 1) 216 { 217 queryChild = [children objectAtIndex: 0]; 218 queryTag = [queryChild tagName]; 219 if ([queryTag isEqualToString: @"self"]) 220 response = [self _handlePrincipalMatchSelfInContext: localContext]; 221 else if ([queryTag isEqualToString: @"principal-property"]) 222 response = [self _handlePrincipalMatchPrincipalProperty: queryChild 223 inContext: localContext]; 224 else 225 { 226 error = (@"Query element must be either '{DAV:}principal-property'" 227 @" or '{DAV:}self'"); 228 response = [NSException exceptionWithHTTPStatus: 400 229 reason: error]; 230 } 231 } 232 else 233 { 234 error = (@"Query must have one element: '{DAV:}principal-property'" 235 @" or '{DAV:}self'"); 236 response = [NSException exceptionWithHTTPStatus: 400 237 reason: error]; 238 } 239 240 return response; 241} 242 243- (WOResponse *) davPrincipalMatch: (WOContext *) localContext 244{ 245 WOResponse *r; 246 id <DOMDocument> document; 247 NSDictionary *xmlResponse; 248 249 r = [localContext response]; 250 251 document = [[localContext request] contentAsDOMDocument]; 252 xmlResponse = [self _handlePrincipalMatchReport: document 253 inContext: localContext]; 254 if ([xmlResponse isKindOfClass: [NSException class]]) 255 r = (WOResponse *) xmlResponse; 256 else 257 { 258 [r prepareDAVResponse]; 259 [r appendContentString: [xmlResponse asWebDavStringWithNamespaces: nil]]; 260 } 261 262 return r; 263} 264 265- (void) _fillMatches: (NSMutableDictionary *) matches 266 fromElement: (NSObject <DOMElement> *) searchElement 267{ 268 NSObject <DOMNodeList> *list; 269 NSObject <DOMNode> *valueNode; 270 NSArray *elements; 271 NSString *property=nil, *match=nil; 272 273 list = [searchElement getElementsByTagName: @"prop"]; 274 if ([list length]) 275 { 276 elements = [self domNode: [list objectAtIndex: 0] 277 getChildNodesByType: DOM_ELEMENT_NODE]; 278 if ([elements count]) 279 { 280 valueNode = [elements objectAtIndex: 0]; 281 property = [NSString stringWithFormat: @"{%@}%@", 282 [valueNode namespaceURI], 283 [valueNode nodeName]]; 284 } 285 } 286 list = [searchElement getElementsByTagName: @"match"]; 287 if ([list length]) 288 { 289 valueNode = [[list objectAtIndex: 0] firstChild]; 290 match = [valueNode nodeValue]; 291 } 292 293 [matches setObject: match forKey: property]; 294} 295 296- (void) _fillProperties: (NSMutableArray *) properties 297 fromElement: (NSObject <DOMElement> *) propElement 298{ 299 NSEnumerator *elements; 300 NSObject <DOMElement> *propNode; 301 NSString *property; 302 303 elements = [[self domNode: propElement 304 getChildNodesByType: DOM_ELEMENT_NODE] 305 objectEnumerator]; 306 while ((propNode = [elements nextObject])) 307 { 308 property = [NSString stringWithFormat: @"{%@}%@", 309 [propNode namespaceURI], 310 [propNode nodeName]]; 311 [properties addObject: property]; 312 } 313} 314 315- (void) _fillPrincipalMatches: (NSMutableDictionary *) matches 316 andProperties: (NSMutableArray *) properties 317 fromElement: (NSObject <DOMElement> *) documentElement 318{ 319 NSEnumerator *children; 320 NSObject <DOMElement> *currentElement; 321 NSString *tag; 322 323 children = [[self domNode: documentElement 324 getChildNodesByType: DOM_ELEMENT_NODE] 325 objectEnumerator]; 326 while ((currentElement = [children nextObject])) 327 { 328 tag = [currentElement tagName]; 329 if ([tag isEqualToString: @"property-search"]) 330 [self _fillMatches: matches fromElement: currentElement]; 331 else if ([tag isEqualToString: @"prop"]) 332 [self _fillProperties: properties fromElement: currentElement]; 333 else 334 [self errorWithFormat: @"principal-property-search: unknown tag '%@'", 335 tag]; 336 } 337} 338 339#warning this is a bit ugly, as usual 340- (void) _fillCollections: (NSMutableArray *) collections 341 withCalendarHomeSetMatching: (NSString *) value 342 inContext: (WOContext *) localContext 343{ 344 SOGoUserFolder *collection; 345 NSRange substringRange; 346 347 substringRange = [value rangeOfString: @"/SOGo/dav/"]; 348 349 if (substringRange.location == NSNotFound) 350 return; 351 352 value = [value substringFromIndex: NSMaxRange (substringRange)]; 353 substringRange = [value rangeOfString: @"/Calendar"]; 354 355 if (substringRange.location == NSNotFound) 356 return; 357 358 value = [value substringToIndex: substringRange.location]; 359 collection = [[SOGoUser userWithLogin: value] 360 homeFolderInContext: localContext]; 361 if (collection) 362 [collections addObject: collection]; 363} 364 365- (void) _fillCollections: (NSMutableArray *) collections 366 withEmailAddressSetMatching: (NSString *) value 367 inContext: (WOContext *) localContext 368{ 369 SOGoUserManager *um; 370 NSArray *records; 371 NSUInteger count, max; 372 NSString *uid; 373 SOGoUserFolder *collection; 374 375 um = [SOGoUserManager sharedUserManager]; 376 records = [um fetchUsersMatching: value 377 inDomain: [[localContext activeUser] domain]]; 378 379 max = [records count]; 380 for (count = 0; count < max; count++) 381 { 382 uid = [[records objectAtIndex: count] objectForKey: @"c_uid"]; 383 if ([uid length] > 0) 384 { 385 collection = [[SOGoUser userWithLogin: uid] 386 homeFolderInContext: localContext]; 387 [collections addObject: collection]; 388 } 389 } 390} 391 392- (void) _fillCollections: (NSMutableArray *) collections 393 where: (NSString *) key 394 matches: (NSString *) value 395 inContext: (WOContext *) localContext 396{ 397 if ([key 398 isEqualToString: @"{urn:ietf:params:xml:ns:caldav}calendar-home-set"]) 399 [self _fillCollections: collections withCalendarHomeSetMatching: value 400 inContext: localContext]; 401 else if ([key isEqualToString: @"{http://calendarserver.org/ns/}email-address-set"]) 402 [self _fillCollections: collections withEmailAddressSetMatching: value 403 inContext: localContext]; 404 else 405 [self warnWithFormat: @"principal-property-search: unhandled key '%@'", 406 key]; 407} 408 409- (NSArray *) _principalCollectionsMatching: (NSDictionary *) matches 410 inContext: (WOContext *) localContext 411{ 412 NSMutableArray *collections; 413 NSEnumerator *allKeys; 414 NSString *currentKey; 415 416 collections = [NSMutableArray array]; 417 418 allKeys = [[matches allKeys] objectEnumerator]; 419 while ((currentKey = [allKeys nextObject])) 420 [self _fillCollections: collections 421 where: currentKey 422 matches: [matches objectForKey: currentKey] 423 inContext: localContext]; 424 425 return collections; 426} 427 428/* <D:principal-property-search xmlns:D="DAV:"> 429 <D:property-search> 430 <D:prop> 431 <D:displayname/> 432 </D:prop> 433 <D:match>doE</D:match> 434 </D:property-search> 435 <D:property-search> 436 <D:prop xmlns:B="http://www.example.com/ns/"> 437 <B:title/> 438 </D:prop> 439 <D:match>Sales</D:match> 440 </D:property-search> 441 <D:prop xmlns:B="http://www.example.com/ns/"> 442 <D:displayname/> 443 <B:department/> 444 <B:phone/> 445 <B:office/> 446 <B:salary/> 447 </D:prop> 448 </D:principal-property-search> */ 449 450- (void) _appendProperties: (NSArray *) properties 451 ofCollection: (SOGoUserFolder *) collection 452 toResponses: (NSMutableArray *) responses 453{ 454 unsigned int count, max; 455 SEL methodSel; 456 NSString *currentProperty; 457 id currentValue; 458 NSMutableArray *response, *props, *propstat; 459 NSDictionary *keyTuple; 460 461 response = [NSMutableArray array]; 462 [response addObject: davElementWithContent (@"href", XMLNS_WEBDAV, 463 [collection davURLAsString])]; 464 props = [NSMutableArray array]; 465 466 max = [properties count]; 467 for (count = 0; count < max; count++) 468 { 469 currentProperty = [properties objectAtIndex: count]; 470 methodSel = SOGoSelectorForPropertyGetter (currentProperty); 471 if (methodSel && [collection respondsToSelector: methodSel]) 472 { 473 currentValue = [collection performSelector: methodSel]; 474 #warning evil eVIL EVIl! 475 if ([currentValue isKindOfClass: [NSArray class]]) 476 { 477 currentValue = [currentValue objectAtIndex: 0]; 478 currentValue 479 = davElementWithContent ([currentValue objectAtIndex: 0], 480 [currentValue objectAtIndex: 1], 481 [currentValue objectAtIndex: 3]); 482 } 483 keyTuple = [currentProperty asWebDAVTuple]; 484 [props addObject: davElementWithContent ([keyTuple objectForKey: @"method"], 485 [keyTuple objectForKey: @"ns"], 486 currentValue)]; 487 } 488 } 489 490 propstat = [NSMutableArray array]; 491 [propstat addObject: davElementWithContent (@"status", XMLNS_WEBDAV, 492 @"HTTP/1.1 200 OK")]; 493 [propstat addObject: davElementWithContent (@"prop", XMLNS_WEBDAV, 494 props)]; 495 [response addObject: davElementWithContent (@"propstat", XMLNS_WEBDAV, propstat)]; 496 [responses addObject: davElementWithContent (@"response", XMLNS_WEBDAV, 497 response)]; 498} 499 500- (void) _appendProperties: (NSArray *) properties 501 ofCollections: (NSArray *) collections 502 toResponse: (WOResponse *) response 503{ 504 NSDictionary *mStatus; 505 NSMutableArray *responses; 506 unsigned int count, max; 507 508 max = [collections count]; 509 responses = [NSMutableArray arrayWithCapacity: max]; 510 for (count = 0; count < max; count++) 511 [self _appendProperties: properties 512 ofCollection: [collections objectAtIndex: count] 513 toResponses: responses]; 514 mStatus = davElementWithContent (@"multistatus", XMLNS_WEBDAV, responses); 515 [response appendContentString: [mStatus asWebDavStringWithNamespaces: nil]]; 516} 517 518/* rfc3744, should be moved into SOGoUserFolder */ 519- (WOResponse *) davPrincipalPropertySearch: (WOContext *) localContext 520{ 521 NSObject <DOMDocument> *document; 522 NSObject <DOMElement> *documentElement; 523 NSArray *collections; 524 NSMutableDictionary *matches; 525 NSMutableArray *properties; 526 WOResponse *r; 527 528 document = [[localContext request] contentAsDOMDocument]; 529 documentElement = [document documentElement]; 530 531 matches = [NSMutableDictionary dictionary]; 532 properties = [NSMutableArray array]; 533 [self _fillPrincipalMatches: matches andProperties: properties 534 fromElement: documentElement]; 535 collections = [self _principalCollectionsMatching: matches 536 inContext: localContext]; 537 r = [localContext response]; 538 [r prepareDAVResponse]; 539 [self _appendProperties: properties ofCollections: collections 540 toResponse: r]; 541 542 return r; 543} 544 545- (SOGoWebDAVValue *) davCurrentUserPrincipal 546{ 547 NSDictionary *userHREF; 548 NSString *usersUrl, *login; 549 SOGoUser *activeUser; 550 SOGoWebDAVValue *davCurrentUserPrincipal; 551 552 activeUser = [[self context] activeUser]; 553 login = [activeUser login]; 554 if ([login isEqualToString: @"anonymous"]) 555 davCurrentUserPrincipal = nil; 556 else 557 { 558 usersUrl = [NSString stringWithFormat: @"%@%@/", 559 [self davURLAsString], login]; 560 userHREF = davElementWithContent (@"href", XMLNS_WEBDAV, usersUrl); 561 davCurrentUserPrincipal 562 = [davElementWithContent (@"current-user-principal", 563 XMLNS_WEBDAV, 564 userHREF) 565 asWebDAVValue]; 566 } 567 568 return davCurrentUserPrincipal; 569} 570 571- (NSArray *) davComplianceClassesInContext: (WOContext *) localContext 572{ 573 static NSMutableArray *newClasses = nil; 574 NSArray *selfClasses; 575 576 if (!newClasses) 577 { 578 newClasses 579 = [[super davComplianceClassesInContext: localContext] mutableCopy]; 580 selfClasses = [NSArray arrayWithObjects: @"access-control", 581 @"addressbook", @"calendar-access", 582 @"calendar-schedule", @"calendar-auto-schedule", 583 @"calendar-proxy", 584 @"calendar-query-extended", 585 @"extended-mkcol", 586 @"calendarserver-principal-property-search", 587 nil]; 588 [newClasses addObjectsFromArray: selfClasses]; 589 } 590 591 return newClasses; 592} 593 594- (SOGoWebDAVValue *) davPrincipalCollectionSet 595{ 596 NSString *davURL; 597 NSDictionary *collectionHREF; 598 NSString *classes; 599 WOContext *context; 600 601 context = [self context]; 602 if ([[context request] isICal4]) 603 { 604 classes = [[self davComplianceClassesInContext: context] 605 componentsJoinedByString: @", "]; 606 [[context response] setHeader: classes forKey: @"dav"]; 607 } 608 609 /* WOApplication has no support for the DAV methods we define here so we 610 use the user's principal object as a reference */ 611 davURL = [self davURLAsString]; 612 collectionHREF = davElementWithContent (@"href", XMLNS_WEBDAV, davURL); 613 614 return [davElementWithContent (@"principal-collection-set", 615 XMLNS_WEBDAV, 616 [NSArray arrayWithObject: collectionHREF]) 617 asWebDAVValue]; 618} 619 620@end 621