[Zope-Checkins] CVS: Zope3/lib/python/Zope/Publisher/Browser - IBrowserApplicationRequest.py:1.1.2.1 IBrowserGetPublication.py:1.1.2.1 Request.py:1.1.2.1 Response.py:1.1.2.1
Jim Fulton
jim@zope.com
Sat, 16 Mar 2002 09:44:02 -0500
Update of /cvs-repository/Zope3/lib/python/Zope/Publisher/Browser
In directory cvs.zope.org:/tmp/cvs-serv24057/python/Zope/Publisher/Browser
Added Files:
Tag: Zope-3x-branch
IBrowserApplicationRequest.py IBrowserGetPublication.py
Request.py Response.py
Log Message:
Checking in partial publisher refactoring on the
Zope3-publisher-refactor-branch branch to facilitate collaboration
with Stephan.
=== Added File Zope3/lib/python/Zope/Publisher/Browser/IBrowserApplicationRequest.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""
Revision information:
$Id: IBrowserApplicationRequest.py,v 1.1.2.1 2002/03/16 14:44:00 jim Exp $
"""
from Zope.Publisher.IApplicationRequest import IApplicationRequest
class IHTTPApplicationRequest(IApplicationRequest):
"""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>
- Cookies
These are the cookie data, if present.
- Other
Data that may be set by an application object.
The form data 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, and then cookies.
"""
=== Added File Zope3/lib/python/Zope/Publisher/Browser/IBrowserGetPublication.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""
Revision information:
$Id: IBrowserGetPublication.py,v 1.1.2.1 2002/03/16 14:44:00 jim Exp $
"""
from Zope.Publisher.IPublication import IPublication
class IBrowserGetPublication (IPublication):
"""
Object publication framework.
"""
def getDefaultTraversal(request, ob):
"""Get the default published object for the request
Allows a default view to be added to traversal.
Returns (ob, steps_reversed).
"""
=== Added File Zope3/lib/python/Zope/Publisher/Browser/Request.py === (475/575 lines abridged)
##############################################################################
#
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 1.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
##############################################################################
"""
$Id: Request.py,v 1.1.2.1 2002/03/16 14:44:00 jim Exp $
"""
from Zope.Publisher.HTTP import HTTPRequest
class BrowserRequest(HTTPRequest):
__implements__ = HTTPRequest.__implements__, IHTTPCredentials, IHTTPRequest
__auth = None # The value of the HTTP_AUTHORIZATION header.
__computed_urls = () # Names of computed URLx variables
__script = () # SERVER_URL + __script + quoted_steps == full URL
__server_script = '' # __server_script + quoted_steps = full URL
__server_url = '' # Possible munged server_url
def __init__(self, body_instream, outstream, environ):
super(BrowserRequest, self).__init__(body_instream, outstream, environ)
self.__steps = []
self.__quoted_steps = []
self._orig_env = environ
environ = sane_environment(environ)
if environ.has_key('HTTP_AUTHORIZATION'):
self.__auth = environ['HTTP_AUTHORIZATION']
del environ['HTTP_AUTHORIZATION']
self.__environ=environ
get_env=environ.get
other = self.other
self.__form={}
################################################################
# Get base info first. This isn't likely to cause
# errors and might be useful to error handlers.
[-=- -=- -=- 475 lines omitted -=- -=- -=-]
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 parse_cookie(
text,
result=None,
qparmre=re.compile(
'([\x00- ]*([^\x00- ;,="]+)="([^"]*)"([\x00- ]*[;,])?[\x00- ]*)'),
parmre=re.compile(
'([\x00- ]*([^\x00- ;,="]+)=([^\x00- ;,"]*)([\x00- ]*[;,])?[\x00- ]*)'),
):
if result is None: result={}
already_have=result.has_key
mo_q = qparmre.match(text)
if mo_q:
# Match quoted correct cookies
l = len(mo_q.group(1))
name = mo_q.group(2)
value = mo_q.group(3)
else:
# Match evil MSIE cookies ;)
mo_p = parmre.match(text)
if mo_p:
l = len(mo_p.group(1))
name = mo_p.group(2)
value = mo_p.group(3)
else:
return result
if not already_have(name): result[name]=value
return apply(parse_cookie,(text[l:],result))
=== Added File Zope3/lib/python/Zope/Publisher/Browser/Response.py ===
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 1.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
'''HTTP Response Output formatter
$Id: Response.py,v 1.1.2.1 2002/03/16 14:44:00 jim Exp $'''
__version__='$Revision: 1.1.2.1 $'[11:-2]
import sys, re
from types import StringType, ClassType
from cgi import escape
from Zope.Publisher.BaseResponse import BaseResponse
from Zope.Publisher.Exceptions import Redirect
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[val.replace(' ', '').lower()] = key
status_codes[val.lower()] = key
status_codes[key]=key
status_codes[str(key)]=key
en = [n.lower() for n in dir(__builtins__) if n.endswith('Error')]
for name in en:
status_codes[name] = 500
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 object 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 = None
base = None
realm = 'Zope'
_error_format = 'text/html'
payload = None
_wrote_headers = 0
_streaming = 0
status = 200
reason = 'Ok'
def __init__(self, payload, outstream, header_output=None):
self.payload = payload
self._orig_payload = payload
self.header_output = header_output
BaseResponse.__init__(self, outstream)
def retry(self):
"""
Returns a response object to be used in a retry attempt
"""
return self.__class__(self._orig_payload, self.outstream,
self.header_output)
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 status is None:
status = 200
else:
if isinstance(status, StringType):
status = status.lower()
if status_codes.has_key(status):
status=status_codes[status]
else:
status=500
self.status=status
if reason is None:
if status == 200:
reason = 'Ok'
elif status_reasons.has_key(status):
reason = status_reasons[status]
else:
reason = 'Unknown'
self.reason = reason
def setHeader(self, name, value, literal=0):
'''
Sets an HTTP return header "name" with value "value", clearing
the previous value set for the header, if one exists. If the
literal flag is true, the case of the header name is preserved,
otherwise word-capitalization will be performed on the header
name on output.
'''
key = name.lower()
if accumulate_header(key):
self.addHeader(name, value)
else:
name = literal and name or key
self.headers[name]=value
__setitem__ = setHeader
def addHeader(self, name, value):
'''
Sets a new HTTP return header with the given value, while retaining
any previously set headers with the same name.'''
accum = self.accumulated_headers
if not accum:
self.accumulated_headers = accum = []
accum.append('%s: %s' % (name, value))
def setBody(self, body):
return self.payload.setBody(self, body)
def updateContentLength(self):
blen = str(len(self.body))
if blen.endswith('L'):
blen = blen[:-1]
self.setHeader('content-length', blen)
def appendToCookie(self, name, value):
'''
Creates 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):
'''
Causes 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
cookies=self.cookies
if cookies.has_key(name):
# Cancel previous setCookie().
del cookies[name]
self.setCookie(name, 'deleted', **dict)
def setCookie(self, name, value, **kw):
'''
Sets 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 appendToHeader(self, name, value, delimiter=","):
'''
Appends a value to a header
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\r\n\t%s" % (h,delimiter,value)
else: h=value
self.setHeader(name,h)
def redirect(self, location, status=302):
"""Causes a redirection without raising an error"""
self.setStatus(status)
self.setHeader('Location', location)
return location
def handleException(self, exc_info):
"""
"""
self.payload.handleException(self, exc_info)
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 = name.lower()
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' and v: cookie = '%s; Secure' % cookie
cookie_list.append(cookie)
# Should really check size of cookies here!
return cookie_list
def getHeaders(self):
"""
Returns a mapping of correctly-cased header names to values.
"""
res = {}
headers = self.headers
if (not self._streaming and not headers.has_key('content-length')
and not headers.has_key('transfer-encoding')):
self.updateContentLength()
res["X-Powered-By"] = "Zope (www.zope.org), Python (www.python.org)"
for key, val in headers.items():
if key.lower() == key:
# only change non-literal header names
key="%s%s" % (key[:1].upper(), key[1:])
start=0
l=key.find('-',start)
while l >= start:
key="%s-%s%s" % (key[:l],key[l+1:l+2].upper(),key[l+2:])
start=l+1
l=key.find('-',start)
res[key] = val
return res
def getHeaderText(self, m):
lst = ['Status: %s %s' % (self.status, self.reason)]
lst.extend(map(lambda x: '%s: %s' % x, m.items()))
lst.extend(self._cookie_list())
accum = self.accumulated_headers
if accum:
lst.extend(accum)
return ('%s\r\n\r\n' % '\r\n'.join(lst))
def outputHeaders(self):
m = self.getHeaders()
header_output = self.header_output
if header_output is not None:
# Use the IHeaderOutput interface.
header_output.setResponseStatus(self.status, self.reason)
header_output.setResponseHeaders(m)
cookies = self._cookie_list()
if cookies:
header_output.appendResponseHeaders(cookies)
accum = self.accumulated_headers
if accum:
header_output.appendResponseHeaders(accum)
else:
# Write directly to outstream.
s = self.getHeaderText(m)
self.outstream.write(s)
def __str__(self):
"""
Debugging output. Does not include headers added for connection
control.
"""
m = self.getHeaders()
return self.getHeaderText(m) + self.body
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.
"""
if streaming:
self._streaming = 1
self.output(data)
def output(self, data):
if not self._wrote_headers:
self.outputHeaders()
self._wrote_headers = 1
self.outstream.write(data)
def outputBody(self):
"""
Outputs the response body.
"""
self.output(self.body)