FIX #102: uploading pkgs with +! chars in version.

+ Use `content.raw_filename` for allowing PEP0440 chars.
+ Add upload app-TCs.
+ Improve parse-pkg core-TC.
+ Update CHANGES on forthcomming release.
This commit is contained in:
ankostis on tokoti 2016-01-17 21:57:16 +01:00
parent 031f7a4289
commit 6b904db6c5
5 changed files with 104 additions and 9 deletions

@ -1,6 +1,17 @@
Changelog
=========
1.X.X (2016-01-xx)
------------------
- Package-versions parsing:
- TODO: #104: Stopped parsing invalid package-versions prefixed with `v`; they are
invalid according to :pep-reference:`0440`.
- TODO: Support versions with epochs separated by `!` like `package-1!1.1.0`.
- #102: FIX regression on uploading packages with `+` char in their version
caused by recent bottle-upgrade.
1.1.9 (2015-12-21)
------------------
"Ssss-elections" bug-fix & maintenance release.

@ -137,16 +137,17 @@ def update():
except KeyError:
raise HTTPError(400, "content file field not found")
if "/" in content.filename:
if not core.is_valid_pkg_filename(content.raw_filename):
raise HTTPError(400, "bad filename")
if not config.overwrite and core.exists(packages.root, content.filename):
if not config.overwrite and core.exists(packages.root, content.raw_filename):
log.warn("Cannot upload package(%s) since it already exists! \n" +
" You may use `--overwrite` option when starting server to disable this check. ",
content.filename)
raise HTTPError(409, "file already exists")
content.raw_filename)
msg = "Package already exists! Use `--overwrite` option on server."
raise HTTPError(409, msg)
core.store(packages.root, content.filename, content.save)
core.store(packages.root, content.raw_filename, content.save)
return ""

@ -180,6 +180,11 @@ def is_allowed_path(path_part):
p = path_part.replace("\\", "/")
return not (p.startswith(".") or "/." in p)
_bottle_upload_filename_re = re.compile(r'^[a-z0-9_.!+-]+$', re.I)
def is_valid_pkg_filename(fname):
return _bottle_upload_filename_re.match(fname) is not None
class PkgFile(object):
@ -211,7 +216,7 @@ class PkgFile(object):
if not hasattr(self, '_hash'):
self._hash = '%s=%.32s' % (hash_algo, digest_file(self.fn, hash_algo))
return self._hash
def _listdir(root):
root = os.path.abspath(root)

@ -4,12 +4,13 @@ import contextlib
import glob
import logging
import os
from pypiserver import __main__, bottle
import subprocess
import pytest
import webtest
from pypiserver import __main__, bottle
import test_core
# Enable logging to detect any problems with it
@ -305,3 +306,70 @@ def test_cache_control_set(root):
resp = app_with_cache.get("/packages/foo_bar-1.0.tar.gz")
assert "Cache-Control" in resp.headers
assert resp.headers["Cache-Control"] == 'public, max-age=%s' % AGE
def test_upload_noAction(root, testapp):
resp = testapp.post("/", expect_errors=1)
assert resp.status == '400 Bad Request'
assert ":action field not found" in resp.text
def test_upload_badAction(root, testapp):
resp = testapp.post("/", params={':action': 'BAD'}, expect_errors=1)
assert resp.status == '400 Bad Request'
assert "action not supported: BAD" in resp.text
@pytest.mark.parametrize(("package"), [f[0] for f in test_core.files if f[1]])
def test_upload(package, root, testapp):
resp = testapp.post("/", params={':action': 'file_upload'},
upload_files=[('content', package, b'')])
assert resp.status_int == 200
uploaded_pkgs = [f.basename for f in root.listdir()]
assert len(uploaded_pkgs) == 1
assert uploaded_pkgs[0].lower() == package.lower()
@pytest.mark.parametrize(("package"), [
f[0] for f in test_core.files
if f[1] is None])
def test_upload_badFilename(package, root, testapp):
resp = testapp.post("/", params={':action': 'file_upload'},
upload_files=[('content', package, b'')],
expect_errors=1)
assert resp.status == '400 Bad Request'
assert "bad filename" in resp.text
def test_remove_pkg_missingNaveVersion(root, testapp):
resp = testapp.post("/", expect_errors=1,
params={
':action': 'remove_pkg',
'version': '',
})
assert resp.status == '400 Bad Request'
assert "Name or version not specified" in resp.text
resp = testapp.post("/", expect_errors=1,
params={
':action': 'remove_pkg',
'name': '',
})
assert resp.status == '400 Bad Request'
assert "Name or version not specified" in resp.text
resp = testapp.post("/", expect_errors=1,
params={
':action': 'remove_pkg',
})
assert resp.status == '400 Bad Request'
assert "Name or version not specified" in resp.text
def test_remove_pkg_notFound(root, testapp):
resp = testapp.post("/", expect_errors=1,
params={
':action': 'remove_pkg',
'name': 'foo',
'version': '123',
})
assert resp.status == '404 Not Found'
assert "foo (123) not found" in resp.text
@pytest.mark.xfail()
def test_remove_pkg(root, testapp):
assert 0

