# -*- coding: UTF-8 -*-


# Expression
# By: Frederic Peters <fpeters@entrouvert.com>
#     Emmanuel Raviart <eraviart@entrouvert.com>
#
# Copyright (C) 2004 Entr'ouvert, Frederic Peters & Emmanuel Raviart
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.


"""Stations Module"""


import os
import types
import urllib

import libxml2
import libxslt

import environs
import faults
import http
import locations
import logs
import modules
import namespaces
import xpaths

_depth = 0

class AbstractStation(object):
    _inRawMode = None # Boolean. When None, indicates inherited (=> ask parent).
    _parent = None
    isRemote = False
    isRootElder = False
    isUriDirectory = False # True for directory-like stations (see getUriDirectoryAbsolutePath)
    previous = None
    remoteUri = None
    uriAuthority = None
    uriPathFragment = None
    uriScheme = None

    def __init__(self, previous = None, parent = None, uriPathFragment = None,
                 uriScheme = None, uriAuthority = None, isRemote = False,
                 remoteUri = None):
        if isRemote:
            assert previous is None
            assert parent is None
            assert uriPathFragment is None
            assert uriScheme is None
            assert uriAuthority is None
            assert remoteUri
            self.isRemote = isRemote
            self.remoteUri = remoteUri
        else:
            assert remoteUri is None
            if previous is not None:
                assert isinstance(previous, AbstractStation)
                self.previous = previous
            if parent is not None:
                assert isinstance(parent, AbstractStation)
                self._parent = parent
            if uriPathFragment is not None:
                self.uriPathFragment = uriPathFragment
            if uriScheme is not None:
                self.uriScheme = uriScheme
            if uriAuthority is not None:
                self.uriAuthority = uriAuthority

    def __repr__(self):
        try:
            path = self.getAbsolutePath()
        except:
            path = "no path"
        return "<%s.%s at %s>" % (
            self.__class__.__module__, self.__class__.__name__, path)

    def appendSessionTokenToUrl(self, uri):
        """Return the uri argument, appended with the session token if needed.
        """
        if not environs.getVar("canUseCookie", default = False) \
               or environs.getVar("testCookieSupport", default = False):
            session = environs.getVar("session", default = None)
            if session is not None and session.publishToken:
                uri = locations.appendParameterToUrl(
                    uri, "sessionToken", session.token)
        return uri

    def callHttpFunction(self, function, uriPathFragments = None, command = "GET",
                         instruction = None):
        if command == "HEAD":
            command = "GET"
        if uriPathFragments is None:
            uriPathFragments = ()
        environs.push(_level = "callHttpFunction",
                      currentStation = self,
                      httpCommand = command,
                      instruction = instruction)
        try:
            return function(*uriPathFragments)
        finally:
            environs.pull(_level = "callHttpFunction")

    def checkAccess(self, uriPathFragments, command = None, instruction = None):
        if not self.exists():
            if instruction in ("exists", "existsAndIsAuthorized"):
                return False
            raise faults.PathNotFound("/".join(uriPathFragments))
        if not self.isAccessAuthorized(None):
            if instruction in ("existsAndIsAuthorized", "isAuthorized"):
                return False
            raise faults.PathUnauthorized("/".join(uriPathFragments))
        return True

    def checkAccessAndWalk(self, uriPathFragments, command = None, instruction = None):
        if not self.checkAccess(uriPathFragments, command, instruction):
            return False
        return self.walk(uriPathFragments, command, instruction)

    def constructAbsoluteUri(self, location):
        lowerLocation = location.lower()
        if lowerLocation.find(":", 0, 10) >= 0:
            # Don't touch location if it begins with "http:", "mailto", etc. 
            return location
        else:
            # Relative URI, which may or not start with "file://".
            # FIXME: This is not the good solution. We need a better way to
            # convert file systems path to URI paths.
            if lowerLocation.startswith("file://"):
                absolutePath = location[len("file://") - 1:]
                while len(absolutePath) > 1 and absolutePath[1] == "/":
                    absolutePath = absolutePath[1:]
            else:
                absolutePath = self.constructUriAbsolutePath(location)
            if absolutePath == "/":
                absolutePath = ""
            return "%s://%s%s" % (
                environs.getVar("uriScheme"), environs.getVar("uriAuthority"), absolutePath)

    def constructUri(self, location, preserveAbsolutePath = False):
        """Return the best URI to use inside web pages."""
    
        if location.find(":", 0, 10) >= 0:
            # Don't touch location if it begins with "http:", "mailto", etc. 
            return location
        else:
            # We assume that location is a relative URI whithout "file://" prefix.
            uri = self.constructUriAbsolutePath(
                location, preserveAbsolutePath = preserveAbsolutePath)
            uri = locations.cleanUpUrl(uri, "sessionToken")
            if not environs.getVar("canUseCookie") or environs.getVar("testCookieSupport"):
                session = environs.getVar("session")
                if session is not None and session.publishToken:
                    uri = locations.appendParameterToUrl(uri, "sessionToken", session.token)
            return uri

    def constructUriAbsolutePath(self, location, preserveAbsolutePath = False):
        # Assume that location is a relative URI without "file://" prefix.
        if location.startswith("//"):
            # Relative URI with network path
            raise "TODO"
        elif location and location[0] == "/":
            # Relative URI with absolute path.
            if preserveAbsolutePath:
                return location
            else:
                absolutePath = environs.getVar("httpScriptDirectoryPath")
                if absolutePath[-1] == "/":
                    return "%s%s" % (absolutePath, location[1:])
                else:
                    return "%s/%s" % (absolutePath, location[1:])
        elif location and location[0] == "#":
            absolutePath = self.getUriAbsolutePath()
            return "%s%s" % (absolutePath, location)
        else:
            # Relative URI with relative path.
            absolutePath = self.getUriDirectoryAbsolutePath()
            if absolutePath[-1] == "/":
                return "%s%s" % (absolutePath, location)
            else:
                return "%s/%s" % (absolutePath, location)

    def constructUriInternPath(self, location):
        # Assume that location is a relative URI without "file://" prefix.
        if location.startswith("//"):
            # Relative URI with network path
            raise "TODO"
        elif location and location[0] == "/":
            # Relative URI with absolute path.
            return location
        elif location and location[0] == "#":
            internPath = self.getUriInternPath()
            return "%s%s" % (internPath, location)
        else:
            # Relative URI with relative path.
            internPath = self.getUriDirectoryInternPath()
            if internPath[-1] == "/":
                return "%s%s" % (internPath, location)
            else:
                return "%s/%s" % (internPath, location)

    def convertAbsolutePathToBaseRelative(self, absolutePath):
        baseAbsolutePath = self.getConfigAbsolutePath("yep:base", default = "/")
        prefix = os.path.commonprefix([absolutePath, baseAbsolutePath])
        baseRelativePath = absolutePath[len(prefix):]
        if not baseRelativePath.startswith("/"):
            baseRelativePath = "/" + baseRelativePath
        return baseRelativePath

    def convertBaseRelativePathToAbsolute(self, baseRelativePath):
        while baseRelativePath[0] == "/":
            baseRelativePath = baseRelativePath[1:]
        return os.path.normpath(os.path.join(
            self.getConfigAbsolutePath("yep:base", default = "/"), baseRelativePath))

    def convertNodesToAbsolutePath(self, nodes, nodesOwner, xpath, default = 'none'):
        if not nodes:
            if default == 'none':
                raise KeyError(xpath)
            return default
        location = nodes[0].content.strip()
        return nodesOwner.convertRelativeLocationToAbsolutePath(location)

    def convertNodesToList(self, nodes, nodesOwner, xpath, default = "none"):
        if not nodes:
            if default == "none":
                raise KeyError(xpath)
            return default
        return [x.content for x in nodes]

    def convertNodesToPythonClass(self, nodes, nodesOwner, xpath, default = "none"):
        if not nodes:
            if default == "none":
                raise KeyError(xpath)
            return default
        classPath = nodes[0].content.strip()
        i = classPath.rfind(".")
        assert i >= 0
        modulePath = classPath[:i]
        component = __import__(modulePath)
        componentNames = classPath.split(".")
        try:
            for componentName in componentNames[1:]:
                component = getattr(component, componentName)
        except AttributeError:
            logs.info('Unknown Python class = "%s"' % classPath)
            if default == "none":
                raise KeyError(xpath)
            return default
        return component

    def convertNodesToString(self, nodes, nodesOwner, xpath, default = "none"):
        if not nodes:
            if default == "none":
                raise KeyError(xpath)
            return default
        return nodes[0].content

    def convertPathToAbsolute(self, path):
        if path.startswith("/"):
            return self.convertBaseRelativePathToAbsolute(path)
        else:
            return os.path.normpath(os.path.join(self.getDirectoryAbsolutePath(), path))

    def convertPathToBaseRelative(self, path):
        if path.startswith("/"):
            return path
        else:
            return os.path.normpath(os.path.join(self.getDirectoryBaseRelativePath(), path))

    def convertRelativeLocationToAbsolutePath(self, location):
        if not location:
            return None
        if location.startswith('http://') or location.startswith('https://'):
            # Location is an absolute URL. Use it as a system identifier in XML catalog.
            fileUri = libxml2.catalogResolveSystem(location)
            if fileUri is None:
                logs.debug('Unknown location "%s" in XML catalog.' % location)
                return None
            absolutePath = fileUri[len('file://'):]
            return absolutePath
        elif location.startswith('-//'):
            # Location is a public URI. Use it as a public identifier in XML catalog.
            fileUri = libxml2.catalogResolvePublic(location)
            if fileUri is None:
                logs.debug('Unknown location "%s" in XML catalog.' % location)
                return None
            absolutePath = fileUri[len('file://'):]
            return absolutePath
        else:
            return self.convertPathToAbsolute(location)

    def doHttpAccess(self):
        return self

    def doHttpDelete(self):
        content = environs.getVar("submission").readFile()
        if content is not None:
            logs.debug(content)
        environs.getVar("httpRequestHandler").outputErrorAccessForbidden(self.getUriAbsolutePath())

    def doHttpDelete1(self, multistatus):
        multistatusNode = multistatus.node
        responseNode = multistatusNode.newTextChild(None, "response", None)
        # The href URI is quoted, because Nautilus requires that the returned href is the
        # same as the URL in the PROPFIND command. Otherwise it can not open "untitled
        # folder", because it expects "untitled%20folder".
        responseNode.newTextChild(None, "href", self.getAbsoluteUri(quote = True))
        responseNode.newTextChild(None, "status", "HTTP/1.1 403 Access Forbidden")
        return False

    def doHttpGet(self):
        if self.getInRawMode():
            return self.source()
        return self.styled()

    def doHttpMkcol(self):
        content = environs.getVar("submission").readFile()
        if content is not None:
            logs.debug(content)
        environs.getVar("httpRequestHandler").outputErrorMethodNotAllowed(
            "%s already exists" % self.getUriAbsolutePath())

    def doHttpOptions(self):
        # We need to flush the input buffer anyway.
        content = environs.getVar("submission").readFile()
        if content is not None:
            logs.debug(content)
        environs.getVar("httpRequestHandler").outputData(
            "", contentLocation = None, headers = {"DAV": "1"})

    def doHttpPost(self):
        # FIXME
        raise NotImplementedError

    def doHttpPropfind(self):
        import libxml2
        import documents
        content = environs.getVar("submission").readFile()
        if content is not None:
            logs.debug(content)
        else:
            #If no content, we construct an empty but valid XML content.
            content = """<?xml version="1.0" encoding="utf-8"?>
            <propfind xmlns="DAV:"><prop>
            <getlastmodified xmlns="DAV:"/>
            <creationdate xmlns="DAV:"/>
            <resourcetype xmlns="DAV:"/>
            <getcontenttype xmlns="DAV:"/>
            <getcontentlength xmlns="DAV:"/>
            <getetag xmlns="DAV:"/>
            </prop></propfind>"""
            

        depth = environs.getVar("httpRequestHandler").headers.get("Depth", "infinity")
        try:
            depth = int(depth)
        except ValueError:
            if depth != "infinity":
                raise faults.BadRequest("Bad depth header for PROPFIND request = %s" % depth)
            # Note: A PROPFIND request with a Depth: Infinity header can impose a large burden on
            # the server. The WebDAV Working Group has stated that it is acceptable for DAV servers
            # to refuse these kinds of requests. Properly written client software should not
            # issue them.
        else:
            if depth > 1:
                raise faults.BadRequest("Bad depth header for PROPFIND request = %s" % depth)
        requestDocumentNode = libxml2.readDoc(
            content, None, None,
            libxml2.XML_PARSE_DTDLOAD | libxml2.XML_PARSE_NONET)
        requestDocument = documents.TemporaryDocument(requestDocumentNode)
        self.replyToHttpPropfindRequest(requestDocument, depth)

    def doHttpPut(self):
        httpRequestHandler = environs.getVar("httpRequestHandler")
        httpRequestHandler.outputErrorMethodNotAllowed("Not a document")

    def download(self):
        """Handles HTTP GET."""
        command = environs.getVar("httpCommand")
        if command == "GET":
            self.outputHttpDownload()
        else:
            raise faults.PathNotFound("")

    def exists(self):
        return True

    def generatePlainText(self):
        logs.debug("generatePlainText: No plain text generation for %s." % self)
        return u""

    def generateXml(self, layout):
        logs.debug("generateHtml: No XML generation for %s." % self)
        return False

    def generateXmlDocument(self):
        import documents
        xmlDocument = documents.newTemporaryDocument()
        self.generateXml(xmlDocument)
        return xmlDocument

    def generateXmlDocumentStyledData(self, xmlDocument, stylesheet = None):
        if stylesheet is None:
            return xmlDocument.mimeType, xmlDocument.serialize()
        stylesheetHolder = stylesheet.prototype.getDataHolder()
        stylesheetStyle = libxslt.parseStylesheetDoc(stylesheetHolder.node)
        if stylesheetStyle is None:
            return xmlDocument.mimeType, xmlDocument.serialize()
        environs.push(_level = "generateXmlDocumentStyledData",
                      currentXmlDocument = xmlDocument,
                      currentStation = self)
        try:
            styledDoc = stylesheetStyle.applyStylesheet(xmlDocument.node,
                    environs.getVar("xsltParams", None))
        finally:
            environs.pull(_level = "generateXmlDocumentStyledData")
        data = stylesheetStyle.saveResultToString(styledDoc)
        styledDoc.freeDoc()
        stylesheetStyle.freeStylesheet()
        return "text/html", data

    def getAbsolutePath(self):
        parent = self.getParent()
        assert parent is not None
        return parent.getAbsolutePath()

    def getAbsoluteUri(self, quote = False):
        """Returns the absolute URI for this station.

        example : http://localhost:1997/test.xml
        """
        if self.isRemote:
            return self.constructAbsoluteUri(self.remoteUri)
        else:
            parent = self.getParent()
            if parent is None:
                absoluteUri = self.getAbsoluteUriSchemeAndAuthority()
            else:
                absoluteUri = parent.getAbsoluteUri(quote = quote)
            if self.uriPathFragment and quote:
                uriPathFragment = urllib.quote(self.uriPathFragment)
            else:
                uriPathFragment = self.uriPathFragment
            if uriPathFragment:
                if absoluteUri[-1] == "/":
                    absoluteUri += uriPathFragment
                else:
                    absoluteUri = "%s/%s" % (absoluteUri, uriPathFragment)
            return absoluteUri

    def getAbsoluteUriSchemeAndAuthority(self):
        if self.uriScheme is None:
            return "file:"
        elif self.uriScheme in ("http", "https"):
            return "%s://%s" % (self.uriScheme, self.uriAuthority)
        else:
            raise Exception("Unknown URI scheme = %s" % self.uriScheme)

    def getActionUri(self, action = None):
        uri = self.getActionUriAbsolutePath(action)
        if not environs.getVar("canUseCookie") or environs.getVar("testCookieSupport"):
            session = environs.getVar("session")
            if session is not None and session.publishToken:
                uri = locations.appendParameterToUrl(uri, "sessionToken", session.token)
        return uri

    def getActionUriAbsolutePath(self, action = None):
        uri = self.getUriAbsolutePath()
        if action:
            if uri[-1] == "/":
                uri = "%s%s" % (uri, action)
            else:
                uri = "%s/%s" % (uri, action)
        return uri

    def getAdministrators(self, command = None):
        if command is None:
            commandName = "ACCESS"
        elif command == "HEAD":
            commandName = "GET"
        else:
            commandName = command
        # When there is no "commands" attribute, it means "all commands".
        administratorsNodes, administratorsNodesOwner = self.getConfigNodesAndOwner(
            "yep:administrators[not(@commands) or contains(@commands, '%s')]"
            % commandName.replace("'", "&apos;"))
        if administratorsNodes:
            import elements
            return elements.newElement(administratorsNodes[0], owner = administratorsNodesOwner)
        return None

    def getAllConfigElementNodesAndOwnerCouples(self, xpath):
        parent = self.getParent()
        if parent is None:
            # Root directory of a virtual host.
            virtualHost = environs.getVar("virtualHost", default = None)
            if virtualHost is None:
                configuration = environs.getVar("configuration", default = None)
                if configuration is None:
                    return [], self
                return configuration.getAllConfigElementNodesAndOwnerCouples(xpath)
            return virtualHost.getAllConfigElementNodesAndOwnerCouples(xpath)
        return parent.getAllConfigElementNodesAndOwnerCouples(xpath)

    def getBaseRelativePath(self):
        return self.convertAbsolutePathToBaseRelative(self.getBaseAbsolutePath())

    def getConfigAbsolutePath(self, xpath, default = "none", ignoreGeneralValue = False):
        nodes, nodesOwner = self.getConfigNodesAndOwner(
            xpath, ignoreGeneralValue = ignoreGeneralValue)
        return self.convertNodesToAbsolutePath(nodes, nodesOwner, xpath, default = default)

    def getConfigBaseRelativePath(self, xpath, default = "none", ignoreGeneralValue = False):
        absolutePath = self.getConfigAbsolutePath(
            xpath, default = default, ignoreGeneralValue = ignoreGeneralValue)
        return self.convertAbsolutePathToBaseRelative(absolutePath)

    def getConfigBoolean(self, xpath, default = "none", ignoreGeneralValue = False):
        nodes, nodesOwner = self.getConfigNodesAndOwner(
            xpath, ignoreGeneralValue = ignoreGeneralValue)
        if not nodes:
            if default == "none":
                raise KeyError(xpath)
            return default
        return nodes[0].content.strip().lower() not in ("0", "false")

    def getConfigDataHolderNodesAndOwner(self, mimeType, xpath, ignoreGeneralValue = False):
        parent = self.getParent()
        if parent is None:
            # Root directory of a virtual host.
            virtualHost = environs.getVar("virtualHost", default = None)
            if virtualHost is None:
                configuration = environs.getVar("configuration", default = None)
                if configuration is None:
                    return [], self
                return configuration.getConfigDataHolderNodesAndOwner(
                    mimeType, xpath, ignoreGeneralValue = ignoreGeneralValue)
            return virtualHost.getConfigDataHolderNodesAndOwner(
                mimeType, xpath, ignoreGeneralValue = ignoreGeneralValue)
        return parent.getConfigDataHolderNodesAndOwner(
            mimeType, xpath, ignoreGeneralValue = ignoreGeneralValue)

    def getConfigElementNodesAndOwner(self, namespaceUri, name, xpath, ignoreGeneralValue = False):
        parent = self.getParent()
        if parent is None:
            # Root directory of a virtual host.
            virtualHost = environs.getVar("virtualHost", default = None)
            if virtualHost is None:
                configuration = environs.getVar("configuration", default = None)
                if configuration is None:
                    return [], self
                return configuration.getConfigElementNodesAndOwner(
                    namespaceUri, name, xpath, ignoreGeneralValue = ignoreGeneralValue)
            return virtualHost.getConfigElementNodesAndOwner(
                namespaceUri, name, xpath, ignoreGeneralValue = ignoreGeneralValue)
        return parent.getConfigElementNodesAndOwner(
            namespaceUri, name, xpath, ignoreGeneralValue = ignoreGeneralValue)

    def getConfigElementPythonClass(self, namespaceUri, name, xpath, default = "none",
                                    ignoreGeneralValue = False):
        nodes, nodesOwner = self.getConfigElementNodesAndOwner(
            namespaceUri, name, xpath, ignoreGeneralValue = ignoreGeneralValue)
        return self.convertNodesToPythonClass(nodes, nodesOwner, xpath, default = default)

    def getConfigExistence(self, xpath, ignoreGeneralValue = False):
        nodes, nodesOwner = self.getConfigNodesAndOwner(
            xpath, ignoreGeneralValue = ignoreGeneralValue)
        return bool(nodes)

    def getConfigList(self, xpath, default = "none", ignoreGeneralValue = False):
        nodes, nodesOwner = self.getConfigNodesAndOwner(
            xpath, ignoreGeneralValue = ignoreGeneralValue)
        return self.convertNodesToList(nodes, nodesOwner, xpath, default = default)

    def getConfigNodesAndOwner(self, xpath, ignoreGeneralValue = False):
        assert not ignoreGeneralValue
        return self.getConfigStationNodesAndOwner(xpath)

    def getConfigPythonClass(self, xpath, default = "none", ignoreGeneralValue = False):
        nodes, nodesOwner = self.getConfigNodesAndOwner(
            xpath, ignoreGeneralValue = ignoreGeneralValue)
        return self.convertNodesToPythonClass(nodes, nodesOwner, xpath, default = default)

    def getConfigStationNodesAndOwner(self, xpath):
        parent = self.getParent()
        if parent is None:
            # Root directory of a virtual host.
            virtualHost = environs.getVar("virtualHost", default = None)
            if virtualHost is None:
                configuration = environs.getVar("configuration", default = None)
                if configuration is None:
                    return [], self
                return configuration.getConfigDataHolderNodesAndOwner(
                    mimeType, xpath, ignoreGeneralValue = ignoreGeneralValue)
            return virtualHost.getConfigStationNodesAndOwner(xpath)
        return parent.getConfigStationNodesAndOwner(xpath)

    def getConfigString(self, xpath, default = "none", ignoreGeneralValue = False):
        nodes, nodesOwner = self.getConfigNodesAndOwner(
            xpath, ignoreGeneralValue = ignoreGeneralValue)
        return self.convertNodesToString(nodes, nodesOwner, xpath, default = default)

    def getContainedFileSystemPath(self):
        return self.getFileSystemPath()

    def getContentAdministrators(self, contentName):
        administratorsNodes, administratorsNodesOwner = self.getConfigNodesAndOwner(
            """yep:mode[@name = "%s"]/yep:administrators""" % contentName)
        if administratorsNodes:
            administratorsNode = administratorsNodes[0]
            import elements
            return elements.newElement(administratorsNode, owner = administratorsNodesOwner)
        return None

    def getContentUsers(self, contentName, command = None):
        usersNodes, usersNodesOwner = self.getConfigNodesAndOwner(
            """yep:mode[@name = "%s"]/yep:users""" % contentName)
        if usersNodes:
            if command is None:
                commandName = "ACCESS"
            elif command == "HEAD":
                commandName = "GET"
            else:
                commandName = command
            for usersNode in usersNodes:
                commandsNodes = xpaths.evaluateXpath("@commands", contextNode = usersNode)
                if not commandsNodes or commandName in commandsNodes[0].content.split():
                    # When there is no "commands" attribute, it means "all commands".
                    import elements
                    return elements.newElement(usersNode, owner = usersNodesOwner)
        # By defaut, no action can be done.
        return None

    def getDataHolder(self):
        import dataholders
        if isinstance(self, dataholders.DataHolder):
            return self
        else:
            return self.getParent().getDataHolder()

    def getDirectoryAbsolutePath(self):
        return self.getParent().getDirectoryAbsolutePath()

    def getDirectoryBaseRelativePath(self):
        return self.convertAbsolutePathToBaseRelative(self.getDirectoryAbsolutePath())

    def getElementFeature(self, namespaceUri, elementName):
        elementFeature = None
        # Try to get element features from config.
        elementNodes, elementOwner = self.getConfigElementNodesAndOwner(
            namespaceUri, elementName, None, ignoreGeneralValue = True)
        if elementNodes:
            elementNode = elementNodes[0]
            elementClassNodes = xpaths.evaluateXpath("yep:pythonClass", contextNode = elementNode)
            if elementClassNodes:
                elementClass = self.convertNodesToPythonClass(
                    elementClassNodes, elementOwner, "yep:pythonClass", default = None)
            else:
                elementClass = None
            schemaLocationNodes = xpaths.evaluateXpath("yep:schema", contextNode = elementNode)
            if schemaLocationNodes:
                schemaAbsolutePath = self.convertNodesToAbsolutePath(
                    schemaLocationNodes, elementOwner, "yep:schema", default = None)
            else:
                schemaAbsolutePath = None
            descriptionLocationNodes = xpaths.evaluateXpath(
                "yep:description", contextNode = elementNode)
            if descriptionLocationNodes:
                descriptionAbsolutePath = self.convertNodesToAbsolutePath(
                    descriptionLocationNodes, elementOwner, "yep:description", default = None)
            else:
                descriptionAbsolutePath = None
            import elements
            elementFeature = elements.ElementFeature(
                namespaceUri, elementName, elementClass, schemaAbsolutePath,
                descriptionAbsolutePath)
        # Otherwise, try to get element feature from those registered by Python modules.
        if elementFeature is None:
            try:
                elementFeature = modules.getElementFeature(namespaceUri, elementName)
            except (KeyError, ValueError):
                pass
        return elementFeature

    def getDocument(self):
        return self.getParent().getDocument()

    def getFileSystemPath(self):
        parent = self.getParent()
        assert parent is not None
        return parent.getContainedFileSystemPath()

    def getHttpPostUri(self):
        return self.getUri()

    def getInRawMode(self):
        if self._inRawMode is not None:
            return self._inRawMode
        parent = self.getParent()
        if parent is None:
            return False
        return parent.getInRawMode()

    def getParent(self):
        parent = self._parent
        if parent is None and not self.isRootElder:
            parent = self.previous
        return parent

    def getSimplestDownloadUrl(self):
        """If a station is downloadable, its content is his source."""
        
        return self.getSimplestSourceUrl()

    def getSimplestSourceUrl(self):
        """Return the simplest URL that can return the source of the station.

        Return the simplest form. So when both /.../xxx and /.../xxx/source work, it returns
        /.../xxx.
        Return None, when the station has no source.
        """
        if self.getInRawMode():
            return self.getUri()
        return self.getActionUri("source")

    def getSimplestStyledUrl(self):
        """Return the simplest URL that can return a style X(HT)ML representation of the station.

        Return the simplest form. So when both /.../xxx and /.../xxx/styled work, it returns
        /.../xxx.
        Return None, when the station can not be represented as X(HT)ML.
        """
        if self.getInRawMode():
            return self.getActionUri("styled")
        return self.getUri()

    def getSubPathInternUri(self, subPath):
        assert not subPath or subPath[0] != "/"
        uri = self.getUriInternPath()
        if subPath is not None:
            if uri[-1] == "/":
                uri = "%s%s" % (uri, subPath)
            else:
                uri = "%s/%s" % (uri, subPath)
        return uri

    def getUri(self, quote = False):
        """Returns the station uri within the vhost, including the session token.

        Use this method to get a link to this station from the same vhost.
        Example: /dir/file.xml?sessionToken=123456
        """
        return self.appendSessionTokenToUrl(self.getUriAbsolutePath(quote = quote))

    def getUriAbsolutePath(self, quote = False):
        """Returns the station URL within the vhost.
        Use getUri() rather, in case the Url needs to be appended with a sessionToken.

        Example : "/dir/file.xml"
        """
        previous = self.previous
        if previous is None:
            uriAbsolutePath = "/"
        else:
            uriAbsolutePath = previous.getUriAbsolutePath(quote = quote)
        if self.uriPathFragment and quote:
            uriPathFragment = urllib.quote(self.uriPathFragment)
        else:
            uriPathFragment = self.uriPathFragment
        if uriPathFragment:
            if uriAbsolutePath[-1] == "/":
                uriAbsolutePath += uriPathFragment
            else:
                uriAbsolutePath = "%s/%s" % (uriAbsolutePath, uriPathFragment)
        return uriAbsolutePath

    def getUriDirectoryStation(self):
        """Returns the "directory" station, as shown in the URI.

           recursive method used by getUriDirectoryAbsolutePath
        """
        if self.isUriDirectory or self.isRootElder:
            return self
        else:
            return self.previous.getUriDirectoryStation()

    def getUriDirectoryAbsolutePath(self):
        """Returns the current directory URI

        Examples :
            "/dir/file.xml" -> "/dir"
            "/dir" -> "/dir"
        """
        return self.getUriDirectoryStation().getUriAbsolutePath()

    def getUriDirectoryInternPath(self):
        return self.getUriPrevious().getUriInternPath()

    def getUriInternPath(self):
        previous = self.previous
        if previous is None:
            uriInternPath = "/"
        else:
            uriInternPath = previous.getUriInternPath()
            if self.uriPathFragment:
                if uriInternPath[-1] == "/":
                    uriInternPath += self.uriPathFragment
                else:
                    uriInternPath = "%s/%s" % (uriInternPath, self.uriPathFragment)
        return uriInternPath

    def getUriPrevious(self):
        previous = self.previous
        if previous is None:
            return None
        elif previous.uriPathFragment or previous.previous is None:
            return previous
        else:
            return previous.getUriPrevious()

    def getUsers(self, command = None):
        if command is None:
            commandName = "ACCESS"
        elif command == "HEAD":
            commandName = "GET"
        else:
            commandName = command
        # When there is no "commands" attribute, it means "all commands".
        usersNodes, usersNodesOwner = self.getConfigNodesAndOwner(
            "yep:users[not(@commands) or contains(@commands, '%s')]"
            % commandName.replace("'", "&apos;"))
        if usersNodes:
            import elements
            return elements.newElement(usersNodes[0], owner = usersNodesOwner)
        if command in (None, "GET", "HEAD"):
            import elements
            return elements.Everybody()
        return None

    def getWebDavContentLength(self):
        return None

    def getWebDavContentType(self):
        return None

    def getWebDavCreationDate(self):
        return None

    def getWebDavLastModified(self):
        return None

    def isAccessAuthorized(self, command):
        users = self.getUsers(command)
        if users is not None and users.containsUser():
            return True
        administrators = self.getAdministrators(command)
        return administrators is not None and administrators.containsUser()

    def isContentAccessAuthorized(self, contentName, command):
        users = self.getContentUsers(contentName, command)
        if users is not None and users.containsUser():
            return True
        administrators = self.getContentAdministrators(contentName)
        return administrators is not None and administrators.containsUser()

    def isWebDavCollection(self):
        return False

    def itemDestroyed(self, item):
        """Called before an item (hierarchical) is destroyed"""
        pass

    def itemRenamed(self, item, itemOldLocalId):
        """Called after an item (hierarchical) is renamed"""
        pass

    def outputHttpHtmlDocument(self, htmlDocument, stylesheet = None):
        mimeType, data = self.generateXmlDocumentStyledData(htmlDocument, stylesheet)
        environs.getVar("httpRequestHandler").outputData(data, mimeType = mimeType, contentLocation = self.getUri())

    def outputHttpDownload(self):
        raise faults.PathNotFound("")

    def outputHttpSource(self):
        raise faults.PathNotFound("")

    def replyToHttpPropfindRequest(self, requestDocument, depth):
        import webdav
        multistatus = webdav.Multistatus.newInTemporaryDocument()
        requestDocument.getRootElement().generateWebDavResponse(self, multistatus, depth)
        data = multistatus.getDocument().serialize()
        logs.debug("\nPROPFIND response =\n%s" % data)
        environs.getVar("httpRequestHandler").outputData(
            data, contentLocation = None, mimeType = "text/xml", successCode = 207)

    def source(self):
        """Handles HTTP GET & PUT."""
        command = environs.getVar("httpCommand")
        if command == "GET":
            self.outputHttpSource()
        elif command == "PUT":
            self.doHttpPut()
        else:
            raise faults.PathNotFound("")

    def styled(self):
        """Handles HTTP GET."""
        command = environs.getVar("httpCommand")
        if command == "GET":
            xmlDocument = self.generateXmlDocument()
            if xmlDocument:
                stylesheet = self.getDataHolder().getSiteXslt()
                self.outputHttpHtmlDocument(xmlDocument, stylesheet)
        else:
            raise faults.PathNotFound("")

    def unstyled(self):
        """Handles HTTP GET."""
        command = environs.getVar("httpCommand")
        if command == "GET":
            if xmlDocument:
                xmlDocument = self.generateXmlDocument()
                self.outputHttpHtmlDocument(xmlDocument)
        else:
            raise faults.PathNotFound("")

    def walk(self, uriPathFragments, command = None, instruction = None):
        # Don't check access to self: it must have been done before call.
        #
        # If there is a content, check that it can be accessed, then walk to it.
        import stations
        logs.info("%s%s %s" % (" " * stations._depth, self.__class__.__name__, '/'.join(uriPathFragments)))
        stations._depth += 1
        if uriPathFragments:
            content, contentUriPathFragments = self.walkToContent(
                uriPathFragments, command, instruction)
            assert content is not None
            if content != self:
                r = content.checkAccessAndWalk(contentUriPathFragments, command, instruction)
                stations._depth -= 1
                return r
            uriPathFragments = contentUriPathFragments
        # There is no content. The walk is finished (at least, it seems so) => call the default
        # function.

        # but first we may want to forbid a few referrers
        bad_referrers = self.getConfigList("yep:badReferrers/yep:host", default = [])
        if environs.getVar("httpRequestHandler").headers.get("Referer", "") in bad_referrers:
            return environs.getVar("httpRequestHandler").outputErrorAccessForbidden(
                    self.getUriAbsolutePath())
        
        try:
            if command is None:
                function = self.doHttpAccess
            elif command == "DELETE":
                function = self.doHttpDelete
            elif command in ("GET", "HEAD"):
                function = self.doHttpGet
            elif command == "MKCOL":
                function = self.doHttpMkcol
            elif command == "OPTIONS":
                function = self.doHttpOptions
            elif command == "POST":
                function = self.doHttpPost
            elif command == "PROPFIND":
                function = self.doHttpPropfind
            elif command == "PUT":
                function = self.doHttpPut
            else:
                raise Exception('Unknown HTTP command "%s"' % command)
        except AttributeError:
            function = None

        logs.info('%s%s()' % (" " * stations._depth, function.__name__))
        r = self.walkToFunction(function, uriPathFragments, command, instruction)
        stations._depth -= 1
        return r

    def walkToAction(self, uriPathFragments, command = None, instruction = None):
        actionName = uriPathFragments[0]
        if hasattr(self, actionName):
            method = getattr(self, actionName)
            if type(method) == types.MethodType:
                station = EasyFunctionStation(
                    actionName, method, previous = self, uriPathFragment = actionName)
                return station, uriPathFragments[1:]
        return self, uriPathFragments

    def walkToContent(self, uriPathFragments, command = None, instruction = None):
        item, itemUriPathFragments = self.walkToItem(uriPathFragments, command, instruction)
        assert item is not None
        if item != self or itemUriPathFragments != uriPathFragments:
            return item, itemUriPathFragments
        action, actionUriPathFragments = self.walkToAction(uriPathFragments, command, instruction)
        assert action is not None
        return action, actionUriPathFragments

    def walkToFunction(self, function, uriPathFragments, command = None, instruction = None):
        if function is None:
            if instruction in ("exists", "existsAndIsAuthorized"):
                return False
            raise faults.PathNotFound("/".join(uriPathFragments))
        # Check the number of arguments of function.
        functionCode = function.func_code
        acceptsArbitraryPositionalArguments = functionCode.co_flags & 4 != 0
        if not acceptsArbitraryPositionalArguments:
            argumentsCount = len(uriPathFragments)
            if isinstance(function, types.MethodType):
                argumentsCount += 1 # For argument "self".
            maxArgumentsCount = functionCode.co_argcount
            minArgumentsCount = maxArgumentsCount
            if function.func_defaults is not None:
                minArgumentsCount -= len(function.func_defaults)
            if argumentsCount < minArgumentsCount or argumentsCount > maxArgumentsCount:
                if instruction in ("exists", "existsAndIsAuthorized"):
                    return False
                raise faults.PathNotFound("/".join(uriPathFragments))
        if command is None:
            if instruction in ("exists", "existsAndIsAuthorized", "isAuthorized"):
                return True
            return self
        if not self.isAccessAuthorized(command):
            if instruction in ("existsAndIsAuthorized", "isAuthorized"):
                return False
            raise faults.PathUnauthorized("/".join(uriPathFragments))
        if instruction in ("exists", "existsAndIsAuthorized", "isAuthorized"):
            # Note: In some cases, it will answer True, but the real call (with instruction ==
            # None) may raise a fault.
            return True
        try:
            return self.callHttpFunction(function, uriPathFragments, command, instruction)
        except TypeError:
            # Function takes exactly n argument (but t were given). Act as if the function
            # wasn't found.
            logs.exception("A TypeError exception occured during call of %s(%s)" % (
                function, uriPathFragments))
            if instruction in ("exists", "existsAndIsAuthorized"):
                return False
            raise faults.PathNotFound("/".join(uriPathFragments))
        except faults.PathNotFound, fault:
            logs.exception("Fault %s occured while calling %s(%s)" % (
                fault, function, uriPathFragments))
            if instruction in ("exists", "existsAndIsAuthorized"):
                return False
            raise
        except (faults.PathForbidden, faults.PathUnauthorized), fault:
            logs.exception("Fault %s occured while calling %s(%s)" % (
                fault, function, uriPathFragments))
            if instruction in ("existsAndIsAuthorized", "isAuthorized"):
                return False
            raise

    def walkToItem(self, uriPathFragments, command = None, instruction = None):
        return self, uriPathFragments

    def walkToLocation(self, location, command = None, instruction = None, submission = None,
                       **keywords):
        if locations.isLocalUri(location):
            station = environs.getVar("rootStation")
            uriInternPath = self.constructUriInternPath(location)
            uriPathFragments = [name
                                for name in uriInternPath.split("/") if name]
        else:
            import dataholders
            locationCore, locationExtension = os.path.splitext(location)
            mimeType = None
            if locationExtension in dataholders.mimeTypes:
                mimeType = dataholders.mimeTypes[locationExtension]
            station = dataholders.DataHolder(
                isRemote = True, remoteUri = location, mimeType = mimeType)
            uriPathFragments = []
        if submission is None:
            import submissions
            submission = submissions.FakeSubmission(keywords)
        else:
            assert not keywords
        environs.push(
            _level = "walkToLocation",
            submission = submission)
        try:
            return station.walk(uriPathFragments, command, instruction)
        finally:
            environs.pull(_level = "walkToLocation")


