15768696 evaluate changes needed to upgrade to external python libraries
authorYiteng Zhang <yiteng.zhang@oracle.com>
Tue, 03 Nov 2015 02:27:20 -0800
changeset 3274 e06a0700e218
parent 3273 776d569cd953
child 3275 ca81b2f0ac63
15768696 evaluate changes needed to upgrade to external python libraries
src/depot.py
src/modules/arch.py
src/modules/client/imageplan.py
src/modules/facet.py
src/modules/misc.py
src/modules/pipeutils.py
src/modules/server/api.py
src/modules/server/depot.py
src/modules/server/depotresponse.py
src/modules/server/face.py
src/modules/sysattr.py
src/pkg/manifests/package:pkg.p5m
src/tests/cli/t_pkg_depotd.py
src/util/apache2/depot/depot_index.py
src/web/en/stats.shtml
--- a/src/depot.py	Thu Oct 29 23:17:39 2015 +1100
+++ b/src/depot.py	Tue Nov 03 02:27:20 2015 -0800
@@ -71,6 +71,8 @@
 import os.path
 import OpenSSL.crypto as crypto
 import shlex
+import six
+import string
 import subprocess
 import sys
 import tempfile
@@ -84,15 +86,14 @@
         # comparison requires same type, therefore list conversion is needed
         if list(map(int, version)) < [3, 1, 0]:
                 raise ImportError
-        elif list(map(int, version)) >= [3, 2, 0]:
-                raise ImportError
 except ImportError:
-        print("""cherrypy 3.1.0 or greater (but less than """
-            """3.2.0) is required to use this program.""", file=sys.stderr)
+        print("""cherrypy 3.1.0 or greater is required to use this program.""",
+            file=sys.stderr)
         sys.exit(2)
 
 import cherrypy.process.servers
 from cherrypy.process.plugins import Daemonizer
+from cherrypy._cpdispatch import Dispatcher
 
 from pkg.misc import msg, emsg, setlocale
 from pkg.client.debugvalues import DebugValues
@@ -103,10 +104,23 @@
 import pkg.portable.util as os_util
 import pkg.search_errors as search_errors
 import pkg.server.depot as ds
-import pkg.server.depotresponse as dr
 import pkg.server.repository as sr
 
 
+# Starting in CherryPy 3.2, its default dispatcher converts all punctuation to
+# underscore. Since publisher name can contain the hyphen symbol "-", in order
+# to let the dispatcher to find the correct page handler, we need to skip
+# converting the hyphen symbol.
+punc = string.punctuation.replace("-", "_")
+if six.PY2:
+        translate = string.maketrans(punc, "_" * len(string.punctuation))
+else:
+        translate = str.maketrans(punc, "_" * len(string.punctuation))
+class Pkg5Dispatcher(Dispatcher):
+        def __init__(self, **args):
+                Dispatcher.__init__(self, translate=translate)
+
+
 class LogSink(object):
         """This is a dummy object that we can use to discard log entries
         without relying on non-portable interfaces such as /dev/null."""
@@ -903,18 +917,15 @@
 
         # Now build our site configuration.
         conf = {
-            "/": {
-                # We have to override cherrypy's default response_class so that
-                # we have access to the write() callable to stream data
-                # directly to the client.
-                "wsgi.response_class": dr.DepotResponse,
-            },
+            "/": {},
             "/robots.txt": {
                 "tools.staticfile.on": True,
                 "tools.staticfile.filename": os.path.join(depot.web_root,
                     "robots.txt")
             },
         }
+        if list(map(int, version)) >= [3, 2, 0]:
+                conf["/"]["request.dispatch"] = Pkg5Dispatcher()
 
         proxy_base = dconf.get_property("pkg", "proxy_base")
         if proxy_base:
--- a/src/modules/arch.py	Thu Oct 29 23:17:39 2015 +1100
+++ b/src/modules/arch.py	Tue Nov 03 02:27:20 2015 -0800
@@ -71,14 +71,14 @@
     if buf2 == NULL and buf1:
         buf = buf1
 
-    from pkg.misc import bytes_to_unicode
+    from pkg.misc import force_text
     # ffi.string returns a bytes
     if buf == NULL:
