diff --git a/requirements/main.in b/requirements/main.in index ff62e5248969..9c40b32d6cbc 100644 --- a/requirements/main.in +++ b/requirements/main.in @@ -25,7 +25,7 @@ lxml mistune msgpack natsort -packaging>=15.2 +packaging>=20.9 paginate>=0.5.2 paginate_sqlalchemy passlib>=1.6.4 diff --git a/tests/unit/forklift/test_legacy.py b/tests/unit/forklift/test_legacy.py index 235c56700087..42643c9f0332 100644 --- a/tests/unit/forklift/test_legacy.py +++ b/tests/unit/forklift/test_legacy.py @@ -20,7 +20,6 @@ from cgi import FieldStorage from unittest import mock -import pkg_resources import pretend import pytest import requests @@ -669,6 +668,45 @@ def test_is_duplicate_true(self, pyramid_config, db_request): assert legacy._is_duplicate_file(db_request.db, filename, hashes) + @pytest.mark.parametrize("file_name_prefix", ["foo_bar", "foo.bar"]) + @pytest.mark.parametrize("project_name", ["foo_bar", "foo.bar"]) + def test_is_duplicate_true_non_normalized_filename( + self, pyramid_config, db_request, file_name_prefix, project_name + ): + pyramid_config.testing_securitypolicy(userid=1) + + user = UserFactory.create() + EmailFactory.create(user=user) + project = ProjectFactory.create(name=project_name) + release = ReleaseFactory.create(project=project, version="1.0") + RoleFactory.create(user=user, project=project) + + filename = "{}-{}.tar.gz".format(project_name, release.version) + file_content = io.BytesIO(_TAR_GZ_PKG_TESTDATA) + file_value = file_content.getvalue() + + hashes = { + "sha256": hashlib.sha256(file_value).hexdigest(), + "md5": hashlib.md5(file_value).hexdigest(), + "blake2_256": hashlib.blake2b(file_value, digest_size=256 // 8).hexdigest(), + } + db_request.db.add( + File( + release=release, + filename=filename, + md5_digest=hashes["md5"], + sha256_digest=hashes["sha256"], + blake2_256_digest=hashes["blake2_256"], + path="source/{name[0]}/{name}/{filename}".format( + name=project.name, filename=filename + ), + ) + ) + + duplicate_filename = "{}-{}.tar.gz".format(file_name_prefix, release.version) + + assert legacy._is_duplicate_file(db_request.db, duplicate_filename, hashes) + def test_is_duplicate_none(self, pyramid_config, db_request): pyramid_config.testing_securitypolicy(userid=1) @@ -2412,7 +2450,7 @@ def test_upload_fails_with_diff_filename_same_blake2( "filetype": "sdist", "md5_digest": hashlib.md5(file_content.getvalue()).hexdigest(), "content": pretend.stub( - filename="{}-fake.tar.gz".format(project.name), + filename="{}-0.1.tar.gz".format(project.name), file=file_content, type="application/tar", ), @@ -2446,7 +2484,14 @@ def test_upload_fails_with_diff_filename_same_blake2( "400 File already exists. See /the/help/url/ for more information." ) - def test_upload_fails_with_wrong_filename(self, pyramid_config, db_request): + @pytest.mark.parametrize( + "filename", + [ + "nope-{version}.tar.gz", + "nope-{version}-py3-none-any.whl", + ], + ) + def test_upload_fails_with_wrong_prefix(self, pyramid_config, db_request, filename): pyramid_config.testing_securitypolicy(userid=1) user = UserFactory.create() @@ -2456,7 +2501,7 @@ def test_upload_fails_with_wrong_filename(self, pyramid_config, db_request): release = ReleaseFactory.create(project=project, version="1.0") RoleFactory.create(user=user, project=project) - filename = "nope-{}.tar.gz".format(release.version) + filename = filename.format(version=release.version) db_request.POST = MultiDict( { @@ -2480,11 +2525,108 @@ def test_upload_fails_with_wrong_filename(self, pyramid_config, db_request): assert resp.status_code == 400 assert resp.status == ( - "400 Start filename for {!r} with {!r}.".format( - project.name, pkg_resources.safe_name(project.name).lower() + "400 Invalid filename: Start filename for project {!r} with {!r}.".format( + project.name, project.normalized_name.replace("-", "_") ) ) + def test_wheel_upload_passes_with_period_in_prefix( + self, pyramid_config, db_request, metrics, monkeypatch + ): + """ + This tests existing behavior which allows periods in the project name, + which violates the wheel spec at [1], which requires that the filename + start with the PEP 503 normalization of the project name, followed by + replacing - with _. + + [1] https://packaging.python.org/specifications/binary-distribution-format/ + """ + pyramid_config.testing_securitypolicy(userid=1) + + user = UserFactory.create() + db_request.user = user + EmailFactory.create(user=user) + project = ProjectFactory.create(name="some.project") + release = ReleaseFactory.create(project=project, version="1.0") + RoleFactory.create(user=user, project=project) + + filename = "{project_name}-{version}-py3-none-any.whl".format( + version=release.version, project_name=project.name + ) + monkeypatch.setattr(legacy, "_is_valid_dist_file", lambda *a, **kw: True) + + db_request.find_service = pretend.call_recorder( + lambda svc, name=None, context=None: { + IFileStorage: pretend.stub(store=lambda *a, **kw: None), + IMetricsService: metrics, + }.get(svc) + ) + + db_request.user_agent = "warehouse-tests/6.6.6" + db_request.POST = MultiDict( + { + "metadata_version": "1.2", + "name": project.name, + "version": release.version, + "filetype": "bdist_wheel", + "pyversion": "py3", + "md5_digest": _TAR_GZ_PKG_MD5, + "content": pretend.stub( + filename=filename, + file=io.BytesIO(_TAR_GZ_PKG_TESTDATA), + type="application/tar", + ), + } + ) + + resp = legacy.file_upload(db_request) + + assert resp.status_code == 200 + + @pytest.mark.parametrize( + "filename", + [ + "nope-notaversion.tar.gz", + "nope-notaversion-py3-none-any.whl", + ], + ) + def test_upload_fails_with_invalid_version( + self, pyramid_config, db_request, filename + ): + pyramid_config.testing_securitypolicy(userid=1) + + user = UserFactory.create() + db_request.user = user + EmailFactory.create(user=user) + project = ProjectFactory.create() + release = ReleaseFactory.create(project=project, version="1.0") + RoleFactory.create(user=user, project=project) + + filename = filename.format(version=release.version) + + db_request.POST = MultiDict( + { + "metadata_version": "1.2", + "name": project.name, + "version": release.version, + "filetype": "sdist", + "md5_digest": "nope!", + "content": pretend.stub( + filename=filename, + file=io.BytesIO(b"a" * (legacy.MAX_FILESIZE + 1)), + type="application/tar", + ), + } + ) + + with pytest.raises(HTTPBadRequest) as excinfo: + legacy.file_upload(db_request) + + resp = excinfo.value + + assert resp.status_code == 400 + assert resp.status == "400 Invalid filename: Invalid version: 'notaversion'." + def test_upload_fails_with_invalid_extension(self, pyramid_config, db_request): pyramid_config.testing_securitypolicy(userid=1) diff --git a/warehouse/forklift/legacy.py b/warehouse/forklift/legacy.py index 3187bce7ccec..49235986f312 100644 --- a/warehouse/forklift/legacy.py +++ b/warehouse/forklift/legacy.py @@ -26,7 +26,6 @@ import packaging.specifiers import packaging.utils import packaging.version -import pkg_resources import requests import stdlib_list import wtforms @@ -737,6 +736,15 @@ def _is_valid_dist_file(filename, filetype): return True +def _parse_filename(filename): + if filename.endswith(".whl"): + parse_func = packaging.utils.parse_wheel_filename + else: + parse_func = packaging.utils.parse_sdist_filename + + return parse_func(filename) + + def _is_duplicate_file(db_session, filename, hashes): """ Check to see if file already exists, and if it's content matches. @@ -760,7 +768,8 @@ def _is_duplicate_file(db_session, filename, hashes): if file_ is not None: return ( - file_.filename == filename + # This has the effect of canonicalizing the project name and version + _parse_filename(file_.filename) == _parse_filename(filename) and file_.sha256_digest == hashes["sha256"] and file_.md5_digest == hashes["md5"] and file_.blake2_256_digest == hashes["blake2_256"] @@ -1229,13 +1238,31 @@ def file_upload(request): "for more information.", ) - # Make sure that our filename matches the project that it is being uploaded - # to. - prefix = pkg_resources.safe_name(project.name).lower() - if not pkg_resources.safe_name(filename).lower().startswith(prefix): + try: + parsed_filename_parts = _parse_filename(filename) + except ( + packaging.utils.InvalidSdistFilename, + packaging.utils.InvalidWheelFilename, + packaging.version.InvalidVersion, + ) as e: + raise _exc_with_message(HTTPBadRequest, f"Invalid filename: {e}.") + + # Make sure that the normalized version of the project name in the filename + # matches the normalized project name that it is being uploaded to. + # + # NB: This allows periods the project name, which violates the wheel spec + # at [1], which requires that the filename start with the PEP 503 + # normalization of the project name, followed by replacing - with _. + # + # [1] https://packaging.python.org/specifications/binary-distribution-format/ + if not parsed_filename_parts[0] == project.normalized_name: + escaped_normalized_project_name = project.normalized_name.replace("-", "_") + help_url = request.help_url(_anchor="invalid-file-name") raise _exc_with_message( HTTPBadRequest, - "Start filename for {!r} with {!r}.".format(project.name, prefix), + f"Invalid filename: Start filename for project {project.name!r} " + f"with {escaped_normalized_project_name!r}. " + f"See {help_url} for more information", ) # Check the content type of what is being uploaded diff --git a/warehouse/locale/messages.pot b/warehouse/locale/messages.pot index 45cbb56c6e03..f0f9ee8d930b 100644 --- a/warehouse/locale/messages.pot +++ b/warehouse/locale/messages.pot @@ -376,68 +376,68 @@ msgstr "" #: warehouse/templates/packaging/detail.html:350 #: warehouse/templates/pages/classifiers.html:25 #: warehouse/templates/pages/help.html:20 -#: warehouse/templates/pages/help.html:208 -#: warehouse/templates/pages/help.html:215 -#: warehouse/templates/pages/help.html:229 -#: warehouse/templates/pages/help.html:245 -#: warehouse/templates/pages/help.html:249 -#: warehouse/templates/pages/help.html:306 -#: warehouse/templates/pages/help.html:333 -#: warehouse/templates/pages/help.html:338 -#: warehouse/templates/pages/help.html:343 +#: warehouse/templates/pages/help.html:210 +#: warehouse/templates/pages/help.html:217 +#: warehouse/templates/pages/help.html:231 +#: warehouse/templates/pages/help.html:247 +#: warehouse/templates/pages/help.html:251 +#: warehouse/templates/pages/help.html:308 +#: warehouse/templates/pages/help.html:335 +#: warehouse/templates/pages/help.html:340 #: warehouse/templates/pages/help.html:345 -#: warehouse/templates/pages/help.html:350 -#: warehouse/templates/pages/help.html:351 +#: warehouse/templates/pages/help.html:347 #: warehouse/templates/pages/help.html:352 -#: warehouse/templates/pages/help.html:356 -#: warehouse/templates/pages/help.html:389 +#: warehouse/templates/pages/help.html:353 +#: warehouse/templates/pages/help.html:354 +#: warehouse/templates/pages/help.html:358 #: warehouse/templates/pages/help.html:391 -#: warehouse/templates/pages/help.html:394 -#: warehouse/templates/pages/help.html:430 -#: warehouse/templates/pages/help.html:435 -#: warehouse/templates/pages/help.html:441 -#: warehouse/templates/pages/help.html:499 -#: warehouse/templates/pages/help.html:519 -#: warehouse/templates/pages/help.html:525 -#: warehouse/templates/pages/help.html:528 +#: warehouse/templates/pages/help.html:393 +#: warehouse/templates/pages/help.html:396 +#: warehouse/templates/pages/help.html:432 +#: warehouse/templates/pages/help.html:437 +#: warehouse/templates/pages/help.html:443 +#: warehouse/templates/pages/help.html:501 +#: warehouse/templates/pages/help.html:521 +#: warehouse/templates/pages/help.html:527 #: warehouse/templates/pages/help.html:530 -#: warehouse/templates/pages/help.html:539 -#: warehouse/templates/pages/help.html:551 -#: warehouse/templates/pages/help.html:558 -#: warehouse/templates/pages/help.html:570 -#: warehouse/templates/pages/help.html:571 -#: warehouse/templates/pages/help.html:576 -#: warehouse/templates/pages/help.html:601 -#: warehouse/templates/pages/help.html:614 -#: warehouse/templates/pages/help.html:619 -#: warehouse/templates/pages/help.html:631 -#: warehouse/templates/pages/help.html:652 -#: warehouse/templates/pages/help.html:675 -#: warehouse/templates/pages/help.html:682 -#: warehouse/templates/pages/help.html:694 -#: warehouse/templates/pages/help.html:705 -#: warehouse/templates/pages/help.html:710 -#: warehouse/templates/pages/help.html:718 -#: warehouse/templates/pages/help.html:729 -#: warehouse/templates/pages/help.html:746 -#: warehouse/templates/pages/help.html:753 -#: warehouse/templates/pages/help.html:761 -#: warehouse/templates/pages/help.html:777 -#: warehouse/templates/pages/help.html:782 -#: warehouse/templates/pages/help.html:787 +#: warehouse/templates/pages/help.html:532 +#: warehouse/templates/pages/help.html:541 +#: warehouse/templates/pages/help.html:553 +#: warehouse/templates/pages/help.html:560 +#: warehouse/templates/pages/help.html:572 +#: warehouse/templates/pages/help.html:573 +#: warehouse/templates/pages/help.html:578 +#: warehouse/templates/pages/help.html:603 +#: warehouse/templates/pages/help.html:616 +#: warehouse/templates/pages/help.html:621 +#: warehouse/templates/pages/help.html:633 +#: warehouse/templates/pages/help.html:654 +#: warehouse/templates/pages/help.html:677 +#: warehouse/templates/pages/help.html:684 +#: warehouse/templates/pages/help.html:696 +#: warehouse/templates/pages/help.html:707 +#: warehouse/templates/pages/help.html:712 +#: warehouse/templates/pages/help.html:720 +#: warehouse/templates/pages/help.html:731 +#: warehouse/templates/pages/help.html:748 +#: warehouse/templates/pages/help.html:768 +#: warehouse/templates/pages/help.html:776 +#: warehouse/templates/pages/help.html:792 #: warehouse/templates/pages/help.html:797 -#: warehouse/templates/pages/help.html:806 -#: warehouse/templates/pages/help.html:820 -#: warehouse/templates/pages/help.html:828 -#: warehouse/templates/pages/help.html:836 -#: warehouse/templates/pages/help.html:844 -#: warehouse/templates/pages/help.html:854 +#: warehouse/templates/pages/help.html:802 +#: warehouse/templates/pages/help.html:812 +#: warehouse/templates/pages/help.html:821 +#: warehouse/templates/pages/help.html:835 +#: warehouse/templates/pages/help.html:843 +#: warehouse/templates/pages/help.html:851 +#: warehouse/templates/pages/help.html:859 #: warehouse/templates/pages/help.html:869 #: warehouse/templates/pages/help.html:884 -#: warehouse/templates/pages/help.html:885 -#: warehouse/templates/pages/help.html:886 -#: warehouse/templates/pages/help.html:887 -#: warehouse/templates/pages/help.html:892 +#: warehouse/templates/pages/help.html:899 +#: warehouse/templates/pages/help.html:900 +#: warehouse/templates/pages/help.html:901 +#: warehouse/templates/pages/help.html:902 +#: warehouse/templates/pages/help.html:907 #: warehouse/templates/pages/security.html:36 #: warehouse/templates/pages/sponsors.html:33 #: warehouse/templates/pages/sponsors.html:37 @@ -522,7 +522,7 @@ msgstr "" #: warehouse/templates/base.html:41 warehouse/templates/base.html:55 #: warehouse/templates/base.html:256 #: warehouse/templates/includes/current-user-indicator.html:55 -#: warehouse/templates/pages/help.html:105 +#: warehouse/templates/pages/help.html:106 #: warehouse/templates/pages/sitemap.html:27 msgid "Help" msgstr "" @@ -1881,7 +1881,7 @@ msgstr "" #: warehouse/templates/includes/packaging/project-data.html:84 #: warehouse/templates/includes/packaging/project-data.html:86 -#: warehouse/templates/pages/help.html:562 +#: warehouse/templates/pages/help.html:564 msgid "Maintainer:" msgstr "" @@ -3437,7 +3437,7 @@ msgid "" msgstr "" #: warehouse/templates/manage/roles.html:39 -#: warehouse/templates/pages/help.html:561 +#: warehouse/templates/pages/help.html:563 msgid "There are two possible roles for collaborators:" msgstr "" @@ -4348,97 +4348,101 @@ msgid "" msgstr "" #: warehouse/templates/pages/help.html:89 -msgid "Why isn't my desired project name available?" +msgid "Why am I getting an \"Invalid filename\" error?" msgstr "" #: warehouse/templates/pages/help.html:90 -msgid "How do I claim an abandoned or previously registered project name?" +msgid "Why isn't my desired project name available?" msgstr "" #: warehouse/templates/pages/help.html:91 -msgid "What collaborator roles are available for a project on PyPI?" +msgid "How do I claim an abandoned or previously registered project name?" msgstr "" #: warehouse/templates/pages/help.html:92 -msgid "How do I become an owner/maintainer of a project on PyPI?" +msgid "What collaborator roles are available for a project on PyPI?" msgstr "" #: warehouse/templates/pages/help.html:93 -msgid "How can I upload a project description in a different format?" +msgid "How do I become an owner/maintainer of a project on PyPI?" msgstr "" #: warehouse/templates/pages/help.html:94 -msgid "How do I request a new trove classifier?" +msgid "How can I upload a project description in a different format?" msgstr "" #: warehouse/templates/pages/help.html:95 +msgid "How do I request a new trove classifier?" +msgstr "" + +#: warehouse/templates/pages/help.html:96 msgid "Where can I report a bug or provide feedback about PyPI?" msgstr "" -#: warehouse/templates/pages/help.html:97 +#: warehouse/templates/pages/help.html:98 msgid "Who maintains PyPI?" msgstr "" -#: warehouse/templates/pages/help.html:98 +#: warehouse/templates/pages/help.html:99 msgid "What powers PyPI?" msgstr "" -#: warehouse/templates/pages/help.html:99 +#: warehouse/templates/pages/help.html:100 msgid "Can I depend on PyPI being available?" msgstr "" -#: warehouse/templates/pages/help.html:100 +#: warehouse/templates/pages/help.html:101 msgid "How can I contribute to PyPI?" msgstr "" -#: warehouse/templates/pages/help.html:101 +#: warehouse/templates/pages/help.html:102 msgid "How do I keep up with upcoming changes to PyPI?" msgstr "" -#: warehouse/templates/pages/help.html:102 +#: warehouse/templates/pages/help.html:103 msgid "" "What does the \"beta feature\" badge mean? What are Warehouse's current " "beta features?" msgstr "" -#: warehouse/templates/pages/help.html:103 +#: warehouse/templates/pages/help.html:104 msgid "How do I pronounce \"PyPI\"?" msgstr "" -#: warehouse/templates/pages/help.html:110 +#: warehouse/templates/pages/help.html:111 msgid "Common questions" msgstr "" -#: warehouse/templates/pages/help.html:113 -#: warehouse/templates/pages/help.html:196 +#: warehouse/templates/pages/help.html:114 +#: warehouse/templates/pages/help.html:198 msgid "Basics" msgstr "" -#: warehouse/templates/pages/help.html:124 +#: warehouse/templates/pages/help.html:125 msgid "My Account" msgstr "" -#: warehouse/templates/pages/help.html:141 -#: warehouse/templates/pages/help.html:516 +#: warehouse/templates/pages/help.html:142 +#: warehouse/templates/pages/help.html:518 msgid "Integrating" msgstr "" -#: warehouse/templates/pages/help.html:151 -#: warehouse/templates/pages/help.html:543 +#: warehouse/templates/pages/help.html:152 +#: warehouse/templates/pages/help.html:545 msgid "Administration of projects on PyPI" msgstr "" -#: warehouse/templates/pages/help.html:166 -#: warehouse/templates/pages/help.html:627 +#: warehouse/templates/pages/help.html:167 +#: warehouse/templates/pages/help.html:629 msgid "Troubleshooting" msgstr "" -#: warehouse/templates/pages/help.html:183 -#: warehouse/templates/pages/help.html:773 +#: warehouse/templates/pages/help.html:185 +#: warehouse/templates/pages/help.html:788 msgid "About" msgstr "" -#: warehouse/templates/pages/help.html:199 +#: warehouse/templates/pages/help.html:201 #, python-format msgid "" "\n" @@ -4462,7 +4466,7 @@ msgid "" " " msgstr "" -#: warehouse/templates/pages/help.html:208 +#: warehouse/templates/pages/help.html:210 #, python-format msgid "" "To learn how to install a file from PyPI, visit the Python Packaging User Guide." msgstr "" -#: warehouse/templates/pages/help.html:215 +#: warehouse/templates/pages/help.html:217 #, python-format msgid "" "For full instructions on configuring, packaging and distributing your " @@ -4482,7 +4486,7 @@ msgid "" "target=\"_blank\" rel=\"noopener\">Python Packaging User Guide." msgstr "" -#: warehouse/templates/pages/help.html:222 +#: warehouse/templates/pages/help.html:224 #, python-format msgid "" "Classifiers are used to categorize projects on PyPI. See PyPI itself has not suffered a breach. This is a protective measure " @@ -4570,7 +4574,7 @@ msgid "" "href=\"%(reset_pwd_href)s\">reset your password.

" msgstr "" -#: warehouse/templates/pages/help.html:284 +#: warehouse/templates/pages/help.html:286 #, python-format msgid "" "

All PyPI user events are stored under security history in account " @@ -4580,7 +4584,7 @@ msgid "" "href=\"mailto:%(admin_email)s\">%(admin_email)s

" msgstr "" -#: warehouse/templates/pages/help.html:296 +#: warehouse/templates/pages/help.html:298 msgid "" "

A PyPI API token linked to your account was posted on a public " "website. It was automatically revoked, but before regenerating a new one," @@ -4589,7 +4593,7 @@ msgid "" "applies too.

" msgstr "" -#: warehouse/templates/pages/help.html:306 +#: warehouse/templates/pages/help.html:308 #, python-format msgid "" "

Two factor authentication (2FA) makes your account more secure by " @@ -4608,7 +4612,7 @@ msgid "" "rel=\"noopener\">discuss.python.org.

" msgstr "" -#: warehouse/templates/pages/help.html:333 +#: warehouse/templates/pages/help.html:335 #, python-format msgid "" "PyPI users can set up two-factor authentication using any authentication " @@ -4617,21 +4621,21 @@ msgid "" "password\">TOTP standard." msgstr "" -#: warehouse/templates/pages/help.html:334 +#: warehouse/templates/pages/help.html:336 msgid "" "TOTP authentication " "applications generate a regularly changing authentication code to use " "when logging into your account." msgstr "" -#: warehouse/templates/pages/help.html:335 +#: warehouse/templates/pages/help.html:337 msgid "" "Because TOTP is an " "open standard, there are many applications that are compatible with your " "PyPI account. Popular applications include:" msgstr "" -#: warehouse/templates/pages/help.html:338 +#: warehouse/templates/pages/help.html:340 #, python-format msgid "" "Google Authenticator for iOS" msgstr "" -#: warehouse/templates/pages/help.html:341 #: warehouse/templates/pages/help.html:343 -#: warehouse/templates/pages/help.html:348 +#: warehouse/templates/pages/help.html:345 #: warehouse/templates/pages/help.html:350 +#: warehouse/templates/pages/help.html:352 msgid "(proprietary)" msgstr "" -#: warehouse/templates/pages/help.html:345 +#: warehouse/templates/pages/help.html:347 #, python-format msgid "" "Duo Mobile for iOS" msgstr "" -#: warehouse/templates/pages/help.html:351 -#: warehouse/templates/pages/help.html:352 +#: warehouse/templates/pages/help.html:353 +#: warehouse/templates/pages/help.html:354 msgid "(open source)" msgstr "" -#: warehouse/templates/pages/help.html:356 +#: warehouse/templates/pages/help.html:358 #, python-format msgid "" "Some password managers (e.g. 2FA with an " "authentication application:" msgstr "" -#: warehouse/templates/pages/help.html:366 +#: warehouse/templates/pages/help.html:368 msgid "" "Open an authentication (TOTP) application" msgstr "" -#: warehouse/templates/pages/help.html:367 +#: warehouse/templates/pages/help.html:369 msgid "" "Log in to your PyPI account, go to your account settings, and choose " "\"Add 2FA with " "authentication application\"" msgstr "" -#: warehouse/templates/pages/help.html:368 +#: warehouse/templates/pages/help.html:370 msgid "" "PyPI will generate a secret key, specific to your account. This is " "displayed as a QR code, and as a text code." msgstr "" -#: warehouse/templates/pages/help.html:369 +#: warehouse/templates/pages/help.html:371 msgid "" "Scan the QR code with your authentication application, or type it in " "manually. The method of input will depend on the application you have " "chosen." msgstr "" -#: warehouse/templates/pages/help.html:370 +#: warehouse/templates/pages/help.html:372 msgid "" "Your application will generate an authentication code - use this to " "verify your set up on PyPI" msgstr "" -#: warehouse/templates/pages/help.html:373 +#: warehouse/templates/pages/help.html:375 msgid "" "The PyPI server and your application now share your PyPI secret key, " "allowing your application to generate valid authentication codes for your" " PyPI account." msgstr "" -#: warehouse/templates/pages/help.html:375 -#: warehouse/templates/pages/help.html:417 +#: warehouse/templates/pages/help.html:377 +#: warehouse/templates/pages/help.html:419 msgid "Next time you log in to PyPI you'll need to:" msgstr "" -#: warehouse/templates/pages/help.html:377 -#: warehouse/templates/pages/help.html:469 +#: warehouse/templates/pages/help.html:379 +#: warehouse/templates/pages/help.html:471 msgid "Provide your username and password, as normal" msgstr "" -#: warehouse/templates/pages/help.html:378 +#: warehouse/templates/pages/help.html:380 msgid "Open your authentication application to generate an authentication code" msgstr "" -#: warehouse/templates/pages/help.html:379 +#: warehouse/templates/pages/help.html:381 msgid "Use this code to finish logging into PyPI" msgstr "" -#: warehouse/templates/pages/help.html:385 +#: warehouse/templates/pages/help.html:387 msgid "" "A security device is a USB key or other " "device that generates a one-time password and sends that password to " @@ -4741,11 +4745,11 @@ msgid "" "user." msgstr "" -#: warehouse/templates/pages/help.html:387 +#: warehouse/templates/pages/help.html:389 msgid "To set up two factor authentication with a USB key, you'll need:" msgstr "" -#: warehouse/templates/pages/help.html:389 +#: warehouse/templates/pages/help.html:391 #, python-format msgid "" "To use a :" msgstr "" -#: warehouse/templates/pages/help.html:394 +#: warehouse/templates/pages/help.html:396 #, python-format msgid "" "Popular keys include Thetis." msgstr "" -#: warehouse/templates/pages/help.html:401 +#: warehouse/templates/pages/help.html:403 msgid "" "Note that some older Yubico USB keys do not follow the FIDO " "specification, and will therefore not work with PyPI" msgstr "" -#: warehouse/templates/pages/help.html:406 +#: warehouse/templates/pages/help.html:408 msgid "Follow these steps:" msgstr "" -#: warehouse/templates/pages/help.html:408 +#: warehouse/templates/pages/help.html:410 msgid "" "\n" "
  • Log in to your PyPI account, go to your account settings, " @@ -4800,13 +4804,13 @@ msgid "" " " msgstr "" -#: warehouse/templates/pages/help.html:415 +#: warehouse/templates/pages/help.html:417 msgid "" "Once complete, your USB key will be registered to your PyPI account and " "can be used during the log in process." msgstr "" -#: warehouse/templates/pages/help.html:419 +#: warehouse/templates/pages/help.html:421 msgid "" "\n" "
  • Provide your username and password, as normal
  • \n" @@ -4815,7 +4819,7 @@ msgid "" " " msgstr "" -#: warehouse/templates/pages/help.html:430 +#: warehouse/templates/pages/help.html:432 #, python-format msgid "" "There is a growing ecosystem of mobile phones to act as security devices." msgstr "" -#: warehouse/templates/pages/help.html:441 +#: warehouse/templates/pages/help.html:443 #, python-format msgid "" "As PyPI's two factor implementation follows the authentication " "application or security device, you can use " "these codes to sign into PyPI." msgstr "" -#: warehouse/templates/pages/help.html:453 +#: warehouse/templates/pages/help.html:455 msgid "" "Recovery codes are one time use. They are not a " "substitute for a authentication application or API tokens provide an alternative way (instead of username " @@ -4917,41 +4921,41 @@ msgid "" " " msgstr "" -#: warehouse/templates/pages/help.html:484 +#: warehouse/templates/pages/help.html:486 msgid "To make an API token:" msgstr "" -#: warehouse/templates/pages/help.html:487 +#: warehouse/templates/pages/help.html:489 msgid "Verify your email address" msgstr "" -#: warehouse/templates/pages/help.html:487 +#: warehouse/templates/pages/help.html:489 #, python-format msgid "(check your account settings)" msgstr "" -#: warehouse/templates/pages/help.html:488 +#: warehouse/templates/pages/help.html:490 #, python-format msgid "" "In your account settings, go to the API tokens " "section and select \"Add API token\"" msgstr "" -#: warehouse/templates/pages/help.html:491 +#: warehouse/templates/pages/help.html:493 msgid "To use an API token:" msgstr "" -#: warehouse/templates/pages/help.html:494 +#: warehouse/templates/pages/help.html:496 msgid "Set your username to __token__" msgstr "" -#: warehouse/templates/pages/help.html:495 +#: warehouse/templates/pages/help.html:497 msgid "" "Set your password to the token value, including the pypi- " "prefix" msgstr "" -#: warehouse/templates/pages/help.html:499 +#: warehouse/templates/pages/help.html:501 #, python-format msgid "" "Where you edit or add these values will depend on your individual use " @@ -4963,14 +4967,14 @@ msgid "" "rel=\"noopener\">.travis.yml if you are using Travis)." msgstr "" -#: warehouse/templates/pages/help.html:503 +#: warehouse/templates/pages/help.html:505 msgid "" "Advanced users may wish to inspect their token by decoding it with " "base64, and checking the output against the unique identifier displayed " "on PyPI." msgstr "" -#: warehouse/templates/pages/help.html:507 +#: warehouse/templates/pages/help.html:509 msgid "" "\n" "

    PyPI asks you to confirm your password before you want to " @@ -4984,15 +4988,15 @@ msgid "" " " msgstr "" -#: warehouse/templates/pages/help.html:519 +#: warehouse/templates/pages/help.html:521 msgid "Yes, including RSS feeds of new packages and new releases." msgstr "" -#: warehouse/templates/pages/help.html:519 +#: warehouse/templates/pages/help.html:521 msgid "See the API reference." msgstr "" -#: warehouse/templates/pages/help.html:522 +#: warehouse/templates/pages/help.html:524 #, python-format msgid "" "If you need to run your own mirror of PyPI, the GitHub apps." msgstr "" -#: warehouse/templates/pages/help.html:528 +#: warehouse/templates/pages/help.html:530 #, python-format msgid "" "You can analyze PyPI project/package metadata and via our public dataset on Google BigQuery." msgstr "" -#: warehouse/templates/pages/help.html:530 +#: warehouse/templates/pages/help.html:532 #, python-format msgid "" "other relevant factors." msgstr "" -#: warehouse/templates/pages/help.html:539 +#: warehouse/templates/pages/help.html:541 #, python-format msgid "" "For recent statistics on uptime and performance, see ." msgstr "" -#: warehouse/templates/pages/help.html:546 +#: warehouse/templates/pages/help.html:548 #, python-format msgid "" "PyPI does not support publishing private packages. If you need to publish" @@ -5051,7 +5055,7 @@ msgid "" "run your own deployment of the devpi project." msgstr "" -#: warehouse/templates/pages/help.html:549 +#: warehouse/templates/pages/help.html:551 msgid "" "Your publishing tool may return an error that your new project can't be " "created with your desired name, despite no evidence of a project or " @@ -5059,7 +5063,7 @@ msgid "" "reasons this may occur:" msgstr "" -#: warehouse/templates/pages/help.html:551 +#: warehouse/templates/pages/help.html:553 #, python-format msgid "" "The project name conflicts with a module from any major version from 2.5 to present." msgstr "" -#: warehouse/templates/pages/help.html:552 +#: warehouse/templates/pages/help.html:554 msgid "" "The project name is too similar to an existing project and may be " "confusable." msgstr "" -#: warehouse/templates/pages/help.html:553 +#: warehouse/templates/pages/help.html:555 #, python-format msgid "" "The project name has been explicitly prohibited by the PyPI " @@ -5082,18 +5086,18 @@ msgid "" "with a malicious package." msgstr "" -#: warehouse/templates/pages/help.html:554 +#: warehouse/templates/pages/help.html:556 msgid "" "The project name has been registered by another user, but no releases " "have been created." msgstr "" -#: warehouse/templates/pages/help.html:554 +#: warehouse/templates/pages/help.html:556 #, python-format msgid "See %(anchor_text)s" msgstr "" -#: warehouse/templates/pages/help.html:558 +#: warehouse/templates/pages/help.html:560 #, python-format msgid "" "Follow the PEP 541." msgstr "" -#: warehouse/templates/pages/help.html:562 +#: warehouse/templates/pages/help.html:564 msgid "" "Can upload releases for a package. Cannot add collaborators. Cannot " "delete files, releases, or the project." msgstr "" -#: warehouse/templates/pages/help.html:563 +#: warehouse/templates/pages/help.html:565 msgid "Owner:" msgstr "" -#: warehouse/templates/pages/help.html:563 +#: warehouse/templates/pages/help.html:565 msgid "" "Can upload releases. Can add other collaborators. Can delete files, " "releases, or the entire project." msgstr "" -#: warehouse/templates/pages/help.html:566 +#: warehouse/templates/pages/help.html:568 msgid "" "Only the current owners of a project have the ability to add new owners " "or maintainers. If you need to request ownership, you should contact the " @@ -5126,12 +5130,12 @@ msgid "" "project page." msgstr "" -#: warehouse/templates/pages/help.html:567 +#: warehouse/templates/pages/help.html:569 #, python-format msgid "If the owner is unresponsive, see %(anchor_text)s" msgstr "" -#: warehouse/templates/pages/help.html:570 +#: warehouse/templates/pages/help.html:572 #, python-format msgid "" "By default, an upload's description will render with file an issue and tell us:" msgstr "" -#: warehouse/templates/pages/help.html:585 -#: warehouse/templates/pages/help.html:606 +#: warehouse/templates/pages/help.html:587 +#: warehouse/templates/pages/help.html:608 msgid "A link to your project on PyPI (or Test PyPI)" msgstr "" -#: warehouse/templates/pages/help.html:586 +#: warehouse/templates/pages/help.html:588 msgid "The size of your release, in megabytes" msgstr "" -#: warehouse/templates/pages/help.html:587 +#: warehouse/templates/pages/help.html:589 msgid "Which index/indexes you need the increase for (PyPI, Test PyPI, or both)" msgstr "" -#: warehouse/templates/pages/help.html:588 -#: warehouse/templates/pages/help.html:608 +#: warehouse/templates/pages/help.html:590 +#: warehouse/templates/pages/help.html:610 msgid "" "A brief description of your project, including the reason for the " "additional size." msgstr "" -#: warehouse/templates/pages/help.html:594 +#: warehouse/templates/pages/help.html:596 msgid "" "If you can't upload your project's release to PyPI because you're hitting" " the project size limit, first remove any unnecessary releases or " "individual files to lower your overall project size." msgstr "" -#: warehouse/templates/pages/help.html:601 +#: warehouse/templates/pages/help.html:603 #, python-format msgid "" "If that is not possible, we can sometimes increase your limit. File an issue and tell us:" msgstr "" -#: warehouse/templates/pages/help.html:607 +#: warehouse/templates/pages/help.html:609 msgid "The total size of your project, in gigabytes" msgstr "" -#: warehouse/templates/pages/help.html:614 +#: warehouse/templates/pages/help.html:616 #, python-format msgid "" "PyPI receives reports on vulnerabilities in the packages hosted on it " @@ -5213,7 +5217,7 @@ msgid "" "Advisory Database." msgstr "" -#: warehouse/templates/pages/help.html:619 +#: warehouse/templates/pages/help.html:621 #, python-format msgid "" "If you believe vulnerability data for your project is invalid or " @@ -5221,7 +5225,7 @@ msgid "" "target=\"_blank\" rel=\"noopener\">file an issue with details." msgstr "" -#: warehouse/templates/pages/help.html:631 +#: warehouse/templates/pages/help.html:633 #, python-format msgid "" "PyPI will reject uploads if the package description fails to render. You " @@ -5229,41 +5233,41 @@ msgid "" "command to locally check a description for validity." msgstr "" -#: warehouse/templates/pages/help.html:637 +#: warehouse/templates/pages/help.html:639 msgid "" "If you've forgotten your PyPI password but you remember your email " "address or username, follow these steps to reset your password:" msgstr "" -#: warehouse/templates/pages/help.html:639 +#: warehouse/templates/pages/help.html:641 #, python-format msgid "Go to reset your password." msgstr "" -#: warehouse/templates/pages/help.html:640 +#: warehouse/templates/pages/help.html:642 msgid "Enter the email address or username you used for PyPI and submit the form." msgstr "" -#: warehouse/templates/pages/help.html:641 +#: warehouse/templates/pages/help.html:643 msgid "You'll receive an email with a password reset link." msgstr "" -#: warehouse/templates/pages/help.html:646 +#: warehouse/templates/pages/help.html:648 msgid "If you've lost access to your PyPI account due to:" msgstr "" -#: warehouse/templates/pages/help.html:648 +#: warehouse/templates/pages/help.html:650 msgid "Lost access to the email address associated with your account" msgstr "" -#: warehouse/templates/pages/help.html:649 +#: warehouse/templates/pages/help.html:651 msgid "" "Lost two factor authentication application, device, and recovery " "codes" msgstr "" -#: warehouse/templates/pages/help.html:652 +#: warehouse/templates/pages/help.html:654 #, python-format msgid "" "You can proceed to API Token for uploads:" msgstr "" -#: warehouse/templates/pages/help.html:666 +#: warehouse/templates/pages/help.html:668 msgid "Ensure that your API Token is valid and has not been revoked." msgstr "" -#: warehouse/templates/pages/help.html:667 +#: warehouse/templates/pages/help.html:669 msgid "" "Ensure that your API Token is properly " "formatted and does not contain any trailing characters such as " "newlines." msgstr "" -#: warehouse/templates/pages/help.html:669 +#: warehouse/templates/pages/help.html:671 msgid "" "In both cases, remember that PyPI and TestPyPI each require you to create" " an account, so your credentials may be different." msgstr "" -#: warehouse/templates/pages/help.html:671 +#: warehouse/templates/pages/help.html:673 msgid "" "\n" " If you're using Windows and trying to paste your password or " @@ -5317,7 +5321,7 @@ msgid "" " " msgstr "" -#: warehouse/templates/pages/help.html:675 +#: warehouse/templates/pages/help.html:677 #, python-format msgid "" "This is a Learn why on the PSF blog." msgstr "" -#: warehouse/templates/pages/help.html:689 +#: warehouse/templates/pages/help.html:691 #, python-format msgid "" "If you are having trouble with %(command)s and get a " @@ -5346,7 +5350,7 @@ msgid "" "information:" msgstr "" -#: warehouse/templates/pages/help.html:691 +#: warehouse/templates/pages/help.html:693 msgid "" "If you see an error like There was a problem confirming the ssl " "certificate or tlsv1 alert protocol version or " @@ -5354,7 +5358,7 @@ msgid "" "PyPI with a newer TLS support library." msgstr "" -#: warehouse/templates/pages/help.html:692 +#: warehouse/templates/pages/help.html:694 msgid "" "The specific steps you need to take will depend on your operating system " "version, where your installation of Python originated (python.org, your " @@ -5362,7 +5366,7 @@ msgid "" " Python, setuptools, and pip." msgstr "" -#: warehouse/templates/pages/help.html:694 +#: warehouse/templates/pages/help.html:696 #, python-format msgid "" "For help, go to %(command)s." msgstr "" -#: warehouse/templates/pages/help.html:705 +#: warehouse/templates/pages/help.html:707 #, python-format msgid "" "We take , so we can try to fix the problem, for you and others." msgstr "" -#: warehouse/templates/pages/help.html:718 +#: warehouse/templates/pages/help.html:720 #, python-format msgid "" "In a previous version of PyPI, it used to be possible for maintainers to " @@ -5401,7 +5405,7 @@ msgid "" "rel=\"noopener\">use twine to upload your project to PyPI." msgstr "" -#: warehouse/templates/pages/help.html:727 +#: warehouse/templates/pages/help.html:729 msgid "" "Spammers return to PyPI with some regularity hoping to place their Search" " Engine Optimized phishing, scam, and click-farming content on the site. " @@ -5410,7 +5414,7 @@ msgid "" "prime target." msgstr "" -#: warehouse/templates/pages/help.html:729 +#: warehouse/templates/pages/help.html:731 #, python-format msgid "" "When the PyPI administrators are overwhelmed by spam or " @@ -5421,29 +5425,29 @@ msgid "" "have updated it with reasoning for the intervention." msgstr "" -#: warehouse/templates/pages/help.html:738 +#: warehouse/templates/pages/help.html:740 msgid "PyPI will return these errors for one of these reasons:" msgstr "" -#: warehouse/templates/pages/help.html:740 +#: warehouse/templates/pages/help.html:742 msgid "Filename has been used and file exists" msgstr "" -#: warehouse/templates/pages/help.html:741 +#: warehouse/templates/pages/help.html:743 msgid "Filename has been used but file no longer exists" msgstr "" -#: warehouse/templates/pages/help.html:742 +#: warehouse/templates/pages/help.html:744 msgid "A file with the exact same content exists" msgstr "" -#: warehouse/templates/pages/help.html:744 +#: warehouse/templates/pages/help.html:746 msgid "" "PyPI does not allow for a filename to be reused, even once a project has " "been deleted and recreated." msgstr "" -#: warehouse/templates/pages/help.html:746 +#: warehouse/templates/pages/help.html:748 #, python-format msgid "" "To avoid this situation, pypi.org." msgstr "" -#: warehouse/templates/pages/help.html:753 +#: warehouse/templates/pages/help.html:754 +msgid "" +"Package files uploaded to PyPI need to have names in a standard format. " +"Your packaging tools should create files with suitable names by default. " +"The filenames consist of a number of parts, separated by hyphens " +"(-):" +msgstr "" + +#: warehouse/templates/pages/help.html:756 +msgid "" +"For source distributions: normalized project name and version, e.g. " +"importlib_metadata-4.10.1.tar.gz" +msgstr "" + +#: warehouse/templates/pages/help.html:757 +msgid "" +"For wheels: normalized project name, version, build tag (optional), " +"Python tag, ABI tag, and platform tag, e.g. " +"importlib_metadata-4.10.1-py3-none-any.whl" +msgstr "" + +#: warehouse/templates/pages/help.html:760 +#, python-format +msgid "" +"A normalized project name must be fully lower-cased, with any runs of " +"_-. characters replaced with a single underscore " +"(_). The version number should be normalized as described in" +" PEP 440. All parts are expected to consist " +"only of ASCII characters." +msgstr "" + +#: warehouse/templates/pages/help.html:764 +msgid "" +"These rules have become stricter over time, so you may see existing " +"packages with names which would no longer be allowed for new uploads." +msgstr "" + +#: warehouse/templates/pages/help.html:768 #, python-format msgid "" "If you would like to request a new trove classifier file a pull request " @@ -5461,7 +5502,7 @@ msgid "" " to include a brief justification of why it is important." msgstr "" -#: warehouse/templates/pages/help.html:761 +#: warehouse/templates/pages/help.html:776 #, python-format msgid "" "If you're experiencing an issue with PyPI itself, we welcome " @@ -5472,14 +5513,14 @@ msgid "" " first check that a similar issue does not already exist." msgstr "" -#: warehouse/templates/pages/help.html:768 +#: warehouse/templates/pages/help.html:783 msgid "" "If you are having an issue is with a specific package installed from " "PyPI, you should reach out to the maintainers of that project directly " "instead." msgstr "" -#: warehouse/templates/pages/help.html:777 +#: warehouse/templates/pages/help.html:792 #, python-format msgid "" "PyPI is powered by the Warehouse project; ." msgstr "" -#: warehouse/templates/pages/help.html:804 +#: warehouse/templates/pages/help.html:819 msgid "" "As of April 16, 2018, PyPI.org is at \"production\" status, meaning that " "it has moved out of beta and replaced the old site (pypi.python.org). It " "is now robust, tested, and ready for expected browser and API traffic." msgstr "" -#: warehouse/templates/pages/help.html:806 +#: warehouse/templates/pages/help.html:821 #, python-format msgid "" "PyPI is heavily cached and distributed via private index." msgstr "" -#: warehouse/templates/pages/help.html:820 +#: warehouse/templates/pages/help.html:835 #, python-format msgid "" "We have a huge amount of work to do to continue to maintain and improve " @@ -5552,22 +5593,22 @@ msgid "" "target=\"_blank\" rel=\"noopener\">the Warehouse project)." msgstr "" -#: warehouse/templates/pages/help.html:825 +#: warehouse/templates/pages/help.html:840 msgid "Financial:" msgstr "" -#: warehouse/templates/pages/help.html:825 +#: warehouse/templates/pages/help.html:840 #, python-format msgid "" "We would deeply appreciate your donations to fund " "development and maintenance." msgstr "" -#: warehouse/templates/pages/help.html:826 +#: warehouse/templates/pages/help.html:841 msgid "Development:" msgstr "" -#: warehouse/templates/pages/help.html:826 +#: warehouse/templates/pages/help.html:841 msgid "" "Warehouse is open source, and we would love to see some new faces working" " on the project. You do not need to be an experienced " @@ -5575,7 +5616,7 @@ msgid "" " you make your first open source pull request!" msgstr "" -#: warehouse/templates/pages/help.html:828 +#: warehouse/templates/pages/help.html:843 #, python-format msgid "" "If you have skills in Python, ElasticSearch, HTML, SCSS, JavaScript, or " @@ -5589,7 +5630,7 @@ msgid "" "here." msgstr "" -#: warehouse/templates/pages/help.html:836 +#: warehouse/templates/pages/help.html:851 #, python-format msgid "" "Issues are grouped into Python packaging forum on Discourse." msgstr "" -#: warehouse/templates/pages/help.html:854 +#: warehouse/templates/pages/help.html:869 #, python-format msgid "" "Changes to PyPI are generally announced on both the {% trans %}Troubleshooting{% endtrans %}

  • {{ accessibility() }}
  • {{ admin_intervention() }}
  • {{ file_name_reuse() }}
  • +
  • {{ invalid_file_name() }}
  • {{ new_classifier() }}
  • {{ feedback() }}
  • @@ -748,6 +750,19 @@

    {{ file_name_reuse() }}

    {% endtrans %}

    +

    {{ invalid_file_name() }}

    +

    {% trans %}Package files uploaded to PyPI need to have names in a standard format. Your packaging tools should create files with suitable names by default. The filenames consist of a number of parts, separated by hyphens (-):{% endtrans %}

    + +

    + {% trans trimmed pep440="https://peps.python.org/pep-0440/" %} + A normalized project name must be fully lower-cased, with any runs of _-. characters replaced with a single underscore (_). The version number should be normalized as described in PEP 440. All parts are expected to consist only of ASCII characters. + {% endtrans %} +

    +

    {% trans %}These rules have become stricter over time, so you may see existing packages with names which would no longer be allowed for new uploads.{% endtrans %}

    +

    {{ new_classifier() }}

    {% trans trimmed href='https://github.com/pypa/trove-classifiers/', title=gettext('External link') %}