diff --git a/CHANGES.rst b/CHANGES.rst
index b81a3f7..18b01f5 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -10,6 +10,10 @@
 - ``FileUpload`` objects now support the ``seekable()`` method on Python 3.
   Fixes `issue 44 <https://github.com/zopefoundation/zope.publisher/issue/44>`_.
 
+- Character set handling was rather comprehensively broken on Python 3.
+  It should be fixed now.  See `issue 41
+  <https://github.com/zopefoundation/zope.publisher/issue/41>`_.
+
 
 5.0.1 (2018-10-19)
 ==================
diff --git a/src/zope/publisher/browser.py b/src/zope/publisher/browser.py
index ca10d3f..2cf2546 100644
--- a/src/zope/publisher/browser.py
+++ b/src/zope/publisher/browser.py
@@ -20,9 +20,10 @@ packaged into a nice, Python-friendly 'FileUpload' object.
 """
 __docformat__ = 'restructuredtext'
 
+import locale
 import re
-from cgi import FieldStorage
 import tempfile
+from cgi import FieldStorage
 
 import zope.component
 import zope.interface
@@ -247,22 +248,30 @@ class BrowserRequest(HTTPRequest):
 
     def _decode(self, text):
         """Try to decode the text using one of the available charsets."""
-        # According to PEP-3333, in python-3, QUERY_STRING is a string,
-        # representing 'latin-1' encoded byte array. So, if we are in python-3
-        # context, encode text as 'latin-1' first, to try to decode
-        # resulting byte array using user-supplied charset.
-        if not isinstance(text, bytes):
-            text = text.encode('latin-1')
+        # All text comes from cgi.FieldStorage.  On Python 2 it's all bytes
+        # and we must decode.  On Python 3 it's already been decoded into
+        # Unicode, using the charset we specified when instantiating the
+        # FieldStorage instance (Latin-1).
         if self.charsets is None:
             envadapter = IUserPreferredCharsets(self)
             self.charsets = envadapter.getPreferredCharsets() or ['utf-8']
             self.charsets = [c for c in self.charsets if c != '*']
+        if not PYTHON2 and not isinstance(text, bytes):
+            if self.charsets and self.charsets[0] == 'iso-8859-1':
+                # optimization: we are trying to decode something
+                # cgi.FieldStorage already decoded for us, let's just return it
+                # rather than waste time decoding...
+                return text
+            # undo what cgi.FieldStorage did and maintain backwards compat
+            text = text.encode('latin-1')
         for charset in self.charsets:
             try:
                 text = text.decode(charset)
                 break
             except UnicodeError:
                 pass
+        # XXX so when none of the provided charsets works we just return bytes
+        # and let the application crash???
         return text
 
     def processInputs(self):
@@ -298,8 +307,18 @@ class BrowserRequest(HTTPRequest):
             env = env.copy()
             del env['QUERY_STRING']
 
-
-        args = {'encoding': 'utf-8'} if not PYTHON2 else {}
+        if not PYTHON2 and 'QUERY_STRING' in env:
+            # According to PEP-3333, in python-3, QUERY_STRING is a string,
+            # representing 'latin-1' encoded byte array. So, if we are in python-3
+            # context, encode text as 'latin-1' first, to try to decode
+            # resulting byte array using user-supplied charset.
+            #
+            # We also need to re-encode it in locale.getpreferredencoding() so that cgi.py
+            # FieldStorage can later decode it.
+            qs = env['QUERY_STRING'].encode('latin-1')
+            env['QUERY_STRING'] = qs.decode(locale.getpreferredencoding(), 'surrogateescape')
+
+        args = {'encoding': 'latin-1'} if not PYTHON2 else {}
         fs = ZopeFieldStorage(fp=fp, environ=env,
                               keep_blank_values=1, **args)
         # On python 3.4 and up, FieldStorage explictly closes files
diff --git a/tox.ini b/tox.ini
index 1ce8300..c0937de 100644
--- a/tox.ini
+++ b/tox.ini
@@ -4,7 +4,7 @@ envlist =
 
 [testenv]
 commands =
-    zope-testrunner --test-path=src {posargs:-pvc}
+    zope-testrunner --test-path=src {posargs:-vc}
 # without explicit deps, setup.py test will download a bunch of eggs into $PWD
 deps =
     .[test]
@@ -22,7 +22,7 @@ usedevelop = true
 basepython =
     python3.6
 commands =
-    coverage run -m zope.testrunner --test-path=src []
+    coverage run -m zope.testrunner --test-path=src {posargs:-vc}
     coverage report --fail-under=92
 deps =
     {[testenv]deps}

    UnicodeEncodeError: 'latin-1' codec can't encode character '\u0173' in position 9: ordinal not in range(256) on Python 3.7, while parsing POST requests containing non-ASCII field values
BrowserRequest.processInputs() is working on the assumption that cgi.FieldStorage will give it UTF-8 text encoded to Latin-1, as per PEP-3333.  This is not the case: on POST requests on Python 3.7 I'm seeing the FieldStorage hold native `str` objects containing already-decoded Unicode text, so when BrowserRequest._decode() tries to `text = text.encode('latin-1')`, things fail.
diff --git a/src/zope/publisher/tests/test_browserrequest.py b/src/zope/publisher/tests/test_browserrequest.py
index 07533fb..24f6c0c 100644
--- a/src/zope/publisher/tests/test_browserrequest.py
+++ b/src/zope/publisher/tests/test_browserrequest.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
 ##############################################################################
 #
 # Copyright (c) 2001, 2002 Zope Foundation and Contributors.
@@ -40,6 +41,14 @@ from zope.publisher.tests.basetestipublisherrequest \
 from zope.publisher.tests.basetestiapplicationrequest \
      import BaseTestIApplicationRequest
 
+
+EMPTY_FILE_BODY = b"""-----------------------------1
+Content-Disposition: form-data; name="upload"; filename=""
+Content-Type: application/octet-stream
+
+-----------------------------1--
+"""
+
 LARGE_FILE_BODY = b''.join([b"""-----------------------------1
 Content-Disposition: form-data; name="upload"; filename="test"
 Content-Type: text/plain