-        buf1 = bytes_to_unicode(ffi.string(ffi.cast("char *", buf1)))
-        buf2 = bytes_to_unicode(ffi.string(ffi.cast("char *", buf2)))
+        buf1 = force_text(ffi.string(ffi.cast("char *", buf1)))
+        buf2 = force_text(ffi.string(ffi.cast("char *", buf2)))
         robj = [buf1, buf2]
     else:
-        buf = bytes_to_unicode(ffi.string(ffi.cast("char *", buf)))
+        buf = force_text(ffi.string(ffi.cast("char *", buf)))
         robj = [buf]
 
     return robj
@@ -89,8 +89,8 @@
     buf = _get_sysinfo(lib.SI_RELEASE)
     if buf == NULL:
         return
-    from pkg.misc import bytes_to_unicode
-    return bytes_to_unicode(ffi.string(ffi.cast("char *", buf)))
+    from pkg.misc import force_text
+    return force_text(ffi.string(ffi.cast("char *", buf)))
 
 
 def get_platform():
@@ -98,5 +98,5 @@
     buf = _get_sysinfo(lib.SI_PLATFORM)
     if buf == NULL:
         return
-    from pkg.misc import bytes_to_unicode
-    return bytes_to_unicode(ffi.string(ffi.cast("char *", buf)))
+    from pkg.misc import force_text
+    return force_text(ffi.string(ffi.cast("char *", buf)))
--- a/src/modules/client/imageplan.py	Thu Oct 29 23:17:39 2015 +1100
+++ b/src/modules/client/imageplan.py	Tue Nov 03 02:27:20 2015 -0800
@@ -356,7 +356,17 @@
                 # if we're changing variants or facets, save that to the plan.
                 if new_variants or facet_change or masked_facet_change:
                         self.pd._varcets_change = True
-                        self.pd._new_variants = new_variants
+                        if new_variants:
+                                # This particular data are passed as unicode
+                                # instead of bytes in the child image due to the
+                                # jsonrpclib update, so we use force_str here to
+                                # reduce the pain in comparing json data type.
+                                self.pd._new_variants = {}
+                                for k, v in new_variants.items():
+                                        self.pd._new_variants[misc.force_str(k)] = \
+                                            misc.force_str(v)
+                        else:
+                                self.pd._new_variants = new_variants
                         self.pd._old_facets   = self.image.cfg.facets
                         self.pd._new_facets   = new_facets
                         self.pd._facet_change = facet_change
--- a/src/modules/facet.py	Thu Oct 29 23:17:39 2015 +1100
+++ b/src/modules/facet.py	Tue Nov 03 02:27:20 2015 -0800
@@ -111,10 +111,10 @@
                 that that can be easily stored using JSON, pickle, etc."""
 
                 return [
-                        [k, v, True]
+                        [misc.force_text(k), v, True]
                         for k, v in six.iteritems(obj.__inherited)
                 ] + [
-                        [k, v, False]
+                        [misc.force_text(k), v, False]
                         for k, v in six.iteritems(obj.__local)
                 ]
 
--- a/src/modules/misc.py	Thu Oct 29 23:17:39 2015 +1100
+++ b/src/modules/misc.py	Tue Nov 03 02:27:20 2015 -0800
@@ -1958,12 +1958,14 @@
             "unexpected {0} for {1}, expected: {2}, value: {3}".format(
                 data_type, name, desc_type, data)
 
+        # The following situation is only true for Python 2.
         # We should not see unicode strings getting passed in. The assert is
         # necessary since we use the PkgDecoder hook function during json_decode
         # to convert unicode objects back into escaped str objects, which would
         # otherwise do that conversion unintentionally.
-        assert not isinstance(data_type, six.text_type), \
-            "unexpected unicode string: {0}".format(data)
+        if six.PY2:
+                assert not isinstance(data_type, six.text_type), \
+                    "unexpected unicode string: {0}".format(data)
 
         # we don't need to do anything for basic types
         for t in json_types_immediates:
@@ -2388,14 +2390,15 @@
 
 def json_hook(dct):
         """Hook routine used by the JSON module to ensure that unicode objects
-        are converted to string objects."""
+        are converted to bytes objects in Python 2 and ensures that bytes
+        objects are converted to str objects in Python 3."""
 
         rvdct = {}
         for k, v in six.iteritems(dct):
