forked from github.com/pypiserver
Rework package parsing.
+ FIX #104: Stopped parsing invalid package-versions prefixed with `v`; they are invalid according to :pep-reference:`0440`. + Also support versions with epochs separated by `!` like `package-1!1.1.0`. + Move bottle-filename check on app module.
This commit is contained in:
parent
02ef881387
commit
ab2f170fe9
@ -5,9 +5,9 @@ Changelog
|
||||
------------------
|
||||
- Package-versions parsing:
|
||||
|
||||
- TODO: #104: Stopped parsing invalid package-versions prefixed with `v`; they are
|
||||
- #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`.
|
||||
- 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.
|
||||
|
||||
|
@ -2,6 +2,7 @@ import os
|
||||
import zipfile
|
||||
import mimetypes
|
||||
import logging
|
||||
import re
|
||||
|
||||
from . import core
|
||||
|
||||
@ -89,6 +90,11 @@ def root():
|
||||
SIMPLE=urljoin(fp, "simple/")
|
||||
)
|
||||
|
||||
_bottle_upload_filename_re = re.compile(r'^[a-z0-9_.!+-]+$', re.I)
|
||||
def is_valid_pkg_filename(fname):
|
||||
"""See https://github.com/pypiserver/pypiserver/issues/102"""
|
||||
return _bottle_upload_filename_re.match(fname) is not None
|
||||
|
||||
|
||||
@app.post('/')
|
||||
@auth("update")
|
||||
@ -138,7 +144,8 @@ def update():
|
||||
except KeyError:
|
||||
raise HTTPError(400, "Missing 'content' file-field!")
|
||||
|
||||
if not core.is_valid_pkg_filename(content.raw_filename):
|
||||
if (not is_valid_pkg_filename(content.raw_filename) or
|
||||
core.guess_pkgname_and_version(content.raw_filename) is None):
|
||||
raise HTTPError(400, "Bad filename: %s" % content.raw_filename)
|
||||
|
||||
if not config.overwrite and core.exists(packages.root, content.raw_filename):
|
||||
|
@ -93,8 +93,9 @@ mimetypes.add_type("application/octet-stream", ".egg")
|
||||
mimetypes.add_type("application/octet-stream", ".whl")
|
||||
|
||||
|
||||
# --- the following two functions were copied from distribute's pkg_resources module
|
||||
component_re = re.compile(r'(\d+ | [a-z]+ | \.| -)', re.VERBOSE)
|
||||
#### Next 2 functions adapted from :mod:`distribute.pkg_resources`.
|
||||
#
|
||||
component_re = re.compile(r'(\d+ | [a-z]+ | \.| -)', re.I | re.VERBOSE)
|
||||
replace = {'pre': 'c', 'preview': 'c', '-': 'final-', 'rc': 'c', 'dev': '@'}.get
|
||||
|
||||
|
||||
@ -120,22 +121,23 @@ def parse_version(s):
|
||||
parts.pop()
|
||||
parts.append(part)
|
||||
return tuple(parts)
|
||||
#
|
||||
#### -- End of distribute's code.
|
||||
|
||||
# -- end of distribute's code
|
||||
|
||||
_archive_suffix_rx = re.compile(
|
||||
r"(\.zip|\.tar\.gz|\.tgz|\.tar\.bz2|-py[23]\.\d-.*|\.win-amd64-py[23]\.\d\..*|\.win32-py[23]\.\d\..*|\.egg)$",
|
||||
re.IGNORECASE)
|
||||
|
||||
r"(\.zip|\.tar\.gz|\.tgz|\.tar\.bz2|-py[23]\.\d-.*|"
|
||||
"\.win-amd64-py[23]\.\d\..*|\.win32-py[23]\.\d\..*|\.egg)$",
|
||||
re.I)
|
||||
wheel_file_re = re.compile(
|
||||
r"""^(?P<namever>(?P<name>.+?)-(?P<ver>\d.*?))
|
||||
((-(?P<build>\d.*?))?-(?P<pyver>.+?)-(?P<abi>.+?)-(?P<plat>.+?)
|
||||
\.whl|\.dist-info)$""",
|
||||
re.VERBOSE)
|
||||
|
||||
_pkgname_re = re.compile(r'-(?i)v?\d+[\.a-z]')
|
||||
_pkgname_parts_re = re.compile(r'[\.\-](?=(?i)cp\d|py\d|macosx|linux|sunos|'
|
||||
'solaris|irix|aix|cygwin|win)')
|
||||
_pkgname_re = re.compile(r'-\d+[a-z_.!+]', re.I)
|
||||
_pkgname_parts_re = re.compile(
|
||||
r"[\.\-](?=cp\d|py\d|macosx|linux|sunos|solaris|irix|aix|cygwin|win)",
|
||||
re.I)
|
||||
|
||||
|
||||
def _guess_pkgname_and_version_wheel(basename):
|
||||
@ -180,11 +182,6 @@ 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):
|
||||
|
||||
@ -210,7 +207,8 @@ class PkgFile(object):
|
||||
def __repr__(self):
|
||||
return "%s(%s)" % (
|
||||
self.__class__.__name__,
|
||||
", ".join(["%s=%r" % (k, getattr(self, k, v)) for k in sorted(self.__slots__)]))
|
||||
", ".join(["%s=%r" % (k, getattr(self, k))
|
||||
for k in sorted(self.__slots__)]))
|
||||
|
||||
def hash(self, hash_algo):
|
||||
if not hasattr(self, '_hash'):
|
||||
|
@ -323,7 +323,9 @@ def test_upload_badAction(root, testapp):
|
||||
assert resp.status == '400 Bad Request'
|
||||
assert "Unsupported ':action' field: BAD" in hp.unescape(resp.text)
|
||||
|
||||
@pytest.mark.parametrize(("package"), [f[0] for f in test_core.files if f[1]])
|
||||
@pytest.mark.parametrize(("package"), [f[0]
|
||||
for f in test_core.files
|
||||
if f[1] and '/' not in f[0]])
|
||||
def test_upload(package, root, testapp):
|
||||
resp = testapp.post("/", params={':action': 'file_upload'},
|
||||
upload_files=[('content', package, b'')])
|
||||
|
@ -1,9 +1,13 @@
|
||||
#! /usr/bin/env py.test
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import pytest
|
||||
from pypiserver import __main__, core
|
||||
import logging
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
from pypiserver import __main__, core
|
||||
|
||||
|
||||
## Enable logging to detect any problems with it
|
||||
##
|
||||
@ -22,18 +26,19 @@ 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"),
|
||||
("ABC12-34_V1X-1.2.3.zip", "ABC12-34_V1X", "1.2.3"),
|
||||
("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"),
|
||||
("package-1.0.0-alpha.1.zip", "package", "1.0.0-alpha.1"),
|
||||
("package-1.3.7+build.11.e0f985a.zip", "package", "1.3.7+build.11.e0f985a"),
|
||||
("package-v1.8.1.301.ga0df26f.zip", "package", "v1.8.1.301.ga0df26f"),
|
||||
("package-v1-8.1.301.ga0df26f.zip", "package-v1", "8.1.301.ga0df26f"),
|
||||
("package-v1.1-8.1.301.ga0df26f.zip", "package-v1.1", "8.1.301.ga0df26f"),
|
||||
("package-2013.02.17.dev123.zip", "package", "2013.02.17.dev123"),
|
||||
("package-20000101.zip", "package", "20000101"),
|
||||
("flup-123-1.0.3.dev-20110405.tar.gz", "flup-123", "1.0.3.dev-20110405"),
|
||||
("package-123-1.0.0-alpha.1.zip", "package-123", "1.0.0-alpha.1"),
|
||||
("package-123-1.3.7+build.11.e0f985a.zip", "package-123", "1.3.7+build.11.e0f985a"),
|
||||
("package-123-v1.8.1.301.ga0df26f.zip", "package-123", "v1.8.1.301.ga0df26f"),
|
||||
("package-123-v1.1_3-8.1.zip", "package-123-v1.1_3", "8.1"),
|
||||
("package-123-2013.02.17.dev123.zip", "package-123", "2013.02.17.dev123"),
|
||||
("package-123-20000101.zip", "package-123", "20000101"),
|
||||
("pyelasticsearch-0.5-brainbot-1-20130712.zip", "pyelasticsearch", "0.5-brainbot-1-20130712"),
|
||||
@ -44,21 +49,24 @@ 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),
|
||||
("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
|
||||
("pkg.zip", 'pkg', ''),
|
||||
("foo/pkg.zip", 'pkg', ''),
|
||||
("foo/pkg-1b.zip", 'pkg', '1b'),
|
||||
]
|
||||
|
||||
def _capitalize_ext(fpath):
|
||||
f, e = os.path.splitext(fpath)
|
||||
if e != '.whl':
|
||||
e = e.upper()
|
||||
return f + e
|
||||
|
||||
@pytest.mark.parametrize(("filename", "pkgname", "version"), files)
|
||||
def test_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
|
||||
assert core.guess_pkgname_and_version(_capitalize_ext(filename)) == exp
|
||||
|
||||
|
||||
def test_listdir_bad_name(tmpdir):
|
||||
|
Loading…
Reference in New Issue
Block a user