/* Copyright (C) 2002-2004 SKYRIX Software AG This file is part of OpenGroupware.org. OGo 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. OGo 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. */ // $Id$ #include "SoObject.h" #include "SoClassRegistry.h" #include "SoClass.h" #include "SoSecurityManager.h" #include #include #include #include #include "common.h" @interface NSObject(Folders) - (BOOL)isFolderish; @end static NSString *SoRootURLVarKey = @"SoRootURL"; @implementation NSObject(SoObject) static int debugLookup = -1; static int debugBaseURL = -1; static void _initialize(void) { if (debugLookup == -1) { debugLookup = [[NSUserDefaults standardUserDefaults] boolForKey:@"SoDebugKeyLookup"] ? 1 : 0; } if (debugBaseURL == -1) { debugBaseURL = [[NSUserDefaults standardUserDefaults] boolForKey:@"SoDebugBaseURL"] ? 1 : 0; } } /* classes */ + (SoClass *)soClass { static SoClassRegistry *registry = nil; // THREAD if (registry == nil) registry = [[SoClassRegistry sharedClassRegistry] retain]; return [registry soClassForClass:self]; } - (SoClass *)soClass { return [[self class] soClass]; } - (NSString *)soClassName { return [[self soClass] className]; } + (SoClassSecurityInfo *)soClassSecurityInfo { return [[self soClass] soClassSecurityInfo]; } - (NSClassDescription *)soClassDescription { return [[self soClass] soClassDescription]; } /* invocation */ - (BOOL)isCallable { return NO; } - (id)clientObject { return self; } - (id)callOnObject:(id)_client inContext:(id)_ctx { return nil; } - (NSString *)defaultMethodNameInContext:(id)_ctx { return @"index"; } - (id)lookupDefaultMethod { id ctx = nil; // TODO: lookupDefaultMethod should be rewritten to take a context! ctx = [[WOApplication application] context]; // TODO: we might want to return a redirect?! return [self lookupName:[self defaultMethodNameInContext:ctx] inContext:ctx acquire:YES]; } /* keys */ - (BOOL)hasName:(NSString *)_key inContext:(id)_ctx { /* this corresponds to Zope's/Pythons __hasattr__() */ if ([[self soClass] hasKey:_key inContext:_ctx]) return YES; if ([[self toOneRelationshipKeys] containsObject:_key]) return YES; return NO; } - (id)lookupName:(NSString *)_key inContext:(id)_ctx acquire:(BOOL)_flag { /* this corresponds to Zope's/Pythons __getattr__() */ id value; _initialize(); if (debugLookup) [self debugWithFormat:@"lookup key '%@'", _key]; /* we might want to cache class methods ? */ if ((value = [[self soClass] lookupKey:_key inContext:_ctx]) == nil) { if (debugLookup) { [self logWithFormat:@" did not find key '%@' in SoClass: %@", _key, [self soClass]]; } if ([[self toOneRelationshipKeys] containsObject:_key]) { if (debugLookup) { [self logWithFormat: @" %@ is a toOneRelationshipKey (use -valueForKey:)", _key]; } value = [self valueForKey:_key]; } } if (value) { if ((value = [value bindToObject:self inContext:_ctx]) == nil) { if (debugLookup) [self logWithFormat:@" value from class did not bind: %@", [self soClass]]; return nil; } } else if (_flag) { /* try to acquire from container */ if (debugLookup) [self logWithFormat:@" try to acquire %@ from container ...", _key]; value = [[self container] lookupName:_key inContext:_ctx acquire:YES]; } if (debugLookup) [self logWithFormat:@" looked up value: %@", value]; return value; } - (NSException *)validateName:(NSString *)_key inContext:(id)_ctx { static SoSecurityManager *sm = nil; if (sm == nil) sm = [[SoSecurityManager sharedSecurityManager] retain]; return [sm validateName:_key ofObject:self inContext:_ctx]; } /* binding */ - (id)bindToObject:(id)_object inContext:(id)_ctx { return self; } /* security */ - (NSString *)ownerInContext:(id)_ctx { /* objects are not owned by default, suggest to inherit owner */ return [[self container] ownerInContext:_ctx]; } - (id)authenticatorInContext:(id)_ctx { return [[_ctx application] authenticatorInContext:_ctx]; } /* containment */ - (id)container { return nil; } - (void)detachFromContainer { } - (NSString *)nameInContainer { return nil; } - (NSArray *)objectContainmentStack { NSMutableArray *ma; id object; if ((object = [self container]) == nil) /* this is root */ return [NSArray arrayWithObject:self]; ma = [[NSMutableArray alloc] initWithCapacity:16]; for (object = self; object; object = [object container]) [ma insertObject:(object ? object : (id)[NSNull null]) atIndex:0]; object = [ma shallowCopy]; [ma release]; return [object autorelease]; } - (NSArray *)reversedPathArrayToSoObject { NSMutableArray *ma; id object, nextObject; if ((object = [self container]) == nil) /* this is root */ return [NSArray array]; ma = [NSMutableArray arrayWithCapacity:16]; for (object = self; (nextObject = [object container]); object = nextObject) { NSString *oname; oname = [object nameInContainer]; [ma addObject:(oname ? oname : (id)[NSNull null])]; } return ma; } - (NSArray *)pathArrayToSoObject { NSArray *pathArray; NSEnumerator *e; if ((pathArray = [self reversedPathArrayToSoObject]) == nil) return nil; e = [pathArray reverseObjectEnumerator]; pathArray = [[[NSArray alloc] initWithObjectsFromEnumerator:e] autorelease]; return pathArray; } - (NSString *)baseURLInContext:(id)_ctx { NSString *baseURL; id parent; _initialize(); // TODO: should we check the traversal path? if ((parent = [self container]) != nil) { /* Note: cannot use -stringByAppendingPathComponent: on OSX! */ NSString *name; if (parent == self) { [self logWithFormat: @"WARNING: container==object in baseURL calculation (loop?): %@", self]; } baseURL = [parent baseURLInContext:_ctx]; if (![baseURL hasSuffix:@"/"]) baseURL = [baseURL stringByAppendingString:@"/"]; name = [[self nameInContainer] stringByEscapingURL]; baseURL = [baseURL stringByAppendingString:name]; if (debugBaseURL) { [self logWithFormat:@"baseURL(%@,%@): %@", [self nameInContainer], [[self container] baseURL], baseURL]; } } else { baseURL = [self rootURLInContext:_ctx]; if (debugBaseURL) { [self logWithFormat:@"ROOT baseURL(no container, name=%@): %@", [self nameInContainer], baseURL]; } } /* add a trailing slash for folders */ if (![baseURL hasSuffix:@"/"]) { if ([self respondsToSelector:@selector(isFolderish)]) { if ([self isFolderish]) baseURL = [baseURL stringByAppendingString:@"/"]; } } return baseURL; } - (NSString *)rootURLInContext:(id)_ctx { NSMutableString *ms; BOOL isHTTPS = NO; // TODO: what about https?? NSString *rootURL; WORequest *rq; NSString *rh, *tmp; int port; _initialize(); if ((rootURL = [(WOContext *)_ctx objectForKey:SoRootURLVarKey]) != nil) { if (debugBaseURL) { [self logWithFormat:@" using root-url from context (SoRootURL): %@", rootURL]; } return rootURL; } // TODO: this is somewhat weird, why don't we use WOContext for URL gen.? rq = [(WOContext *)_ctx request]; port = [[rq headerForKey:@"x-webobjects-server-port"] intValue]; /* TODO: how to handle Evolution bug which sends invalid port ? */ if (port == 0) { static BOOL didWarn = NO; if (!didWarn) { [self logWithFormat: @"WARNING(%s:%i): got an empty port, probably buggy " @"SOUP host header!", __PRETTY_FUNCTION__, __LINE__]; didWarn = YES; } port = 23000; } ms = [[NSMutableString alloc] initWithCapacity:128]; if ((tmp = [rq headerForKey:@"host"])) { /* check whether we have a host header with port */ if ([tmp rangeOfString:@":"].length == 0) tmp = nil; } if (tmp) { isHTTPS = [[rq headerForKey:@"x-webobjects-server-url"] hasPrefix:@"https"]; [ms appendString:isHTTPS ? @"https://" : @"http://"]; [ms appendString:tmp]; } else if ((tmp = [rq headerForKey:@"x-webobjects-server-url"])) { /* sometimes the URL is just wrong! (suggests port 80) */ if ([tmp hasSuffix:@":0"] && [tmp length] > 2) // TODO: bad bad bad tmp = [tmp substringToIndex:([tmp length] - 2)]; [ms appendString:tmp]; } else { [ms appendString:isHTTPS ? @"https://" : @"http://"]; [ms appendString:[rq headerForKey:@"x-webobjects-server-name"]]; if ((isHTTPS ? (port != 443) : (port != 80)) && port != 0) [ms appendFormat:@":%i", port]; } if (![ms hasSuffix:@"/"]) [ms appendString:@"/"]; /* appname, two cases: */ /* a) direct access, eg /MyFolder */ /* b) access via app, eg /MyApp/so/MyFolder */ [ms appendString:[rq applicationName]]; [ms appendString:@"/"]; /* done */ rootURL = [[ms copy] autorelease]; [ms release]; if (debugBaseURL) [self logWithFormat:@" constructed root-url: %@", rootURL]; /* some hack for the request handler? */ rh = [rq requestHandlerKey]; if ([[[_ctx application] registeredRequestHandlerKeys] containsObject:rh]) rootURL = [rootURL stringByAppendingFormat:@"%@/", rh]; if (debugBaseURL) { [self logWithFormat:@" setting root-url in context (SoRootURL): %@", rootURL]; } [(WOContext *)_ctx setObject:rootURL forKey:SoRootURLVarKey]; return rootURL; } - (NSString *)baseURL { /* you should use the context method ! */ return [self baseURLInContext:[[WOApplication application] context]]; } @end /* NSObject(SoObject) */ @implementation WOApplication(Authenticator) - (NSString *)ownerInContext:(id)_ctx { /* objects are not owned by default */ return nil; } - (id)authenticatorInContext:(id)_ctx { return nil; } @end /* WOApplication(Authenticator) */