-                if type(k) == six.text_type:
-                        k = k.encode("utf-8")
-                if type(v) == six.text_type:
-                        v = v.encode("utf-8")
+                if isinstance(k, six.string_types):
+                        k = force_str(k)
+                if isinstance(v, six.string_types):
+                        v= force_str(v)
 
                 rvdct[k] = v
         return rvdct
@@ -2911,17 +2914,41 @@
                 # if that ever happens, just ignore it.
                 pass
 
-def bytes_to_unicode(s):
-        """Convert bytes to unicode with encoding 'utf-8'."""
+
+def force_bytes(s, encoding="utf-8", errors="strict"):
+        """Force the string into bytes."""
 
         if isinstance(s, bytes):
-                return s.decode("utf-8")
-        return s
-
-
-def unicode_to_bytes(s):
-        """Convert unicode to bytes with encoding 'utf-8'."""
+                if encoding == "utf-8":
+                        return s
+                return s.decode("utf-8", errors).encode(encoding,
+                    errors)
+        elif isinstance(s, six.string_types):
+                # this case is: unicode in Python 2 and str in Python 3
+                return s.encode(encoding, errors)
+        elif six.PY3:
+                # type not a string and Python 3's bytes() requires
+                # a string argument
+                return six.text_type(s).encode(encoding)
+        # type not a string
+        return bytes(s)
+
+
+def force_text(s, encoding="utf-8", errors="strict"):
+        """Force the string into text."""
 
         if isinstance(s, six.text_type):
-                return s.encode("utf-8")
-        return s
+                return s
+        if isinstance(s, six.string_types):
+                # this case is: str(bytes) in Python 2
+                return s.decode(encoding, errors)
+        elif isinstance(s, bytes):
+                # this case is: bytes in Python 3
+                return s.decode(encoding, errors)
+        # type not a string
+        return six.text_type(s)
+
+if six.PY3:
+        force_str = force_text
+else:
+        force_str = force_bytes
--- a/src/modules/pipeutils.py	Thu Oct 29 23:17:39 2015 +1100
+++ b/src/modules/pipeutils.py	Tue Nov 03 02:27:20 2015 -0800
@@ -394,7 +394,14 @@
         def __init__(self, fd, http_enc=True):
                 self.__pipe_file = PipeFile(fd, "client-transport")
                 self.__http_enc = http_enc
-                rpc.Transport.__init__(self)
+                # This is a workaround to cope with the jsonrpclib update
+                # (version 0.2.6) more safely. Once jsonrpclib is out in
+                # the OS build, we can change it to always pass a 'config'
+                # argument to __init__.
+                if hasattr(rpclib.config, "DEFAULT"):
+                        rpc.Transport.__init__(self, rpclib.config.DEFAULT)
+                else:
+                        rpc.Transport.__init__(self)
                 self.verbose = False
                 self._extra_headers = None
 
--- a/src/modules/server/api.py	Thu Oct 29 23:17:39 2015 +1100
+++ b/src/modules/server/api.py	Tue Nov 03 02:27:20 2015 -0800
@@ -628,20 +628,6 @@
                 return self._depot.repo.file_requests
 
         @property
-        def filelist_requests(self):
-                """The number of /filelist operation requests that have occurred
-                during the current server session.
-                """
-                return self._depot.flist_requests
-
-        @property
-        def filelist_file_requests(self):
-                """The number of files served by /filelist operations requested
-                during the current server session.
-                """
-                return self._depot.flist_file_requests
-
-        @property
         def in_flight_transactions(self):
                 """The number of package transactions awaiting completion.
                 """
--- a/src/modules/server/depot.py	Thu Oct 29 23:17:39 2015 +1100
+++ b/src/modules/server/depot.py	Tue Nov 03 02:27:20 2015 -0800
@@ -107,7 +107,6 @@
             "catalog",
             "info",
             "manifest",
-            "filelist",
             "file",
             "open",
             "append",
@@ -127,7 +126,6 @@
             "catalog",
             "info",
             "manifest",
-            "filelist",
             "file",
             "p5i",
             "publisher",
