summaryrefslogtreecommitdiff
path: root/pypers/europython05/Quixote-2.0/publish1.py
diff options
context:
space:
mode:
Diffstat (limited to 'pypers/europython05/Quixote-2.0/publish1.py')
-rwxr-xr-xpypers/europython05/Quixote-2.0/publish1.py270
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))