From 19b30bffcdf8259545d0b01596d61cba7da14d85 Mon Sep 17 00:00:00 2001 From: Joachim Metz Date: Sat, 27 Jun 2020 08:39:51 +0200 Subject: [PATCH] Deprecated AtMaximumDepth and Matches methods of find specification #397 (#457) --- config/dpkg/changelog | 4 +- dfvfs/__init__.py | 2 +- dfvfs/helpers/file_system_searcher.py | 293 ++++++++++++++++++-------- dfvfs/vfs/fake_file_system.py | 3 +- tests/helpers/file_system_searcher.py | 174 +++++++++++++-- 5 files changed, 362 insertions(+), 114 deletions(-) diff --git a/config/dpkg/changelog b/config/dpkg/changelog index 6b8f89fd..146dc353 100644 --- a/config/dpkg/changelog +++ b/config/dpkg/changelog @@ -1,5 +1,5 @@ -dfvfs (20200619-1) unstable; urgency=low +dfvfs (20200625-1) unstable; urgency=low * Auto-generated - -- Log2Timeline maintainers Fri, 19 Jun 2020 21:34:04 +0200 \ No newline at end of file + -- Log2Timeline maintainers Thu, 25 Jun 2020 10:39:38 +0200 \ No newline at end of file diff --git a/dfvfs/__init__.py b/dfvfs/__init__.py index 4d11c004..8cc5c031 100644 --- a/dfvfs/__init__.py +++ b/dfvfs/__init__.py @@ -6,4 +6,4 @@ storage media types and file formats. """ -__version__ = '20200619' +__version__ = '20200625' diff --git a/dfvfs/helpers/file_system_searcher.py b/dfvfs/helpers/file_system_searcher.py index 2acd1310..30bad387 100644 --- a/dfvfs/helpers/file_system_searcher.py +++ b/dfvfs/helpers/file_system_searcher.py @@ -9,6 +9,7 @@ from dfvfs.lib import definitions from dfvfs.lib import errors from dfvfs.lib import glob2regex +from dfvfs.lib import decorators from dfvfs.path import factory as path_spec_factory @@ -248,63 +249,58 @@ def _CheckIsSocket(self, file_entry): return False return file_entry.IsSocket() - def _CheckLocation(self, file_entry, search_depth): - """Checks the location find specification. + def _CompareWithLocationSegment(self, location_segment, segment_index): + """Compares a location segment against a find specification. Args: - file_entry (FileEntry): file entry. - search_depth (int): number of location path segments to compare. + location_segment (str): location segment. + segment_index (int): index of the location segment to compare against, + where 0 represents the root segment. Returns: - bool: True if the file entry matches the find specification, False if not. + bool: True if the location segment of the file entry matches that of the + find specification, False if not or if the find specification has no + location defined. """ - if self._location_segments is None: - return False - - if search_depth < 0 or search_depth > self._number_of_location_segments: + if (self._location_segments is None or segment_index < 0 or + segment_index > self._number_of_location_segments): return False # Note that the root has no entry in the location segments and # no name to match. - if search_depth == 0: - segment_name = '' - else: - segment_name = self._location_segments[search_depth - 1] - - if self._is_regex: - if isinstance(segment_name, str): - # Allow '\n' to be matched by '.' and make '\w', '\W', '\b', '\B', - # '\d', '\D', '\s' and '\S' Unicode safe. - flags = re.DOTALL | re.UNICODE - if not self._is_case_sensitive: - flags |= re.IGNORECASE - - try: - segment_name = r'^{0:s}$'.format(segment_name) - segment_name = re.compile(segment_name, flags=flags) - except sre_constants.error: - # TODO: set self._location_segments[search_depth - 1] to None ? - return False - - self._location_segments[search_depth - 1] = segment_name - - elif not self._is_case_sensitive: - segment_name = segment_name.lower() - self._location_segments[search_depth - 1] = segment_name - - if search_depth > 0: - if self._is_regex: - if not segment_name.match(file_entry.name): # pylint: disable=no-member + if segment_index == 0: + return True + + segment_name = self._location_segments[segment_index - 1] + + if self._is_regex: + if isinstance(segment_name, str): + # Allow '\n' to be matched by '.' and make '\w', '\W', '\b', '\B', + # '\d', '\D', '\s' and '\S' Unicode safe. + flags = re.DOTALL | re.UNICODE + if not self._is_case_sensitive: + flags |= re.IGNORECASE + + try: + segment_name = r'^{0:s}$'.format(segment_name) + segment_name = re.compile(segment_name, flags=flags) + except sre_constants.error: + # TODO: set self._location_segments[segment_index - 1] to None ? return False - elif self._is_case_sensitive: - if segment_name != file_entry.name: - return False + self._location_segments[segment_index - 1] = segment_name - elif segment_name != file_entry.name.lower(): - return False + elif not self._is_case_sensitive: + segment_name = segment_name.lower() + self._location_segments[segment_index - 1] = segment_name - return True + if self._is_regex: + return bool(segment_name.match(location_segment)) # pylint: disable=no-member + + if self._is_case_sensitive: + return bool(segment_name == location_segment) + + return bool(segment_name == location_segment.lower()) def _ConvertLocationGlob2Regex(self, location_glob): """Converts a location glob into a regular expression. @@ -335,23 +331,144 @@ def _SplitPath(self, path, path_separator): # Split the path with the path separator and remove empty path segments. return list(filter(None, path.split(path_separator))) - def AtMaximumDepth(self, search_depth): + def AtLastLocationSegment(self, segment_index): + """Determines if the a location segment is the last one or greater. + + Args: + segment_index (int): index of the location path segment. + + Returns: + bool: True if at maximum depth, False if not. + """ + return bool(self._location_segments is not None and + segment_index >= self._number_of_location_segments) + + @decorators.deprecated + def AtMaximumDepth(self, segment_index): """Determines if the find specification is at maximum depth. + This method is deprecated use AtLastLocationSegment instead. + Args: - search_depth (int): number of location path segments to compare. + segment_index (int): index of the location path segment. Returns: bool: True if at maximum depth, False if not. """ - if self._location_segments is not None: - if search_depth >= self._number_of_location_segments: - return True + return self.AtLastLocationSegment(segment_index) + + def CompareLocation(self, file_entry, mount_point=None): + """Compares a file entry location against the find specification. + + Args: + file_entry (FileEntry): file entry. + mount_point (Optional[PathSpec]): mount point path specification that + refers to the base location of the file system. The mount point + is ignored if it is not an OS path specification. + + Returns: + bool: True if the location of the file entry matches that of the find + specification, False if not or if the find specification has no + location defined. + + Raises: + ValueError: if mount point is set and it does not match the type + indicator of the path specification of the file entry or file entry + location falls outside the mount point. + """ + location = getattr(file_entry.path_spec, 'location', None) + if self._location_segments is None or location is None: + return False + + if (mount_point and + mount_point.type_indicator == definitions.TYPE_INDICATOR_OS): + if file_entry.path_spec.type_indicator != definitions.TYPE_INDICATOR_OS: + raise ValueError( + 'File entry path specification and mount point type indicators ' + 'do not match.') + + if not location.startswith(mount_point.location): + raise ValueError( + 'File entry path specification location not inside mount point.') + + location = location[len(mount_point.location):] + + file_system = file_entry.GetFileSystem() + location_segments = file_system.SplitPath(location) + + for segment_index in range(self._number_of_location_segments): + try: + location_segment = location_segments[segment_index] + except IndexError: + return False + + if not self._CompareWithLocationSegment( + location_segment, segment_index + 1): + return False + + return True + + def CompareNameWithLocationSegment(self, file_entry, segment_index): + """Compares a file entry name against a find specification location segment. + + Args: + file_entry (FileEntry): file entry. + segment_index (int): index of the location segment to compare against, + where 0 represents the root segment. + + Returns: + bool: True if the location segment of the file entry matches that of the + find specification, False if not or if the find specification has no + location defined. + """ + return self._CompareWithLocationSegment(file_entry.name, segment_index) + + def CompareTraits(self, file_entry): + """Compares a file entry traits against the find specification. + + Args: + file_entry (FileEntry): file entry. + + Returns: + bool: True if the traits of the file entry, such as type, matches the + find specification, False otherwise. + """ + match = self._CheckFileEntryType(file_entry) + if match is not None and not match: + return False + + match = self._CheckIsAllocated(file_entry) + if match is not None and not match: + return False + + return True - return False + def HasLocation(self): + """Determines if the find specification has a location defined. + Returns: + bool: True if find specification has a location defined, False if not. + """ + return bool(self._location_segments) + + def IsLastLocationSegment(self, segment_index): + """Determines if the a location segment is the last one. + + Args: + segment_index (int): index of the location path segment. + + Returns: + bool: True if at maximum depth, False if not. + """ + return bool(self._location_segments is not None and + segment_index == self._number_of_location_segments) + + @decorators.deprecated def Matches(self, file_entry, search_depth=None): - """Determines if the file entry matches the find specification. + """Compares a file entry against the find specification. + + This method is deprecated use CompareNameWithLocationSegment, CompareTraits + or CompareLocation instead. Args: file_entry (FileEntry): file entry. @@ -362,8 +479,8 @@ def Matches(self, file_entry, search_depth=None): Returns: tuple: contains: - bool: True if the file entry matches the find specification, False - otherwise. + bool: True if the traits of the file entry, such as type, matches the + find specification, False otherwise. bool: True if the location matches, False if not or None if no location specified. """ @@ -373,22 +490,14 @@ def Matches(self, file_entry, search_depth=None): if search_depth is None: search_depth = self._number_of_location_segments - location_match = self._CheckLocation(file_entry, search_depth) - if not location_match: + location_match = self._CompareWithLocationSegment( + file_entry.name, search_depth) + is_last_location_segment = self.IsLastLocationSegment(search_depth) + if not location_match or not is_last_location_segment: return False, location_match - if search_depth != self._number_of_location_segments: - return False, location_match - - match = self._CheckFileEntryType(file_entry) - if match is not None and not match: - return False, location_match - - match = self._CheckIsAllocated(file_entry) - if match is not None and not match: - return False, location_match - - return True, location_match + match = self.CompareTraits(file_entry) + return match, location_match class FileSystemSearcher(object): @@ -419,39 +528,49 @@ def __init__(self, file_system, mount_point): self._file_system = file_system self._mount_point = mount_point - def _FindInFileEntry(self, file_entry, find_specs, search_depth): + def _FindInFileEntry(self, file_entry, find_specs, segment_index): """Searches for matching file entries within the file entry. Args: file_entry (FileEntry): file entry. find_specs (list[FindSpec]): find specifications. - search_depth (int): number of location path segments to compare. + segment_index (int): index of the location path segment to compare. Yields: PathSpec: path specification of a matching file entry. """ sub_find_specs = [] for find_spec in find_specs: - match, location_match = find_spec.Matches( - file_entry, search_depth=search_depth) - if match: - yield file_entry.path_spec - - # pylint: disable=singleton-comparison - if location_match != False and not find_spec.AtMaximumDepth(search_depth): + has_location = find_spec.HasLocation() + # Do a quick check to see if the current location segment matches. + location_match = find_spec.CompareNameWithLocationSegment( + file_entry, segment_index) + is_last_location_segment = find_spec.IsLastLocationSegment( + segment_index) + + if location_match and is_last_location_segment: + # Check if the full location matches. + location_match = find_spec.CompareLocation( + file_entry, mount_point=self._mount_point) + + if not has_location or (location_match and is_last_location_segment): + if find_spec.CompareTraits(file_entry): + yield file_entry.path_spec + + at_last_location_segment = find_spec.AtLastLocationSegment(segment_index) + if (not has_location or location_match) and not at_last_location_segment: sub_find_specs.append(find_spec) - if not sub_find_specs: - return - - search_depth += 1 - try: - for sub_file_entry in file_entry.sub_file_entries: - for matching_path_spec in self._FindInFileEntry( - sub_file_entry, sub_find_specs, search_depth): - yield matching_path_spec - except errors.AccessError: - pass + if sub_find_specs: + segment_index += 1 + try: + for sub_file_entry in file_entry.sub_file_entries: + for matching_path_spec in self._FindInFileEntry( + sub_file_entry, sub_find_specs, segment_index): + yield matching_path_spec + + except errors.AccessError: + pass def Find(self, find_specs=None): """Searches for matching file entries within the file system. diff --git a/dfvfs/vfs/fake_file_system.py b/dfvfs/vfs/fake_file_system.py index 6a6d03a4..9530ea2d 100644 --- a/dfvfs/vfs/fake_file_system.py +++ b/dfvfs/vfs/fake_file_system.py @@ -142,9 +142,10 @@ def GetFileEntryByPath(self, path): return None path_spec = fake_path_spec.FakePathSpec(location=path) + is_root = bool(path == self.LOCATION_ROOT) return fake_file_entry.FakeFileEntry( self._resolver_context, self, path_spec, - file_entry_type=file_entry_type) + file_entry_type=file_entry_type, is_root=is_root) def GetFileEntryByPathSpec(self, path_spec): """Retrieves a file entry for a path specification. diff --git a/tests/helpers/file_system_searcher.py b/tests/helpers/file_system_searcher.py index 18a3dd64..820c3e08 100644 --- a/tests/helpers/file_system_searcher.py +++ b/tests/helpers/file_system_searcher.py @@ -125,7 +125,7 @@ def testInitialize(self): find_spec = file_system_searcher.FindSpec(location_regex={}) def testCheckFileEntryType(self): - """Test the _CheckFileEntryType() function.""" + """Test the _CheckFileEntryType function.""" file_system = self._CreateTestFileSystem() find_spec = file_system_searcher.FindSpec( @@ -149,7 +149,7 @@ def testCheckFileEntryType(self): self.assertIsNone(result) def testCheckIsAllocated(self): - """Test the _CheckIsAllocated() function.""" + """Test the _CheckIsAllocated function.""" file_system = self._CreateTestFileSystem() find_spec = file_system_searcher.FindSpec( @@ -163,7 +163,7 @@ def testCheckIsAllocated(self): self.assertTrue(result) def testCheckIsDevice(self): - """Test the _CheckIsDevice() function.""" + """Test the _CheckIsDevice function.""" file_system = self._CreateTestFileSystem() find_spec = file_system_searcher.FindSpec( @@ -177,7 +177,7 @@ def testCheckIsDevice(self): self.assertFalse(result) def testCheckIsDirectory(self): - """Test the _CheckIsDirectory() function.""" + """Test the _CheckIsDirectory function.""" file_system = self._CreateTestFileSystem() find_spec = file_system_searcher.FindSpec( @@ -191,7 +191,7 @@ def testCheckIsDirectory(self): self.assertFalse(result) def testCheckIsFile(self): - """Test the _CheckIsFile() function.""" + """Test the _CheckIsFile function.""" file_system = self._CreateTestFileSystem() find_spec = file_system_searcher.FindSpec( @@ -205,7 +205,7 @@ def testCheckIsFile(self): self.assertTrue(result) def testCheckIsLink(self): - """Test the _CheckIsLink() function.""" + """Test the _CheckIsLink function.""" file_system = self._CreateTestFileSystem() find_spec = file_system_searcher.FindSpec( @@ -219,7 +219,7 @@ def testCheckIsLink(self): self.assertFalse(result) def testCheckIsPipe(self): - """Test the _CheckIsPipe() function.""" + """Test the _CheckIsPipe function.""" file_system = self._CreateTestFileSystem() find_spec = file_system_searcher.FindSpec( @@ -233,7 +233,7 @@ def testCheckIsPipe(self): self.assertFalse(result) def testCheckIsSocket(self): - """Test the _CheckIsSocket() function.""" + """Test the _CheckIsSocket function.""" file_system = self._CreateTestFileSystem() find_spec = file_system_searcher.FindSpec( @@ -246,32 +246,26 @@ def testCheckIsSocket(self): result = find_spec._CheckIsSocket(file_entry) self.assertFalse(result) - def testCheckLocation(self): - """Test the _CheckLocation() function.""" - file_system = self._CreateTestFileSystem() - - path_spec = fake_path_spec.FakePathSpec( - location='/usr/lib/python2.7/site-packages/dfvfs/__init__.py') - file_entry = file_system.GetFileEntryByPathSpec(path_spec) - + def testCompareWithLocationSegment(self): + """Test the _CompareWithLocationSegment function.""" find_spec = file_system_searcher.FindSpec( location='/usr/lib/python2.7/site-packages/dfvfs/__init__.py', location_separator='/') - result = find_spec._CheckLocation(file_entry, 6) + result = find_spec._CompareWithLocationSegment('__init__.py', 6) self.assertTrue(result) - result = find_spec._CheckLocation(file_entry, 0) + result = find_spec._CompareWithLocationSegment('__init__.py', 0) self.assertTrue(result) - result = find_spec._CheckLocation(file_entry, 5) + result = find_spec._CompareWithLocationSegment('__init__.py', 5) self.assertFalse(result) find_spec = file_system_searcher.FindSpec( location='/usr/lib/python2.7/site-packages/dfvfs/bogus.py', location_separator='/') - result = find_spec._CheckLocation(file_entry, 6) + result = find_spec._CompareWithLocationSegment('__init__.py', 6) self.assertFalse(result) def testConvertLocationGlob2Regex(self): @@ -289,10 +283,144 @@ def testSplitPath(self): path_segments = find_spec._SplitPath('/tmp/location', '/') self.assertEqual(path_segments, ['tmp', 'location']) - # TODO: add tests for AtMaximumDepth + def testAtLastLocationSegment(self): + """Test the AtLastLocationSegment function.""" + find_spec = file_system_searcher.FindSpec() + + result = find_spec.AtLastLocationSegment(6) + self.assertFalse(result) + + find_spec = file_system_searcher.FindSpec( + location='/usr/lib/python2.7/site-packages/dfvfs/__init__.py', + location_separator='/') + + result = find_spec.AtLastLocationSegment(0) + self.assertFalse(result) + + result = find_spec.AtLastLocationSegment(6) + self.assertTrue(result) + + result = find_spec.AtLastLocationSegment(9) + self.assertTrue(result) + + def testAtMaximumDepth(self): + """Test the AtMaximumDepth function.""" + find_spec = file_system_searcher.FindSpec() + + result = find_spec.AtMaximumDepth(6) + self.assertFalse(result) + + find_spec = file_system_searcher.FindSpec( + location='/usr/lib/python2.7/site-packages/dfvfs/__init__.py', + location_separator='/') + + result = find_spec.AtMaximumDepth(0) + self.assertFalse(result) + + result = find_spec.AtMaximumDepth(6) + self.assertTrue(result) + + result = find_spec.AtMaximumDepth(9) + self.assertTrue(result) + + def testCompareLocation(self): + """Test the CompareLocation function.""" + file_system = self._CreateTestFileSystem() + + path_spec = fake_path_spec.FakePathSpec( + location='/usr/lib/python2.7/site-packages/dfvfs/__init__.py') + file_entry = file_system.GetFileEntryByPathSpec(path_spec) + + find_spec = file_system_searcher.FindSpec( + location='/usr/lib/python2.7/site-packages/dfvfs/__init__.py', + location_separator='/') + + result = find_spec.CompareLocation(file_entry) + self.assertTrue(result) + + find_spec = file_system_searcher.FindSpec( + location='/usr/lib/python2.7/site-packages/dfvfs/bogus.py', + location_separator='/') + + result = find_spec.CompareLocation(file_entry) + self.assertFalse(result) + + def testCompareNameWithLocationSegment(self): + """Test the CompareNameWithLocationSegment function.""" + file_system = self._CreateTestFileSystem() + + path_spec = fake_path_spec.FakePathSpec( + location='/usr/lib/python2.7/site-packages/dfvfs/__init__.py') + file_entry = file_system.GetFileEntryByPathSpec(path_spec) + + find_spec = file_system_searcher.FindSpec( + location='/usr/lib/python2.7/site-packages/dfvfs/__init__.py', + location_separator='/') + + result = find_spec.CompareNameWithLocationSegment(file_entry, 6) + self.assertTrue(result) + + result = find_spec.CompareNameWithLocationSegment(file_entry, 5) + self.assertFalse(result) + + # Currently comparing against the root location segment always + # returns True. + result = find_spec.CompareNameWithLocationSegment(file_entry, 0) + self.assertTrue(result) + + find_spec = file_system_searcher.FindSpec( + location='/usr/lib/python2.7/site-packages/dfvfs/bogus.py', + location_separator='/') + + result = find_spec.CompareNameWithLocationSegment(file_entry, 6) + self.assertFalse(result) + + def testCompareTraits(self): + """Test the CompareTraits function.""" + file_system = self._CreateTestFileSystem() + + path_spec = fake_path_spec.FakePathSpec( + location='/usr/lib/python2.7/site-packages/dfvfs/__init__.py') + file_entry = file_system.GetFileEntryByPathSpec(path_spec) + + find_spec = file_system_searcher.FindSpec( + location='/usr/lib/python2.7/site-packages/dfvfs/__init__.py', + location_separator='/') + + result = find_spec.CompareTraits(file_entry) + self.assertTrue(result) + + def testHasLocation(self): + """Test the HasLocation function.""" + find_spec = file_system_searcher.FindSpec() + + result = find_spec.HasLocation() + self.assertFalse(result) + + find_spec = file_system_searcher.FindSpec( + location='/usr/lib/python2.7/site-packages/dfvfs/__init__.py', + location_separator='/') + + result = find_spec.HasLocation() + self.assertTrue(result) + + def testIsLastLocationSegment(self): + """Test the IsLastLocationSegment function.""" + find_spec = file_system_searcher.FindSpec( + location='/usr/lib/python2.7/site-packages/dfvfs/__init__.py', + location_separator='/') + + result = find_spec.IsLastLocationSegment(0) + self.assertFalse(result) + + result = find_spec.IsLastLocationSegment(6) + self.assertTrue(result) + + result = find_spec.IsLastLocationSegment(9) + self.assertFalse(result) def testMatches(self): - """Test the Matches() function.""" + """Test the Matches function.""" file_system = self._CreateTestFileSystem() path_spec = fake_path_spec.FakePathSpec( @@ -347,7 +475,7 @@ def setUp(self): self._tsk_file_system.Open(self._tsk_path_spec) def testFind(self): - """Test the Find() function.""" + """Test the Find function.""" searcher = file_system_searcher.FileSystemSearcher( self._tsk_file_system, self._qcow_path_spec)