Commit accff8b5 authored by Jim Fulton's avatar Jim Fulton

Major decomposition!

parent 7fa9e2be
##############################################################################
#
# Zope Public License (ZPL) Version 0.9.4
# ---------------------------------------
#
# Copyright (c) Digital Creations. All rights reserved.
#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
# conditions are met:
#
# 1. Redistributions in source code must retain the above
# copyright notice, this list of conditions, and the following
# disclaimer.
#
# 2. Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions, and the following
# disclaimer in the documentation and/or other materials
# provided with the distribution.
#
# 3. Any use, including use of the Zope software to operate a
# website, must either comply with the terms described below
# under "Attribution" or alternatively secure a separate
# license from Digital Creations.
#
# 4. All advertising materials, documentation, or technical papers
# mentioning features derived from or use of this software must
# display the following acknowledgement:
#
# "This product includes software developed by Digital
# Creations for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# 5. Names associated with Zope or Digital Creations must not be
# used to endorse or promote products derived from this
# software without prior written permission from Digital
# Creations.
#
# 6. Redistributions of any form whatsoever must retain the
# following acknowledgment:
#
# "This product includes software developed by Digital
# Creations for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# 7. Modifications are encouraged but must be packaged separately
# as patches to official Zope releases. Distributions that do
# not clearly separate the patches from the original work must
# be clearly labeled as unofficial distributions.
#
# Disclaimer
#
# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND
# ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
# FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
# SHALL DIGITAL CREATIONS OR ITS CONTRIBUTORS BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
# THE POSSIBILITY OF SUCH DAMAGE.
#
# Attribution
#
# Individuals or organizations using this software as a web site
# must provide attribution by placing the accompanying "button"
# and a link to the accompanying "credits page" on the website's
# main entry point. In cases where this placement of
# attribution is not feasible, a separate arrangment must be
# concluded with Digital Creations. Those using the software
# for purposes other than web sites must provide a corresponding
# attribution in locations that include a copyright using a
# manner best suited to the application environment.
#
# This software consists of contributions made by Digital
# Creations and many individuals on behalf of Digital Creations.
# Specific attributions are listed in the accompanying credits
# file.
#
##############################################################################
__version__='$Revision: 1.1 $'[11:-2]
from string import join, split, find, rfind, lower, upper
from urllib import quote
UNSPECIFIED_ROLES=''
try:
from ExtensionClass import Base
class RequestContainer(Base):
def __init__(self,**kw):
for k,v in kw.items(): self.__dict__[k]=v
def manage_property_types(self):
return type_converters.keys()
except:
class RequestContainer:
def __init__(self,**kw):
for k,v in kw.items(): self.__dict__[k]=v
_marker=[]
class BaseRequest:
"""Provide basic ZPublisher request management
This object provides access to request data. Request data may
vary depending on the protocol used.
Request objects are created by the object publisher and will be
passed to published objects through the argument name, REQUEST.
The request object is a mapping object that represents a
collection of variable to value mappings.
"""
common={} # Common request data
_auth=None
def __init__(self, other=None, **kw):
if other is None: other=kw
else:
for k, v in kw.items(): other[k]=v
self.other=other
def __setitem__(self,key,value):
"""Set application variables
This method is used to set a variable in the requests "other"
category.
"""
self.other[key]=value
set=__setitem__
def __getitem__(self,key,
default=_marker, # Any special internal marker will do
):
"""Get a variable value
Return a value for the required variable name.
The value will be looked up from one of the request data
categories. The search order is environment variables,
other variables, form data, and then cookies.
"""
if key=='REQUEST': return self
v=self.other.get(key, _marker)
if v is not _marker: return v
v=self.common.get(key, default)
if v is not _marker: return v
raise KeyError, key
__getattr__=get=__getitem__
def has_key(self,key):
return self.get(key, _marker) is not _marker
def keys(self):
keys = {}
keys.update(self.common)
keys.update(self.other)
return keys.keys()
def items(self):
result = []
get=self.get
for k in self.keys():
result.append((k, get(k)))
return result
def values(self):
result = []
get=self.get
for k in self.keys():
result.append(get(k))
return result
def __str__(self):
return join(map(lambda item: "%s:\t%s" % item, self.items()), "\n")
__repr__=__str__
def traverse(self, path, response=None):
"""Traverse the object space
The REQUEST must already have a PARENTS item with at least one
object in it. This is typically the root object.
"""
request=self
request_get=request.get
if response is None: response=self.response
debug_mode=response.debug_mode
if path[:1] != '/': path='/'+path
if path[-1:] != '/': path=path+'/'
if find(path,'/.') >= 0:
path=join(split(path,'/./'),'/')
l=find(path,'/../',1)
while l > 0:
p1=path[:l]
path=path[:rfind(p1,'/')+1]+path[l+4:]
l=find(path,'/../',1)
path=path[1:-1]
path=split(path,'/')
while path and not path[0]: path = path[1:]
method=upper(request_get('REQUEST_METHOD',''))
baseflag=0
if method=='GET' or method=='POST':
method='index_html'
else: baseflag=1
URL=request['URL']
parents=request['PARENTS']
object=parents[-1]
del parents[-1]
if hasattr(object,'__roles__'): roles=object.__roles__
else: roles=UNSPECIFIED_ROLES
# if the top object has a __bobo_traverse__ method, then use it
# to possibly traverse to an alternate top-level object.
if hasattr(object,'__bobo_traverse__'):
try: object=object.__bobo_traverse__(request)
except: pass
# Get default object if no path was specified:
if not path:
if not method: return response.forbiddenError(entry_name)
entry_name=method
try:
if hasattr(object,entry_name):
response.setBase(URL)
path=[entry_name]
else:
try:
if object.has_key(entry_name):
path=[entry_name]
except: pass
except: pass
# Traverse the URL to find the object:
if hasattr(object, '__of__'):
# Try to bind the top-level object to the request
object=object.__of__(RequestContainer(REQUEST=request))
steps=[]
path.reverse()
while path:
entry_name=path[-1]
del path[-1]
URL="%s/%s" % (URL,quote(entry_name))
got=0
if entry_name:
if entry_name[:1]=='_':
if debug_mode:
return response.debugError(
"Object name begins with an underscore at: %s"
% URL)
else: return response.forbiddenError(entry_name)
if hasattr(object,'__bobo_traverse__'):
request['URL']=URL
subobject=object.__bobo_traverse__(request,entry_name)
if type(subobject) is type(()) and len(subobject) > 1:
while len(subobject) > 2:
parents.append(subobject[0])
subobject=subobject[1:]
object, subobject = subobject
else:
try:
if baseflag and hasattr(object, 'aq_base'):
subobject=getattr(object.aq_base, entry_name)
else: subobject=getattr(object,entry_name)
except AttributeError:
got=1
try: subobject=object[entry_name]
except (KeyError, IndexError,
TypeError, AttributeError):
if entry_name=='.': subobject=object
elif entry_name=='..' and parents:
subobject=parents[-1]
elif debug_mode:
return response.debugError(
"Cannot locate object at: %s" %URL)
else: return response.notFoundError(URL)
if subobject is object and entry_name=='.':
URL=URL[:rfind(URL,'/')]
else:
try:
try: doc=subobject.__doc__
except: doc=getattr(object, entry_name+'__doc__')
if not doc: raise AttributeError, entry_name
except:
if debug_mode:
return response.debugError("Missing doc string at: %s"
% URL)
else: return response.notFoundError("%s" % (URL))
if hasattr(subobject,'__roles__'):
roles=subobject.__roles__
else:
if not got:
roleshack=entry_name+'__roles__'
if hasattr(object, roleshack):
roles=getattr(object, roleshack)
# Promote subobject to object
parents.append(object)
object=subobject
steps.append(entry_name)
# Check for method:
if not path:
if method and hasattr(object,method) and entry_name != method:
request._hacked_path=1
path=[method]
else:
if (hasattr(object, '__call__') and
hasattr(object.__call__,'__roles__')):
roles=object.__call__.__roles__
if request._hacked_path:
i=rfind(URL,'/')
if i > 0: response.setBase(URL[:i])
# THIS LOOKS WRONG!
if entry_name != method and method != 'index_html':
if debug_mode:
response.debugError("Method %s not found at: %s"
% (method,URL))
else: response.notFoundError(method)
request.steps=steps
parents.reverse()
# Do authorization checks
user=groups=None
i=0
if roles is not None:
last_parent_index=len(parents)
if hasattr(object, '__allow_groups__'):
groups=object.__allow_groups__
inext=0
else:
inext=None
for i in range(last_parent_index):
if hasattr(parents[i],'__allow_groups__'):
groups=parents[i].__allow_groups__
inext=i+1
break
if inext is not None:
i=inext
if hasattr(groups, 'validate'): v=groups.validate
else: v=old_validation
auth=request._auth
if v is old_validation and roles is UNSPECIFIED_ROLES:
# No roles, so if we have a named group, get roles from
# group keys
if hasattr(groups,'keys'): roles=groups.keys()
else:
try: groups=groups()
except: pass
try: roles=groups.keys()
except: pass
if groups is None:
# Public group, hack structures to get it to validate
roles=None
auth=''
if v is old_validation:
user=old_validation(groups, request, auth, roles)
elif roles is UNSPECIFIED_ROLES: user=v(request, auth)
else: user=v(request, auth, roles)
while user is None and i < last_parent_index:
parent=parents[i]
i=i+1
if hasattr(parent, '__allow_groups__'):
groups=parent.__allow_groups__
else: continue
if hasattr(groups,'validate'): v=groups.validate
else: v=old_validation
if v is old_validation:
user=old_validation(groups, request, auth, roles)
elif roles is UNSPECIFIED_ROLES: user=v(request, auth)
else: user=v(request, auth, roles)
if user is None and roles != UNSPECIFIED_ROLES:
response.unauthorized()
steps=join(steps[:-i],'/')
if user is not None:
request['AUTHENTICATED_USER']=user
request['AUTHENTICATION_PATH']=steps
request['URL']=URL
return object
def old_validation(groups, request, auth,
roles=UNSPECIFIED_ROLES):
if auth:
auth=request._authUserPW()
if auth: name,password = auth
elif roles is None: return ''
else: return None
elif request.environ.has_key('REMOTE_USER'):
name=request.environ['REMOTE_USER']
password=None
else:
if roles is None: return ''
return None
if roles is None: return name
keys=None
try:
keys=groups.keys
except:
try:
groups=groups() # Maybe it was a method defining a group
keys=groups.keys
except: pass
if keys is not None:
# OK, we have a named group, so apply the roles to the named
# group.
if roles is UNSPECIFIED_ROLES: roles=keys()
g=[]
for role in roles:
if groups.has_key(role): g.append(groups[role])
groups=g
for d in groups:
if d.has_key(name) and (d[name]==password or password is None):
return name
if keys is None:
# Not a named group, so don't go further
raise 'Forbidden', (
"""<strong>You are not authorized to access this resource""")
return None
##############################################################################
#
# Zope Public License (ZPL) Version 0.9.4
# ---------------------------------------
#
# Copyright (c) Digital Creations. All rights reserved.
#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
# conditions are met:
#
# 1. Redistributions in source code must retain the above
# copyright notice, this list of conditions, and the following
# disclaimer.
#
# 2. Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions, and the following
# disclaimer in the documentation and/or other materials
# provided with the distribution.
#
# 3. Any use, including use of the Zope software to operate a
# website, must either comply with the terms described below
# under "Attribution" or alternatively secure a separate
# license from Digital Creations.
#
# 4. All advertising materials, documentation, or technical papers
# mentioning features derived from or use of this software must
# display the following acknowledgement:
#
# "This product includes software developed by Digital
# Creations for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# 5. Names associated with Zope or Digital Creations must not be
# used to endorse or promote products derived from this
# software without prior written permission from Digital
# Creations.
#
# 6. Redistributions of any form whatsoever must retain the
# following acknowledgment:
#
# "This product includes software developed by Digital
# Creations for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# 7. Modifications are encouraged but must be packaged separately
# as patches to official Zope releases. Distributions that do
# not clearly separate the patches from the original work must
# be clearly labeled as unofficial distributions.
#
# Disclaimer
#
# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND
# ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
# FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
# SHALL DIGITAL CREATIONS OR ITS CONTRIBUTORS BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
# THE POSSIBILITY OF SUCH DAMAGE.
#
# Attribution
#
# Individuals or organizations using this software as a web site
# must provide attribution by placing the accompanying "button"
# and a link to the accompanying "credits page" on the website's
# main entry point. In cases where this placement of
# attribution is not feasible, a separate arrangment must be
# concluded with Digital Creations. Those using the software
# for purposes other than web sites must provide a corresponding
# attribution in locations that include a copyright using a
# manner best suited to the application environment.
#
# This software consists of contributions made by Digital
# Creations and many individuals on behalf of Digital Creations.
# Specific attributions are listed in the accompanying credits
# file.
#
##############################################################################
'''CGI Response Output formatter
$Id: BaseResponse.py,v 1.1 1999/02/18 17:17:55 jim Exp $'''
__version__='$Revision: 1.1 $'[11:-2]
import string, types, sys, regex
from string import find, rfind, lower, upper, strip, split, join, translate
from types import StringType, InstanceType
class BaseResponse:
"""Base Response Class
What should be here?
"""
debug_mode=None
_auth=None
def __init__(self, stdout, stderr,
body='', headers=None, status=None, cookies=None):
self.stdout=stdout
self.stderr=stderr
self.body=body
if headers is None: headers={}
self.headers=headers
self.status=status
if cookies is None: cookies={}
self.cookies=cookies
def setStatus(self, status, reason=None):
self.status=status
def setHeader(self, name, value):
self.headers[n]=value
__setitem__=setHeader
def setBody(self, body):
self.body=body
def getStatus(self):
'Returns the current HTTP status code as an integer. '
return self.status
def setCookie(self,name,value,**kw):
'''\
Set an HTTP cookie on the browser
The response will include an HTTP header that sets a cookie on
cookie-enabled browsers with a key "name" and value
"value". This overwrites any previously set value for the
cookie in the Response object.
'''
cookies=self.cookies
if cookies.has_key(name):
cookie=cookies[name]
else: cookie=cookies[name]={}
for k, v in kw.items():
cookie[k]=v
cookie['value']=value
def appendBody(self, body):
self.setBody(self.getBody() + body)
def getHeader(self, name):
'''\
Get a header value
Returns the value associated with a HTTP return header, or
"None" if no such header has been set in the response
yet. '''
return self.headers.get(name, None)
def __getitem__(self, name):
'Get the value of an output header'
return self.headers[name]
def getBody(self):
'Returns a string representing the currently set body. '
return self.body
def __str__(self):
return str(self.body)
def __repr__(self):
return '%s(%s)' % (self.__class__.__name__, `self.body`)
def flush(self): pass
def write(self,data):
"""\
Return data as a stream
HTML data may be returned using a stream-oriented interface.
This allows the browser to display partial results while
computation of a response to proceed.
The published object should first set any output headers or
cookies on the response object.
Note that published objects must not generate any errors
after beginning stream-oriented output.
"""
self.body=self.body+data
##############################################################################
#
# Zope Public License (ZPL) Version 0.9.4
# ---------------------------------------
#
# Copyright (c) Digital Creations. All rights reserved.
#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
# conditions are met:
#
# 1. Redistributions in source code must retain the above
# copyright notice, this list of conditions, and the following
# disclaimer.
#
# 2. Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions, and the following
# disclaimer in the documentation and/or other materials
# provided with the distribution.
#
# 3. Any use, including use of the Zope software to operate a
# website, must either comply with the terms described below
# under "Attribution" or alternatively secure a separate
# license from Digital Creations.
#
# 4. All advertising materials, documentation, or technical papers
# mentioning features derived from or use of this software must
# display the following acknowledgement:
#
# "This product includes software developed by Digital
# Creations for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# 5. Names associated with Zope or Digital Creations must not be
# used to endorse or promote products derived from this
# software without prior written permission from Digital
# Creations.
#
# 6. Redistributions of any form whatsoever must retain the
# following acknowledgment:
#
# "This product includes software developed by Digital
# Creations for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# 7. Modifications are encouraged but must be packaged separately
# as patches to official Zope releases. Distributions that do
# not clearly separate the patches from the original work must
# be clearly labeled as unofficial distributions.
#
# Disclaimer
#
# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND
# ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
# FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
# SHALL DIGITAL CREATIONS OR ITS CONTRIBUTORS BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
# THE POSSIBILITY OF SUCH DAMAGE.
#
# Attribution
#
# Individuals or organizations using this software as a web site
# must provide attribution by placing the accompanying "button"
# and a link to the accompanying "credits page" on the website's
# main entry point. In cases where this placement of
# attribution is not feasible, a separate arrangment must be
# concluded with Digital Creations. Those using the software
# for purposes other than web sites must provide a corresponding
# attribution in locations that include a copyright using a
# manner best suited to the application environment.
#
# This software consists of contributions made by Digital
# Creations and many individuals on behalf of Digital Creations.
# Specific attributions are listed in the accompanying credits
# file.
#
##############################################################################
__version__='$Revision: 1.1 $'[11:-2]
import regex, sys, os
from string import lower, atoi, rfind, split, strip, join, upper, find
from BaseRequest import BaseRequest
from HTTPResponse import HTTPResponse
from cgi import FieldStorage
from urllib import quote, unquote
from Converters import type_converters
from maybe_lock import allocate_lock
isCGI_NAME = {
'SERVER_SOFTWARE' : 1,
'SERVER_NAME' : 1,
'GATEWAY_INTERFACE' : 1,
'SERVER_PROTOCOL' : 1,
'SERVER_PORT' : 1,
'REQUEST_METHOD' : 1,
'PATH_INFO' : 1,
'PATH_TRANSLATED' : 1,
'SCRIPT_NAME' : 1,
'QUERY_STRING' : 1,
'REMOTE_HOST' : 1,
'REMOTE_ADDR' : 1,
'AUTH_TYPE' : 1,
'REMOTE_USER' : 1,
'REMOTE_IDENT' : 1,
'CONTENT_TYPE' : 1,
'CONTENT_LENGTH' : 1,
'SERVER_URL': 1,
}.has_key
hide_key={'HTTP_AUTHORIZATION':1,
'HTTP_CGI_AUTHORIZATION': 1,
}.has_key
_marker=[]
class HTTPRequest(BaseRequest):
"""\
Model HTTP request data.
This object provides access to request data. This includes, the
input headers, form data, server data, and cookies.
Request objects are created by the object publisher and will be
passed to published objects through the argument name, REQUEST.
The request object is a mapping object that represents a
collection of variable to value mappings. In addition, variables
are divided into four categories:
- Environment variables
These variables include input headers, server data, and other
request-related data. The variable names are as <a
href="http://hoohoo.ncsa.uiuc.edu/cgi/env.html">specified</a>
in the <a
href="http://hoohoo.ncsa.uiuc.edu/cgi/interface.html">CGI
specification</a>
- Form data
These are data extracted from either a URL-encoded query
string or body, if present.
- Cookies
These are the cookie data, if present.
- Other
Data that may be set by an application object.
The form attribute of a request is actually a Field Storage
object. When file uploads are used, this provides a richer and
more complex interface than is provided by accessing form data as
items of the request. See the FieldStorage class documentation
for more details.
The request object may be used as a mapping object, in which case
values will be looked up in the order: environment variables,
other variables, form data, and then cookies.
"""
_hacked_path=None
def __init__(self, stdin, environ, response, clean=0):
# Avoid the overhead of scrubbing the environment in the
# case of request cloning for traversal purposes. If the
# clean flag is set, we know we can use the passed in
# environ dict directly.
if not clean: environ=sane_environment(environ)
if environ.get('REQUEST_METHOD','GET') != 'GET': fp=stdin
else: fp=None
if environ.has_key('HTTP_AUTHORIZATION'):
self._auth=environ['HTTP_AUTHORIZATION']
response._auth=1
del environ['HTTP_AUTHORIZATION']
form={}
form_has=form.has_key
meth=None
fs=FieldStorage(fp=fp,environ=environ,keep_blank_values=1)
if not hasattr(fs,'list') or fs.list is None:
form['BODY']=fs.value
else:
fslist=fs.list
tuple_items={}
type_re=regex.compile(':[a-zA-Z][a-zA-Z0-9_]+')
type_search=type_re.search
lt=type([])
CGI_name=isCGI_NAME
for item in fslist:
key=unquote(item.name)
if (hasattr(item,'file') and hasattr(item,'filename')
and hasattr(item,'headers')):
if (item.file and
(item.filename is not None or
'content-type' in map(lower,
item.headers.keys()))):
item=FileUpload(item)
else:
item=item.value
seqf=None
l=type_search(key)
while l >= 0:
type_name=type_re.group(0)[1:]
key=key[:l]+key[l+len(type_name)+1:]
if type_name == 'list':
seqf=list
elif type_name == 'tuple':
seqf=tuple
tuple_items[key]=1
elif type_name == 'method':
if l: meth=key
else: meth=item
elif type_name == 'default_method':
if not meth:
if l: meth=key
else: meth=item
else:
item=type_converters[type_name](item)
l=type_search(key)
# Filter out special names from form:
if CGI_name(key) or key[:5]=='HTTP_': continue
if form_has(key):
found=form[key]
if type(found) is lt: found.append(item)
else:
found=[found,item]
form[key]=found
else:
if seqf: item=[item]
form[key]=item
for key in tuple_items.keys():
item=tuple(form[key])
form[key]=item
other=self.other={}
other.update(form)
if meth:
if environ.has_key('PATH_INFO'):
path=environ['PATH_INFO']
while path[-1:]=='/': path=path[:-1]
else: path=''
other['PATH_INFO']=path="%s/%s" % (path,meth)
self._hacked_path=1
# Cookie values should *not* be appended to existing form
# vars with the same name - they are more like default values
# for names not otherwise specified in the form.
cookies={}
if environ.has_key('HTTP_COOKIE'):
parse_cookie(environ['HTTP_COOKIE'],cookies)
for k,item in cookies.items():
if not other.has_key(k):
other[k]=item
self.form=form
self.cookies=cookies
other['RESPONSE']=self.response=response
self.environ=environ
self.stdin=stdin
have_env=environ.has_key
b=script=strip(environ['SCRIPT_NAME'])
while b and b[-1]=='/': b=b[:-1]
p = rfind(b,'/')
if p >= 0: b=b[:p+1]
else: b=''
while b and b[0]=='/': b=b[1:]
if have_env('SERVER_URL'):
server_url=strip(environ['SERVER_URL'])
else:
if have_env('HTTPS') and (
environ['HTTPS'] == "on" or environ['HTTPS'] == "ON"):
server_url='https://'
elif (have_env('SERVER_PORT_SECURE') and
environ['SERVER_PORT_SECURE'] == "1"):
server_url='https://'
else: server_url='http://'
if have_env('HTTP_HOST'):
server_url=server_url+strip(environ['HTTP_HOST'])
else:
server_url=server_url+strip(environ['SERVER_NAME'])
server_port=environ['SERVER_PORT']
if server_port!='80': server_url=server_url+':'+server_port
other['SERVER_URL']=server_url
if server_url[-1:]=='/': server_url=server_url[:-1]
self.base="%s/%s" % (server_url,b)
while script[:1]=='/': script=script[1:]
if script: script="%s/%s" % (server_url,script)
else: script=server_url
other['URL']=self.script=script
def resolve_url(self, url):
# Attempt to resolve a url into an object in the Zope
# namespace. The url must be a fully-qualified url. The
# method will return the requested object if it is found
# or raise the same HTTP error that would be raised in
# the case of a real web request. If the passed in url
# does not appear to describe an object in the system
# namespace (e.g. the host, port or script name dont
# match that of the current request), a ValueError will
# be raised.
if find(url, self.script) != 0:
raise ValueError, 'Different namespace.'
path=url[len(self.script):]
while path and path[0]=='/': path=path[1:]
while path and path[-1]=='/': path=path[:-1]
req=self.clone()
rsp=req.response
req['PATH_INFO']=path
object=None
try: object=req.traverse(path)
except: rsp.exception(0)
if object is not None:
return object
raise rsp.errmsg, sys.exc_value
def clone(self):
# Return a clone of the current request object
# that may be used to perform object traversal.
environ=self.environ.copy()
environ['REQUEST_METHOD']='GET'
if self._auth: environ['HTTP_AUTHORIZATION']=self._auth
clone=HTTPRequest(None, environ, HTTPResponse(), clean=1)
clone['PARENTS']=[self['PARENTS'][-1]]
clone._auth=self._auth
return clone
def get_header(self, name, default=None):
"""Return the named HTTP header, or an optional default
argument or None if the header is not found. Note that
both original and CGI-ified header names are recognized,
e.g. 'Content-Type', 'CONTENT_TYPE' and 'HTTP_CONTENT_TYPE'
should all return the Content-Type header, if available.
"""
environ=self.environ
name=upper(join(split(name,"-"),"_"))
val=environ.get(name, None)
if val is not None:
return val
if name[:5] != 'HTTP_':
name='HTTP_%s' % name
return environ.get(name, default)
def __getitem__(self,key,
default=_marker, # Any special internal marker will do
URLmatch=regex.compile('URL[0-9]$').match,
BASEmatch=regex.compile('BASE[0-9]$').match,
):
"""Get a variable value
Return a value for the required variable name.
The value will be looked up from one of the request data
categories. The search order is environment variables,
other variables, form data, and then cookies.
""" #"
other=self.other
if other.has_key(key):
if key=='REQUEST': return self
return other[key]
if key[:1]=='U' and URLmatch(key) >= 0:
n=ord(key[3])-ord('0')
URL=other['URL']
for i in range(0,n):
l=rfind(URL,'/')
if l >= 0: URL=URL[:l]
else: raise KeyError, key
other[key]=URL
return URL
if isCGI_NAME(key) or key[:5] == 'HTTP_':
environ=self.environ
if environ.has_key(key) and (not hide_key(key)):
return environ[key]
return ''
if key=='REQUEST': return self
if key[:1]=='B' and BASEmatch(key) >= 0:
n=ord(key[4])-ord('0')
if n:
v=self.script
while v[-1:]=='/': v=v[:-1]
v=join([v]+self.steps[:n-1],'/')
else:
v=self.base
while v[-1:]=='/': v=v[:-1]
other[key]=v
return v
v=self.common.get(key, default)
if v is not _marker: return v
raise KeyError, key
__getattr__=get=__getitem__
def keys(self):
keys = {}
keys.update(self.common)
for key in self.environ.keys():
if (isCGI_NAME(key) or key[:5] == 'HTTP_') and \
(not hide_key(key)):
keys[key] = 1
keys.update(self.other)
lasturl = ""
for n in "0123456789":
key = "URL%s"%n
try:
if lasturl != self[key]:
keys[key] = 1
lasturl = self[key]
else:
break
except KeyError:
pass
for n in "0123456789":
key = "BASE%s"%n
try:
if lasturl != self[key]:
keys[key] = 1
lasturl = self[key]
else:
break
except KeyError:
pass
return keys.keys()
def _authUserPW(self):
global base64
auth=self._auth
if auth:
if lower(auth[:6]) == 'basic ':
if base64 is None: import base64
[name,password] = split(
base64.decodestring(split(auth)[-1]), ':')
return name, password
base64=None
def sane_environment(env):
# return an environment mapping which has been cleaned of
# funny business such as REDIRECT_ prefixes added by Apache
# or HTTP_CGI_AUTHORIZATION hacks.
dict={}
for key, val in env.items():
while key[:9]=='REDIRECT_':
key=key[9:]
dict[key]=val
if dict.has_key('HTTP_CGI_AUTHORIZATION'):
dict['HTTP_AUTHORIZATION']=dict['HTTP_CGI_AUTHORIZATION']
try: del dict['HTTP_CGI_AUTHORIZATION']
except: pass
return dict
def str_field(v):
if type(v) is ListType:
return map(str_field,v)
if hasattr(v,'__class__') and v.__class__ is FieldStorage:
v=v.value
elif type(v) is not StringType:
if hasattr(v,'file') and v.file: v=v.file
elif hasattr(v,'value'): v=v.value
return v
class FileUpload:
'''\
File upload objects
File upload objects are used to represent file-uploaded data.
File upload objects can be used just like files.
In addition, they have a 'headers' attribute that is a dictionary
containing the file-upload headers, and a 'filename' attribute
containing the name of the uploaded file.
'''
def __init__(self, aFieldStorage):
file=aFieldStorage.file
if hasattr(file, '__methods__'): methods=file.__methods__
else: methods= ['close', 'fileno', 'flush', 'isatty',
'read', 'readline', 'readlines', 'seek',
'tell', 'truncate', 'write', 'writelines']
d=self.__dict__
for m in methods:
if hasattr(file,m): d[m]=getattr(file,m)
self.headers=aFieldStorage.headers
self.filename=aFieldStorage.filename
parse_cookie_lock=allocate_lock()
def parse_cookie(text,
result=None,
qparmre=regex.compile(
'\([\0- ]*'
'\([^\0- ;,=\"]+\)="\([^"]*\)\"'
'\([\0- ]*[;,]\)?[\0- ]*\)'
),
parmre=regex.compile(
'\([\0- ]*'
'\([^\0- ;,=\"]+\)=\([^\0- ;,\"]*\)'
'\([\0- ]*[;,]\)?[\0- ]*\)'
),
acquire=parse_cookie_lock.acquire,
release=parse_cookie_lock.release,
):
if result is None: result={}
already_have=result.has_key
acquire()
try:
if qparmre.match(text) >= 0:
# Match quoted correct cookies
name=qparmre.group(2)
value=qparmre.group(3)
l=len(qparmre.group(1))
elif parmre.match(text) >= 0:
# Match evil MSIE cookies ;)
name=parmre.group(2)
value=parmre.group(3)
l=len(parmre.group(1))
else:
# this may be an invalid cookie.
# We'll simply bail without raising an error
# if the cookie is invalid.
return result
finally: release()
if not already_have(name): result[name]=value
return apply(parse_cookie,(text[l:],result))
##############################################################################
#
# Zope Public License (ZPL) Version 0.9.4
# ---------------------------------------
#
# Copyright (c) Digital Creations. All rights reserved.
#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
# conditions are met:
#
# 1. Redistributions in source code must retain the above
# copyright notice, this list of conditions, and the following
# disclaimer.
#
# 2. Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions, and the following
# disclaimer in the documentation and/or other materials
# provided with the distribution.
#
# 3. Any use, including use of the Zope software to operate a
# website, must either comply with the terms described below
# under "Attribution" or alternatively secure a separate
# license from Digital Creations.
#
# 4. All advertising materials, documentation, or technical papers
# mentioning features derived from or use of this software must
# display the following acknowledgement:
#
# "This product includes software developed by Digital
# Creations for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# 5. Names associated with Zope or Digital Creations must not be
# used to endorse or promote products derived from this
# software without prior written permission from Digital
# Creations.
#
# 6. Redistributions of any form whatsoever must retain the
# following acknowledgment:
#
# "This product includes software developed by Digital
# Creations for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# 7. Modifications are encouraged but must be packaged separately
# as patches to official Zope releases. Distributions that do
# not clearly separate the patches from the original work must
# be clearly labeled as unofficial distributions.
#
# Disclaimer
#
# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND
# ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
# FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
# SHALL DIGITAL CREATIONS OR ITS CONTRIBUTORS BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
# THE POSSIBILITY OF SUCH DAMAGE.
#
# Attribution
#
# Individuals or organizations using this software as a web site
# must provide attribution by placing the accompanying "button"
# and a link to the accompanying "credits page" on the website's
# main entry point. In cases where this placement of
# attribution is not feasible, a separate arrangment must be
# concluded with Digital Creations. Those using the software
# for purposes other than web sites must provide a corresponding
# attribution in locations that include a copyright using a
# manner best suited to the application environment.
#
# This software consists of contributions made by Digital
# Creations and many individuals on behalf of Digital Creations.
# Specific attributions are listed in the accompanying credits
# file.
#
##############################################################################
'''CGI Response Output formatter
$Id: HTTPResponse.py,v 1.1 1999/02/18 17:17:56 jim Exp $'''
__version__='$Revision: 1.1 $'[11:-2]
import string, types, sys, regex
from string import find, rfind, lower, upper, strip, split, join, translate
from types import StringType, InstanceType
from BaseResponse import BaseResponse
nl2sp=string.maketrans('\n',' ')
status_reasons={
100: 'Continue',
101: 'Switching Protocols',
102: 'Processing',
200: 'OK',
201: 'Created',
202: 'Accepted',
203: 'Non-Authoritative Information',
204: 'No Content',
205: 'Reset Content',
206: 'Partial Content',
207: 'Multi-Status',
300: 'Multiple Choices',
301: 'Moved Permanently',
302: 'Moved Temporarily',
303: 'See Other',
304: 'Not Modified',
305: 'Use Proxy',
307: 'Temporary Redirect',
400: 'Bad Request',
401: 'Unauthorized',
402: 'Payment Required',
403: 'Forbidden',
404: 'Not Found',
405: 'Method Not Allowed',
406: 'Not Acceptable',
407: 'Proxy Authentication Required',
408: 'Request Time-out',
409: 'Conflict',
410: 'Gone',
411: 'Length Required',
412: 'Precondition Failed',
413: 'Request Entity Too Large',
414: 'Request-URI Too Large',
415: 'Unsupported Media Type',
416: 'Requested range not satisfiable',
417: 'Expectation Failed',
422: 'Unprocessable Entity',
423: 'Locked',
424: 'Failed Dependency',
500: 'Internal Server Error',
501: 'Not Implemented',
502: 'Bad Gateway',
503: 'Service Unavailable',
504: 'Gateway Time-out',
505: 'HTTP Version not supported',
507: 'Insufficient Storage',
}
status_codes={}
# Add mappings for builtin exceptions and
# provide text -> error code lookups.
for key, val in status_reasons.items():
status_codes[lower(join(split(val, ' '), ''))]=key
status_codes[lower(val)]=key
status_codes[key]=key
en=filter(lambda n: n[-5:]=='Error', dir(__builtins__))
for name in map(lower, en):
status_codes[name]=500
status_codes['nameerror']=503
status_codes['keyerror']=503
status_codes['redirect']=300
end_of_header_search=regex.compile('</head>',regex.casefold).search
accumulate_header={'set-cookie': 1}.has_key
class HTTPResponse(BaseResponse):
"""\
An object representation of an HTTP response.
The Response type encapsulates all possible responses to HTTP
requests. Responses are normally created by the object publisher.
A published object may recieve the response abject as an argument
named 'RESPONSE'. A published object may also create it's own
response object. Normally, published objects use response objects
to:
- Provide specific control over output headers,
- Set cookies, or
- Provide stream-oriented output.
If stream oriented output is used, then the response object
passed into the object must be used.
""" #'
accumulated_headers=''
body=''
realm='Zope'
def __init__(self,body='',status=200,headers=None,
stdout=sys.stdout, stderr=sys.stderr,):
'''\
Creates a new response. In effect, the constructor calls
"self.setBody(body); self.setStatus(status); for name in
headers.keys(): self.setHeader(name, headers[name])"
'''
if headers is None: headers={}
self.headers=headers
if status==200:
self.status=200
self.errmsg='OK'
headers['status']="200 OK"
else: self.setStatus(status)
self.base=''
if body: self.setBody(body)
self.cookies={}
self.stdout=stdout
self.stderr=stderr
def setStatus(self, status, reason=None):
'''\
Sets the HTTP status code of the response; the argument may
either be an integer or a string from { OK, Created, Accepted,
NoContent, MovedPermanently, MovedTemporarily,
NotModified, BadRequest, Unauthorized, Forbidden,
NotFound, InternalError, NotImplemented, BadGateway,
ServiceUnavailable } that will be converted to the correct
integer value. '''
if type(status) is types.StringType:
status=lower(status)
if status_codes.has_key(status): status=status_codes[status]
else: status=500
self.status=status
if reason is None:
if status_reasons.has_key(status): reason=status_reasons[status]
else: reason='Unknown'
self.setHeader('Status', "%d %s" % (status,str(reason)))
self.errmsg=reason
def setHeader(self, name, value):
'''\
Sets an HTTP return header "name" with value "value", clearing
the previous value set for the header, if one exists. '''
n=lower(name)
if accumulate_header(n):
self.accumulated_headers=(
"%s%s: %s\n" % (self.accumulated_headers, name, value))
else:
self.headers[n]=value
__setitem__=setHeader
def setBody(self, body, title='',
bogus_str_search=regex.compile(" [a-fA-F0-9]+>$").search,
):
'''\
Set the body of the response
Sets the return body equal to the (string) argument "body". Also
updates the "content-length" return header.
You can also specify a title, in which case the title and body
will be wrapped up in html, head, title, and body tags.
If the body is a 2-element tuple, then it will be treated
as (title,body)
'''
if type(body) is types.TupleType:
title,body=body
if type(body) is not types.StringType:
if hasattr(body,'asHTML'):
body=body.asHTML()
body=str(body)
l=len(body)
if (find(body,'>')==l-1 and body[:1]=='<' and l < 200 and
bogus_str_search(body) > 0):
if self.debug_mode: _tbopen, _tbclose = '<PRE>', '</PRE>'
else: _tbopen, _tbclose = '<!--', '-->'
raise 'NotFound', (
"Sorry, the requested document does not exist.<p>"
"\n%s\n%s\n%s" % (_tbopen, body[1:-1], _tbclose))
if(title):
self.body=('<html>\n<head>\n<title>%s</title>\n</head>\n'
'<body>\n%s\n</body>\n</html>'
% (str(title),str(body)))
else:
self.body=str(body)
self.insertBase()
return self
def setBase(self,base):
'Set the base URL for the returned document.'
if base[-1:] != '/': base=base+'/'
self.base=base
self.insertBase()
def insertBase(self,
base_re_search=regex.compile('\(<base[\0- ]+[^>]+>\)',
regex.casefold).search
):
if (self.headers.has_key('content-type') and
self.headers['content-type']!='text/html'): return
if self.base:
body=self.body
if body:
e=end_of_header_search(body)
if e >= 0:
b=base_re_search(body)
if b < 0:
self.body=('%s\t<base href="%s">\n%s' %
(body[:e],self.base,body[e:]))
def appendCookie(self, name, value):
'''\
Returns an HTTP header that sets a cookie on cookie-enabled
browsers with a key "name" and value "value". If a value for the
cookie has previously been set in the response object, the new
value is appended to the old one separated by a colon. '''
cookies=self.cookies
if cookies.has_key(name): cookie=cookies[name]
else: cookie=cookies[name]={}
if cookie.has_key('value'):
cookie['value']='%s:%s' % (cookie['value'], value)
else: cookie['value']=value
def expireCookie(self, name, **kw):
'''\
Cause an HTTP cookie to be removed from the browser
The response will include an HTTP header that will remove the cookie
corresponding to "name" on the client, if one exists. This is
accomplished by sending a new cookie with an expiration date
that has already passed. Note that some clients require a path
to be specified - this path must exactly match the path given
when creating the cookie. The path can be specified as a keyword
argument.
'''
dict={'max_age':0, 'expires':'Wed, 31-Dec-97 23:59:59 GMT'}
for k, v in kw.items():
dict[k]=v
apply(Response.setCookie, (self, name, 'deleted'), dict)
def setCookie(self,name,value,**kw):
'''\
Set an HTTP cookie on the browser
The response will include an HTTP header that sets a cookie on
cookie-enabled browsers with a key "name" and value
"value". This overwrites any previously set value for the
cookie in the Response object.
'''
cookies=self.cookies
if cookies.has_key(name):
cookie=cookies[name]
else: cookie=cookies[name]={}
for k, v in kw.items():
cookie[k]=v
cookie['value']=value
def appendHeader(self, name, value, delimiter=","):
'''\
Append a value to a cookie
Sets an HTTP return header "name" with value "value",
appending it following a comma if there was a previous value
set for the header. '''
headers=self.headers
if headers.has_key(name):
h=self.header[name]
h="%s%s\n\t%s" % (h,delimiter,value)
else: h=value
self.setHeader(name,h)
def isHTML(self,str):
return lower(strip(str)[:6]) == '<html>' or find(str,'</') > 0
def quoteHTML(self,text,
subs={'&':'&amp;', "<":'&lt;', ">":'&gt;', '\"':'&quot;'}
):
for ent in '&<>\"':
if find(text, ent) >= 0:
text=join(split(text,ent),subs[ent])
return text
def format_exception(self,etype,value,tb,limit=None):
import traceback
result=['Traceback (innermost last):']
if limit is None:
if hasattr(sys, 'tracebacklimit'):
limit = sys.tracebacklimit
n = 0
while tb is not None and (limit is None or n < limit):
f = tb.tb_frame
lineno = tb.tb_lineno
co = f.f_code
filename = co.co_filename
name = co.co_name
locals=f.f_locals
result.append(' File %s, line %d, in %s'
% (filename,lineno,name))
try: result.append(' (Object: %s)' %
locals[co.co_varnames[0]].__name__)
except: pass
try: result.append(' (Info: %s)' %
str(locals['__traceback_info__']))
except: pass
tb = tb.tb_next
n = n+1
result.append(join(traceback.format_exception_only(etype, value),
' '))
return result
def _traceback(self,t,v,tb):
tb=self.format_exception(t,v,tb,200)
tb=join(tb,'\n')
tb=self.quoteHTML(tb)
if self.debug_mode: _tbopen, _tbclose = '<PRE>', '</PRE>'
else: _tbopen, _tbclose = '<!--', '-->'
return "\n%s\n%s\n%s" % (_tbopen, tb, _tbclose)
def redirect(self, location):
"""Cause a redirection without raising an error"""
self.status=302
headers=self.headers
headers['status']='302 Moved Temporarily'
headers['location']=location
return location
def _html(self,title,body):
return ("<html>\n"
"<head>\n<title>%s</title>\n</head>\n"
"<body>\n%s\n</body>\n"
"</html>\n" % (title,body))
def notFoundError(self,entry='who knows!'):
raise 'NotFound',self._html(
"Resource not found",
"Sorry, the requested document does not exist.<p>"
"\n<!--\n%s\n-->" % entry)
forbiddenError=notFoundError # If a resource is forbidden,
# why reveal that it exists?
def debugError(self,entry):
raise 'NotFound',self._html(
"Debugging Notice",
"Bobo has encountered a problem publishing your object.<p>"
"\n%s" % entry)
def badRequestError(self,name):
if regex.match('^[A-Z_0-9]+$',name) >= 0:
raise 'InternalError', self._html(
"Internal Error",
"Sorry, an internal error occurred in this resource.")
raise 'BadRequest',self.html(
"Invalid request",
"The parameter, <em>%s</em>, was omitted from the request."
"<!--%s-->"
% (name,self.request))
def _unauthorized(self):
realm=self.realm
if realm: self['WWW-authenticate']='basic realm="%s"' % realm
def unauthorized(self):
self._unauthorized()
m="<strong>You are not authorized to access this resource.</strong>"
if self.debug_mode:
if self._auth:
m=m+'\nUsername and password are not correct.'
else:
m=m+'\nNo Authorization header found.'
raise 'Unauthorized', m
def forbiddenError(self,object=None):
raise 'NotFound',self._html(
"Resource not found",
"Sorry, the requested document does not exist.<p>"
"<!--%s-->" % object)
def exception(self, fatal=0, info=None,
absuri_match=regex.compile(
"^"
"\(/\|\([a-zA-Z0-9+.-]+:\)\)"
"[^\000- \"\\#<>]*"
"\\(#[^\000- \"\\#<>]*\\)?"
"$"
).match,
tag_search=regex.compile('[a-zA-Z]>').search,
):
if type(info) is type(()) and len(info)==3: t,v,tb = info
else: t,v,tb = sys.exc_info()
if str(t)=='Unauthorized': self._unauthorized()
stb=tb
# Abort running transaction, if any:
try: get_transaction().abort()
except: pass
try:
# Try to capture exception info for bci calls
et=translate(str(t),nl2sp)
ev=translate(str(v),nl2sp)
# Get the tb tail, which is the interesting part:
while tb.tb_next is not None: tb=tb.tb_next
el=str(tb.tb_lineno)
ef=str(tb.tb_frame.f_code.co_filename)
if find(ev,'<html>') >= 0: ev='bobo exception'
self.setHeader('bobo-exception-type',et)
self.setHeader('bobo-exception-value',ev[:255])
self.setHeader('bobo-exception-file',ef)
self.setHeader('bobo-exception-line',el)
except:
# Dont try so hard that we cause other problems ;)
pass
tb=stb
stb=None
self.setStatus(t)
if self.status >= 300 and self.status < 400:
if type(v) == types.StringType and absuri_match(v) >= 0:
if self.status==300: self.setStatus(302)
self.setHeader('location', v)
tb=None
return self
else:
try:
l,b=v
if type(l) == types.StringType and absuri_match(l) >= 0:
if self.status==300: self.setStatus(302)
self.setHeader('location', l)
self.setBody(b)
tb=None
return self
except: pass
b=v
if isinstance(b,Exception): b=str(b)
if fatal:
if t is SystemExit and v.code==0:
tb=self.setBody(
(str(t),
'This application has exited normally.<p>'
+ self._traceback(t,v,tb)))
else:
tb=self.setBody(
(str(t),
'Sorry, a SERIOUS APPLICATION ERROR occurred.<p>'
+ self._traceback(t,v,tb)))
elif type(b) is not types.StringType or tag_search(b) < 0:
tb=self.setBody(
(str(t),
'Sorry, an error occurred.<p>'
+ self._traceback(t,v,tb)))
elif self.isHTML(b):
tb=self.setBody(b+self._traceback(t,'(see above)',tb))
else:
tb=self.setBody((str(t),b+self._traceback(t,'(see above)',tb)))
return tb
_wrote=None
def _cookie_list(self):
cookie_list=[]
for name, attrs in self.cookies.items():
# Note that as of May 98, IE4 ignores cookies with
# quoted cookie attr values, so only the value part
# of name=value pairs may be quoted.
cookie='Set-Cookie: %s="%s"' % (name, attrs['value'])
for name, v in attrs.items():
name=lower(name)
if name=='expires': cookie = '%s; Expires=%s' % (cookie,v)
elif name=='domain': cookie = '%s; Domain=%s' % (cookie,v)
elif name=='path': cookie = '%s; Path=%s' % (cookie,v)
elif name=='max_age': cookie = '%s; Max-Age=%s' % (cookie,v)
elif name=='comment': cookie = '%s; Comment=%s' % (cookie,v)
elif name=='secure': cookie = '%s; Secure' % cookie
cookie_list.append(cookie)
# Should really check size of cookies here!
return cookie_list
def __str__(self,
html_search=regex.compile('<html>',regex.casefold).search,
):
if self._wrote: return '' # Streaming output was used.
headers=self.headers
body=self.body
if body:
isHTML=self.isHTML(body)
if not headers.has_key('content-type'):
if isHTML:
c='text/html'
else:
c='text/plain'
self.setHeader('content-type',c)
else:
isHTML = headers['content-type']=='text/html'
if isHTML and end_of_header_search(self.body) < 0:
lhtml=html_search(body)
if lhtml >= 0:
lhtml=lhtml+6
body='%s<head></head>\n%s' % (body[:lhtml],body[lhtml:])
else:
body='<html><head></head>\n' + body
self.setBody(body)
body=self.body
if not headers.has_key('content-type') and self.status == 200:
self.setStatus('nocontent')
if not headers.has_key('content-length'):
self.setHeader('content-length',len(body))
headersl=[]
append=headersl.append
# Make sure status comes out first!
try:
v=headers['status']
del headers['status']
except: v="200 OK"
append("Status: "+v)
for k,v in headers.items():
k=upper(k[:1])+k[1:]
start=0
l=find(k,'-',start)
while l >= start:
k="%s-%s%s" % (k[:l],upper(k[l+1:l+2]),k[l+2:])
start=l+1
l=find(k,'-',start)
append("%s: %s" % (k,v))
if self.cookies:
headersl=headersl+self._cookie_list()
headersl[len(headersl):]=[self.accumulated_headers,body]
return join(headersl,'\n')
def write(self,data):
"""\
Return data as a stream
HTML data may be returned using a stream-oriented interface.
This allows the browser to display partial results while
computation of a response to proceed.
The published object should first set any output headers or
cookies on the response object.
Note that published objects must not generate any errors
after beginning stream-oriented output.
"""
self.body=self.body+data
headers=self.headers
if headers.has_key('content-length'):
del headers['content-length']
if not self.headers.has_key('content-type'):
self.setHeader('content-type', 'text/html')
self.insertBase()
body=self.body
self.body=''
self.write=write=self.stdout.write
try: self.flush=self.stdout.flush
except: pass
write(str(self))
self._wrote=1
write(body)
......@@ -84,237 +84,19 @@
##############################################################################
__doc__="""Python Object Publisher -- Publish Python objects on web servers
$Id: Publish.py,v 1.122 1999/01/26 14:42:31 brian Exp $"""
__version__='$Revision: 1.122 $'[11:-2]
$Id: Publish.py,v 1.123 1999/02/18 17:17:56 jim Exp $"""
__version__='$Revision: 1.123 $'[11:-2]
import sys, os, string, cgi, regex
from string import lower, atoi, rfind, split, strip, join, upper, find
import sys, os
from string import lower, atoi, rfind, strip
from Response import Response
from urllib import quote, unquote
from cgi import FieldStorage
from Request import Request, isCGI_NAME
from Converters import type_converters
# Waaaa, I wish I didn't have to work this hard.
try: from thread import allocate_lock
except:
class allocate_lock:
def acquire(*args): pass
def release(*args): pass
ListType=type([])
StringType=type('')
UNSPECIFIED_ROLES=''
try:
from ExtensionClass import Base
class RequestContainer(Base):
def __init__(self,**kw):
for k,v in kw.items(): self.__dict__[k]=v
def manage_property_types(self):
return type_converters.keys()
except:
class RequestContainer:
def __init__(self,**kw):
for k,v in kw.items(): self.__dict__[k]=v
def sane_environment(env):
# return an environment mapping which has been cleaned of
# funny business such as REDIRECT_ prefixes added by Apache
# or HTTP_CGI_AUTHORIZATION hacks.
dict={}
for key, val in env.items():
while key[:9]=='REDIRECT_':
key=key[9:]
dict[key]=val
if dict.has_key('HTTP_CGI_AUTHORIZATION'):
dict['HTTP_AUTHORIZATION']=dict['HTTP_CGI_AUTHORIZATION']
try: del dict['HTTP_CGI_AUTHORIZATION']
except: pass
return dict
class ModulePublisher:
HTTP_AUTHORIZATION=None
_hacked_path=None
def __init__(self,
stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr,
environ=os.environ):
environ=sane_environment(environ)
fp=None
try:
if environ['REQUEST_METHOD'] != 'GET': fp=stdin
except: pass
if environ.has_key('HTTP_AUTHORIZATION'):
self.HTTP_AUTHORIZATION=environ['HTTP_AUTHORIZATION']
try: del environ['HTTP_AUTHORIZATION']
except: pass
form={}
form_has=form.has_key
other={}
meth=None
fs=FieldStorage(fp=fp,environ=environ,keep_blank_values=1)
if not hasattr(fs,'list') or fs.list is None:
form['BODY']=other['BODY']=fs.value
else:
fslist=fs.list
tuple_items={}
type_re=regex.compile(':[a-zA-Z][a-zA-Z0-9_]+')
type_search=type_re.search
lt=type([])
CGI_name=isCGI_NAME
for item in fslist:
key=unquote(item.name)
if (hasattr(item,'file') and hasattr(item,'filename')
and hasattr(item,'headers')):
if (item.file and
(item.filename is not None or
'content-type' in map(lower,
item.headers.keys()))):
item=FileUpload(item)
else:
item=item.value
seqf=None
l=type_search(key)
while l >= 0:
type_name=type_re.group(0)[1:]
key=key[:l]+key[l+len(type_name)+1:]
if type_name == 'list':
seqf=list
elif type_name == 'tuple':
seqf=tuple
tuple_items[key]=1
elif type_name == 'method':
if l: meth=key
else: meth=item
elif type_name == 'default_method':
if not meth:
if l: meth=key
else: meth=item
else:
item=type_converters[type_name](item)
l=type_search(key)
# Filter out special names from form:
if CGI_name(key) or key[:5]=='HTTP_': continue
if form_has(key):
found=form[key]
if type(found) is lt: found.append(item)
else:
found=[found,item]
form[key]=found
other[key]=found
else:
if seqf: item=[item]
form[key]=item
other[key]=item
for key in tuple_items.keys():
item=tuple(form[key])
form[key]=item
other[key]=item
if meth:
if environ.has_key('PATH_INFO'):
path=environ['PATH_INFO']
while path[-1:]=='/': path=path[:-1]
else: path=''
other['PATH_INFO']=path="%s/%s" % (path,meth)
self._hacked_path=1
# Cookie values should *not* be appended to existing form
# vars with the same name - they are more like default values
# for names not otherwise specified in the form.
cookies={}
if environ.has_key('HTTP_COOKIE'):
parse_cookie(environ['HTTP_COOKIE'],cookies)
for k,item in cookies.items():
if not other.has_key(k):
other[k]=item
request=self.request=Request(environ,other,stdin)
request.form=form
if cookies is not None: request.cookies=cookies
self.response=response=Response(stdout=stdout, stderr=stderr)
request['RESPONSE']=response
def html(self,title,body):
return ("<html>\n"
"<head>\n<title>%s</title>\n</head>\n"
"<body>\n%s\n</body>\n"
"</html>\n" % (title,body))
def notFoundError(self,entry='who knows!'):
raise 'NotFound',self.html(
"Resource not found",
"Sorry, the requested document does not exist.<p>"
"\n<!--\n%s\n-->" % entry)
forbiddenError=notFoundError # If a resource is forbidden,
# why reveal that it exists?
def debugError(self,entry):
raise 'NotFound',self.html(
"Debugging Notice",
"Bobo has encountered a problem publishing your object.<p>"
"\n%s" % entry)
def badRequestError(self,name):
if regex.match('^[A-Z_0-9]+$',name) >= 0:
raise 'InternalError', self.html(
"Internal Error",
"Sorry, an internal error occurred in this resource.")
raise 'BadRequest',self.html(
"Invalid request",
"The parameter, <em>%s</em>, was omitted from the request."
"<!--%s-->"
% (name,self.request))
def unauthorized(self, realm,debug_mode=None):
if not (self.request.has_key('REMOTE_USER') and
self.request['REMOTE_USER']):
self.response['WWW-authenticate']='basic realm="%s"' % realm
m="<strong>You are not authorized to access this resource.</strong>"
if debug_mode:
if self.HTTP_AUTHORIZATION:
m=m+'\nUsername and password are not correct.'
else:
m=m+'\nNo Authorization header found.'
raise 'Unauthorized', m
def forbiddenError(self,object=None):
raise 'NotFound',self.html(
"Resource not found",
"Sorry, the requested document does not exist.<p>"
"<!--%s-->" % object)
def get_request_data(self,request_params):
try: request_params=request_params()
except: pass
for key in request_params.keys():
self.request[key]=request_params[key]
from Request import Request
from maybe_lock import allocate_lock
def publish(self, module_name, after_list, published='web_objects',
imported_modules={}, module_dicts={},debug=0):
def publish(request, module_name, after_list, debug=0):
request=self.request
request_get=request.get
response=self.response
response=request.response
# First check for "cancel" redirect:
cancel=''
......@@ -322,232 +104,39 @@ class ModulePublisher:
cancel=request_get('CANCEL_ACTION','')
if cancel: raise 'Redirect', cancel
(bobo_before, bobo_after, request_params,
inherited_groups, groups, roles,
object, doc, published, realm, module_name,
debug_mode)= get_module_info(module_name)
bobo_before, bobo_after, object, realm, debug_mode = get_module_info(
module_name)
after_list[0]=bobo_after
if debug_mode: response.debug_mode=debug_mode
if realm and not request.get('REMOTE_USER',None):
response.realm=realm
if bobo_before is not None: bobo_before();
if request_params: self.get_request_data(request_params)
# Get a nice clean path list:
path=strip(request_get('PATH_INFO'))
__traceback_info__=path
if path[:1] != '/': path='/'+path
if path[-1:] != '/': path=path+'/'
if find(path,'/.') >= 0:
path=join(split(path,'/./'),'/')
l=find(path,'/../',1)
while l > 0:
p1=path[:l]
path=path[:rfind(p1,'/')+1]+path[l+4:]
l=find(path,'/../',1)
path=path[1:-1]
path=split(path,'/')
while path and not path[0]: path = path[1:]
method=upper(request_get('REQUEST_METHOD'))
if method=='GET' or method=='POST': method='index_html'
URL=request.script
# if the top object has a __bobo_traverse__ method, then use it
# to possibly traverse to an alternate top-level object.
if hasattr(object,'__bobo_traverse__'):
request['URL']=URL
try: object=object.__bobo_traverse__(request)
except: pass
# Get default object if no path was specified:
if not path:
entry_name=method
try:
if hasattr(object,entry_name):
response.setBase(URL)
path=[entry_name]
else:
try:
if object.has_key(entry_name):
path=[entry_name]
except: pass
except: pass
if not path: path = ['help']
# Traverse the URL to find the object:
request['PARENTS']=parents=[]
if hasattr(object, '__of__'):
# Try to bind the top-level object to the request
object=object.__of__(RequestContainer(REQUEST=request))
steps=[]
while path:
entry_name,path=path[0], path[1:]
URL="%s/%s" % (URL,quote(entry_name))
got=0
if entry_name:
if entry_name[:1]=='_':
if debug_mode:
self.debugError("Object name begins with an underscore at: %s" % URL)
else: self.forbiddenError(entry_name)
if hasattr(object,'__bobo_traverse__'):
request['URL']=URL
subobject=object.__bobo_traverse__(request,entry_name)
if type(subobject) is type(()) and len(subobject) > 1:
while len(subobject) > 2:
parents.append(subobject[0])
subobject=subobject[1:]
object, subobject = subobject
else:
try:
subobject=getattr(object,entry_name)
except AttributeError:
got=1
try: subobject=object[entry_name]
except (KeyError, IndexError,
TypeError, AttributeError):
if entry_name=='.': subobject=object
elif entry_name=='..' and parents:
subobject=parents[-1]
elif debug_mode:
self.debugError(
"Cannot locate object at: %s" %URL)
else: self.notFoundError(URL)
if subobject is object and entry_name=='.':
URL=URL[:rfind(URL,'/')]
else:
try:
try: doc=subobject.__doc__
except: doc=getattr(object, entry_name+'__doc__')
if not doc: raise AttributeError, entry_name
except:
if debug_mode:
self.debugError("Missing doc string at: %s" % URL)
else: self.notFoundError("%s" % (URL))
if hasattr(subobject,'__roles__'):
roles=subobject.__roles__
else:
if not got:
roleshack=entry_name+'__roles__'
if hasattr(object, roleshack):
roles=getattr(object, roleshack)
# Promote subobject to object
parents.append(object)
object=subobject
steps.append(entry_name)
# Check for method:
if not path:
if hasattr(object,method) and entry_name != method:
response.setBase(URL)
path=[method]
else:
if (hasattr(object, '__call__') and
hasattr(object.__call__,'__roles__')):
roles=object.__call__.__roles__
if self._hacked_path:
i=rfind(URL,'/')
if i > 0: response.setBase(URL[:i])
if entry_name != method and method != 'index_html':
if debug_mode:
self.debugError("Method %s not found at: %s" % (method,URL))
else: self.notFoundError(method)
request.steps=steps
parents.reverse()
# Do authorization checks
user=None
i=0
if roles is not None:
last_parent_index=len(parents)
if hasattr(object, '__allow_groups__'):
groups=object.__allow_groups__
inext=0
else:
inext=None
for i in range(last_parent_index):
if hasattr(parents[i],'__allow_groups__'):
groups=parents[i].__allow_groups__
inext=i+1
break
if inext is not None:
i=inext
if hasattr(groups, 'validate'): v=groups.validate
else: v=old_validation
auth=self.HTTP_AUTHORIZATION
if v is old_validation and roles is UNSPECIFIED_ROLES:
# No roles, so if we have a named group, get roles from
# group keys
if hasattr(groups,'keys'): roles=groups.keys()
else:
try: groups=groups()
except: pass
try: roles=groups.keys()
except: pass
if groups is None:
# Public group, hack structures to get it to validate
roles=None
auth=''
if v is old_validation:
user=old_validation(groups, request, auth, roles)
elif roles is UNSPECIFIED_ROLES: user=v(request, auth)
else: user=v(request, auth, roles)
while user is None and i < last_parent_index:
parent=parents[i]
i=i+1
if hasattr(parent, '__allow_groups__'):
groups=parent.__allow_groups__
else: continue
if hasattr(groups,'validate'): v=groups.validate
else: v=old_validation
if v is old_validation:
user=old_validation(groups, request, auth, roles)
elif roles is UNSPECIFIED_ROLES: user=v(request, auth)
else: user=v(request, auth, roles)
if user is None and roles != UNSPECIFIED_ROLES:
self.unauthorized(realm,debug_mode)
steps=join(steps[:-i],'/')
if user is not None:
request['AUTHENTICATED_USER']=user
request['AUTHENTICATION_PATH']=steps
request['PARENTS']=parents=[object]
# Attempt to start a transaction:
try: transaction=get_transaction()
except: transaction=None
if transaction is not None: transaction.begin()
object=request.traverse(path)
# Record transaction meta-data
if transaction is not None:
info="\t" + request_get('PATH_INFO')
auth_user=request_get('AUTHENTICATED_USER',None)
if auth_user is not None:
info=("%s %s" % (steps,auth_user))+info
transaction.begin(info)
# Now get object meta-data to decide if and how it should be
# called:
info=("%s %s" %
(request_get('AUTHENTICATION_PATH'), auth_user))+info
transaction.note(info)
# Now get object meta-data to decide if and how it should be called:
object_as_function=object
# First, assume we have a method:
......@@ -570,9 +159,6 @@ class ModulePublisher:
else: return response.setBody(object)
request['URL']=URL
request['PARENT_URL']=URL[:rfind(URL,'/')]
args=[]
nrequired=len(argument_names) - (len(defaults or []))
for name_index in range(len(argument_names)):
......@@ -586,7 +172,7 @@ class ModulePublisher:
else: args.append(v)
args=tuple(args)
if debug: result=self.call_object(object,args)
if debug: result=call_object(object,args)
else: result=apply(object,args)
if result and result is not response: response.setBody(result)
......@@ -595,7 +181,7 @@ class ModulePublisher:
return response
def call_object(self,object,args):
def call_object(object,args):
result=apply(object,args) # Type s<cr> to step into published object.
return result
......@@ -642,27 +228,6 @@ def get_module_info(module_name, modules={},
except: debug_mode=None
else: debug_mode=None
# Check whether tracebacks should be hidden:
if hasattr(module,'__bobo_hide_tracebacks__'):
hide_tracebacks=not not module.__bobo_hide_tracebacks__
elif os.environ.has_key('BOBO_HIDE_TRACEBACKS'):
hide_tracebacks=lower(os.environ['BOBO_HIDE_TRACEBACKS'])
if hide_tracebacks=='y' or hide_tracebacks=='yes':
hide_tracebacks=1
else:
try: hide_tracebacks=atoi(hide_tracebacks)
except: hide_tracebacks=None
else: hide_tracebacks=1
# Reset response handling of tracebacks, if necessary:
if debug_mode or not hide_tracebacks:
def hack_response():
import Response
Response._tbopen = '<PRE>'
Response._tbclose = '</PRE>'
hack_response()
if hasattr(module,'__bobo_before__'):
bobo_before=module.__bobo_before__
else: bobo_before=None
......@@ -670,192 +235,25 @@ def get_module_info(module_name, modules={},
if hasattr(module,'__bobo_after__'): bobo_after=module.__bobo_after__
else: bobo_after=None
# Get request data from outermost environment:
if hasattr(module,'__request_data__'):
request_params=module.__request_data__
else: request_params=None
# Get initial group data:
inherited_groups=[]
if hasattr(module,'__allow_groups__'):
groups=module.__allow_groups__
inherited_groups.append(groups)
else: groups=None
web_objects=None
roles=UNSPECIFIED_ROLES
if hasattr(module,'bobo_application'):
object=module.bobo_application
if hasattr(object,'__allow_groups__'):
groups=object.__allow_groups__
inherited_groups.append(groups)
else: groups=None
if hasattr(object,'__roles__'): roles=object.__roles__
else:
if hasattr(module,'web_objects'):
web_objects=module.web_objects
object=web_objects
elif hasattr(module,'web_objects'):
object=module.web_objects
else: object=module
published=web_objects
try: doc=module.__doc__
except:
if web_objects is not None: doc=' '
else: doc=None
info= (bobo_before, bobo_after, request_params,
inherited_groups, groups, roles,
object, doc, published, realm, module_name,
debug_mode)
info= (bobo_before, bobo_after, object, realm, debug_mode)
modules[module_name]=modules[module_name+'.cgi']=info
return info
except:
if hasattr(sys, 'exc_info'): t,v,tb=sys.exc_info()
else: t, v, tb = sys.exc_type, sys.exc_value, sys.exc_traceback
t,v,tb=sys.exc_info()
v=str(v)
raise ImportError, (t, v), tb
finally:
tb=None
release()
def str_field(v):
if type(v) is ListType:
return map(str_field,v)
if hasattr(v,'__class__') and v.__class__ is FieldStorage:
v=v.value
elif type(v) is not StringType:
if hasattr(v,'file') and v.file: v=v.file
elif hasattr(v,'value'): v=v.value
return v
class FileUpload:
'''\
File upload objects
File upload objects are used to represent file-uploaded data.
File upload objects can be used just like files.
In addition, they have a 'headers' attribute that is a dictionary
containing the file-upload headers, and a 'filename' attribute
containing the name of the uploaded file.
'''
def __init__(self, aFieldStorage):
file=aFieldStorage.file
if hasattr(file, '__methods__'): methods=file.__methods__
else: methods= ['close', 'fileno', 'flush', 'isatty',
'read', 'readline', 'readlines', 'seek',
'tell', 'truncate', 'write', 'writelines']
d=self.__dict__
for m in methods:
if hasattr(file,m): d[m]=getattr(file,m)
self.headers=aFieldStorage.headers
self.filename=aFieldStorage.filename
parse_cookie_lock=allocate_lock()
def parse_cookie(text,
result=None,
qparmre=regex.compile(
'\([\0- ]*'
'\([^\0- ;,=\"]+\)="\([^"]*\)\"'
'\([\0- ]*[;,]\)?[\0- ]*\)'
),
parmre=regex.compile(
'\([\0- ]*'
'\([^\0- ;,=\"]+\)=\([^\0- ;,\"]*\)'
'\([\0- ]*[;,]\)?[\0- ]*\)'
),
acquire=parse_cookie_lock.acquire,
release=parse_cookie_lock.release,
):
if result is None: result={}
already_have=result.has_key
acquire()
try:
if qparmre.match(text) >= 0:
# Match quoted correct cookies
name=qparmre.group(2)
value=qparmre.group(3)
l=len(qparmre.group(1))
elif parmre.match(text) >= 0:
# Match evil MSIE cookies ;)
name=parmre.group(2)
value=parmre.group(3)
l=len(parmre.group(1))
else:
# this may be an invalid cookie.
# We'll simply bail without raising an error
# if the cookie is invalid.
return result
finally: release()
if not already_have(name): result[name]=value
return apply(parse_cookie,(text[l:],result))
base64=None
def old_validation(groups, request, HTTP_AUTHORIZATION,
roles=UNSPECIFIED_ROLES):
global base64
if base64 is None: import base64
if HTTP_AUTHORIZATION:
if lower(HTTP_AUTHORIZATION[:6]) != 'basic ':
if roles is None: return ''
return None
[name,password] = split(
base64.decodestring(
split(HTTP_AUTHORIZATION)[-1]), ':')
elif request.environ.has_key('REMOTE_USER'):
name=request.environ['REMOTE_USER']
password=None
else:
if roles is None: return ''
return None
if roles is None: return name
keys=None
try:
keys=groups.keys
except:
try:
groups=groups() # Maybe it was a method defining a group
keys=groups.keys
except: pass
if keys is not None:
# OK, we have a named group, so apply the roles to the named
# group.
if roles is UNSPECIFIED_ROLES: roles=keys()
g=[]
for role in roles:
if groups.has_key(role): g.append(groups[role])
groups=g
for d in groups:
if d.has_key(name) and (d[name]==password or password is None):
return name
if keys is None:
# Not a named group, so don't go further
raise 'Forbidden', (
"""<strong>You are not authorized to access this resource""")
return None
def publish_module(module_name,
stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr,
environ=os.environ, debug=0):
......@@ -867,15 +265,10 @@ def publish_module(module_name,
try:
try:
response=Response(stdout=stdout, stderr=stderr)
publisher = ModulePublisher(
stdin=stdin, stdout=stdout, stderr=stderr,
environ=environ)
response = publisher.response
request=publisher.request
request=Request(stdin, environ, response)
finally:
pass
response = publisher.publish(module_name,after_list,
debug=debug)
response = publish(request, module_name, after_list, debug=debug)
except SystemExit, v:
if hasattr(sys, 'exc_info'): must_die=sys.exc_info()
else: must_die = SystemExit, v, sys.exc_traceback
......
......@@ -82,253 +82,6 @@
# file.
#
##############################################################################
__version__='$Revision: 1.6 $'[11:-2]
import regex
from string import atoi, atol, join, upper, split, strip, rfind
isCGI_NAME = {
'SERVER_SOFTWARE' : 1,
'SERVER_NAME' : 1,
'GATEWAY_INTERFACE' : 1,
'SERVER_PROTOCOL' : 1,
'SERVER_PORT' : 1,
'REQUEST_METHOD' : 1,
'PATH_INFO' : 1,
'PATH_TRANSLATED' : 1,
'SCRIPT_NAME' : 1,
'QUERY_STRING' : 1,
'REMOTE_HOST' : 1,
'REMOTE_ADDR' : 1,
'AUTH_TYPE' : 1,
'REMOTE_USER' : 1,
'REMOTE_IDENT' : 1,
'CONTENT_TYPE' : 1,
'CONTENT_LENGTH' : 1,
}.has_key
hide_key={'HTTP_AUTHORIZATION':1,
'HTTP_CGI_AUTHORIZATION': 1,
}.has_key
class Request:
"""\
Model HTTP request data.
This object provides access to request data. This includes, the
input headers, form data, server data, and cookies.
Request objects are created by the object publisher and will be
passed to published objects through the argument name, REQUEST.
The request object is a mapping object that represents a
collection of variable to value mappings. In addition, variables
are divided into four categories:
- Environment variables
These variables include input headers, server data, and other
request-related data. The variable names are as <a
href="http://hoohoo.ncsa.uiuc.edu/cgi/env.html">specified</a>
in the <a
href="http://hoohoo.ncsa.uiuc.edu/cgi/interface.html">CGI
specification</a>
- Form data
These are data extracted from either a URL-encoded query
string or body, if present.
- Cookies
These are the cookie data, if present.
- Other
Data that may be set by an application object.
The form attribute of a request is actually a Field Storage
object. When file uploads are used, this provides a richer and
more complex interface than is provided by accessing form data as
items of the request. See the FieldStorage class documentation
for more details.
The request object may be used as a mapping object, in which case
values will be looked up in the order: environment variables,
other variables, form data, and then cookies.
"""
def __init__(self,environ,form,stdin):
self.environ=environ
self.other=form
self.stdin=stdin
have_env=environ.has_key
b=script=strip(environ['SCRIPT_NAME'])
while b and b[-1]=='/': b=b[:-1]
p = rfind(b,'/')
if p >= 0: b=b[:p+1]
else: b=''
while b and b[0]=='/': b=b[1:]
if have_env('SERVER_URL'):
server_url=strip(environ['SERVER_URL'])
else:
if have_env('HTTPS') and (
environ['HTTPS'] == "on" or environ['HTTPS'] == "ON"):
server_url='https://'
elif (have_env('SERVER_PORT_SECURE') and
environ['SERVER_PORT_SECURE'] == "1"):
server_url='https://'
else: server_url='http://'
if have_env('HTTP_HOST'):
server_url=server_url+strip(environ['HTTP_HOST'])
else:
server_url=server_url+strip(environ['SERVER_NAME'])
server_port=environ['SERVER_PORT']
if server_port!='80': server_url=server_url+':'+server_port
if server_url[-1:]=='/': server_url=server_url[:-1]
self.base="%s/%s" % (server_url,b)
while script[:1]=='/': script=script[1:]
if script: self.script="%s/%s" % (server_url,script)
else: self.script=server_url
def get_header(self, name, default=None):
"""Return the named HTTP header, or an optional default
argument or None if the header is not found. Note that
both original and CGI-ified header names are recognized,
e.g. 'Content-Type', 'CONTENT_TYPE' and 'HTTP_CONTENT_TYPE'
should all return the Content-Type header, if available.
"""
environ=self.environ
name=upper(join(split(name,"-"),"_"))
val=environ.get(name, None)
if val is not None:
return val
if name[:5] != 'HTTP_':
name='HTTP_%s' % name
return environ.get(name, default)
def __setitem__(self,key,value):
"""Set application variables
This method is used to set a variable in the requests "other"
category.
"""
self.other[key]=value
set=__setitem__
def __getitem__(self,key,
default=isCGI_NAME, # Any special internal marker will do
URLmatch=regex.compile('URL[0-9]$').match,
BASEmatch=regex.compile('BASE[0-9]$').match,
):
"""Get a variable value
Return a value for the required variable name.
The value will be looked up from one of the request data
categories. The search order is environment variables,
other variables, form data, and then cookies.
""" #"
other=self.other
if other.has_key(key):
if key=='REQUEST': return self
return other[key]
if key[:1]=='U' and URLmatch(key) >= 0 and other.has_key('URL'):
n=ord(key[3])-ord('0')
URL=other['URL']
for i in range(0,n):
l=rfind(URL,'/')
if l >= 0: URL=URL[:l]
else: raise KeyError, key
other[key]=URL
return URL
if isCGI_NAME(key) or key[:5] == 'HTTP_':
environ=self.environ
if environ.has_key(key) and (not hide_key(key)):
return environ[key]
return ''
if key=='REQUEST': return self
if key[:1]=='B' and BASEmatch(key) >= 0 and other.has_key('URL'):
n=ord(key[4])-ord('0')
if n:
v=self.script
while v[-1:]=='/': v=v[:-1]
v=join([v]+self.steps[:n-1],'/')
else:
v=self.base
while v[-1:]=='/': v=v[:-1]
other[key]=v
return v
if default is not isCGI_NAME: # Check marker
return default
raise KeyError, key
__getattr__=get=__getitem__
def has_key(self,key):
return self.get(key, Request) is not Request
def keys(self):
keys = {}
for key in self.environ.keys():
if (isCGI_NAME(key) or key[:5] == 'HTTP_') and \
(not hide_key(key)):
keys[key] = 1
keys.update(self.other)
lasturl = ""
for n in "0123456789":
key = "URL%s"%n
try:
if lasturl != self[key]:
keys[key] = 1
lasturl = self[key]
else:
break
except KeyError:
pass
for n in "0123456789":
key = "BASE%s"%n
try:
if lasturl != self[key]:
keys[key] = 1
lasturl = self[key]
else:
break
except KeyError:
pass
return keys.keys()
def items(self):
result = []
for k in self.keys():
result.append((k, self.get(k)))
return result
def __str__(self):
def str(self,name):
dict=getattr(self,name)
data=[]
for key, val in dict.items():
if not hide_key(key):
data.append('%s: %s' % (key, `val`))
return "%s:\n\t%s\n\n" % (name, join(data, '\n\t'))
return "%s\n%s\n" % (str(self,'form'), str(self,'environ'))
__repr__=__str__
import HTTPRequest
Request=HTTPRequest.HTTPRequest
del HTTPRequest
......@@ -82,572 +82,6 @@
# file.
#
##############################################################################
'''CGI Response Output formatter
$Id: Response.py,v 1.47 1999/02/05 21:12:47 brian Exp $'''
__version__='$Revision: 1.47 $'[11:-2]
import string, types, sys, regex
from string import find, rfind, lower, upper, strip, split, join, translate
from types import StringType, InstanceType
nl2sp=string.maketrans('\n',' ')
status_reasons={
100: 'Continue',
101: 'Switching Protocols',
102: 'Processing',
200: 'OK',
201: 'Created',
202: 'Accepted',
203: 'Non-Authoritative Information',
204: 'No Content',
205: 'Reset Content',
206: 'Partial Content',
207: 'Multi-Status',
300: 'Multiple Choices',
301: 'Moved Permanently',
302: 'Moved Temporarily',
303: 'See Other',
304: 'Not Modified',
305: 'Use Proxy',
307: 'Temporary Redirect',
400: 'Bad Request',
401: 'Unauthorized',
402: 'Payment Required',
403: 'Forbidden',
404: 'Not Found',
405: 'Method Not Allowed',
406: 'Not Acceptable',
407: 'Proxy Authentication Required',
408: 'Request Time-out',
409: 'Conflict',
410: 'Gone',
411: 'Length Required',
412: 'Precondition Failed',
413: 'Request Entity Too Large',
414: 'Request-URI Too Large',
415: 'Unsupported Media Type',
416: 'Requested range not satisfiable',
417: 'Expectation Failed',
422: 'Unprocessable Entity',
423: 'Locked',
424: 'Failed Dependency',
500: 'Internal Server Error',
501: 'Not Implemented',
502: 'Bad Gateway',
503: 'Service Unavailable',
504: 'Gateway Time-out',
505: 'HTTP Version not supported',
507: 'Insufficient Storage',
}
status_codes={}
# Add mappings for builtin exceptions and
# provide text -> error code lookups.
for key, val in status_reasons.items():
status_codes[lower(join(split(val, ' '), ''))]=key
status_codes[lower(val)]=key
status_codes[key]=key
en=filter(lambda n: n[-5:]=='Error', dir(__builtins__))
for name in map(lower, en):
status_codes[name]=500
status_codes['nameerror']=503
status_codes['keyerror']=503
status_codes['redirect']=300
end_of_header_search=regex.compile('</head>',regex.casefold).search
accumulate_header={'set-cookie': 1}.has_key
_tbopen, _tbclose = '<!--', '-->'
class Response:
"""\
An object representation of an HTTP response.
The Response type encapsulates all possible responses to HTTP
requests. Responses are normally created by the object publisher.
A published object may recieve the response abject as an argument
named 'RESPONSE'. A published object may also create it's own
response object. Normally, published objects use response objects
to:
- Provide specific control over output headers,
- Set cookies, or
- Provide stream-oriented output.
If stream oriented output is used, then the response object
passed into the object must be used.
""" #'
accumulated_headers=''
body=''
def __init__(self,body='',status=200,headers=None,
stdout=sys.stdout, stderr=sys.stderr,):
'''\
Creates a new response. In effect, the constructor calls
"self.setBody(body); self.setStatus(status); for name in
headers.keys(): self.setHeader(name, headers[name])"
'''
if headers is None: headers={}
self.headers=headers
if status==200:
self.status=200
headers['status']="200 OK"
else: self.setStatus(status)
self.base=''
if body: self.setBody(body)
self.cookies={}
self.stdout=stdout
self.stderr=stderr
def setStatus(self, status, reason=None):
'''\
Sets the HTTP status code of the response; the argument may
either be an integer or a string from { OK, Created, Accepted,
NoContent, MovedPermanently, MovedTemporarily,
NotModified, BadRequest, Unauthorized, Forbidden,
NotFound, InternalError, NotImplemented, BadGateway,
ServiceUnavailable } that will be converted to the correct
integer value. '''
if type(status) is types.StringType:
status=lower(status)
if status_codes.has_key(status): status=status_codes[status]
else: status=500
self.status=status
if reason is None:
if status_reasons.has_key(status): reason=status_reasons[status]
else: reason='Unknown'
self.setHeader('Status', "%d %s" % (status,str(reason)))
def setHeader(self, name, value):
'''\
Sets an HTTP return header "name" with value "value", clearing
the previous value set for the header, if one exists. '''
n=lower(name)
if accumulate_header(n):
self.accumulated_headers=(
"%s%s: %s\n" % (self.accumulated_headers, name, value))
else:
self.headers[n]=value
__setitem__=setHeader
def setBody(self, body, title='',
bogus_str_search=regex.compile(" [a-fA-F0-9]+>$").search,
):
'''\
Set the body of the response
Sets the return body equal to the (string) argument "body". Also
updates the "content-length" return header.
You can also specify a title, in which case the title and body
will be wrapped up in html, head, title, and body tags.
If the body is a 2-element tuple, then it will be treated
as (title,body)
'''
if type(body) is types.TupleType:
title,body=body
if type(body) is not types.StringType:
if hasattr(body,'asHTML'):
body=body.asHTML()
body=str(body)
l=len(body)
if (find(body,'>')==l-1 and body[:1]=='<' and l < 200 and
bogus_str_search(body) > 0):
raise 'NotFound', (
"Sorry, the requested document does not exist.<p>"
"\n%s\n%s\n%s" % (_tbopen, body[1:-1], _tbclose))
if(title):
self.body=('<html>\n<head>\n<title>%s</title>\n</head>\n'
'<body>\n%s\n</body>\n</html>'
% (str(title),str(body)))
else:
self.body=str(body)
self.insertBase()
return self
def getStatus(self):
'Returns the current HTTP status code as an integer. '
return self.status
def setBase(self,base):
'Set the base URL for the returned document.'
if base[-1:] != '/': base=base+'/'
self.base=base
self.insertBase()
def insertBase(self,
base_re_search=regex.compile('\(<base[\0- ]+[^>]+>\)',
regex.casefold).search
):
if (self.headers.has_key('content-type') and
self.headers['content-type']!='text/html'): return
if self.base:
body=self.body
if body:
e=end_of_header_search(body)
if e >= 0:
b=base_re_search(body)
if b < 0:
self.body=('%s\t<base href="%s">\n%s' %
(body[:e],self.base,body[e:]))
def appendCookie(self, name, value):
'''\
Returns an HTTP header that sets a cookie on cookie-enabled
browsers with a key "name" and value "value". If a value for the
cookie has previously been set in the response object, the new
value is appended to the old one separated by a colon. '''
cookies=self.cookies
if cookies.has_key(name): cookie=cookies[name]
else: cookie=cookies[name]={}
if cookie.has_key('value'):
cookie['value']='%s:%s' % (cookie['value'], value)
else: cookie['value']=value
def expireCookie(self, name, **kw):
'''\
Cause an HTTP cookie to be removed from the browser
The response will include an HTTP header that will remove the cookie
corresponding to "name" on the client, if one exists. This is
accomplished by sending a new cookie with an expiration date
that has already passed. Note that some clients require a path
to be specified - this path must exactly match the path given
when creating the cookie. The path can be specified as a keyword
argument.
'''
dict={'max_age':0, 'expires':'Wed, 31-Dec-97 23:59:59 GMT'}
for k, v in kw.items():
dict[k]=v
apply(Response.setCookie, (self, name, 'deleted'), dict)
def setCookie(self,name,value,**kw):
'''\
Set an HTTP cookie on the browser
The response will include an HTTP header that sets a cookie on
cookie-enabled browsers with a key "name" and value
"value". This overwrites any previously set value for the
cookie in the Response object.
'''
cookies=self.cookies
if cookies.has_key(name):
cookie=cookies[name]
else: cookie=cookies[name]={}
for k, v in kw.items():
cookie[k]=v
cookie['value']=value
def appendBody(self, body):
self.setBody(self.getBody() + body)
def getHeader(self, name):
'''\
Get a header value
Returns the value associated with a HTTP return header, or
"None" if no such header has been set in the response
yet. '''
headers=self.headers
if headers.has_key(name): return headers[name]
def __getitem__(self, name):
'Get the value of an output header'
return self.headers[name]
def getBody(self):
'Returns a string representing the currently set body. '
return self.body
def appendHeader(self, name, value, delimiter=","):
'''\
Append a value to a cookie
Sets an HTTP return header "name" with value "value",
appending it following a comma if there was a previous value
set for the header. '''
headers=self.headers
if headers.has_key(name):
h=self.header[name]
h="%s%s\n\t%s" % (h,delimiter,value)
else: h=value
self.setHeader(name,h)
def isHTML(self,str):
return lower(strip(str)[:6]) == '<html>' or find(str,'</') > 0
def quoteHTML(self,text,
subs={'&':'&amp;', "<":'&lt;', ">":'&gt;', '\"':'&quot;'}
):
for ent in '&<>\"':
if find(text, ent) >= 0:
text=join(split(text,ent),subs[ent])
return text
def format_exception(self,etype,value,tb,limit=None):
import traceback
result=['Traceback (innermost last):']
if limit is None:
if hasattr(sys, 'tracebacklimit'):
limit = sys.tracebacklimit
n = 0
while tb is not None and (limit is None or n < limit):
f = tb.tb_frame
lineno = tb.tb_lineno
co = f.f_code
filename = co.co_filename
name = co.co_name
locals=f.f_locals
result.append(' File %s, line %d, in %s'
% (filename,lineno,name))
try: result.append(' (Object: %s)' %
locals[co.co_varnames[0]].__name__)
except: pass
try: result.append(' (Info: %s)' %
str(locals['__traceback_info__']))
except: pass
tb = tb.tb_next
n = n+1
result.append(join(traceback.format_exception_only(etype, value),
' '))
return result
def _traceback(self,t,v,tb):
tb=self.format_exception(t,v,tb,200)
tb=join(tb,'\n')
tb=self.quoteHTML(tb)
return "\n%s\n%s\n%s" % (_tbopen, tb, _tbclose)
def redirect(self, location):
"""Cause a redirection without raising an error"""
self.status=302
headers=self.headers
headers['status']='302 Moved Temporarily'
headers['location']=location
return location
def exception(self, fatal=0, info=None,
absuri_match=regex.compile(
"^"
"\(/\|\([a-zA-Z0-9+.-]+:\)\)"
"[^\000- \"\\#<>]*"
"\\(#[^\000- \"\\#<>]*\\)?"
"$"
).match,
tag_search=regex.compile('[a-zA-Z]>').search,
):
if type(info) is type(()) and len(info)==3: t,v,tb = info
elif hasattr(sys, 'exc_info'): t,v,tb = sys.exc_info()
else: t,v,tb = sys.exc_type, sys.exc_value, sys.exc_traceback
stb=tb
# Abort running transaction, if any:
try: get_transaction().abort()
except: pass
try:
# Try to capture exception info for bci calls
et=translate(str(t),nl2sp)
ev=translate(str(v),nl2sp)
# Get the tb tail, which is the interesting part:
while tb.tb_next is not None: tb=tb.tb_next
el=str(tb.tb_lineno)
ef=str(tb.tb_frame.f_code.co_filename)
if find(ev,'<html>') >= 0: ev='bobo exception'
self.setHeader('bobo-exception-type',et)
self.setHeader('bobo-exception-value',ev[:255])
self.setHeader('bobo-exception-file',ef)
self.setHeader('bobo-exception-line',el)
except:
# Dont try so hard that we cause other problems ;)
pass
tb=stb
stb=None
self.setStatus(t)
if self.status >= 300 and self.status < 400:
if type(v) == types.StringType and absuri_match(v) >= 0:
if self.status==300: self.setStatus(302)
self.setHeader('location', v)
tb=None
return self
else:
try:
l,b=v
if type(l) == types.StringType and absuri_match(l) >= 0:
if self.status==300: self.setStatus(302)
self.setHeader('location', l)
self.setBody(b)
tb=None
return self
except: pass
b=v
if isinstance(b,Exception): b=str(b)
if fatal:
if t is SystemExit and v.code==0:
tb=self.setBody(
(str(t),
'This application has exited normally.<p>'
+ self._traceback(t,v,tb)))
else:
tb=self.setBody(
(str(t),
'Sorry, a SERIOUS APPLICATION ERROR occurred.<p>'
+ self._traceback(t,v,tb)))
elif type(b) is not types.StringType or tag_search(b) < 0:
tb=self.setBody(
(str(t),
'Sorry, an error occurred.<p>'
+ self._traceback(t,v,tb)))
elif self.isHTML(b):
tb=self.setBody(b+self._traceback(t,'(see above)',tb))
else:
tb=self.setBody((str(t),b+self._traceback(t,'(see above)',tb)))
return tb
_wrote=None
def _cookie_list(self):
cookie_list=[]
for name, attrs in self.cookies.items():
# Note that as of May 98, IE4 ignores cookies with
# quoted cookie attr values, so only the value part
# of name=value pairs may be quoted.
cookie='Set-Cookie: %s="%s"' % (name, attrs['value'])
for name, v in attrs.items():
name=lower(name)
if name=='expires': cookie = '%s; Expires=%s' % (cookie,v)
elif name=='domain': cookie = '%s; Domain=%s' % (cookie,v)
elif name=='path': cookie = '%s; Path=%s' % (cookie,v)
elif name=='max_age': cookie = '%s; Max-Age=%s' % (cookie,v)
elif name=='comment': cookie = '%s; Comment=%s' % (cookie,v)
elif name=='secure': cookie = '%s; Secure' % cookie
cookie_list.append(cookie)
# Should really check size of cookies here!
return cookie_list
def __str__(self,
html_search=regex.compile('<html>',regex.casefold).search,
):
if self._wrote: return '' # Streaming output was used.
headers=self.headers
body=self.body
if body:
isHTML=self.isHTML(body)
if not headers.has_key('content-type'):
if isHTML:
c='text/html'
else:
c='text/plain'
self.setHeader('content-type',c)
else:
isHTML = headers['content-type']=='text/html'
if isHTML and end_of_header_search(self.body) < 0:
lhtml=html_search(body)
if lhtml >= 0:
lhtml=lhtml+6
body='%s<head></head>\n%s' % (body[:lhtml],body[lhtml:])
else:
body='<html><head></head>\n' + body
self.setBody(body)
body=self.body
if not headers.has_key('content-type') and self.status == 200:
self.setStatus('nocontent')
if not headers.has_key('content-length'):
self.setHeader('content-length',len(body))
headersl=[]
append=headersl.append
# Make sure status comes out first!
try:
v=headers['status']
del headers['status']
except: v="200 OK"
append("Status: "+v)
for k,v in headers.items():
k=upper(k[:1])+k[1:]
start=0
l=find(k,'-',start)
while l >= start:
k="%s-%s%s" % (k[:l],upper(k[l+1:l+2]),k[l+2:])
start=l+1
l=find(k,'-',start)
append("%s: %s" % (k,v))
if self.cookies:
headersl=headersl+self._cookie_list()
headersl[len(headersl):]=[self.accumulated_headers,body]
return join(headersl,'\n')
def __repr__(self):
return 'Response(%s)' % `self.body`
def flush(self): pass
def write(self,data):
"""\
Return data as a stream
HTML data may be returned using a stream-oriented interface.
This allows the browser to display partial results while
computation of a response to proceed.
The published object should first set any output headers or
cookies on the response object.
Note that published objects must not generate any errors
after beginning stream-oriented output.
"""
self.body=self.body+data
headers=self.headers
if headers.has_key('content-length'):
del headers['content-length']
if not self.headers.has_key('content-type'):
self.setHeader('content-type', 'text/html')
self.insertBase()
body=self.body
self.body=''
self.write=write=self.stdout.write
try: self.flush=self.stdout.flush
except: pass
write(str(self))
self._wrote=1
write(body)
import HTTPResponse
Response=HTTPResponse.HTTPResponse
del HTTPResponse
......@@ -162,9 +162,9 @@ Examples
s
$Id: Test.py,v 1.28 1998/12/04 20:15:34 jim Exp $
$Id: Test.py,v 1.29 1999/02/18 17:17:56 jim Exp $
'''
__version__='$Revision: 1.28 $'[11:-2]
__version__='$Revision: 1.29 $'[11:-2]
import sys, traceback, profile, os, getopt, string
from time import clock
......@@ -243,23 +243,15 @@ def publish_module_pm(module_name,
environ=os.environ, debug=0):
from Response import Response
from Publish import ModulePublisher
from Request import Request
from Publish import publish
after_list=[None]
request=None
try:
response=Response(stdout=stdout, stderr=stderr)
publisher = ModulePublisher(stdin=stdin, stdout=stdout, stderr=stderr,
environ=environ)
response = publisher.response
request=publisher.request
response = publisher.publish(module_name,after_list,debug=debug)
request.other={}
response=str(response)
finally:
try: request.other={}
except: pass
if after_list[0] is not None: after_list[0]()
request=Request(stdin, environ, response)
response = publish(request, module_name, after_list, debug=debug)
try: from codehack import getlineno
except:
......@@ -325,7 +317,7 @@ def publish(script=None,path_info='/',
else: run(c)
elif debug:
import Publish
from Publish import ModulePublisher
from Publish import publish, call_object
import pdb
class Pdb(pdb.Pdb):
......@@ -355,10 +347,8 @@ def publish(script=None,path_info='/',
filename = code.co_filename
db.set_break(filename,lineno)
fbreak(db,ModulePublisher.publish)
fbreak(db,ModulePublisher.call_object)
#fbreak(db,Publish.new_find_object)
#fbreak(db,Publish.old_find_object)
fbreak(db,publish)
fbreak(db,call_object)
dbdata={'breakpoints':(), 'env':env}
b=''
......
##############################################################################
#
# Zope Public License (ZPL) Version 0.9.4
# ---------------------------------------
#
# Copyright (c) Digital Creations. All rights reserved.
#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
# conditions are met:
#
# 1. Redistributions in source code must retain the above
# copyright notice, this list of conditions, and the following
# disclaimer.
#
# 2. Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions, and the following
# disclaimer in the documentation and/or other materials
# provided with the distribution.
#
# 3. Any use, including use of the Zope software to operate a
# website, must either comply with the terms described below
# under "Attribution" or alternatively secure a separate
# license from Digital Creations.
#
# 4. All advertising materials, documentation, or technical papers
# mentioning features derived from or use of this software must
# display the following acknowledgement:
#
# "This product includes software developed by Digital
# Creations for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# 5. Names associated with Zope or Digital Creations must not be
# used to endorse or promote products derived from this
# software without prior written permission from Digital
# Creations.
#
# 6. Redistributions of any form whatsoever must retain the
# following acknowledgment:
#
# "This product includes software developed by Digital
# Creations for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# 7. Modifications are encouraged but must be packaged separately
# as patches to official Zope releases. Distributions that do
# not clearly separate the patches from the original work must
# be clearly labeled as unofficial distributions.
#
# Disclaimer
#
# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND
# ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
# FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
# SHALL DIGITAL CREATIONS OR ITS CONTRIBUTORS BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
# THE POSSIBILITY OF SUCH DAMAGE.
#
# Attribution
#
# Individuals or organizations using this software as a web site
# must provide attribution by placing the accompanying "button"
# and a link to the accompanying "credits page" on the website's
# main entry point. In cases where this placement of
# attribution is not feasible, a separate arrangment must be
# concluded with Digital Creations. Those using the software
# for purposes other than web sites must provide a corresponding
# attribution in locations that include a copyright using a
# manner best suited to the application environment.
#
# This software consists of contributions made by Digital
# Creations and many individuals on behalf of Digital Creations.
# Specific attributions are listed in the accompanying credits
# file.
#
##############################################################################
__version__='$Revision: 1.1 $'[11:-2]
# Waaaa, I wish I didn't have to work this hard.
try: from thread import allocate_lock
except:
class allocate_lock:
def acquire(*args): pass
def release(*args): pass
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment