Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from six import StringIO

from trashcli.lib.my_input import HardCodedInput
from trashcli.restore.file_system import FakeReadCwd, FileReader
from trashcli.restore.restore_cmd import RestoreCmd
from trashcli.restore.trashed_files import TrashedFiles

HOME = '/home/user'
INFO = '/home/user/.local/share/Trash/info/report.trashinfo'


class FakeReader(FileReader):
def __init__(self, contents):
self.contents = contents

def contents_of(self, path):
return self.contents


class FakeSearcher:
# the home trash lives on the /home volume, not on "/"
def __init__(self, volume):
self.volume = volume

def all_file_in_info_dir(self, trash_dir_from_cli):
from collections import namedtuple
InfoFile = namedtuple('InfoFile', 'path type volume')
yield InfoFile(INFO, 'trashinfo', self.volume)


class NullLogger:
def warning(self, message):
pass


class TestRestoreHomeTrashOnSeparateVolume:
def run_restore_from(self, path, trashinfo, volume):
stdout = StringIO()
trashed_files = TrashedFiles(NullLogger(), FakeReader(trashinfo),
FakeSearcher(volume))
cmd = RestoreCmd.make(
stdout=stdout, stderr=StringIO(), exit=lambda _: None,
input=HardCodedInput(''), version="0.0.0",
trashed_files=trashed_files, read_fs=None, write_fs=None,
read_cwd=FakeReadCwd(path))
cmd.run(['trash-restore', path])
return stdout.getvalue()

def test_an_absolute_home_entry_on_a_separate_home_volume_is_listed(self):
trashinfo = ('[Trash Info]\n'
'Path=/home/user/report.txt\n'
'DeletionDate=2026-01-01T00:00:00\n')

output = self.run_restore_from(HOME, trashinfo, volume='/home')

assert '/home/user/report.txt' in output
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,12 @@ def test_a_relative_path_inside_the_volume_is_restorable(self):

self.assertEqual(['good.trashinfo'], self._restorable())

def test_an_absolute_path_is_not_restorable(self):
self._add('evil', '/etc/passwd')
def test_an_absolute_path_is_restorable(self):
# the home trash stores absolute paths by design, and the volume
# that contains it is not necessarily "/" (e.g. a /home partition)
self._add('home-entry', '/home/user/document.txt')

self.assertEqual([], self._restorable())
self.assertEqual(['home-entry.trashinfo'], self._restorable())

def test_a_path_escaping_the_volume_is_not_restorable(self):
self._add('evil', '../../../etc/shadow')
Expand Down
13 changes: 6 additions & 7 deletions trashcli/parse_trashinfo/parse_original_location.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,11 @@

def parse_original_location(contents, volume_path):
path = parse_path(contents)
if os.path.isabs(path):
# the home trash legitimately stores absolute paths, on any volume
return os.path.normpath(path)
resolved = os.path.normpath(os.path.join(volume_path, path))
if volume_path != os.path.sep:
# A volume trash must record a location that is relative to and inside that volume.
if os.path.isabs(path):
raise ParseError("Path= must be relative for volume trashes")
rel = os.path.relpath(resolved, os.path.normpath(volume_path))
if rel == os.pardir or rel.startswith(os.pardir + os.sep):
raise ParseError("Path= escapes the volume root")
rel = os.path.relpath(resolved, os.path.normpath(volume_path))
if rel == os.pardir or rel.startswith(os.pardir + os.sep):
raise ParseError("Path= escapes the volume root")
return resolved