class AbstractContext(AbstractStation):
    prototype = None
    specimen = None

    def __init__(self, prototype, specimen, previous = None, uriPathFragment = None):
        super(AbstractContext, self).__init__(
            previous = previous, uriPathFragment = uriPathFragment)
        if prototype is not None:
            self.prototype = prototype
        if specimen is not None:
            self.specimen = specimen


class EasyFunctionStation(AbstractStation):
    actionName = None
    firstWalkDone = False
    function = None
    uriPathFragments = ()
 
    def __init__(self, actionName, function, *arguments, **keywords):
        super(EasyFunctionStation, self).__init__(*arguments, **keywords)
        assert actionName is not None
        self.actionName = actionName
        assert function is not None
        self.function = function

    def __repr__(self):
        return "%s.%s.%s.%s%s" % (self.__class__.__module__, self.__class__.__name__,
                                  self.actionName, self.function, self.uriPathFragments)

    def isAccessAuthorized(self, command):
        return self.previous.isContentAccessAuthorized(self.actionName, command)

    def walk(self, uriPathFragments, command = None, instruction = None):
        return self.walkToFunction(self.function, uriPathFragments, command, instruction)


class HolderConstructorStation(AbstractStation):
    actionName = None
    elementFeature = None

    def __init__(self, actionName, elementFeature, *arguments, **keywords):
        super(HolderConstructorStation, self).__init__(*arguments, **keywords)
        assert actionName is not None
        self.actionName = actionName
        assert elementFeature is not None
        self.elementFeature = elementFeature

    def __repr__(self):
        return "%s.%s(%s, %s)" % (self.__class__.__module__, self.__class__.__name__,
                                  self.actionName, self.elementFeature)

    def isAccessAuthorized(self, command):
        return self.previous.isContentAccessAuthorized(self.actionName, command)

    def walk(self, uriPathFragments, command = None, instruction = None):
        xmlHolder = self.elementFeature.newXmlHolder(
            self.previous, uriPathFragment = self.actionName, temporary = True)
        return xmlHolder.checkAccessAndWalk(uriPathFragments, command, instruction)


