# -*- coding: iso-8859-15 -*-


# Glasnost
# By: Odile Bénassy <obenassy@entrouvert.com>
#     Romain Chantereau <rchantereau@entrouvert.com>
#     Nicolas Clapiès <nclapies@easter-eggs.org>
#     Pierre-Antoine Dejace <padejace@entrouvert.be>
#     Thierry Dulieu <tdulieu@easter-eggs.com>
#     Florent Monnier <monnier@codelutin.com>
#     Cédric Musso <cmusso@easter-eggs.org>
#     Frédéric Péters <fpeters@entrouvert.be>
#     Benjamin Poussin <poussin@codelutin.com>
#     Emmanuel Raviart <eraviart@entrouvert.com>
#     Sébastien Régnier <regnier@codelutin.com>
#     Emmanuel Saracco <esaracco@easter-eggs.com>
#
# Copyright (C) 2000, 2001 Easter-eggs & Emmanuel Raviart
# Copyright (C) 2002 Odile Bénassy, Code Lutin, Thierry Dulieu, Easter-eggs,
#     Entr'ouvert, Frédéric Péters, Benjamin Poussin, Emmanuel Raviart,
#     Emmanuel Saracco & Théridion
# Copyright (C) 2003 Odile Bénassy, Romain Chantereau, Nicolas Clapiès,
#     Code Lutin, Pierre-Antoine Dejace, Thierry Dulieu, Easter-eggs,
#     Entr'ouvert, Florent Monnier, Cédric Musso, Ouvaton, Frédéric Péters,
#     Benjamin Poussin, Rodolphe Quiédeville, Emmanuel Raviart, Sébastien
#     Régnier, Emmanuel Saracco, Théridion & Vecam
#
# 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.


__doc__ = """Glasnost Web Handler"""

__version__ = '$Revision$'[11:-2]


import __builtin__
import base64
import binascii
import Cookie
import cStringIO
import errno
import fcntl
import md5
import os
import sys
import time
import types

from mod_python import apache, util

glasnostPythonDir = '/usr/local/lib/glasnost-devel' # changed on make install
sys.path.insert(0, glasnostPythonDir)

import glasnost

import glasnost.common.applications as applications
import glasnost.common.context as context
from glasnost.common.cache import cache, temporaryCache
import glasnost.common.faults as faults
import glasnost.common.tools_new as commonTools
import glasnost.web.tools_new as webTools
import glasnost.common.xhtmlgenerator as X

from glasnost.proxy.DispatcherProxy import getRegisteredRoles

from glasnost.web.tools import *
    

