[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