# -*- coding: UTF-8 -*- # Expression # By: Frederic Peters # Emmanuel Raviart # # 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 = """ """ 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("'", "'")) 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("'", "'")) 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")