class Application(applications.Application):
    applicationName = 'GlasnostWeb'
    applicationRole = 'web'

    def callFunction(self):
        function = context.getVar('function')
        positionalArguments = context.getVar('positionalArguments')
        keywordsArguments = context.getVar('keywordsArguments')
        objectId = context.getVar('objectId')

        if positionalArguments is None:
            arguments = []
        else:
            arguments = positionalArguments[:]
        if keywordsArguments is None:
            keywords = {}
        else:
            keywords = keywordsArguments.copy()
        if objectId is not None:
            keywords['id'] = objectId
        
        if keywords.has_key('nextUri'):
            context.setVar('nextUri', keywords['nextUri'])
            del keywords['nextUri']

        functionCode = function.func_code
        expectedArguments = functionCode.co_varnames[:functionCode.co_argcount]
        if function.func_defaults is None:
            requiredArguments = expectedArguments        
        else:
            requiredArguments = expectedArguments[
                    :-len(function.func_defaults)]
        acceptsArbitraryKeywordsArguments = functionCode.co_flags & 8 != 0
        acceptsArbitraryPositionalArguments = functionCode.co_flags & 4 != 0

        # Remove unexpected arguments.
        if not acceptsArbitraryKeywordsArguments:
            for argumentName in keywords.keys():
                if argumentName not in expectedArguments:
                    del keywords[argumentName]

        # Insert the needed keywords arguments inside the positional arguments
        # and check that there is no missing argument.
        newArguments = []
        hasSelf = 0
        for argumentName in requiredArguments:
            if argumentName == 'self':
                hasSelf = 1
            elif argumentName in keywords.keys():
                newArguments.append(keywords[argumentName])
                del keywords[argumentName]
            elif not arguments:
                if context.getVar('debug'):
                    raise Exception('missing argument = %s' % argumentName)
                return HTTP_NOT_FOUND
            else:
                newArguments.append(arguments[0])
                del arguments[0]
        arguments = newArguments + arguments

        # Check that there is not too much positional arguments.
        if not acceptsArbitraryPositionalArguments \
           and hasSelf + len(arguments) > len(expectedArguments):
            if context.getVar('debug'):
                raise Exception('Too much positional arguments = %s'
                                % arguments)
            return HTTP_NOT_FOUND

        req = context.getVar('req')
        try:
            try:
                result = function(*arguments, **keywords)
            finally:
                if req.buffered:
                    req.finish()
                session = context.getVar('session')
                if session is not None:
                    getProxyForServerRole('sessions').setSession(session,
                            context.getVar('virtualHost').defaultDispatcherId)
        except (faults.MissingItem, faults.UnknownServerId):
            if context.getVar('debug'):
                raise
            if req.caching:
                req.cancel()
            if req.buffered:
                req.finish()
            return HTTP_NOT_FOUND
        except faults.UserAccessDenied:
            if context.getVar('debug'):
                raise
            if req.caching:
                req.cancel()
            if req.buffered:
                req.finish()
            return HTTP_UNAUTHORIZED
        except IOError, exc:
            if exc.filename:
                # TODO: check this doesn't catch IOError to client socket
                raise
            # Write failed, client closed connection.
            if req.caching:
                req.cancel()
            return HTTP_INTERNAL_SERVER_ERROR
        except:
            if req.caching:
                req.cancel()
            raise

        return result

    def handler(self, req):
        # Use direct access to _req for speed.
        try:
            _req = req._req
        except AttributeError:
            _req = req

        # If the file at webFilePath exists and is static (images,
        # stylesheets...) then return its content directly.
        if not (_req.filename.endswith('/index') or
                _req.filename.endswith('.py') or
                _req.filename.endswith('.tal') or
                _req.filename.endswith('.xtal')) and \
                os.path.exists(_req.filename):
            staticFile = None
            try:
                staticFile = open(_req.filename)
            except IOError, error:
                if error.errno in (errno.EACCES, errno.EISDIR):
                    return HTTP_FORBIDDEN
            if staticFile:
                lastModTime = time.gmtime(os.stat(_req.filename)[-2])
                try:
                    return self.outputStaticFile(
                        req, staticFile, lastModTime)
                except IOError, error:
                    if error.errno in (errno.EACCES, errno.EISDIR):
                        return HTTP_FORBIDDEN

        context.push(
            _level = 'handler',
            applicationId = None,
            applicationToken = None,
            cache = None,
            canUseCookie = 0, # Are the cookies accepted by the client browser?
            cookie = None,
            defaultDispatcherId = None,
            dispatcherId = None,
            fallbackTemplatesDirectoryPath = None,
            function = None,
            helpInUrl = 0,
            htmlHeaders = [],
            httpHostName = None,
            httpLocalPath = None,
                # The HTTP path, without the httpScriptDirectoryPath prefix.
            httpMethod = req.method,
            httpPath = None,
                # The full HTTP path, beginning with httpScriptDirectoryPath.
            httpPort = None,
            httpProtocol = None, # http or https
            httpQuery = None,
            httpScriptDirectoryPath = None,
                # Used when glasnost is inside another web site.
            keywordsArguments = None,
            knownRoles = None,
            languageSetInUrl = 0,
            objectId = None,
            positionalArguments = None,
            readLanguages = None,
            req = req,
            sectionLevel = 1,
            serverRole = None,
            session = None,
            sessionToken = None,
            sessionTokenInCookie = 0, # Was the sessionToken stored in the url?
            talEngine = None,
            templateDirectoryName = None,
            templatesDirectoryPath = None,
            templateFileName = None,
            templatePrefix = None,
            temporaryCache = None,
            user = None,
            userId = '',
            userToken = '',
            virtualHost = None,
            webFileName = None,
            webDirectoryPaths = [],
            xmlPost = None,
            )

        httpPath = _req.uri
        while len(httpPath) > 1 and httpPath[-1] == '/':
            httpPath = httpPath[:-1]
        if not httpPath:
            httpPath = '/'
        context.setVar('httpPath', httpPath)
        context.setVar('httpQuery', req.args)
        if _req.path_info:
            httpScriptPath = _req.uri[: - len(_req.path_info)]
        else:
            httpScriptPath = _req.uri
        while len(httpScriptPath) > 1 and httpScriptPath[-1] == '/':
            httpScriptPath = httpScriptPath[:-1]
        if not httpScriptPath:
            httpScriptPath = '/'
        httpScriptDirectoryPath = commonTools.makeHttpScriptDirectoryPath(
                httpScriptPath)
        context.setVar(
            'httpScriptDirectoryPath', httpScriptDirectoryPath)
        if httpScriptDirectoryPath == '/':
            httpLocalPath = httpPath
        else:
            httpLocalPath = httpPath[len(httpScriptDirectoryPath):]
        context.setVar('httpLocalPath', httpLocalPath)

        webFilePath = _req.filename
        while len(webFilePath) > 1 and webFilePath[-2] == '/':
            webFilePath = webFilePath[:-1]
        if not webFilePath:
            webFilePath = '/'
        if os.path.isdir(webFilePath):
            webFilePath += '/'
        webDirectoryPath, webFileName = os.path.split(webFilePath)
        if not webFileName:
            choices = ['index.tal', 'index.py', 'index.html']
            for c in choices:
                if os.path.exists(os.path.join(webDirectoryPath, c)):
                    webFileName = c
                    webFilePath += '/' + webFileName
                    break
            else:
                return HTTP_NOT_FOUND

        context.getVar('webDirectoryPaths').append(webDirectoryPath)
        context.setVar('webFileName', webFileName)

        webFileExtension = ''
        splittedWebFileName = webFileName.split('.')
        if len(splittedWebFileName) > 1:
            webFileExtension = splittedWebFileName[-1]

        cookie = None
        cookieContent = {}
        if req.headers_in.has_key('Cookie'):
            cookie = Cookie.SimpleCookie(req.headers_in['Cookie'])
            for k, v in cookie.items():
                cookieContent[k] = v.value
        context.setVar('cookie', cookie)

        # There seems to be no way to know if we are currently using the http
        # or https protocol!
        # context.setVar('httpProtocol', None)
        httpHostName = None
        httpPort = None
        if req.headers_in.has_key('Host'):
            hostNameAndPort = req.headers_in['Host']
            if ':' in hostNameAndPort:
                httpHostName, httpPort = hostNameAndPort.split(':')
            else:
                httpHostName = hostNameAndPort
        context.setVar('httpHostName', httpHostName)
        context.setVar('httpPort', httpPort)

        if not httpHostName:
            return HTTP_BAD_REQUEST

        dispatcherId = 'glasnost://%s' % httpHostName
        # Ensure that dispatcherId is valid.
        dispatcherId = commonTools.extractDispatcherId(dispatcherId)
        context.setVar('dispatcherId', dispatcherId)
        context.setVar('defaultDispatcherId', dispatcherId)
        context.setVar('applicationId', commonTools.makeApplicationId(
                dispatcherId, self.applicationRole))

        # we have to do it before asking for the VirtualHost object
        context.setVar('cache', cache)
        context.setVar('temporaryCache', temporaryCache)
        try:
            cache.checkCachedValues()
            temporaryCache.checkCachedValues()
        except AssertionError:
            # [NOTAFIX] sth happens; don't know why; ignore
            # File ".../glasnost/proxy/DispatcherProxy.py", line 296, in add
            #    assert self.serverAccessor == serverAccessor
            pass

        virtualHost = None
        splitedHttpLocalPath = [x for x in httpLocalPath.split('/') if x]
        if splitedHttpLocalPath and splitedHttpLocalPath[0] == 'help':
            import glasnost.web.VirtualHostsWeb as VirtualHostsWeb
            virtualHost = VirtualHostsWeb.VirtualHost()
            virtualHost.title = N_('Glasnost Help')
            virtualHost.hostName = 'help'
            virtualHost.defaultDispatcherId = 'glasnost://help'
            virtualHost.templateDirectoryName = 'help'
            virtualHost.language = 'en'
        else:
            virtualHostsWeb = getWebForServerRole('virtualhosts')
            try:
                virtualHost = virtualHostsWeb.getObjectByHostName(httpHostName)
            except faults.MissingItem:
                if context.getVar('debug'):
                    raise 'No Virtual Host defined for %s' % httpHostName
                return HTTP_NOT_FOUND
            except (faults.UnknownDispatcherInId, faults.UnknownServerId):
                if context.getVar('debug'):
                    raise
                return HTTP_SERVICE_UNAVAILABLE
        context.setVar('virtualHost', virtualHost)

        self.loadWebConfigOptions()
        dispatcherId = context.getVar('dispatcherId')

        if not webFileExtension in ['tal', 'py', 'xtal'] \
               and webFileName != 'index':
            mimeTypes = { '.css': 'text/css',
                          '.jpeg': 'image/jpeg',
                          '.png': 'image/png',
                          '.htc': 'text/x-component'}
            mimeType = None
            for k, v in mimeTypes.items():
                if httpPath.endswith(k):
                    mimeType = v
                    break
            for path in context.getVar('webDirectoryPaths'):
                fileName = os.path.join(
                        path, httpPath.replace('/help/', '/')[1:])
                if os.path.exists(fileName):
                    staticFilePath = fileName
                    try:
                        staticFile = open(staticFilePath)
                    except IOError, error:
                        if error.errno in (errno.EACCES, errno.EISDIR):
                            return HTTP_FORBIDDEN
                        raise
                    lastModTime = time.gmtime(os.stat(staticFilePath)[-2])
                    try:
                        return self.outputStaticFile(
                            req, staticFile, lastModTime, mimeType)
                    except IOError, error:
                        if error.errno in (errno.EACCES, errno.EISDIR):
                            return HTTP_FORBIDDEN

        args = {}
        context.setVar('keywordsArguments', args)

        # Process input, if any.
        try:
            fieldStorage = util.FieldStorage(req, keep_blank_values = 1)
        except apache.SERVER_RETURN, e:
            if e.args[0] != 501:
                raise
            # POST method with unknown content-type
            ctype = _req.headers_in['content-type']
            if ctype != 'text/xml':
                raise apache.SERVER_RETURN, apache.HTTP_NOT_IMPLEMENTED
            # we support content-type: text/xml for things like the Comment API
            # (see http://wellformedweb.org/story/9)
            clen = int(_req.headers_in['content-length'])
            xmlPost = req.read(clen)
            context.setVar('xmlPost', xmlPost)
            class FakeFieldStorage:
                list = []
                
                def keys(self):
                    return []
            fieldStorage = FakeFieldStorage()

        fields = {}
        # This code is commented, because it doesn't work when a field has
        # several occurences using the same key. In this case, the key is
        # listed several times in fieldStorage.list, but
        # fieldStorage[field.name] is already a complete list.