@@ -136,7 +134,6 @@
 
         REPO_OPS_MIRROR = [
             "versions",
-            "filelist",
             "file",
             "publisher",
             "status",
@@ -161,8 +158,6 @@
 
                 self.cfg = dconf
                 self.repo = repo
-                self.flist_requests = 0
-                self.flist_file_requests = 0
                 self.request_pub_func = request_pub_func
 
                 content_root = dconf.get_property("pkg", "content_root")
@@ -763,77 +758,6 @@
 
                         cherrypy.request.tar_stream = None
 
-        def filelist_0(self, *tokens, **params):
-                """Request data contains application/x-www-form-urlencoded
-                entries with the requested filenames.  The resulting tar stream
-                is output directly to the client. """
-
-                try:
-                        self.flist_requests += 1
-
-                        # Create a dummy file object that hooks to the write()
-                        # callable which is all tarfile needs to output the
-                        # stream.  This will write the bytes to the client
-                        # through our parent server process.
-                        f = Dummy()
-                        f.write = cherrypy.response.write
-
-                        tar_stream = tarfile.open(mode = "w|",
-                            fileobj = f)
-
-                        # We can use the request object for storage of data
-                        # specific to this request.  In this case, it allows us
-                        # to provide our on_end_request function with access to
-                        # the stream we are processing.
-                        cherrypy.request.tar_stream = tar_stream
-
-                        # This is a special hook just for this request so that
-                        # if an exception is encountered, the stream will be
-                        # closed properly regardless of which thread is
-                        # executing.
-                        cherrypy.request.hooks.attach("on_end_request",
-                            self._tar_stream_close, failsafe=True)
-
-                        pub = self._get_req_pub()
-                        for v in params.values():
-                                try:
-                                        filepath = self.repo.file(v, pub=pub)
-                                except srepo.RepositoryFileNotFoundError:
-                                        # If file isn't here, skip it
-                                        continue
-
-                                tar_stream.add(filepath, v, False)
-                                self.flist_file_requests += 1
-
-                        # Flush the remaining bytes to the client.
-                        tar_stream.close()
-                        cherrypy.request.tar_stream = None
-
-                except Exception as e:
-                        # If we find an exception of this type, the
-                        # client has most likely been interrupted.
-                        if isinstance(e, socket.error) \
-                            and e.args[0] == errno.EPIPE:
-                                return
-                        raise
-
-                yield ""
-
-        # We have to configure the headers either through the _cp_config
-        # namespace, or inside the function itself whenever we are using
-        # a streaming generator.  This is because headers have to be setup
-        # before the response even begins and the point at which @tools
-        # hooks in is too late.
-        filelist_0._cp_config = {
-            "response.stream": True,
-            "tools.response_headers.on": True,
-            "tools.response_headers.headers": [
-                ("Content-Type", "application/data"),
-                ("Pragma", "no-cache"),
-                ("Cache-Control", "no-cache, no-transform, must-revalidate"),
-                ("Expires", 0)
-            ]
-        }
 
         def file_0(self, *tokens):
                 """Outputs the contents of the file, named by the SHA-1 hash
@@ -1889,136 +1813,6 @@
             "tools.nasty_before.maxroll": 200
         }
 
-        def filelist_0(self, *tokens, **params):
-                """Request data contains application/x-www-form-urlencoded
-                entries with the requested filenames.  The resulting tar stream
-                is output directly to the client. """
-
-                try:
-                        self.flist_requests += 1
-
-                        # NASTY
-                        if self.need_nasty_2():
-                                cherrypy.log("NASTY filelist_0: empty response")
-                                return
-
-                        # Create a dummy file object that hooks to the write()
-                        # callable which is all tarfile needs to output the
-                        # stream.  This will write the bytes to the client
-                        # through our parent server process.
-                        f = Dummy()
-                        f.write = cherrypy.response.write
-
-                        tar_stream = tarfile.open(mode = "w|",
-                            fileobj = f)
-
-                        # We can use the request object for storage of data
-                        # specific to this request.  In this case, it allows us
-                        # to provide our on_end_request function with access to
-                        # the stream we are processing.
-                        cherrypy.request.tar_stream = tar_stream
-
-                        # This is a special hook just for this request so that
-                        # if an exception is encountered, the stream will be
-                        # closed properly regardless of which thread is
-                        # executing.
-                        cherrypy.request.hooks.attach("on_end_request",
-                            self._tar_stream_close, failsafe=True)
-
-                        pub = self._get_req_pub()
-                        for v in params.values():
-
-                                # NASTY
-                                # Stash filename for later use.
-                                # Toss out the list if it's larger than 1024
-                                # items.
-                                if len(self.requested_files) > 1024:
-                                        self.requested_files = [v]
-                                else:
-                                        self.requested_files.append(v)
-
-                                # NASTY
-                                if self.need_nasty_3():
-                                        # Give up early
-                                        cherrypy.log(
-                                            "NASTY filelist_0: give up early")
-                                        break
-                                elif self.need_nasty_3():
-                                        # Skip this file
-                                        cherrypy.log(
-                                            "NASTY filelist_0: skip a file")
-                                        continue
-                                elif self.need_nasty_4():
-                                        # Take a nap
-                                        self.nasty_nap()
-
-                                try:
-                                        filepath = self.repo.file(v, pub=pub)
-                                except srepo.RepositoryFileNotFoundError:
-                                        # If file isn't here, skip it
-                                        continue
-
-                                # NASTY
-                                # Send a file with the wrong content
-                                if self.need_nasty_4():
-                                        cherrypy.log(
-                                            "NASTY filelist_0: wrong content")
-                                        badfn = \
-                                            random.choice(self.requested_files)
-                                        badpath = self.__get_bad_path(badfn)
-
-                                        tar_stream.add(badpath, v, False)
-                                else:
-                                        tar_stream.add(filepath, v, False)
-
-                                self.flist_file_requests += 1
-
-                        # NASTY
-                        # Write garbage into the stream
-                        if self.need_nasty_3():
-                                cherrypy.log(
-                                    "NASTY filelist_0: add stream garbage")
-                                f.write("NASTY!")
-
-                        # NASTY
-                        # Send an extraneous file
-                        if self.need_nasty_3():
-                                cherrypy.log(
-                                    "NASTY filelist_0: send extra file")
-                                extrafn = random.choice(self.requested_files)
-                                extrapath = self.repo.file(extrafn, pub=pub)
-                                tar_stream.add(extrapath, extrafn, False)
-
-                        # Flush the remaining bytes to the client.
-                        tar_stream.close()
-                        cherrypy.request.tar_stream = None
-
-                except Exception as e:
-                        # If we find an exception of this type, the
-                        # client has most likely been interrupted.
-                        if isinstance(e, socket.error) \
-                            and e.args[0] == errno.EPIPE:
-                                return
-                        raise
-
-                yield ""
-
-        # We have to configure the headers either through the _cp_config
-        # namespace, or inside the function itself whenever we are using
-        # a streaming generator.  This is because headers have to be setup
-        # before the response even begins and the point at which @tools
-        # hooks in is too late.
-        filelist_0._cp_config = {
-            "response.stream": True,
-            "tools.response_headers.on": True,
-            "tools.response_headers.headers": [
-                ("Content-Type", "application/data"),
-                ("Pragma", "no-cache"),
-                ("Cache-Control", "no-cache, must-revalidate"),
-                ("Expires", 0)
-            ]
-        }
-
         def __get_bad_path(self, v):
                 fpath = self.repo.file(v, pub=self._get_req_pub())
                 return os.path.join(os.path.dirname(fpath), fpath)
--- a/src/modules/server/depotresponse.py	Thu Oct 29 23:17:39 2015 +1100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,109 +0,0 @@
-#!/usr/bin/python
-#
-# copyright (c) 2004-2007, cherrypy team ([email protected])
-# all rights reserved.
-#
-# redistribution and use in source and binary forms, with or without modification,
-# are permitted provided that the following conditions are met:
-#
-#     * redistributions of source code must retain the above copyright notice,
-#       this list of conditions and the following disclaimer.
-#     * redistributions in binary form must reproduce the above copyright notice,
-#       this list of conditions and the following disclaimer in the documentation
-#       and/or other materials provided with the distribution.
-#     * neither the name of the cherrypy team nor the names of its contributors
-#       may be used to endorse or promote products derived from this software
-#       without specific prior written permission.
-#
-# this software is provided by the copyright holders and contributors "as is" and
-# any express or implied warranties, including, but not limited to, the implied
-# warranties of merchantability and fitness for a particular purpose are
-# disclaimed. in no event shall the copyright owner or contributors be liable
-# for any direct, indirect, incidental, special, exemplary, or consequential
-# damages (including, but not limited to, procurement of substitute goods or
-# services; loss of use, data, or profits; or business interruption) however
-# caused and on any theory of liability, whether in contract, strict liability,
-# or tort (including negligence or otherwise) arising in any way out of the use
-# of this software, even if advised of the possibility of such damage.
-#
-
-#
-# Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved.
-#
-
-import sys as _sys
-import cherrypy as _cherrypy
-from cherrypy import _cperror
-from cherrypy import _cpwsgi
-
-class DepotResponse(_cpwsgi.AppResponse):
-        """ This class is a partial combination of a cherrypy's original
-            AppResponse class with a change to "Stage 2" of setapp to provide
-            access to the write() callable specified by PEP 333.  Access to this
-            callable is necessary to maintain a minimal memory and disk
-            footprint for streaming operations performed by the depot server,
-            such as filelist. """
-
-        def __add_write_hook(self, s, h, exc):
-                # The WSGI specification includes a special write()
-                # callable returned by the start_response callable.
-                # cherrypy traditionally hides this from applications
-                # as new WSGI applications and frameworks are not
-                # supposed to use it if at all possible.  The write()
-                # callable is considered a hack to support imperative
-                # streaming APIs.
-                #
-                # As a result, we have to provide access to the write()
-                # callable ourselves by replacing the default
-                # response_class with our own.  This callable is
-                # provided so that streaming APIs can be treated as if
-                # their output had been yielded by an iterable.
-                #
-                # The cherrypy singleton below is thread-local, and
-                # guaranteed to only be set for a specific request.
-                # This means any callables that use the singleton
-                # to access this method are guaranteed to write output
-                # back to the same request.
-                #
-                # See: http://www.python.org/dev/peps/pep-0333/
-                #
-                _cherrypy.response.write = self.start_response(s, h, exc)
-
-        def setapp(self):
-                try:
-                        self.request = self.get_request()
-                        s, h, b = self.get_response()
-                        self.iter_response = iter(b)
-                        self.__add_write_hook(s, h, None)
-                except self.throws:
-                        self.close()
-                        raise
-                except _cherrypy.InternalRedirect as ir:
-                        self.environ['cherrypy.previous_request'] = _cherrypy.serving.request
-                        self.close()
-                        self.iredirect(ir.path, ir.query_string)
-                        return
-                except:
-                        if getattr(self.request, "throw_errors", False):
-                                self.close()
-                                raise
-
-                        tb = _cperror.format_exc()
-                        _cherrypy.log(tb, severity=40)
-                        if not getattr(self.request, "show_tracebacks", True):
-                                tb = ""
-                        s, h, b = _cperror.bare_error(tb)
-                        self.iter_response = iter(b)
-
-                        try:
-                                self.__add_write_hook(s, h, _sys.exc_info())
-                        except:
-                                # "The application must not trap any exceptions raised by
-                                # start_response, if it called start_response with exc_info.
-                                # Instead, it should allow such exceptions to propagate
-                                # back to the server or gateway."
-                                # But we still log and call close() to clean up ourselves.
-                                _cherrypy.log(traceback=True, severity=40)
-                                self.close()
-                                raise
-
--- a/src/modules/server/face.py	Thu Oct 29 23:17:39 2015 +1100
+++ b/src/modules/server/face.py	Tue Nov 03 02:27:20 2015 -0800
@@ -35,6 +35,7 @@
 from six.moves import http_client
 from six.moves.urllib.parse import unquote
 
+import pkg.misc as misc
 import pkg.server.api as api
 import pkg.server.api_errors as sae
 import pkg.server.feed
@@ -68,8 +69,10 @@
 def __render_template(depot, request, path, pub, http_depot=None):
         template = tlookup.get_template(path)
         base = api.BaseInterface(request, depot, pub)
-        return template.render_unicode(g_vars={ "base": base, "pub": pub,
-            "http_depot": http_depot})
+        # Starting in CherryPy 3.2, cherrypy.response.body only allows
+        # bytes.
+        return misc.force_bytes(template.render(g_vars={ "base": base,
+            "pub": pub, "http_depot": http_depot}))
 
 def __handle_error(path, error):
         # All errors are treated as a 404 since reverse proxies such as Apache
--- a/src/modules/sysattr.py	Thu Oct 29 23:17:39 2015 +1100
+++ b/src/modules/sysattr.py	Tue Nov 03 02:27:20 2015 -0800
@@ -49,7 +49,7 @@
     Returns a list of verbose attributes by default. If 'compact' is True,
     return a string consisting of compact option identifiers."""
 
-    from pkg.misc import bytes_to_unicode, unicode_to_bytes
+    from pkg.misc import force_text
     if not isinstance(filename, six.string_types):
         raise TypeError("filename must be string type")
 
@@ -94,13 +94,13 @@
                 count += 1
             else:
                 # ffi.string returns a bytes
-                string = bytes_to_unicode(ffi.string(name))
+                string = force_text(ffi.string(name))
                 if string:
                     attr_list.append(string)
         pair = next_pair
 
     if compact:
-        cattrs = bytes_to_unicode(ffi.string(cattrs))
+        cattrs = force_text(ffi.string(cattrs))
         return cattrs
     return attr_list
 
@@ -119,7 +119,7 @@
     compact attributes example: 'HAT'
     """
 
-    from pkg.misc import bytes_to_unicode, unicode_to_bytes
+    from pkg.misc import force_bytes
     if not isinstance(filename, six.string_types):
         raise TypeError("filename must be string type")
     if not attr:
@@ -139,7 +139,7 @@
         compact = True
 
     for c in attr:
-        c = unicode_to_bytes(c)
+        c = force_bytes(c)
         if compact:
             sys_attr = lib.option_to_attr(c)
         else:
@@ -181,12 +181,12 @@
         }
     """
 
-    from pkg.misc import bytes_to_unicode, unicode_to_bytes
+    from pkg.misc import force_text
     sys_attrs = {}
     for i in range(F_ATTR_ALL):
         if not is_supported(i):
             continue
-        key = bytes_to_unicode(ffi.string(lib.attr_to_name(i)))
-        value = bytes_to_unicode(ffi.string(lib.attr_to_option(i)))
+        key = force_text(ffi.string(lib.attr_to_name(i)))
+        value = force_text(ffi.string(lib.attr_to_option(i)))
         sys_attrs.setdefault(key, value)
     return sys_attrs
--- a/src/pkg/manifests/package:pkg.p5m	Thu Oct 29 23:17:39 2015 +1100
+++ b/src/pkg/manifests/package:pkg.p5m	Tue Nov 03 02:27:20 2015 -0800
@@ -218,7 +218,6 @@
 file path=$(PYDIRVP)/pkg/server/api_errors.py
 file path=$(PYDIRVP)/pkg/server/catalog.py
 file path=$(PYDIRVP)/pkg/server/depot.py pkg.depend.bypass-generate=.*six.*
-file path=$(PYDIRVP)/pkg/server/depotresponse.py
 file path=$(PYDIRVP)/pkg/server/face.py pkg.depend.bypass-generate=.*six.*
 file path=$(PYDIRVP)/pkg/server/feed.py pkg.depend.bypass-generate=.*six.*
 file path=$(PYDIRVP)/pkg/server/query_parser.py
--- a/src/tests/cli/t_pkg_depotd.py	Thu Oct 29 23:17:39 2015 +1100
+++ b/src/tests/cli/t_pkg_depotd.py	Tue Nov 03 02:27:20 2015 -0800
@@ -483,6 +483,29 @@
 
                 res = urlopen(repourl)
 
+        def test_publisher_prefix(self):
+                """Test that various publisher prefixes can be understood
+                by CherryPy's dispatcher."""
+
+                if self.dc.started:
+                        self.dc.stop()
+
+                depot_url = self.dc.get_depot_url()
+                repopath = os.path.join(self.test_root, "repo")
+                self.create_repo(repopath)
+                self.dc.set_repodir(repopath)
+                pubs = ["test-hyphen", "test.dot"]
+                for p in pubs:
+                        self.pkgrepo("-s {0} add-publisher {1}".format(
+                            repopath, p))
+                self.dc.start()
+                for p in pubs:
+                        # test that the catalog file can be found
+                        url = urljoin(depot_url,
+                            "{0}/catalog/1/catalog.attrs".format(p))
+                        urlopen(url)
+                self.dc.stop()
+
 
 class TestDepotController(pkg5unittest.CliTestCase):
 
@@ -1020,7 +1043,7 @@
                         self.assertEqual(returned, expected)
 
         def test_2_depot_p5i(self):
-                """Verify the output of the depot /publisher operation."""
+                """Verify the output of the depot /p5i operation."""
 
                 # Now update the repository configuration while the depot is
                 # stopped so changes won't be overwritten on exit.
@@ -1102,24 +1125,16 @@
                     'search/1/False_2_None_None_%2Fvar%2Ffile',
                     "versions/0", "manifest/0/{0}".format(pfmri.get_url_path()),
                     "catalog/0", "catalog/1/catalog.attrs",
-                    "file/0/3aad0bca6f3a6f502c175700ebe90ef36e312d7e",
-                    "filelist/0"):
+                    "file/0/3aad0bca6f3a6f502c175700ebe90ef36e312d7e"):
                         hdrs = dict(get_headers(req_path))
 
                         # Fields must be referenced in lowercase.
