[Checkins] SVN: zope.publisher/tags/3.9.0/ Tag 3.9.0

Dan Korostelev nadako at gmail.com
Thu Aug 27 10:51:30 EDT 2009


Log message for revision 103284:
  Tag 3.9.0

Changed:
  A   zope.publisher/tags/3.9.0/
  D   zope.publisher/tags/3.9.0/CHANGES.txt
  A   zope.publisher/tags/3.9.0/CHANGES.txt
  D   zope.publisher/tags/3.9.0/setup.py
  A   zope.publisher/tags/3.9.0/setup.py
  D   zope.publisher/tags/3.9.0/src/zope/publisher/browser.py
  A   zope.publisher/tags/3.9.0/src/zope/publisher/browser.py
  D   zope.publisher/tags/3.9.0/src/zope/publisher/configure.zcml
  A   zope.publisher/tags/3.9.0/src/zope/publisher/configure.zcml
  D   zope.publisher/tags/3.9.0/src/zope/publisher/defaultview.py
  A   zope.publisher/tags/3.9.0/src/zope/publisher/defaultview.py
  D   zope.publisher/tags/3.9.0/src/zope/publisher/interfaces/ftp.py
  A   zope.publisher/tags/3.9.0/src/zope/publisher/interfaces/ftp.py
  D   zope.publisher/tags/3.9.0/src/zope/publisher/interfaces/http.py
  A   zope.publisher/tags/3.9.0/src/zope/publisher/interfaces/http.py
  D   zope.publisher/tags/3.9.0/src/zope/publisher/interfaces/xmlrpc.py
  A   zope.publisher/tags/3.9.0/src/zope/publisher/interfaces/xmlrpc.py
  A   zope.publisher/tags/3.9.0/src/zope/publisher/meta.zcml
  D   zope.publisher/tags/3.9.0/src/zope/publisher/tests/test_browserlanguages.py
  A   zope.publisher/tags/3.9.0/src/zope/publisher/tests/test_browserlanguages.py
  D   zope.publisher/tags/3.9.0/src/zope/publisher/tests/test_browserrequest.py
  A   zope.publisher/tags/3.9.0/src/zope/publisher/tests/test_browserrequest.py
  A   zope.publisher/tags/3.9.0/src/zope/publisher/tests/test_zcml.py
  D   zope.publisher/tags/3.9.0/src/zope/publisher/xmlrpc.py
  A   zope.publisher/tags/3.9.0/src/zope/publisher/xmlrpc.py
  A   zope.publisher/tags/3.9.0/src/zope/publisher/zcml.py

-=-
Deleted: zope.publisher/tags/3.9.0/CHANGES.txt
===================================================================
--- zope.publisher/trunk/CHANGES.txt	2009-08-27 14:38:12 UTC (rev 103281)
+++ zope.publisher/tags/3.9.0/CHANGES.txt	2009-08-27 14:51:30 UTC (rev 103284)
@@ -1,264 +0,0 @@
-CHANGES
-=======
-
-3.8.1 (unreleased)
-------------------
-
-- Introduced ``IReRaiseException`` interface. If during publishing an
-  exception occurs and for this exception an adapter is available that
-  returns ``False`` on being called, the exception won't be reraised
-  by the publisher. This happens only if ``handle_errors`` parameter
-  of the ``publish()`` method is set to ``False``. Fixes problems when
-  acting in a WSGI pipeline with a debugger middleware enabled.
-
-  See https://bugs.launchpad.net/grok/+bug/332061 for details.
-
-- Fix #98471: Restrict redirects to current host. This causes a ValueError to
-  be raised in the case of redirecting to a different host. If this is
-  intentional, the parameter `trusted` can be given.
-
-- Moved dependency on zope.testing from install_requires to tests_require.
-
-- Removed behavior of doing a time.sleep in the supportsRetry http request.
-
-3.8.0 (2009-05-23)
-------------------
-
-- Moved IHTTPException, IMethodNotAllowed, and MethodNotAllowed from
-  zope.app.http to zope.publisher.interfaces.http, fixing dependency
-  cycles involving zope.app.http.
-
-- Moved the DefaultViewName API from zope.app.publisher.browser to
-  zope.publisher.defaultview, making it accessible to other packages
-  that need it.
-
-3.7.0 (2009-05-13)
-------------------
-
-- Move ``IView`` and ``IBrowserView`` interfaces into
-  ``zope.browser.interfaces``, leaving BBB imports.
-
-3.6.4 (2009-04-26)
-------------------
-
-- Added some BBB code to setDefaultSkin to allow IBrowserRequest's to continue
-  to work without configuring any special adapter for IDefaultSkin.
-
-- Move `getDefaultSkin` to the skinnable module next to the `setDefaultSkin`
-  method, leaving a BBB import in place. Mark `IDefaultBrowserLayer` as a
-  `IBrowserSkinType` in code instead of relying on the ZCML to be loaded.
-
-3.6.3 (2009-03-18)
-------------------
-
-- Mark HTTPRequest as IAttributeAnnotatable if ``zope.annotation`` is
-  available, this was previously done by ``zope.app.i18n``.
-
-- Register `IHTTPRequest` -> `IUserPreferredCharsets` adapter in ZCML
-  configuration. This was also previously done by ``zope.app.i18n``.
-
-3.6.2 (2009-03-14)
-------------------
-
-- Add an adapter from ``zope.security.interfaces.IPrincipal`` to
-  ``zope.publisher.interfaces.logginginfo.ILoggingInfo``. It was moved
-  from ``zope.app.security`` as a part of refactoring process. 
-
-- Add adapters from HTTP and FTP request to
-  ``zope.authentication.ILoginPassword`` interface. They are moved from
-  ``zope.app.security`` as a part of refactoring process. This change adds a
-  dependency on the ``zope.authentication`` package, but it's okay, since it's
-  a tiny contract definition-only package.
-
-  See http://mail.zope.org/pipermail/zope-dev/2009-March/035325.html for
-  reasoning.
-
-3.6.1 (2009-03-09)
-------------------
-
-- Fix: remove IBrowserRequest dependency in http implementation based on
-  condition for setDefaultSkin. Use ISkinnable instead of IBrowserRequest.
-
-3.6.0 (2009-03-08)
-------------------
-
-- Clean-up: Move skin related code from zope.publisher.interfaces.browser and
-  zope.publisher.browser to zope.publihser.interfaces and
-  zope.publisher.skinnable and provide BBB imports. See skinnable.txt for more
-  information.
-
-- Fix: ensure that we only apply skin interface in setDefaultSkin which also
-  provide IBrowserSkinType. This will ensure that we find a skin if the
-  applySkin method will lookup for a skin based on this type interface.
-
-- Fix: Make it possible to use adapters and not only interfaces as skins from
-  the adapter registry. Right now the defaultSkin directive registers simple
-  interfaces as skin adapters which will run into a TypeError if someone tries
-  to adapter such a skin adapter. Probably we should change the defaultSkin
-  directive and register real adapters instead of using the interfaces as fake
-  adapters where we expect adapter factories.
-
-- Feature: allow to use applySkin with different skin types using the optional
-  argument skinType which is by default set to IBrowserSkinType
-
-- Feature: implemented the default skin pattern within adapters. This allows
-  us to register default skins for other requests then only IBrowserRequest
-  using IDefaultSkin adapters.
-  
-  Note, ISkinnable and ISkinType and the skin implementation should be moved
-  out of the browser request modules. Packages like z3c.jsonrpc do not depend
-  on IBrowserRequest but they are skinnable.
-
-- Feature: added ISkinnable interface which allows us to implement the apply
-  skin pattern not only for IBrowserRequest
-
-- Fix: Don't cause warnings on Python 2.6
-
-- Fix: Make IBrowserPage inherit IBrowserView.
-
-- Move IView and IDefaultViewName from zope.component.interfaces to
-  zope.publisher.interfaces. Stop inheriting from deprecated (for years)
-  interfaces defined in zope.component.
-
-- Remove deprecated code.
-
-- Clean-up: Move "zope.testing" from extras to dependencies, per Zope
-  Framework policy.  Remove zope.app.testing as a dependency: tests run fine
-  without it.
-
-3.5.6 (2009-02-14)
-------------------
-
-Bugs fixed:
-
-* An untested code path that incorrectly attempted to construct a NotFound was
-  fixed, with a test.
-
-
-3.5.5 (2009-02-04)
-------------------
-
-* LP #322486: setStatus() now allows any int()-able status value.
-
-
-3.5.4 (2008-09-22)
-------------------
-
-Bugs fixed:
-
-* LP #98440: interfaces lost on retried request
-
-* LP #273296: dealing more nicely with malformed HTTP_ACCEPT_LANGUAGE headers
-  within getPreferredLanguages().
-
-* LP #253362: dealing more nicely with malformed HTTP_ACCEPT_CHARSET headers
-  within getPreferredCharsets().
-
-* LP #98284: Pass the ``size`` argument to readline, as the version of
-  twisted used in zope.app.twisted supports it.
-
-* Fix the LP #98284 fix: do not pass ``size`` argument of None that causes
-  cStringIO objects to barf with a TypeError.
-
-
-3.5.3 (2008-06-20)
-------------------
-
-Bugs fixed:
-
-* It turns out that some Web servers (Paste for example) do not send the EOF
-  character after the data has been transmitted and the read() of the cached
-  stream simply hangs if no expected content length has been specified.
-
-
-3.5.2 (2008-04-06)
-------------------
-
-Bugs fixed:
-
-* A previous fix to handle posting of non-form data broke handling of
-  form data with extra information in the content type, as in::
-
-    application/x-www-form-urlencoded; charset=UTF-8
-
-3.5.1 (2008-03-23)
-------------------
-
-Bugs fixed:
-
-* When posting non-form (and non-multipart) data, the request body was
-  consumed and discarded. This makes it impossible to deal with other
-  post types, like xml-rpc or json without resorting to overly complex
-  "request factory" contortions.
-
-* https://bugs.launchpad.net/zope2/+bug/143873
-
-  The zope.publisher.http.HTTPCharsets was confused by the Zope 2
-  publisher, which gives missleading information about which headers
-  it has.
-
-3.5.0 (2008-03-02)
-------------------
-
-Features added:
-
-* Added a PasteDeploy app_factory implementation.  This should make
-  it easier to integrate Zope 3 applications with PasteDeploy.  It
-  also makes it easier to control the publication used, giving far
-  greater control over application policies (e.g. whether or not to
-  use the ZODB).
-
-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.9.0/CHANGES.txt (from rev 103282, zope.publisher/trunk/CHANGES.txt)
===================================================================
--- zope.publisher/tags/3.9.0/CHANGES.txt	                        (rev 0)
+++ zope.publisher/tags/3.9.0/CHANGES.txt	2009-08-27 14:51:30 UTC (rev 103284)
@@ -0,0 +1,275 @@
+CHANGES
+=======
+
+3.9.0 (2009-08-27)
+------------------
+
+- Some parts of zope.app.publisher packages was moved into this package
+  during zope.app.publisher refactoring:
+  
+   * IModifiableUserPreferredLanguages adapter for requests
+   * browser:defaultView and browser:defaultSkin ZCML directives
+   * IHTTPView, IXMLRPCView and like interfaces
+   * security ZCML declarations for some of zope.publisher classes
+
+- Introduced ``IReRaiseException`` interface. If during publishing an
+  exception occurs and for this exception an adapter is available that
+  returns ``False`` on being called, the exception won't be reraised
+  by the publisher. This happens only if ``handle_errors`` parameter
+  of the ``publish()`` method is set to ``False``. Fixes problems when
+  acting in a WSGI pipeline with a debugger middleware enabled.
+
+  See https://bugs.launchpad.net/grok/+bug/332061 for details.
+
+- Fix #98471: Restrict redirects to current host. This causes a ValueError to
+  be raised in the case of redirecting to a different host. If this is
+  intentional, the parameter `trusted` can be given.
+
+- Moved dependency on zope.testing from install_requires to tests_require.
+
+- Removed behavior of doing a time.sleep in the supportsRetry http request.
+
+- Add a fix for Internet Explorer versions that upload files will full
+  filesystem paths as filenames.
+
+3.8.0 (2009-05-23)
+------------------
+
+- Moved IHTTPException, IMethodNotAllowed, and MethodNotAllowed from
+  zope.app.http to zope.publisher.interfaces.http, fixing dependency
+  cycles involving zope.app.http.
+
+- Moved the DefaultViewName API from zope.app.publisher.browser to
+  zope.publisher.defaultview, making it accessible to other packages
+  that need it.
+
+3.7.0 (2009-05-13)
+------------------
+
+- Move ``IView`` and ``IBrowserView`` interfaces into
+  ``zope.browser.interfaces``, leaving BBB imports.
+
+3.6.4 (2009-04-26)
+------------------
+
+- Added some BBB code to setDefaultSkin to allow IBrowserRequest's to continue
+  to work without configuring any special adapter for IDefaultSkin.
+
+- Move `getDefaultSkin` to the skinnable module next to the `setDefaultSkin`
+  method, leaving a BBB import in place. Mark `IDefaultBrowserLayer` as a
+  `IBrowserSkinType` in code instead of relying on the ZCML to be loaded.
+
+3.6.3 (2009-03-18)
+------------------
+
+- Mark HTTPRequest as IAttributeAnnotatable if ``zope.annotation`` is
+  available, this was previously done by ``zope.app.i18n``.
+
+- Register `IHTTPRequest` -> `IUserPreferredCharsets` adapter in ZCML
+  configuration. This was also previously done by ``zope.app.i18n``.
+
+3.6.2 (2009-03-14)
+------------------
+
+- Add an adapter from ``zope.security.interfaces.IPrincipal`` to
+  ``zope.publisher.interfaces.logginginfo.ILoggingInfo``. It was moved
+  from ``zope.app.security`` as a part of refactoring process. 
+
+- Add adapters from HTTP and FTP request to
+  ``zope.authentication.ILoginPassword`` interface. They are moved from
+  ``zope.app.security`` as a part of refactoring process. This change adds a
+  dependency on the ``zope.authentication`` package, but it's okay, since it's
+  a tiny contract definition-only package.
+
+  See http://mail.zope.org/pipermail/zope-dev/2009-March/035325.html for
+  reasoning.
+
+3.6.1 (2009-03-09)
+------------------
+
+- Fix: remove IBrowserRequest dependency in http implementation based on
+  condition for setDefaultSkin. Use ISkinnable instead of IBrowserRequest.
+
+3.6.0 (2009-03-08)
+------------------
+
+- Clean-up: Move skin related code from zope.publisher.interfaces.browser and
+  zope.publisher.browser to zope.publihser.interfaces and
+  zope.publisher.skinnable and provide BBB imports. See skinnable.txt for more
+  information.
+
+- Fix: ensure that we only apply skin interface in setDefaultSkin which also
+  provide IBrowserSkinType. This will ensure that we find a skin if the
+  applySkin method will lookup for a skin based on this type interface.
+
+- Fix: Make it possible to use adapters and not only interfaces as skins from
+  the adapter registry. Right now the defaultSkin directive registers simple
+  interfaces as skin adapters which will run into a TypeError if someone tries
+  to adapter such a skin adapter. Probably we should change the defaultSkin
+  directive and register real adapters instead of using the interfaces as fake
+  adapters where we expect adapter factories.
+
+- Feature: allow to use applySkin with different skin types using the optional
+  argument skinType which is by default set to IBrowserSkinType
+
+- Feature: implemented the default skin pattern within adapters. This allows
+  us to register default skins for other requests then only IBrowserRequest
+  using IDefaultSkin adapters.
+  
+  Note, ISkinnable and ISkinType and the skin implementation should be moved
+  out of the browser request modules. Packages like z3c.jsonrpc do not depend
+  on IBrowserRequest but they are skinnable.
+
+- Feature: added ISkinnable interface which allows us to implement the apply
+  skin pattern not only for IBrowserRequest
+
+- Fix: Don't cause warnings on Python 2.6
+
+- Fix: Make IBrowserPage inherit IBrowserView.
+
+- Move IView and IDefaultViewName from zope.component.interfaces to
+  zope.publisher.interfaces. Stop inheriting from deprecated (for years)
+  interfaces defined in zope.component.
+
+- Remove deprecated code.
+
+- Clean-up: Move "zope.testing" from extras to dependencies, per Zope
+  Framework policy.  Remove zope.app.testing as a dependency: tests run fine
+  without it.
+
+3.5.6 (2009-02-14)
+------------------
+
+Bugs fixed:
+
+* An untested code path that incorrectly attempted to construct a NotFound was
+  fixed, with a test.
+
+
+3.5.5 (2009-02-04)
+------------------
+
+* LP #322486: setStatus() now allows any int()-able status value.
+
+
+3.5.4 (2008-09-22)
+------------------
+
+Bugs fixed:
+
+* LP #98440: interfaces lost on retried request
+
+* LP #273296: dealing more nicely with malformed HTTP_ACCEPT_LANGUAGE headers
+  within getPreferredLanguages().
+
+* LP #253362: dealing more nicely with malformed HTTP_ACCEPT_CHARSET headers
+  within getPreferredCharsets().
+
+* LP #98284: Pass the ``size`` argument to readline, as the version of
+  twisted used in zope.app.twisted supports it.
+
+* Fix the LP #98284 fix: do not pass ``size`` argument of None that causes
+  cStringIO objects to barf with a TypeError.
+
+
+3.5.3 (2008-06-20)
+------------------
+
+Bugs fixed:
+
+* It turns out that some Web servers (Paste for example) do not send the EOF
+  character after the data has been transmitted and the read() of the cached
+  stream simply hangs if no expected content length has been specified.
+
+
+3.5.2 (2008-04-06)
+------------------
+
+Bugs fixed:
+
+* A previous fix to handle posting of non-form data broke handling of
+  form data with extra information in the content type, as in::
+
+    application/x-www-form-urlencoded; charset=UTF-8
+
+3.5.1 (2008-03-23)
+------------------
+
+Bugs fixed:
+
+* When posting non-form (and non-multipart) data, the request body was
+  consumed and discarded. This makes it impossible to deal with other
+  post types, like xml-rpc or json without resorting to overly complex
+  "request factory" contortions.
+
+* https://bugs.launchpad.net/zope2/+bug/143873
+
+  The zope.publisher.http.HTTPCharsets was confused by the Zope 2
+  publisher, which gives missleading information about which headers
+  it has.
+
+3.5.0 (2008-03-02)
+------------------
+
+Features added:
+
+* Added a PasteDeploy app_factory implementation.  This should make
+  it easier to integrate Zope 3 applications with PasteDeploy.  It
+  also makes it easier to control the publication used, giving far
+  greater control over application policies (e.g. whether or not to
+  use the ZODB).
+
+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.9.0/setup.py
===================================================================
--- zope.publisher/trunk/setup.py	2009-08-27 14:38:12 UTC (rev 103281)
+++ zope.publisher/tags/3.9.0/setup.py	2009-08-27 14:51:30 UTC (rev 103284)
@@ -1,58 +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.
-#
-##############################################################################
-
-from setuptools import setup, find_packages
-
-entry_points = """
-[paste.app_factory]
-main = zope.publisher.paste:Application
-
-[zope.publisher.publication_factory]
-sample = zope.publisher.tests.test_paste:SamplePublication
-"""
-
-setup(name='zope.publisher',
-      version='3.8.1dev',
-      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=(open('README.txt').read()
-                        + '\n\n'
-                        + open('CHANGES.txt').read()),
-
-      entry_points=entry_points,
-
-      packages=find_packages('src'),
-      package_dir={'': 'src'},
-
-      namespace_packages=['zope',],
-      tests_require=['zope.testing'],
-      install_requires=['setuptools',
-                        'zope.authentication',
-                        'zope.browser',
-                        'zope.component',
-                        'zope.event',
-                        'zope.exceptions',
-                        'zope.i18n',
-                        'zope.interface',
-                        'zope.location',
-                        'zope.proxy',
-                        'zope.security',
-                       ],
-      include_package_data=True,
-
-      zip_safe=False,
-      )

Copied: zope.publisher/tags/3.9.0/setup.py (from rev 103282, zope.publisher/trunk/setup.py)
===================================================================
--- zope.publisher/tags/3.9.0/setup.py	                        (rev 0)
+++ zope.publisher/tags/3.9.0/setup.py	2009-08-27 14:51:30 UTC (rev 103284)
@@ -0,0 +1,59 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+
+from setuptools import setup, find_packages
+
+entry_points = """
+[paste.app_factory]
+main = zope.publisher.paste:Application
+
+[zope.publisher.publication_factory]
+sample = zope.publisher.tests.test_paste:SamplePublication
+"""
+
+setup(name='zope.publisher',
+      version='3.9.0',
+      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=(open('README.txt').read()
+                        + '\n\n'
+                        + open('CHANGES.txt').read()),
+
+      entry_points=entry_points,
+
+      packages=find_packages('src'),
+      package_dir={'': 'src'},
+
+      namespace_packages=['zope',],
+      tests_require=['zope.testing'],
+      install_requires=['setuptools',
+                        'zope.authentication',
+                        'zope.browser',
+                        'zope.component',
+                        'zope.configuration',
+                        'zope.event',
+                        'zope.exceptions',
+                        'zope.i18n',
+                        'zope.interface',
+                        'zope.location',
+                        'zope.proxy',
+                        'zope.security',
+                       ],
+      include_package_data=True,
+
+      zip_safe=False,
+      )