class XslTransformStation(AbstractStation):
    def __init__(self, xsltFilePathNode, nodeOwner, *arguments, **keywords):
        super(XslTransformStation, self).__init__(*arguments, **keywords)
        self.xsltFilePath = \
            nodeOwner.convertRelativeLocationToAbsolutePath(xsltFilePathNode.content)
        self.mimeType = xsltFilePathNode.prop("mimeType")
        self.mode = xsltFilePathNode.prop("mode")
    
    def transformation(self, uriPathFragments):
        command = environs.getVar("httpCommand")
        if command != "GET":
            raise faults.PathNotFound("")

        xmlDocument = self.getParent().generateXmlDocument()
        stylesheetStyle = libxslt.parseStylesheetFile(self.xsltFilePath)
        styledDoc = stylesheetStyle.applyStylesheet(xmlDocument.node,
                environs.getVar("xsltParams", None))
        data = styledDoc.serialize()
        stylesheetStyle.freeStylesheet()
        if self.mode == "raw":
            environs.getVar("httpRequestHandler").outputData(data, mimeType = self.mimeType)
        else:
            self.outputHttpHtmlDocument(styledDoc, self.getParent().getDataHolder().getSiteXslt())


    def walk(self, uriPathFragments, command = None, instruction = None):
        return self.walkToFunction(self.transformation, uriPathFragments, command, instruction)

def walkToLocation(location, command = None, instruction = None, submission = None, **keywords):
    if locations.isLocalUri(location):
        station = environs.getVar("rootStation")
        assert location and location[0] == "/", \
            'Location "%s" is not an absolute path. Use method Station.walkToLocation instead.' \
                % location
        uriInternPath = location
        uriPathFragments = [name for name in uriInternPath.split("/") if name]
    else:
        import dataholders
        locationCore, locationExtension = os.path.splitext(location)
        mimeType = None
        if locationExtension in dataholders.mimeTypes:
            mimeType = dataholders.mimeTypes[locationExtension]
        station = dataholders.DataHolder(
            isRemote = True, remoteUri = location, mimeType = mimeType)
        uriPathFragments = []
    if submission is None:
        import submissions
        submission = submissions.FakeSubmission(keywords)
    else:
        assert not keywords
    environs.push(
        _level = "walkToLocation",
        submission = submission)
    try:
        return station.walk(uriPathFragments, command, instruction)
    finally:
        environs.pull(_level = "walkToLocation")
