mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-16 00:13:25 -05:00
fix: never follow a link at a partial download
This commit is contained in:
1 parent
a3941e843c
commit
12f362dfb9
4 files changed
+41
-4
No files matched your search
+1
-1
@@ -8,7 +8,7 @@ param(
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$defaultInstallUrl = "https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
|
||||
$defaultInstallSha256 = "17ccf172256b945fe369952d10d091de79168c3be50208841c27fdc74df9fad6"
|
||||
$defaultInstallSha256 = "95e9cf9c274d3ccf5a305ee4a32e74c5c173177b7cf012069835a096e7ff2b35"
|
||||
$maximumInstallerBytes = 2MB
|
||||
$installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null }
|
||||
$temporary = $null
|
||||
|
||||
+22
-2
@@ -23,6 +23,7 @@ import os
|
||||
import platform
|
||||
import re
|
||||
import shutil
|
||||
import stat
|
||||
import sys
|
||||
import tempfile
|
||||
import threading
|
||||
@@ -1579,7 +1580,7 @@ def _download_one(
|
||||
offset = 0
|
||||
if resumable:
|
||||
tmp_path = dest.parent / f".{dest.name}.part"
|
||||
offset = tmp_path.stat().st_size if tmp_path.exists() else 0
|
||||
offset = _partial_size(tmp_path)
|
||||
if offset >= expected_size:
|
||||
tmp_path.unlink()
|
||||
offset = 0
|
||||
@@ -1595,7 +1596,7 @@ def _download_one(
|
||||
f"Content-Length {raw_length} != expected {expected_size - offset}"
|
||||
)
|
||||
if resumable:
|
||||
out = open(tmp_path, "ab" if offset else "wb")
|
||||
out = _open_partial(tmp_path, append=bool(offset))
|
||||
else:
|
||||
out = tempfile.NamedTemporaryFile(
|
||||
mode="wb", dir=dest.parent, prefix=f".{dest.name}.",
|
||||
@@ -1683,6 +1684,25 @@ def _download_one(
|
||||
return f["dest"], False, reason
|
||||
|
||||
|
||||
def _partial_size(path: Path) -> int:
|
||||
"""Bytes already held by a partial copy; anything but a plain file is dropped."""
|
||||
try:
|
||||
info = path.lstat()
|
||||
except FileNotFoundError:
|
||||
return 0
|
||||
if stat.S_ISREG(info.st_mode):
|
||||
return info.st_size
|
||||
path.unlink()
|
||||
return 0
|
||||
|
||||
|
||||
def _open_partial(path: Path, append: bool):
|
||||
"""Open the partial copy without following a link planted at its name."""
|
||||
flags = os.O_WRONLY | os.O_CREAT | getattr(os, "O_NOFOLLOW", 0) | getattr(os, "O_BINARY", 0)
|
||||
flags |= os.O_APPEND if append else os.O_TRUNC
|
||||
return os.fdopen(os.open(path, flags, 0o644), "ab" if append else "wb")
|
||||
|
||||
|
||||
class _Corrupt(Exception):
|
||||
"""The bytes arrived but are not the file the manifest names."""
|
||||
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ esac
|
||||
TEMP_INSTALLER=""
|
||||
TEMP_DIRECTORY=""
|
||||
DEFAULT_INSTALL_URL="https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
|
||||
DEFAULT_INSTALL_SHA256="17ccf172256b945fe369952d10d091de79168c3be50208841c27fdc74df9fad6"
|
||||
DEFAULT_INSTALL_SHA256="95e9cf9c274d3ccf5a305ee4a32e74c5c173177b7cf012069835a096e7ff2b35"
|
||||
MAX_INSTALLER_BYTES=2097152
|
||||
|
||||
cleanup() {
|
||||
|
||||
@@ -102,6 +102,23 @@ class LargeFilesResume(unittest.TestCase):
|
||||
self.assertTrue(ok)
|
||||
self.assertEqual(_RangeHandler.ranges, [""])
|
||||
|
||||
@unittest.skipUnless(hasattr(os, "symlink"), "needs symlinks")
|
||||
def test_a_link_at_the_partial_name_is_never_written_through(self):
|
||||
with _Server(), tempfile.TemporaryDirectory() as tmp, \
|
||||
mock.patch.object(install, "RESUME_FROM_SIZE", 1024):
|
||||
victim = Path(tmp) / "victim.txt"
|
||||
victim.write_bytes(b"keep me")
|
||||
part = Path(tmp) / "bios" / ".big.bin.part"
|
||||
part.parent.mkdir()
|
||||
part.symlink_to(victim)
|
||||
dest, ok, _ = install._download_one(dict(ENTRY), Path(tmp) / "bios")
|
||||
self.assertTrue(ok)
|
||||
self.assertEqual(victim.read_bytes(), b"keep me")
|
||||
final = Path(tmp) / "bios" / "big.bin"
|
||||
self.assertFalse(final.is_symlink())
|
||||
self.assertEqual(final.read_bytes(), PAYLOAD)
|
||||
self.assertEqual(_RangeHandler.ranges, [""])
|
||||
|
||||
|
||||
class RetriesWait(unittest.TestCase):
|
||||
def test_a_server_error_is_retried_after_the_delay_it_asks(self):
|
||||
|
||||
Reference in new issue
Block a user