Deleted: zope.publisher/tags/3.9.0/src/zope/publisher/browser.py
===================================================================
--- zope.publisher/trunk/src/zope/publisher/browser.py	2009-08-27 14:38:12 UTC (rev 103281)
+++ zope.publisher/tags/3.9.0/src/zope/publisher/browser.py	2009-08-27 14:51:30 UTC (rev 103284)
@@ -1,916 +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.
-#
-##############################################################################
-"""Browser-specific Publisher classes
-
-Here we define the specific 'BrowserRequest' and 'BrowserResponse' class. The
-big improvement of the 'BrowserRequest' to 'HTTPRequest' is that is can handle
-HTML form data and convert them into a Python-native format. Even file data is
-packaged into a nice, Python-friendly 'FileUpload' object.
-
-$Id$
-"""
-__docformat__ = 'restructuredtext'
-
-import re
-from types import ListType, TupleType, StringType
-from cgi import FieldStorage
-import tempfile
-
-import zope.component
-import zope.interface
-from zope.interface import implements, directlyProvides
-from zope.i18n.interfaces import IUserPreferredLanguages
-from zope.i18n.interfaces import IUserPreferredCharsets
-from zope.location import Location
-
-from zope.publisher.interfaces import NotFound
-from zope.publisher.interfaces import IDefaultSkin
-from zope.publisher.interfaces.browser import IBrowserRequest
-from zope.publisher.interfaces.browser import IDefaultBrowserLayer
-from zope.publisher.interfaces.browser import IBrowserApplicationRequest
-from zope.publisher.interfaces.browser import IBrowserView
-from zope.publisher.interfaces.browser import IBrowserPage
-from zope.publisher.interfaces.browser import IBrowserSkinType
-from zope.publisher.interfaces.http import IHTTPRequest
-from zope.publisher.http import HTTPRequest, HTTPResponse
-
-# BBB imports, this compoennts get moved from this module
-from zope.publisher.interfaces import ISkinType #BBB import
-from zope.publisher.interfaces import ISkinChangedEvent #BBB import
-from zope.publisher.skinnable import getDefaultSkin #BBB import
-from zope.publisher.skinnable import setDefaultSkin #BBB import
-from zope.publisher.skinnable import applySkin #BBB import
-from zope.publisher.skinnable import SkinChangedEvent #BBB import
-
-
-__ArrayTypes = (ListType, TupleType)
-
-start_of_header_search=re.compile('(<head[^>]*>)', re.I).search
-base_re_search=re.compile('(<base.*?>)',re.I).search
-isRelative = re.compile("[-_.!~*a-zA-z0-9'()@&=+$,]+(/|$)").match
-newlines = re.compile('\r\n|\n\r|\r')
-
-def is_text_html(content_type):
-    return content_type.startswith('text/html')
-
-# Flag Constants
-SEQUENCE = 1
-DEFAULT = 2
-RECORD = 4
-RECORDS = 8
-REC = RECORD | RECORDS
-CONVERTED = 32
-DEFAULTABLE_METHODS = 'GET', 'POST', 'HEAD'
-
-
-def field2string(v):
-    if hasattr(v, 'read'):
-        return v.read()
-    return str(v)
-
-def field2text(v, nl=newlines):
-    return nl.sub("\n", field2string(v))
-
-def field2required(v):
-    v = field2string(v)
-    if not v.strip():
-        raise ValueError('No input for required field<p>')
-    return v
-
-def field2int(v):
-    if isinstance(v, __ArrayTypes):
-        return map(field2int, v)
-    v = field2string(v)
-    if not v:
-        raise ValueError('Empty entry when <strong>integer</strong> expected')
-    try:
-        return int(v)
-    except ValueError:
-        raise ValueError("An integer was expected in the value '%s'" % v)
-
-def field2float(v):
-    if isinstance(v, __ArrayTypes):
-        return map(field2float, v)
-    v = field2string(v)
-    if not v:
-        raise ValueError(
-            'Empty entry when <strong>floating-point number</strong> expected')
-    try:
-        return float(v)
-    except ValueError:
-        raise ValueError(
-                "A floating-point number was expected in the value '%s'" % v)
-
-def field2long(v):
-    if isinstance(v, __ArrayTypes):
-        return map(field2long, v)
-    v = field2string(v)
-
-    # handle trailing 'L' if present.
-    if v and v[-1].upper() == 'L':
-        v = v[:-1]
-    if not v:
-        raise ValueError('Empty entry when <strong>integer</strong> expected')
-    try:
-        return long(v)
-    except ValueError:
-        raise ValueError("A long integer was expected in the value '%s'" % v)
-
-def field2tokens(v):
-    return field2string(v).split()
-
-def field2lines(v):
-    if isinstance(v, __ArrayTypes):
-        return [str(item) for item in v]
-    return field2text(v).splitlines()
-
-def field2boolean(v):
-    return bool(v)
-
-type_converters = {
-    'float':    field2float,
-    'int':      field2int,
-    'long':     field2long,
-    'string':   field2string,
-    'required': field2required,
-    'tokens':   field2tokens,
-    'lines':    field2lines,
-    'text':     field2text,
-    'boolean':  field2boolean,
-    }
-
-get_converter = type_converters.get
-
-def registerTypeConverter(field_type, converter, replace=False):
-    """Add a custom type converter to the registry.
-
-    o If 'replace' is not true, raise a KeyError if a converter is
-      already registered for 'field_type'.
-    """
-    existing = type_converters.get(field_type)
-
-    if existing is not None and not replace:
-        raise KeyError('Existing converter for field_type: %s' % field_type)
-
-    type_converters[field_type] = converter
-
-
-isCGI_NAME = {
-    # These fields are placed in request.environ instead of request.form.
-    'SERVER_SOFTWARE' : 1,
-    'SERVER_NAME' : 1,
-    'GATEWAY_INTERFACE' : 1,
-    'SERVER_PROTOCOL' : 1,
-    'SERVER_PORT' : 1,
-    'REQUEST_METHOD' : 1,
-    'PATH_INFO' : 1,
-    'PATH_TRANSLATED' : 1,
-    'SCRIPT_NAME' : 1,
-    'QUERY_STRING' : 1,
-    'REMOTE_HOST' : 1,
-    'REMOTE_ADDR' : 1,
-    'AUTH_TYPE' : 1,
-    'REMOTE_USER' : 1,
-    'REMOTE_IDENT' : 1,
-    'CONTENT_TYPE' : 1,
-    'CONTENT_LENGTH' : 1,
-    'SERVER_URL': 1,
-    }.has_key
-
-hide_key={
-    'HTTP_AUTHORIZATION':1,
-    'HTTP_CGI_AUTHORIZATION': 1,
-    }.has_key
-
-class Record(object):
-
-    _attrs = frozenset(('get', 'keys', 'items', 'values', 'copy',
-                       'has_key', '__contains__'))
-
-    def __getattr__(self, key, default=None):
-        if key in self._attrs:
-            return getattr(self.__dict__, key)
-        raise AttributeError(key)
-
-    def __getitem__(self, key):
-        return self.__dict__[key]
-
-    def __str__(self):
-        items = self.__dict__.items()
-        items.sort()
-        return "{" + ", ".join(["%s: %s" % item for item in items]) + "}"
-
-    def __repr__(self):
-        items = self.__dict__.items()
-        items.sort()
-        return ("{"
-            + ", ".join(["%s: %s" % (key, repr(value))
-            for key, value in items]) + "}")
-
-_get_or_head = 'GET', 'HEAD'
-class BrowserRequest(HTTPRequest):
-    implements(IBrowserRequest, IBrowserApplicationRequest)
-
-    __slots__ = (
-        '__provides__', # Allow request to directly provide interfaces
-        'form', # Form data
-        'charsets', # helper attribute
-        '__meth',
-        '__tuple_items',
-        '__defaults',
-        '__annotations__',
-        )
-
-    # Set this to True in a subclass to redirect GET requests when the
-    # effective and actual URLs differ.
-    use_redirect = False
-
-    def __init__(self, body_instream, environ, response=None):
-        self.form = {}
-        self.charsets = None
-        super(BrowserRequest, self).__init__(body_instream, environ, response)
-
-
-    def _createResponse(self):
-        return BrowserResponse()
-
-    def _decode(self, text):
-        """Try to decode the text using one of the available charsets."""
-        if self.charsets is None:
-            envadapter = IUserPreferredCharsets(self)
-            self.charsets = envadapter.getPreferredCharsets() or ['utf-8']
-        for charset in self.charsets:
-            try:
-                text = unicode(text, charset)
-                break
-            except UnicodeError:
-                pass
-        return text
-
-    def processInputs(self):
-        'See IPublisherRequest'
-
-        if self.method not in _get_or_head:
-            # Process self.form if not a GET request.
-            fp = self._body_instream
-            if self.method == 'POST':
-                content_type = self._environ.get('CONTENT_TYPE')
-                if content_type and not (
-                    content_type.startswith('application/x-www-form-urlencoded')
-                    or
-                    content_type.startswith('multipart/')
-                    ):
-                    # for non-multi and non-form content types, FieldStorage
-                    # consumes the body and we have no good place to put it.
-                    # So we just won't call FieldStorage. :)
-                    return
-        else:
-            fp = None
-
-        # If 'QUERY_STRING' is not present in self._environ
-        # FieldStorage will try to get it from sys.argv[1]
-        # which is not what we need.
-        if 'QUERY_STRING' not in self._environ:
-            self._environ['QUERY_STRING'] = ''
-
-        fs = ZopeFieldStorage(fp=fp, environ=self._environ,
-                              keep_blank_values=1)
-
-        fslist = getattr(fs, 'list', None)
-        if fslist is not None:
-            self.__meth = None
-            self.__tuple_items = {}
-            self.__defaults = {}
-
-            # process all entries in the field storage (form)
-            for item in fslist:
-                self.__processItem(item)
-
-            if self.__defaults:
-                self.__insertDefaults()
-
-            if self.__tuple_items:
-                self.__convertToTuples()
-
-            if self.__meth:
-                self.setPathSuffix((self.__meth,))
-
-    _typeFormat = re.compile('([a-zA-Z][a-zA-Z0-9_]+|\\.[xy])$')
-
-    def __processItem(self, item):
-        """Process item in the field storage."""
-
-        # Check whether this field is a file upload object
-        # Note: A field exists for files, even if no filename was
-        # passed in and no data was uploaded. Therefore we can only
-        # tell by the empty filename that no upload was made.
-        key = item.name
-        if (hasattr(item, 'file') and hasattr(item, 'filename')
-            and hasattr(item,'headers')):
-            if (item.file and
-                (item.filename is not None and item.filename != ''
-                 # RFC 1867 says that all fields get a content-type.
-                 # or 'content-type' in map(lower, item.headers.keys())
-                 )):
-                item = FileUpload(item)
-            else:
-                item = item.value
-
-        flags = 0
-        converter = None
-
-        # Loop through the different types and set
-        # the appropriate flags
-        # Syntax: var_name:type_name
-
-        # We'll search from the back to the front.
-        # We'll do the search in two steps.  First, we'll
-        # do a string search, and then we'll check it with
-        # a re search.
-
-        while key:
-            pos = key.rfind(":")
-            if pos < 0:
-                break
-            match = self._typeFormat.match(key, pos + 1)
-            if match is None:
-                break
-
-            key, type_name = key[:pos], key[pos + 1:]
-
-            # find the right type converter
-            c = get_converter(type_name, None)
-
-            if c is not None:
-                converter = c
-                flags |= CONVERTED
-            elif type_name == 'list':
-                flags |= SEQUENCE
-            elif type_name == 'tuple':
-                self.__tuple_items[key] = 1
-                flags |= SEQUENCE
-            elif (type_name == 'method' or type_name == 'action'):
-                if key:
-                    self.__meth = key
-                else:
-                    self.__meth = item
-            elif (type_name == 'default_method'
-                    or type_name == 'default_action') and not self.__meth:
-                if key:
-                    self.__meth = key
-                else:
-                    self.__meth = item
-            elif type_name == 'default':
-                flags |= DEFAULT
-            elif type_name == 'record':
-                flags |= RECORD
-            elif type_name == 'records':
-                flags |= RECORDS
-            elif type_name == 'ignore_empty' and not item:
-                # skip over empty fields
-                return
-
-        # Make it unicode if not None
-        if key is not None:
-            key = self._decode(key)
-
-        if type(item) == StringType:
-            item = self._decode(item)
-
-        if flags:
-            self.__setItemWithType(key, item, flags, converter)
-        else:
-            self.__setItemWithoutType(key, item)
-
-    def __setItemWithoutType(self, key, item):
-        """Set item value without explicit type."""
-        form = self.form
-        if key not in form:
-            form[key] = item
-        else:
-            found = form[key]
-            if isinstance(found, list):
-                found.append(item)
-            else:
-                form[key] = [found, item]
-
-    def __setItemWithType(self, key, item, flags, converter):
-        """Set item value with explicit type."""
-        #Split the key and its attribute
-        if flags & REC:
-            key, attr = self.__splitKey(key)
-
-        # defer conversion
-        if flags & CONVERTED:
-            try:
-                item = converter(item)
-            except:
-                if item or flags & DEFAULT or key not in self.__defaults:
-                    raise
-                item = self.__defaults[key]
-                if flags & RECORD:
-                    item = getattr(item, attr)
-                elif flags & RECORDS:
-                    item = getattr(item[-1], attr)
-
-        # Determine which dictionary to use
-        if flags & DEFAULT:
-            form = self.__defaults
-        else:
-            form = self.form
-
-        # Insert in dictionary
-        if key not in form:
-            if flags & SEQUENCE:
-                item = [item]
-            if flags & RECORD:
-                r = form[key] = Record()
-                setattr(r, attr, item)
-            elif flags & RECORDS:
-                r = Record()
-                setattr(r, attr, item)
-                form[key] = [r]
-            else:
-                form[key] = item
-        else:
-            r = form[key]
-            if flags & RECORD:
-                if not flags & SEQUENCE:
-                    setattr(r, attr, item)
-                else:
-                    if not hasattr(r, attr):
-                        setattr(r, attr, [item])
-                    else:
-                        getattr(r, attr).append(item)
-            elif flags & RECORDS:
-                last = r[-1]
-                if not hasattr(last, attr):
-                    if flags & SEQUENCE:
-                        item = [item]
-                    setattr(last, attr, item)
-                else:
-                    if flags & SEQUENCE:
-                        getattr(last, attr).append(item)
-                    else:
-                        new = Record()
-                        setattr(new, attr, item)
-                        r.append(new)
-            else:
-                if isinstance(r, list):
-                    r.append(item)
-                else:
-                    form[key] = [r, item]
-
-    def __splitKey(self, key):
-        """Split the key and its attribute."""
-        i = key.rfind(".")
-        if i >= 0:
-            return key[:i], key[i + 1:]
-        return key, ""
-
-    def __convertToTuples(self):
-        """Convert form values to tuples."""
-        form = self.form
-
-        for key in self.__tuple_items:
-            if key in form:
-                form[key] = tuple(form[key])
-            else:
-                k, attr = self.__splitKey(key)
-
-                # remove any type_names in the attr
-                i = attr.find(":")
-                if i >= 0:
-                    attr = attr[:i]
-
-                if k in form:
-                    item = form[k]
-                    if isinstance(item, Record):
-                        if hasattr(item, attr):
-                            setattr(item, attr, tuple(getattr(item, attr)))
-                    else:
-                        for v in item:
-                            if hasattr(v, attr):
-                                setattr(v, attr, tuple(getattr(v, attr)))
-
-    def __insertDefaults(self):
-        """Insert defaults into form dictionary."""
-        form = self.form
-
-        for keys, values in self.__defaults.iteritems():
-            if not keys in form:
-                form[keys] = values
-            else:
-                item = form[keys]
-                if isinstance(values, Record):
-                    for k, v in values.items():
-                        if not hasattr(item, k):
-                            setattr(item, k, v)
-                elif isinstance(values, list):
-                    for val in values:
-                        if isinstance(val, Record):
-                            for k, v in val.items():
-                                for r in item:
-                                    if not hasattr(r, k):
-                                        setattr(r, k, v)
-                        elif not val in item:
-                            item.append(val)
-
-    def traverse(self, obj):
-        'See IPublisherRequest'
-
-        ob = super(BrowserRequest, self).traverse(obj)
-        method = self.method
-
-        base_needed = 0
-        if self._path_suffix:
-            # We had a :method variable, so we need to set the base,
-            # but we don't look for default documents any more.
-            base_needed = 1
-            redirect = 0
-        elif method in DEFAULTABLE_METHODS:
-            # We need to check for default documents
-            publication = self.publication
-
-            nsteps = 0
-            ob, add_steps = publication.getDefaultTraversal(self, ob)
-            while add_steps:
-                nsteps += len(add_steps)
-                add_steps = list(add_steps)
-                add_steps.reverse()
-                self.setTraversalStack(add_steps)
-                ob = super(BrowserRequest, self).traverse(ob)
-                ob, add_steps = publication.getDefaultTraversal(self, ob)
-
-            if nsteps != self._endswithslash:
-                base_needed = 1
-                redirect = self.use_redirect and method == 'GET'
-
-
-        if base_needed:
-            url = self.getURL()
-            response = self.response
-            if redirect:
-                response.redirect(url)
-                return ''
-            elif not response.getBase():
-                response.setBase(url)
-
-        return ob
-
-    def keys(self):
-        'See Interface.Common.Mapping.IEnumerableMapping'
-        d = {}
-        d.update(self._environ)
-        d.update(self._cookies)
-        d.update(self.form)
-        return d.keys()
-
-
-    def get(self, key, default=None):
-        'See Interface.Common.Mapping.IReadMapping'
-        marker = object()
-        result = self.form.get(key, marker)
-        if result is not marker:
-            return result
-
-        return super(BrowserRequest, self).get(key, default)
-
-class ZopeFieldStorage(FieldStorage):
-
-    def make_file(self, binary=None):
-        return tempfile.NamedTemporaryFile('w+b')
-
-
-class FileUpload(object):
-    '''File upload objects
-
-    File upload objects are used to represent file-uploaded data.
-
-    File upload objects can be used just like files.
-
-    In addition, they have a 'headers' attribute that is a dictionary
-    containing the file-upload headers, and a 'filename' attribute
-    containing the name of the uploaded file.
-    '''
-
-    def __init__(self, aFieldStorage):
-
-        file = aFieldStorage.file
-        if hasattr(file, '__methods__'):
-            methods = file.__methods__
-        else:
-            methods = ['close', 'fileno', 'flush', 'isatty',
-                'read', 'readline', 'readlines', 'seek',
-                'tell', 'truncate', 'write', 'writelines',
-                'name']
-
-        d = self.__dict__
-        for m in methods:
-            if hasattr(file,m):
-                d[m] = getattr(file,m)
-
-        self.headers = aFieldStorage.headers
-        self.filename = unicode(aFieldStorage.filename, 'UTF-8')
-
-class RedirectingBrowserRequest(BrowserRequest):
-    """Browser requests that redirect when the actual and effective URLs differ
-    """
-
-    use_redirect = True
-
-class TestRequest(BrowserRequest):
-    """Browser request with a constructor convenient for testing
-    """
-
-    def __init__(self, body_instream=None, environ=None, form=None,
-                 skin=None, **kw):
-
-        _testEnv =  {
-            'SERVER_URL':         'http://127.0.0.1',
-            'HTTP_HOST':          '127.0.0.1',
-            'CONTENT_LENGTH':     '0',
-            'GATEWAY_INTERFACE':  'TestFooInterface/1.0',
-            }
-
-        if environ is not None:
-            _testEnv.update(environ)
-
-        if kw:
-            _testEnv.update(kw)
-        if body_instream is None:
-            from StringIO import StringIO
-            body_instream = StringIO('')
-
-        super(TestRequest, self).__init__(body_instream, _testEnv)
-        if form:
-            self.form.update(form)
-
-        # Setup locale object
-        langs = BrowserLanguages(self).getPreferredLanguages()
-        from zope.i18n.locales import locales
-        if not langs or langs[0] == '':
-            self._locale = locales.getLocale(None, None, None)
-        else:
-            parts = (langs[0].split('-') + [None, None])[:3]
-            self._locale = locales.getLocale(*parts)
-
-        if skin is not None:
-            directlyProvides(self, skin)
-        else:
-            directlyProvides(self, IDefaultBrowserLayer)
-
-
-
-class BrowserResponse(HTTPResponse):
-    """Browser response
-    """
-
-    __slots__ = (
-        '_base', # The base href
-        )
-
-    def _implicitResult(self, body):
-        content_type = self.getHeader('content-type')
-        if content_type is None:
-            if isHTML(body):
-                content_type = 'text/html'
-            else:
-                content_type = 'text/plain'
-            self.setHeader('x-content-type-warning', 'guessed from content')
-            self.setHeader('content-type', content_type)
-
-        body, headers = super(BrowserResponse, self)._implicitResult(body)
-        body = self.__insertBase(body)
-        # Update the Content-Length header to account for the inserted
-        # <base> tag.
-        headers = [
-            (name, value) for name, value in headers
-            if name != 'content-length'
-            ]
-        headers.append(('content-length', str(len(body))))
-        return body, headers
-
-
-    def __insertBase(self, body):
-        # Only insert a base tag if content appears to be html.
-        content_type = self.getHeader('content-type', '')
-        if content_type and not is_text_html(content_type):
-            return body
-
-        if self.getBase():
-            if body:
-                match = start_of_header_search(body)
-                if match is not None:
-                    index = match.start(0) + len(match.group(0))
-                    ibase = base_re_search(body)
-                    if ibase is None:
-                        # Make sure the base URL is not a unicode string.
-                        base = str(self.getBase())
-                        body = ('%s\n<base href="%s" />\n%s' %
-                                (body[:index], base, body[index:]))
-        return body
-
-    def getBase(self):
-        return getattr(self, '_base', '')
-
-    def setBase(self, base):
-        self._base = base
-
-    def redirect(self, location, status=None, trusted=False):
-        base = getattr(self, '_base', '')
-        if base and isRelative(str(location)):
-            l = base.rfind('/')
-            if l >= 0:
-                base = base[:l+1]
-            else:
-                base += '/'
-            location = base + location
-
-        # TODO: HTTP redirects must provide an absolute location, see
-        #       http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.30
-        #       So, what if location is relative and base is unknown?  Uncomment
-        #       the following and you'll see that it actually happens.
-        #
-        # if isRelative(str(location)):
-        #     raise AssertionError('Cannot determine absolute location')
-
-        return super(BrowserResponse, self).redirect(location, status, trusted)
-
-    def reset(self):
-        super(BrowserResponse, self).reset()
-        self._base = ''
-
-def isHTML(str):
-     """Try to determine whether str is HTML or not."""
-     s = str.lstrip().lower()
-     if s.startswith('<!doctype html'):
-         return True
-     if s.startswith('<html') and (s[5:6] in ' >'):
-         return True
-     if s.startswith('<!--'):
-         idx = s.find('<html')
-         return idx > 0 and (s[idx+5:idx+6] in ' >')
-     else:
-         return False
-
-def normalize_lang(lang):
-    lang = lang.strip().lower()
-    lang = lang.replace('_', '-')
-    lang = lang.replace(' ', '')
-    return lang
-
-class BrowserLanguages(object):
-    zope.component.adapts(IHTTPRequest)
-    implements(IUserPreferredLanguages)
-
-    def __init__(self, request):
-        self.request = request
-
-    def getPreferredLanguages(self):
-        '''See interface IUserPreferredLanguages'''
-        accept_langs = self.request.get('HTTP_ACCEPT_LANGUAGE', '').split(',')
-
-        # Normalize lang strings
-        accept_langs = [normalize_lang(l) for l in accept_langs]
-        # Then filter out empty ones
-        accept_langs = [l for l in accept_langs if l]
-
-        accepts = []
-        for index, lang in enumerate(accept_langs):
-            l = lang.split(';', 2)
-
-            # If not supplied, quality defaults to 1...
-            quality = 1.0
-
-            if len(l) == 2:
-                q = l[1]
-                if q.startswith('q='):
-                    q = q.split('=', 2)[1]
-                    try:
-                        quality = float(q)
-                    except ValueError:
-                        # malformed quality value, skip it.
-                        continue
-
-            if quality == 1.0:
-                # ... but we use 1.9 - 0.001 * position to
-                # keep the ordering between all items with
-                # 1.0 quality, which may include items with no quality
-                # defined, and items with quality defined as 1.
-                quality = 1.9 - (0.001 * index)
-
-            accepts.append((quality, l[0]))
-
-        # Filter langs with q=0, which means
-        # unwanted lang according to the spec
-        # See: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
-        accepts = [acc for acc in accepts if acc[0]]
-
-        accepts.sort()
-        accepts.reverse()
-
-        return [lang for quality, lang in accepts]
-
-class BrowserView(Location):
-    """Browser View.
-
-    >>> view = BrowserView("context", "request")
-    >>> view.context
-    'context'
-    >>> view.request
-    'request'
-
-    >>> view.__parent__
-    'context'
-    >>> view.__parent__ = "parent"
-    >>> view.__parent__
-    'parent'
-    """
-    implements(IBrowserView)
-
-    def __init__(self, context, request):
-        self.context = context
-        self.request = request
-
-    def __getParent(self):
-        return getattr(self, '_parent', self.context)
-
-    def __setParent(self, parent):
-        self._parent = parent
-
-    __parent__ = property(__getParent, __setParent)
-
-class BrowserPage(BrowserView):
-    """Browser page
-
-    To create a page, which is an object that is published as a page,
-    you need to provide an object that:
-
-    - has a __call__ method and that
-
-    - provides IBrowserPublisher, and
-
-    - if ZPT is going to be used, then your object should also provide
-      request and context attributes.
-
-    The BrowserPage base class provides a standard constructor and a
-    simple implementation of IBrowserPublisher:
-
-      >>> class MyPage(BrowserPage):
-      ...     pass
-
-      >>> request = TestRequest()
-      >>> context = object()
-      >>> page = MyPage(context, request)
-
-      >>> from zope.publisher.interfaces.browser import IBrowserPublisher
-      >>> IBrowserPublisher.providedBy(page)
-      True
-
-      >>> page.browserDefault(request) == (page, ())
-      True
-
-      >>> page.publishTraverse(request, 'bob') # doctest: +ELLIPSIS
-      Traceback (most recent call last):
-      ...
-      NotFound: Object: <zope.publisher.browser.MyPage object at ...>, name: 'bob'
-
-      >>> page.request is request
-      True
-
-      >>> page.context is context
-      True
-
-    But it doesn't supply a __call__ method:
-
-      >>> page()
-      Traceback (most recent call last):
-        ...
-      NotImplementedError: Subclasses should override __call__ to provide a response body
-
-    It is the subclass' responsibility to do that.
-
-    """
-    implements(IBrowserPage)
-
-    def browserDefault(self, request):
-        return self, ()
-
-    def publishTraverse(self, request, name):
-        raise NotFound(self, name, request)
-
-    def __call__(self, *args, **kw):
-        raise NotImplementedError("Subclasses should override __call__ to "
-                                  "provide a response body")

