diff options
Diffstat (limited to 'pypers/europython05/Quixote-2.0/publish1.py')
-rwxr-xr-x | pypers/europython05/Quixote-2.0/publish1.py | 270 |
1 files changed, 270 insertions, 0 deletions
diff --git a/pypers/europython05/Quixote-2.0/publish1.py b/pypers/europython05/Quixote-2.0/publish1.py new file mode 100755 index 0000000..93bfaf3 --- /dev/null +++ b/pypers/europython05/Quixote-2.0/publish1.py @@ -0,0 +1,270 @@ +"""$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/quixote/publish1.py $ +$Id: publish1.py 25664 2004-11-22 20:35:07Z nascheme $ + +Provides a publisher object that behaves like the Quixote 1 Publisher. +Specifically, arbitrary namespaces may be exported and the HTTPRequest +object is passed as the first argument to exported functions. Also, +the _q_lookup(), _q_resolve(), and _q_access() methods work as they did +in Quixote 1. +""" + +import sys +import re +import types +import warnings +from quixote import errors, get_request, redirect +from quixote.publish import Publisher as _Publisher +from quixote.directory import Directory +from quixote.html import htmltext + + +class Publisher(_Publisher): + """ + Instance attributes: + namespace_stack : [ module | instance | class ] + """ + + def __init__(self, root_namespace, config=None): + from quixote.config import Config + if type(root_namespace) is types.StringType: + root_namespace = _get_module(root_namespace) + self.namespace_stack = [root_namespace] + if config is None: + config = Config() + directory = RootDirectory(root_namespace, self.namespace_stack) + _Publisher.__init__(self, directory, config=config) + + def debug(self, msg): + self.log(msg) + + def get_namespace_stack(self): + """get_namespace_stack() -> [ module | instance | class ] + """ + return self.namespace_stack + + +class RootDirectory(Directory): + def __init__(self, root_namespace, namespace_stack): + self.root_namespace = root_namespace + self.namespace_stack = namespace_stack + + def _q_traverse(self, path): + # Initialize the publisher's namespace_stack + del self.namespace_stack[:] + + request = get_request() + + # Traverse package to a (hopefully-) callable object + object = _traverse_url(self.root_namespace, path, request, + self.namespace_stack) + + # None means no output -- traverse_url() just issued a redirect. + if object is None: + return None + + # Anything else must be either a string... + if isstring(object): + output = object + + # ...or a callable. + elif callable(object): + output = object(request) + if output is None: + raise RuntimeError, 'callable %s returned None' % repr(object) + + # Uh-oh: 'object' is neither a string nor a callable. + else: + raise RuntimeError( + "object is neither callable nor a string: %s" % repr(object)) + + return output + + +def _get_module(name): + """Get a module object by name.""" + __import__(name) + module = sys.modules[name] + return module + + +_slash_pat = re.compile("//*") + +def _traverse_url(root_namespace, path_components, request, namespace_stack): + """(root_namespace : any, path_components : [string], + request : HTTPRequest, namespace_stack : list) -> (object : any) + + Perform traversal based on the provided path, starting at the root + object. It returns the script name and path info values for + the arrived-at object, along with the object itself and + a list of the namespaces traversed to get there. + + It's expected that the final object is something callable like a + function or a method; intermediate objects along the way will + usually be packages or modules. + + To prevent crackers from writing URLs that traverse private + objects, every package, module, or object along the way must have + a _q_exports attribute containing a list of publicly visible + names. Not having a _q_exports attribute is an error, though + having _q_exports be an empty list is OK. If a component of the path + isn't in _q_exports, that also produces an error. + + Modifies the namespace_stack as it traverses the url, so that + any exceptions encountered along the way can be handled by the + nearest handler. + """ + + path = '/' + '/'.join(path_components) + + # If someone accesses a Quixote driver script without a trailing + # slash, we'll wind up here with an empty path. This won't + # work; relative references in the page generated by the root + # namespace's _q_index() will be off. Fix it by redirecting the + # user to the right URL; when the client follows the redirect, + # we'll wind up here again with path == '/'. + if not path: + return redirect(request.environ['SCRIPT_NAME'] + '/' , permanent=1) + + # Traverse starting at the root + object = root_namespace + namespace_stack.append(object) + + # Loop over the components of the path + for component in path_components: + if component == "": + # "/q/foo/" == "/q/foo/_q_index" + component = "_q_index" + object = _get_component(object, component, request, namespace_stack) + + if not (isstring(object) or callable(object)): + # We went through all the components of the path and ended up at + # something which isn't callable, like a module or an instance + # without a __call__ method. + if path[-1] != '/': + if not request.form: + # This is for the convenience of users who type in paths. + # Repair the path and redirect. This should not happen for + # URLs within the site. + return redirect(request.get_path() + "/", permanent=1) + + else: + # Automatic redirects disabled or there is form data. If + # there is form data then the programmer is using the + # wrong path. A redirect won't work if the form data came + # from a POST anyhow. + raise errors.TraversalError( + "object is neither callable nor string " + "(missing trailing slash?)", + private_msg=repr(object), + path=path) + else: + raise errors.TraversalError( + "object is neither callable nor string", + private_msg=repr(object), + path=path) + + return object + + +def _get_component(container, component, request, namespace_stack): + """Get one component of a path from a namespace. + """ + # First security check: if the container doesn't even have an + # _q_exports list, fail now: all Quixote-traversable namespaces + # (modules, packages, instances) must have an export list! + if not hasattr(container, '_q_exports'): + raise errors.TraversalError( + private_msg="%r has no _q_exports list" % container) + + # Second security check: call _q_access function if it's present. + if hasattr(container, '_q_access'): + # will raise AccessError if access failed + container._q_access(request) + + # Third security check: make sure the current name component + # is in the export list or is '_q_index'. If neither + # condition is true, check for a _q_lookup() and call it. + # '_q_lookup()' translates an arbitrary string into an object + # that we continue traversing. (This is very handy; it lets + # you put user-space objects into your URL-space, eliminating + # the need for digging ID strings out of a query, or checking + # PATHINFO after Quixote's done with it. But it is a + # compromise to security: it opens up the traversal algorithm + # to arbitrary names not listed in _q_exports!) If + # _q_lookup() doesn't exist or is None, a TraversalError is + # raised. + + # Check if component is in _q_exports. The elements in + # _q_exports can be strings or 2-tuples mapping external names + # to internal names. + if component in container._q_exports or component == '_q_index': + internal_name = component + else: + # check for an explicit external to internal mapping + for value in container._q_exports: + if type(value) is types.TupleType: + if value[0] == component: + internal_name = value[1] + break + else: + internal_name = None + + if internal_name is None: + # Component is not in exports list. + object = None + if hasattr(container, "_q_lookup"): + object = container._q_lookup(request, component) + elif hasattr(container, "_q_getname"): + warnings.warn("_q_getname() on %s used; should " + "be replaced by _q_lookup()" % type(container)) + object = container._q_getname(request, component) + if object is None: + raise errors.TraversalError( + private_msg="object %r has no attribute %r" % ( + container, + component)) + + # From here on, you can assume that the internal_name is not None + elif hasattr(container, internal_name): + # attribute is in _q_exports and exists + object = getattr(container, internal_name) + + elif internal_name == '_q_index': + if hasattr(container, "_q_lookup"): + object = container._q_lookup(request, "") + else: + raise errors.AccessError( + private_msg=("_q_index not found in %r" % container)) + + elif hasattr(container, "_q_resolve"): + object = container._q_resolve(internal_name) + if object is None: + raise RuntimeError, ("component listed in _q_exports, " + "but not returned by _q_resolve(%r)" + % internal_name) + else: + # Set the object, so _q_resolve won't need to be called again. + setattr(container, internal_name, object) + + elif type(container) is types.ModuleType: + # try importing it as a sub-module. If we get an ImportError + # here we don't catch it. It means that something that + # doesn't exist was exported or an exception was raised from + # deeper in the code. + mod_name = container.__name__ + '.' + internal_name + object = _get_module(mod_name) + + else: + # a non-existent attribute is in _q_exports, + # and the container is not a module. Give up. + raise errors.TraversalError( + private_msg=("%r in _q_exports list, " + "but not found in %r" % (component, + container))) + + namespace_stack.append(object) + return object + + +def isstring(x): + return isinstance(x, (str, unicode, htmltext)) |