--- 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>