Copied: zope.publisher/tags/3.9.0/src/zope/publisher/browser.py (from rev 103282, zope.publisher/trunk/src/zope/publisher/browser.py)
===================================================================
--- zope.publisher/tags/3.9.0/src/zope/publisher/browser.py	                        (rev 0)
+++ zope.publisher/tags/3.9.0/src/zope/publisher/browser.py	2009-08-27 14:51:30 UTC (rev 103284)
@@ -0,0 +1,963 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""Browser-specific Publisher classes
+
+Here we define the specific 'BrowserRequest' and 'BrowserResponse' class. The
+big improvement of the 'BrowserRequest' to 'HTTPRequest' is that is can handle
+HTML form data and convert them into a Python-native format. Even file data is
+packaged into a nice, Python-friendly 'FileUpload' object.
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import re
+from types import ListType, TupleType, StringType
+from cgi import FieldStorage
+import tempfile
+
+import zope.component
+import zope.interface
+from zope.interface import implements, directlyProvides
+from zope.i18n.interfaces import IUserPreferredLanguages
+from zope.i18n.interfaces import IUserPreferredCharsets
+from zope.i18n.interfaces import IModifiableUserPreferredLanguages
+from zope.location import Location
+
+from zope.publisher.interfaces import NotFound
+from zope.publisher.interfaces import IDefaultSkin
+from zope.publisher.interfaces.browser import IBrowserRequest
+from zope.publisher.interfaces.browser import IDefaultBrowserLayer
+from zope.publisher.interfaces.browser import IBrowserApplicationRequest
+from zope.publisher.interfaces.browser import IBrowserView
+from zope.publisher.interfaces.browser import IBrowserPage
+from zope.publisher.interfaces.browser import IBrowserSkinType
+from zope.publisher.interfaces.http import IHTTPRequest
+from zope.publisher.http import HTTPRequest, HTTPResponse
+
+# BBB imports, this compoennts get moved from this module
+from zope.publisher.interfaces import ISkinType #BBB import
+from zope.publisher.interfaces import ISkinChangedEvent #BBB import
+from zope.publisher.skinnable import getDefaultSkin #BBB import
+from zope.publisher.skinnable import setDefaultSkin #BBB import
+from zope.publisher.skinnable import applySkin #BBB import
+from zope.publisher.skinnable import SkinChangedEvent #BBB import
+
+
+__ArrayTypes = (ListType, TupleType)
+
+start_of_header_search=re.compile('(<head[^>]*>)', re.I).search
+base_re_search=re.compile('(<base.*?>)',re.I).search
+isRelative = re.compile("[-_.!~*a-zA-z0-9'()@&=+$,]+(/|$)").match
+newlines = re.compile('\r\n|\n\r|\r')
+
+def is_text_html(content_type):
+    return content_type.startswith('text/html')
+
+# Flag Constants
+SEQUENCE = 1
+DEFAULT = 2
+RECORD = 4
+RECORDS = 8
+REC = RECORD | RECORDS
+CONVERTED = 32
+DEFAULTABLE_METHODS = 'GET', 'POST', 'HEAD'
+
+
+def field2string(v):
+    if hasattr(v, 'read'):
+        return v.read()
+    return str(v)
+
+def field2text(v, nl=newlines):
+    return nl.sub("\n", field2string(v))
+
+def field2required(v):
+    v = field2string(v)
+    if not v.strip():
+        raise ValueError('No input for required field<p>')
+    return v
+
+def field2int(v):
+    if isinstance(v, __ArrayTypes):
+        return map(field2int, v)
+    v = field2string(v)
+    if not v:
+        raise ValueError('Empty entry when <strong>integer</strong> expected')
+    try:
+        return int(v)
+    except ValueError:
+        raise ValueError("An integer was expected in the value '%s'" % v)
+
+def field2float(v):
+    if isinstance(v, __ArrayTypes):
+        return map(field2float, v)
+    v = field2string(v)
+    if not v:
+        raise ValueError(
+            'Empty entry when <strong>floating-point number</strong> expected')
+    try:
+        return float(v)
+    except ValueError:
+        raise ValueError(
+                "A floating-point number was expected in the value '%s'" % v)
+
+def field2long(v):
+    if isinstance(v, __ArrayTypes):
+        return map(field2long, v)
+    v = field2string(v)
+
+    # handle trailing 'L' if present.
+    if v and v[-1].upper() == 'L':
+        v = v[:-1]
+    if not v:
+        raise ValueError('Empty entry when <strong>integer</strong> expected')
+    try:
+        return long(v)
+    except ValueError:
+        raise ValueError("A long integer was expected in the value '%s'" % v)
+
+def field2tokens(v):
+    return field2string(v).split()
+
+def field2lines(v):
+    if isinstance(v, __ArrayTypes):
+        return [str(item) for item in v]
+    return field2text(v).splitlines()
+
+def field2boolean(v):
+    return bool(v)
+
+type_converters = {
+    'float':    field2float,
+    'int':      field2int,
+    'long':     field2long,
+    'string':   field2string,
+    'required': field2required,
+    'tokens':   field2tokens,
+    'lines':    field2lines,
+    'text':     field2text,
+    'boolean':  field2boolean,
+    }
+
+get_converter = type_converters.get
+
+def registerTypeConverter(field_type, converter, replace=False):
+    """Add a custom type converter to the registry.
+
+    o If 'replace' is not true, raise a KeyError if a converter is
+      already registered for 'field_type'.
+    """
+    existing = type_converters.get(field_type)
+
+    if existing is not None and not replace:
+        raise KeyError('Existing converter for field_type: %s' % field_type)
+
+    type_converters[field_type] = converter
+
+
+isCGI_NAME = {
+    # These fields are placed in request.environ instead of request.form.
+    'SERVER_SOFTWARE' : 1,
+    'SERVER_NAME' : 1,
+    'GATEWAY_INTERFACE' : 1,
+    'SERVER_PROTOCOL' : 1,
+    'SERVER_PORT' : 1,
+    'REQUEST_METHOD' : 1,
+    'PATH_INFO' : 1,
+    'PATH_TRANSLATED' : 1,
+    'SCRIPT_NAME' : 1,
+    'QUERY_STRING' : 1,
+    'REMOTE_HOST' : 1,
+    'REMOTE_ADDR' : 1,
+    'AUTH_TYPE' : 1,
+    'REMOTE_USER' : 1,
+    'REMOTE_IDENT' : 1,
+    'CONTENT_TYPE' : 1,
+    'CONTENT_LENGTH' : 1,
+    'SERVER_URL': 1,
+    }.has_key
+
+hide_key={
+    'HTTP_AUTHORIZATION':1,
+    'HTTP_CGI_AUTHORIZATION': 1,
+    }.has_key
+
+class Record(object):
+
+    _attrs = frozenset(('get', 'keys', 'items', 'values', 'copy',
+                       'has_key', '__contains__'))
+
+    def __getattr__(self, key, default=None):
+        if key in self._attrs:
+            return getattr(self.__dict__, key)
+        raise AttributeError(key)
+
+    def __getitem__(self, key):
+        return self.__dict__[key]
+
+    def __str__(self):
+        items = self.__dict__.items()
+        items.sort()
+        return "{" + ", ".join(["%s: %s" % item for item in items]) + "}"
+
+    def __repr__(self):
+        items = self.__dict__.items()
+        items.sort()
+        return ("{"
+            + ", ".join(["%s: %s" % (key, repr(value))
+            for key, value in items]) + "}")
+
+_get_or_head = 'GET', 'HEAD'
+class BrowserRequest(HTTPRequest):
+    implements(IBrowserRequest, IBrowserApplicationRequest)
+
+    __slots__ = (
+        '__provides__', # Allow request to directly provide interfaces
+        'form', # Form data
+        'charsets', # helper attribute
+        '__meth',
+        '__tuple_items',
+        '__defaults',
+        '__annotations__',
+        )
+
+    # Set this to True in a subclass to redirect GET requests when the
+    # effective and actual URLs differ.
+    use_redirect = False
+
+    def __init__(self, body_instream, environ, response=None):
+        self.form = {}
+        self.charsets = None
+        super(BrowserRequest, self).__init__(body_instream, environ, response)
+
+
+    def _createResponse(self):
+        return BrowserResponse()
+
+    def _decode(self, text):
+        """Try to decode the text using one of the available charsets."""
+        if self.charsets is None:
+            envadapter = IUserPreferredCharsets(self)
+            self.charsets = envadapter.getPreferredCharsets() or ['utf-8']
+        for charset in self.charsets:
+            try:
+                text = unicode(text, charset)
+                break
+            except UnicodeError:
+                pass
+        return text
+
+    def processInputs(self):
+        'See IPublisherRequest'
+
+        if self.method not in _get_or_head:
+            # Process self.form if not a GET request.
+            fp = self._body_instream
+            if self.method == 'POST':
+                content_type = self._environ.get('CONTENT_TYPE')
+                if content_type and not (
+                    content_type.startswith('application/x-www-form-urlencoded')
+                    or
+                    content_type.startswith('multipart/')
+                    ):
+                    # for non-multi and non-form content types, FieldStorage
+                    # consumes the body and we have no good place to put it.
+                    # So we just won't call FieldStorage. :)
+                    return
+        else:
+            fp = None
+
+        # If 'QUERY_STRING' is not present in self._environ
+        # FieldStorage will try to get it from sys.argv[1]
+        # which is not what we need.
+        if 'QUERY_STRING' not in self._environ:
+            self._environ['QUERY_STRING'] = ''
+
+        fs = ZopeFieldStorage(fp=fp, environ=self._environ,
+                              keep_blank_values=1)
+
+        fslist = getattr(fs, 'list', None)
+        if fslist is not None:
+            self.__meth = None
+            self.__tuple_items = {}
+            self.__defaults = {}
+
+            # process all entries in the field storage (form)
+            for item in fslist:
+                self.__processItem(item)
+
+            if self.__defaults:
+                self.__insertDefaults()
+
+            if self.__tuple_items:
+                self.__convertToTuples()
+
+            if self.__meth:
+                self.setPathSuffix((self.__meth,))
+
+    _typeFormat = re.compile('([a-zA-Z][a-zA-Z0-9_]+|\\.[xy])$')
+
+    def __processItem(self, item):
+        """Process item in the field storage."""
+
+        # Check whether this field is a file upload object
+        # Note: A field exists for files, even if no filename was
+        # passed in and no data was uploaded. Therefore we can only
+        # tell by the empty filename that no upload was made.
+        key = item.name
+        if (hasattr(item, 'file') and hasattr(item, 'filename')
+            and hasattr(item,'headers')):
+            if (item.file and
+                (item.filename is not None and item.filename != ''
+                 # RFC 1867 says that all fields get a content-type.
+                 # or 'content-type' in map(lower, item.headers.keys())
+                 )):
+                item = FileUpload(item)
+            else:
+                item = item.value
+
+        flags = 0
+        converter = None
+
+        # Loop through the different types and set
+        # the appropriate flags
+        # Syntax: var_name:type_name
+
+        # We'll search from the back to the front.
+        # We'll do the search in two steps.  First, we'll
+        # do a string search, and then we'll check it with
+        # a re search.
+
+        while key:
+            pos = key.rfind(":")
+            if pos < 0:
+                break
+            match = self._typeFormat.match(key, pos + 1)
+            if match is None:
+                break
+
+            key, type_name = key[:pos], key[pos + 1:]
+
+            # find the right type converter
+            c = get_converter(type_name, None)
+
+            if c is not None:
+                converter = c
+                flags |= CONVERTED
+            elif type_name == 'list':
+                flags |= SEQUENCE
+            elif type_name == 'tuple':
+                self.__tuple_items[key] = 1
+                flags |= SEQUENCE
+            elif (type_name == 'method' or type_name == 'action'):
+                if key:
+                    self.__meth = key
+                else:
+                    self.__meth = item
+            elif (type_name == 'default_method'
+                    or type_name == 'default_action') and not self.__meth:
+                if key:
+                    self.__meth = key
+                else:
+                    self.__meth = item
+            elif type_name == 'default':
+                flags |= DEFAULT
+            elif type_name == 'record':
+                flags |= RECORD
+            elif type_name == 'records':
+                flags |= RECORDS
+            elif type_name == 'ignore_empty' and not item:
+                # skip over empty fields
+                return
+
+        # Make it unicode if not None
+        if key is not None:
+            key = self._decode(key)
+
+        if type(item) == StringType:
+            item = self._decode(item)
+
+        if flags:
+            self.__setItemWithType(key, item, flags, converter)
+        else:
+            self.__setItemWithoutType(key, item)
+
+    def __setItemWithoutType(self, key, item):
+        """Set item value without explicit type."""
+        form = self.form
+        if key not in form:
+            form[key] = item
+        else:
+            found = form[key]
+            if isinstance(found, list):
+                found.append(item)
+            else:
+                form[key] = [found, item]
+
+    def __setItemWithType(self, key, item, flags, converter):
+        """Set item value with explicit type."""
+        #Split the key and its attribute
+        if flags & REC:
+            key, attr = self.__splitKey(key)
+
+        # defer conversion
+        if flags & CONVERTED:
+            try:
+                item = converter(item)
+            except:
+                if item or flags & DEFAULT or key not in self.__defaults:
+                    raise
+                item = self.__defaults[key]
+                if flags & RECORD:
+                    item = getattr(item, attr)
+                elif flags & RECORDS:
+                    item = getattr(item[-1], attr)
+
+        # Determine which dictionary to use
+        if flags & DEFAULT:
+            form = self.__defaults
+        else:
+            form = self.form
+
+        # Insert in dictionary
+        if key not in form:
+            if flags & SEQUENCE:
+                item = [item]
+            if flags & RECORD:
+                r = form[key] = Record()
+                setattr(r, attr, item)
+            elif flags & RECORDS:
+                r = Record()
+                setattr(r, attr, item)
+                form[key] = [r]
+            else:
+                form[key] = item
+        else:
+            r = form[key]
+            if flags & RECORD:
+                if not flags & SEQUENCE:
+                    setattr(r, attr, item)
+                else:
+                    if not hasattr(r, attr):
+                        setattr(r, attr, [item])
+                    else:
+                        getattr(r, attr).append(item)
+            elif flags & RECORDS:
+                last = r[-1]
+                if not hasattr(last, attr):
+                    if flags & SEQUENCE:
+                        item = [item]
+                    setattr(last, attr, item)
+                else:
+                    if flags & SEQUENCE:
+                        getattr(last, attr).append(item)
+                    else:
+                        new = Record()
+                        setattr(new, attr, item)
+                        r.append(new)
+            else:
+                if isinstance(r, list):
+                    r.append(item)
+                else:
+                    form[key] = [r, item]
+
+    def __splitKey(self, key):
+        """Split the key and its attribute."""
+        i = key.rfind(".")
+        if i >= 0:
+            return key[:i], key[i + 1:]
+        return key, ""
+
+    def __convertToTuples(self):
+        """Convert form values to tuples."""
+        form = self.form
+
+        for key in self.__tuple_items:
+            if key in form:
+                form[key] = tuple(form[key])
+            else:
+                k, attr = self.__splitKey(key)
+
+                # remove any type_names in the attr
+                i = attr.find(":")
+                if i >= 0:
+                    attr = attr[:i]
+
+                if k in form:
+                    item = form[k]
+                    if isinstance(item, Record):
+                        if hasattr(item, attr):
+                            setattr(item, attr, tuple(getattr(item, attr)))
+                    else:
+                        for v in item:
+                            if hasattr(v, attr):
+                                setattr(v, attr, tuple(getattr(v, attr)))
+
+    def __insertDefaults(self):
+        """Insert defaults into form dictionary."""
+        form = self.form
+
+        for keys, values in self.__defaults.iteritems():
+            if not keys in form:
+                form[keys] = values
+            else:
+                item = form[keys]
+                if isinstance(values, Record):
+                    for k, v in values.items():
+                        if not hasattr(item, k):
+                            setattr(item, k, v)
+                elif isinstance(values, list):
+                    for val in values:
+                        if isinstance(val, Record):
+                            for k, v in val.items():
+                                for r in item:
+                                    if not hasattr(r, k):
+                                        setattr(r, k, v)
+                        elif not val in item:
+                            item.append(val)
+
+    def traverse(self, obj):
+        'See IPublisherRequest'
+
+        ob = super(BrowserRequest, self).traverse(obj)
+        method = self.method
+
+        base_needed = 0
+        if self._path_suffix:
+            # We had a :method variable, so we need to set the base,
+            # but we don't look for default documents any more.
+            base_needed = 1
+            redirect = 0
+        elif method in DEFAULTABLE_METHODS:
+            # We need to check for default documents
+            publication = self.publication
+
+            nsteps = 0
+            ob, add_steps = publication.getDefaultTraversal(self, ob)
+            while add_steps:
+                nsteps += len(add_steps)
+                add_steps = list(add_steps)
+                add_steps.reverse()
+                self.setTraversalStack(add_steps)
+                ob = super(BrowserRequest, self).traverse(ob)
+                ob, add_steps = publication.getDefaultTraversal(self, ob)
+
+            if nsteps != self._endswithslash:
+                base_needed = 1
+                redirect = self.use_redirect and method == 'GET'
+
+
+        if base_needed:
+            url = self.getURL()
+            response = self.response
+            if redirect:
+                response.redirect(url)
+                return ''
+            elif not response.getBase():
+                response.setBase(url)
+
+        return ob
+
+    def keys(self):
+        'See Interface.Common.Mapping.IEnumerableMapping'
+        d = {}
+        d.update(self._environ)
+        d.update(self._cookies)
+        d.update(self.form)
+        return d.keys()
+
+
+    def get(self, key, default=None):
+        'See Interface.Common.Mapping.IReadMapping'
+        marker = object()
+        result = self.form.get(key, marker)
+        if result is not marker:
+            return result
+
+        return super(BrowserRequest, self).get(key, default)
+
+class ZopeFieldStorage(FieldStorage):
+
+    def make_file(self, binary=None):
+        return tempfile.NamedTemporaryFile('w+b')
+
+
+class FileUpload(object):
+    '''File upload objects
+
+    File upload objects are used to represent file-uploaded data.
+
+    File upload objects can be used just like files.
+
+    In addition, they have a 'headers' attribute that is a dictionary
+    containing the file-upload headers, and a 'filename' attribute
+    containing the name of the uploaded file.
+    '''
+
+    def __init__(self, aFieldStorage):
+
+        file = aFieldStorage.file
+        if hasattr(file, '__methods__'):
+            methods = file.__methods__
+        else:
+            methods = ['close', 'fileno', 'flush', 'isatty',
+                'read', 'readline', 'readlines', 'seek',
+                'tell', 'truncate', 'write', 'writelines',
+                'name']
+
+        d = self.__dict__
+        for m in methods:
+            if hasattr(file,m):
+                d[m] = getattr(file,m)
+
+        self.headers = aFieldStorage.headers
+        filename = unicode(aFieldStorage.filename, 'UTF-8')
+        # fix for IE full paths
+        filename = filename[filename.rfind('\\')+1:].strip()
+        self.filename = filename
+
+class RedirectingBrowserRequest(BrowserRequest):
+    """Browser requests that redirect when the actual and effective URLs differ
+    """
+
+    use_redirect = True
+
+class TestRequest(BrowserRequest):
+    """Browser request with a constructor convenient for testing
+    """
+
+    def __init__(self, body_instream=None, environ=None, form=None,
+                 skin=None, **kw):
+
+        _testEnv =  {
+            'SERVER_URL':         'http://127.0.0.1',
+            'HTTP_HOST':          '127.0.0.1',
+            'CONTENT_LENGTH':     '0',
+            'GATEWAY_INTERFACE':  'TestFooInterface/1.0',
+            }
+
+        if environ is not None:
+            _testEnv.update(environ)
+
+        if kw:
+            _testEnv.update(kw)
+        if body_instream is None:
+            from StringIO import StringIO
+            body_instream = StringIO('')
+
+        super(TestRequest, self).__init__(body_instream, _testEnv)
+        if form:
+            self.form.update(form)
+
+        # Setup locale object
+        langs = BrowserLanguages(self).getPreferredLanguages()
+        from zope.i18n.locales import locales
+        if not langs or langs[0] == '':
+            self._locale = locales.getLocale(None, None, None)
+        else:
+            parts = (langs[0].split('-') + [None, None])[:3]
+            self._locale = locales.getLocale(*parts)
+
+        if skin is not None:
+            directlyProvides(self, skin)
+        else:
+            directlyProvides(self, IDefaultBrowserLayer)
+
+
+
+class BrowserResponse(HTTPResponse):
+    """Browser response
+    """
+
+    __slots__ = (
+        '_base', # The base href
+        )
+
+    def _implicitResult(self, body):
+        content_type = self.getHeader('content-type')
+        if content_type is None:
+            if isHTML(body):
+                content_type = 'text/html'
+            else:
+                content_type = 'text/plain'
+            self.setHeader('x-content-type-warning', 'guessed from content')
+            self.setHeader('content-type', content_type)
+
+        body, headers = super(BrowserResponse, self)._implicitResult(body)
+        body = self.__insertBase(body)
+        # Update the Content-Length header to account for the inserted
+        # <base> tag.
+        headers = [
+            (name, value) for name, value in headers
+            if name != 'content-length'
+            ]
+        headers.append(('content-length', str(len(body))))
+        return body, headers
+
+
+    def __insertBase(self, body):
+        # Only insert a base tag if content appears to be html.
+        content_type = self.getHeader('content-type', '')
+        if content_type and not is_text_html(content_type):
+            return body
+
+        if self.getBase():
+            if body:
+                match = start_of_header_search(body)
+                if match is not None:
+                    index = match.start(0) + len(match.group(0))
+                    ibase = base_re_search(body)
+                    if ibase is None:
+                        # Make sure the base URL is not a unicode string.
+                        base = str(self.getBase())
+                        body = ('%s\n<base href="%s" />\n%s' %
+                                (body[:index], base, body[index:]))
+        return body
+
+    def getBase(self):
+        return getattr(self, '_base', '')
+
+    def setBase(self, base):
+        self._base = base
+
+    def redirect(self, location, status=None, trusted=False):
+        base = getattr(self, '_base', '')
+        if base and isRelative(str(location)):
+            l = base.rfind('/')
+            if l >= 0:
+                base = base[:l+1]
+            else:
+                base += '/'
+            location = base + location
+
+        # TODO: HTTP redirects must provide an absolute location, see
+        #       http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.30
+        #       So, what if location is relative and base is unknown?  Uncomment
+        #       the following and you'll see that it actually happens.
+        #
+        # if isRelative(str(location)):
+        #     raise AssertionError('Cannot determine absolute location')
+
+        return super(BrowserResponse, self).redirect(location, status, trusted)
+
+    def reset(self):
+        super(BrowserResponse, self).reset()
+        self._base = ''
+
+def isHTML(str):
+     """Try to determine whether str is HTML or not."""
+     s = str.lstrip().lower()
+     if s.startswith('<!doctype html'):
+         return True
+     if s.startswith('<html') and (s[5:6] in ' >'):
+         return True
+     if s.startswith('<!--'):
+         idx = s.find('<html')
+         return idx > 0 and (s[idx+5:idx+6] in ' >')
+     else:
+         return False
+
+def normalize_lang(lang):
+    lang = lang.strip().lower()
+    lang = lang.replace('_', '-')
+    lang = lang.replace(' ', '')
+    return lang
+
+class BrowserLanguages(object):
+    zope.component.adapts(IHTTPRequest)
+    implements(IUserPreferredLanguages)
+
+    def __init__(self, request):
+        self.request = request
+
+    def getPreferredLanguages(self):
+        '''See interface IUserPreferredLanguages'''
+        accept_langs = self.request.get('HTTP_ACCEPT_LANGUAGE', '').split(',')
+
+        # Normalize lang strings
+        accept_langs = [normalize_lang(l) for l in accept_langs]
+        # Then filter out empty ones
+        accept_langs = [l for l in accept_langs if l]
+
+        accepts = []
+        for index, lang in enumerate(accept_langs):
+            l = lang.split(';', 2)
+
+            # If not supplied, quality defaults to 1...
+            quality = 1.0
+
+            if len(l) == 2:
+                q = l[1]
+                if q.startswith('q='):
+                    q = q.split('=', 2)[1]
+                    try:
+                        quality = float(q)
+                    except ValueError:
+                        # malformed quality value, skip it.
+                        continue
+
+            if quality == 1.0:
+                # ... but we use 1.9 - 0.001 * position to
+                # keep the ordering between all items with
+                # 1.0 quality, which may include items with no quality
+                # defined, and items with quality defined as 1.
+                quality = 1.9 - (0.001 * index)
+
+            accepts.append((quality, l[0]))
+
+        # Filter langs with q=0, which means
+        # unwanted lang according to the spec
+        # See: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
+        accepts = [acc for acc in accepts if acc[0]]
+
+        accepts.sort()
+        accepts.reverse()
+
+        return [lang for quality, lang in accepts]
+
+class NotCompatibleAdapterError(Exception):
+    """Adapter not compatible with
+       zope.i18n.interfaces.IModifiableBrowserLanguages has been used.
+    """
+
+BROWSER_LANGUAGES_KEY = "zope.publisher.browser.IUserPreferredLanguages"
+
+class CacheableBrowserLanguages(BrowserLanguages):
+
+    implements(IUserPreferredLanguages)
+
+    def getPreferredLanguages(self):
+        languages_data = self._getLanguagesData()
+        if "overridden" in languages_data:
+            return languages_data["overridden"]
+        elif "cached" not in languages_data:
+            languages_data["cached"] = super(
+                CacheableBrowserLanguages, self).getPreferredLanguages()
+        return languages_data["cached"]
+
+    def _getLanguagesData(self):
+        annotations = self.request.annotations
+        languages_data = annotations.get(BROWSER_LANGUAGES_KEY)
+        if languages_data is None:
+            annotations[BROWSER_LANGUAGES_KEY] = languages_data = {}
+        return languages_data
+
+class ModifiableBrowserLanguages(CacheableBrowserLanguages):
+
+    implements(IModifiableUserPreferredLanguages)
+
+    def setPreferredLanguages(self, languages):
+        languages_data = self.request.annotations.get(BROWSER_LANGUAGES_KEY)
+        if languages_data is None:
+            # Better way to create a compatible with
+            # IModifiableUserPreferredLanguages adapter is to use
+            # CacheableBrowserLanguages as base class or as example.
+            raise NotCompatibleAdapterError("Adapter not compatible with "
+                "zope.i18n.interfaces.IModifiableBrowserLanguages "
+                "has been used.")
+        languages_data["overridden"] = languages
+        self.request.setupLocale()
+
+class BrowserView(Location):
+    """Browser View.
+
+    >>> view = BrowserView("context", "request")
+    >>> view.context
+    'context'
+    >>> view.request
+    'request'
+
+    >>> view.__parent__
+    'context'
+    >>> view.__parent__ = "parent"
+    >>> view.__parent__
+    'parent'
+    """
+    implements(IBrowserView)
+
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+
+    def __getParent(self):
+        return getattr(self, '_parent', self.context)
+
+    def __setParent(self, parent):
+        self._parent = parent
+
+    __parent__ = property(__getParent, __setParent)
+
+class BrowserPage(BrowserView):
+    """Browser page
+
+    To create a page, which is an object that is published as a page,
+    you need to provide an object that:
+
+    - has a __call__ method and that
+
+    - provides IBrowserPublisher, and
+
+    - if ZPT is going to be used, then your object should also provide
+      request and context attributes.
+
+    The BrowserPage base class provides a standard constructor and a
+    simple implementation of IBrowserPublisher:
+
+      >>> class MyPage(BrowserPage):
+      ...     pass
+
+      >>> request = TestRequest()
+      >>> context = object()
+      >>> page = MyPage(context, request)
+
+      >>> from zope.publisher.interfaces.browser import IBrowserPublisher
+      >>> IBrowserPublisher.providedBy(page)
+      True
+
+      >>> page.browserDefault(request) == (page, ())
+      True
+
+      >>> page.publishTraverse(request, 'bob') # doctest: +ELLIPSIS
+      Traceback (most recent call last):
+      ...
+      NotFound: Object: <zope.publisher.browser.MyPage object at ...>, name: 'bob'
+
+      >>> page.request is request
+      True
+
+      >>> page.context is context
+      True
+
+    But it doesn't supply a __call__ method:
+
+      >>> page()
+      Traceback (most recent call last):
+        ...
+      NotImplementedError: Subclasses should override __call__ to provide a response body
+
+    It is the subclass' responsibility to do that.
+
+    """
+    implements(IBrowserPage)
+
+    def browserDefault(self, request):
+        return self, ()
+
+    def publishTraverse(self, request, name):
+        raise NotFound(self, name, request)
+
+    def __call__(self, *args, **kw):
+        raise NotImplementedError("Subclasses should override __call__ to "
+                                  "provide a response body")