##         for field in fieldStorage.list:
##             if field.name is not None and not fields.has_key(field.name):
##                 fields[field.name] = fieldStorage[field.name]
##             elif field.name is not None:
##                 if not type(fields[field.name]) is type([]):
##                     fields[field.name] = [ fields[field.name] ]
##                 fields[field.name].append(fieldStorage[field.name])
        for key in fieldStorage.keys():
            if key is not None:
                fields[key] = fieldStorage[key]

        fieldNamesToDelete = []
        for fieldName, fieldValue  in fields.items():
            if fieldName[-7:] == '|base64' \
               and type(fieldValue) in [types.StringType, types.UnicodeType]:
                fieldNamesToDelete.append(fieldName)
                name = fieldName[:-7]
                fields[name] = base64.decodestring(fieldValue)
        for fieldName, fieldValue  in fields.items():
            if fieldName[-6:] == '_field':
                if type(fieldValue) in [types.StringType, types.UnicodeType]:
                    raise Exception('Wrong field value for %s = "%s"' % (
                        fieldName, fieldValue))
                else:
                    fileContent = fieldValue.file.read()
                    if fileContent:
                        fieldNamesToDelete.append(fieldName)
                        name = fieldName[:-6]
                        fields[name] = fileContent
                        fields[name + 'FileName'] = fieldValue.filename
                        fields[name + 'Type'] = fieldValue.type

        for fieldName in fieldNamesToDelete:
            del fields[fieldName]
        args.update(fields)

        # Handle optional session.
        canUseCookie = 1
        sessionToken = None
        sessionTokenInCookie = 0
        sessionTokenInUrl = 0
        if args.has_key('sessionToken'):
            sessionToken = args['sessionToken']
            sessionTokenInUrl = 1
        if cookieContent.has_key('sessionToken'):
            if sessionToken is None:
                sessionTokenInCookie = 1
                sessionToken = cookieContent['sessionToken']
            elif sessionToken == cookieContent['sessionToken']:
                sessionTokenInCookie = 1
        session = None
        if sessionToken is not None:
            try:
                session = getProxyForServerRole('sessions').getSession(
                    sessionToken, req.connection.remote_ip)
            except faults.UnknownSessionToken:
                if sessionTokenInUrl:
                    uri = cleanUpUnparsedUri(['sessionToken'], 'http')
                    return redirect(uri)
                else:
                    sessionToken = None
                    sessionTokenInCookie = 0
            except faults.InvalidSessionToken:
                session = None
                sessionToken = None
            except faults.UnknownServerId:
                # no SessionServer, no problem
                session = None
                sessionToken = None
            if sessionToken is not None and not sessionTokenInCookie:
                # The sessionToken is valid but is not stored in the cookie.
                # So, don't try to use the cookie.
                canUseCookie = 0
        context.setVar('canUseCookie', canUseCookie)
        context.setVar('session', session)
        context.setVar('sessionToken', sessionToken)
        context.setVar('sessionTokenInCookie', sessionTokenInCookie)
        userToken = ''
        userId = None
        user = None
        if session and session.has_key('userToken') and session['userToken']:
            userToken = session['userToken']
            context.setVar('userToken', userToken)
            userId = getWebForServerRole('identities').getUserId()
            if userId:
                try:
                    user = getWeb(userId).getObject(userId)
                except faults.MissingItem:
                    pass
            if user is None:
                userToken = ''
                userId = None
                del session['userToken']
                session['isDirty'] = 1
        context.setVar('userToken', userToken)
        context.setVar('userId', userId)
        context.setVar('user', user)

        if not userToken and req.headers_in.has_key('Authorization'):
            authorization = req.headers_in['Authorization']
            tokens = authorization.split()
            if len(tokens) != 2 or tokens[0] != 'Basic':
                raise apache.SERVER_RETURN, apache.HTTP_NOT_ACCEPTABLE
            # TODO: http auth

        # Handle languages.
        languages = []
        if session and session.has_key('lang'):
            languages = [session['lang']]
        elif user is not None:
            userLanguage = user.getLanguage()
            if userLanguage is not None:
                languages = [userLanguage]
        if  not languages and req.headers_in.has_key('Accept-Language'):
            try:
                languages = req.headers_in['Accept-Language']
                languages = languages.split(',')
                languages = [x.strip()[:2] for x in languages]
            except: # TODO: tighter check
                return HTTP_BAD_REQUEST
        translationsProxy = getProxyForServerRole('translations')
        languages = [language == 'C' and 'en' or language
                     for language in languages]
        try:
            possibleLanguages = translationsProxy.getPossibleLanguages()
        except (faults.UnknownServerId, faults.UnknownDispatcherInId):
            possibleLanguages = []
        if not virtualHost.language in possibleLanguages:
            possibleLanguages.append(virtualHost.language)
        languages = [x for x in languages if x in possibleLanguages]
        if not languages:
            languages = [virtualHost.language]
        context.setVar('readLanguages', languages)

        self.setLanguage()


        # Handle web cache.
        reqCache = None
        if sessionToken is None:
            try:
                reqCache = RequestCache(req, languages[0])
            except OSError:
                reqCache = None
            reqCache.cacheTime = context.getVar('cacheTime')
            if not virtualHost.cacheFiles:
                reqCache = None
            else:
                req.headers_out['Expires'] = reqCache.getExpires()
                modTime = None
            if reqCache and reqCache.isPageCached():
                try:
                    cachedPage, contentType = reqCache.getCachePage()
                except OSError:
                    # file was removed just on the wrong time
                    cachedPage = None
                
                if cachedPage is not None:
                    setHttpCookie()
                    if context.getVar('debug'):
                        req.headers_out['X-Glasnost-CachedFile'] = 'yep'
                    return self.outputStaticFile(
                            req, cachedPage, modTime, contentType)
        elif context.getVar('useBufferedRequests'):
            reqCache = BufferedRequest(req)
            reqCache.caching = 0
            reqCache.depends = []

        if reqCache is None:
            # The system caching is not activated when a session is open.
            reqCache = Request(req)
            reqCache.caching = 0
            reqCache.depends = []
            reqCache.buffered = 0
        context.setVar('req', reqCache)

        # Handle the httpLocalPath in URL and call appropriate function.
        knownRoles = getRegisteredRoles(dispatcherId)
        context.setVar('knownRoles', knownRoles)
        context.setVar('templateFileName', 'template.html')
        context.setVar('templatePrefix', '')
        result = self.parseHttpPath([x for x in httpLocalPath.split('/') if x])
        if result is not None:
            return result
        return HTTP_NOT_FOUND

    def loadConfigOptions(self):
        applications.Application.loadConfigOptions(self)

        configContext = context.get(_level = 'config')

        varDirectoryPath = commonTools.getConfig(
                'Misc', 'vardir', default = '/var/lib/glasnost')
        configContext.setVar('varDirectoryPath', varDirectoryPath)

        defaultWebDirectoryPath = commonTools.getConfig(
                'Misc', 'WebDirectoryPath')
        configContext.setVar(
                'defaultWebDirectoryPath', defaultWebDirectoryPath)

    def loadWebConfigOptions(self):
        handlerContext = context.get(_level = 'handler')

        virtualHost = context.getVar('virtualHost')

        dispatcherId = virtualHost.defaultDispatcherId
        # Ensure that dispatcherId is valid.
        dispatcherId = commonTools.extractDispatcherId(dispatcherId)
        handlerContext.setVar('dispatcherId', dispatcherId)
        handlerContext.setVar('defaultDispatcherId', dispatcherId)

        handlerContext.setVar('applicationId', commonTools.makeApplicationId(
                dispatcherId, self.applicationRole))

        virtualHost.useHTTPS = \
                webTools.getConfig('UseHTTPS', 'false') == 'true'
        virtualHost.cacheFiles = \
                webTools.getConfig('CacheFiles', 'false') == 'true'

        debug = webTools.getConfig('Debug', 'false') == 'true'
        handlerContext.setVar('debug', debug)

        debugTal = webTools.getConfig('DebugTAL', 'false') == 'true'
        handlerContext.setVar('debugTal', debugTal)

        helpMode = webTools.getConfig('HelpMode')
        if helpMode not in ('local', 'internet', 'none'):
            helpMode = 'internet'
        handlerContext.setVar('helpMode', helpMode)

        profiling = webTools.getConfig('Profiling', 'false') == 'true'
        handlerContext.setVar('profiling', profiling)

        # TODO: should be set in template infos.xml
        sectionLevel = int(
                webTools.getConfig('sectionLevel', default = 1))
        handlerContext.setVar('sectionLevel', sectionLevel)

        cacheTime = int(webTools.getConfig('CacheTime', '15')) * 60
        handlerContext.setVar('cacheTime', cacheTime)

        useBufferedRequests = webTools.getConfig(
                'UseBufferedRequests', 'false') == 'true'
        handlerContext.setVar('useBufferedRequests', useBufferedRequests)

        talExtensionsDirectoryPaths = []
        talExtensionsDirectoryPath = commonTools.getConfig(
                context.getVar('dispatcherId'), 'TalExtensionsDirectoryPath')
        if talExtensionsDirectoryPath:
            talExtensionsDirectoryPaths.append(talExtensionsDirectoryPath)
        talExtensionsDirectoryPaths.append(
                os.path.join(context.getVar('varDirectoryPath'), 'extensions'))
        handlerContext.setVar('talExtensionsDirectoryPaths',
                             talExtensionsDirectoryPaths)

        templatesDirectoryPath = commonTools.getConfig(
            context.getVar('dispatcherId'), 'TemplatesDirectoryPath',
            default = commonTools.getConfig('Misc', 'TemplatesDirectoryPath'))
        if templatesDirectoryPath and virtualHost.templateDirectoryName:
            directoryName = '%s/%s/web' % (templatesDirectoryPath,
                                           virtualHost.templateDirectoryName)
            if os.path.exists(directoryName):
                context.getVar('webDirectoryPaths').insert(0, directoryName)

        webDirectoryPath = commonTools.getConfig(
                virtualHost.hostName, 'WebDirectoryPath')
        if webDirectoryPath and os.path.exists(webDirectoryPath):
            context.getVar('webDirectoryPaths').insert(0, webDirectoryPath)

        webDirectoryPath = context.getVar('defaultWebDirectoryPath')
        if webDirectoryPath and os.path.exists(webDirectoryPath):
            context.getVar('webDirectoryPaths').append(webDirectoryPath)

        fallbackTemplatesDirectoryPath = commonTools.getConfig(
            'Misc', 'TemplatesDirectoryPath', default=None)
        if not templatesDirectoryPath:
            # configuration file changed between 0.5.4 and 0.5.5. This
            # fallbacks to the old configuration. Will be removed someday.
            templatesDirectoryPath = commonTools.getConfig(
                    'Templates', 'Directory') + '/../'
            fallbackTemplatesDirectoryPath = templatesDirectoryPath
        handlerContext.setVar('templatesDirectoryPath', templatesDirectoryPath)
        handlerContext.setVar('fallbackTemplatesDirectoryPath',
                              fallbackTemplatesDirectoryPath)

        templateDirectoryName = commonTools.getConfig(
                'Misc', 'DefaultTemplate')
        if virtualHost.templateDirectoryName is not None:
            templateDirectoryName = virtualHost.templateDirectoryName
        handlerContext.setVar('templateDirectoryName', templateDirectoryName)

        scriptsDirectoryPaths = []
        scriptsDirectoryPath = commonTools.getConfig(
                context.getVar('dispatcherId'), 'ScriptDirectoryPath')
        if scriptsDirectoryPath:
            scriptsDirectoryPaths.append(scriptsDirectoryPath)
        if templatesDirectoryPath \
               and not templatesDirectoryPath in scriptsDirectoryPaths:
            scriptsDirectoryPaths.append(templatesDirectoryPath)
        handlerContext.setVar('scriptsDirectoryPaths', scriptsDirectoryPaths)

        enableSpellChecking = webTools.getConfig(
                'DisableSpellchecking', 'false') != 'true'
        handlerContext.setVar('enableSpellChecking', enableSpellChecking)

        adminEmailAddress = commonTools.getConfig(
                context.getVar('dispatcherId'), 'AdminEmailAddress',
                default = commonTools.getConfig(
                        'Mail', 'Admin', default = 'root@localhost'))
        handlerContext.setVar('adminEmailAddress', adminEmailAddress)

    def outputStaticFile(self, req, data, lastModTime = None, mimeType = None):
        try:
            import gzip
        except ImportError:
            gzip = None

        if type(data) is type(''):
            fileSize = len(data)
        else:
            fileSize = os.fstat(data.fileno())[6]
        if gzip and fileSize < 1000000 and \
                req.headers_in.has_key('Accept-Encoding'):
            value = req.headers_in['Accept-Encoding']
            if value.find('gzip') != -1 and value.find('gzip;q=0') == -1:
                if not type(data) is type(''):
                    data = data.read()
                zbuf = cStringIO.StringIO()
                zfile = gzip.GzipFile(mode = 'wb',  fileobj = zbuf)
                zfile.write(data)
                zfile.close()
                data = zbuf.getvalue()
                del zbuf
                req.headers_out['Content-Encoding'] = 'gzip'
                fileSize = len(data)
        if lastModTime:
            req.headers_out['Last-Modified'] = time.strftime(
                '%a, %d %b %Y %H:%M:%S GMT', lastModTime)
        req.headers_out['Content-Length'] = '%d' % fileSize
        # TODO: could also output Content-MD5
        if lastModTime and req.headers_in.has_key('If-Modified-Since'):
            # we don't want to use bandwith if the file was not modified
            try:
                t = time.strptime(req.headers_in['If-Modified-Since'][:25],
                                    '%a, %d %b %Y %H:%M:%S')
                if lastModTime[:8] <= t[:8]:
                    return HTTP_NOT_MODIFIED
            except (ValueError, KeyError):
                pass
        if mimeType:
            req.content_type = mimeType
        req.send_http_header()
        if req.method != 'HEAD':
            if type(data) is type(''):
                try:
                    req.write(data)
                except IOError:
                    # the user probably cancelled the request
                    return OK
            else:
                try:
                    t = 1
                    while t:
                        t = data.read(1000000) # 1MB chunks
                        req.write(t)
                except IOError:
                    # the user probably cancelled the request
                    return OK
        return OK

    def parseHttpPath(self, remaining):
        context.push()
        try:
            result = self.parseHttpPathHelp(remaining)
            if result is not None:
                return result
            result = self.parseHttpPathRemote(remaining)
            if result is not None:
                return result
            result = self.parseHttpPathRemoteSso(remaining)
            if result is not None:
                return result
            result = self.parseHttpPathLanguage(remaining)
            if result is not None:
                return result
            result = self.parseHttpPathWorkspace(remaining)
            if result is not None:
                return result
            result = self.parseHttpPathAlias(remaining)
            if result is not None:
                return result
            result = self.parseHttpPathServerRole(remaining)
            if result is not None:
                return result
            result = self.parseHttpPathTalFile(remaining)
            if result is not None:
                return result
            result = self.parseHttpPathModule(remaining)
            if result is not None:
                return result
            return None
        finally:
            context.pull()

    def parseHttpPathAction(self, remaining):
        context.push()
        try:
            if not remaining:
                action = None
            else:
                action = remaining[0]
                remaining = remaining[1:]
            if action is not None and '-' in action:
                splittedAction = [x for x in action.split('-') if x]
                if len(splittedAction) >= 2:
                    template = '-'.join(splittedAction[:-1])
                    context.setVar(
                        'templateFileName', 'template-%s.html' % template)
                    context.setVar('templatePrefix', '%s-' % template)
                    action = splittedAction[-1]
            serverRole = context.getVar('serverRole')
            web = getWebForServerRole(serverRole)
            context.setVar('web', web)
            function = getAppropriateFunction(
                    serverRole, context.getVar('objectId'), action)
            if function is None:
                if context.getVar('objectId') is not None:
                    if action is not None:
                        remaining.insert(0, action)
                    web = getWebForServerRole(context.getVar('serverRole'))
                    if hasattr(web, 'parseHttpPathAction'):
                        return web.parseHttpPathAction(remaining)
                return None
            context.setVar('function', function)

            result = self.parseHttpPathArguments(remaining)
            if result is not None:
                return result
            return None
        finally:
            context.pull()

    def parseHttpPathAlias(self, remaining):
        context.push()
        try:
            assert context.getVar('objectId') is None
            assert context.getVar('serverRole') is None
            if not remaining:
                return None
            if not 'pagenames' in context.getVar('knownRoles'):
                return None
            pageNamesProxy = getProxyForServerRole('pagenames')
            try:
                path = ''
                num = 1
                lastKnown, lastNum = '', 0
                for r in remaining:
                    path += r
                    objectId = getWebForServerRole('pagenames').getIdByName(
                            path, serverId = context.getVar('dispatcherId'))
                    if objectId:
                        lastKnown, lastNum = objectId, num
                    if not pageNamesProxy.hasObjectStartingWithPath(path,
                            serverId = context.getVar('dispatcherId')):
                        break
                    path += '/'
                    num += 1
            except faults.UnknownServerId:
                return None
            objectId, num = lastKnown, lastNum
            if not objectId:
                return None
            context.setVar('dispatcherId',
                    commonTools.extractDispatcherId(objectId))
            context.setVar('serverRole', commonTools.extractRole(objectId))
            context.setVar('objectId', objectId)
            remaining = remaining[num:]

            result = self.parseHttpPathAction(remaining)
            if result is not None:
                return result
            return None
        finally:
            context.pull()

    def parseHttpPathArguments(self, remaining):
        context.push()
        try:
            if remaining:
                context.setVar('positionalArguments', remaining)
                remaining = []
            return self.callFunction()
        finally:
            context.pull()

    def parseHttpPathHelp(self, remaining):
        context.push()
        try:
            assert context.getVar('serverRole') is None
            if len(remaining) < 1:
                return None
            if remaining[0] != 'help':
                return None
            context.setVar('helpInUrl', 1)
            remaining = remaining[1:]

            result = self.parseHttpPathLanguage(remaining)
            if result is not None:
                return result
            result = self.parseHttpPathWorkspace(remaining)
            if result is not None:
                return result
            result = self.parseHttpPathAlias(remaining)
            if result is not None:
                return result
            result = self.parseHttpPathServerRole(remaining)
            if result is not None:
                return result
            result = self.parseHttpPathTalFile(remaining)
            if result is not None:
                return result
            result = self.parseHttpPathModule(remaining)
            if result is not None:
                return result
            return None
        finally:
            context.pull()

    def parseHttpPathId(self, remaining):
        context.push()
        try:
            assert context.getVar('objectId') is None
            assert context.getVar('serverRole') is not None
            web = getWebForServerRole(context.getVar('serverRole'))
            if hasattr(web, 'parseHttpPathId'):
                return web.parseHttpPathId(remaining)
            if not remaining:
                return None
            objectId = '%s/%s/%s' % (
                    context.getVar('dispatcherId'),
                    context.getVar('serverRole'),
                    remaining[0])
            remaining = remaining[1:]
            context.setVar('objectId', objectId)
            result = self.parseHttpPathAction(remaining)
            if result is not None:
                return result
            return None
        finally:
            context.pull()

    def parseHttpPathLanguage(self, remaining):
        context.push()
        try:
            translationsProxy = getProxyForServerRole('translations')
            if not translationsProxy:
                return
            if len(remaining) < 1:
                return None
            try:
                if remaining[0] not in translationsProxy.getPossibleLanguages(
                        serverId = context.getVar('dispatcherId')):
                    return None
            except faults.UnknownDispatcherInId:
                return None
            languages = [remaining[0]]
            context.setVar('readLanguages', languages)
            context.setVar('languageSetInUrl', 1)
            self.setLanguage()
            remaining = remaining[1:]

            result = self.parseHttpPathWorkspace(remaining)
            if result is not None:
                return result
            result = self.parseHttpPathAlias(remaining)
            if result is not None:
                return result
            result = self.parseHttpPathServerRole(remaining)
            if result is not None:
                return result
            result = self.parseHttpPathTalFile(remaining)
            if result is not None:
                return result
            result = self.parseHttpPathModule(remaining)
            if result is not None:
                return result
            return None
        finally:
            context.pull()
            
    def parseHttpPathModule(self, remaining):
        context.push()
        try:
            if remaining:
                moduleName = remaining[0]
                if moduleName.endswith('.py'):
                    moduleName = moduleName[:-len('.py')]
                if len(remaining) >= 2:
                    functionName = remaining[1]
                    if functionName[0] == '_':
                        return None
                    remaining = remaining[2:]
                else:
                    functionName = 'index'
                    remaining = remaining[1:]
            else:
                moduleName = 'index'
                functionName = 'index'
            # FIXME HTTP errors should be handled better than that:
            try:
                if sys.modules.has_key(moduleName):
                    module = sys.modules[moduleName]
                else:
                    module = apache.import_module(
                            moduleName,
                            context.getVar('req')._req,
                            context.getVar('webDirectoryPaths'))
            except ImportError:
                if context.getVar('debug'):
                    raise
                return None
            if not hasattr(module, functionName):
                remaining.insert(0, functionName)
                functionName = 'index'
            try:
                function = getattr(module, functionName)
            except AttributeError:
                if context.getVar('debug'):
                    raise
                return None
            if type(function) != types.FunctionType:
                return None
            # Build a fake serverRole (needed by X.xxxUrl functions).
            context.setVar('serverRole', moduleName)
            context.setVar('function', function)

            result = self.parseHttpPathArguments(remaining)
            if result is not None:
                return result
            return None
        finally:
            context.pull()

    def parseHttpPathRemote(self, remaining):
        context.push()
        try:
            assert context.getVar('serverRole') is None
            if len(remaining) < 2:
                return None
            if remaining[0] != 'remote':
                return None
            dispatcherHostName = remaining[1]
            context.setVar('dispatcherId',
                           'glasnost://%s' % dispatcherHostName)
            remaining = remaining[2:]

            result = self.parseHttpPathHelp(remaining)
            if result is not None:
                return result
            result = self.parseHttpPathLanguage(remaining)
            if result is not None:
                return result
            result = self.parseHttpPathWorkspace(remaining)
            if result is not None:
                return result
            result = self.parseHttpPathAlias(remaining)
            if result is not None:
                return result
            result = self.parseHttpPathServerRole(remaining)
            if result is not None:
                return result
            result = self.parseHttpPathTalFile(remaining)
            if result is not None:
                return result
            result = self.parseHttpPathModule(remaining)
            if result is not None:
                return result
            return None
        finally:
            context.pull()

    def parseHttpPathRemoteSso(self, remaining):
        context.push()
        try:
            assert context.getVar('serverRole') is None
            if len(remaining) < 2:
                return None
            if remaining[0] != 'remote-sso':
                return None
            dispatcherHostName = remaining[1]

            user = context.getVar('user')
            try:
                identitiesProxy = getProxyForServerRole('identities')
                if user:
                    identification = user.identityIdentifications[0]
                    token = identitiesProxy.checkIdentityLocalNameIdentifierIDP(
                            context.getVar('httpHostName'),
                            identification.peerNameIdentifier,
                            serverId = 'glasnost://%s' % dispatcherHostName)
                    context.setVar('localUserToken', context.getVar('userToken'))
                    context.setVar('userToken', token)
                else:
                    # testing other end
                    identitiesProxy.isAdmin(
                        serverId = 'glasnost://%s' % dispatcherHostName)
            except (faults.UnknownServerId, faults.UnknownDispatcherInId):
                return failure(
                        _('Unable to connect to %s.') % dispatcherHostName,
                        X.rootUrl())

            context.setVar('dispatcherId',
                           'glasnost://%s' % dispatcherHostName)
            context.setVar('remoteSso', 1)
            remaining = remaining[2:]

            result = self.parseHttpPathHelp(remaining)
            if result is not None:
                return result
            result = self.parseHttpPathLanguage(remaining)
            if result is not None:
                return result
            result = self.parseHttpPathWorkspace(remaining)
            if result is not None:
                return result
            result = self.parseHttpPathAlias(remaining)
            if result is not None:
                return result
            result = self.parseHttpPathServerRole(remaining)
            if result is not None:
                return result
            result = self.parseHttpPathTalFile(remaining)
            if result is not None:
                return result
            result = self.parseHttpPathModule(remaining)
            if result is not None:
                return result
            return None
        finally:
            context.pull()

    def parseHttpPathServerRole(self, remaining):
        context.push()
        try:
            assert context.getVar('serverRole') is None
            if not remaining:
                return None
            if context.getVar('defaultDispatcherId') != context.getVar('dispatcherId'):
                context.getVar('knownRoles').append(remaining[0])
            if not remaining[0] in context.getVar('knownRoles'):
                return None
            context.setVar('serverRole', remaining[0])
            remaining = remaining[1:]

            result = self.parseHttpPathAction(remaining)
            if result is not None:
                return result
            result = self.parseHttpPathId(remaining)
            if result is not None:
                return result
            return None
        finally:
            context.pull()

    def parseHttpPathTalFile(self, remaining):
        context.push()
        try:
            if remaining:
                fileNameCore = remaining[0]
                if fileNameCore.endswith('.tal'):
                    fileNameCore = fileNameCore[:-len('.tal')]
                elif fileNameCore.endswith('.xtal'):
                    fileNameCore = fileNameCore[:-len('.xtal')]
                remaining = remaining[1:]
            else:
                fileNameCore = 'index'
            for webDirectoryPath in context.getVar('webDirectoryPaths'):
                talFilePathCore = os.path.join(webDirectoryPath, fileNameCore)
                talFilePath = '%s.tal' % talFilePathCore
                xtalFilePath = '%s.xtal' % talFilePathCore
                if os.path.exists(talFilePath):
                    function = processTALFile
                    remaining.insert(0, talFilePath)
                    break
                elif os.path.exists(xtalFilePath):
                    function = processXTALFile
                    remaining.insert(0, xtalFilePath)
                    break
            else:
                return None
            # Build a fake serverRole (needed by X.xxxUrl functions).
            context.setVar('serverRole', fileNameCore)
            context.setVar('function', function)

            result = self.parseHttpPathArguments(remaining)
            if result is not None:
                return result
            return None
        finally:
            context.pull()

    def parseHttpPathWorkspace(self, remaining):
        context.push()
        try:
            assert context.getVar('objectId') is None
            assert context.getVar('serverRole') is None
            if not remaining:
                return None
            if not 'workspace' in context.getVar('knownRoles'):
                return None
            workspaceName = remaining[0]
            if not 0: # test of workgroup existance
                return None
            # alter dispatcher ?
            remaining = remaining[1:]
            return self.parseHttpPath(remaining)
        finally:
            context.pull()

    def setLanguage(self):
        virtualHost = context.getVar('virtualHost')
        languages = context.getVar('readLanguages')
        import locale
        try:
            locale.setlocale(locale.LC_COLLATE, (languages[0], None))
        except locale.Error:
            locale.setlocale(locale.LC_COLLATE, 'C')

        domains = [ '%s-web' % glasnost.applicationName ] + (
                    virtualHost.locales or [])
        translation = commonTools.translation(domains, languages)
        __builtin__.__dict__['_'] = translation.gettext