14
tests/test_core.py Executable file → Normal file

@ -1,4 +1,5 @@
#! /usr/bin/env py.test
# -*- coding: utf-8 -*-
import pytest
from pypiserver import __main__, core
@ -13,6 +14,7 @@ files = [
("pytz-2012b.tar.bz2", "pytz", "2012b"),
("pytz-2012b.tgz", "pytz", "2012b"),
("pytz-2012b.ZIP", "pytz", "2012b"),
("pytz-2012a.zip", "pytz", "2012a"),
("gevent-1.0b1.win32-py2.6.exe", "gevent", "1.0b1"),
("gevent-1.0b1.win32-py2.7.msi", "gevent", "1.0b1"),
("greenlet-0.3.4-py3.1-win-amd64.egg", "greenlet", "0.3.4"),
@ -20,7 +22,6 @@ files = [
("greenlet-0.3.4-py3.2-win32.egg", "greenlet", "0.3.4"),
("greenlet-0.3.4-py2.7-linux-x86_64.egg", "greenlet", "0.3.4"),
("pep8-0.6.0.zip", "pep8", "0.6.0"),
("pytz-2012b.zip", "pytz", "2012b"),
("ABC12-34_V1X-1.2.3.zip", "ABC12-34_V1X", "1.2.3"),
("A100-200-XYZ-1.2.3.zip", "A100-200-XYZ", "1.2.3"),
("flup-1.0.3.dev-20110405.tar.gz", "flup", "1.0.3.dev-20110405"),
@ -43,12 +44,21 @@ files = [
("package-name-0.0.1.dev0.linux-x86_64.tar.gz", "package-name", "0.0.1.dev0"),
("package-name-0.0.1.dev0.macosx-10.10-intel.tar.gz", "package-name", "0.0.1.dev0"),
("package-name-0.0.1.alpha.1.win-amd64-py3.2.exe", "package-name", "0.0.1.alpha.1"),
("pkg-3!1.0-0.1.tgz", 'pkg-3!1.0', '0.1'), # TO BE FIXED
("pkg-3!1+.0-0.1.tgz", 'pkg-3!1+.0', '0.1'), # TO BE FIXED
("a-γρεεκ-package-1.0", None, None),
("some/pkg-1.0", None, None),
]
@pytest.mark.parametrize(("filename", "pkgname", "version"), files)
def test_guess_pkgname_and_version(filename, pkgname, version):
assert core.guess_pkgname_and_version(filename) == (pkgname, version)
if pkgname is None:
exp = None
else:
exp = (pkgname, version)
assert core.guess_pkgname_and_version(filename) == exp
def test_listdir_bad_name(tmpdir):