mirror of
https://github.com/pypiserver/pypiserver
synced 2024-12-20 13:55:49 +01:00
bump bottle to version 0.14-dev (#619)
* bump bottle to version 0.14-dev Support modern python versions. This is commit 7af7135965e6e02993fea89d8932a4bc713b6692 from bottle's master branch. Signed-off-by: Oz Tiram <oz.tiram@gmail.com> * chore: update declared python versions Drop everything older than 3.9. * chore: clean old python versions from tox Add python3.12 to tox * fix: remove assigning custom_full path to request Instead use a function call. This less expensive than before_request hook (which is invoked to every route). Signed-off-by: Oz Tiram <oz.tiram@gmail.com> * fix: correct server adapters name match those classes found in bottle-0.14-dev * chore: remove unused custom_host Signed-off-by: Oz Tiram <oz.tiram@gmail.com> * fix: redirectio to bad_url_path moved get_bad_url_redirect_path from core.py to _app.py Signed-off-by: Oz Tiram <oz.tiram@gmail.com> * fix: tests for core and _app after moving stuff around * fix: properly create a Request object for test_redirect_project_encodes_newlines * chore: emove unused core import * fix: black formatting * fix: remove constraint on python 3.12 This was probably relevant because of bottle not supporting this Python version. --------- Signed-off-by: Oz Tiram <oz.tiram@gmail.com>
This commit is contained in:
parent
16c9cecdd3
commit
e6926d56a5
@ -69,9 +69,10 @@ class AutoServer(enum.Enum):
|
||||
"""Expected servers that can be automaticlaly selected by bottle."""
|
||||
|
||||
Waitress = enum.auto()
|
||||
Paste = enum.auto()
|
||||
PasteServer = enum.auto()
|
||||
Twisted = enum.auto()
|
||||
CherryPy = enum.auto()
|
||||
Cheroot = enum.auto()
|
||||
WsgiRef = enum.auto()
|
||||
|
||||
|
||||
@ -79,10 +80,10 @@ class AutoServer(enum.Enum):
|
||||
# auto servers in bottle.py
|
||||
AUTO_SERVER_IMPORTS = (
|
||||
(AutoServer.Waitress, "waitress"),
|
||||
(AutoServer.Paste, "paste"),
|
||||
(AutoServer.PasteServer, "paste"),
|
||||
(AutoServer.Twisted, "twisted.web"),
|
||||
(AutoServer.CherryPy, "cheroot.wsgi"),
|
||||
(AutoServer.CherryPy, "cherrypy.wsgiserver"),
|
||||
(AutoServer.CherryPy, "cherrypy"),
|
||||
(AutoServer.Cheroot, "cheroot"),
|
||||
# this should always be available because it's part of the stdlib
|
||||
(AutoServer.WsgiRef, "wsgiref"),
|
||||
)
|
||||
|
@ -9,11 +9,10 @@ from collections import defaultdict
|
||||
from collections import namedtuple
|
||||
from io import BytesIO
|
||||
from json import dumps
|
||||
from urllib.parse import urljoin, urlparse
|
||||
from urllib.parse import urljoin, urlparse, quote
|
||||
|
||||
from pypiserver.config import RunConfig
|
||||
from . import __version__
|
||||
from . import core
|
||||
from .bottle import (
|
||||
static_file,
|
||||
redirect,
|
||||
@ -30,6 +29,22 @@ config: RunConfig
|
||||
app = Bottle()
|
||||
|
||||
|
||||
def request_fullpath(request):
|
||||
parsed = urlparse(request.urlparts.scheme + "://" + request.urlparts.netloc)
|
||||
return parsed.path.rstrip("/") + "/" + request.fullpath.lstrip("/")
|
||||
|
||||
|
||||
def get_bad_url_redirect_path(request, project):
|
||||
"""Get the path for a bad root url."""
|
||||
uri = request_fullpath(request)
|
||||
if uri.endswith("/"):
|
||||
uri = uri[:-1]
|
||||
uri = uri.rsplit("/", 1)[0]
|
||||
project = quote(project)
|
||||
uri += f"/simple/{project}/"
|
||||
return uri
|
||||
|
||||
|
||||
class auth:
|
||||
"""decorator to apply authentication if specified for the decorated method & action"""
|
||||
|
||||
@ -55,15 +70,6 @@ def log_request():
|
||||
log.info(config.log_req_frmt, request.environ)
|
||||
|
||||
|
||||
@app.hook("before_request")
|
||||
def print_request():
|
||||
parsed = urlparse(request.urlparts.scheme + "://" + request.urlparts.netloc)
|
||||
request.custom_host = parsed.netloc
|
||||
request.custom_fullpath = (
|
||||
parsed.path.rstrip("/") + "/" + request.fullpath.lstrip("/")
|
||||
)
|
||||
|
||||
|
||||
@app.hook("after_request")
|
||||
def log_response():
|
||||
log.info(
|
||||
@ -90,7 +96,8 @@ def favicon():
|
||||
|
||||
@app.route("/")
|
||||
def root():
|
||||
fp = request.custom_fullpath
|
||||
parsed = urlparse(request.urlparts.scheme + "://" + request.urlparts.netloc)
|
||||
fp = parsed.path.rstrip("/") + "/" + request.fullpath.lstrip("/")
|
||||
|
||||
# Ensure template() does not consider `msg` as filename!
|
||||
msg = config.welcome_msg + "\n"
|
||||
@ -212,7 +219,7 @@ def update():
|
||||
@app.route("/packages")
|
||||
@auth("list")
|
||||
def pep_503_redirects(project=None):
|
||||
return redirect(request.custom_fullpath + "/", 301)
|
||||
return redirect(request_fullpath(request) + "/", 301)
|
||||
|
||||
|
||||
@app.post("/RPC2")
|
||||
@ -291,7 +298,7 @@ def simple(project):
|
||||
return redirect(f"{config.fallback_url.rstrip('/')}/{project}/")
|
||||
return HTTPError(404, f"Not Found ({normalized} does not exist)\n\n")
|
||||
|
||||
current_uri = request.custom_fullpath
|
||||
current_uri = request_fullpath(request)
|
||||
|
||||
links = (
|
||||
(
|
||||
@ -322,7 +329,7 @@ def simple(project):
|
||||
@app.route("/packages/")
|
||||
@auth("list")
|
||||
def list_packages():
|
||||
fp = request.custom_fullpath
|
||||
fp = request_fullpath(request)
|
||||
packages = sorted(
|
||||
config.backend.get_all_packages(),
|
||||
key=lambda x: (os.path.dirname(x.relfn), x.pkgname, x.parsed_version),
|
||||
@ -405,4 +412,4 @@ def json_info(project):
|
||||
@app.route("/:project/")
|
||||
def bad_url(project):
|
||||
"""Redirect unknown root URLs to /simple/."""
|
||||
return redirect(core.get_bad_url_redirect_path(request, project))
|
||||
return redirect(get_bad_url_redirect_path(request, project))
|
||||
|
3072
pypiserver/bottle.py
3072
pypiserver/bottle.py
File diff suppressed because it is too large
Load Diff
@ -3,7 +3,6 @@
|
||||
|
||||
import mimetypes
|
||||
import typing as t
|
||||
from urllib.parse import quote
|
||||
|
||||
from pypiserver.pkg_helpers import normalize_pkgname, parse_version
|
||||
|
||||
@ -12,17 +11,6 @@ mimetypes.add_type("application/octet-stream", ".whl")
|
||||
mimetypes.add_type("text/plain", ".asc")
|
||||
|
||||
|
||||
def get_bad_url_redirect_path(request, project):
|
||||
"""Get the path for a bad root url."""
|
||||
uri = request.custom_fullpath
|
||||
if uri.endswith("/"):
|
||||
uri = uri[:-1]
|
||||
uri = uri.rsplit("/", 1)[0]
|
||||
project = quote(project)
|
||||
uri += f"/simple/{project}/"
|
||||
return uri
|
||||
|
||||
|
||||
class PkgFile:
|
||||
__slots__ = [
|
||||
"pkgname", # The projects/package name with possible capitalization
|
||||
|
7
setup.py
7
setup.py
@ -22,7 +22,7 @@ setup_requires = [
|
||||
install_requires = [
|
||||
"pip>=7",
|
||||
"packaging>=23.2",
|
||||
"importlib_resources;python_version>'3.8' and python_version<'3.12'",
|
||||
"importlib_resources;python_version>'3.8'",
|
||||
]
|
||||
|
||||
|
||||
@ -77,11 +77,10 @@ setup(
|
||||
"Operating System :: POSIX",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.6",
|
||||
"Programming Language :: Python :: 3.7",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: Implementation :: CPython",
|
||||
"Programming Language :: Python :: Implementation :: PyPy",
|
||||
"Topic :: Software Development :: Build Tools",
|
||||
|
@ -13,7 +13,7 @@ import webtest
|
||||
|
||||
# Local Imports
|
||||
from tests.test_pkg_helpers import files, invalid_files
|
||||
from pypiserver import __main__, bottle, core, Bottle
|
||||
from pypiserver import __main__, bottle, core, Bottle, _app
|
||||
from pypiserver.backend import CachingFileBackend, SimpleFileBackend
|
||||
|
||||
# Enable logging to detect any problems with it
|
||||
@ -104,9 +104,14 @@ def welcome_file_all_vars(request, root):
|
||||
def add_file_to_root(app):
|
||||
def file_adder(root, filename, content=""):
|
||||
root.join(filename).write(content)
|
||||
backend = app.config.backend
|
||||
if isinstance(backend, CachingFileBackend):
|
||||
backend.cache_manager.invalidate_root_cache(root)
|
||||
# bottle has deprecated the use of attribute access
|
||||
# in configdicts
|
||||
try:
|
||||
backend = app.config.backend
|
||||
if isinstance(backend, CachingFileBackend):
|
||||
backend.cache_manager.invalidate_root_cache(root)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
return file_adder
|
||||
|
||||
@ -722,3 +727,13 @@ class TestRemovePkg:
|
||||
)
|
||||
assert resp.status == "404 Not Found"
|
||||
assert "foo (123) not found" in unescape(resp.text)
|
||||
|
||||
|
||||
def test_redirect_project_encodes_newlines():
|
||||
"""Ensure raw newlines are url encoded in the generated redirect."""
|
||||
project = "\nSet-Cookie:malicious=1;"
|
||||
request = bottle.Request(
|
||||
{"HTTP_X_FORWARDED_PROTO": "/\nSet-Cookie:malicious=1;"}
|
||||
)
|
||||
newpath = _app.get_bad_url_redirect_path(request, project)
|
||||
assert "\n" not in newpath
|
||||
|
@ -59,14 +59,6 @@ def test_fname_and_hash(tmp_path, hash_algo):
|
||||
assert pkgfile.fname_and_hash == f"{f.name}#{digester(pkgfile)}"
|
||||
|
||||
|
||||
def test_redirect_project_encodes_newlines():
|
||||
"""Ensure raw newlines are url encoded in the generated redirect."""
|
||||
request = Namespace(custom_fullpath="/\nSet-Cookie:malicious=1;")
|
||||
project = "\nSet-Cookie:malicious=1;"
|
||||
newpath = core.get_bad_url_redirect_path(request, project)
|
||||
assert "\n" not in newpath
|
||||
|
||||
|
||||
def test_normalize_pkgname_for_url_encodes_newlines():
|
||||
"""Ensure newlines are url encoded in package names for urls."""
|
||||
assert "\n" not in normalize_pkgname_for_url("/\nSet-Cookie:malicious=1;")
|
||||
|
2
tox.ini
2
tox.ini
@ -1,5 +1,5 @@
|
||||
[tox]
|
||||
envlist = py36, py37, py38, py39, py310, py311, pypy3
|
||||
envlist = py310, py311, py312, pypy3
|
||||
|
||||
[testenv]
|
||||
deps=-r{toxinidir}/requirements/test.pip
|
||||
|
Loading…
Reference in New Issue
Block a user