@@ -231,6 +240,15 @@ class BrowserTests(HTTPTests):
             # File objects on Python 3 have a seekable() method
             self.assertTrue(request.form['upload'].seekable())
 
+    def testEmptyFilePost(self):
+        extra = {'REQUEST_METHOD':'POST',
+                 'PATH_INFO': u"/",
+                 'CONTENT_TYPE': 'multipart/form-data;\
+                 boundary=---------------------------1'}
+
+        request  = self._createRequest(extra, body=EMPTY_FILE_BODY)
+        request.processInputs()
+
     def testLargePostValue(self):
         extra = {'REQUEST_METHOD':'POST',
                  'PATH_INFO': u"/",
@@ -286,6 +304,76 @@ class BrowserTests(HTTPTests):
         self.assertEqual(request.form,
                          {u"a": u"5", u"b": 6})
 
+    def testFormMultipartUTF8(self):
+        extra = {
+            'REQUEST_METHOD': 'POST',
+            'CONTENT_TYPE': 'multipart/form_data; boundary=-123',
+        }
+        body = b'\n'.join([
+            b'---123',
+            b'Content-Disposition: form-data; name="a"',
+            b'',
+            b'5',
+            b'---123',
+            b'Content-Disposition: form-data; name="b:int"',
+            b'',
+            b'6',
+            b'---123',
+            b'Content-Disposition: form-data; name="street"',
+            b'',
+            b'\xe6\xb1\x89\xe8\xaf\xad/\xe6\xbc\xa2\xe8\xaa\x9e',
+            b'---123--',
+            b'',
+        ])
+        request = self._createRequest(extra, body)
+        publish(request)
+        self.assertTrue(isinstance(request.form[u"street"], unicode))
+        self.assertEqual(u"汉语/漢語", request.form['street'])
+
+    def testFormURLEncodedUTF8(self):
+        extra = {
+            'REQUEST_METHOD': 'POST',
+            'CONTENT_TYPE': 'application/x-www-form-urlencoded',
+        }
+        body = b'a=5&b:int=6&street=\xe6\xb1\x89\xe8\xaf\xad/\xe6\xbc\xa2\xe8\xaa\x9e'
+        request = self._createRequest(extra, body)
+        publish(request)
+        self.assertTrue(isinstance(request.form[u"street"], unicode))
+        self.assertEqual(u"汉语/漢語", request.form['street'])
+
+    def testFormQueryStringUTF8(self):
+        extra = {
+            'QUERY_STRING': 'a=5&b:int=6&street=\xe6\xb1\x89\xe8\xaf\xad/\xe6\xbc\xa2\xe8\xaa\x9e'
+        }
+        request = self._createRequest(extra)
+        publish(request)
+        self.assertTrue(isinstance(request.form[u"street"], unicode))
+        self.assertEqual(u"汉语/漢語", request.form['street'])
+
+    def testFormURLEncodedLatin1(self):
+        extra = {
+            'REQUEST_METHOD': 'POST',
+            'CONTENT_TYPE': 'application/x-www-form-urlencoded',
+            'HTTP_ACCEPT_CHARSET': 'ISO-8859-1',
+        }
+        body = b'a=5&b:int=6&street=K\xf6hlerstra\xdfe'
+        request = self._createRequest(extra, body)
+        publish(request)
+        self.assertTrue(isinstance(request.form[u"street"], unicode))
+        self.assertEqual(u"K\xf6hlerstra\xdfe", request.form['street'])
+
+    def testFormURLEncodedLatin7(self):
+        extra = {
+            'REQUEST_METHOD': 'POST',
+            'CONTENT_TYPE': 'application/x-www-form-urlencoded',
+            'HTTP_ACCEPT_CHARSET': 'ISO-8859-13',
+        }
+        body = u'a=5&b:int=6&street=Ąžuolyno'.encode('iso-8859-13')
+        request = self._createRequest(extra, body)
+        publish(request)
+        self.assertTrue(isinstance(request.form[u"street"], unicode))
+        self.assertEqual(u"Ąžuolyno", request.form['street'])
+
     def testFormNoEncodingUsesUTF8(self):
         encoded = 'K\xc3\xb6hlerstra\xc3\x9fe'
         extra = {

pip install -e .[test]
parse_log_pytest
requirements.txt
pytest
3.6
doc-requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs==22.2.0
certifi==2021.5.30
importlib-metadata==4.8.3
iniconfig==1.1.1
packaging==21.3
pluggy==1.0.0
py==1.11.0
pyparsing==3.1.4
pytest==7.0.1
python-gettext==4.1
pytz==2025.2
six==1.17.0
swebench-matterhorn @ file:///swebench_matterhorn
tomli==1.2.3
typing_extensions==4.1.1
zipp==3.6.0
zope.browser==2.4
zope.component==5.1.0
zope.configuration==4.4.1
zope.contenttype==4.6
zope.deprecation==4.4.0
zope.event==4.6
zope.exceptions==4.6
zope.hookable==5.4
zope.i18n==4.9.0
zope.i18nmessageid==5.1.1
zope.interface==5.5.2
zope.location==4.3
zope.proxy==4.6.1
-e git+https://github.com/zopefoundation/zope.publisher.git@8f3e22e76da739a77168298a7708f5a0515537c5#egg=zope.publisher
zope.schema==6.2.1
zope.security==5.8
zope.testing==5.0.1
zope.testrunner==5.6

name: zope.publisher
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - attrs==22.2.0
      - importlib-metadata==4.8.3
      - iniconfig==1.1.1
      - packaging==21.3
      - pluggy==1.0.0
      - py==1.11.0
      - pyparsing==3.1.4
      - pytest==7.0.1
      - python-gettext==4.1
      - pytz==2025.2
      - six==1.17.0
      - swebench-matterhorn==0.0.0
      - tomli==1.2.3
      - typing-extensions==4.1.1
      - zipp==3.6.0
      - zope-browser==2.4
      - zope-component==5.1.0
      - zope-configuration==4.4.1
      - zope-contenttype==4.6
      - zope-deprecation==4.4.0
      - zope-event==4.6
      - zope-exceptions==4.6
      - zope-hookable==5.4
      - zope-i18n==4.9.0
      - zope-i18nmessageid==5.1.1
      - zope-interface==5.5.2
      - zope-location==4.3
      - zope-proxy==4.6.1
      - zope-schema==6.2.1
      - zope-security==5.8
      - zope-testing==5.0.1
      - zope-testrunner==5.6
prefix: /opt/conda/envs/zope.publisher

src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormMultipartUTF8 src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormURLEncodedLatin1 src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormURLEncodedLatin7 src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormURLEncodedUTF8
src/zope/publisher/tests/test_browserrequest.py::HTTPTests::testBasicAuth src/zope/publisher/tests/test_browserrequest.py::HTTPTests::testCookieErrorToLog src/zope/publisher/tests/test_browserrequest.py::HTTPTests::testCookies src/zope/publisher/tests/test_browserrequest.py::HTTPTests::testCookiesUnicode src/zope/publisher/tests/test_browserrequest.py::HTTPTests::testDeduceServerURL src/zope/publisher/tests/test_browserrequest.py::HTTPTests::testHeaders src/zope/publisher/tests/test_browserrequest.py::HTTPTests::testInterface src/zope/publisher/tests/test_browserrequest.py::HTTPTests::testRedirect src/zope/publisher/tests/test_browserrequest.py::HTTPTests::testRequestEnvironment src/zope/publisher/tests/test_browserrequest.py::HTTPTests::testRequestLocale src/zope/publisher/tests/test_browserrequest.py::HTTPTests::testResponseWriteFaile src/zope/publisher/tests/test_browserrequest.py::HTTPTests::testSetPrincipal src/zope/publisher/tests/test_browserrequest.py::HTTPTests::testTraversalToItem src/zope/publisher/tests/test_browserrequest.py::HTTPTests::testUnicodeURLs src/zope/publisher/tests/test_browserrequest.py::HTTPTests::testUnregisteredStatus src/zope/publisher/tests/test_browserrequest.py::HTTPTests::testUntrustedRedirect src/zope/publisher/tests/test_browserrequest.py::HTTPTests::test_PathTrailingWhitespace src/zope/publisher/tests/test_browserrequest.py::HTTPTests::test_double_dots src/zope/publisher/tests/test_browserrequest.py::HTTPTests::test_getVirtualHostRoot src/zope/publisher/tests/test_browserrequest.py::HTTPTests::test_method src/zope/publisher/tests/test_browserrequest.py::HTTPTests::test_repr src/zope/publisher/tests/test_browserrequest.py::HTTPTests::test_setApplicationNames src/zope/publisher/tests/test_browserrequest.py::HTTPTests::test_setApplicationServer src/zope/publisher/tests/test_browserrequest.py::HTTPTests::test_setVirtualHostRoot src/zope/publisher/tests/test_browserrequest.py::HTTPTests::test_traverse src/zope/publisher/tests/test_browserrequest.py::HTTPTests::test_traverseDuplicateHooks src/zope/publisher/tests/test_browserrequest.py::HTTPTests::test_unacceptable_charset src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testBadPath src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testBadPath2 src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testBasicAuth src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testCookieErrorToLog src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testCookies src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testCookiesUnicode src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testDeduceServerURL src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testDefault src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testDefault2 src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testDefault3 src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testDefault4 src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testDefault6 src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testDefaultPOST src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testEmptyFilePost src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFileUploadPost src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testForm src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormAcceptsStarButNotUTF8 src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormBooleanTypes src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormDefaults src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormDefaults2 src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormFieldName src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormFieldValue src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormFloatTypes src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormIntTypes src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormLinesTypes src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormListRecordTypes src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormListTypes src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormListTypes2 src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormLongTypes src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormMultipleRecordsTypes src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormNoEncodingUsesUTF8 src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormQueryStringUTF8 src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormRecordsTypes src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormRequiredTypes src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormStringTypes src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormTextTypes src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormTokensTypes src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormTupleRecordTypes src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormTupleTypes src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testHeaders src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testInterface src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testIssue394 src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testIssue559 src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testLargePostValue src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testNoDefault src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testNoneFieldNamePost src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testQueryStringIgnoredForPOST src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testRedirect src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testRequestEnvironment src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testRequestLocale src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testResponseWriteFaile src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testSetPrincipal src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testTraversalToItem src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testUnicodeURLs src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testUnregisteredStatus src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testUntrustedRedirect src/zope/publisher/tests/test_browserrequest.py::BrowserTests::test_PathTrailingWhitespace src/zope/publisher/tests/test_browserrequest.py::BrowserTests::test_double_dots src/zope/publisher/tests/test_browserrequest.py::BrowserTests::test_getVirtualHostRoot src/zope/publisher/tests/test_browserrequest.py::BrowserTests::test_method src/zope/publisher/tests/test_browserrequest.py::BrowserTests::test_post_body_not_consumed_unnecessarily src/zope/publisher/tests/test_browserrequest.py::BrowserTests::test_post_body_not_necessarily src/zope/publisher/tests/test_browserrequest.py::BrowserTests::test_repr src/zope/publisher/tests/test_browserrequest.py::BrowserTests::test_setApplicationNames src/zope/publisher/tests/test_browserrequest.py::BrowserTests::test_setApplicationServer src/zope/publisher/tests/test_browserrequest.py::BrowserTests::test_setVirtualHostRoot src/zope/publisher/tests/test_browserrequest.py::BrowserTests::test_traverse src/zope/publisher/tests/test_browserrequest.py::BrowserTests::test_traverseDuplicateHooks src/zope/publisher/tests/test_browserrequest.py::BrowserTests::test_unacceptable_charset src/zope/publisher/tests/test_browserrequest.py::APITests::testEnvironment src/zope/publisher/tests/test_browserrequest.py::APITests::testGetAndDefaultInMapping src/zope/publisher/tests/test_browserrequest.py::APITests::testHaveCustomTestsForIApplicationRequest src/zope/publisher/tests/test_browserrequest.py::APITests::testHaveCustomTestsForIPublicationRequest src/zope/publisher/tests/test_browserrequest.py::APITests::testHaveCustomTestsForIPublisherRequest src/zope/publisher/tests/test_browserrequest.py::APITests::testHoldCloseAndGetResponse src/zope/publisher/tests/test_browserrequest.py::APITests::testIReadMapping src/zope/publisher/tests/test_browserrequest.py::APITests::testPublicationManagement src/zope/publisher/tests/test_browserrequest.py::APITests::testSkinManagement src/zope/publisher/tests/test_browserrequest.py::APITests::testTraversalStack src/zope/publisher/tests/test_browserrequest.py::APITests::testVerifyIApplicationRequest src/zope/publisher/tests/test_browserrequest.py::APITests::testVerifyIPublicationRequest src/zope/publisher/tests/test_browserrequest.py::APITests::testVerifyIPublisherRequest src/zope/publisher/tests/test_browserrequest.py::APITests::testVerifyISkinnable src/zope/publisher/tests/test_browserrequest.py::APITests::test_IApplicationRequest_bodyStream src/zope/publisher/tests/test_browserrequest.py::APITests::test_IBrowserRequest src/zope/publisher/tests/test_browserrequest.py::APITests::test_IPublicationRequest_getPositionalArguments src/zope/publisher/tests/test_browserrequest.py::APITests::test_IPublisherRequest_processInputs src/zope/publisher/tests/test_browserrequest.py::APITests::test_IPublisherRequest_retry src/zope/publisher/tests/test_browserrequest.py::APITests::test_IPublisherRequest_traverse src/zope/publisher/tests/test_browserrequest.py::APITests::test_ISkinnable src/zope/publisher/tests/test_browserrequest.py::APITests::test___len__ src/zope/publisher/tests/test_browserrequest.py::APITests::test_items src/zope/publisher/tests/test_browserrequest.py::APITests::test_keys src/zope/publisher/tests/test_browserrequest.py::APITests::test_values src/zope/publisher/tests/test_browserrequest.py::test_suite
src/zope/publisher/browser.py tox.ini CHANGES.rst
src/zope/publisher/browser.py tox.ini CHANGES.rst