diff --git a/dfvfs/vfs/tsk_file_entry.py b/dfvfs/vfs/tsk_file_entry.py index 542b1e80..0633366f 100644 --- a/dfvfs/vfs/tsk_file_entry.py +++ b/dfvfs/vfs/tsk_file_entry.py @@ -194,6 +194,10 @@ class TSKFileEntry(file_entry.FileEntry): pytsk3.TSK_FS_TYPE_HFS, pytsk3.TSK_FS_TYPE_HFS_DETECT] + _TSK_ISO9660_FS_TYPES = [ + pytsk3.TSK_FS_TYPE_ISO9660, + pytsk3.TSK_FS_TYPE_ISO9660_DETECT] + _TSK_NTFS_FS_TYPES = [ pytsk3.TSK_FS_TYPE_NTFS, pytsk3.TSK_FS_TYPE_NTFS_DETECT] @@ -222,6 +226,7 @@ class TSKFileEntry(file_entry.FileEntry): _TSK_CRTIME_FS_TYPES = [pytsk3.TSK_FS_TYPE_EXT4] _TSK_CRTIME_FS_TYPES.extend(_TSK_FAT_FS_TYPES) _TSK_CRTIME_FS_TYPES.extend(_TSK_HFS_FS_TYPES) + _TSK_CRTIME_FS_TYPES.extend(_TSK_ISO9660_FS_TYPES) _TSK_CRTIME_FS_TYPES.extend(_TSK_NTFS_FS_TYPES) _TSK_DTIME_FS_TYPES = _TSK_EXT_FS_TYPES diff --git a/test_data/iso9660.raw b/test_data/iso9660.raw new file mode 100644 index 00000000..c4a34469 Binary files /dev/null and b/test_data/iso9660.raw differ diff --git a/tests/vfs/tsk_file_entry.py b/tests/vfs/tsk_file_entry.py index 33e9f214..df8eb0d0 100644 --- a/tests/vfs/tsk_file_entry.py +++ b/tests/vfs/tsk_file_entry.py @@ -409,7 +409,7 @@ def testSubFileEntries(self): self.assertEqual(file_entry.number_of_sub_file_entries, 5) - # Note that passwords.txt~ is currently ignored by dfvfs, since + # Note that passwords.txt~ is currently ignored by dfVFS, since # its directory entry has no pytsk3.TSK_FS_META object. expected_sub_file_entry_names = [ 'a_directory', @@ -763,7 +763,7 @@ def testSubFileEntries(self): self.assertEqual(file_entry.number_of_sub_file_entries, 7) - # Note that passwords.txt~ is currently ignored by dfvfs, since + # Note that passwords.txt~ is currently ignored by dfVFS, since # its directory entry has no pytsk3.TSK_FS_META object. expected_sub_file_entry_names = [ '$FAT1', @@ -1277,6 +1277,387 @@ def testGetDataStream(self): self.assertIsNotNone(data_stream) +class TSKFileEntryTestISO9660(shared_test_lib.BaseTestCase): + """Tests the SleuthKit (TSK) file entry on ISO9660.""" + + # pylint: disable=protected-access + + _INODE_A_DIRECTORY = 1 + _INODE_A_FILE = 5 + _INODE_ANOTHER_FILE = 4 + + def setUp(self): + """Sets up the needed objects used throughout the test.""" + self._resolver_context = context.Context() + test_path = self._GetTestFilePath(['iso9660.raw']) + self._SkipIfPathNotExists(test_path) + + self.test_os_path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_OS, location=test_path) + self._tsk_path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_TSK, location='/', + parent=self.test_os_path_spec) + + self._file_system = tsk_file_system.TSKFileSystem( + self._resolver_context, self._tsk_path_spec) + self._file_system.Open() + + def tearDown(self): + """Cleans up the needed objects used throughout the test.""" + self._resolver_context.Empty() + + def testInitialize(self): + """Tests the __init__ function.""" + file_entry = tsk_file_entry.TSKFileEntry( + self._resolver_context, self._file_system, self._tsk_path_spec) + + self.assertIsNotNone(file_entry) + + def testGetAttributes(self): + """Tests the _GetAttributes function.""" + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_TSK, inode=self._INODE_A_FILE, + location='/A_DIRECTORY/A_FILE', parent=self.test_os_path_spec) + file_entry = self._file_system.GetFileEntryByPathSpec(path_spec) + self.assertIsNotNone(file_entry) + + self.assertIsNone(file_entry._attributes) + + file_entry._GetAttributes() + self.assertIsNotNone(file_entry._attributes) + self.assertEqual(len(file_entry._attributes), 0) + + def testGetDataStreams(self): + """Tests the _GetDataStreams function.""" + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_TSK, inode=self._INODE_ANOTHER_FILE, + location='/A_DIRECTORY/ANOTHER_FILE', parent=self.test_os_path_spec) + file_entry = self._file_system.GetFileEntryByPathSpec(path_spec) + self.assertIsNotNone(file_entry) + + data_streams = file_entry._GetDataStreams() + self.assertEqual(len(data_streams), 1) + + # TODO: add tests for _GetDirectory + # TODO: add tests for _GetLink + + def testGetStatAttribute(self): + """Tests the _GetStatAttribute function.""" + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_TSK, inode=self._INODE_ANOTHER_FILE, + location='/A_DIRECTORY/ANOTHER_FILE', parent=self.test_os_path_spec) + file_entry = self._file_system.GetFileEntryByPathSpec(path_spec) + self.assertIsNotNone(file_entry) + + stat_attribute = file_entry._GetStatAttribute() + + self.assertIsNotNone(stat_attribute) + self.assertEqual(stat_attribute.group_identifier, 0) + self.assertEqual(stat_attribute.inode_number, 4) + self.assertEqual(stat_attribute.mode, 0) + self.assertEqual(stat_attribute.number_of_links, 1) + self.assertEqual(stat_attribute.owner_identifier, 0) + self.assertEqual(stat_attribute.size, 22) + self.assertEqual(stat_attribute.type, stat_attribute.TYPE_FILE) + + # TODO: add tests for _GetSubFileEntries + # TODO: add tests for _GetTimeValue + + def testAccessTime(self): + """Test the access_time property.""" + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_TSK, inode=self._INODE_ANOTHER_FILE, + location='/A_DIRECTORY/ANOTHER_FILE', parent=self.test_os_path_spec) + file_entry = self._file_system.GetFileEntryByPathSpec(path_spec) + + self.assertIsNotNone(file_entry) + self.assertIsNone(file_entry.access_time) + + def testBackupTime(self): + """Test the backup_time property.""" + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_TSK, inode=self._INODE_ANOTHER_FILE, + location='/A_DIRECTORY/ANOTHER_FILE', parent=self.test_os_path_spec) + file_entry = self._file_system.GetFileEntryByPathSpec(path_spec) + + self.assertIsNotNone(file_entry) + self.assertIsNone(file_entry.backup_time) + + def testChangeTime(self): + """Test the change_time property.""" + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_TSK, inode=self._INODE_ANOTHER_FILE, + location='/A_DIRECTORY/ANOTHER_FILE', parent=self.test_os_path_spec) + file_entry = self._file_system.GetFileEntryByPathSpec(path_spec) + + self.assertIsNotNone(file_entry) + self.assertIsNone(file_entry.change_time) + + def testCreationTime(self): + """Test the creation_time property.""" + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_TSK, inode=self._INODE_ANOTHER_FILE, + location='/A_DIRECTORY/ANOTHER_FILE', parent=self.test_os_path_spec) + file_entry = self._file_system.GetFileEntryByPathSpec(path_spec) + + self.assertIsNotNone(file_entry) + self.assertIsNotNone(file_entry.creation_time) + + def testDeletionTime(self): + """Test the deletion_time property.""" + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_TSK, inode=self._INODE_ANOTHER_FILE, + location='/A_DIRECTORY/ANOTHER_FILE', parent=self.test_os_path_spec) + file_entry = self._file_system.GetFileEntryByPathSpec(path_spec) + + self.assertIsNotNone(file_entry) + self.assertIsNone(file_entry.deletion_time) + + def testModificationTime(self): + """Test the modification_time property.""" + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_TSK, inode=self._INODE_ANOTHER_FILE, + location='/A_DIRECTORY/ANOTHER_FILE', parent=self.test_os_path_spec) + file_entry = self._file_system.GetFileEntryByPathSpec(path_spec) + + self.assertIsNotNone(file_entry) + self.assertIsNone(file_entry.modification_time) + + def testName(self): + """Test the name property.""" + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_TSK, inode=self._INODE_ANOTHER_FILE, + location='/A_DIRECTORY/ANOTHER_FILE', parent=self.test_os_path_spec) + file_entry = self._file_system.GetFileEntryByPathSpec(path_spec) + + self.assertIsNotNone(file_entry) + self.assertEqual(file_entry.name, 'ANOTHER_FILE') + + def testSize(self): + """Test the size property.""" + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_TSK, inode=self._INODE_ANOTHER_FILE, + location='/A_DIRECTORY/ANOTHER_FILE', parent=self.test_os_path_spec) + file_entry = self._file_system.GetFileEntryByPathSpec(path_spec) + + self.assertIsNotNone(file_entry) + self.assertEqual(file_entry.size, 22) + + def testGetExtents(self): + """Tests the GetExtents function.""" + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_EXT, inode=self._INODE_ANOTHER_FILE, + location='/A_DIRECTORY/ANOTHER_FILE', parent=self.test_os_path_spec) + file_entry = self._file_system.GetFileEntryByPathSpec(path_spec) + self.assertIsNotNone(file_entry) + + extents = file_entry.GetExtents() + self.assertEqual(len(extents), 1) + + self.assertEqual(extents[0].extent_type, definitions.EXTENT_TYPE_DATA) + self.assertEqual(extents[0].offset, 55296) + self.assertEqual(extents[0].size, 2048) + + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_EXT, inode=self._INODE_A_DIRECTORY, + location='/A_DIRECTORY', parent=self.test_os_path_spec) + file_entry = self._file_system.GetFileEntryByPathSpec(path_spec) + self.assertIsNotNone(file_entry) + + extents = file_entry.GetExtents() + self.assertEqual(len(extents), 0) + + def testGetFileEntryByPathSpec(self): + """Tests the GetFileEntryByPathSpec function.""" + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_TSK, inode=self._INODE_ANOTHER_FILE, + parent=self.test_os_path_spec) + file_entry = self._file_system.GetFileEntryByPathSpec(path_spec) + + self.assertIsNotNone(file_entry) + + def testGetFileObject(self): + """Tests the GetFileObject function.""" + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_TSK, inode=self._INODE_ANOTHER_FILE, + location='/A_DIRECTORY/ANOTHER_FILE', parent=self.test_os_path_spec) + file_entry = self._file_system.GetFileEntryByPathSpec(path_spec) + self.assertIsNotNone(file_entry) + + file_object = file_entry.GetFileObject() + self.assertIsNotNone(file_object) + + self.assertEqual(file_object.get_size(), 22) + + file_object = file_entry.GetFileObject(data_stream_name='bogus') + self.assertIsNone(file_object) + + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_TSK, inode=self._INODE_A_DIRECTORY, + location='/A_DIRECTORY', parent=self.test_os_path_spec) + file_entry = self._file_system.GetFileEntryByPathSpec(path_spec) + self.assertIsNotNone(file_entry) + + with self.assertRaises(errors.BackEndError): + file_entry.GetFileObject() + + def testGetLinkedFileEntry(self): + """Tests the GetLinkedFileEntry function.""" + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_TSK, inode=self._INODE_A_FILE, + location='/A_DIRECTORY/A_FILE', parent=self.test_os_path_spec) + file_entry = self._file_system.GetFileEntryByPathSpec(path_spec) + self.assertIsNotNone(file_entry) + + linked_file_entry = file_entry.GetLinkedFileEntry() + + self.assertIsNone(linked_file_entry) + + def testGetParentFileEntry(self): + """Tests the GetParentFileEntry function.""" + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_TSK, inode=self._INODE_ANOTHER_FILE, + location='/A_DIRECTORY/ANOTHER_FILE', parent=self.test_os_path_spec) + file_entry = self._file_system.GetFileEntryByPathSpec(path_spec) + self.assertIsNotNone(file_entry) + + parent_file_entry = file_entry.GetParentFileEntry() + + self.assertIsNotNone(parent_file_entry) + + self.assertEqual(parent_file_entry.name, 'A_DIRECTORY') + + # TODO: add tests for GetTSKFile + + def testIsFunctions(self): + """Tests the Is? functions.""" + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_TSK, inode=self._INODE_ANOTHER_FILE, + location='/A_DIRECTORY/ANOTHER_FILE', parent=self.test_os_path_spec) + file_entry = self._file_system.GetFileEntryByPathSpec(path_spec) + self.assertIsNotNone(file_entry) + + self.assertFalse(file_entry.IsRoot()) + self.assertFalse(file_entry.IsVirtual()) + self.assertTrue(file_entry.IsAllocated()) + + self.assertFalse(file_entry.IsDevice()) + self.assertFalse(file_entry.IsDirectory()) + self.assertTrue(file_entry.IsFile()) + self.assertFalse(file_entry.IsLink()) + self.assertFalse(file_entry.IsPipe()) + self.assertFalse(file_entry.IsSocket()) + + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_TSK, inode=self._INODE_A_DIRECTORY, + location='/A_DIRECTORY', parent=self.test_os_path_spec) + file_entry = self._file_system.GetFileEntryByPathSpec(path_spec) + self.assertIsNotNone(file_entry) + + self.assertFalse(file_entry.IsRoot()) + self.assertFalse(file_entry.IsVirtual()) + self.assertTrue(file_entry.IsAllocated()) + + self.assertFalse(file_entry.IsDevice()) + self.assertTrue(file_entry.IsDirectory()) + self.assertFalse(file_entry.IsFile()) + self.assertFalse(file_entry.IsLink()) + self.assertFalse(file_entry.IsPipe()) + self.assertFalse(file_entry.IsSocket()) + + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_TSK, location='/', + parent=self.test_os_path_spec) + file_entry = self._file_system.GetFileEntryByPathSpec(path_spec) + self.assertIsNotNone(file_entry) + + self.assertTrue(file_entry.IsRoot()) + self.assertFalse(file_entry.IsVirtual()) + self.assertTrue(file_entry.IsAllocated()) + + self.assertFalse(file_entry.IsDevice()) + self.assertTrue(file_entry.IsDirectory()) + self.assertFalse(file_entry.IsFile()) + self.assertFalse(file_entry.IsLink()) + self.assertFalse(file_entry.IsPipe()) + self.assertFalse(file_entry.IsSocket()) + + def testSubFileEntries(self): + """Tests the number_of_sub_file_entries and sub_file_entries properties.""" + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_TSK, location='/', + parent=self.test_os_path_spec) + file_entry = self._file_system.GetFileEntryByPathSpec(path_spec) + self.assertIsNotNone(file_entry) + + self.assertEqual(file_entry.number_of_sub_file_entries, 4) + + expected_sub_file_entry_names = [ + 'A_DIRECTORY', + 'LOST_FOUND', + 'PASSWORDS.TXT', + '$OrphanFiles'] + + sub_file_entry_names = [] + for sub_file_entry in file_entry.sub_file_entries: + sub_file_entry_names.append(sub_file_entry.name) + + self.assertEqual( + len(sub_file_entry_names), len(expected_sub_file_entry_names)) + self.assertEqual( + sorted(sub_file_entry_names), sorted(expected_sub_file_entry_names)) + + # Test a path specification without a location. + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_TSK, inode=self._INODE_A_DIRECTORY, + parent=self.test_os_path_spec) + file_entry = self._file_system.GetFileEntryByPathSpec(path_spec) + self.assertIsNotNone(file_entry) + + self.assertEqual(file_entry.number_of_sub_file_entries, 2) + + def testDataStreams(self): + """Tests the data streams functionality.""" + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_TSK, inode=self._INODE_ANOTHER_FILE, + location='/A_DIRECTORY/ANOTHER_FILE', parent=self.test_os_path_spec) + file_entry = self._file_system.GetFileEntryByPathSpec(path_spec) + self.assertIsNotNone(file_entry) + + self.assertEqual(file_entry.number_of_data_streams, 1) + + data_stream_names = [] + for data_stream in file_entry.data_streams: + data_stream_names.append(data_stream.name) + + self.assertEqual(data_stream_names, ['']) + + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_TSK, inode=self._INODE_A_DIRECTORY, + location='/A_DIRECTORY', parent=self.test_os_path_spec) + file_entry = self._file_system.GetFileEntryByPathSpec(path_spec) + self.assertIsNotNone(file_entry) + + self.assertEqual(file_entry.number_of_data_streams, 0) + + data_stream_names = [] + for data_stream in file_entry.data_streams: + data_stream_names.append(data_stream.name) + + self.assertEqual(data_stream_names, []) + + def testGetDataStream(self): + """Tests the GetDataStream function.""" + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_TSK, inode=self._INODE_ANOTHER_FILE, + location='/A_DIRECTORY/ANOTHER_FILE', parent=self.test_os_path_spec) + file_entry = self._file_system.GetFileEntryByPathSpec(path_spec) + self.assertIsNotNone(file_entry) + + data_stream = file_entry.GetDataStream('') + self.assertIsNotNone(data_stream) + + class TSKFileEntryTestNTFS(shared_test_lib.BaseTestCase): """Tests the SleuthKit (TSK) file entry on NTFS.""" diff --git a/utils/generate_test_data_linux.sh b/utils/generate_test_data_linux.sh index d976d702..212074b2 100755 --- a/utils/generate_test_data_linux.sh +++ b/utils/generate_test_data_linux.sh @@ -1,13 +1,6 @@ #!/bin/bash # # Script to generate dfVFS test files on Linux. -# -# Requires: -# * Linux -# * dd -# * mke2fs -# * mkntfs -# * qemu-img EXIT_SUCCESS=0; EXIT_FAILURE=1; @@ -94,6 +87,7 @@ assert_availability_binary cryptsetup; assert_availability_binary dd; assert_availability_binary ewfacquire; assert_availability_binary fdisk; +assert_availability_binary genisoimage; assert_availability_binary gdisk; assert_availability_binary hformat; assert_availability_binary losetup; @@ -427,3 +421,10 @@ create_test_file_entries_without_link ${MOUNT_POINT}; sudo umount ${MOUNT_POINT}; +# Create test image with ISO9660 level 3 file system +sudo mount -o loop,rw test_data/ext2.raw ${MOUNT_POINT}; + +genisoimage -input-charset utf8 -iso-level 3 -o test_data/iso9660.raw ${MOUNT_POINT}; + +sudo umount ${MOUNT_POINT}; +