[Checkins] SVN: zope.publisher/tags/3.4.4/ tag for 3.4.4

Christophe Combelles ccomb at free.fr
Sun Aug 17 18:40:10 EDT 2008


Log message for revision 89935:
  tag for 3.4.4
  

Changed:
  A   zope.publisher/tags/3.4.4/
  D   zope.publisher/tags/3.4.4/CHANGES.txt
  A   zope.publisher/tags/3.4.4/CHANGES.txt
  D   zope.publisher/tags/3.4.4/setup.py
  A   zope.publisher/tags/3.4.4/setup.py
  D   zope.publisher/tags/3.4.4/src/zope/publisher/http.py
  A   zope.publisher/tags/3.4.4/src/zope/publisher/http.py

-=-
Copied: zope.publisher/tags/3.4.4 (from rev 89876, zope.publisher/branches/3.4)


Property changes on: zope.publisher/tags/3.4.4
___________________________________________________________________
Name: svn:ignore
   + bin
build
dist
lib
develop-eggs
eggs
parts
.installed.cfg

Name: svn:mergeinfo
   + 

Deleted: zope.publisher/tags/3.4.4/CHANGES.txt
===================================================================
--- zope.publisher/branches/3.4/CHANGES.txt	2008-08-15 12:26:13 UTC (rev 89876)
+++ zope.publisher/tags/3.4.4/CHANGES.txt	2008-08-17 22:40:09 UTC (rev 89935)
@@ -1,63 +0,0 @@
-Changes
-=======
-
-3.4.4 (unreleased)
-------------------
-
-* Fixed bug #98440 (interfaces lost on retried request)
-
-3.4.3 (2008-07-31)
-------------------
-
-* LP #253362: better dealing with malformed HTTP_ACCEPT_CHARSET headers
-
-3.4.2 (2007-12-07)
-------------------
-
-* Made segmentation of URLs not strip (trailing) whitespace from path segments
-  to allow URLs ending in %20 to be handled correctly. (#172742)
-
-3.4.1 (2007-09-29)
-------------------
-
-No changes since 3.4.1b2.
-
-3.4.1b2 (2007-08-02)
---------------------
-
-* zope.publisher now works on Python 2.5.
-
-* Fix a problem with request.get() when the object that's to be
-  retrieved is the request itself.
-
-3.4.1b1 (2007-07-13)
---------------------
-
-No changes.
-
-3.4.0b2 (2007-07-05)
---------------------
-
-* Fix https://bugs.launchpad.net/zope3/+bug/122054:
-  HTTPInputStream understands both the CONTENT_LENGTH and
-  HTTP_CONTENT_LENGTH environment variables. It is also now tolerant
-  of empty strings and will treat those as if the variable were
-  absent.
-
-3.4.0b1 (2007-07-05)
---------------------
-
-* Fix caching issue. The input stream never got cached in a temp file 
-  because of a wrong content-length header lookup. Added CONTENT_LENGTH 
-  header check in addition to the previous used HTTP_CONTENT_LENGTH. The 
-  HTTP\_ prefix is sometimes added by some CGI proxies, but CONTENT_LENGTH
-  is the right header info for the size.
-
-* Fix https://bugs.launchpad.net/zope3/+bug/98413:
-  HTTPResponse.handleException should set the content type
-
-3.4.0a1 (2007-04-22)
---------------------
-
-Initial release as a separate project, corresponds to zope.publisher
-from Zope 3.4.0a1

Copied: zope.publisher/tags/3.4.4/CHANGES.txt (from rev 89933, zope.publisher/branches/3.4/CHANGES.txt)
===================================================================
--- zope.publisher/tags/3.4.4/CHANGES.txt	                        (rev 0)
+++ zope.publisher/tags/3.4.4/CHANGES.txt	2008-08-17 22:40:09 UTC (rev 89935)
@@ -0,0 +1,66 @@
+Changes
+=======
+
+3.4.4 (2008-08-18)
+------------------
+
+* Fixed bug #98440 (interfaces lost on retried request)
+
+* LP #98284: Pass the ``size`` argument to readline, as the version of
+  twisted used in zope.app.twisted supports it
+
+3.4.3 (2008-07-31)
+------------------
+
+* LP #253362: better dealing with malformed HTTP_ACCEPT_CHARSET headers
+
+3.4.2 (2007-12-07)
+------------------
+
+* Made segmentation of URLs not strip (trailing) whitespace from path segments
+  to allow URLs ending in %20 to be handled correctly. (#172742)
+
+3.4.1 (2007-09-29)
+------------------
+
+No changes since 3.4.1b2.
+
+3.4.1b2 (2007-08-02)
+--------------------
+
+* zope.publisher now works on Python 2.5.
+
+* Fix a problem with request.get() when the object that's to be
+  retrieved is the request itself.
+
+3.4.1b1 (2007-07-13)
+--------------------
+
+No changes.
+
+3.4.0b2 (2007-07-05)
+--------------------
+
+* Fix https://bugs.launchpad.net/zope3/+bug/122054:
+  HTTPInputStream understands both the CONTENT_LENGTH and
+  HTTP_CONTENT_LENGTH environment variables. It is also now tolerant
+  of empty strings and will treat those as if the variable were
+  absent.
+
+3.4.0b1 (2007-07-05)
+--------------------
+
+* Fix caching issue. The input stream never got cached in a temp file 
+  because of a wrong content-length header lookup. Added CONTENT_LENGTH 
+  header check in addition to the previous used HTTP_CONTENT_LENGTH. The 
+  HTTP\_ prefix is sometimes added by some CGI proxies, but CONTENT_LENGTH
+  is the right header info for the size.
+
+* Fix https://bugs.launchpad.net/zope3/+bug/98413:
+  HTTPResponse.handleException should set the content type
+
+3.4.0a1 (2007-04-22)
+--------------------
+
+Initial release as a separate project, corresponds to zope.publisher
+from Zope 3.4.0a1

Deleted: zope.publisher/tags/3.4.4/setup.py
===================================================================
--- zope.publisher/branches/3.4/setup.py	2008-08-15 12:26:13 UTC (rev 89876)
+++ zope.publisher/tags/3.4.4/setup.py	2008-08-17 22:40:09 UTC (rev 89935)
@@ -1,55 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2006 Zope Corporation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.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.
-#
-##############################################################################
-"""Setup for zope.publisher package
-
-$Id$
-"""
-
-from setuptools import setup, find_packages
-
-
-long_description = (open('README.txt').read() +
-                    '\n\n' +
-                    open('CHANGES.txt').read())
-
-
-setup(name='zope.publisher',
-      version='3.4.4dev',
-      url='http://pypi.python.org/pypi/zope.publisher',
-      license='ZPL 2.1',
-      author='Zope Corporation and Contributors',
-      author_email='zope3-dev at zope.org',
-      description="The Zope publisher publishes Python objects on the web.",
-      long_description=long_description,
-
-      packages=find_packages('src'),
-      package_dir={'': 'src'},
-      namespace_packages=['zope',],
-      tests_require=['zope.testing'],
-      install_requires=['setuptools',
-                        'zope.component',
-                        'zope.event',
-                        'zope.exceptions',
-                        'zope.i18n',
-                        'zope.interface',
-                        'zope.location',
-                        'zope.proxy',
-                        'zope.security',
-                        'zope.testing',
-                        'zope.app.testing',
-                        'zope.deprecation',
-                        'zope.deferredimport'],
-      include_package_data=True,
-      zip_safe=False,
-      )

Copied: zope.publisher/tags/3.4.4/setup.py (from rev 89933, zope.publisher/branches/3.4/setup.py)
===================================================================
--- zope.publisher/tags/3.4.4/setup.py	                        (rev 0)
+++ zope.publisher/tags/3.4.4/setup.py	2008-08-17 22:40:09 UTC (rev 89935)
@@ -0,0 +1,55 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.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.
+#
+##############################################################################
+"""Setup for zope.publisher package
+
+$Id$
+"""
+
+from setuptools import setup, find_packages
+
+
+long_description = (open('README.txt').read() +
+                    '\n\n' +
+                    open('CHANGES.txt').read())
+
+
+setup(name='zope.publisher',
+      version='3.4.4',
+      url='http://pypi.python.org/pypi/zope.publisher',
+      license='ZPL 2.1',
+      author='Zope Corporation and Contributors',
+      author_email='zope-dev at zope.org',
+      description="The Zope publisher publishes Python objects on the web.",
+      long_description=long_description,
+
+      packages=find_packages('src'),
+      package_dir={'': 'src'},
+      namespace_packages=['zope',],
+      tests_require=['zope.testing'],
+      install_requires=['setuptools',
+                        'zope.component',
+                        'zope.event',
+                        'zope.exceptions',
+                        'zope.i18n',
+                        'zope.interface',
+                        'zope.location',
+                        'zope.proxy',
+                        'zope.security',
+                        'zope.testing',
+                        'zope.app.testing',
+                        'zope.deprecation',
+                        'zope.deferredimport'],
+      include_package_data=True,
+      zip_safe=False,
+      )

Deleted: zope.publisher/tags/3.4.4/src/zope/publisher/http.py
===================================================================
--- zope.publisher/branches/3.4/src/zope/publisher/http.py	2008-08-15 12:26:13 UTC (rev 89876)
+++ zope.publisher/tags/3.4.4/src/zope/publisher/http.py	2008-08-17 22:40:09 UTC (rev 89935)
@@ -1,1004 +0,0 @@
-##############################################################################
-#
-# 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.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 Publisher
-
-$Id$
-"""
-import re, time, random
-from cStringIO import StringIO
-from urllib import quote, unquote, splitport
-from types import StringTypes, ClassType
-from cgi import escape
-from Cookie import SimpleCookie
-from Cookie import CookieError
-import logging
-from tempfile import TemporaryFile
-
-from zope import component, interface, event
-from zope.deprecation import deprecation
-
-from zope.publisher import contenttype
-from zope.publisher.interfaces.http import IHTTPCredentials
-from zope.publisher.interfaces.http import IHTTPRequest
-from zope.publisher.interfaces.http import IHTTPApplicationRequest
-from zope.publisher.interfaces.http import IHTTPPublisher
-from zope.publisher.interfaces.http import IHTTPVirtualHostChangedEvent
-from zope.publisher.interfaces.http import IResult
-
-from zope.publisher.interfaces import Redirect
-from zope.publisher.interfaces.http import IHTTPResponse
-from zope.publisher.interfaces.http import IHTTPApplicationResponse
-from zope.publisher.interfaces.logginginfo import ILoggingInfo
-from zope.i18n.interfaces import IUserPreferredCharsets
-from zope.i18n.interfaces import IUserPreferredLanguages
-from zope.i18n.locales import locales, LoadLocaleError
-
-from zope.publisher import contenttype
-from zope.publisher.base import BaseRequest, BaseResponse
-from zope.publisher.base import RequestDataProperty, RequestDataMapper
-from zope.publisher.base import RequestDataGetter
-
-
-# Default Encoding
-ENCODING = 'UTF-8'
-
-eventlog = logging.getLogger('eventlog')
-
-class CookieMapper(RequestDataMapper):
-    _mapname = '_cookies'
-
-class HeaderGetter(RequestDataGetter):
-    _gettrname = 'getHeader'
-
-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.
-    # It also makes sure PATH_INFO is a unicode string.
-    dict = {}
-    for key, val in env.items():
-        while key.startswith('REDIRECT_'):
-            key = key[9:]
-        dict[key] = val
-    if 'HTTP_CGI_AUTHORIZATION' in dict:
-        dict['HTTP_AUTHORIZATION'] = dict.pop('HTTP_CGI_AUTHORIZATION')
-    if 'PATH_INFO' in dict:
-        dict['PATH_INFO'] = dict['PATH_INFO'].decode('utf-8')
-    return dict
-
-class HTTPVirtualHostChangedEvent(object):
-    interface.implements(IHTTPVirtualHostChangedEvent)
-
-    request = None
-
-    def __init__(self, request):
-        self.request = request
-
-# Possible HTTP status responses
-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={}
-
-def init_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
-
-init_status_codes()
-
-
-class URLGetter(object):
-
-    __slots__ = "__request"
-
-    def __init__(self, request):
-        self.__request = request
-
-    def __str__(self):
-        return self.__request.getURL()
-
-    def __getitem__(self, name):
-        url = self.get(name, None)
-        if url is None:
-            raise KeyError(name)
-        return url
-
-    def get(self, name, default=None):
-        i = int(name)
-        try:
-            if i < 0:
-                i = -i
-                return self.__request.getURL(i)
-            else:
-                return self.__request.getApplicationURL(i)
-        except IndexError, v:
-            if v[0] == i:
-                return default
-            raise
-
-class HTTPInputStream(object):
-    """Special stream that supports caching the read data.
-
-    This is important, so that we can retry requests.
-    """
-
-    def __init__(self, stream, environment):
-        self.stream = stream
-        size = environment.get('CONTENT_LENGTH')
-        # There can be no size in the environment (None) or the size
-        # can be an empty string, in which case we treat it as absent.
-        if not size:
-            size = environment.get('HTTP_CONTENT_LENGTH')
-        if not size or int(size) < 65536:
-            self.cacheStream = StringIO()
-        else:
-            self.cacheStream = TemporaryFile()
-
-    def getCacheStream(self):
-        self.read()
-        self.cacheStream.seek(0)
-        return self.cacheStream
-
-    def read(self, size=-1):
-        data = self.stream.read(size)
-        self.cacheStream.write(data)
-        return data
-
-    def readline(self, size=None):
-        # XXX We should pass the ``size`` argument to self.stream.readline
-        # but twisted.web2.wsgi.InputStream.readline() doesn't accept it.
-        # See http://www.zope.org/Collectors/Zope3-dev/535 and
-        #     http://twistedmatrix.com/trac/ticket/1451
-        #     https://bugs.launchpad.net/zope3/+bug/98284
-        data = self.stream.readline()
-        self.cacheStream.write(data)
-        return data
-
-    def readlines(self, hint=0):
-        data = self.stream.readlines(hint)
-        self.cacheStream.write(''.join(data))
-        return data
-        
-
-DEFAULT_PORTS = {'http': '80', 'https': '443'}
-STAGGER_RETRIES = True
-
-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.
-    """
-    interface.implements(IHTTPCredentials,
-                         IHTTPRequest,
-                         IHTTPApplicationRequest)
-
-    __slots__ = (
-        '__provides__',   # Allow request to directly provide interfaces
-        '_auth',          # The value of the HTTP_AUTHORIZATION header.
-        '_cookies',       # The request cookies
-        '_path_suffix',   # Extra traversal steps after normal traversal
-        '_retry_count',   # How many times the request has been retried
-        '_app_names',     # The application path as a sequence
-        '_app_server',    # The server path of the application url
-        '_orig_env',      # The original environment
-        '_endswithslash', # Does the given path end with /
-        'method',         # The upper-cased request method (REQUEST_METHOD)
-        '_locale',        # The locale for the request
-        '_vh_root',       # Object at the root of the virtual host
-        )
-
-    retry_max_count = 3    # How many times we're willing to retry
-
-    def __init__(self, body_instream, environ, response=None):
-
-        super(HTTPRequest, self).__init__(
-            HTTPInputStream(body_instream, environ), environ, response)
-
-        self._orig_env = environ
-        environ = sane_environment(environ)
-
-        if 'HTTP_AUTHORIZATION' in environ:
-            self._auth = environ['HTTP_AUTHORIZATION']
-            del environ['HTTP_AUTHORIZATION']
-        else:
-            self._auth = None
-
-        self.method = environ.get("REQUEST_METHOD", 'GET').upper()
-
-        self._environ = environ
-
-        self.__setupCookies()
-        self.__setupPath()
-        self.__setupURLBase()
-        self._vh_root = None
-        self.setupLocale()
-
-    def setupLocale(self):
-        envadapter = IUserPreferredLanguages(self, None)
-        if envadapter is None:
-            self._locale = None
-            return
-
-        langs = envadapter.getPreferredLanguages()
-        for httplang in langs:
-            parts = (httplang.split('-') + [None, None])[:3]
-            try:
-                self._locale = locales.getLocale(*parts)
-                return
-            except LoadLocaleError:
-                # Just try the next combination
-                pass
-        else:
-            # No combination gave us an existing locale, so use the default,
-            # which is guaranteed to exist
-            self._locale = locales.getLocale(None, None, None)
-
-    def _getLocale(self):
-        return self._locale
-    locale = property(_getLocale)
-
-    def __setupURLBase(self):
-        get_env = self._environ.get
-        # Get base info first. This isn't likely to cause
-        # errors and might be useful to error handlers.
-        script = get_env('SCRIPT_NAME', '').strip()
-
-        # _script and the other _names are meant for URL construction
-        self._app_names = filter(None, script.split('/'))
-
-        # get server URL and store it too, since we are already looking it up
-        server_url = get_env('SERVER_URL', None)
-        if server_url is not None:
-            self._app_server = server_url = server_url.strip()
-        else:
-            server_url = self.__deduceServerURL()
-
-        if server_url.endswith('/'):
-            server_url = server_url[:-1]
-
-        # strip off leading /'s of script
-        while script.startswith('/'):
-            script = script[1:]
-
-        self._app_server = server_url
-
-    def __deduceServerURL(self):
-        environ = self._environ
-
-        if (environ.get('HTTPS', '').lower() == "on" or
-            environ.get('SERVER_PORT_SECURE') == "1"):
-            protocol = 'https'
-        else:
-            protocol = 'http'
-
-        if environ.has_key('HTTP_HOST'):
-            host = environ['HTTP_HOST'].strip()
-            hostname, port = splitport(host)
-        else:
-            hostname = environ.get('SERVER_NAME', '').strip()
-            port = environ.get('SERVER_PORT', '')
-
-        if port and port != DEFAULT_PORTS.get(protocol):
-            host = hostname + ':' + port
-        else:
-            host = hostname
-
-        return '%s://%s' % (protocol, host)
-
-    def _parseCookies(self, text, result=None):
-        """Parse 'text' and return found cookies as 'result' dictionary."""
-
-        if result is None:
-            result = {}
-
-        # ignore cookies on a CookieError
-        try:
-            c = SimpleCookie(text)
-        except CookieError, e:
-            eventlog.warn(e)
-            return result
-
-        for k,v in c.items():
-            result[unicode(k, ENCODING)] = unicode(v.value, ENCODING)
-
-        return result
-
-    def __setupCookies(self):
-        # 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.
-        self._cookies = {}
-        cookie_header = self._environ.get('HTTP_COOKIE', None)
-        if cookie_header is not None:
-            self._parseCookies(cookie_header, self._cookies)
-
-    def __setupPath(self):
-        # PATH_INFO is unicode here, so setupPath_helper sets up the
-        # traversal stack correctly.
-        self._setupPath_helper("PATH_INFO")
-
-    def supportsRetry(self):
-        'See IPublisherRequest'
-        count = getattr(self, '_retry_count', 0)
-        if count < self.retry_max_count:
-            if STAGGER_RETRIES:
-                time.sleep(random.uniform(0, 2**(count)))
-            return True
-
-    def retry(self):
-        'See IPublisherRequest'
-        count = getattr(self, '_retry_count', 0)
-        self._retry_count = count + 1
-
-        request = self.__class__(
-            # Use the cache stream as the new input stream.
-            body_instream=self._body_instream.getCacheStream(),
-            environ=self._orig_env,
-            response=self.response.retry(),
-            )
-        # restore the interfaces
-        interface.alsoProvides(request, interface.providedBy(self))
-
-        request.setPublication(self.publication)
-        request._retry_count = self._retry_count
-        return request
-
-    def traverse(self, obj):
-        'See IPublisherRequest'
-
-        ob = super(HTTPRequest, self).traverse(obj)
-        if self._path_suffix:
-            self._traversal_stack = self._path_suffix
-            ob = super(HTTPRequest, self).traverse(ob)
-
-        return ob
-
-    def getHeader(self, name, default=None, literal=False):
-        'See IHTTPRequest'
-        environ = self._environ
-        if not literal:
-            name = name.replace('-', '_').upper()
-        val = environ.get(name, None)
-        if val is not None:
-            return val
-        if not name.startswith('HTTP_'):
-            name='HTTP_%s' % name
-        return environ.get(name, default)
-
-    headers = RequestDataProperty(HeaderGetter)
-
-    def getCookies(self):
-        'See IHTTPApplicationRequest'
-        return self._cookies
-
-    cookies = RequestDataProperty(CookieMapper)
-
-    def setPathSuffix(self, steps):
-        'See IHTTPRequest'
-        steps = list(steps)
-        steps.reverse()
-        self._path_suffix = steps
-
-    def _authUserPW(self):
-        'See IHTTPCredentials'
-        if self._auth and self._auth.lower().startswith('basic '):
-            encoded = self._auth.split(None, 1)[-1]
-            name, password = encoded.decode("base64").split(':', 1)
-            return name, password
-
-    def unauthorized(self, challenge):
-        'See IHTTPCredentials'
-        self._response.setHeader("WWW-Authenticate", challenge, True)
-        self._response.setStatus(401)
-
-    def setPrincipal(self, principal):
-        'See IPublicationRequest'
-        super(HTTPRequest, self).setPrincipal(principal)
-        logging_info = ILoggingInfo(principal, None)
-        if logging_info is None:
-            message = '-'
-        else:
-            message = logging_info.getLogMessage()
-        self.response.authUser = message
-
-    def _createResponse(self):
-        # Should be overridden by subclasses
-        return HTTPResponse()
-
-
-    def getURL(self, level=0, path_only=False):
-        names = self._app_names + self._traversed_names
-        if level:
-            if level > len(names):
-                raise IndexError(level)
-            names = names[:-level]
-        # See: http://www.ietf.org/rfc/rfc2718.txt, Section 2.2.5
-        names = [quote(name.encode("utf-8"), safe='/+@') for name in names]
-
-        if path_only:
-            if not names:
-                return '/'
-            return '/' + '/'.join(names)
-        else:
-            if not names:
-                return self._app_server
-            return "%s/%s" % (self._app_server, '/'.join(names))
-
-    def getApplicationURL(self, depth=0, path_only=False):
-        """See IHTTPApplicationRequest"""
-        if depth:
-            names = self._traversed_names
-            if depth > len(names):
-                raise IndexError(depth)
-            names = self._app_names + names[:depth]
-        else:
-            names = self._app_names
-
-        # See: http://www.ietf.org/rfc/rfc2718.txt, Section 2.2.5
-        names = [quote(name.encode("utf-8"), safe='/+@') for name in names]
-
-        if path_only:
-            return names and ('/' + '/'.join(names)) or '/'
-        else:
-            return (names and ("%s/%s" % (self._app_server, '/'.join(names)))
-                    or self._app_server)
-
-    def setApplicationServer(self, host, proto='http', port=None):
-        if port and str(port) != DEFAULT_PORTS.get(proto):
-            host = '%s:%s' % (host, port)
-        self._app_server = '%s://%s' % (proto, host)
-        event.notify(HTTPVirtualHostChangedEvent(self))
-
-    def shiftNameToApplication(self):
-        """Add the name being traversed to the application name
-
-        This is only allowed in the case where the name is the first name.
-
-        A Value error is raise if the shift can't be performed.
-        """
-        if len(self._traversed_names) == 1:
-            self._app_names.append(self._traversed_names.pop())
-            event.notify(HTTPVirtualHostChangedEvent(self))
-            return
-
-        raise ValueError("Can only shift leading traversal "
-                         "names to application names")
-
-    def setVirtualHostRoot(self, names=()):
-        del self._traversed_names[:]
-        self._vh_root = self._last_obj_traversed
-        self._app_names = list(names)
-        event.notify(HTTPVirtualHostChangedEvent(self))
-
-    def getVirtualHostRoot(self):
-        return self._vh_root
-
-    URL = RequestDataProperty(URLGetter)
-
-    def __repr__(self):
-        # Returns a *short* string.
-        return '<%s.%s instance URL=%s>' % (
-            self.__class__.__module__, self.__class__.__name__, str(self.URL))
-
-    def get(self, key, default=None):
-        'See Interface.Common.Mapping.IReadMapping'
-        marker = object()
-        result = self._cookies.get(key, marker)
-        if result is not marker:
-            return result
-
-        return super(HTTPRequest, self).get(key, default)
-
-    def keys(self):
-        'See Interface.Common.Mapping.IEnumerableMapping'
-        d = {}
-        d.update(self._environ)
-        d.update(self._cookies)
-        return d.keys()
-
-
-
-class HTTPResponse(BaseResponse):
-    interface.implements(IHTTPResponse, IHTTPApplicationResponse)
-
-    __slots__ = (
-        'authUser',             # Authenticated user string
-        # BBB: Remove for Zope 3.4.
-        '_header_output',       # Hook object to collaborate with a server
-                                # for header generation.
-        '_headers',
-        '_cookies',
-        '_status',              # The response status (usually an integer)
-        '_reason',              # The reason that goes with the status
-        '_status_set',          # Boolean: status explicitly set
-        '_charset',             # String: character set for the output
-        )
-
-
-    def __init__(self):
-        super(HTTPResponse, self).__init__()
-        self.reset()
-
-
-    def reset(self):
-        'See IResponse'
-        super(HTTPResponse, self).reset()
-        self._headers = {}
-        self._cookies = {}
-        self._status = 599
-        self._reason = 'No status set'
-        self._status_set = False
-        self._charset = None
-        self.authUser = '-'
-
-    def setStatus(self, status, reason=None):
-        'See IHTTPResponse'
-        if status is None:
-            status = 200
-        else:
-            if type(status) in StringTypes:
-                status = status.lower()
-            status = status_codes.get(status, 500)
-        self._status = status
-
-        if reason is None:
-            reason = status_reasons.get(status, "Unknown")
-
-        self._reason = reason
-        self._status_set = True
-
-
-    def getStatus(self):
-        'See IHTTPResponse'
-        return self._status
-
-    def getStatusString(self):
-        'See IHTTPResponse'
-        return '%i %s' % (self._status, self._reason)
-
-    def setHeader(self, name, value, literal=False):
-        'See IHTTPResponse'
-        name = str(name)
-        value = str(value)
-
-        if not literal:
-            name = name.lower()
-
-        self._headers[name] = [value]
-
-
-    def addHeader(self, name, value):
-        'See IHTTPResponse'
-        values = self._headers.setdefault(name, [])
-        values.append(value)
-
-
-    def getHeader(self, name, default=None, literal=False):
-        'See IHTTPResponse'
-        key = name.lower()
-        name = literal and name or key
-        result = self._headers.get(name)
-        if result:
-            return result[0]
-        return default
-
-
-    def getHeaders(self):
-        'See IHTTPResponse'
-        result = []
-        headers = self._headers
-
-        result.append(
-            ("X-Powered-By", "Zope (www.zope.org), Python (www.python.org)"))
-
-        for key, values in headers.items():
-            if key.lower() == key:
-                # only change non-literal header names
-                key = '-'.join([k.capitalize() for k in key.split('-')])
-            result.extend([(key, val) for val in values])
-
-        result.extend([tuple(cookie.split(': ', 1))
-                       for cookie in self._cookie_list()])
-
-        return result
-
-
-    def appendToCookie(self, name, value):
-        'See IHTTPResponse'
-        cookies = self._cookies
-        if name in cookies:
-            cookie = cookies[name]
-        else:
-            cookie = cookies[name] = {}
-        if 'value' in cookie:
-            cookie['value'] = '%s:%s' % (cookie['value'], value)
-        else:
-            cookie['value'] = value
-
-
-    def expireCookie(self, name, **kw):
-        'See IHTTPResponse'
-        dict = {'max_age':0, 'expires':'Wed, 31-Dec-97 23:59:59 GMT'}
-        for k, v in kw.items():
-            if v is not None:
-                dict[k] = v
-        cookies = self._cookies
-        if name in cookies:
-            # Cancel previous setCookie().
-            del cookies[name]
-        self.setCookie(name, 'deleted', **dict)
-
-
-    def setCookie(self, name, value, **kw):
-        'See IHTTPResponse'
-        cookies = self._cookies
-        cookie = cookies.setdefault(name, {})
-
-        for k, v in kw.items():
-            if v is not None:
-                cookie[k.lower()] = v
-
-        cookie['value'] = value
-
-
-    def getCookie(self, name, default=None):
-        'See IHTTPResponse'
-        return self._cookies.get(name, default)
-
-
-    def setResult(self, result):
-        'See IHTTPResponse'
-        if IResult.providedBy(result):
-            r = result
-        else:
-            r = component.queryMultiAdapter((result, self._request), IResult)
-            if r is None:
-                if isinstance(result, basestring):
-                    r = result
-                elif result is None:
-                    r = ''
-                else:
-                    raise TypeError(
-                        'The result should be None, a string, or adaptable to '
-                        'IResult.')
-            if isinstance(r, basestring):
-                r, headers = self._implicitResult(r)
-                self._headers.update(dict((k, [v]) for (k, v) in headers))
-                r = (r,) # chunking should be much larger than per character
-
-        self._result = r
-        if not self._status_set:
-            self.setStatus(200)
-
-
-    def consumeBody(self):
-        'See IHTTPResponse'
-        return ''.join(self._result)
-
-
-    def consumeBodyIter(self):
-        'See IHTTPResponse'
-        return self._result
-
-
-    def _implicitResult(self, body):
-        encoding = getCharsetUsingRequest(self._request) or 'utf-8'
-        content_type = self.getHeader('content-type')
-
-        if isinstance(body, unicode):
-            try:
-                if not content_type.startswith('text/'):
-                    raise ValueError(
-                        'Unicode results must have a text content type.')
-            except AttributeError:
-                    raise ValueError(
-                        'Unicode results must have a text content type.')
-
-
-            major, minor, params = contenttype.parse(content_type)
-
-            if 'charset' in params:
-                encoding = params['charset']
-            else:
-                content_type += ';charset=%s' %encoding
-
-            body = body.encode(encoding)
-
-        if content_type:
-            headers = [('content-type', content_type),
-                       ('content-length', str(len(body)))]
-        else:
-            headers = [('content-length', str(len(body)))]
-
-        return body, headers
-
-
-    def handleException(self, exc_info):
-        """
-        Calls self.setBody() with an error response.
-        """
-        t, v = exc_info[:2]
-        if isinstance(t, (ClassType, type)):    
-            if issubclass(t, Redirect):
-                self.redirect(v.getLocation())
-                return
-            title = tname = t.__name__
-        else:
-            title = tname = unicode(t)
-
-        # Throwing non-protocol-specific exceptions is a good way
-        # for apps to control the status code.
-        self.setStatus(tname)
-
-        body = self._html(title, "A server error occurred." )
-        self.setHeader("Content-Type", "text/html")
-        self.setResult(body)
-
-    def internalError(self):
-        'See IPublisherResponse'
-        self.setStatus(500, u"The engines can't take any more, Jim!")
-
-    def _html(self, title, content):
-        t = escape(title)
-        return (
-            u"<html><head><title>%s</title></head>\n"
-            u"<body><h2>%s</h2>\n"
-            u"%s\n"
-            u"</body></html>\n" %
-            (t, t, content)
-            )
-
-    def retry(self):
-        """
-        Returns a response object to be used in a retry attempt
-        """
-        return self.__class__()
-
-    def redirect(self, location, status=None):
-        """Causes a redirection without raising an error"""
-        if status is None:
-            # parse the HTTP version and set default accordingly
-            if (self._request.get("SERVER_PROTOCOL","HTTP/1.0") <
-                "HTTP/1.1"):
-                status=302
-            else:
-                status=303
-
-        self.setStatus(status)
-        self.setHeader('Location', location)
-        self.setResult(DirectResult(()))
-        return location
-
-    def _cookie_list(self):
-        try:
-            c = SimpleCookie()
-        except CookieError, e:
-            eventlog.warn(e)
-            return []
-        for name, attrs in self._cookies.items():
-            name = str(name)
-            c[name] = attrs['value'].encode(ENCODING)
-            for k,v in attrs.items():
-                if k == 'value':
-                    continue
-                if k == 'secure':
-                    if v:
-                        c[name]['secure'] = True
-                    continue
-                if k == 'max_age':
-                    k = 'max-age'
-                elif k == 'comment':
-                    # Encode rather than throw an exception
-                    v = quote(v.encode('utf-8'), safe="/?:@&+")
-                c[name][k] = str(v)
-        return str(c).splitlines()
-
-    def write(*_):
-        raise TypeError(
-            "The HTTP response write method is no longer supported. "
-            "See the file httpresults.txt in the zope.publisher package "
-            "for more information."
-            )
-
-def sort_charsets(x, y):
-    if y[1] == 'utf-8':
-        return 1
-    if x[1] == 'utf-8':
-        return -1
-    return cmp(y, x)
-
-
-class HTTPCharsets(object):
-    component.adapts(IHTTPRequest)
-    interface.implements(IUserPreferredCharsets)
-
-    def __init__(self, request):
-        self.request = request
-
-    def getPreferredCharsets(self):
-        '''See interface IUserPreferredCharsets'''
-        charsets = []
-        sawstar = sawiso88591 = 0
-        header_present = bool(self.request.get('HTTP_ACCEPT_CHARSET'))
-        for charset in self.request.get('HTTP_ACCEPT_CHARSET', '').split(','):
-            charset = charset.strip().lower()
-            if charset:
-                if ';' in charset:
-                    try:
-                        charset, quality = charset.split(';')
-                    except ValueError:
-                        continue
-                    if not quality.startswith('q='):
-                        # not a quality parameter
-                        quality = 1.0
-                    else:
-                        try:
-                            quality = float(quality[2:])
-                        except ValueError:
-                            continue
-                else:
-                    quality = 1.0
-                if quality == 0.0:
-                    continue
-                if charset == '*':
-                    sawstar = 1
-                if charset == 'iso-8859-1':
-                    sawiso88591 = 1
-                charsets.append((quality, charset))
-        # Quoting RFC 2616, $14.2: If no "*" is present in an Accept-Charset
-        # field, then all character sets not explicitly mentioned get a
-        # quality value of 0, except for ISO-8859-1, which gets a quality
-        # value of 1 if not explicitly mentioned.
-        # And quoting RFC 2616, $14.2: "If no Accept-Charset header is
-        # present, the default is that any character set is acceptable."
-        if not sawstar and not sawiso88591 and header_present:
-            charsets.append((1.0, 'iso-8859-1'))
-        # UTF-8 is **always** preferred over anything else.
-        # Reason: UTF-8 is not specific and can encode the entire unicode
-        # range , unlike many other encodings. Since Zope can easily use very
-        # different ranges, like providing a French-Chinese dictionary, it is
-        # always good to use UTF-8.
-        charsets.sort(sort_charsets)
-        charsets = [charset for quality, charset in charsets]
-        if sawstar and 'utf-8' not in charsets:
-            charsets.insert(0, 'utf-8')
-        return charsets
-
-
-def getCharsetUsingRequest(request):
-    'See IHTTPResponse'
-    envadapter = IUserPreferredCharsets(request, None)
-    if envadapter is None:
-        return
-
-    try:
-        charset = envadapter.getPreferredCharsets()[0]
-    except IndexError:
-        # Exception caused by empty list! This is okay though, since the
-        # browser just could have sent a '*', which means we can choose
-        # the encoding, which we do here now.
-        charset = 'utf-8'
-    return charset
-
-
-class DirectResult(object):
-    """A generic result object.
-
-    The result's body can be any iterable. It is the responsibility of the
-    application to specify all headers related to the content, such as the
-    content type and length.
-    """
-    interface.implements(IResult)
-
-    def __init__(self, body):
-        self.body = body
-
-    def __iter__(self):
-        return iter(self.body)

Copied: zope.publisher/tags/3.4.4/src/zope/publisher/http.py (from rev 89888, zope.publisher/branches/3.4/src/zope/publisher/http.py)
===================================================================
--- zope.publisher/tags/3.4.4/src/zope/publisher/http.py	                        (rev 0)
+++ zope.publisher/tags/3.4.4/src/zope/publisher/http.py	2008-08-17 22:40:09 UTC (rev 89935)
@@ -0,0 +1,1002 @@
+##############################################################################
+#
+# 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.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 Publisher
+
+$Id$
+"""
+import re, time, random
+from cStringIO import StringIO
+from urllib import quote, unquote, splitport
+from types import StringTypes, ClassType
+from cgi import escape
+from Cookie import SimpleCookie
+from Cookie import CookieError
+import logging
+from tempfile import TemporaryFile
+
+from zope import component, interface, event
+from zope.deprecation import deprecation
+
+from zope.publisher import contenttype
+from zope.publisher.interfaces.http import IHTTPCredentials
+from zope.publisher.interfaces.http import IHTTPRequest
+from zope.publisher.interfaces.http import IHTTPApplicationRequest
+from zope.publisher.interfaces.http import IHTTPPublisher
+from zope.publisher.interfaces.http import IHTTPVirtualHostChangedEvent
+from zope.publisher.interfaces.http import IResult
+
+from zope.publisher.interfaces import Redirect
+from zope.publisher.interfaces.http import IHTTPResponse
+from zope.publisher.interfaces.http import IHTTPApplicationResponse
+from zope.publisher.interfaces.logginginfo import ILoggingInfo
+from zope.i18n.interfaces import IUserPreferredCharsets
+from zope.i18n.interfaces import IUserPreferredLanguages
+from zope.i18n.locales import locales, LoadLocaleError
+
+from zope.publisher import contenttype
+from zope.publisher.base import BaseRequest, BaseResponse
+from zope.publisher.base import RequestDataProperty, RequestDataMapper
+from zope.publisher.base import RequestDataGetter
+
+
+# Default Encoding
+ENCODING = 'UTF-8'
+
+eventlog = logging.getLogger('eventlog')
+
+class CookieMapper(RequestDataMapper):
+    _mapname = '_cookies'
+
+class HeaderGetter(RequestDataGetter):
+    _gettrname = 'getHeader'
+
+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.
+    # It also makes sure PATH_INFO is a unicode string.
+    dict = {}
+    for key, val in env.items():
+        while key.startswith('REDIRECT_'):
+            key = key[9:]
+        dict[key] = val
+    if 'HTTP_CGI_AUTHORIZATION' in dict:
+        dict['HTTP_AUTHORIZATION'] = dict.pop('HTTP_CGI_AUTHORIZATION')
+    if 'PATH_INFO' in dict:
+        dict['PATH_INFO'] = dict['PATH_INFO'].decode('utf-8')
+    return dict
+
+class HTTPVirtualHostChangedEvent(object):
+    interface.implements(IHTTPVirtualHostChangedEvent)
+
+    request = None
+
+    def __init__(self, request):
+        self.request = request
+
+# Possible HTTP status responses
+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={}
+
+def init_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
+
+init_status_codes()
+
+
+class URLGetter(object):
+
+    __slots__ = "__request"
+
+    def __init__(self, request):
+        self.__request = request
+
+    def __str__(self):
+        return self.__request.getURL()
+
+    def __getitem__(self, name):
+        url = self.get(name, None)
+        if url is None:
+            raise KeyError(name)
+        return url
+
+    def get(self, name, default=None):
+        i = int(name)
+        try:
+            if i < 0:
+                i = -i
+                return self.__request.getURL(i)
+            else:
+                return self.__request.getApplicationURL(i)
+        except IndexError, v:
+            if v[0] == i:
+                return default
+            raise
+
+class HTTPInputStream(object):
+    """Special stream that supports caching the read data.
+
+    This is important, so that we can retry requests.
+    """
+
+    def __init__(self, stream, environment):
+        self.stream = stream
+        size = environment.get('CONTENT_LENGTH')
+        # There can be no size in the environment (None) or the size
+        # can be an empty string, in which case we treat it as absent.
+        if not size:
+            size = environment.get('HTTP_CONTENT_LENGTH')
+        if not size or int(size) < 65536:
+            self.cacheStream = StringIO()
+        else:
+            self.cacheStream = TemporaryFile()
+
+    def getCacheStream(self):
+        self.read()
+        self.cacheStream.seek(0)
+        return self.cacheStream
+
+    def read(self, size=-1):
+        data = self.stream.read(size)
+        self.cacheStream.write(data)
+        return data
+
+    def readline(self, size=None):
+        # Previous versions of Twisted did not support the ``size`` argument
+        # See http://twistedmatrix.com/trac/ticket/1451
+        #     https://bugs.launchpad.net/zope3/+bug/98284
+        data = self.stream.readline(size)
+        self.cacheStream.write(data)
+        return data
+
+    def readlines(self, hint=0):
+        data = self.stream.readlines(hint)
+        self.cacheStream.write(''.join(data))
+        return data
+        
+
+DEFAULT_PORTS = {'http': '80', 'https': '443'}
+STAGGER_RETRIES = True
+
+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.
+    """
+    interface.implements(IHTTPCredentials,
+                         IHTTPRequest,
+                         IHTTPApplicationRequest)
+
+    __slots__ = (
+        '__provides__',   # Allow request to directly provide interfaces
+        '_auth',          # The value of the HTTP_AUTHORIZATION header.
+        '_cookies',       # The request cookies
+        '_path_suffix',   # Extra traversal steps after normal traversal
+        '_retry_count',   # How many times the request has been retried
+        '_app_names',     # The application path as a sequence
+        '_app_server',    # The server path of the application url
+        '_orig_env',      # The original environment
+        '_endswithslash', # Does the given path end with /
+        'method',         # The upper-cased request method (REQUEST_METHOD)
+        '_locale',        # The locale for the request
+        '_vh_root',       # Object at the root of the virtual host
+        )
+
+    retry_max_count = 3    # How many times we're willing to retry
+
+    def __init__(self, body_instream, environ, response=None):
+
+        super(HTTPRequest, self).__init__(
+            HTTPInputStream(body_instream, environ), environ, response)
+
+        self._orig_env = environ
+        environ = sane_environment(environ)
+
+        if 'HTTP_AUTHORIZATION' in environ:
+            self._auth = environ['HTTP_AUTHORIZATION']
+            del environ['HTTP_AUTHORIZATION']
+        else:
+            self._auth = None
+
+        self.method = environ.get("REQUEST_METHOD", 'GET').upper()
+
+        self._environ = environ
+
+        self.__setupCookies()
+        self.__setupPath()
+        self.__setupURLBase()
+        self._vh_root = None
+        self.setupLocale()
+
+    def setupLocale(self):
+        envadapter = IUserPreferredLanguages(self, None)
+        if envadapter is None:
+            self._locale = None
+            return
+
+        langs = envadapter.getPreferredLanguages()
+        for httplang in langs:
+            parts = (httplang.split('-') + [None, None])[:3]
+            try:
+                self._locale = locales.getLocale(*parts)
+                return
+            except LoadLocaleError:
+                # Just try the next combination
+                pass
+        else:
+            # No combination gave us an existing locale, so use the default,
+            # which is guaranteed to exist
+            self._locale = locales.getLocale(None, None, None)
+
+    def _getLocale(self):
+        return self._locale
+    locale = property(_getLocale)
+
+    def __setupURLBase(self):
+        get_env = self._environ.get
+        # Get base info first. This isn't likely to cause
+        # errors and might be useful to error handlers.
+        script = get_env('SCRIPT_NAME', '').strip()
+
+        # _script and the other _names are meant for URL construction
+        self._app_names = filter(None, script.split('/'))
+
+        # get server URL and store it too, since we are already looking it up
+        server_url = get_env('SERVER_URL', None)
+        if server_url is not None:
+            self._app_server = server_url = server_url.strip()
+        else:
+            server_url = self.__deduceServerURL()
+
+        if server_url.endswith('/'):
+            server_url = server_url[:-1]
+
+        # strip off leading /'s of script
+        while script.startswith('/'):
+            script = script[1:]
+
+        self._app_server = server_url
+
+    def __deduceServerURL(self):
+        environ = self._environ
+
+        if (environ.get('HTTPS', '').lower() == "on" or
+            environ.get('SERVER_PORT_SECURE') == "1"):
+            protocol = 'https'
+        else:
+            protocol = 'http'
+
+        if environ.has_key('HTTP_HOST'):
+            host = environ['HTTP_HOST'].strip()
+            hostname, port = splitport(host)
+        else:
+            hostname = environ.get('SERVER_NAME', '').strip()
+            port = environ.get('SERVER_PORT', '')
+
+        if port and port != DEFAULT_PORTS.get(protocol):
+            host = hostname + ':' + port
+        else:
+            host = hostname
+
+        return '%s://%s' % (protocol, host)
+
+    def _parseCookies(self, text, result=None):
+        """Parse 'text' and return found cookies as 'result' dictionary."""
+
+        if result is None:
+            result = {}
+
+        # ignore cookies on a CookieError
+        try:
+            c = SimpleCookie(text)
+        except CookieError, e:
+            eventlog.warn(e)
+            return result
+
+        for k,v in c.items():
+            result[unicode(k, ENCODING)] = unicode(v.value, ENCODING)
+
+        return result
+
+    def __setupCookies(self):
+        # 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.
+        self._cookies = {}
+        cookie_header = self._environ.get('HTTP_COOKIE', None)
+        if cookie_header is not None:
+            self._parseCookies(cookie_header, self._cookies)
+
+    def __setupPath(self):
+        # PATH_INFO is unicode here, so setupPath_helper sets up the
+        # traversal stack correctly.
+        self._setupPath_helper("PATH_INFO")
+
+    def supportsRetry(self):
+        'See IPublisherRequest'
+        count = getattr(self, '_retry_count', 0)
+        if count < self.retry_max_count:
+            if STAGGER_RETRIES:
+                time.sleep(random.uniform(0, 2**(count)))
+            return True
+
+    def retry(self):
+        'See IPublisherRequest'
+        count = getattr(self, '_retry_count', 0)
+        self._retry_count = count + 1
+
+        request = self.__class__(
+            # Use the cache stream as the new input stream.
+            body_instream=self._body_instream.getCacheStream(),
+            environ=self._orig_env,
+            response=self.response.retry(),
+            )
+        # restore the interfaces
+        interface.alsoProvides(request, interface.providedBy(self))
+
+        request.setPublication(self.publication)
+        request._retry_count = self._retry_count
+        return request
+
+    def traverse(self, obj):
+        'See IPublisherRequest'
+
+        ob = super(HTTPRequest, self).traverse(obj)
+        if self._path_suffix:
+            self._traversal_stack = self._path_suffix
+            ob = super(HTTPRequest, self).traverse(ob)
+
+        return ob
+
+    def getHeader(self, name, default=None, literal=False):
+        'See IHTTPRequest'
+        environ = self._environ
+        if not literal:
+            name = name.replace('-', '_').upper()
+        val = environ.get(name, None)
+        if val is not None:
+            return val
+        if not name.startswith('HTTP_'):
+            name='HTTP_%s' % name
+        return environ.get(name, default)
+
+    headers = RequestDataProperty(HeaderGetter)
+
+    def getCookies(self):
+        'See IHTTPApplicationRequest'
+        return self._cookies
+
+    cookies = RequestDataProperty(CookieMapper)
+
+    def setPathSuffix(self, steps):
+        'See IHTTPRequest'
+        steps = list(steps)
+        steps.reverse()
+        self._path_suffix = steps
+
+    def _authUserPW(self):
+        'See IHTTPCredentials'
+        if self._auth and self._auth.lower().startswith('basic '):
+            encoded = self._auth.split(None, 1)[-1]
+            name, password = encoded.decode("base64").split(':', 1)
+            return name, password
+
+    def unauthorized(self, challenge):
+        'See IHTTPCredentials'
+        self._response.setHeader("WWW-Authenticate", challenge, True)
+        self._response.setStatus(401)
+
+    def setPrincipal(self, principal):
+        'See IPublicationRequest'
+        super(HTTPRequest, self).setPrincipal(principal)
+        logging_info = ILoggingInfo(principal, None)
+        if logging_info is None:
+            message = '-'
+        else:
+            message = logging_info.getLogMessage()
+        self.response.authUser = message
+
+    def _createResponse(self):
+        # Should be overridden by subclasses
+        return HTTPResponse()
+
+
+    def getURL(self, level=0, path_only=False):
+        names = self._app_names + self._traversed_names
+        if level:
+            if level > len(names):
+                raise IndexError(level)
+            names = names[:-level]
+        # See: http://www.ietf.org/rfc/rfc2718.txt, Section 2.2.5
+        names = [quote(name.encode("utf-8"), safe='/+@') for name in names]
+
+        if path_only:
+            if not names:
+                return '/'
+            return '/' + '/'.join(names)
+        else:
+            if not names:
+                return self._app_server
+            return "%s/%s" % (self._app_server, '/'.join(names))
+
+    def getApplicationURL(self, depth=0, path_only=False):
+        """See IHTTPApplicationRequest"""
+        if depth:
+            names = self._traversed_names
+            if depth > len(names):
+                raise IndexError(depth)
+            names = self._app_names + names[:depth]
+        else:
+            names = self._app_names
+
+        # See: http://www.ietf.org/rfc/rfc2718.txt, Section 2.2.5
+        names = [quote(name.encode("utf-8"), safe='/+@') for name in names]
+
+        if path_only:
+            return names and ('/' + '/'.join(names)) or '/'
+        else:
+            return (names and ("%s/%s" % (self._app_server, '/'.join(names)))
+                    or self._app_server)
+
+    def setApplicationServer(self, host, proto='http', port=None):
+        if port and str(port) != DEFAULT_PORTS.get(proto):
+            host = '%s:%s' % (host, port)
+        self._app_server = '%s://%s' % (proto, host)
+        event.notify(HTTPVirtualHostChangedEvent(self))
+
+    def shiftNameToApplication(self):
+        """Add the name being traversed to the application name
+
+        This is only allowed in the case where the name is the first name.
+
+        A Value error is raise if the shift can't be performed.
+        """
+        if len(self._traversed_names) == 1:
+            self._app_names.append(self._traversed_names.pop())
+            event.notify(HTTPVirtualHostChangedEvent(self))
+            return
+
+        raise ValueError("Can only shift leading traversal "
+                         "names to application names")
+
+    def setVirtualHostRoot(self, names=()):
+        del self._traversed_names[:]
+        self._vh_root = self._last_obj_traversed
+        self._app_names = list(names)
+        event.notify(HTTPVirtualHostChangedEvent(self))
+
+    def getVirtualHostRoot(self):
+        return self._vh_root
+
+    URL = RequestDataProperty(URLGetter)
+
+    def __repr__(self):
+        # Returns a *short* string.
+        return '<%s.%s instance URL=%s>' % (
+            self.__class__.__module__, self.__class__.__name__, str(self.URL))
+
+    def get(self, key, default=None):
+        'See Interface.Common.Mapping.IReadMapping'
+        marker = object()
+        result = self._cookies.get(key, marker)
+        if result is not marker:
+            return result
+
+        return super(HTTPRequest, self).get(key, default)
+
+    def keys(self):
+        'See Interface.Common.Mapping.IEnumerableMapping'
+        d = {}
+        d.update(self._environ)
+        d.update(self._cookies)
+        return d.keys()
+
+
+
+class HTTPResponse(BaseResponse):
+    interface.implements(IHTTPResponse, IHTTPApplicationResponse)
+
+    __slots__ = (
+        'authUser',             # Authenticated user string
+        # BBB: Remove for Zope 3.4.
+        '_header_output',       # Hook object to collaborate with a server
+                                # for header generation.
+        '_headers',
+        '_cookies',
+        '_status',              # The response status (usually an integer)
+        '_reason',              # The reason that goes with the status
+        '_status_set',          # Boolean: status explicitly set
+        '_charset',             # String: character set for the output
+        )
+
+
+    def __init__(self):
+        super(HTTPResponse, self).__init__()
+        self.reset()
+
+
+    def reset(self):
+        'See IResponse'
+        super(HTTPResponse, self).reset()
+        self._headers = {}
+        self._cookies = {}
+        self._status = 599
+        self._reason = 'No status set'
+        self._status_set = False
+        self._charset = None
+        self.authUser = '-'
+
+    def setStatus(self, status, reason=None):
+        'See IHTTPResponse'
+        if status is None:
+            status = 200
+        else:
+            if type(status) in StringTypes:
+                status = status.lower()
+            status = status_codes.get(status, 500)
+        self._status = status
+
+        if reason is None:
+            reason = status_reasons.get(status, "Unknown")
+
+        self._reason = reason
+        self._status_set = True
+
+
+    def getStatus(self):
+        'See IHTTPResponse'
+        return self._status
+
+    def getStatusString(self):
+        'See IHTTPResponse'
+        return '%i %s' % (self._status, self._reason)
+
+    def setHeader(self, name, value, literal=False):
+        'See IHTTPResponse'
+        name = str(name)
+        value = str(value)
+
+        if not literal:
+            name = name.lower()
+
+        self._headers[name] = [value]
+
+
+    def addHeader(self, name, value):
+        'See IHTTPResponse'
+        values = self._headers.setdefault(name, [])
+        values.append(value)
+
+
+    def getHeader(self, name, default=None, literal=False):
+        'See IHTTPResponse'
+        key = name.lower()
+        name = literal and name or key
+        result = self._headers.get(name)
+        if result:
+            return result[0]
+        return default
+
+
+    def getHeaders(self):
+        'See IHTTPResponse'
+        result = []
+        headers = self._headers
+
+        result.append(
+            ("X-Powered-By", "Zope (www.zope.org), Python (www.python.org)"))
+
+        for key, values in headers.items():
+            if key.lower() == key:
+                # only change non-literal header names
+                key = '-'.join([k.capitalize() for k in key.split('-')])
+            result.extend([(key, val) for val in values])
+
+        result.extend([tuple(cookie.split(': ', 1))
+                       for cookie in self._cookie_list()])
+
+        return result
+
+
+    def appendToCookie(self, name, value):
+        'See IHTTPResponse'
+        cookies = self._cookies
+        if name in cookies:
+            cookie = cookies[name]
+        else:
+            cookie = cookies[name] = {}
+        if 'value' in cookie:
+            cookie['value'] = '%s:%s' % (cookie['value'], value)
+        else:
+            cookie['value'] = value
+
+
+    def expireCookie(self, name, **kw):
+        'See IHTTPResponse'
+        dict = {'max_age':0, 'expires':'Wed, 31-Dec-97 23:59:59 GMT'}
+        for k, v in kw.items():
+            if v is not None:
+                dict[k] = v
+        cookies = self._cookies
+        if name in cookies:
+            # Cancel previous setCookie().
+            del cookies[name]
+        self.setCookie(name, 'deleted', **dict)
+
+
+    def setCookie(self, name, value, **kw):
+        'See IHTTPResponse'
+        cookies = self._cookies
+        cookie = cookies.setdefault(name, {})
+
+        for k, v in kw.items():
+            if v is not None:
+                cookie[k.lower()] = v
+
+        cookie['value'] = value
+
+
+    def getCookie(self, name, default=None):
+        'See IHTTPResponse'
+        return self._cookies.get(name, default)
+
+
+    def setResult(self, result):
+        'See IHTTPResponse'
+        if IResult.providedBy(result):
+            r = result
+        else:
+            r = component.queryMultiAdapter((result, self._request), IResult)
+            if r is None:
+                if isinstance(result, basestring):
+                    r = result
+                elif result is None:
+                    r = ''
+                else:
+                    raise TypeError(
+                        'The result should be None, a string, or adaptable to '
+                        'IResult.')
+            if isinstance(r, basestring):
+                r, headers = self._implicitResult(r)
+                self._headers.update(dict((k, [v]) for (k, v) in headers))
+                r = (r,) # chunking should be much larger than per character
+
+        self._result = r
+        if not self._status_set:
+            self.setStatus(200)
+
+
+    def consumeBody(self):
+        'See IHTTPResponse'
+        return ''.join(self._result)
+
+
+    def consumeBodyIter(self):
+        'See IHTTPResponse'
+        return self._result
+
+
+    def _implicitResult(self, body):
+        encoding = getCharsetUsingRequest(self._request) or 'utf-8'
+        content_type = self.getHeader('content-type')
+
+        if isinstance(body, unicode):
+            try:
+                if not content_type.startswith('text/'):
+                    raise ValueError(
+                        'Unicode results must have a text content type.')
+            except AttributeError:
+                    raise ValueError(
+                        'Unicode results must have a text content type.')
+
+
+            major, minor, params = contenttype.parse(content_type)
+
+            if 'charset' in params:
+                encoding = params['charset']
+            else:
+                content_type += ';charset=%s' %encoding
+
+            body = body.encode(encoding)
+
+        if content_type:
+            headers = [('content-type', content_type),
+                       ('content-length', str(len(body)))]
+        else:
+            headers = [('content-length', str(len(body)))]
+
+        return body, headers
+
+
+    def handleException(self, exc_info):
+        """
+        Calls self.setBody() with an error response.
+        """
+        t, v = exc_info[:2]
+        if isinstance(t, (ClassType, type)):    
+            if issubclass(t, Redirect):
+                self.redirect(v.getLocation())
+                return
+            title = tname = t.__name__
+        else:
+            title = tname = unicode(t)
+
+        # Throwing non-protocol-specific exceptions is a good way
+        # for apps to control the status code.
+        self.setStatus(tname)
+
+        body = self._html(title, "A server error occurred." )
+        self.setHeader("Content-Type", "text/html")
+        self.setResult(body)
+
+    def internalError(self):
+        'See IPublisherResponse'
+        self.setStatus(500, u"The engines can't take any more, Jim!")
+
+    def _html(self, title, content):
+        t = escape(title)
+        return (
+            u"<html><head><title>%s</title></head>\n"
+            u"<body><h2>%s</h2>\n"
+            u"%s\n"
+            u"</body></html>\n" %
+            (t, t, content)
+            )
+
+    def retry(self):
+        """
+        Returns a response object to be used in a retry attempt
+        """
+        return self.__class__()
+
+    def redirect(self, location, status=None):
+        """Causes a redirection without raising an error"""
+        if status is None:
+            # parse the HTTP version and set default accordingly
+            if (self._request.get("SERVER_PROTOCOL","HTTP/1.0") <
+                "HTTP/1.1"):
+                status=302
+            else:
+                status=303
+
+        self.setStatus(status)
+        self.setHeader('Location', location)
+        self.setResult(DirectResult(()))
+        return location
+
+    def _cookie_list(self):
+        try:
+            c = SimpleCookie()
+        except CookieError, e:
+            eventlog.warn(e)
+            return []
+        for name, attrs in self._cookies.items():
+            name = str(name)
+            c[name] = attrs['value'].encode(ENCODING)
+            for k,v in attrs.items():
+                if k == 'value':
+                    continue
+                if k == 'secure':
+                    if v:
+                        c[name]['secure'] = True
+                    continue
+                if k == 'max_age':
+                    k = 'max-age'
+                elif k == 'comment':
+                    # Encode rather than throw an exception
+                    v = quote(v.encode('utf-8'), safe="/?:@&+")
+                c[name][k] = str(v)
+        return str(c).splitlines()
+
+    def write(*_):
+        raise TypeError(
+            "The HTTP response write method is no longer supported. "
+            "See the file httpresults.txt in the zope.publisher package "
+            "for more information."
+            )
+
+def sort_charsets(x, y):
+    if y[1] == 'utf-8':
+        return 1
+    if x[1] == 'utf-8':
+        return -1
+    return cmp(y, x)
+
+
+class HTTPCharsets(object):
+    component.adapts(IHTTPRequest)
+    interface.implements(IUserPreferredCharsets)
+
+    def __init__(self, request):
+        self.request = request
+
+    def getPreferredCharsets(self):
+        '''See interface IUserPreferredCharsets'''
+        charsets = []
+        sawstar = sawiso88591 = 0
+        header_present = bool(self.request.get('HTTP_ACCEPT_CHARSET'))
+        for charset in self.request.get('HTTP_ACCEPT_CHARSET', '').split(','):
+            charset = charset.strip().lower()
+            if charset:
+                if ';' in charset:
+                    try:
+                        charset, quality = charset.split(';')
+                    except ValueError:
+                        continue
+                    if not quality.startswith('q='):
+                        # not a quality parameter
+                        quality = 1.0
+                    else:
+                        try:
+                            quality = float(quality[2:])
+                        except ValueError:
+                            continue
+                else:
+                    quality = 1.0
+                if quality == 0.0:
+                    continue
+                if charset == '*':
+                    sawstar = 1
+                if charset == 'iso-8859-1':
+                    sawiso88591 = 1
+                charsets.append((quality, charset))
+        # Quoting RFC 2616, $14.2: If no "*" is present in an Accept-Charset
+        # field, then all character sets not explicitly mentioned get a
+        # quality value of 0, except for ISO-8859-1, which gets a quality
+        # value of 1 if not explicitly mentioned.
+        # And quoting RFC 2616, $14.2: "If no Accept-Charset header is
+        # present, the default is that any character set is acceptable."
+        if not sawstar and not sawiso88591 and header_present:
+            charsets.append((1.0, 'iso-8859-1'))
+        # UTF-8 is **always** preferred over anything else.
+        # Reason: UTF-8 is not specific and can encode the entire unicode
+        # range , unlike many other encodings. Since Zope can easily use very
+        # different ranges, like providing a French-Chinese dictionary, it is
+        # always good to use UTF-8.
+        charsets.sort(sort_charsets)
+        charsets = [charset for quality, charset in charsets]
+        if sawstar and 'utf-8' not in charsets:
+            charsets.insert(0, 'utf-8')
+        return charsets
+
+
+def getCharsetUsingRequest(request):
+    'See IHTTPResponse'
+    envadapter = IUserPreferredCharsets(request, None)
+    if envadapter is None:
+        return
+
+    try:
+        charset = envadapter.getPreferredCharsets()[0]
+    except IndexError:
+        # Exception caused by empty list! This is okay though, since the
+        # browser just could have sent a '*', which means we can choose
+        # the encoding, which we do here now.
+        charset = 'utf-8'
+    return charset
+
+
+class DirectResult(object):
+    """A generic result object.
+
+    The result's body can be any iterable. It is the responsibility of the
+    application to specify all headers related to the content, such as the
+    content type and length.
+    """
+    interface.implements(IResult)
+
+    def __init__(self, body):
+        self.body = body
+
+    def __iter__(self):
+        return iter(self.body)



More information about the Checkins mailing list