2 Copyright (C) 2000-2003 SKYRIX Software AG
4 This file is part of OGo
6 OGo 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
11 OGo 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.
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
23 #include "NGLdapFileManager.h"
24 #include "NGLdapConnection.h"
25 #include "NGLdapEntry.h"
26 #include "NGLdapAttribute.h"
27 #include "NGLdapURL.h"
28 #include "NSString+DN.h"
29 #import <NGExtensions/NGFileFolderInfoDataSource.h>
32 @implementation NGLdapFileManager
35 return [super version] + 0 /* v0 */;
38 NSAssert2([super version] == 0,
39 @"invalid superclass (%@) version %i !",
40 NSStringFromClass([self superclass]), [super version]);
43 static NSString *LDAPObjectClassKey = @"objectclass";
44 static NSArray *objectClassAttrs = nil;
45 static NSArray *fileInfoAttrs = nil;
48 if (objectClassAttrs == nil) {
50 [[NSArray alloc] initWithObjects:&LDAPObjectClassKey count:1];
52 if (fileInfoAttrs == nil) {
54 [[NSArray alloc] initWithObjects:
64 - (id)initWithLdapConnection:(NGLdapConnection *)_con { // designated initializer
70 [[self class] _initCache];
72 if ((self = [super init])) {
73 self->connection = [_con retain];
78 - (id)initWithHostName:(NSString *)_host port:(int)_port
79 bindDN:(NSString *)_login credentials:(NSString *)_pwd
80 rootDN:(NSString *)_rootDN
82 NGLdapConnection *ldap;
84 ldap = [[NGLdapConnection alloc] initWithHostName:_host port:_port?_port:389];
89 ldap = [ldap autorelease];
91 if (![ldap bindWithMethod:@"simple" binddn:_login credentials:_pwd]) {
92 NSLog(@"couldn't bind as DN '%@' with %@", _login, ldap);
97 if ((self = [self initWithLdapConnection:ldap])) {
99 /* check cn=config as available in OpenLDAP */
102 if ((nctxs = [self->connection namingContexts])) {
103 if ([nctxs count] > 1)
104 NSLog(@"WARNING: more than one naming context handled by server !");
105 if ([nctxs count] > 0)
106 _rootDN = [[nctxs objectAtIndex:0] lowercaseString];
111 ASSIGNCOPY(self->rootDN, _rootDN);
112 ASSIGNCOPY(self->currentDN, _rootDN);
113 self->currentPath = @"/";
119 - (id)initWithURLString:(NSString *)_url {
122 if ((url = [NGLdapURL ldapURLWithString:_url]) == nil) {
123 /* couldn't parse URL */
128 return [self initWithHostName:[url hostName] port:[url port]
129 bindDN:nil credentials:nil
130 rootDN:[url baseDN]];
132 - (id)initWithURL:(id)_url {
133 return (![_url isKindOfClass:[NSURL class]])
134 ? [self initWithURLString:[_url stringValue]]
135 : [self initWithURLString:[_url absoluteString]];
139 [self->connection release];
140 [self->rootDN release];
141 [self->currentDN release];
142 [self->currentPath release];
148 - (NSString *)_rdnForPathComponent:(NSString *)_pathComponent {
149 return _pathComponent;
151 - (NSString *)_pathComponentForRDN:(NSString *)_rdn {
155 - (NSString *)pathForDN:(NSString *)_dn {
156 NSEnumerator *dnComponents;
160 if (_dn == nil) return nil;
161 _dn = [_dn lowercaseString];
163 if (![_dn hasSuffix:self->rootDN]) {
164 /* DN is not rooted in this hierachy */
169 _dn = [_dn substringToIndex:([_dn length] - [self->rootDN length])];
172 dnComponents = [[_dn dnComponents] reverseObjectEnumerator];
173 while ((rdn = [dnComponents nextObject])) {
174 NSString *pathComponent;
176 pathComponent = [self _pathComponentForRDN:rdn];
178 path = [path stringByAppendingPathComponent:pathComponent];
183 - (NGLdapConnection *)ldapConnection {
184 return self->connection;
186 - (NSString *)dnForPath:(NSString *)_path {
188 NSArray *pathComponents;
191 if (![_path isAbsolutePath])
192 _path = [[self currentDirectoryPath] stringByAppendingPathComponent:_path];
194 if ([_path length] == 0) return nil;
196 NSAssert1([_path isAbsolutePath],
197 @"path %@ is not an absolute path (after append to cwd) !", _path);
198 NSAssert(self->rootDN, @"missing root DN !");
200 pathComponents = [_path pathComponents];
201 for (i = 0, count = [pathComponents count]; i < count; i++) {
202 NSString *pathComponent;
205 pathComponent = [pathComponents objectAtIndex:i];
207 if ([pathComponent isEqualToString:@"."])
209 if ([pathComponent length] == 0)
212 if ([pathComponent isEqualToString:@"/"]) {
217 if ([pathComponent isEqualToString:@".."]) {
218 dn = [dn stringByDeletingLastDNComponent];
222 rdn = [self _rdnForPathComponent:pathComponent];
223 dn = [dn stringByAppendingDNComponent:rdn];
226 return [dn lowercaseString];
231 - (BOOL)changeCurrentDirectoryPath:(NSString *)_path {
235 if ([_path length] == 0)
238 if ((dn = [self dnForPath:_path]) == nil)
241 if ((path = [self pathForDN:dn]) == nil)
244 ASSIGNCOPY(self->currentDN, dn);
245 ASSIGNCOPY(self->currentPath, path);
249 - (NSString *)currentDirectoryPath {
250 return self->currentPath;
254 - (NSArray *)directoryContentsAtPath:(NSString *)_path {
257 NSMutableArray *rdns;
260 if ((dn = [self dnForPath:_path]) == nil)
263 e = [self->connection flatSearchAtBaseDN:dn
265 attributes:objectClassAttrs];
270 while ((entry = [e nextObject])) {
272 rdns = [NSMutableArray arrayWithCapacity:128];
274 [rdns addObject:[entry rdn]];
277 return [[rdns copy] autorelease];
280 - (NSArray *)subpathsAtPath:(NSString *)_path {
283 NSMutableArray *paths;
286 if ((dn = [self dnForPath:_path]) == nil)
289 _path = [self pathForDN:dn];
291 e = [self->connection deepSearchAtBaseDN:dn
293 attributes:objectClassAttrs];
298 while ((entry = [e nextObject])) {
304 if ((path = [self pathForDN:sdn]) == nil) {
305 NSLog(@"got no path for dn '%@' ..", sdn);
309 if ([path hasPrefix:_path])
310 path = [path substringFromIndex:[_path length]];
313 paths = [NSMutableArray arrayWithCapacity:128];
315 [paths addObject:path];
318 return [[paths copy] autorelease];
321 - (NSDictionary *)fileAttributesAtPath:(NSString *)_path traverseLink:(BOOL)_fl {
324 NGLdapAttribute *attr;
329 if ((dn = [self dnForPath:_path]) == nil)
332 entry = [self->connection entryAtDN:dn attributes:fileInfoAttrs];
337 if ((attr = [entry attributeWithName:@"modifytimestamp"])) {
338 keys[count] = NSFileModificationDate;
339 vals[count] = [[attr stringValueAtIndex:0] ldapTimestamp];
342 if ((attr = [entry attributeWithName:@"modifiersname"])) {
343 keys[count] = NSFileOwnerAccountName;
344 vals[count] = [[attr allStringValues] componentsJoinedByString:@","];
347 if ((attr = [entry attributeWithName:@"creatorsname"])) {
348 keys[count] = @"NSFileCreatorAccountName";
349 vals[count] = [[attr allStringValues] componentsJoinedByString:@","];
352 if ((attr = [entry attributeWithName:@"createtimestamp"])) {
353 keys[count] = @"NSFileCreationDate";
354 vals[count] = [[attr stringValueAtIndex:0] ldapTimestamp];
357 if ((attr = [entry attributeWithName:@"objectclass"])) {
358 keys[count] = @"LDAPObjectClasses";
359 vals[count] = [attr allStringValues];
363 keys[count] = @"NSFileIdentifier";
364 if ((vals[count] = [entry dn]))
367 keys[count] = NSFilePath;
368 if ((vals[count] = _path))
371 keys[count] = NSFileName;
372 if ((vals[count] = [self _pathComponentForRDN:[dn lastDNComponent]]))
375 return [NSDictionary dictionaryWithObjects:vals forKeys:keys count:count];
378 /* determine access */
380 - (BOOL)fileExistsAtPath:(NSString *)_path {
381 return [self fileExistsAtPath:_path isDirectory:NULL];
383 - (BOOL)fileExistsAtPath:(NSString *)_path isDirectory:(BOOL *)_isDir {
387 if ((dn = [self dnForPath:_path]) == nil)
390 entry = [self->connection entryAtDN:dn attributes:objectClassAttrs];
397 /* is-dir based on child-availablitiy */
398 e = [self->connection flatSearchAtBaseDN:dn
400 attributes:objectClassAttrs];
401 *_isDir = [e nextObject] ? YES : NO;
406 - (BOOL)isReadableFileAtPath:(NSString *)_path {
407 return [self fileExistsAtPath:_path];
409 - (BOOL)isWritableFileAtPath:(NSString *)_path {
410 return [self fileExistsAtPath:_path];
412 - (BOOL)isExecutableFileAtPath:(NSString *)_path {
415 - (BOOL)isDeletableFileAtPath:(NSString *)_path {
416 return [self fileExistsAtPath:_path];
419 /* reading contents */
421 - (BOOL)contentsEqualAtPath:(NSString *)_path1 andPath:(NSString *)_path2 {
423 NGLdapEntry *e1, *e2;
425 if ((dn1 = [self dnForPath:_path1]) == nil)
427 if ((dn2 = [self dnForPath:_path2]) == nil)
430 if ([dn1 isEqualToString:dn2])
434 e1 = [self->connection entryAtDN:dn1 attributes:nil];
435 e2 = [self->connection entryAtDN:dn2 attributes:nil];
437 return [e1 isEqual:e2];
439 - (NSData *)contentsAtPath:(NSString *)_path {
440 /* generate LDIF for record */
444 if ((dn = [self dnForPath:_path]) == nil)
447 entry = [self->connection entryAtDN:dn attributes:nil];
451 return [[entry ldif] dataUsingEncoding:NSUTF8StringEncoding];
456 - (NSDictionary *)_errDictForPath:(NSString *)_path toPath:(NSString *)_dest
457 dn:(NSString *)_dn reason:(NSString *)_reason
466 keys[count] = @"Path";
467 values[count] = _path;
471 keys[count] = @"ToPath";
472 values[count] = _dest;
476 keys[count] = @"Error";
477 values[count] = _reason;
484 keys[count] = @"ldap";
485 values[count] = self->connection;
489 return [NSDictionary dictionaryWithObjects:values forKeys:keys count:count];
492 - (BOOL)removeFileAtPath:(NSString *)_path handler:(id)_fhandler {
495 [_fhandler fileManager:(id)self willProcessPath:_path];
497 if ((dn = [self dnForPath:_path]) == nil) {
499 NSDictionary *errDict;
501 errDict = [self _errDictForPath:_path toPath:nil dn:nil
502 reason:@"couldn't map path to LDAP dn"];
504 if ([_fhandler fileManager:(id)self shouldProceedAfterError:errDict])
510 /* should delete sub-entries first ... */
514 if (![self->connection removeEntryWithDN:dn]) {
516 NSDictionary *errDict;
518 errDict = [self _errDictForPath:_path toPath:nil dn:dn
519 reason:@"couldn't remove LDAP entry"];
521 if ([_fhandler fileManager:(id)self shouldProceedAfterError:errDict])
530 - (BOOL)copyPath:(NSString *)_path toPath:(NSString *)_destination
531 handler:(id)_fhandler
534 NSString *fromDN, *toDN, *toRDN;
536 [_fhandler fileManager:(id)self willProcessPath:_path];
538 if ((fromDN = [self dnForPath:_path]) == nil) {
540 NSDictionary *errDict;
542 errDict = [self _errDictForPath:_path toPath:_destination dn:nil
543 reason:@"couldn't map source path to LDAP dn"];
545 if ([_fhandler fileManager:(id)self shouldProceedAfterError:errDict])
552 split destination. 'toDN' is the target 'directory', 'toRDN' the name of
555 toDN = [self dnForPath:_destination];
556 toRDN = [toDN lastDNComponent];
557 toDN = [toDN stringByDeletingLastDNComponent];
559 if ((toDN == nil) || (toRDN == nil)) {
561 NSDictionary *errDict;
563 errDict = [self _errDictForPath:_path toPath:_destination dn:fromDN
564 reason:@"couldn't map destination path to LDAP dn"];
566 if ([_fhandler fileManager:(id)self shouldProceedAfterError:errDict])
574 if ((e = [self->connection entryAtDN:fromDN attributes:nil]) == nil) {
576 NSDictionary *errDict;
578 errDict = [self _errDictForPath:_path toPath:_destination dn:fromDN
579 reason:@"couldn't load source LDAP record"];
581 if ([_fhandler fileManager:(id)self shouldProceedAfterError:errDict])
587 /* create new record with the attributes of the old one */
591 attrs = [[e attributes] allValues];
592 newe = [[NGLdapEntry alloc] initWithDN:toDN attributes:attrs];
593 newe = [newe autorelease];
595 /* insert record in target space */
596 if (![self->connection addEntry:newe]) {
600 NSDictionary *errDict;
602 errDict = [self _errDictForPath:_path toPath:_destination dn:toDN
603 reason:@"couldn't insert LDAP record in target dn"];
605 if ([_fhandler fileManager:(id)self shouldProceedAfterError:errDict])
612 /* should process children ? */
617 - (BOOL)movePath:(NSString *)_path toPath:(NSString *)_destination
618 handler:(id)_fhandler
620 /* needs to invoke a modrdn operation */
621 [_fhandler fileManager:(id)self willProcessPath:_path];
626 - (BOOL)linkPath:(NSString *)_path toPath:(NSString *)_destination
627 handler:(id)_fhandler
629 /* LDAP doesn't support links .. */
630 [_fhandler fileManager:(id)self willProcessPath:_path];
635 - (BOOL)createFileAtPath:(NSString *)path
636 contents:(NSData *)contents
637 attributes:(NSDictionary *)attributes
644 - (NSString *)description {
647 ms = [NSMutableString stringWithCapacity:64];
648 [ms appendFormat:@"<0x%08X[%@]:", self, NSStringFromClass([self class])];
651 [ms appendFormat:@" root=%@", self->rootDN];
652 if (self->currentDN && ![self->currentDN isEqualToString:self->rootDN])
653 [ms appendFormat:@" cwd=%@", self->currentDN];
655 if (self->connection)
656 [ms appendFormat:@" ldap=%@", self->connection];
658 [ms appendString:@">"];
662 @end /* NGLdapFileManager */
664 #include <NGLdap/NGLdapDataSource.h>
665 #include <NGLdap/NGLdapGlobalID.h>
667 @implementation NGLdapFileManager(ExtendedFileManager)
671 - (BOOL)supportsVersioningAtPath:(NSString *)_path {
674 - (BOOL)supportsLockingAtPath:(NSString *)_path {
677 - (BOOL)supportsFolderDataSourceAtPath:(NSString *)_path {
683 - (BOOL)writeContents:(NSData *)_content atPath:(NSString *)_path {
684 /* should decode LDIF and store at path .. */
688 /* datasources (work on folders) */
690 - (EODataSource *)dataSourceAtPath:(NSString *)_path {
691 NGLdapDataSource *ds;
694 if ((dn = [self dnForPath:_path]) == nil)
695 /* couldn't get DN for specified path .. */
698 ds = [[NGLdapDataSource alloc]
699 initWithLdapConnection:self->connection
701 return [ds autorelease];
703 - (EODataSource *)dataSource {
704 return [self dataSourceAtPath:[self currentDirectoryPath]];
709 - (EOGlobalID *)globalIDForPath:(NSString *)_path {
713 if ((dn = [self dnForPath:_path]) == nil)
716 gid = [[NGLdapGlobalID alloc]
717 initWithHost:[self->connection hostName]
718 port:[self->connection port]
720 return [gid autorelease];
723 - (NSString *)pathForGlobalID:(EOGlobalID *)_gid {
726 if (![_gid isKindOfClass:[NGLdapGlobalID class]])
729 gid = (NGLdapGlobalID *)_gid;
731 /* check whether host&port is correct */
732 if (![[self->connection hostName] isEqualToString:[gid host]])
734 if (![self->connection port] == [gid port])
737 return [self pathForDN:[gid dn]];
742 - (BOOL)supportsTrashFolderAtPath:(NSString *)_path {
745 - (NSString *)trashFolderForPath:(NSString *)_path {
749 @end /* NGLdapFileManager(ExtendedFileManager) */