class BufferedRequest:
    buffered = 1

    def __init__(self, req):
        self._req = req
        self.buffer = cStringIO.StringIO()
    
    def write(self, str):
        self.buffer.write(str)

    def finish(self):
        self.buffer.seek(0)
        self._req.write(self.buffer.read())

    def __getattr__(self, attr):
        try:
            return getattr(self._req, attr)
        except AttributeError:
            raise AttributeError, attr

    def __setattr__(self, attr, val):
        try:
            if attr != '_req':
                setattr(self._req, attr, val)
            else:
                self.__dict__['_req'] = val
        except AttributeError:
            self.__dict__[attr] = val

class Request(apache.Request):
    def write(self, st):
        if type(st) is type(u''):
            st = st.encode('iso-8859-1')
        return self._req.write(st)

class RequestCache:
    buffered = 0
    cacheDirectoryPath = None
    cacheFile = None
    cacheFilePath = None
    cacheFilePathDepends = None
    cacheFilePathMimeType = None
    cacheTime = 15 * 60 # Default cache time: 15 minutes.
    caching = 1
    depends = None

    def __init__(self, req, language):
        for key, value in req.__dict__.items():
            self.__dict__[key] = value
        self.cacheDirectoryPath = os.path.join(
                context.getVar('varDirectoryPath'), 'webcache',
                context.getVar('virtualHost').hostName)
        if not os.path.exists(self.cacheDirectoryPath):
            try:
                os.mkdir(self.cacheDirectoryPath)
            except OSError:
                return

        self.cacheFilePath = os.path.join(
                self.cacheDirectoryPath,
                self.getCacheFileName(language))
        self.cacheFilePathDepends = os.path.join(
                self.cacheDirectoryPath,
                self.getCacheFileName(language) + '.depends')
        self.cacheFilePathMimeType = os.path.join(
                self.cacheDirectoryPath,
                self.getCacheFileName(language) + '.mimetype')
        self.depends = []

    def cancel(self):
        if self.cacheFile is not None:
            self.cacheFile.close()
            del self.cacheFile
        if os.path.exists(self.cacheFilePath):
            try:
                os.unlink(self.cacheFilePath)
            except OSError:
                pass

    def checkDepends(self):
        if not os.path.exists(self.cacheFilePathDepends):
            return
        if not os.path.exists(self.cacheFilePath):
            return
        ids = open(self.cacheFilePathDepends).readlines()
        modificationTime = 0
        for id in ids:
            web = getWeb(id)
            object = web.getPartialObject(id, ['modificationTime'])
            if not hasattr(object, 'modificationTime') or \
                    object.modificationTime > os.stat(self.cacheFilePath)[ST_CTIME]:
                try:
                    os.unlink(self.cacheFilePath)
                except OSError:
                    pass
                break
            if object.modificationTime > modificationTime:
                modificationTime = object.modificationTime
        else:
            self.caching = 1
        return time.gmtime(modificationTime)

    def isPageCached(self):
        return os.path.exists(self.cacheFilePath) \
               and time.time() - \
               os.stat(self.cacheFilePath)[ST_CTIME] <= self.cacheTime

    def closeCachePage(self):
        self.cacheFile.close()
        del self.cacheFile

        if self.caching == -1:
            if self.depends:
                try:
                    open(self.cacheFilePathDepends, 'w').write('\n'.join(
                        self.depends))
                    open(self.cacheFilePathMimeType, 'w').write(
                        self.content_type)
                except IOError:
                    pass
            else:
                try:
                    os.unlink(self.cacheFilePath)
                except OSError:
                    pass
        elif self.caching == 1:
            open(self.cacheFilePathMimeType, 'w').write(self.content_type)


    def getCachePage(self):
        self.cacheFile = open(self.cacheFilePath, 'r')
        fcntl.lockf(self.cacheFile, fcntl.LOCK_SH)
        cachePage = self.cacheFile.read()
        fcntl.lockf(self.cacheFile, fcntl.LOCK_UN)
        self.cacheFile.close()
        if os.path.exists(self.cacheFilePathMimeType):
            mimeType = open(self.cacheFilePathMimeType).read()
        else:
            mimeType = 'text/html'
        return cachePage, mimeType

    def getCacheFileName(self, language):
        uri = self.unparsed_uri
        return binascii.hexlify(md5.new(uri + language).digest())

    def getExpires(self):
        try:
            t = os.stat(self.cacheFilePath)[ST_CTIME] + self.cacheTime
        except OSError:
            t = time.time() + self.cacheTime
        return time.strftime('%a, %d %b %Y %H:%M:%S %Z', time.gmtime(t))

    def openCachePage(self):
        try:
            self.cacheFile = open(self.cacheFilePath, 'w')
        except IOError:
            self.cacheFile = open('/dev/null', 'w')

    def write(self, str):
        self._req.write(str)
        if self.cacheFile is not None:
            fcntl.lockf(self.cacheFile, fcntl.LOCK_EX)
            self.cacheFile.write(str)
            fcntl.lockf(self.cacheFile, fcntl.LOCK_UN)

    def __getattr__(self, attr):
        try:
            return getattr(self._req, attr)
        except AttributeError:
            raise AttributeError, attr

    def __setattr__(self, attr, val):
        try:
            if attr != '_req':
                setattr(self._req, attr, val)
            else:
                self.__dict__['_req'] = val
        except AttributeError:
            self.__dict__[attr] = val


def handler(req):
    if req.method in ('OPTIONS', 'TRACE', 'CONNECT', 'PROPFIND'):
        return HTTP_NOT_IMPLEMENTED

    application = Application()
    application.launch()
    try:
        result = application.handler(req)
    except apache.SERVER_RETURN, e:
        # those are legitimate and should be raised to modpython handler
        raise
    except Exception, e:
        import traceback
        f = cStringIO.StringIO()
        traceback.print_exc(file = f)
        f = f.getvalue()
        webTools.sendTalkBack(req, f)
        raise
    return result


if webTools.getConfig('Profiling', 'false') == 'true':
    _handler = handler
    def handler(req):
        import profile
        prof = profile.Profile()
        try:
            prof = prof.runctx('global result; result = _handler(req)',
                               globals(), locals())
        except SystemExit:
            pass
        prof.dump_stats('/tmp/glasnost-web-%s.prof' % \
                os.path.split(req.filename)[1])
        return result