-                        if req_path.startswith("filelist"):
-                                self.assertEqual(hdrs.get("expires", ""), "0")
-                                self.assertEqual(hdrs.get("cache-control", ""),
-                                    "no-cache, no-transform, must-revalidate")
-                                self.assertEqual(hdrs.get("pragma", None),
-                                    "no-cache")
-                        else:
-                                cc = hdrs.get("cache-control", "")
-                                self.assert_(cc.startswith("must-revalidate, "
-                                    "no-transform, max-age="))
-                                exp = hdrs.get("expires", None)
-                                self.assertNotEqual(exp, None)
-                                self.assert_(exp.endswith(" GMT"))
+                        cc = hdrs.get("cache-control", "")
+                        self.assertTrue(cc.startswith("must-revalidate, "
+                            "no-transform, max-age="))
+                        exp = hdrs.get("expires", None)
+                        self.assertNotEqual(exp, None)
+                        self.assertTrue(exp.endswith(" GMT"))
 
                 for req_path in ("catalog/1/catalog.hatters",
                     "file/0/3aad0bca6f3a6f502c175700ebe90ef36e312d7f"):
--- a/src/util/apache2/depot/depot_index.py	Thu Oct 29 23:17:39 2015 +1100
+++ b/src/util/apache2/depot/depot_index.py	Tue Nov 03 02:27:20 2015 -0800
@@ -39,6 +39,7 @@
 from six.moves.urllib.parse import quote
 from six.moves.urllib.request import urlopen
 
