Implement PEP 503 Package Name Normalization and URL Redirects

This commit is contained in:
Dana Powers 2016-05-14 19:45:46 -07:00
parent d083266025
commit 1f6da1fe00
3 changed files with 63 additions and 45 deletions

@ -188,9 +188,11 @@ def update():
@app.route("/simple")
@app.route("/simple/:prefix")
@app.route('/packages')
@auth("list")
def simpleindex_redirect():
return redirect(request.fullpath + "/")
def pep_503_redirects(prefix=None):
return redirect(request.fullpath + "/", 301)
@app.route("/simple/")
@ -213,13 +215,13 @@ def simpleindex():
return template(tmpl, links=links)
@app.route("/simple/:prefix")
@app.route("/simple/:prefix/")
@auth("list")
def simple(prefix=""):
fp = request.fullpath
if not fp.endswith("/"):
fp += "/"
# PEP 503: require normalized prefix
normalized = core.normalize_pkgname(prefix)
if prefix != normalized:
return redirect('/simple/{0}/'.format(normalized), 301)
files = sorted(core.find_packages(packages(), prefix=prefix),
key=lambda x: (x.parsed_version, x.relfn))
@ -228,6 +230,7 @@ def simple(prefix=""):
return redirect("%s/%s/" % (config.fallback_url.rstrip("/"), prefix))
return HTTPError(404)
fp = request.fullpath
links = [(os.path.basename(f.relfn),
urljoin(fp, "../../packages/%s#%s" % (f.relfn_unix,
@ -249,14 +252,10 @@ def simple(prefix=""):
return template(tmpl, prefix=prefix, links=links)
@app.route('/packages')
@app.route('/packages/')
@auth("list")
def list_packages():
fp = request.fullpath
if not fp.endswith("/"):
fp += "/"
files = sorted(core.find_packages(packages()),
key=lambda x: (os.path.dirname(x.relfn),
x.pkgname,

@ -181,7 +181,8 @@ def guess_pkgname_and_version(path):
def normalize_pkgname(name):
return name.lower().replace("-", "_")
"""Perform PEP 503 normalization"""
return re.sub(r"[-_.]+", "-", name).lower()
def is_allowed_path(path_part):
@ -258,23 +259,11 @@ def find_packages(pkgs, prefix=""):
def get_prefixes(pkgs):
pkgnames = set()
normalized_pkgnames = set()
eggs = set()
for x in pkgs:
if x.pkgname:
if x.relfn.endswith(".egg"):
eggs.add(x.pkgname)
else:
pkgnames.add(x.pkgname)
normalized_pkgnames.add(x.pkgname_norm)
for x in eggs:
if x not in normalized_pkgnames:
pkgnames.add(x)
return pkgnames
normalized_pkgnames.add(x.pkgname_norm)
return normalized_pkgnames
def exists(root, filename):

@ -133,8 +133,15 @@ def test_root_remove_not_found_msg_antiXSS(testapp):
resp.mustcontain("alert", "somehost.org", no="<alert>")
def test_packages_empty(testapp):
def test_packages_redirect(testapp):
resp = testapp.get("/packages")
assert resp.status_code >= 300
assert resp.status_code < 400
assert resp.location.endswith('/packages/')
def test_packages_empty(testapp):
resp = testapp.get("/packages/")
assert len(resp.html("a")) == 0
@ -165,6 +172,13 @@ def test_packages_list_no_dotfiles(root, testapp):
assert "foo" not in resp
def test_simple_redirect(testapp):
resp = testapp.get("/simple")
assert resp.status_code >= 300
assert resp.status_code < 400
assert resp.location.endswith('/simple/')
def test_simple_list_no_dotfiles(root, testapp):
root.join(".foo-1.0.zip").write("secret")
resp = testapp.get("/simple/")
@ -200,13 +214,34 @@ def test_simple_list_no_dotdir2(root, testapp):
assert resp.html("a") == []
def test_simple_name_redirect(testapp):
resp = testapp.get("/simple/foobar")
assert resp.status_code >= 300
assert resp.status_code < 400
assert resp.location.endswith('/simple/foobar/')
@pytest.mark.parametrize('package,normalized', [
('FooBar', 'foobar'),
('Foo.Bar', 'foo-bar'),
('foo_bar', 'foo-bar'),
('Foo-Bar', 'foo-bar'),
('foo--_.bar', 'foo-bar'),
])
def test_simple_normalized_name_redirect(testapp, package, normalized):
resp = testapp.get("/simple/{0}/".format(package))
assert resp.status_code >= 300
assert resp.status_code < 400
assert resp.location.endswith('/simple/{0}/'.format(normalized))
def test_simple_index(root, testapp):
root.join("foobar-1.0.zip").write("")
root.join("foobar-1.1.zip").write("")
root.join("foobarbaz-1.1.zip").write("")
root.join("foobar.baz-1.1.zip").write("")
resp = testapp.get("/simple/foobar")
resp = testapp.get("/simple/foobar/")
assert len(resp.html("a")) == 2
@ -223,7 +258,7 @@ def test_simple_index_list(root, testapp):
def test_simple_index_case(root, testapp):
root.join("FooBar-1.0.zip").write("")
root.join("FooBar-1.1.zip").write("")
resp = testapp.get("/simple/foobar")
resp = testapp.get("/simple/foobar/")
assert len(resp.html("a")) == 2
@ -234,23 +269,18 @@ def test_nonroot_root(testpriv):
def test_nonroot_simple_index(root, testpriv):
root.join("foobar-1.0.zip").write("")
for path in ["/priv/simple/foobar",
"/priv/simple/foobar/"]:
resp = testpriv.get(path)
links = resp.html("a")
assert len(links) == 1
assert links[0]["href"].startswith("/priv/packages/foobar-1.0.zip#")
resp = testpriv.get("/priv/simple/foobar/")
links = resp.html("a")
assert len(links) == 1
assert links[0]["href"].startswith("/priv/packages/foobar-1.0.zip#")
def test_nonroot_simple_packages(root, testpriv):
root.join("foobar-1.0.zip").write("123")
for path in ["/priv/packages",
"/priv/packages/"]:
resp = testpriv.get(path)
links = resp.html("a")
assert len(links) == 1
assert links[0]["href"].startswith("/priv/packages/foobar-1.0.zip#")
resp = testpriv.get("/priv/packages/")
links = resp.html("a")
assert len(links) == 1
assert links[0]["href"].startswith("/priv/packages/foobar-1.0.zip#")
def test_root_no_relative_paths(testpriv):
@ -276,14 +306,14 @@ def test_simple_index_list_name_with_underscore(root, testapp):
resp = testapp.get("/simple/")
assert len(resp.html("a")) == 1
hrefs = [x["href"] for x in resp.html("a")]
assert hrefs == ["foo_bar/"]
assert hrefs == ["foo-bar/"]
def test_simple_index_egg_and_tarball(root, testapp):
root.join("foo-bar-1.0.tar.gz").write("")
root.join("foo_bar-1.0-py2.7.egg").write("")
resp = testapp.get("/simple/foo-bar")
resp = testapp.get("/simple/foo-bar/")
assert len(resp.html("a")) == 2
@ -292,9 +322,9 @@ def test_simple_index_list_name_with_underscore_no_egg(root, testapp):
root.join("foo-bar-1.1.tar.gz").write("")
resp = testapp.get("/simple/")
assert len(resp.html("a")) == 2
assert len(resp.html("a")) == 1
hrefs = set([x["href"] for x in resp.html("a")])
assert hrefs == set(["foo_bar/", "foo-bar/"])
assert hrefs == set(["foo-bar/"])
def test_no_cache_control_set(root, _app, testapp):