This repository has been archived on 2023-02-21. You can view files and clone it, but cannot push or open issues or pull requests.
glasnost/glasnost-web/code/webhandler.py

1354 lines
51 KiB
Python

# -*- 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 imp
import locale
import md5
import os
import socket
import sys
import time
import types
try:
import gzip
except ImportError:
gzip = None
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
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_FORBIDDEN
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.EACCESS, errno.EISDIR):
return HTTP_FORBIDDEN
if staticFile:
lastModTime = time.gmtime(os.stat(_req.filename)[-2])
return self.outputStaticFile(
req, staticFile.read(), lastModTime)
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,
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,
preferences = 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,
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)
cache.checkCachedValues()
virtualHost = None
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' }
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[1:])
if os.path.exists(fileName):
staticFilePath = fileName
try:
staticFile = open(staticFilePath)
except IOError, error:
if error.errno in (errno.EACCESS, errno.EISDIR):
return HTTP_FORBIDDEN
lastModTime = time.gmtime(os.stat(staticFilePath)[-2])
return self.outputStaticFile(
req, staticFile.read(), lastModTime, mimeType)
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'])
xmlPostRaw = req.read(clen)
try:
import xml.dom.minidom
except ImportError:
raise apache.SERVER_RETURN, apache.HTTP_NOT_IMPLEMENTED
try:
xmlPost = xml.dom.minidom.parseString(xmlPost)
except: # TODO: tighter check
if context.getVar('debug'):
raise
raise apache.SERVER_RETURN, apache.HTTP_NOT_ACCEPTABLE
context.setVar('xmlPost', xmlPost)
context.setVar('xmlPostRaw', xmlPostRaw)
class FakeFieldStorage:
list = []
fieldStorage = FakeFieldStorage
fields = {}
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])
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
if session and session.has_key('userToken') and session['userToken']:
userToken = session['userToken']
context.setVar('userToken', userToken)
userId = getProxyForServerRole('authentication').getUserId()
if userId:
userToken = session['userToken']
else:
userToken = None
userId = None
del session['userToken']
if session.has_key('userId'):
del session['userId']
session['isDirty'] = 1
context.setVar('userId', userId)
context.setVar('userToken', userToken)
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 preferences
preferences = None
if userToken:
try:
preferences = getWebForServerRole(
'preferences').getPreference()
except faults.UnknownServerId:
pass
else:
context.setVar('preferences', preferences)
# Handle languages.
languages = []
if session and session.has_key('lang'):
languages = [session['lang']]
elif preferences is not None and preferences.language \
and preferences.language != 'None':
languages = [preferences.language]
elif 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:
reqCache = RequestCache(req, languages[0])
reqCache.cacheTime = context.getVar('cacheTime')
if not virtualHost.cacheFiles:
reqCache.caching = -1
modTime = reqCache.checkDepends()
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)
profiling = webTools.getConfig('Profiling', 'false') == 'true'
handlerContext.setVar('profiling', profiling)
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):
if gzip 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:
zbuf = cStringIO.StringIO()
zfile = gzip.GzipFile(mode = 'wb', fileobj = zbuf)
zfile.write(data)
zfile.close()
data = zbuf.getvalue()
req.headers_out['Content-Encoding'] = 'gzip'
t1 = context.getVar('t1')
if lastModTime:
req.headers_out['Last-Modified'] = time.strftime(
'%a, %d %b %Y %H:%M:%S GMT', lastModTime)
req.headers_out['Content-Length'] = '%d' % len(data)
# 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 <= t:
return HTTP_NOT_MODIFIED
except (ValueError, KeyError):
pass
if mimeType:
req.content_type = mimeType
req.send_http_header()
if req.method != 'HEAD':
try:
req.write(data)
except IOError:
# the user probably cancelled the request
return OK
return OK
def parseHttpPath(self, remaining):
context.push()
try:
result = self.parseHttpPathRemote(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 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:
if moduleName == HTTP_NOT_FOUND:
return moduleName
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.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 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')
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')
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):
if self.headers_in.has_key('Host'):
hostName = self.headers_in['Host']
else:
hostName = ''
uri = self.unparsed_uri
return binascii.hexlify(md5.new(hostName + 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