diff --git a/dissect/shellitem/lnk/lnk.py b/dissect/shellitem/lnk/lnk.py index c772dcd..759b343 100644 --- a/dissect/shellitem/lnk/lnk.py +++ b/dissect/shellitem/lnk/lnk.py @@ -68,16 +68,16 @@ def _parse(self, fh: BinaryIO) -> None: ) return - struct = c_lnk.typedefs[block_name](block_data) + if block_name == "VISTA_AND_ABOVE_IDLIST_PROPS": + struct = LnkTargetIdList(BytesIO(block_data), read_size) + else: + struct = c_lnk.typedefs[block_name](block_data) if block_name == "PROPERTY_STORE_PROPS": # TODO implement actual serialized property parsing guid = self._parse_guid(struct.format_id) struct._values.update({"format_id": guid}) - elif block_name == "VISTA_AND_ABOVE_IDLIST_PROPS": - struct = LnkTargetIdList(BytesIO(block_data), read_size) - elif block_name == "TRACKER_PROPS": for name, value in struct._values.items(): if "droid" in name: @@ -103,7 +103,7 @@ def _parse(self, fh: BinaryIO) -> None: self.extradata.update({block_name: struct}) else: - log.warning(f"Unknown extra data block encountered with signature 0x{signature:x}") + log.warning("Unknown extra data block encountered with signature %x", signature) # keep calling parse until the TERMINAL_BLOCK is hit. self._parse(fh) @@ -111,8 +111,8 @@ def _parse(self, fh: BinaryIO) -> None: def _parse_guid(self, guid: bytes, endianness: str = "<") -> UUID: if endianness == "<": return UUID(bytes_le=guid) - else: - return UUID(bytes=guid) + + return UUID(bytes=guid) def __getattr__(self, attr: str) -> Any: try: @@ -208,8 +208,8 @@ def __init__(self, fh: Optional[BinaryIO] = None): # if so the LocalBasePathOffsetUnicode and CommonPathSuffixOffsetUnicode fields are present if self.linkinfo_header.link_info_header_size >= 0x00000024: log.error( - "Unicode link_info_header encountered. Size bigger than 0x00000024. Size encountered:" - f"{self.linkinfo_header.link_info_header_size}" + "Unicode link_info_header encountered. Size bigger than 0x00000024. Size encountered: %x", + self.linkinfo_header.link_info_header_size, ) # TODO parse unicode headers. none encountered yet. @@ -435,16 +435,18 @@ def _parse_header(self, fh: Optional[BinaryIO]) -> Optional[c_lnk.SHELL_LINK_HEA if link_clsid == "00021401-0000-0000-c000-000000000046": return link_header - else: - log.info(f"Encountered invalid link file header: {link_header}. Skipping.") - return None - else: - log.info( - f"Encountered invalid link file with magic header size 0x{header_size:x} - " - f"magic header size should be 0x{LINK_HEADER_SIZE:x}. Skipping." - ) + + log.info("Encountered invalid link file header: %s. Skipping.", link_header) return None + log.info( + "Encountered invalid link file with magic header size 0x%x. \ + Magic header size should be 0x%x. Skipping.", + header_size, + LINK_HEADER_SIZE, + ) + return None + @property def clsid(self) -> UUID: """Returns the class id (clsid) of the LNK file.""" diff --git a/tests/conftest.py b/tests/conftest.py index a72b1b9..38c98a0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,33 +1,37 @@ -import os from pathlib import Path import pytest -def absolute_path(filename): - return os.path.join(os.path.dirname(__file__), filename) +def absolute_path(filename: str) -> Path: + return Path(__file__).parent.joinpath(filename) @pytest.fixture -def xp_modified_remote_lnk_file(): - return Path(absolute_path("data/modified_remote.file.xp.lnk")) +def xp_modified_remote_lnk_file() -> Path: + return absolute_path("data/modified_remote.file.xp.lnk") @pytest.fixture -def xp_remote_lnk_file(): - return Path(absolute_path("data/remote.file.xp.lnk")) +def xp_remote_lnk_file() -> Path: + return absolute_path("data/remote.file.xp.lnk") @pytest.fixture -def xp_remote_lnk_dir(): - return Path(absolute_path("data/remote.directory.xp.lnk")) +def xp_remote_lnk_dir() -> Path: + return absolute_path("data/remote.directory.xp.lnk") @pytest.fixture -def win7_local_lnk_dir(): - return Path(absolute_path("data/local.directory.seven.lnk")) +def win7_local_lnk_dir() -> Path: + return absolute_path("data/local.directory.seven.lnk") @pytest.fixture -def win81_downloads_lnk_dir(): - return Path(absolute_path("data/downloads.win81.lnk")) +def win81_downloads_lnk_dir() -> Path: + return absolute_path("data/downloads.win81.lnk") + + +@pytest.fixture +def vista_idlist_lnk_file() -> Path: + return absolute_path("data/vista.idlist.lnk") diff --git a/tests/data/vista.idlist.lnk b/tests/data/vista.idlist.lnk new file mode 100755 index 0000000..974bacb Binary files /dev/null and b/tests/data/vista.idlist.lnk differ diff --git a/tests/test_lnk.py b/tests/test_lnk.py index 28c381c..cf92224 100644 --- a/tests/test_lnk.py +++ b/tests/test_lnk.py @@ -1,9 +1,11 @@ +from pathlib import Path + from dissect.util.ts import uuid1timestamp from dissect.shellitem.lnk import Lnk, c_lnk -def test_xp_custom_destination_remote_lnk_file(xp_modified_remote_lnk_file): +def test_xp_custom_destination_remote_lnk_file(xp_modified_remote_lnk_file: Path) -> None: # The first 16 bytes contain the LNK GUID a to simulate a Jumplist CustomDestination file fh = xp_modified_remote_lnk_file.open("rb") fh.seek(16) @@ -14,7 +16,7 @@ def test_xp_custom_destination_remote_lnk_file(xp_modified_remote_lnk_file): assert str(lnk_file.clsid) == "00021401-0000-0000-c000-000000000046" -def test_xp_remote_lnk_file(xp_remote_lnk_file): +def test_xp_remote_lnk_file(xp_remote_lnk_file: Path) -> None: fh = xp_remote_lnk_file.open("rb") lnk_file = Lnk(fh) @@ -68,7 +70,7 @@ def test_xp_remote_lnk_file(xp_remote_lnk_file): assert uuid1timestamp(tracker_props.file_droid.time).ctime() == "Wed Feb 8 07:52:55 2006" -def test_xp_remote_lnk_dir(xp_remote_lnk_dir): +def test_xp_remote_lnk_dir(xp_remote_lnk_dir: Path) -> None: fh = xp_remote_lnk_dir.open("rb") lnk_file = Lnk(fh) @@ -81,7 +83,7 @@ def test_xp_remote_lnk_dir(xp_remote_lnk_dir): idlist = lnk_file.target_idlist.idlist assert len(idlist.itemid_list) == 7 assert idlist.terminalid == b"\x00\x00" - assert all([entry.itemid_size == len(entry.dumps()) for entry in idlist.itemid_list]) + assert all(entry.itemid_size == len(entry.dumps()) for entry in idlist.itemid_list) assert flags & c_lnk.LINK_FLAGS.has_link_info link_info = lnk_file.linkinfo.link_info @@ -116,7 +118,7 @@ def test_xp_remote_lnk_dir(xp_remote_lnk_dir): assert uuid1timestamp(tracker_props.file_droid.time).ctime() == "Tue Jul 21 07:31:44 2009" -def test_win7_local_lnk_dir(win7_local_lnk_dir): +def test_win7_local_lnk_dir(win7_local_lnk_dir: Path) -> None: fh = win7_local_lnk_dir.open("rb") lnk_file = Lnk(fh) @@ -179,7 +181,7 @@ def test_win7_local_lnk_dir(win7_local_lnk_dir): assert str(property_store_props.format_id) == "b725f130-47ef-101a-a5f1-02608c9eebac" -def test_common_path_suffix(win81_downloads_lnk_dir): +def test_common_path_suffix(win81_downloads_lnk_dir: Path) -> None: fh = win81_downloads_lnk_dir.open("rb") downloads = Lnk(fh) @@ -190,3 +192,13 @@ def test_common_path_suffix(win81_downloads_lnk_dir): assert link_info_flags & c_lnk.LINK_INFO_FLAGS.common_network_relative_link_and_pathsuffix == 0 assert link_info.common_path_suffix == b"" assert link_info.common_path_suffix.decode() == "" + + +def test_vista_and_above_idlist_lnk_props(vista_idlist_lnk_file: Path) -> None: + fh = vista_idlist_lnk_file.open("rb") + lnk_file = Lnk(fh) + vista_props = lnk_file.extradata.extradata["VISTA_AND_ABOVE_IDLIST_PROPS"] + + assert vista_props.size == 0x62 # 98 + assert vista_props.idlist.itemid_list[0].itemid_size == 0x60 # 96 + assert len(vista_props.idlist.itemid_list[0].data) == 0x5E # 94