Deleted: zope.publisher/tags/3.9.0/src/zope/publisher/configure.zcml
===================================================================
--- zope.publisher/trunk/src/zope/publisher/configure.zcml	2009-08-27 14:38:12 UTC (rev 103281)
+++ zope.publisher/tags/3.9.0/src/zope/publisher/configure.zcml	2009-08-27 14:51:30 UTC (rev 103284)
@@ -1,67 +0,0 @@
-<configure 
-    xmlns="http://namespaces.zope.org/zope"
-    xmlns:apidoc="http://namespaces.zope.org/apidoc"
-    xmlns:zcml="http://namespaces.zope.org/zcml"
-    >
-
-  <interface interface="zope.publisher.interfaces.browser.IBrowserSkinType" />
-  <interface interface="zope.publisher.interfaces.xmlrpc.IXMLRPCRequest" />
-
-  <interface
-      interface="zope.publisher.interfaces.browser.IDefaultBrowserLayer"
-      />
-
-  <class class="zope.publisher.http.HTTPRequest">
-    <implements
-        zcml:condition="installed zope.annotation"
-        interface="zope.annotation.interfaces.IAttributeAnnotatable"
-        />
-  </class>
-
-  <adapter factory="zope.publisher.http.HTTPCharsets" />
-  
-  <class class="xmlrpclib.Binary">
-    <allow attributes="data encode decode" />
-  </class>
-
-  <adapter factory=".xmlrpc.ListPreMarshaller" />
-  <adapter factory=".xmlrpc.TuplePreMarshaller" />
-  <adapter factory=".xmlrpc.BinaryPreMarshaller" />
-  <adapter factory=".xmlrpc.FaultPreMarshaller" />
-  <adapter factory=".xmlrpc.DateTimePreMarshaller" />
-  <adapter factory=".xmlrpc.PythonDateTimePreMarshaller" />
-  <adapter factory=".xmlrpc.DictPreMarshaller" />
-
-  <adapter
-      name="default"
-      factory=".skinnable.getDefaultSkin"
-      for="zope.publisher.interfaces.browser.IBrowserRequest"
-      provides="zope.publisher.interfaces.IDefaultSkin"
-      />
-
-  <adapter
-      factory=".principallogging.PrincipalLogging"
-      provides=".interfaces.logginginfo.ILoggingInfo"
-      for="zope.security.interfaces.IPrincipal"
-      />
-
-  <adapter
-      factory=".http.BasicAuthAdapter"
-      provides="zope.authentication.interfaces.ILoginPassword"
-      for=".interfaces.http.IHTTPCredentials"
-      />
-
-  <adapter
-      factory=".ftp.FTPAuth"
-      provides="zope.authentication.interfaces.ILoginPassword"
-      for=".interfaces.ftp.IFTPCredentials"
-      />
-
-  <apidoc:bookchapter
-      zcml:condition="have apidoc"
-      id="zopepublisherhttpresults.txt"
-      title="Creating HTTP Results"
-      doc_path="httpresults.txt"
-      />
-
-</configure>

Copied: zope.publisher/tags/3.9.0/src/zope/publisher/configure.zcml (from rev 103282, zope.publisher/trunk/src/zope/publisher/configure.zcml)
===================================================================
--- zope.publisher/tags/3.9.0/src/zope/publisher/configure.zcml	                        (rev 0)
+++ zope.publisher/tags/3.9.0/src/zope/publisher/configure.zcml	2009-08-27 14:51:30 UTC (rev 103284)
@@ -0,0 +1,112 @@
+<configure 
+    xmlns="http://namespaces.zope.org/zope"
+    xmlns:apidoc="http://namespaces.zope.org/apidoc"
+    xmlns:zcml="http://namespaces.zope.org/zcml"
+    >
+
+  <interface interface="zope.publisher.interfaces.browser.IBrowserSkinType" />
+  <interface interface="zope.publisher.interfaces.xmlrpc.IXMLRPCRequest" />
+
+  <interface
+      interface="zope.publisher.interfaces.browser.IDefaultBrowserLayer"
+      />
+
+  <class class="zope.publisher.http.HTTPRequest">
+    <implements
+        zcml:condition="installed zope.annotation"
+        interface="zope.annotation.interfaces.IAttributeAnnotatable"
+        />
+    <require
+        permission="zope.View"
+        interface="zope.publisher.interfaces.http.IHTTPApplicationRequest"/>
+  </class>
+
+  <class class="zope.publisher.http.URLGetter">
+    <allow attributes="get __getitem__ __str__" />
+  </class>
+
+  <class class="zope.publisher.http.DirectResult">
+    <allow interface="zope.publisher.http.IResult" />
+  </class>
+
+  <class class="zope.publisher.browser.BrowserRequest">
+    <allow
+      interface="zope.publisher.interfaces.browser.IBrowserApplicationRequest"
+      attributes="response locale __str__"
+      />
+  </class>
+
+  <class class="zope.publisher.browser.TestRequest">
+    <allow
+      interface="zope.publisher.interfaces.browser.IBrowserApplicationRequest"
+      attributes="response"
+      />
+  </class>
+
+  <class class="zope.publisher.browser.BrowserResponse">
+    <allow
+      interface="zope.publisher.interfaces.http.IHTTPResponse"
+      />
+  </class>
+
+  <adapter factory="zope.publisher.http.HTTPCharsets" />
+
+  <adapter
+    factory=".browser.ModifiableBrowserLanguages"
+    for="zope.publisher.interfaces.http.IHTTPRequest"
+    provides="zope.i18n.interfaces.IModifiableUserPreferredLanguages"
+    />
+  
+  <class class="xmlrpclib.Binary">
+    <allow attributes="data encode decode" />
+  </class>
+
+  <class class="xmlrpclib.Fault">
+    <allow attributes="faultCode faultString" />
+  </class>
+
+  <class class="xmlrpclib.DateTime">
+    <allow attributes="value" />
+  </class>
+
+  <adapter factory=".xmlrpc.ListPreMarshaller" />
+  <adapter factory=".xmlrpc.TuplePreMarshaller" />
+  <adapter factory=".xmlrpc.BinaryPreMarshaller" />
+  <adapter factory=".xmlrpc.FaultPreMarshaller" />
+  <adapter factory=".xmlrpc.DateTimePreMarshaller" />
+  <adapter factory=".xmlrpc.PythonDateTimePreMarshaller" />
+  <adapter factory=".xmlrpc.DictPreMarshaller" />
+
+  <adapter
+      name="default"
+      factory=".skinnable.getDefaultSkin"
+      for="zope.publisher.interfaces.browser.IBrowserRequest"
+      provides="zope.publisher.interfaces.IDefaultSkin"
+      />
+
+  <adapter
+      factory=".principallogging.PrincipalLogging"
+      provides=".interfaces.logginginfo.ILoggingInfo"
+      for="zope.security.interfaces.IPrincipal"
+      />
+
+  <adapter
+      factory=".http.BasicAuthAdapter"
+      provides="zope.authentication.interfaces.ILoginPassword"
+      for=".interfaces.http.IHTTPCredentials"
+      />
+
+  <adapter
+      factory=".ftp.FTPAuth"
+      provides="zope.authentication.interfaces.ILoginPassword"
+      for=".interfaces.ftp.IFTPCredentials"
+      />
+
+  <apidoc:bookchapter
+      zcml:condition="have apidoc"
+      id="zopepublisherhttpresults.txt"
+      title="Creating HTTP Results"
+      doc_path="httpresults.txt"
+      />
+
+</configure>

Deleted: zope.publisher/tags/3.9.0/src/zope/publisher/defaultview.py
===================================================================
--- zope.publisher/trunk/src/zope/publisher/defaultview.py	2009-08-27 14:38:12 UTC (rev 103281)
+++ zope.publisher/tags/3.9.0/src/zope/publisher/defaultview.py	2009-08-27 14:51:30 UTC (rev 103284)
@@ -1,92 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2003 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.
-#
-##############################################################################
-"""Default view name API
-
-$Id$
-"""
-from zope.component.interfaces import ComponentLookupError
-from zope.component import getSiteManager
-
-import zope.interface
-from zope.publisher.interfaces import IDefaultViewName
-
-
-class IDefaultViewNameAPI(zope.interface.Interface):
-
-    def getDefaultViewName(object, request, context=None):
-        """Get the name of the default view for the object and request.
-
-        If a matching default view name cannot be found, raises
-        ComponentLookupError.
-
-        If context is not specified, attempts to use
-        object to specify a context.
-        """
-
-    def queryDefaultViewName(object, request, default=None, context=None):
-        """Look for the name of the default view for the object and request.
-
-        If a matching default view name cannot be found, returns the default.
-
-        If context is not specified, attempts to use object to specify
-        a context.
-        """
-
-# TODO: needs tests
-def getDefaultViewName(object, request, context=None):
-    name = queryDefaultViewName(object, request, context=context)
-    if name is not None:
-        return name
-    raise ComponentLookupError("Couldn't find default view name",
-                               context, request)
-
-def queryDefaultViewName(object, request, default=None, context=None):
-    """
-    query the default view for a given object and request.
-
-      >>> from zope.publisher.defaultview import queryDefaultViewName
-
-    lets create an object with a default view.
-
-      >>> import zope.interface
-      >>> class IMyObject(zope.interface.Interface):
-      ...   pass
-      >>> class MyObject(object):
-      ...   zope.interface.implements(IMyObject)
-      >>> queryDefaultViewName(MyObject(), object()) is None
-      True
-
-    Now we can will set a default view.
-
-      >>> import zope.component
-      >>> import zope.publisher.interfaces
-      >>> zope.component.provideAdapter('name',
-      ...     adapts=(IMyObject, zope.interface.Interface),
-      ...     provides=zope.publisher.interfaces.IDefaultViewName)
-      >>> queryDefaultViewName(MyObject(), object())
-      'name'
-
-    This also works if the name is empty
-
-      >>> zope.component.provideAdapter('',
-      ...     adapts=(IMyObject, zope.interface.Interface),
-      ...     provides=zope.publisher.interfaces.IDefaultViewName)
-      >>> queryDefaultViewName(MyObject(), object())
-      ''
-    """
-    name = getSiteManager(context).adapters.lookup(
-        map(zope.interface.providedBy, (object, request)), IDefaultViewName)
-    if name is None:
-        return default
-    return name

Copied: zope.publisher/tags/3.9.0/src/zope/publisher/defaultview.py (from rev 103282, zope.publisher/trunk/src/zope/publisher/defaultview.py)
===================================================================
--- zope.publisher/tags/3.9.0/src/zope/publisher/defaultview.py	                        (rev 0)
+++ zope.publisher/tags/3.9.0/src/zope/publisher/defaultview.py	2009-08-27 14:51:30 UTC (rev 103284)
@@ -0,0 +1,91 @@
+##############################################################################
+#
+# Copyright (c) 2003 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.
+#
+##############################################################################
+"""Default view name API
+
+$Id$
+"""
+from zope.component.interfaces import ComponentLookupError
+from zope.component import getSiteManager
+
+import zope.interface
+from zope.publisher.interfaces import IDefaultViewName
+
+
+class IDefaultViewNameAPI(zope.interface.Interface):
+
+    def getDefaultViewName(object, request, context=None):
+        """Get the name of the default view for the object and request.
+
+        If a matching default view name cannot be found, raises
+        ComponentLookupError.
+
+        If context is not specified, attempts to use
+        object to specify a context.
+        """
+
+    def queryDefaultViewName(object, request, default=None, context=None):
+        """Look for the name of the default view for the object and request.
+
+        If a matching default view name cannot be found, returns the default.
+
+        If context is not specified, attempts to use object to specify
+        a context.
+        """
+
+def getDefaultViewName(object, request, context=None):
+    name = queryDefaultViewName(object, request, context=context)
+    if name is not None:
+        return name
+    raise ComponentLookupError("Couldn't find default view name",
+                               context, request)
+
+def queryDefaultViewName(object, request, default=None, context=None):
+    """
+    query the default view for a given object and request.
+
+      >>> from zope.publisher.defaultview import queryDefaultViewName
+
+    lets create an object with a default view.
+
+      >>> import zope.interface
+      >>> class IMyObject(zope.interface.Interface):
+      ...   pass
+      >>> class MyObject(object):
+      ...   zope.interface.implements(IMyObject)
+      >>> queryDefaultViewName(MyObject(), object()) is None
+      True
+
+    Now we can will set a default view.
+
+      >>> import zope.component
+      >>> import zope.publisher.interfaces
+      >>> zope.component.provideAdapter('name',
+      ...     adapts=(IMyObject, zope.interface.Interface),
+      ...     provides=zope.publisher.interfaces.IDefaultViewName)
+      >>> queryDefaultViewName(MyObject(), object())
+      'name'
+
+    This also works if the name is empty
+
+      >>> zope.component.provideAdapter('',
+      ...     adapts=(IMyObject, zope.interface.Interface),
+      ...     provides=zope.publisher.interfaces.IDefaultViewName)
+      >>> queryDefaultViewName(MyObject(), object())
+      ''
+    """
+    name = getSiteManager(context).adapters.lookup(
+        map(zope.interface.providedBy, (object, request)), IDefaultViewName)
+    if name is None:
+        return default
+    return name

Deleted: zope.publisher/tags/3.9.0/src/zope/publisher/interfaces/ftp.py
===================================================================
--- zope.publisher/trunk/src/zope/publisher/interfaces/ftp.py	2009-08-27 14:38:12 UTC (rev 103281)
+++ zope.publisher/tags/3.9.0/src/zope/publisher/interfaces/ftp.py	2009-08-27 14:51:30 UTC (rev 103284)
@@ -1,42 +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.
-#
-##############################################################################
-"""Virtual File System interfaces for the publisher.
-
-$Id$
-"""
-
-__docformat__ = "reStructuredText"
-
-from zope.interface import Interface
-
-from zope.publisher.interfaces import IPublishTraverse
-from zope.publisher.interfaces import IRequest
-
-
-class IFTPRequest(IRequest):
-    """FTP Request
-    """
-
-class IFTPCredentials(Interface):
-
-    def _authUserPW():
-        """Return (login, password) if there are basic credentials;
-        return None if there aren't."""
-
-    def unauthorized(challenge):
-        """Cause a FTP-based unautorized error message"""
-
-
-class IFTPPublisher(IPublishTraverse):
-    """FTP Publisher"""

Copied: zope.publisher/tags/3.9.0/src/zope/publisher/interfaces/ftp.py (from rev 103282, zope.publisher/trunk/src/zope/publisher/interfaces/ftp.py)
===================================================================
--- zope.publisher/tags/3.9.0/src/zope/publisher/interfaces/ftp.py	                        (rev 0)
+++ zope.publisher/tags/3.9.0/src/zope/publisher/interfaces/ftp.py	2009-08-27 14:51:30 UTC (rev 103284)
@@ -0,0 +1,45 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""Virtual File System interfaces for the publisher.
+
+$Id$
+"""
+
+__docformat__ = "reStructuredText"
+
+from zope.interface import Interface
+
+from zope.publisher.interfaces import IPublishTraverse
+from zope.publisher.interfaces import IRequest
+from zope.publisher.interfaces import IView
+
+class IFTPRequest(IRequest):
+    """FTP Request
+    """
+
+class IFTPView(IView):
+    """FTP View"""
+
+class IFTPCredentials(Interface):
+
+    def _authUserPW():
+        """Return (login, password) if there are basic credentials;
+        return None if there aren't."""
+
+    def unauthorized(challenge):
+        """Cause a FTP-based unautorized error message"""
+
+
+class IFTPPublisher(IPublishTraverse):
+    """FTP Publisher"""