+import pkg.misc as misc
 import pkg.p5i
 import pkg.server.api
 import pkg.server.repository as sr
@@ -404,10 +405,12 @@
                             pub)) for pub in repo.publishers]
                 repo_list.sort()
                 template = tlookup.get_template("repos.shtml")
-                return template.render_unicode(g_vars={"base": base,
+                # Starting in CherryPy 3.2, cherrypy.response.body only allows
+                # bytes.
+                return misc.force_bytes(template.render(g_vars={"base": base,
                     "pub": None, "http_depot": "true", "lang": accept_lang,
                     "repo_list": repo_list, "repo_pubs": repo_pubs
-                    })
+                    }))
 
         def default(self, *tokens, **params):
                 """ Our default handler is here to make sure we've called
@@ -739,6 +742,17 @@
 
                 toks = path_info.lstrip("/").split("/")
                 params = request.params
+                if not params:
+                        try:
+                                # Starting in CherryPy 3.2, it seems that
+                                # query_string doesn't pass into request.params,
+                                # so try harder here.
+                                from cherrypy.lib.httputil import parse_query_string
+                                params = parse_query_string(
+                                    request.query_string)
+                                request.params.update(params)
+                        except ImportError:
+                                pass
                 file_type = toks[-1].split(".")[-1]
 
                 try:
--- a/src/web/en/stats.shtml	Thu Oct 29 23:17:39 2015 +1100
+++ b/src/web/en/stats.shtml	Tue Nov 03 02:27:20 2015 -0800
@@ -66,10 +66,6 @@
                         <tr class="first">
                                 <th scope="row" class="last" colspan="2">Depot</th>
                         </tr>
-                        <tr>
-                                <td scope="row" class="label">Files served by filelist</td>
-                                <td class="value">${config.filelist_file_requests}</td>
-                        </tr>
 % if not config.mirror:
                         <tr>
                                 <td scope="row" class="label">In-flight Transactions</td>
@@ -94,10 +90,6 @@
                                 <td scope="row" class="label">file</td>
                                 <td class="value">${config.file_requests}</td>
                         </tr>
-                        <tr>
-                                <td scope="row" class="label">filelist</td>
-                                <td class="value">${config.filelist_requests}</td>
-                        </tr>
 % if not config.mirror:
                         <tr>
                                 <td scope="row" class="label">manifest</td>