Skip to content

Commit 43b7c61

Browse files
committed
Ensure all accepted hash types are checked
1 parent f457d26 commit 43b7c61

File tree

4 files changed

+114
-18
lines changed

4 files changed

+114
-18
lines changed

poetry.lock

+7-7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

poetry/installation/executor.py

+23-9
Original file line numberDiff line numberDiff line change
@@ -608,16 +608,30 @@ def _download_link(self, operation, link):
608608
archive = self._chef.prepare(archive)
609609

610610
if package.files:
611-
archive_hash = (
612-
"sha256:"
613-
+ FileDependency(
614-
package.name,
615-
Path(archive.path) if isinstance(archive, Link) else archive,
616-
).hash()
617-
)
618-
if archive_hash not in {f["hash"] for f in package.files}:
611+
hashes = {f["hash"] for f in package.files}
612+
hash_types = {h.split(":")[0] for h in hashes}
613+
archive_hashes = set()
614+
for hash_type in hash_types:
615+
archive_hashes.add(
616+
"{}:{}".format(
617+
hash_type,
618+
FileDependency(
619+
package.name,
620+
Path(archive.path)
621+
if isinstance(archive, Link)
622+
else archive,
623+
).hash(hash_type),
624+
)
625+
)
626+
627+
if archive_hashes.isdisjoint(hashes):
619628
raise RuntimeError(
620-
"Invalid hash for {} using archive {}".format(package, archive.name)
629+
"Invalid hashes ({}) for {} using archive {}. Expected one of {}.".format(
630+
", ".join(sorted(archive_hashes)),
631+
package,
632+
archive.name,
633+
", ".join(sorted(hashes)),
634+
)
621635
)
622636

623637
return archive

pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ classifiers = [
2424
[tool.poetry.dependencies]
2525
python = "~2.7 || ^3.5"
2626

27-
poetry-core = "~1.0.5"
27+
poetry-core = "~1.0.6"
2828
cleo = "^0.8.1"
2929
clikit = "^0.6.2"
3030
crashtest = { version = "^0.3.0", python = "^3.6" }

tests/installation/test_executor.py

+83-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
from poetry.config.config import Config
1313
from poetry.core.packages.package import Package
14+
from poetry.core.packages.utils.link import Link
15+
from poetry.installation.chef import Chef
1416
from poetry.installation.executor import Executor
1517
from poetry.installation.operations import Install
1618
from poetry.installation.operations import Uninstall
@@ -52,7 +54,9 @@ def callback(request, uri, headers):
5254
return [200, headers, f.read()]
5355

5456
http.register_uri(
55-
http.GET, re.compile("^https://files.pythonhosted.org/.*$"), body=callback,
57+
http.GET,
58+
re.compile("^https://files.pythonhosted.org/.*$"),
59+
body=callback,
5660
)
5761

5862

@@ -251,3 +255,81 @@ def test_executor_should_delete_incomplete_downloads(
251255
executor._download(Install(Package("tomlkit", "0.5.3")))
252256

253257
assert not destination_fixture.exists()
258+
259+
260+
def test_executor_should_check_every_possible_hash_types(
261+
config, io, pool, mocker, fixture_dir, tmp_dir
262+
):
263+
mocker.patch.object(
264+
Chef,
265+
"get_cached_archive_for_link",
266+
side_effect=lambda link: link,
267+
)
268+
mocker.patch.object(
269+
Executor,
270+
"_download_archive",
271+
return_value=fixture_dir("distributions").joinpath(
272+
"demo-0.1.0-py2.py3-none-any.whl"
273+
),
274+
)
275+
276+
env = MockEnv(path=Path(tmp_dir))
277+
executor = Executor(env, pool, config, io)
278+
279+
package = Package("demo", "0.1.0")
280+
package.files = [
281+
{
282+
"file": "demo-0.1.0-py2.py3-none-any.whl",
283+
"hash": "md5:15507846fd4299596661d0197bfb4f90",
284+
}
285+
]
286+
287+
archive = executor._download_link(
288+
Install(package), Link("https://example.com/demo-0.1.0-py2.py3-none-any.whl")
289+
)
290+
291+
assert archive == fixture_dir("distributions").joinpath(
292+
"demo-0.1.0-py2.py3-none-any.whl"
293+
)
294+
295+
296+
def test_executor_should_check_every_possible_hash_types_before_failing(
297+
config, io, pool, mocker, fixture_dir, tmp_dir
298+
):
299+
mocker.patch.object(
300+
Chef,
301+
"get_cached_archive_for_link",
302+
side_effect=lambda link: link,
303+
)
304+
mocker.patch.object(
305+
Executor,
306+
"_download_archive",
307+
return_value=fixture_dir("distributions").joinpath(
308+
"demo-0.1.0-py2.py3-none-any.whl"
309+
),
310+
)
311+
312+
env = MockEnv(path=Path(tmp_dir))
313+
executor = Executor(env, pool, config, io)
314+
315+
package = Package("demo", "0.1.0")
316+
package.files = [
317+
{"file": "demo-0.1.0-py2.py3-none-any.whl", "hash": "md5:123456"},
318+
{"file": "demo-0.1.0-py2.py3-none-any.whl", "hash": "sha256:123456"},
319+
]
320+
321+
expected_message = (
322+
"Invalid hashes "
323+
"("
324+
"md5:15507846fd4299596661d0197bfb4f90, "
325+
"sha256:70e704135718fffbcbf61ed1fc45933cfd86951a744b681000eaaa75da31f17a"
326+
") "
327+
"for demo (0.1.0) using archive demo-0.1.0-py2.py3-none-any.whl. "
328+
"Expected one of md5:123456, sha256:123456."
329+
)
330+
331+
with pytest.raises(RuntimeError, match=re.escape(expected_message)):
332+
executor._download_link(
333+
Install(package),
334+
Link("https://example.com/demo-0.1.0-py2.py3-none-any.whl"),
335+
)

0 commit comments

Comments
 (0)