Deleted: zope.publisher/tags/3.9.0/src/zope/publisher/interfaces/http.py
===================================================================
--- zope.publisher/trunk/src/zope/publisher/interfaces/http.py	2009-08-27 14:38:12 UTC (rev 103281)
+++ zope.publisher/tags/3.9.0/src/zope/publisher/interfaces/http.py	2009-08-27 14:51:30 UTC (rev 103284)
@@ -1,507 +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-related publisher interfaces.
-
-$Id$
-"""
-
-__docformat__ = "reStructuredText"
-
-from zope.interface import Interface
-from zope.interface import Attribute
-from zope.interface import implements
-from zope.interface.common.interfaces import IException
-
-from zope.publisher.interfaces import IApplicationRequest
-from zope.publisher.interfaces import IPublishTraverse
-from zope.publisher.interfaces import IRequest
-from zope.publisher.interfaces import IResponse
-
-
-class IVirtualHostRequest(Interface):
-    """The support for virtual hosts in Zope is very important.
-
-    In order to make virtual hosts working, we need to support several
-    methods in our Request object. This interface defines the required
-    methods.
-    """
-
-    def setVirtualHostRoot(names):
-        """Marks the currently traversed object as the root of a virtual host.
-
-        Any path elements traversed up to that
-
-        Set the names which compose the application path.
-        These are the path elements that appear in the beginning of
-        the generated URLs.
-
-        Should be called during traversal.
-        """
-
-    def getVirtualHostRoot():
-        """Returns the object which is the virtual host root for this request
-
-        Return None if setVirtualHostRoot hasn't been called.
-        """
-
-    def setApplicationServer(host, proto='http', port=None):
-        """Override the host, protocol and port parts of generated URLs.
-
-        This affects automatically inserted <base> tags and URL getters
-        in the request, but not things like @@absolute_url views.
-        """
-
-    def shiftNameToApplication():
-        """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 raised if the shift can't be performed.
-        """
-
-
-class IHTTPApplicationRequest(IApplicationRequest, IVirtualHostRequest):
-    """HTTP request data.
-
-    This object provides access to request data.  This includes, the
-    input headers, 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 request object may be used as a mapping object, in which case
-    values will be looked up in the order: environment variables,
-    other variables, cookies, and special.
-    """
-
-    def __getitem__(key):
-        """Return HTTP request data
-
-        Request data sre retrieved from one of:
-
-        - 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.
-
-        Cookies are searched before environmental data.
-        """
-
-    def getCookies():
-        """Return the cookie data
-
-        Data are returned as a mapping object, mapping cookie name to value.
-        """
-        return IMapping(str, str)
-
-    cookies = Attribute(
-        """Request cookie data
-
-        This is a read-only mapping from variable name to value.
-        """)
-
-    def getHeader(name, default=None, literal=False):
-        """Get a header value
-
-        Return the named HTTP header, or an optional default
-        argument or None if the header is not found. Note that
-        both original and CGI-ified header names are recognized,
-        e.g. 'Content-Type', 'CONTENT_TYPE' and 'HTTP_CONTENT_TYPE'
-        should all return the Content-Type header, if available.
-
-        If the literal argument is passed, the header is searched
-        'as is', eg: only if the case matches.
-        """
-
-    headers = Attribute(
-        """Request header data
-
-        This is a read-only mapping from variable name to value.
-        It does *not* support iteration.
-        """)
-
-    URL = Attribute(
-        """Request URL data
-
-        When converted to a string, this gives the effective published URL.
-
-        This object can also be used as a mapping object. The key must
-        be an integer or a string that can be converted to an
-        integer. A non-negative integer returns a URL n steps from the
-        URL of the top-level application objects. A negative integer
-        gives a URL that is -n steps back from the effective URL.
-
-        For example, 'request.URL[-2]' is equivalent to the Zope 2
-        'request["URL2"]'. The notion is that this would be used in
-        path expressions, like 'request/URL/-2'.
-        """)
-
-
-    def getURL(level=0, path_only=False):
-        """Return the published URL with level names removed from the end.
-
-        If path_only is true, then only a path will be returned.
-        """
-
-    def getApplicationURL(depth=0, path_only=False):
-        """Return the application URL plus depth steps
-
-        If path_only is true, then only a path will be returned.
-        """
-
-
-class IHTTPPublisher(IPublishTraverse):
-    """HTTP Publisher"""
-
-
-class IHTTPRequest(IRequest):
-
-    method = Attribute("Request method, normalized to upper case")
-
-    def setPathSuffix(steps):
-        """Add additional traversal steps to be taken after all other traversal
-
-        This is used to handle HTTP request methods (except for GET
-        and POST in the case of browser requests) and XML-RPC methods.
-        """
-
-    locale = Attribute(
-        "Return the locale object associated with this request.")
-
-    def setupLocale():
-        """Setup the locale object based on languages returned by
-        IUserPreferredLanguages adapter.
-        """
-
-
-class IHTTPCredentials(Interface):
-
-    # TODO: Eventially this will be a different method
-    def _authUserPW():
-        """Return (login, password) if there are basic credentials;
-        return None if there aren't."""
-
-    def unauthorized(challenge):
-        """Issue a 401 Unauthorized error (asking for login/password).
-        The challenge is the value of the WWW-Authenticate header."""
-
-
-class IHTTPApplicationResponse(Interface):
-    """HTTP Response"""
-
-    def redirect(location, status=302, trusted=False):
-        """Causes a redirection without raising an error.
-
-        By default redirects are untrusted which restricts target URLs to the
-        same host that the request was sent to.
-
-        If the `trusted` flag is set, redirects are allowed for any target
-        URL.
-
-        """
-
-class IHeaderOutput(Interface):
-    """Interface for setting HTTP response headers.
-
-    This allows the HTTP server and the application to both set response
-    headers.
-
-    zope.publisher.http.HTTPResponse is optionally passed an
-    object which implements this interface in order to intermingle
-    its headers with the HTTP server's response headers,
-    and for the purpose of better logging.
-    """
-
-    def setResponseStatus(status, reason):
-        """Sets the status code and the accompanying message.
-        """
-
-    def setResponseHeaders(mapping):
-        """Sets headers.  The headers must be Correctly-Cased.
-        """
-
-    def appendResponseHeaders(lst):
-        """Sets headers that can potentially repeat.
-
-        Takes a list of strings.
-        """
-
-    def wroteResponseHeader():
-        """Returns a flag indicating whether the response
-
-        header has already been sent.
-        """
-
-    def setAuthUserName(name):
-        """Sets the name of the authenticated user so the name can be logged.
-        """
-
-
-class IResult(Interface):
-    """An iterable that provides the body data of the response.
-    
-    For simplicity, an adapter to this interface may in fact return any
-    iterable, without needing to strictly have the iterable provide
-    IResult.
-
-    IMPORTANT: The result object may be held indefinitely by a server
-    and may be accessed by arbitrary threads. For that reason the result
-    should not hold on to any application resources (i.e., should not
-    have a connection to the database) and should be prepared to be
-    invoked from any thread.
-
-    This iterable should generally be appropriate for WSGI iteration.
-
-    Each element of the iteration should generally be much larger than a
-    character or line; concrete advice on chunk size is hard to come by,
-    but a single chunk of even 100 or 200 K is probably fine.
-
-    If the IResult is a string, then, the default iteration of
-    per-character is wildly too small.  Because this is such a common
-    case, if a string is used as an IResult then this is special-cased
-    to simply convert to a tuple of one value, the string.
-    
-    Adaptation to this interface provides the opportunity for efficient file
-    delivery, pipelining hooks, and more.
-    """
-
-    def __iter__():
-        """iterate over the values that should be returned as the result.
-        
-        See IHTTPResponse.setResult.
-        """
-
-
-class IHTTPResponse(IResponse):
-    """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 its 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.
-    """
-
-    authUser = Attribute('The authenticated user message.')
-
-    def getStatus():
-        """Returns the current HTTP status code as an integer.
-        """
-
-    def setStatus(status, reason=None):
-        """Sets the HTTP status code of the response
-
-        The status parameter must be either an integer (preferred), a value
-        that can be converted to an integer using the int() function,
-        or one of the standard status messages listed in the status_codes
-        dict of the zope.publisher.http module (including "OK", "NotFound",
-        and so on).  If the parameter is some other value, the status will
-        be set to 500.
-
-        The reason parameter is a short message to be sent with the status
-        code to the client.  If reason is not provided, a standard
-        reason will be supplied, falling back to "Unknown" for unregistered
-        status codes.
-        """
-
-    def getStatusString():
-        """Return the status followed by the reason."""
-
-    def setHeader(name, value, literal=False):
-        """Sets an HTTP return header "name" with value "value"
-
-        The previous value is cleared. 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.
-        """
-
-    def addHeader(name, value):
-        """Add an HTTP Header
-
-        Sets a new HTTP return header with the given value, while retaining
-        any previously set headers with the same name.
-        """
-
-    def getHeader(name, default=None):
-        """Gets a header value
-
-        Returns the value associated with a HTTP return header, or
-        'default' if no such header has been set in the response
-        yet.
-        """
-
-    def getHeaders():
-        """Returns a list of header name, value tuples.
-        """
-
-    def appendToCookie(name, value):
-        """Append text to a cookie value
-
-        If a value for the cookie has previously been set, the new
-        value is appended to the old one separated by a colon.
-        """
-
-    def expireCookie(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.
-        If the value of a keyword argument is None, it will be ignored.
-        """
-
-    def setCookie(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.
-        If the value of a keyword argument is None, it will be ignored.
-        """
-
-    def getCookie(name, default=None):
-        """Gets HTTP cookie data as a dict
-
-        Returns the dict of values associated with an HTTP cookie set in the
-        response, or 'default' if no such cookie has been set in the response
-        yet.
-        """
-
-    def setResult(result):
-        """Sets response result value based on input.
-        
-        Input is usually a unicode string, a string, None, or an object
-        that can be adapted to IResult with the request.  The end result
-        is an iterable such as WSGI prefers, determined by following the
-        process described below.
-        
-        Try to adapt the given input, with the request, to IResult
-        (found above in this file).  If this fails, and the original
-        value was a string, use the string as the result; or if was
-        None, use an empty string as the result; and if it was anything
-        else, raise a TypeError.
-        
-        If the result of the above (the adaptation or the default
-        handling of string and None) is unicode, encode it (to the
-        preferred encoding found by adapting the request to
-        zope.i18n.interfaces.IUserPreferredCharsets, usually implemented
-        by looking at the HTTP Accept-Charset header in the request, and
-        defaulting to utf-8) and set the proper encoding information on
-        the Content-Type header, if present.  Otherwise (the end result
-        was not unicode) application is responsible for setting
-        Content-Type header encoding value as necessary.
-        
-        If the result of the above is a string, set the Content-Length
-        header, and make the string be the single member of an iterable
-        such as a tuple (to send large chunks over the wire; see
-        discussion in the IResult interface).  Otherwise (the end result
-        was not a string) application is responsible for setting
-        Content-Length header as necessary.
-        
-        Set the result of all of the above as the response's result. If
-        the status has not been set, set it to 200 (OK). """
-
-    def consumeBody():
-        """Returns the response body as a string.
-
-        Note that this function can be only requested once, since it is
-        constructed from the result.
-        """
-
-    def consumeBodyIter():
-        """Returns the response body as an iterable.
-
-        Note that this function can be only requested once, since it is
-        constructed from the result.
-        """
-        
-class IHTTPVirtualHostChangedEvent(Interface):
-    """The host, port and/or the application path have changed.
-    
-    The request referred to in this event implements at least the 
-    IHTTPAppliationRequest interface.
-    """
-    request = Attribute(u'The application request whose virtual host info has '
-                        u'been altered')
-
-class IHTTPException(Interface):
-    """Marker interface for http exceptions views
-    """
-    pass
-
-class IMethodNotAllowed(IException):
-    """An exception that signals the 405 Method Not Allowed HTTP error"""
-
-    object = Attribute("""The object on which the error occurred""")
-
-    request = Attribute("""The request in which the error occurred""")
-
-
-class MethodNotAllowed(Exception):
-    """An exception that signals the 405 Method Not Allowed HTTP error"""
-
-    implements(IMethodNotAllowed)
-
-    def __init__(self, object, request):
-        self.object = object
-        self.request = request
-
-    def __str__(self):
-        return "%r, %r" % (self.object, self.request)
-

Copied: zope.publisher/tags/3.9.0/src/zope/publisher/interfaces/http.py (from rev 103282, zope.publisher/trunk/src/zope/publisher/interfaces/http.py)
===================================================================
--- zope.publisher/tags/3.9.0/src/zope/publisher/interfaces/http.py	                        (rev 0)
+++ zope.publisher/tags/3.9.0/src/zope/publisher/interfaces/http.py	2009-08-27 14:51:30 UTC (rev 103284)
@@ -0,0 +1,512 @@
+##############################################################################
+#
+# 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-related publisher interfaces.
+
+$Id$
+"""
+
+__docformat__ = "reStructuredText"
+
+from zope.interface import Interface
+from zope.interface import Attribute
+from zope.interface import implements
+from zope.interface.common.interfaces import IException
+
+from zope.publisher.interfaces import IApplicationRequest
+from zope.publisher.interfaces import IPublishTraverse
+from zope.publisher.interfaces import IRequest
+from zope.publisher.interfaces import IResponse
+from zope.publisher.interfaces import IView
+
+
+class IVirtualHostRequest(Interface):
+    """The support for virtual hosts in Zope is very important.
+
+    In order to make virtual hosts working, we need to support several
+    methods in our Request object. This interface defines the required
+    methods.
+    """
+
+    def setVirtualHostRoot(names):
+        """Marks the currently traversed object as the root of a virtual host.
+
+        Any path elements traversed up to that
+
+        Set the names which compose the application path.
+        These are the path elements that appear in the beginning of
+        the generated URLs.
+
+        Should be called during traversal.
+        """
+
+    def getVirtualHostRoot():
+        """Returns the object which is the virtual host root for this request
+
+        Return None if setVirtualHostRoot hasn't been called.
+        """
+
+    def setApplicationServer(host, proto='http', port=None):
+        """Override the host, protocol and port parts of generated URLs.
+
+        This affects automatically inserted <base> tags and URL getters
+        in the request, but not things like @@absolute_url views.
+        """
+
+    def shiftNameToApplication():
+        """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 raised if the shift can't be performed.
+        """
+
+
+class IHTTPApplicationRequest(IApplicationRequest, IVirtualHostRequest):
+    """HTTP request data.
+
+    This object provides access to request data.  This includes, the
+    input headers, 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 request object may be used as a mapping object, in which case
+    values will be looked up in the order: environment variables,
+    other variables, cookies, and special.
+    """
+
+    def __getitem__(key):
+        """Return HTTP request data
+
+        Request data sre retrieved from one of:
+
+        - 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.
+
+        Cookies are searched before environmental data.
+        """
+
+    def getCookies():
+        """Return the cookie data
+
+        Data are returned as a mapping object, mapping cookie name to value.
+        """
+        return IMapping(str, str)
+
+    cookies = Attribute(
+        """Request cookie data
+
+        This is a read-only mapping from variable name to value.
+        """)
+
+    def getHeader(name, default=None, literal=False):
+        """Get a header value
+
+        Return the named HTTP header, or an optional default
+        argument or None if the header is not found. Note that
+        both original and CGI-ified header names are recognized,
+        e.g. 'Content-Type', 'CONTENT_TYPE' and 'HTTP_CONTENT_TYPE'
+        should all return the Content-Type header, if available.
+
+        If the literal argument is passed, the header is searched
+        'as is', eg: only if the case matches.
+        """
+
+    headers = Attribute(
+        """Request header data
+
+        This is a read-only mapping from variable name to value.
+        It does *not* support iteration.
+        """)
+
+    URL = Attribute(
+        """Request URL data
+
+        When converted to a string, this gives the effective published URL.
+
+        This object can also be used as a mapping object. The key must
+        be an integer or a string that can be converted to an
+        integer. A non-negative integer returns a URL n steps from the
+        URL of the top-level application objects. A negative integer
+        gives a URL that is -n steps back from the effective URL.
+
+        For example, 'request.URL[-2]' is equivalent to the Zope 2
+        'request["URL2"]'. The notion is that this would be used in
+        path expressions, like 'request/URL/-2'.
+        """)
+
+
+    def getURL(level=0, path_only=False):
+        """Return the published URL with level names removed from the end.
+
+        If path_only is true, then only a path will be returned.
+        """
+
+    def getApplicationURL(depth=0, path_only=False):
+        """Return the application URL plus depth steps
+
+        If path_only is true, then only a path will be returned.
+        """
+
+
+class IHTTPPublisher(IPublishTraverse):
+    """HTTP Publisher"""
+
+
+class IHTTPRequest(IRequest):
+
+    method = Attribute("Request method, normalized to upper case")
+
+    def setPathSuffix(steps):
+        """Add additional traversal steps to be taken after all other traversal
+
+        This is used to handle HTTP request methods (except for GET
+        and POST in the case of browser requests) and XML-RPC methods.
+        """
+
+    locale = Attribute(
+        "Return the locale object associated with this request.")
+
+    def setupLocale():
+        """Setup the locale object based on languages returned by
+        IUserPreferredLanguages adapter.
+        """
+
+
+class IHTTPView(IView):
+    "HTTP View"
+
+
+class IHTTPCredentials(Interface):
+
+    # TODO: Eventially this will be a different method
+    def _authUserPW():
+        """Return (login, password) if there are basic credentials;
+        return None if there aren't."""
+
+    def unauthorized(challenge):
+        """Issue a 401 Unauthorized error (asking for login/password).
+        The challenge is the value of the WWW-Authenticate header."""
+
+
+class IHTTPApplicationResponse(Interface):
+    """HTTP Response"""
+
+    def redirect(location, status=302, trusted=False):
+        """Causes a redirection without raising an error.
+
+        By default redirects are untrusted which restricts target URLs to the
+        same host that the request was sent to.
+
+        If the `trusted` flag is set, redirects are allowed for any target
+        URL.
+
+        """
+
+class IHeaderOutput(Interface):
+    """Interface for setting HTTP response headers.
+
+    This allows the HTTP server and the application to both set response
+    headers.
+
+    zope.publisher.http.HTTPResponse is optionally passed an
+    object which implements this interface in order to intermingle
+    its headers with the HTTP server's response headers,
+    and for the purpose of better logging.
+    """
+
+    def setResponseStatus(status, reason):
+        """Sets the status code and the accompanying message.
+        """
+
+    def setResponseHeaders(mapping):
+        """Sets headers.  The headers must be Correctly-Cased.
+        """
+
+    def appendResponseHeaders(lst):
+        """Sets headers that can potentially repeat.
+
+        Takes a list of strings.
+        """
+
+    def wroteResponseHeader():
+        """Returns a flag indicating whether the response
+
+        header has already been sent.
+        """
+
+    def setAuthUserName(name):
+        """Sets the name of the authenticated user so the name can be logged.
+        """
+
+
+class IResult(Interface):
+    """An iterable that provides the body data of the response.
+    
+    For simplicity, an adapter to this interface may in fact return any
+    iterable, without needing to strictly have the iterable provide
+    IResult.
+
+    IMPORTANT: The result object may be held indefinitely by a server
+    and may be accessed by arbitrary threads. For that reason the result
+    should not hold on to any application resources (i.e., should not
+    have a connection to the database) and should be prepared to be
+    invoked from any thread.
+
+    This iterable should generally be appropriate for WSGI iteration.
+
+    Each element of the iteration should generally be much larger than a
+    character or line; concrete advice on chunk size is hard to come by,
+    but a single chunk of even 100 or 200 K is probably fine.
+
+    If the IResult is a string, then, the default iteration of
+    per-character is wildly too small.  Because this is such a common
+    case, if a string is used as an IResult then this is special-cased
+    to simply convert to a tuple of one value, the string.
+    
+    Adaptation to this interface provides the opportunity for efficient file
+    delivery, pipelining hooks, and more.
+    """
+
+    def __iter__():
+        """iterate over the values that should be returned as the result.
+        
+        See IHTTPResponse.setResult.
+        """
+
+
+class IHTTPResponse(IResponse):
+    """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 its 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.
+    """
+
+    authUser = Attribute('The authenticated user message.')
+
+    def getStatus():
+        """Returns the current HTTP status code as an integer.
+        """
+
+    def setStatus(status, reason=None):
+        """Sets the HTTP status code of the response
+
+        The status parameter must be either an integer (preferred), a value
+        that can be converted to an integer using the int() function,
+        or one of the standard status messages listed in the status_codes
+        dict of the zope.publisher.http module (including "OK", "NotFound",
+        and so on).  If the parameter is some other value, the status will
+        be set to 500.
+
+        The reason parameter is a short message to be sent with the status
+        code to the client.  If reason is not provided, a standard
+        reason will be supplied, falling back to "Unknown" for unregistered
+        status codes.
+        """
+
+    def getStatusString():
+        """Return the status followed by the reason."""
+
+    def setHeader(name, value, literal=False):
+        """Sets an HTTP return header "name" with value "value"
+
+        The previous value is cleared. 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.
+        """
+
+    def addHeader(name, value):
+        """Add an HTTP Header
+
+        Sets a new HTTP return header with the given value, while retaining
+        any previously set headers with the same name.
+        """
+
+    def getHeader(name, default=None):
+        """Gets a header value
+
+        Returns the value associated with a HTTP return header, or
+        'default' if no such header has been set in the response
+        yet.
+        """
+
+    def getHeaders():
+        """Returns a list of header name, value tuples.
+        """
+
+    def appendToCookie(name, value):
+        """Append text to a cookie value
+
+        If a value for the cookie has previously been set, the new
+        value is appended to the old one separated by a colon.
+        """
+
+    def expireCookie(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.
+        If the value of a keyword argument is None, it will be ignored.
+        """
+
+    def setCookie(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.
+        If the value of a keyword argument is None, it will be ignored.
+        """
+
+    def getCookie(name, default=None):
+        """Gets HTTP cookie data as a dict
+
+        Returns the dict of values associated with an HTTP cookie set in the
+        response, or 'default' if no such cookie has been set in the response
+        yet.
+        """
+
+    def setResult(result):
+        """Sets response result value based on input.
+        
+        Input is usually a unicode string, a string, None, or an object
+        that can be adapted to IResult with the request.  The end result
+        is an iterable such as WSGI prefers, determined by following the
+        process described below.
+        
+        Try to adapt the given input, with the request, to IResult
+        (found above in this file).  If this fails, and the original
+        value was a string, use the string as the result; or if was
+        None, use an empty string as the result; and if it was anything
+        else, raise a TypeError.
+        
+        If the result of the above (the adaptation or the default
+        handling of string and None) is unicode, encode it (to the
+        preferred encoding found by adapting the request to
+        zope.i18n.interfaces.IUserPreferredCharsets, usually implemented
+        by looking at the HTTP Accept-Charset header in the request, and
+        defaulting to utf-8) and set the proper encoding information on
+        the Content-Type header, if present.  Otherwise (the end result
+        was not unicode) application is responsible for setting
+        Content-Type header encoding value as necessary.
+        
+        If the result of the above is a string, set the Content-Length
+        header, and make the string be the single member of an iterable
+        such as a tuple (to send large chunks over the wire; see
+        discussion in the IResult interface).  Otherwise (the end result
+        was not a string) application is responsible for setting
+        Content-Length header as necessary.
+        
+        Set the result of all of the above as the response's result. If
+        the status has not been set, set it to 200 (OK). """
+
+    def consumeBody():
+        """Returns the response body as a string.
+
+        Note that this function can be only requested once, since it is
+        constructed from the result.
+        """
+
+    def consumeBodyIter():
+        """Returns the response body as an iterable.
+
+        Note that this function can be only requested once, since it is
+        constructed from the result.
+        """
+        
+class IHTTPVirtualHostChangedEvent(Interface):
+    """The host, port and/or the application path have changed.
+    
+    The request referred to in this event implements at least the 
+    IHTTPAppliationRequest interface.
+    """
+    request = Attribute(u'The application request whose virtual host info has '
+                        u'been altered')
+
+class IHTTPException(Interface):
+    """Marker interface for http exceptions views
+    """
+    pass
+
+class IMethodNotAllowed(IException):
+    """An exception that signals the 405 Method Not Allowed HTTP error"""
+
+    object = Attribute("""The object on which the error occurred""")
+
+    request = Attribute("""The request in which the error occurred""")
+
+
+class MethodNotAllowed(Exception):
+    """An exception that signals the 405 Method Not Allowed HTTP error"""
+
+    implements(IMethodNotAllowed)
+
+    def __init__(self, object, request):
+        self.object = object
+        self.request = request
+
+    def __str__(self):
+        return "%r, %r" % (self.object, self.request)
+

Deleted: zope.publisher/tags/3.9.0/src/zope/publisher/interfaces/xmlrpc.py
===================================================================
--- zope.publisher/trunk/src/zope/publisher/interfaces/xmlrpc.py	2009-08-27 14:38:12 UTC (rev 103281)
+++ zope.publisher/tags/3.9.0/src/zope/publisher/interfaces/xmlrpc.py	2009-08-27 14:51:30 UTC (rev 103284)
@@ -1,53 +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.
-#
-##############################################################################
-"""Interfaces for the XMLRPC publisher.
-
-$Id$
-"""
-
-__docformat__ = "reStructuredText"
-
-from zope.interface import Interface
-
-from zope.publisher.interfaces import IPublication
-from zope.publisher.interfaces import IPublishTraverse
-from zope.publisher.interfaces.http import IHTTPRequest
-
-
-class IXMLRPCPublisher(IPublishTraverse):
-    """XML-RPC Publisher"""
-
-
-class IXMLRPCPublication(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).
-        """
-
-class IXMLRPCRequest(IHTTPRequest):
-    """XML-RPC Request
-    """
-
-class IXMLRPCPremarshaller(Interface):
-    """Pre-Marshaller to remove proxies for xmlrpclib"""
-
-    def __call__(self):
-        """Return the given object without proxies."""
-
-
-

Copied: zope.publisher/tags/3.9.0/src/zope/publisher/interfaces/xmlrpc.py (from rev 103282, zope.publisher/trunk/src/zope/publisher/interfaces/xmlrpc.py)
===================================================================
--- zope.publisher/tags/3.9.0/src/zope/publisher/interfaces/xmlrpc.py	                        (rev 0)
+++ zope.publisher/tags/3.9.0/src/zope/publisher/interfaces/xmlrpc.py	2009-08-27 14:51:30 UTC (rev 103284)
@@ -0,0 +1,53 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""Interfaces for the XMLRPC publisher.
+
+$Id$
+"""
+
+__docformat__ = "reStructuredText"
+
+from zope.interface import Interface
+
+from zope.publisher.interfaces import IPublication, IView
+from zope.publisher.interfaces import IPublishTraverse
+from zope.publisher.interfaces.http import IHTTPRequest
+
+
+class IXMLRPCPublisher(IPublishTraverse):
+    """XML-RPC Publisher"""
+
+
+class IXMLRPCPublication(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).
+        """
+
+class IXMLRPCRequest(IHTTPRequest):
+    """XML-RPC Request
+    """
+
+class IXMLRPCView(IView):
+    """XMLRPC View"""
+
+class IXMLRPCPremarshaller(Interface):
+    """Pre-Marshaller to remove proxies for xmlrpclib"""
+
+    def __call__(self):
+        """Return the given object without proxies."""

Copied: zope.publisher/tags/3.9.0/src/zope/publisher/meta.zcml (from rev 103282, zope.publisher/trunk/src/zope/publisher/meta.zcml)
===================================================================
--- zope.publisher/tags/3.9.0/src/zope/publisher/meta.zcml	                        (rev 0)
+++ zope.publisher/tags/3.9.0/src/zope/publisher/meta.zcml	2009-08-27 14:51:30 UTC (rev 103284)
@@ -0,0 +1,19 @@
+<configure xmlns:meta="http://namespaces.zope.org/meta">
+
+  <meta:directives namespace="http://namespaces.zope.org/browser">
+
+    <meta:directive
+        name="defaultView"
+        schema=".zcml.IDefaultViewDirective"
+        handler=".zcml.defaultView"
+        />
+
+    <meta:directive
+        name="defaultSkin"
+        schema=".zcml.IDefaultSkinDirective"
+        handler=".zcml.defaultSkin"
+        />
+
+  </meta:directives>
+
+</configure>

Deleted: zope.publisher/tags/3.9.0/src/zope/publisher/tests/test_browserlanguages.py
===================================================================
--- zope.publisher/trunk/src/zope/publisher/tests/test_browserlanguages.py	2009-08-27 14:38:12 UTC (rev 103281)
+++ zope.publisher/tags/3.9.0/src/zope/publisher/tests/test_browserlanguages.py	2009-08-27 14:51:30 UTC (rev 103284)
@@ -1,63 +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.
-#
-##############################################################################
-"""Test Browser Languages detector
-
-$Id$
-"""
-import unittest
-
-from zope.publisher.browser import BrowserLanguages
-
-
-# Note: The expected output is in order of preference,
-# empty 'q=' means 'q=1', and if theres more than one
-# empty, we assume they are in order of preference.
-data = [
-    ('da, en, pt', ['da', 'en', 'pt']),
-    ('da, en;q=.9, en-gb;q=1.0, en-us', ['da', 'en-gb', 'en-us', 'en']),
-    ('pt_BR; q=0.6, pt_PT; q = .7, en-gb', ['en-gb', 'pt-pt', 'pt-br']),
-    ('en-us, en_GB;q=0.9, en, pt_BR; q=1.0', ['en-us', 'en', 'pt-br', 'en-gb']),
-    ('ro,en-us;q=0.8,es;q=0.5,fr;q=0.3', ['ro', 'en-us', 'es', 'fr']),
-    ('ro,en-us;q=0,es;q=0.5,fr;q=0,ru;q=1,it', ['ro', 'ru', 'it', 'es'])
-    ]
-
-class TestRequest(dict):
-
-    def __init__(self, languages):
-        self.annotations = {}
-        self.localized = False
-        self["HTTP_ACCEPT_LANGUAGE"] = languages
-
-    def setupLocale(self):
-        self.localized = True
-
-class BrowserLanguagesTest(unittest.TestCase):
-
-    def factory(self, request):
-        return BrowserLanguages(request)
-
-    def test_browser_language_handling(self):
-        for req, expected in data:
-            request = TestRequest(req)
-            browser_languages = self.factory(request)
-            self.assertEqual(list(browser_languages.getPreferredLanguages()),
-                             expected)
-
-
-def test_suite():
-    loader=unittest.TestLoader()
-    return loader.loadTestsFromTestCase(BrowserLanguagesTest)
-
-if __name__=='__main__':
-    unittest.TextTestRunner().run(test_suite())

Copied: zope.publisher/tags/3.9.0/src/zope/publisher/tests/test_browserlanguages.py (from rev 103282, zope.publisher/trunk/src/zope/publisher/tests/test_browserlanguages.py)
===================================================================
--- zope.publisher/tags/3.9.0/src/zope/publisher/tests/test_browserlanguages.py	                        (rev 0)
+++ zope.publisher/tags/3.9.0/src/zope/publisher/tests/test_browserlanguages.py	2009-08-27 14:51:30 UTC (rev 103284)
@@ -0,0 +1,99 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""Test Browser Languages detector
+
+$Id$
+"""
+import unittest
+
+from zope.publisher.browser import BrowserLanguages
+from zope.publisher.browser import CacheableBrowserLanguages
+from zope.publisher.browser import ModifiableBrowserLanguages
+from zope.publisher.browser import NotCompatibleAdapterError
+
+# Note: The expected output is in order of preference,
+# empty 'q=' means 'q=1', and if theres more than one
+# empty, we assume they are in order of preference.
+data = [
+    ('da, en, pt', ['da', 'en', 'pt']),
+    ('da, en;q=.9, en-gb;q=1.0, en-us', ['da', 'en-gb', 'en-us', 'en']),
+    ('pt_BR; q=0.6, pt_PT; q = .7, en-gb', ['en-gb', 'pt-pt', 'pt-br']),
+    ('en-us, en_GB;q=0.9, en, pt_BR; q=1.0', ['en-us', 'en', 'pt-br', 'en-gb']),
+    ('ro,en-us;q=0.8,es;q=0.5,fr;q=0.3', ['ro', 'en-us', 'es', 'fr']),
+    ('ro,en-us;q=0,es;q=0.5,fr;q=0,ru;q=1,it', ['ro', 'ru', 'it', 'es'])
+    ]
+
+class TestRequest(dict):
+
+    def __init__(self, languages):
+        self.annotations = {}
+        self.localized = False
+        self["HTTP_ACCEPT_LANGUAGE"] = languages
+
+    def setupLocale(self):
+        self.localized = True
+
+class BrowserLanguagesTest(unittest.TestCase):
+
+    def factory(self, request):
+        return BrowserLanguages(request)
+
+    def test_browser_language_handling(self):
+        for req, expected in data:
+            request = TestRequest(req)
+            browser_languages = self.factory(request)
+            self.assertEqual(list(browser_languages.getPreferredLanguages()),
+                             expected)
+
+class CacheableBrowserLanguagesTests(BrowserLanguagesTest):
+
+    def factory(self, request):
+        return CacheableBrowserLanguages(request)
+
+    def test_cached_languages(self):
+        eq = self.failUnlessEqual
+        request = TestRequest("da, en, pt")
+        browser_languages = self.factory(request)
+        eq(list(browser_languages.getPreferredLanguages()), ["da", "en", "pt"])
+        request["HTTP_ACCEPT_LANGUAGE"] = "ru, en"
+        eq(list(browser_languages.getPreferredLanguages()), ["da", "en", "pt"])
+
+class ModifiableBrowserLanguagesTests(CacheableBrowserLanguagesTests):
+
+    def factory(self, request):
+        return ModifiableBrowserLanguages(request)
+
+    def test_setPreferredLanguages(self):
+        eq = self.failUnlessEqual
+        request = TestRequest("da, en, pt")
+        browser_languages = self.factory(request)
+        eq(list(browser_languages.getPreferredLanguages()), ["da", "en", "pt"])
+        browser_languages.setPreferredLanguages(["ru", "en"])
+        self.failUnless(request.localized)
+        eq(list(browser_languages.getPreferredLanguages()), ["ru", "en"])
+
+    def test_conflicting_adapters(self):
+        request = TestRequest("da, en, pt")
+        not_compatible_browser_languages = BrowserLanguages(request)
+        browser_languages = self.factory(request)
+        self.assertRaises(NotCompatibleAdapterError,
+            browser_languages.setPreferredLanguages, ["ru", "en"])
+
+
+def test_suite():
+    suite = unittest.TestSuite()
+    suite.addTest(unittest.makeSuite(BrowserLanguagesTest))
+    suite.addTest(unittest.makeSuite(CacheableBrowserLanguagesTests))
+    suite.addTest(unittest.makeSuite(ModifiableBrowserLanguagesTests))
+    return suite

Deleted: zope.publisher/tags/3.9.0/src/zope/publisher/tests/test_browserrequest.py
===================================================================
--- zope.publisher/trunk/src/zope/publisher/tests/test_browserrequest.py	2009-08-27 14:38:12 UTC (rev 103281)
+++ zope.publisher/tags/3.9.0/src/zope/publisher/tests/test_browserrequest.py	2009-08-27 14:51:30 UTC (rev 103284)
@@ -1,585 +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.
-#
-##############################################################################
-
-import sys
-import unittest
-from StringIO import StringIO
-
-from zope.interface import implements, directlyProvides, Interface
-from zope.interface.verify import verifyObject
-
-from zope.publisher.publish import publish as publish_
-from zope.publisher.http import HTTPCharsets
-from zope.publisher.browser import BrowserRequest
-from zope.publisher.interfaces import NotFound
-from zope.publisher.interfaces import ISkinnable
-from zope.publisher.interfaces.browser import IBrowserApplicationRequest
-from zope.publisher.interfaces.browser import IBrowserRequest
-from zope.publisher.interfaces.browser import IBrowserPublication
-from zope.publisher.base import DefaultPublication
-
-from zope.publisher.tests.test_http import HTTPTests
-from zope.publisher.tests.publication import TestPublication
-
-from zope.publisher.tests.basetestipublicationrequest \
-     import BaseTestIPublicationRequest
-from zope.publisher.tests.basetestipublisherrequest \
-     import BaseTestIPublisherRequest
-from zope.publisher.tests.basetestiapplicationrequest \
-     import BaseTestIApplicationRequest
-
-LARGE_FILE_BODY = """-----------------------------1
-Content-Disposition: form-data; name="upload"; filename="test"
-Content-Type: text/plain
-
-Here comes some text! %s
------------------------------1--
-""" % ('test' * 1000)
-
-def publish(request):
-    publish_(request, handle_errors=0)
-
-class Publication(DefaultPublication):
-
-    def getDefaultTraversal(self, request, ob):
-        if hasattr(ob, 'browserDefault'):
-            return ob.browserDefault(request)
-        return ob, ()
-
-class TestBrowserRequest(BrowserRequest, HTTPCharsets):
-    """Make sure that our request also implements IHTTPCharsets, so that we do
-    not need to register any adapters."""
-
-    def __init__(self, *args, **kw):
-        self.request = self
-        BrowserRequest.__init__(self, *args, **kw)
-
-
-class BrowserTests(HTTPTests):
-
-    _testEnv =  {
-        'PATH_INFO':           '/folder/item',
-        'QUERY_STRING':        'a=5&b:int=6',
-        'SERVER_URL':          'http://foobar.com',
-        'HTTP_HOST':           'foobar.com',
-        'CONTENT_LENGTH':      '0',
-        'HTTP_AUTHORIZATION':  'Should be in accessible',
-        'GATEWAY_INTERFACE':   'TestFooInterface/1.0',
-        'HTTP_OFF_THE_WALL':   "Spam 'n eggs",
-        'HTTP_ACCEPT_CHARSET': 'ISO-8859-1, UTF-8;q=0.66, UTF-16;q=0.33',
-    }
-
-    def setUp(self):
-        super(BrowserTests, self).setUp()
-
-        class AppRoot(object):
-            """Required docstring for the publisher."""
-
-        class Folder(object):
-            """Required docstring for the publisher."""
-
-        class Item(object):
-            """Required docstring for the publisher."""
-            def __call__(self, a, b):
-                return u"%s, %s" % (`a`, `b`)
-
-        class Item3(object):
-            """Required docstring for the publisher."""
-            def __call__(self, *args):
-                return u"..."
-
-        class View(object):
-            """Required docstring for the publisher."""
-            def browserDefault(self, request):
-                return self, ['index']
-
-            def index(self, a, b):
-                """Required docstring for the publisher."""
-                return u"%s, %s" % (`a`, `b`)
-
-        class Item2(object):
-            """Required docstring for the publisher."""
-            view = View()
-
-            def browserDefault(self, request):
-                return self, ['view']
-
-
-        self.app = AppRoot()
-        self.app.folder = Folder()
-        self.app.folder.item = Item()
-        self.app.folder.item2 = Item2()
-        self.app.folder.item3 = Item3()
-
-    def _createRequest(self, extra_env={}, body=""):
-        env = self._testEnv.copy()
-        env.update(extra_env)
-        if len(body):
-            env['CONTENT_LENGTH'] = str(len(body))
-
-        publication = Publication(self.app)
-        instream = StringIO(body)
-        request = TestBrowserRequest(instream, env)
-        request.setPublication(publication)
-        return request
-
-    def testTraversalToItem(self):
-        res = self._publisherResults()
-        self.failUnlessEqual(
-            res,
-            "Status: 200 Ok\r\n"
-            "Content-Length: 7\r\n"
-            "Content-Type: text/plain;charset=utf-8\r\n"
-            "X-Content-Type-Warning: guessed from content\r\n"
-            "X-Powered-By: Zope (www.zope.org), Python (www.python.org)\r\n"
-            "\r\n"
-            "u'5', 6")
-
-    def testNoDefault(self):
-        request = self._createRequest()
-        response = request.response
-        publish(request)
-        self.failIf(response.getBase())
-
-    def testDefault(self):
-        extra = {'PATH_INFO': '/folder/item2'}
-        request = self._createRequest(extra)
-        response = request.response
-        publish(request)
-        self.assertEqual(response.getBase(),
-                         'http://foobar.com/folder/item2/view/index')
-
-    def testDefaultPOST(self):
-        extra = {'PATH_INFO': '/folder/item2', "REQUEST_METHOD": "POST"}
-        request = self._createRequest(extra, body='a=5&b:int=6')
-        response = request.response
-        publish(request)
-        self.assertEqual(response.getBase(),
-                         'http://foobar.com/folder/item2/view/index')
-
-    def testNoneFieldNamePost(self):
-
-        """Produce a Fieldstorage with a name wich is None, this
-        should be catched"""
-        
-        extra = {'REQUEST_METHOD':'POST',
-                 'PATH_INFO': u'/',
-                 'CONTENT_TYPE': 'multipart/form-data;\
-                 boundary=---------------------------1'}
-
-        body = """-----------------------------1
-        Content-Disposition: form-data; name="field.contentType"
-        ...
-        application/octet-stream
-        -----------------------------1--
-        """
-        request  = self._createRequest(extra,body=body)
-        request.processInputs()
-
-    def testFileUploadPost(self):
-        """Produce a Fieldstorage with a file handle that exposes
-        its filename."""
-
-        extra = {'REQUEST_METHOD':'POST',
-                 'PATH_INFO': u'/',
-                 'CONTENT_TYPE': 'multipart/form-data;\
-                 boundary=---------------------------1'}
-        
-        request  = self._createRequest(extra, body=LARGE_FILE_BODY)
-        request.processInputs()
-        self.assert_(request.form['upload'].name)
-
-    def testDefault2(self):
-        extra = {'PATH_INFO': '/folder/item2/view'}
-        request = self._createRequest(extra)
-        response = request.response
-        publish(request)
-        self.assertEqual(response.getBase(),
-                         'http://foobar.com/folder/item2/view/index')
-
-    def testDefault3(self):
-        extra = {'PATH_INFO': '/folder/item2/view/index'}
-        request = self._createRequest(extra)
-        response = request.response
-        publish(request)
-        self.failIf(response.getBase())
-
-    def testDefault4(self):
-        extra = {'PATH_INFO': '/folder/item2/view/'}
-        request = self._createRequest(extra)
-        response = request.response
-        publish(request)
-        self.failIf(response.getBase())
-
-    def testDefault6(self):
-        extra = {'PATH_INFO': '/folder/item2/'}
-        request = self._createRequest(extra)
-        response = request.response
-        publish(request)
-        self.assertEqual(response.getBase(),
-                         'http://foobar.com/folder/item2/view/index')
-
-    def testBadPath(self):
-        extra = {'PATH_INFO': '/folder/nothere/'}
-        request = self._createRequest(extra)
-        self.assertRaises(NotFound, publish, request)
-
-    def testBadPath2(self):
-        extra = {'PATH_INFO': '/folder%2Fitem2/'}
-        request = self._createRequest(extra)
-        self.assertRaises(NotFound, publish, request)
-
-    def testForm(self):
-        request = self._createRequest()
-        publish(request)
-        self.assertEqual(request.form,
-                         {u'a':u'5', u'b':6})
-
-    def testFormNoEncodingUsesUTF8(self):
-        encoded = 'K\xc3\x83\xc2\xb6hlerstra\xc3\x83\xc2\x9fe'
-        extra = {
-            # if nothing else is specified, form data should be
-            # interpreted as UTF-8, as this stub query string is
-            'QUERY_STRING': 'a=5&b:int=6&street=' + encoded
-            }
-        request = self._createRequest(extra)
-        # many mainstream browsers do not send HTTP_ACCEPT_CHARSET
-        del request._environ['HTTP_ACCEPT_CHARSET']
-        publish(request)
-        self.assert_(isinstance(request.form[u'street'], unicode))
-        self.assertEqual(unicode(encoded, 'utf-8'), request.form['street'])
-
-    def testFormListTypes(self):
-        extra = {'QUERY_STRING':'a:list=5&a:list=6&b=1'}
-        request = self._createRequest(extra)
-        publish(request)
-        self.assertEqual(request.form, {u'a':[u'5',u'6'], u'b':u'1'})
-
-    def testFormTupleTypes(self):
-        extra = {'QUERY_STRING':'a:tuple=5&a:tuple=6&b=1'}
-        request = self._createRequest(extra)
-        publish(request)
-        self.assertEqual(request.form, {u'a':(u'5',u'6'), u'b':u'1'})
-
-    def testFormTupleRecordTypes(self):
-        extra = {'QUERY_STRING':'a.x:tuple:record=5&a.x:tuple:record=6&b=1'}
-        request = self._createRequest(extra)
-        publish(request)
-        keys = request.form.keys()
-        keys.sort()
-        self.assertEqual(keys, [u'a',u'b'])
-        self.assertEqual(request.form[u'b'], u'1')
-        self.assertEqual(request.form[u'a'].keys(), [u'x'])
-        self.assertEqual(request.form[u'a'][u'x'], (u'5',u'6'))
-        self.assertEqual(request.form[u'a'].x, (u'5',u'6'))
-        self.assertEqual(str(request.form[u'a']), "{x: (u'5', u'6')}")
-        self.assertEqual(repr(request.form[u'a']), "{x: (u'5', u'6')}")
-
-    def testFormRecordsTypes(self):
-        extra = {'QUERY_STRING':'a.x:records=5&a.x:records=6&b=1'}
-        request = self._createRequest(extra)
-        publish(request)
-        keys = request.form.keys()
-        keys.sort()
-        self.assertEqual(keys, [u'a',u'b'])
-        self.assertEqual(request.form[u'b'], u'1')
-        self.assertEqual(len(request.form[u'a']), 2)
-        self.assertEqual(request.form[u'a'][0][u'x'], u'5')
-        self.assertEqual(request.form[u'a'][0].x, u'5')
-        self.assertEqual(request.form[u'a'][1][u'x'], u'6')
-        self.assertEqual(request.form[u'a'][1].x, u'6')
-        self.assertEqual(str(request.form[u'a']), "[{x: u'5'}, {x: u'6'}]")
-        self.assertEqual(repr(request.form[u'a']), "[{x: u'5'}, {x: u'6'}]")
-
-    def testFormMultipleRecordsTypes(self):
-        extra = {'QUERY_STRING':'a.x:records:int=5&a.y:records:int=51'
-            '&a.x:records:int=6&a.y:records:int=61&b=1'}
-        request = self._createRequest(extra)
-        publish(request)
-        keys = request.form.keys()
-        keys.sort()
-        self.assertEqual(keys, [u'a',u'b'])
-        self.assertEqual(request.form[u'b'], u'1')
-        self.assertEqual(len(request.form[u'a']), 2)
-        self.assertEqual(request.form[u'a'][0][u'x'], 5)
-        self.assertEqual(request.form[u'a'][0].x, 5)
-        self.assertEqual(request.form[u'a'][0][u'y'], 51)
-        self.assertEqual(request.form[u'a'][0].y, 51)
-        self.assertEqual(request.form[u'a'][1][u'x'], 6)
-        self.assertEqual(request.form[u'a'][1].x, 6)
-        self.assertEqual(request.form[u'a'][1][u'y'], 61)
-        self.assertEqual(request.form[u'a'][1].y, 61)
-        self.assertEqual(str(request.form[u'a']),
-            "[{x: 5, y: 51}, {x: 6, y: 61}]")
-        self.assertEqual(repr(request.form[u'a']),
-            "[{x: 5, y: 51}, {x: 6, y: 61}]")
-
-    def testFormListRecordTypes(self):
-        extra = {'QUERY_STRING':'a.x:list:record=5&a.x:list:record=6&b=1'}
-        request = self._createRequest(extra)
-        publish(request)
-        keys = request.form.keys()
-        keys.sort()
-        self.assertEqual(keys, [u'a',u'b'])
-        self.assertEqual(request.form[u'b'], u'1')
-        self.assertEqual(request.form[u'a'].keys(), [u'x'])
-        self.assertEqual(request.form[u'a'][u'x'], [u'5',u'6'])
-        self.assertEqual(request.form[u'a'].x, [u'5',u'6'])
-        self.assertEqual(str(request.form[u'a']), "{x: [u'5', u'6']}")
-        self.assertEqual(repr(request.form[u'a']), "{x: [u'5', u'6']}")
-
-    def testFormListTypes2(self):
-        extra = {'QUERY_STRING':'a=5&a=6&b=1'}
-        request = self._createRequest(extra)
-        publish(request)
-        self.assertEqual(request.form, {u'a':[u'5',u'6'], u'b':u'1'})
-
-    def testFormIntTypes(self):
-        extra = {'QUERY_STRING':'a:int=5&b:int=-5&c:int=0&d:int=-0'}
-        request = self._createRequest(extra)
-        publish(request)
-        self.assertEqual(request.form, {u'a': 5, u'b': -5, u'c': 0, u'd': 0})
-
-        extra = {'QUERY_STRING':'a:int='}
-        request = self._createRequest(extra)
-        self.assertRaises(ValueError, publish, request)
-
-        extra = {'QUERY_STRING':'a:int=abc'}
-        request = self._createRequest(extra)
-        self.assertRaises(ValueError, publish, request)
-
-    def testFormFloatTypes(self):
-        extra = {'QUERY_STRING':'a:float=5&b:float=-5.01&c:float=0'}
-        request = self._createRequest(extra)
-        publish(request)
-        self.assertEqual(request.form, {u'a': 5.0, u'b': -5.01, u'c': 0.0})
-
-        extra = {'QUERY_STRING':'a:float='}
-        request = self._createRequest(extra)
-        self.assertRaises(ValueError, publish, request)
-
-        extra = {'QUERY_STRING':'a:float=abc'}
-        request = self._createRequest(extra)
-        self.assertRaises(ValueError, publish, request)
-
-    def testFormLongTypes(self):
-        extra = {'QUERY_STRING':'a:long=99999999999999&b:long=0L'}
-        request = self._createRequest(extra)
-        publish(request)
-        self.assertEqual(request.form, {u'a': 99999999999999, u'b': 0})
-
-        extra = {'QUERY_STRING':'a:long='}
-        request = self._createRequest(extra)
-        self.assertRaises(ValueError, publish, request)
-
-        extra = {'QUERY_STRING':'a:long=abc'}
-        request = self._createRequest(extra)
-        self.assertRaises(ValueError, publish, request)
-
-    def testFormTokensTypes(self):
-        extra = {'QUERY_STRING':'a:tokens=a%20b%20c%20d&b:tokens='}
-        request = self._createRequest(extra)
-        publish(request)
-        self.assertEqual(request.form, {u'a': [u'a', u'b', u'c', u'd'],
-                         u'b': []})
-
-    def testFormStringTypes(self):
-        extra = {'QUERY_STRING':'a:string=test&b:string='}
-        request = self._createRequest(extra)
-        publish(request)
-        self.assertEqual(request.form, {u'a': u'test', u'b': u''})
-
-    def testFormLinesTypes(self):
-        extra = {'QUERY_STRING':'a:lines=a%0ab%0ac%0ad&b:lines='}
-        request = self._createRequest(extra)
-        publish(request)
-        self.assertEqual(request.form, {u'a': [u'a', u'b', u'c', u'd'],
-                         u'b': []})
-
-    def testFormTextTypes(self):
-        extra = {'QUERY_STRING':'a:text=a%0a%0db%0d%0ac%0dd%0ae&b:text='}
-        request = self._createRequest(extra)
-        publish(request)
-        self.assertEqual(request.form, {u'a': u'a\nb\nc\nd\ne', u'b': u''})
-
-    def testFormRequiredTypes(self):
-        extra = {'QUERY_STRING':'a:required=%20'}
-        request = self._createRequest(extra)
-        self.assertRaises(ValueError, publish, request)
-
-    def testFormBooleanTypes(self):
-        extra = {'QUERY_STRING':'a:boolean=&b:boolean=1&c:boolean=%20'}
-        request = self._createRequest(extra)
-        publish(request)
-        self.assertEqual(request.form, {u'a': False, u'b': True, u'c': True})
-
-    def testFormDefaults(self):
-        extra = {'QUERY_STRING':'a:default=10&a=6&b=1'}
-        request = self._createRequest(extra)
-        publish(request)
-        self.assertEqual(request.form, {u'a':u'6', u'b':u'1'})
-
-    def testFormDefaults2(self):
-        extra = {'QUERY_STRING':'a:default=10&b=1'}
-        request = self._createRequest(extra)
-        publish(request)
-        self.assertEqual(request.form, {u'a':u'10', u'b':u'1'})
-
-    def testFormFieldName(self):
-        extra = {'QUERY_STRING':'c+%2B%2F%3D%26c%3Aint=6',
-                 'PATH_INFO': '/folder/item3/'}
-        request = self._createRequest(extra)
-        publish(request)
-        self.assertEqual(request.form, {u'c +/=&c': 6})
-
-    def testFormFieldValue(self):
-        extra = {'QUERY_STRING':'a=b+%2B%2F%3D%26b%3Aint',
-                 'PATH_INFO': '/folder/item3/'}
-        request = self._createRequest(extra)
-        publish(request)
-        self.assertEqual(request.form, {u'a':u'b +/=&b:int'})
-
-    def testInterface(self):
-        request = self._createRequest()
-        verifyObject(IBrowserRequest, request)
-        verifyObject(IBrowserApplicationRequest, request)
-        verifyObject(ISkinnable, request)
-
-    def testIssue394(self):
-        extra = {'PATH_INFO': '/folder/item3/'}
-        request = self._createRequest(extra)
-        del request._environ["QUERY_STRING"]
-        argv = sys.argv
-        sys.argv = [argv[0], "test"]
-        try:
-            publish(request)
-            self.assertEqual(request.form, {})
-        finally:
-            sys.argv = argv
-
-    def testIssue559(self):
-        extra = {'QUERY_STRING': 'HTTP_REFERER=peter',
-                 'HTTP_REFERER':'http://localhost/',
-                 'PATH_INFO': '/folder/item3/'}
-        request = self._createRequest(extra)
-        publish(request)
-        self.assertEqual(request.headers.get('HTTP_REFERER'), 'http://localhost/')
-        self.assertEqual(request.form, {u'HTTP_REFERER': u'peter'})
-
-
-    def test_post_body_not_consumed_unnecessarily(self):
-        request = self._createRequest(
-            dict(REQUEST_METHOD='POST',
-                 CONTENT_TYPE='application/x-foo',
-                 ),
-            'test body')
-        request.processInputs()
-        self.assertEqual(request.bodyStream.read(), 'test body')
-
-    def test_post_body_not_necessarily(self):
-        request = self._createRequest(
-            dict(REQUEST_METHOD='POST',
-                 CONTENT_TYPE='application/x-www-form-urlencoded',
-                 QUERY_STRING='',
-                 ),
-            'x=1&y=2')
-        request.processInputs()
-        self.assertEqual(request.bodyStream.read(), '')
-        self.assertEqual(dict(request.form), dict(x='1', y='2'))
-
-        request = self._createRequest(
-            dict(REQUEST_METHOD='POST',
-                 CONTENT_TYPE=('application/x-www-form-urlencoded'
-                               '; charset=UTF-8'),
-                 QUERY_STRING='',
-                 ),
-            'x=1&y=2')
-        request.processInputs()
-        self.assertEqual(request.bodyStream.read(), '')
-        self.assertEqual(dict(request.form), dict(x='1', y='2'))
-
-class TestBrowserPublication(TestPublication):
-    implements(IBrowserPublication)
-
-    def getDefaultTraversal(self, request, ob):
-        return ob, ()
-
-class APITests(BaseTestIPublicationRequest,
-               BaseTestIApplicationRequest,
-               BaseTestIPublisherRequest,
-               unittest.TestCase):
-
-    def _Test__new(self, environ=None, **kw):
-        if environ is None:
-            environ = kw
-        return BrowserRequest(StringIO(''), environ)
-
-    def test_IApplicationRequest_bodyStream(self):
-        request = BrowserRequest(StringIO('spam'), {})
-        self.assertEqual(request.bodyStream.read(), 'spam')
-
-    # Needed by BaseTestIEnumerableMapping tests:
-    def _IEnumerableMapping__stateDict(self):
-        return {'id': 'ZopeOrg', 'title': 'Zope Community Web Site',
-                'greet': 'Welcome to the Zope Community Web site'}
-
-    def _IEnumerableMapping__sample(self):
-        return self._Test__new(**(self._IEnumerableMapping__stateDict()))
-
-    def _IEnumerableMapping__absentKeys(self):
-        return 'foo', 'bar'
-
-    def test_IPublicationRequest_getPositionalArguments(self):
-        self.assertEqual(self._Test__new().getPositionalArguments(), ())
-
-    def test_IPublisherRequest_retry(self):
-        self.assertEqual(self._Test__new().supportsRetry(), True)
-
-    def test_IPublisherRequest_processInputs(self):
-        self._Test__new().processInputs()
-
-    def test_IPublisherRequest_traverse(self):
-        request = self._Test__new()
-        request.setPublication(TestBrowserPublication())
-        app = request.publication.getApplication(request)
-
-        request.setTraversalStack([])
-        self.assertEqual(request.traverse(app).name, '')
-        self.assertEqual(request._last_obj_traversed, app)
-        request.setTraversalStack(['ZopeCorp'])
-        self.assertEqual(request.traverse(app).name, 'ZopeCorp')
-        self.assertEqual(request._last_obj_traversed, app.ZopeCorp)
-        request.setTraversalStack(['Engineering', 'ZopeCorp'])
-        self.assertEqual(request.traverse(app).name, 'Engineering')
-        self.assertEqual(request._last_obj_traversed, app.ZopeCorp.Engineering)
-
-    def test_IBrowserRequest(self):
-        verifyObject(IBrowserRequest, self._Test__new())
-
-    def test_ISkinnable(self):
-        self.assertEqual(ISkinnable.providedBy(self._Test__new()), True)
-
-    def testVerifyISkinnable(self):
-        verifyObject(ISkinnable, self._Test__new())
-
-
-def test_suite():
-    suite = unittest.TestSuite()
-    suite.addTest(unittest.makeSuite(BrowserTests))
-    suite.addTest(unittest.makeSuite(APITests))
-    return suite
-
-
-if __name__ == '__main__':
-    unittest.TextTestRunner().run(test_suite())

Copied: zope.publisher/tags/3.9.0/src/zope/publisher/tests/test_browserrequest.py (from rev 103282, zope.publisher/trunk/src/zope/publisher/tests/test_browserrequest.py)
===================================================================
--- zope.publisher/tags/3.9.0/src/zope/publisher/tests/test_browserrequest.py	                        (rev 0)
+++ zope.publisher/tags/3.9.0/src/zope/publisher/tests/test_browserrequest.py	2009-08-27 14:51:30 UTC (rev 103284)
@@ -0,0 +1,599 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+
+import sys
+import unittest
+from StringIO import StringIO
+
+from zope.interface import implements, directlyProvides, Interface
+from zope.interface.verify import verifyObject
+
+from zope.publisher.publish import publish as publish_
+from zope.publisher.http import HTTPCharsets
+from zope.publisher.browser import BrowserRequest
+from zope.publisher.interfaces import NotFound
+from zope.publisher.interfaces import ISkinnable
+from zope.publisher.interfaces.browser import IBrowserApplicationRequest
+from zope.publisher.interfaces.browser import IBrowserRequest
+from zope.publisher.interfaces.browser import IBrowserPublication
+from zope.publisher.base import DefaultPublication
+
+from zope.publisher.tests.test_http import HTTPTests
+from zope.publisher.tests.publication import TestPublication
+
+from zope.publisher.tests.basetestipublicationrequest \
+     import BaseTestIPublicationRequest
+from zope.publisher.tests.basetestipublisherrequest \
+     import BaseTestIPublisherRequest
+from zope.publisher.tests.basetestiapplicationrequest \
+     import BaseTestIApplicationRequest
+
+LARGE_FILE_BODY = """-----------------------------1
+Content-Disposition: form-data; name="upload"; filename="test"
+Content-Type: text/plain
+
+Here comes some text! %s
+-----------------------------1--
+""" % ('test' * 1000)
+
+IE_FILE_BODY = """-----------------------------1
+Content-Disposition: form-data; name="upload"; filename="C:\\Windows\\notepad.exe"
+Content-Type: text/plain
+
+Some data
+-----------------------------1--
+"""
+
+
+def publish(request):
+    publish_(request, handle_errors=0)
+
+class Publication(DefaultPublication):
+
+    def getDefaultTraversal(self, request, ob):
+        if hasattr(ob, 'browserDefault'):
+            return ob.browserDefault(request)
+        return ob, ()
+
+class TestBrowserRequest(BrowserRequest, HTTPCharsets):
+    """Make sure that our request also implements IHTTPCharsets, so that we do
+    not need to register any adapters."""
+
+    def __init__(self, *args, **kw):
+        self.request = self
+        BrowserRequest.__init__(self, *args, **kw)
+
+
+class BrowserTests(HTTPTests):
+
+    _testEnv =  {
+        'PATH_INFO':           '/folder/item',
+        'QUERY_STRING':        'a=5&b:int=6',
+        'SERVER_URL':          'http://foobar.com',
+        'HTTP_HOST':           'foobar.com',
+        'CONTENT_LENGTH':      '0',
+        'HTTP_AUTHORIZATION':  'Should be in accessible',
+        'GATEWAY_INTERFACE':   'TestFooInterface/1.0',
+        'HTTP_OFF_THE_WALL':   "Spam 'n eggs",
+        'HTTP_ACCEPT_CHARSET': 'ISO-8859-1, UTF-8;q=0.66, UTF-16;q=0.33',
+    }
+
+    def setUp(self):
+        super(BrowserTests, self).setUp()
+
+        class AppRoot(object):
+            """Required docstring for the publisher."""
+
+        class Folder(object):
+            """Required docstring for the publisher."""
+
+        class Item(object):
+            """Required docstring for the publisher."""
+            def __call__(self, a, b):
+                return u"%s, %s" % (`a`, `b`)
+
+        class Item3(object):
+            """Required docstring for the publisher."""
+            def __call__(self, *args):
+                return u"..."
+
+        class View(object):
+            """Required docstring for the publisher."""
+            def browserDefault(self, request):
+                return self, ['index']
+
+            def index(self, a, b):
+                """Required docstring for the publisher."""
+                return u"%s, %s" % (`a`, `b`)
+
+        class Item2(object):
+            """Required docstring for the publisher."""
+            view = View()
+
+            def browserDefault(self, request):
+                return self, ['view']
+
+
+        self.app = AppRoot()
+        self.app.folder = Folder()
+        self.app.folder.item = Item()
+        self.app.folder.item2 = Item2()
+        self.app.folder.item3 = Item3()
+
+    def _createRequest(self, extra_env={}, body=""):
+        env = self._testEnv.copy()
+        env.update(extra_env)
+        if len(body):
+            env['CONTENT_LENGTH'] = str(len(body))
+
+        publication = Publication(self.app)
+        instream = StringIO(body)
+        request = TestBrowserRequest(instream, env)
+        request.setPublication(publication)
+        return request
+
+    def testTraversalToItem(self):
+        res = self._publisherResults()
+        self.failUnlessEqual(
+            res,
+            "Status: 200 Ok\r\n"
+            "Content-Length: 7\r\n"
+            "Content-Type: text/plain;charset=utf-8\r\n"
+            "X-Content-Type-Warning: guessed from content\r\n"
+            "X-Powered-By: Zope (www.zope.org), Python (www.python.org)\r\n"
+            "\r\n"
+            "u'5', 6")
+
+    def testNoDefault(self):
+        request = self._createRequest()
+        response = request.response
+        publish(request)
+        self.failIf(response.getBase())
+
+    def testDefault(self):
+        extra = {'PATH_INFO': '/folder/item2'}
+        request = self._createRequest(extra)
+        response = request.response
+        publish(request)
+        self.assertEqual(response.getBase(),
+                         'http://foobar.com/folder/item2/view/index')
+
+    def testDefaultPOST(self):
+        extra = {'PATH_INFO': '/folder/item2', "REQUEST_METHOD": "POST"}
+        request = self._createRequest(extra, body='a=5&b:int=6')
+        response = request.response
+        publish(request)
+        self.assertEqual(response.getBase(),
+                         'http://foobar.com/folder/item2/view/index')
+
+    def testNoneFieldNamePost(self):
+
+        """Produce a Fieldstorage with a name wich is None, this
+        should be catched"""
+        
+        extra = {'REQUEST_METHOD':'POST',
+                 'PATH_INFO': u'/',
+                 'CONTENT_TYPE': 'multipart/form-data;\
+                 boundary=---------------------------1'}
+
+        body = """-----------------------------1
+        Content-Disposition: form-data; name="field.contentType"
+        ...
+        application/octet-stream
+        -----------------------------1--
+        """
+        request  = self._createRequest(extra,body=body)
+        request.processInputs()
+
+    def testFileUploadPost(self):
+        """Produce a Fieldstorage with a file handle that exposes
+        its filename."""
+
+        extra = {'REQUEST_METHOD':'POST',
+                 'PATH_INFO': u'/',
+                 'CONTENT_TYPE': 'multipart/form-data;\
+                 boundary=---------------------------1'}
+        
+        request  = self._createRequest(extra, body=LARGE_FILE_BODY)
+        request.processInputs()
+        self.assert_(request.form['upload'].name)
+
+        request  = self._createRequest(extra, body=IE_FILE_BODY)
+        request.processInputs()
+        self.assertEquals(request.form['upload'].filename, 'notepad.exe')
+
+
+    def testDefault2(self):
+        extra = {'PATH_INFO': '/folder/item2/view'}
+        request = self._createRequest(extra)
+        response = request.response
+        publish(request)
+        self.assertEqual(response.getBase(),
+                         'http://foobar.com/folder/item2/view/index')
+
+    def testDefault3(self):
+        extra = {'PATH_INFO': '/folder/item2/view/index'}
+        request = self._createRequest(extra)
+        response = request.response
+        publish(request)
+        self.failIf(response.getBase())
+
+    def testDefault4(self):
+        extra = {'PATH_INFO': '/folder/item2/view/'}
+        request = self._createRequest(extra)
+        response = request.response
+        publish(request)
+        self.failIf(response.getBase())
+
+    def testDefault6(self):
+        extra = {'PATH_INFO': '/folder/item2/'}
+        request = self._createRequest(extra)
+        response = request.response
+        publish(request)
+        self.assertEqual(response.getBase(),
+                         'http://foobar.com/folder/item2/view/index')
+
+    def testBadPath(self):
+        extra = {'PATH_INFO': '/folder/nothere/'}
+        request = self._createRequest(extra)
+        self.assertRaises(NotFound, publish, request)
+
+    def testBadPath2(self):
+        extra = {'PATH_INFO': '/folder%2Fitem2/'}
+        request = self._createRequest(extra)
+        self.assertRaises(NotFound, publish, request)
+
+    def testForm(self):
+        request = self._createRequest()
+        publish(request)
+        self.assertEqual(request.form,
+                         {u'a':u'5', u'b':6})
+
+    def testFormNoEncodingUsesUTF8(self):
+        encoded = 'K\xc3\x83\xc2\xb6hlerstra\xc3\x83\xc2\x9fe'
+        extra = {
+            # if nothing else is specified, form data should be
+            # interpreted as UTF-8, as this stub query string is
+            'QUERY_STRING': 'a=5&b:int=6&street=' + encoded
+            }
+        request = self._createRequest(extra)
+        # many mainstream browsers do not send HTTP_ACCEPT_CHARSET
+        del request._environ['HTTP_ACCEPT_CHARSET']
+        publish(request)
+        self.assert_(isinstance(request.form[u'street'], unicode))
+        self.assertEqual(unicode(encoded, 'utf-8'), request.form['street'])
+
+    def testFormListTypes(self):
+        extra = {'QUERY_STRING':'a:list=5&a:list=6&b=1'}
+        request = self._createRequest(extra)
+        publish(request)
+        self.assertEqual(request.form, {u'a':[u'5',u'6'], u'b':u'1'})
+
+    def testFormTupleTypes(self):
+        extra = {'QUERY_STRING':'a:tuple=5&a:tuple=6&b=1'}
+        request = self._createRequest(extra)
+        publish(request)
+        self.assertEqual(request.form, {u'a':(u'5',u'6'), u'b':u'1'})
+
+    def testFormTupleRecordTypes(self):
+        extra = {'QUERY_STRING':'a.x:tuple:record=5&a.x:tuple:record=6&b=1'}
+        request = self._createRequest(extra)
+        publish(request)
+        keys = request.form.keys()
+        keys.sort()
+        self.assertEqual(keys, [u'a',u'b'])
+        self.assertEqual(request.form[u'b'], u'1')
+        self.assertEqual(request.form[u'a'].keys(), [u'x'])
+        self.assertEqual(request.form[u'a'][u'x'], (u'5',u'6'))
+        self.assertEqual(request.form[u'a'].x, (u'5',u'6'))
+        self.assertEqual(str(request.form[u'a']), "{x: (u'5', u'6')}")
+        self.assertEqual(repr(request.form[u'a']), "{x: (u'5', u'6')}")
+
+    def testFormRecordsTypes(self):
+        extra = {'QUERY_STRING':'a.x:records=5&a.x:records=6&b=1'}
+        request = self._createRequest(extra)
+        publish(request)
+        keys = request.form.keys()
+        keys.sort()
+        self.assertEqual(keys, [u'a',u'b'])
+        self.assertEqual(request.form[u'b'], u'1')
+        self.assertEqual(len(request.form[u'a']), 2)
+        self.assertEqual(request.form[u'a'][0][u'x'], u'5')
+        self.assertEqual(request.form[u'a'][0].x, u'5')
+        self.assertEqual(request.form[u'a'][1][u'x'], u'6')
+        self.assertEqual(request.form[u'a'][1].x, u'6')
+        self.assertEqual(str(request.form[u'a']), "[{x: u'5'}, {x: u'6'}]")
+        self.assertEqual(repr(request.form[u'a']), "[{x: u'5'}, {x: u'6'}]")
+
+    def testFormMultipleRecordsTypes(self):
+        extra = {'QUERY_STRING':'a.x:records:int=5&a.y:records:int=51'
+            '&a.x:records:int=6&a.y:records:int=61&b=1'}
+        request = self._createRequest(extra)
+        publish(request)
+        keys = request.form.keys()
+        keys.sort()
+        self.assertEqual(keys, [u'a',u'b'])
+        self.assertEqual(request.form[u'b'], u'1')
+        self.assertEqual(len(request.form[u'a']), 2)
+        self.assertEqual(request.form[u'a'][0][u'x'], 5)
+        self.assertEqual(request.form[u'a'][0].x, 5)
+        self.assertEqual(request.form[u'a'][0][u'y'], 51)
+        self.assertEqual(request.form[u'a'][0].y, 51)
+        self.assertEqual(request.form[u'a'][1][u'x'], 6)
+        self.assertEqual(request.form[u'a'][1].x, 6)
+        self.assertEqual(request.form[u'a'][1][u'y'], 61)
+        self.assertEqual(request.form[u'a'][1].y, 61)
+        self.assertEqual(str(request.form[u'a']),
+            "[{x: 5, y: 51}, {x: 6, y: 61}]")
+        self.assertEqual(repr(request.form[u'a']),
+            "[{x: 5, y: 51}, {x: 6, y: 61}]")
+
+    def testFormListRecordTypes(self):
+        extra = {'QUERY_STRING':'a.x:list:record=5&a.x:list:record=6&b=1'}
+        request = self._createRequest(extra)
+        publish(request)
+        keys = request.form.keys()
+        keys.sort()
+        self.assertEqual(keys, [u'a',u'b'])
+        self.assertEqual(request.form[u'b'], u'1')
+        self.assertEqual(request.form[u'a'].keys(), [u'x'])
+        self.assertEqual(request.form[u'a'][u'x'], [u'5',u'6'])
+        self.assertEqual(request.form[u'a'].x, [u'5',u'6'])
+        self.assertEqual(str(request.form[u'a']), "{x: [u'5', u'6']}")
+        self.assertEqual(repr(request.form[u'a']), "{x: [u'5', u'6']}")
+
+    def testFormListTypes2(self):
+        extra = {'QUERY_STRING':'a=5&a=6&b=1'}
+        request = self._createRequest(extra)
+        publish(request)
+        self.assertEqual(request.form, {u'a':[u'5',u'6'], u'b':u'1'})
+
+    def testFormIntTypes(self):
+        extra = {'QUERY_STRING':'a:int=5&b:int=-5&c:int=0&d:int=-0'}
+        request = self._createRequest(extra)
+        publish(request)
+        self.assertEqual(request.form, {u'a': 5, u'b': -5, u'c': 0, u'd': 0})
+
+        extra = {'QUERY_STRING':'a:int='}
+        request = self._createRequest(extra)
+        self.assertRaises(ValueError, publish, request)
+
+        extra = {'QUERY_STRING':'a:int=abc'}
+        request = self._createRequest(extra)
+        self.assertRaises(ValueError, publish, request)
+
+    def testFormFloatTypes(self):
+        extra = {'QUERY_STRING':'a:float=5&b:float=-5.01&c:float=0'}
+        request = self._createRequest(extra)
+        publish(request)
+        self.assertEqual(request.form, {u'a': 5.0, u'b': -5.01, u'c': 0.0})
+
+        extra = {'QUERY_STRING':'a:float='}
+        request = self._createRequest(extra)
+        self.assertRaises(ValueError, publish, request)
+
+        extra = {'QUERY_STRING':'a:float=abc'}
+        request = self._createRequest(extra)
+        self.assertRaises(ValueError, publish, request)
+
+    def testFormLongTypes(self):
+        extra = {'QUERY_STRING':'a:long=99999999999999&b:long=0L'}
+        request = self._createRequest(extra)
+        publish(request)
+        self.assertEqual(request.form, {u'a': 99999999999999, u'b': 0})
+
+        extra = {'QUERY_STRING':'a:long='}
+        request = self._createRequest(extra)
+        self.assertRaises(ValueError, publish, request)
+
+        extra = {'QUERY_STRING':'a:long=abc'}
+        request = self._createRequest(extra)
+        self.assertRaises(ValueError, publish, request)
+
+    def testFormTokensTypes(self):
+        extra = {'QUERY_STRING':'a:tokens=a%20b%20c%20d&b:tokens='}
+        request = self._createRequest(extra)
+        publish(request)
+        self.assertEqual(request.form, {u'a': [u'a', u'b', u'c', u'd'],
+                         u'b': []})
+
+    def testFormStringTypes(self):
+        extra = {'QUERY_STRING':'a:string=test&b:string='}
+        request = self._createRequest(extra)
+        publish(request)
+        self.assertEqual(request.form, {u'a': u'test', u'b': u''})
+
+    def testFormLinesTypes(self):
+        extra = {'QUERY_STRING':'a:lines=a%0ab%0ac%0ad&b:lines='}
+        request = self._createRequest(extra)
+        publish(request)
+        self.assertEqual(request.form, {u'a': [u'a', u'b', u'c', u'd'],
+                         u'b': []})
+
+    def testFormTextTypes(self):
+        extra = {'QUERY_STRING':'a:text=a%0a%0db%0d%0ac%0dd%0ae&b:text='}
+        request = self._createRequest(extra)
+        publish(request)
+        self.assertEqual(request.form, {u'a': u'a\nb\nc\nd\ne', u'b': u''})
+
+    def testFormRequiredTypes(self):
+        extra = {'QUERY_STRING':'a:required=%20'}
+        request = self._createRequest(extra)
+        self.assertRaises(ValueError, publish, request)
+
+    def testFormBooleanTypes(self):
+        extra = {'QUERY_STRING':'a:boolean=&b:boolean=1&c:boolean=%20'}
+        request = self._createRequest(extra)
+        publish(request)
+        self.assertEqual(request.form, {u'a': False, u'b': True, u'c': True})
+
+    def testFormDefaults(self):
+        extra = {'QUERY_STRING':'a:default=10&a=6&b=1'}
+        request = self._createRequest(extra)
+        publish(request)
+        self.assertEqual(request.form, {u'a':u'6', u'b':u'1'})
+
+    def testFormDefaults2(self):
+        extra = {'QUERY_STRING':'a:default=10&b=1'}
+        request = self._createRequest(extra)
+        publish(request)
+        self.assertEqual(request.form, {u'a':u'10', u'b':u'1'})
+
+    def testFormFieldName(self):
+        extra = {'QUERY_STRING':'c+%2B%2F%3D%26c%3Aint=6',
+                 'PATH_INFO': '/folder/item3/'}
+        request = self._createRequest(extra)
+        publish(request)
+        self.assertEqual(request.form, {u'c +/=&c': 6})
+
+    def testFormFieldValue(self):
+        extra = {'QUERY_STRING':'a=b+%2B%2F%3D%26b%3Aint',
+                 'PATH_INFO': '/folder/item3/'}
+        request = self._createRequest(extra)
+        publish(request)
+        self.assertEqual(request.form, {u'a':u'b +/=&b:int'})
+
+    def testInterface(self):
+        request = self._createRequest()
+        verifyObject(IBrowserRequest, request)
+        verifyObject(IBrowserApplicationRequest, request)
+        verifyObject(ISkinnable, request)
+
+    def testIssue394(self):
+        extra = {'PATH_INFO': '/folder/item3/'}
+        request = self._createRequest(extra)
+        del request._environ["QUERY_STRING"]
+        argv = sys.argv
+        sys.argv = [argv[0], "test"]
+        try:
+            publish(request)
+            self.assertEqual(request.form, {})
+        finally:
+            sys.argv = argv
+
+    def testIssue559(self):
+        extra = {'QUERY_STRING': 'HTTP_REFERER=peter',
+                 'HTTP_REFERER':'http://localhost/',
+                 'PATH_INFO': '/folder/item3/'}
+        request = self._createRequest(extra)
+        publish(request)
+        self.assertEqual(request.headers.get('HTTP_REFERER'), 'http://localhost/')
+        self.assertEqual(request.form, {u'HTTP_REFERER': u'peter'})
+
+
+    def test_post_body_not_consumed_unnecessarily(self):
+        request = self._createRequest(
+            dict(REQUEST_METHOD='POST',
+                 CONTENT_TYPE='application/x-foo',
+                 ),
+            'test body')
+        request.processInputs()
+        self.assertEqual(request.bodyStream.read(), 'test body')
+
+    def test_post_body_not_necessarily(self):
+        request = self._createRequest(
+            dict(REQUEST_METHOD='POST',
+                 CONTENT_TYPE='application/x-www-form-urlencoded',
+                 QUERY_STRING='',
+                 ),
+            'x=1&y=2')
+        request.processInputs()
+        self.assertEqual(request.bodyStream.read(), '')
+        self.assertEqual(dict(request.form), dict(x='1', y='2'))
+
+        request = self._createRequest(
+            dict(REQUEST_METHOD='POST',
+                 CONTENT_TYPE=('application/x-www-form-urlencoded'
+                               '; charset=UTF-8'),
+                 QUERY_STRING='',
+                 ),
+            'x=1&y=2')
+        request.processInputs()
+        self.assertEqual(request.bodyStream.read(), '')
+        self.assertEqual(dict(request.form), dict(x='1', y='2'))
+
+class TestBrowserPublication(TestPublication):
+    implements(IBrowserPublication)
+
+    def getDefaultTraversal(self, request, ob):
+        return ob, ()
+
+class APITests(BaseTestIPublicationRequest,
+               BaseTestIApplicationRequest,
+               BaseTestIPublisherRequest,
+               unittest.TestCase):
+
+    def _Test__new(self, environ=None, **kw):
+        if environ is None:
+            environ = kw
+        return BrowserRequest(StringIO(''), environ)
+
+    def test_IApplicationRequest_bodyStream(self):
+        request = BrowserRequest(StringIO('spam'), {})
+        self.assertEqual(request.bodyStream.read(), 'spam')
+
+    # Needed by BaseTestIEnumerableMapping tests:
+    def _IEnumerableMapping__stateDict(self):
+        return {'id': 'ZopeOrg', 'title': 'Zope Community Web Site',
+                'greet': 'Welcome to the Zope Community Web site'}
+
+    def _IEnumerableMapping__sample(self):
+        return self._Test__new(**(self._IEnumerableMapping__stateDict()))
+
+    def _IEnumerableMapping__absentKeys(self):
+        return 'foo', 'bar'
+
+    def test_IPublicationRequest_getPositionalArguments(self):
+        self.assertEqual(self._Test__new().getPositionalArguments(), ())
+
+    def test_IPublisherRequest_retry(self):
+        self.assertEqual(self._Test__new().supportsRetry(), True)
+
+    def test_IPublisherRequest_processInputs(self):
+        self._Test__new().processInputs()
+
+    def test_IPublisherRequest_traverse(self):
+        request = self._Test__new()
+        request.setPublication(TestBrowserPublication())
+        app = request.publication.getApplication(request)
+
+        request.setTraversalStack([])
+        self.assertEqual(request.traverse(app).name, '')
+        self.assertEqual(request._last_obj_traversed, app)
+        request.setTraversalStack(['ZopeCorp'])
+        self.assertEqual(request.traverse(app).name, 'ZopeCorp')
+        self.assertEqual(request._last_obj_traversed, app.ZopeCorp)
+        request.setTraversalStack(['Engineering', 'ZopeCorp'])
+        self.assertEqual(request.traverse(app).name, 'Engineering')
+        self.assertEqual(request._last_obj_traversed, app.ZopeCorp.Engineering)
+
+    def test_IBrowserRequest(self):
+        verifyObject(IBrowserRequest, self._Test__new())
+
+    def test_ISkinnable(self):
+        self.assertEqual(ISkinnable.providedBy(self._Test__new()), True)
+
+    def testVerifyISkinnable(self):
+        verifyObject(ISkinnable, self._Test__new())
+
+
+def test_suite():
+    suite = unittest.TestSuite()
+    suite.addTest(unittest.makeSuite(BrowserTests))
+    suite.addTest(unittest.makeSuite(APITests))
+    return suite
+
+
+if __name__ == '__main__':
+    unittest.TextTestRunner().run(test_suite())

Copied: zope.publisher/tags/3.9.0/src/zope/publisher/tests/test_zcml.py (from rev 103282, zope.publisher/trunk/src/zope/publisher/tests/test_zcml.py)
===================================================================
--- zope.publisher/tags/3.9.0/src/zope/publisher/tests/test_zcml.py	                        (rev 0)
+++ zope.publisher/tags/3.9.0/src/zope/publisher/tests/test_zcml.py	2009-08-27 14:51:30 UTC (rev 103284)
@@ -0,0 +1,169 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""Tests for browser:defaultSkin and browser:defaultView directives
+
+$Id: tests.py 103163 2009-08-24 16:22:07Z nadako $
+"""
+from cStringIO import StringIO
+import unittest
+
+from zope.testing import cleanup
+from zope import component
+
+from zope.configuration.xmlconfig import XMLConfig, xmlconfig
+from zope.publisher.browser import TestRequest, BrowserView
+from zope.publisher.defaultview import getDefaultViewName
+from zope.publisher.interfaces import IDefaultViewName, IDefaultSkin
+from zope.publisher.interfaces.browser import IBrowserRequest
+from zope.interface import Interface, implements, providedBy, directlyProvides
+
+import zope.publisher
+
+class IOb(Interface):
+    pass
+
+class Ob(object):
+    implements(IOb)
+
+class ITestLayer(IBrowserRequest):
+    """Test Layer."""
+
+class ITestSkin(ITestLayer):
+    """Test Skin."""
+
+class V1(BrowserView):
+    pass
+
+class V2(BrowserView):
+    pass
+
+
+request = TestRequest()
+ob = Ob()
+
+template = """<configure
+   xmlns='http://namespaces.zope.org/zope'
+   xmlns:browser='http://namespaces.zope.org/browser'
+   i18n_domain='zope'>
+   %s
+   </configure>"""
+
+class Test(cleanup.CleanUp, unittest.TestCase):
+
+    def setUp(self):
+        super(Test, self).setUp()
+        XMLConfig('meta.zcml', zope.publisher)()
+
+    def testDefaultView(self):
+        self.assertTrue(
+            component.queryMultiAdapter((ob, request), IDefaultViewName) is None)
+        xmlconfig(StringIO(template % (
+            '''
+            <browser:defaultView
+                name="test"
+                for="zope.publisher.tests.test_zcml.IOb" />
+            '''
+            )))
+
+        self.assertEqual(getDefaultViewName(ob, request), 'test')
+
+    def testDefaultViewWithLayer(self):
+        class FakeRequest(TestRequest):
+            implements(ITestLayer)
+        request2 = FakeRequest()
+
+        self.assertEqual(
+            component.queryMultiAdapter((ob, request2), IDefaultViewName),
+            None)
+
+        xmlconfig(StringIO(template % (
+            '''
+            <browser:defaultView
+                for="zope.publisher.tests.test_zcml.IOb"
+                name="test"
+                />
+            <browser:defaultView
+                for="zope.publisher.tests.test_zcml.IOb"
+                layer="zope.publisher.tests.test_zcml.ITestLayer"
+                name="test2"
+                />
+            '''
+            )))
+
+        self.assertEqual(
+            zope.publisher.defaultview.getDefaultViewName(ob, request2),
+            'test2')
+        self.assertEqual(
+            zope.publisher.defaultview.getDefaultViewName(ob, request),
+            'test')
+
+    def testDefaultViewForClass(self):
+        self.assertEqual(
+            component.queryMultiAdapter((ob, request), IDefaultViewName),
+            None)
+
+        xmlconfig(StringIO(template % (
+            '''
+            <browser:defaultView
+                for="zope.publisher.tests.test_zcml.Ob"
+                name="test"
+                />
+            '''
+            )))
+
+        self.assertEqual(
+            zope.publisher.defaultview.getDefaultViewName(ob, request),
+            'test')
+
+    def testDefaultSkin(self):
+        request = TestRequest()
+        self.assertEqual(
+            component.queryMultiAdapter((ob, request), name='test'),
+            None)
+
+        XMLConfig('meta.zcml', component)()
+        xmlconfig(StringIO(template % (
+            '''
+            <interface
+                interface="
+                  zope.publisher.tests.test_zcml.ITestSkin"
+                type="zope.publisher.interfaces.browser.IBrowserSkinType"
+                name="Test Skin"
+                />
+            <browser:defaultSkin name="Test Skin" />
+            <view
+                for="zope.publisher.tests.test_zcml.IOb"
+                type="zope.publisher.interfaces.browser.IDefaultBrowserLayer"
+                name="test"
+                factory="zope.publisher.tests.test_zcml.V1"
+                />
+            <view
+                for="zope.publisher.tests.test_zcml.IOb"
+                type="zope.publisher.tests.test_zcml.ITestLayer"
+                name="test"
+                factory="zope.publisher.tests.test_zcml.V2"
+                />
+            '''
+            )))
+
+        # Simulate Zope Publication behavior in beforeTraversal()
+        adapters = component.getSiteManager().adapters
+        skin = adapters.lookup((providedBy(request),), IDefaultSkin, '')
+        directlyProvides(request, skin)
+
+        v = component.queryMultiAdapter((ob, request), name='test')
+        self.assertTrue(isinstance(v, V2))
+
+def test_suite():
+    return unittest.makeSuite(Test)

Deleted: zope.publisher/tags/3.9.0/src/zope/publisher/xmlrpc.py
===================================================================
--- zope.publisher/trunk/src/zope/publisher/xmlrpc.py	2009-08-27 14:38:12 UTC (rev 103281)
+++ zope.publisher/tags/3.9.0/src/zope/publisher/xmlrpc.py	2009-08-27 14:51:30 UTC (rev 103284)
@@ -1,234 +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.
-#
-##############################################################################
-"""XML-RPC Publisher
-
-This module contains the XMLRPCRequest and XMLRPCResponse
-
-$Id$
-"""
-__docformat__ = 'restructuredtext'
-
-import sys
-import xmlrpclib
-import datetime
-from StringIO import StringIO
-
-import zope.component
-import zope.interface
-from zope.interface import implements
-from zope.publisher.interfaces.xmlrpc import \
-        IXMLRPCPublisher, IXMLRPCRequest, IXMLRPCPremarshaller
-
-from zope.publisher.http import HTTPRequest, HTTPResponse, DirectResult
-from zope.security.proxy import isinstance
-
-class XMLRPCRequest(HTTPRequest):
-    implements(IXMLRPCRequest)
-
-    _args = ()
-
-    def _createResponse(self):
-        """Create a specific XML-RPC response object."""
-        return XMLRPCResponse()
-
-    def processInputs(self):
-        'See IPublisherRequest'
-        # Parse the request XML structure
-
-        # XXX using readlines() instead of lines()
-        # as twisted's BufferedStream sends back
-        # an empty stream here for read() (bug)
-        lines = ''.join(self._body_instream.readlines())
-        self._args, function = xmlrpclib.loads(lines)
-
-        # Translate '.' to '/' in function to represent object traversal.
-        function = function.split('.')
-
-        if function:
-            self.setPathSuffix(function)
-
-
-class TestRequest(XMLRPCRequest):
-
-    def __init__(self, body_instream=None, environ=None, response=None, **kw):
-
-        _testEnv =  {
-            'SERVER_URL':         'http://127.0.0.1',
-            'HTTP_HOST':          '127.0.0.1',
-            'CONTENT_LENGTH':     '0',
-            'GATEWAY_INTERFACE':  'TestFooInterface/1.0',
-            }
-
-        if environ:
-            _testEnv.update(environ)
-        if kw:
-            _testEnv.update(kw)
-        if body_instream is None:
-            body_instream = StringIO('')
-
-        super(TestRequest, self).__init__(body_instream, _testEnv, response)
-
-
-class XMLRPCResponse(HTTPResponse):
-    """XMLRPC response.
-
-    This object is responsible for converting all output to valid XML-RPC.
-    """
-
-    def setResult(self, result):
-        """Sets the result of the response
-
-        Sets the return body equal to the (string) argument "body". Also
-        updates the "content-length" return header.
-
-        If the body is a 2-element tuple, then it will be treated
-        as (title,body)
-
-        If is_error is true then the HTML will be formatted as a Zope error
-        message instead of a generic HTML page.
-        """
-        body = premarshal(result)
-        if isinstance(body, xmlrpclib.Fault):
-            # Convert Fault object to XML-RPC response.
-            body = xmlrpclib.dumps(body, methodresponse=True)
-        else:
-            # Marshall our body as an XML-RPC response. Strings will be sent
-            # as strings, integers as integers, etc.  We do *not* convert
-            # everything to a string first.
-            try:
-                body = xmlrpclib.dumps((body,), methodresponse=True,
-                                       allow_none=True)
-            except:
-                # We really want to catch all exceptions at this point!
-                self.handleException(sys.exc_info())
-                return
-
-        headers = [('content-type', 'text/xml;charset=utf-8'),
-                   ('content-length', str(len(body)))]
-        self._headers.update(dict((k, [v]) for (k, v) in headers))
-        super(XMLRPCResponse, self).setResult(DirectResult((body,)))
-
-
-    def handleException(self, exc_info):
-        """Handle Errors during publsihing and wrap it in XML-RPC XML
-
-        >>> import sys
-        >>> resp = XMLRPCResponse()
-        >>> try:
-        ...     raise AttributeError('xyz')
-        ... except:
-        ...     exc_info = sys.exc_info()
-        ...     resp.handleException(exc_info)
-
-        >>> resp.getStatusString()
-        '200 OK'
-        >>> resp.getHeader('content-type')
-        'text/xml;charset=utf-8'
-        >>> body = ''.join(resp.consumeBody())
-        >>> 'Unexpected Zope exception: AttributeError: xyz' in body
-        True
-        """
-        t, value = exc_info[:2]
-        s = '%s: %s' % (getattr(t, '__name__', t), value)
-
-        # Create an appropriate Fault object. Unfortunately, we throw away
-        # most of the debugging information. More useful error reporting is
-        # left as an exercise for the reader.
-        Fault = xmlrpclib.Fault
-        fault_text = None
-        try:
-            if isinstance(value, Fault):
-                fault_text = value
-            elif isinstance(value, Exception):
-                fault_text = Fault(-1, "Unexpected Zope exception: " + s)
-            else:
-                fault_text = Fault(-2, "Unexpected Zope error value: " + s)
-        except:
-            fault_text = Fault(-3, "Unknown Zope fault type")
-
-        # Do the damage.
-        self.setResult(fault_text)
-        # XML-RPC prefers a status of 200 ("ok") even when reporting errors.
-        self.setStatus(200)
-
-class PreMarshallerBase(object):
-    """Abstract base class for pre-marshallers."""
-    zope.interface.implements(IXMLRPCPremarshaller)
-
-    def __init__(self, data):
-        self.data = data
-
-    def __call__(self):
-        raise Exception, "Not implemented"
-
-class DictPreMarshaller(PreMarshallerBase):
-    """Pre-marshaller for dicts"""
-    zope.component.adapts(dict)
-
-    def __call__(self):
-        return dict([(premarshal(k), premarshal(v))
-                     for (k, v) in self.data.items()])
-
-class ListPreMarshaller(PreMarshallerBase):
-    """Pre-marshaller for list"""
-    zope.component.adapts(list)
-
-    def __call__(self):
-        return map(premarshal, self.data)
-
-class TuplePreMarshaller(ListPreMarshaller):
-    zope.component.adapts(tuple)
-
-class BinaryPreMarshaller(PreMarshallerBase):
-    """Pre-marshaller for xmlrpc.Binary"""
-    zope.component.adapts(xmlrpclib.Binary)
-
-    def __call__(self):
-        return xmlrpclib.Binary(self.data.data)
-
-class FaultPreMarshaller(PreMarshallerBase):
-    """Pre-marshaller for xmlrpc.Fault"""
-    zope.component.adapts(xmlrpclib.Fault)
-
-    def __call__(self):
-        return xmlrpclib.Fault(
-            premarshal(self.data.faultCode),
-            premarshal(self.data.faultString),
-            )
-
-class DateTimePreMarshaller(PreMarshallerBase):
-    """Pre-marshaller for xmlrpc.DateTime"""
-    zope.component.adapts(xmlrpclib.DateTime)
-
-    def __call__(self):
-        return xmlrpclib.DateTime(self.data.value)
-
-class PythonDateTimePreMarshaller(PreMarshallerBase):
-    """Pre-marshaller for datetime.datetime"""
-    zope.component.adapts(datetime.datetime)
-
-    def __call__(self):
-        return xmlrpclib.DateTime(self.data.isoformat())
-
-def premarshal(data):
-    """Premarshal data before handing it to xmlrpclib for marhalling
-
-    The initial purpose of this function is to remove security proxies
-    without resorting to removeSecurityProxy.   This way, we can avoid
-    inadvertently providing access to data that should be protected.
-    """
-    premarshaller = IXMLRPCPremarshaller(data, alternate=None)
-    if premarshaller is not None:
-        return premarshaller()
-    return data

Copied: zope.publisher/tags/3.9.0/src/zope/publisher/xmlrpc.py (from rev 103282, zope.publisher/trunk/src/zope/publisher/xmlrpc.py)
===================================================================
--- zope.publisher/tags/3.9.0/src/zope/publisher/xmlrpc.py	                        (rev 0)
+++ zope.publisher/tags/3.9.0/src/zope/publisher/xmlrpc.py	2009-08-27 14:51:30 UTC (rev 103284)
@@ -0,0 +1,244 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""XML-RPC Publisher
+
+This module contains the XMLRPCRequest and XMLRPCResponse
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import sys
+import xmlrpclib
+import datetime
+from StringIO import StringIO
+
+import zope.component
+import zope.interface
+from zope.interface import implements
+from zope.publisher.interfaces.xmlrpc import \
+        IXMLRPCPublisher, IXMLRPCRequest, IXMLRPCPremarshaller, IXMLRPCView
+
+from zope.publisher.http import HTTPRequest, HTTPResponse, DirectResult
+from zope.security.proxy import isinstance
+
+class XMLRPCRequest(HTTPRequest):
+    implements(IXMLRPCRequest)
+
+    _args = ()
+
+    def _createResponse(self):
+        """Create a specific XML-RPC response object."""
+        return XMLRPCResponse()
+
+    def processInputs(self):
+        'See IPublisherRequest'
+        # Parse the request XML structure
+
+        # XXX using readlines() instead of lines()
+        # as twisted's BufferedStream sends back
+        # an empty stream here for read() (bug)
+        lines = ''.join(self._body_instream.readlines())
+        self._args, function = xmlrpclib.loads(lines)
+
+        # Translate '.' to '/' in function to represent object traversal.
+        function = function.split('.')
+
+        if function:
+            self.setPathSuffix(function)
+
+
+class TestRequest(XMLRPCRequest):
+
+    def __init__(self, body_instream=None, environ=None, response=None, **kw):
+
+        _testEnv =  {
+            'SERVER_URL':         'http://127.0.0.1',
+            'HTTP_HOST':          '127.0.0.1',
+            'CONTENT_LENGTH':     '0',
+            'GATEWAY_INTERFACE':  'TestFooInterface/1.0',
+            }
+
+        if environ:
+            _testEnv.update(environ)
+        if kw:
+            _testEnv.update(kw)
+        if body_instream is None:
+            body_instream = StringIO('')
+
+        super(TestRequest, self).__init__(body_instream, _testEnv, response)
+
+
+class XMLRPCResponse(HTTPResponse):
+    """XMLRPC response.
+
+    This object is responsible for converting all output to valid XML-RPC.
+    """
+
+    def setResult(self, result):
+        """Sets the result of the response
+
+        Sets the return body equal to the (string) argument "body". Also
+        updates the "content-length" return header.
+
+        If the body is a 2-element tuple, then it will be treated
+        as (title,body)
+
+        If is_error is true then the HTML will be formatted as a Zope error
+        message instead of a generic HTML page.
+        """
+        body = premarshal(result)
+        if isinstance(body, xmlrpclib.Fault):
+            # Convert Fault object to XML-RPC response.
+            body = xmlrpclib.dumps(body, methodresponse=True)
+        else:
+            # Marshall our body as an XML-RPC response. Strings will be sent
+            # as strings, integers as integers, etc.  We do *not* convert
+            # everything to a string first.
+            try:
+                body = xmlrpclib.dumps((body,), methodresponse=True,
+                                       allow_none=True)
+            except:
+                # We really want to catch all exceptions at this point!
+                self.handleException(sys.exc_info())
+                return
+
+        headers = [('content-type', 'text/xml;charset=utf-8'),
+                   ('content-length', str(len(body)))]
+        self._headers.update(dict((k, [v]) for (k, v) in headers))
+        super(XMLRPCResponse, self).setResult(DirectResult((body,)))
+
+
+    def handleException(self, exc_info):
+        """Handle Errors during publsihing and wrap it in XML-RPC XML
+
+        >>> import sys
+        >>> resp = XMLRPCResponse()
+        >>> try:
+        ...     raise AttributeError('xyz')
+        ... except:
+        ...     exc_info = sys.exc_info()
+        ...     resp.handleException(exc_info)
+
+        >>> resp.getStatusString()
+        '200 OK'
+        >>> resp.getHeader('content-type')
+        'text/xml;charset=utf-8'
+        >>> body = ''.join(resp.consumeBody())
+        >>> 'Unexpected Zope exception: AttributeError: xyz' in body
+        True
+        """
+        t, value = exc_info[:2]
+        s = '%s: %s' % (getattr(t, '__name__', t), value)
+
+        # Create an appropriate Fault object. Unfortunately, we throw away
+        # most of the debugging information. More useful error reporting is
+        # left as an exercise for the reader.
+        Fault = xmlrpclib.Fault
+        fault_text = None
+        try:
+            if isinstance(value, Fault):
+                fault_text = value
+            elif isinstance(value, Exception):
+                fault_text = Fault(-1, "Unexpected Zope exception: " + s)
+            else:
+                fault_text = Fault(-2, "Unexpected Zope error value: " + s)
+        except:
+            fault_text = Fault(-3, "Unknown Zope fault type")
+
+        # Do the damage.
+        self.setResult(fault_text)
+        # XML-RPC prefers a status of 200 ("ok") even when reporting errors.
+        self.setStatus(200)
+
+
+class XMLRPCView(object):
+    """A base XML-RPC view that can be used as mix-in for XML-RPC views.""" 
+    implements(IXMLRPCView)
+
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+
+
+class PreMarshallerBase(object):
+    """Abstract base class for pre-marshallers."""
+    zope.interface.implements(IXMLRPCPremarshaller)
+
+    def __init__(self, data):
+        self.data = data
+
+    def __call__(self):
+        raise Exception, "Not implemented"
+
+class DictPreMarshaller(PreMarshallerBase):
+    """Pre-marshaller for dicts"""
+    zope.component.adapts(dict)
+
+    def __call__(self):
+        return dict([(premarshal(k), premarshal(v))
+                     for (k, v) in self.data.items()])
+
+class ListPreMarshaller(PreMarshallerBase):
+    """Pre-marshaller for list"""
+    zope.component.adapts(list)
+
+    def __call__(self):
+        return map(premarshal, self.data)
+
+class TuplePreMarshaller(ListPreMarshaller):
+    zope.component.adapts(tuple)
+
+class BinaryPreMarshaller(PreMarshallerBase):
+    """Pre-marshaller for xmlrpc.Binary"""
+    zope.component.adapts(xmlrpclib.Binary)
+
+    def __call__(self):
+        return xmlrpclib.Binary(self.data.data)
+
+class FaultPreMarshaller(PreMarshallerBase):
+    """Pre-marshaller for xmlrpc.Fault"""
+    zope.component.adapts(xmlrpclib.Fault)
+
+    def __call__(self):
+        return xmlrpclib.Fault(
+            premarshal(self.data.faultCode),
+            premarshal(self.data.faultString),
+            )
+
+class DateTimePreMarshaller(PreMarshallerBase):
+    """Pre-marshaller for xmlrpc.DateTime"""
+    zope.component.adapts(xmlrpclib.DateTime)
+
+    def __call__(self):
+        return xmlrpclib.DateTime(self.data.value)
+
+class PythonDateTimePreMarshaller(PreMarshallerBase):
+    """Pre-marshaller for datetime.datetime"""
+    zope.component.adapts(datetime.datetime)
+
+    def __call__(self):
+        return xmlrpclib.DateTime(self.data.isoformat())
+
+def premarshal(data):
+    """Premarshal data before handing it to xmlrpclib for marhalling
+
+    The initial purpose of this function is to remove security proxies
+    without resorting to removeSecurityProxy.   This way, we can avoid
+    inadvertently providing access to data that should be protected.
+    """
+    premarshaller = IXMLRPCPremarshaller(data, alternate=None)
+    if premarshaller is not None:
+        return premarshaller()
+    return data

Copied: zope.publisher/tags/3.9.0/src/zope/publisher/zcml.py (from rev 103282, zope.publisher/trunk/src/zope/publisher/zcml.py)
===================================================================
--- zope.publisher/tags/3.9.0/src/zope/publisher/zcml.py	                        (rev 0)
+++ zope.publisher/tags/3.9.0/src/zope/publisher/zcml.py	2009-08-27 14:51:30 UTC (rev 103284)
@@ -0,0 +1,120 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""Default view and default skin ZCML configuration feature.
+
+$Id: metaconfigure.py 103163 2009-08-24 16:22:07Z nadako $
+"""
+from zope import component
+from zope.component.interface import provideInterface
+from zope.component.zcml import handler
+from zope.configuration.fields import GlobalObject, GlobalInterface
+from zope.interface import Interface
+from zope.publisher.interfaces import IDefaultViewName
+from zope.publisher.interfaces.browser import IBrowserRequest
+from zope.publisher.interfaces.browser import IBrowserSkinType
+from zope.publisher.interfaces.browser import IDefaultSkin
+from zope.schema import TextLine
+
+
+class IDefaultSkinDirective(Interface):
+    """Sets the default browser skin"""
+
+    name = TextLine(
+        title=u"Default skin name",
+        description=u"Default skin name",
+        required=True
+        )
+
+
+class IDefaultViewDirective(Interface):
+    """
+    The name of the view that should be the default.
+
+    This name refers to view that should be the
+    view used by default (if no view name is supplied
+    explicitly).
+    """
+
+    name = TextLine(
+        title=u"The name of the view that should be the default.",
+        description=u"""
+        This name refers to view that should be the view used by
+        default (if no view name is supplied explicitly).""",
+        required=True
+        )
+
+    for_ = GlobalObject(
+        title=u"The interface this view is the default for.",
+        description=u"""Specifies the interface for which the view is
+        registered. All objects implementing this interface can make use of
+        this view. If this attribute is not specified, the view is available
+        for all objects.""",
+        required=False
+        )
+
+    layer = GlobalInterface(
+        title=u"The layer the default view is declared for",
+        description=u"The default layer for which the default view is "
+                    u"applicable. By default it is applied to all layers.",
+        required=False
+        )
+
+
+def setDefaultSkin(name, info=''):
+    """Set the default skin.
+
+    >>> from zope.interface import directlyProvides
+    >>> from zope.app.testing import ztapi
+
+    >>> class Skin1: pass
+    >>> directlyProvides(Skin1, IBrowserSkinType)
+
+    >>> ztapi.provideUtility(IBrowserSkinType, Skin1, 'Skin1')
+    >>> setDefaultSkin('Skin1')
+    >>> adapters = component.getSiteManager().adapters
+
+	Lookup the default skin for a request that has the
+
+    >>> adapters.lookup((IBrowserRequest,), IDefaultSkin, '') is Skin1
+    True
+    """
+    skin = component.getUtility(IBrowserSkinType, name)
+    handler('registerAdapter',
+            skin, (IBrowserRequest,), IDefaultSkin, '', info),
+
+
+def defaultSkin(_context, name):
+
+    _context.action(
+        discriminator = 'defaultSkin',
+        callable = setDefaultSkin,
+        args = (name, _context.info)
+        )
+
+
+def defaultView(_context, name, for_=None, layer=IBrowserRequest):
+
+    _context.action(
+        discriminator = ('defaultViewName', for_, layer, name),
+        callable = handler,
+        args = ('registerAdapter',
+                name, (for_, layer), IDefaultViewName, '', _context.info)
+        )
+
+    if for_ is not None:
+        _context.action(
+            discriminator = None,
+            callable = provideInterface,
+            args = ('', for_)
+            )



More information about the Checkins mailing list