dissect.target 3.19.dev41__py3-none-any.whl → 3.19.dev43__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- dissect/target/helpers/shell_application_ids.py +732 -0
- dissect/target/helpers/utils.py +11 -0
- dissect/target/plugins/os/windows/catroot.py +1 -11
- dissect/target/plugins/os/windows/jumplist.py +292 -0
- dissect/target/plugins/os/windows/lnk.py +84 -89
- dissect/target/plugins/os/windows/wua_history.py +1073 -0
- {dissect.target-3.19.dev41.dist-info → dissect.target-3.19.dev43.dist-info}/METADATA +2 -1
- {dissect.target-3.19.dev41.dist-info → dissect.target-3.19.dev43.dist-info}/RECORD +13 -10
- {dissect.target-3.19.dev41.dist-info → dissect.target-3.19.dev43.dist-info}/COPYRIGHT +0 -0
- {dissect.target-3.19.dev41.dist-info → dissect.target-3.19.dev43.dist-info}/LICENSE +0 -0
- {dissect.target-3.19.dev41.dist-info → dissect.target-3.19.dev43.dist-info}/WHEEL +0 -0
- {dissect.target-3.19.dev41.dist-info → dissect.target-3.19.dev43.dist-info}/entry_points.txt +0 -0
- {dissect.target-3.19.dev41.dist-info → dissect.target-3.19.dev43.dist-info}/top_level.txt +0 -0
dissect/target/helpers/utils.py
CHANGED
@@ -13,6 +13,17 @@ from dissect.target.helpers import fsutil
|
|
13
13
|
log = logging.getLogger(__name__)
|
14
14
|
|
15
15
|
|
16
|
+
def findall(buf: bytes, needle: bytes) -> Iterator[int]:
|
17
|
+
offset = 0
|
18
|
+
while True:
|
19
|
+
offset = buf.find(needle, offset)
|
20
|
+
if offset == -1:
|
21
|
+
break
|
22
|
+
|
23
|
+
yield offset
|
24
|
+
offset += 1
|
25
|
+
|
26
|
+
|
16
27
|
class StrEnum(str, Enum):
|
17
28
|
"""Sortable and serializible string-based enum"""
|
18
29
|
|
@@ -5,6 +5,7 @@ from flow.record.fieldtypes import digest
|
|
5
5
|
|
6
6
|
from dissect.target.exceptions import UnsupportedPluginError
|
7
7
|
from dissect.target.helpers.record import TargetRecordDescriptor
|
8
|
+
from dissect.target.helpers.utils import findall
|
8
9
|
from dissect.target.plugin import Plugin, export
|
9
10
|
|
10
11
|
try:
|
@@ -36,17 +37,6 @@ CatrootRecord = TargetRecordDescriptor(
|
|
36
37
|
)
|
37
38
|
|
38
39
|
|
39
|
-
def findall(buf: bytes, needle: bytes) -> Iterator[int]:
|
40
|
-
offset = 0
|
41
|
-
while True:
|
42
|
-
offset = buf.find(needle, offset)
|
43
|
-
if offset == -1:
|
44
|
-
break
|
45
|
-
|
46
|
-
yield offset
|
47
|
-
offset += 1
|
48
|
-
|
49
|
-
|
50
40
|
def _get_package_name(sequence: Sequence) -> str:
|
51
41
|
"""Parse sequences within a sequence and return the 'PackageName' value if it exists."""
|
52
42
|
for value in sequence.native.values():
|
@@ -0,0 +1,292 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import io
|
4
|
+
import logging
|
5
|
+
from struct import error as StructError
|
6
|
+
from typing import BinaryIO, Iterator
|
7
|
+
|
8
|
+
from dissect.cstruct import cstruct
|
9
|
+
from dissect.ole import OLE
|
10
|
+
from dissect.ole.exceptions import Error as OleError
|
11
|
+
from dissect.shellitem.lnk import Lnk
|
12
|
+
|
13
|
+
from dissect.target import Target
|
14
|
+
from dissect.target.exceptions import UnsupportedPluginError
|
15
|
+
from dissect.target.helpers.descriptor_extensions import UserRecordDescriptorExtension
|
16
|
+
from dissect.target.helpers.record import create_extended_descriptor
|
17
|
+
from dissect.target.helpers.shell_application_ids import APPLICATION_IDENTIFIERS
|
18
|
+
from dissect.target.helpers.utils import findall
|
19
|
+
from dissect.target.plugin import Plugin, export
|
20
|
+
from dissect.target.plugins.os.windows.lnk import LnkRecord, parse_lnk_file
|
21
|
+
|
22
|
+
log = logging.getLogger(__name__)
|
23
|
+
|
24
|
+
LNK_GUID = b"\x01\x14\x02\x00\x00\x00\x00\x00\xc0\x00\x00\x00\x00\x00\x00\x46"
|
25
|
+
|
26
|
+
JumpListRecord = create_extended_descriptor([UserRecordDescriptorExtension])(
|
27
|
+
"windows/jumplist",
|
28
|
+
[
|
29
|
+
("string", "type"),
|
30
|
+
("string", "application_id"),
|
31
|
+
("string", "application_name"),
|
32
|
+
*LnkRecord.target_fields,
|
33
|
+
],
|
34
|
+
)
|
35
|
+
|
36
|
+
|
37
|
+
custom_destination_def = """
|
38
|
+
struct header {
|
39
|
+
int version;
|
40
|
+
int unknown1;
|
41
|
+
int unknown2;
|
42
|
+
int value_type;
|
43
|
+
}
|
44
|
+
|
45
|
+
struct header_end {
|
46
|
+
int number_of_entries;
|
47
|
+
}
|
48
|
+
|
49
|
+
struct header_end_0 {
|
50
|
+
uint16 name_length;
|
51
|
+
wchar name[name_length];
|
52
|
+
int number_of_entries;
|
53
|
+
}
|
54
|
+
|
55
|
+
struct footer {
|
56
|
+
char magic[4];
|
57
|
+
}
|
58
|
+
"""
|
59
|
+
|
60
|
+
c_custom_destination = cstruct()
|
61
|
+
c_custom_destination.load(custom_destination_def)
|
62
|
+
|
63
|
+
|
64
|
+
class JumpListFile:
|
65
|
+
def __init__(self, fh: BinaryIO, file_name: str):
|
66
|
+
self.fh = fh
|
67
|
+
self.file_name = file_name
|
68
|
+
|
69
|
+
self.application_id, self.application_type = file_name.split(".")
|
70
|
+
self.application_type = self.application_type.split("-")[0]
|
71
|
+
self.application_name = APPLICATION_IDENTIFIERS.get(self.application_id)
|
72
|
+
|
73
|
+
def __iter__(self) -> Iterator[Lnk]:
|
74
|
+
raise NotImplementedError
|
75
|
+
|
76
|
+
@property
|
77
|
+
def name(self) -> str:
|
78
|
+
"""Return the name of the application."""
|
79
|
+
return self.application_name
|
80
|
+
|
81
|
+
@property
|
82
|
+
def id(self) -> str:
|
83
|
+
"""Return the application identifier."""
|
84
|
+
return self.application_id
|
85
|
+
|
86
|
+
@property
|
87
|
+
def type(self) -> str:
|
88
|
+
"""Return the type of the Jump List file."""
|
89
|
+
return self.application_type
|
90
|
+
|
91
|
+
|
92
|
+
class AutomaticDestinationFile(JumpListFile):
|
93
|
+
"""Parse Jump List AutomaticDestination file."""
|
94
|
+
|
95
|
+
def __init__(self, fh: BinaryIO, file_name: str):
|
96
|
+
super().__init__(fh, file_name)
|
97
|
+
self.ole = OLE(self.fh)
|
98
|
+
|
99
|
+
def __iter__(self) -> Iterator[Lnk]:
|
100
|
+
for dir_name in self.ole.root.listdir():
|
101
|
+
if dir_name == "DestList":
|
102
|
+
continue
|
103
|
+
|
104
|
+
dir = self.ole.get(dir_name)
|
105
|
+
|
106
|
+
for item in dir.open():
|
107
|
+
try:
|
108
|
+
yield Lnk(io.BytesIO(item))
|
109
|
+
except StructError:
|
110
|
+
continue
|
111
|
+
except Exception as e:
|
112
|
+
log.warning("Failed to parse LNK file from directory %s", dir_name)
|
113
|
+
log.debug("", exc_info=e)
|
114
|
+
continue
|
115
|
+
|
116
|
+
|
117
|
+
class CustomDestinationFile(JumpListFile):
|
118
|
+
"""Parse Jump List CustomDestination file."""
|
119
|
+
|
120
|
+
MAGIC_FOOTER = 0xBABFFBAB
|
121
|
+
VERSIONS = [2]
|
122
|
+
|
123
|
+
def __init__(self, fh: BinaryIO, file_name: str):
|
124
|
+
super().__init__(fh, file_name)
|
125
|
+
|
126
|
+
self.fh.seek(-4, io.SEEK_END)
|
127
|
+
self.footer = c_custom_destination.footer(self.fh.read(4))
|
128
|
+
self.magic = int.from_bytes(self.footer.magic, "little")
|
129
|
+
|
130
|
+
self.fh.seek(0, io.SEEK_SET)
|
131
|
+
self.header = c_custom_destination.header(self.fh)
|
132
|
+
self.version = self.header.version
|
133
|
+
|
134
|
+
if self.header.value_type == 0:
|
135
|
+
self.header_end = c_custom_destination.header_end_0(self.fh)
|
136
|
+
elif self.header.value_type in [1, 2]:
|
137
|
+
self.header_end = c_custom_destination.header_end(self.fh)
|
138
|
+
else:
|
139
|
+
raise NotImplementedError(
|
140
|
+
f"The value_type ({self.header.value_type}) of the CustomDestination file is not implemented"
|
141
|
+
)
|
142
|
+
|
143
|
+
if self.version not in self.VERSIONS:
|
144
|
+
raise NotImplementedError(f"The CustomDestination file has an unsupported version: {self.version}")
|
145
|
+
|
146
|
+
if not self.MAGIC_FOOTER == self.magic:
|
147
|
+
raise ValueError(f"The CustomDestination file has an invalid magic footer: {self.magic}")
|
148
|
+
|
149
|
+
def __iter__(self) -> Iterator[Lnk]:
|
150
|
+
# Searches for all LNK GUID's because the number of entries in the header is not always correct.
|
151
|
+
buf = self.fh.read()
|
152
|
+
|
153
|
+
for offset in findall(buf, LNK_GUID):
|
154
|
+
try:
|
155
|
+
lnk = Lnk(io.BytesIO(buf[offset + len(LNK_GUID) :]))
|
156
|
+
yield lnk
|
157
|
+
except EOFError:
|
158
|
+
break
|
159
|
+
except Exception as e:
|
160
|
+
log.warning("Failed to parse LNK file from a CustomDestination file")
|
161
|
+
log.debug("", exc_info=e)
|
162
|
+
continue
|
163
|
+
|
164
|
+
|
165
|
+
class JumpListPlugin(Plugin):
|
166
|
+
"""Jump List is a Windows feature introduced in Windows 7.
|
167
|
+
|
168
|
+
It stores information about recently accessed applications and files.
|
169
|
+
|
170
|
+
References:
|
171
|
+
- https://forensics.wiki/jump_lists
|
172
|
+
- https://github.com/libyal/dtformats/blob/main/documentation/Jump%20lists%20format.asciidoc
|
173
|
+
"""
|
174
|
+
|
175
|
+
__namespace__ = "jumplist"
|
176
|
+
|
177
|
+
def __init__(self, target: Target):
|
178
|
+
super().__init__(target)
|
179
|
+
self.automatic_destinations = []
|
180
|
+
self.custom_destinations = []
|
181
|
+
|
182
|
+
for user_details in self.target.user_details.all_with_home():
|
183
|
+
for destination in user_details.home_path.glob(
|
184
|
+
"AppData/Roaming/Microsoft/Windows/Recent/CustomDestinations/*.customDestinations-ms"
|
185
|
+
):
|
186
|
+
self.custom_destinations.append([destination, user_details.user])
|
187
|
+
|
188
|
+
for user_details in self.target.user_details.all_with_home():
|
189
|
+
for destination in user_details.home_path.glob(
|
190
|
+
"AppData/Roaming/Microsoft/Windows/Recent/AutomaticDestinations/*.automaticDestinations-ms"
|
191
|
+
):
|
192
|
+
self.automatic_destinations.append([destination, user_details.user])
|
193
|
+
|
194
|
+
def check_compatible(self) -> None:
|
195
|
+
if not any([self.automatic_destinations, self.custom_destinations]):
|
196
|
+
raise UnsupportedPluginError("No Jump List files found")
|
197
|
+
|
198
|
+
@export(record=JumpListRecord)
|
199
|
+
def custom_destination(self) -> Iterator[JumpListRecord]:
|
200
|
+
"""Return the content of CustomDestination Windows Jump Lists.
|
201
|
+
|
202
|
+
These are created when a user pins an application or a file in a Jump List.
|
203
|
+
|
204
|
+
Yields JumpListRecord with fields:
|
205
|
+
|
206
|
+
.. code-block:: text
|
207
|
+
|
208
|
+
type (string): Type of Jump List.
|
209
|
+
application_id (string): ID of the application.
|
210
|
+
application_name (string): Name of the application.
|
211
|
+
lnk_path (path): Path of the link (.lnk) file.
|
212
|
+
lnk_name (string): Name of the link (.lnk) file.
|
213
|
+
lnk_mtime (datetime): Modification time of the link (.lnk) file.
|
214
|
+
lnk_atime (datetime): Access time of the link (.lnk) file.
|
215
|
+
lnk_ctime (datetime): Creation time of the link (.lnk) file.
|
216
|
+
lnk_relativepath (path): Relative path of target file to the link (.lnk) file.
|
217
|
+
lnk_workdir (path): Path of the working directory the link (.lnk) file will execute from.
|
218
|
+
lnk_iconlocation (path): Path of the display icon used for the link (.lnk) file.
|
219
|
+
lnk_arguments (string): Command-line arguments passed to the target (linked) file.
|
220
|
+
local_base_path (string): Absolute path of the target (linked) file.
|
221
|
+
common_path_suffix (string): Suffix of the local_base_path.
|
222
|
+
lnk_full_path (string): Full path of the linked file. Made from local_base_path and common_path_suffix.
|
223
|
+
lnk_net_name (string): Specifies a server share path; for example, "\\\\server\\share".
|
224
|
+
lnk_device_name (string): Specifies a device; for example, the drive letter "D:"
|
225
|
+
machine_id (string): The NetBIOS name of the machine where the linked file was last known to reside.
|
226
|
+
target_mtime (datetime): Modification time of the target (linked) file.
|
227
|
+
target_atime (datetime): Access time of the target (linked) file.
|
228
|
+
target_ctime (datetime): Creation time of the target (linked) file.
|
229
|
+
"""
|
230
|
+
yield from self._generate_records(self.custom_destinations, CustomDestinationFile)
|
231
|
+
|
232
|
+
@export(record=JumpListRecord)
|
233
|
+
def automatic_destination(self) -> Iterator[JumpListRecord]:
|
234
|
+
"""Return the content of AutomaticDestination Windows Jump Lists.
|
235
|
+
|
236
|
+
These are created automatically when a user opens an application or file.
|
237
|
+
|
238
|
+
Yields JumpListRecord with fields:
|
239
|
+
|
240
|
+
.. code-block:: text
|
241
|
+
|
242
|
+
type (string): Type of Jump List.
|
243
|
+
application_id (string): ID of the application.
|
244
|
+
application_name (string): Name of the application.
|
245
|
+
lnk_path (path): Path of the link (.lnk) file.
|
246
|
+
lnk_name (string): Name of the link (.lnk) file.
|
247
|
+
lnk_mtime (datetime): Modification time of the link (.lnk) file.
|
248
|
+
lnk_atime (datetime): Access time of the link (.lnk) file.
|
249
|
+
lnk_ctime (datetime): Creation time of the link (.lnk) file.
|
250
|
+
lnk_relativepath (path): Relative path of target file to the link (.lnk) file.
|
251
|
+
lnk_workdir (path): Path of the working directory the link (.lnk) file will execute from.
|
252
|
+
lnk_iconlocation (path): Path of the display icon used for the link (.lnk) file.
|
253
|
+
lnk_arguments (string): Command-line arguments passed to the target (linked) file.
|
254
|
+
local_base_path (string): Absolute path of the target (linked) file.
|
255
|
+
common_path_suffix (string): Suffix of the local_base_path.
|
256
|
+
lnk_full_path (string): Full path of the linked file. Made from local_base_path and common_path_suffix.
|
257
|
+
lnk_net_name (string): Specifies a server share path; for example, "\\\\server\\share".
|
258
|
+
lnk_device_name (string): Specifies a device; for example, the drive letter "D:"
|
259
|
+
machine_id (string): The NetBIOS name of the machine where the linked file was last known to reside.
|
260
|
+
target_mtime (datetime): Modification time of the target (linked) file.
|
261
|
+
target_atime (datetime): Access time of the target (linked) file.
|
262
|
+
target_ctime (datetime): Creation time of the target (linked) file.
|
263
|
+
"""
|
264
|
+
yield from self._generate_records(self.automatic_destinations, AutomaticDestinationFile)
|
265
|
+
|
266
|
+
def _generate_records(
|
267
|
+
self,
|
268
|
+
destinations: list,
|
269
|
+
destination_file: AutomaticDestinationFile | CustomDestinationFile,
|
270
|
+
) -> Iterator[JumpListRecord]:
|
271
|
+
for destination, user in destinations:
|
272
|
+
fh = destination.open("rb")
|
273
|
+
|
274
|
+
try:
|
275
|
+
jumplist = destination_file(fh, destination.name)
|
276
|
+
except OleError:
|
277
|
+
continue
|
278
|
+
except Exception as e:
|
279
|
+
self.target.log.warning("Failed to parse Jump List file: %s", destination)
|
280
|
+
self.target.log.debug("", exc_info=e)
|
281
|
+
continue
|
282
|
+
|
283
|
+
for lnk in jumplist:
|
284
|
+
if lnk := parse_lnk_file(self.target, lnk, destination):
|
285
|
+
yield JumpListRecord(
|
286
|
+
type=jumplist.type,
|
287
|
+
application_name=jumplist.name,
|
288
|
+
application_id=jumplist.id,
|
289
|
+
**lnk._asdict(),
|
290
|
+
_user=user,
|
291
|
+
_target=self.target,
|
292
|
+
)
|
@@ -34,6 +34,89 @@ LnkRecord = TargetRecordDescriptor(
|
|
34
34
|
)
|
35
35
|
|
36
36
|
|
37
|
+
def parse_lnk_file(target: Target, lnk_file: Lnk, lnk_path: TargetPath) -> Iterator[LnkRecord]:
|
38
|
+
# we need to get the active codepage from the system to properly decode some values
|
39
|
+
codepage = target.codepage or "ascii"
|
40
|
+
|
41
|
+
lnk_net_name = lnk_device_name = None
|
42
|
+
|
43
|
+
if lnk_file.link_header:
|
44
|
+
lnk_name = lnk_file.stringdata.name_string.string if lnk_file.flag("has_name") else None
|
45
|
+
|
46
|
+
lnk_mtime = ts.from_unix(lnk_path.stat().st_mtime)
|
47
|
+
lnk_atime = ts.from_unix(lnk_path.stat().st_atime)
|
48
|
+
lnk_ctime = ts.from_unix(lnk_path.stat().st_ctime)
|
49
|
+
|
50
|
+
lnk_relativepath = (
|
51
|
+
target.fs.path(lnk_file.stringdata.relative_path.string) if lnk_file.flag("has_relative_path") else None
|
52
|
+
)
|
53
|
+
lnk_workdir = (
|
54
|
+
target.fs.path(lnk_file.stringdata.working_dir.string) if lnk_file.flag("has_working_dir") else None
|
55
|
+
)
|
56
|
+
lnk_iconlocation = (
|
57
|
+
target.fs.path(lnk_file.stringdata.icon_location.string) if lnk_file.flag("has_icon_location") else None
|
58
|
+
)
|
59
|
+
lnk_arguments = lnk_file.stringdata.command_line_arguments.string if lnk_file.flag("has_arguments") else None
|
60
|
+
local_base_path = (
|
61
|
+
lnk_file.linkinfo.local_base_path.decode(codepage)
|
62
|
+
if lnk_file.flag("has_link_info") and lnk_file.linkinfo.flag("volumeid_and_local_basepath")
|
63
|
+
else None
|
64
|
+
)
|
65
|
+
common_path_suffix = (
|
66
|
+
lnk_file.linkinfo.common_path_suffix.decode(codepage) if lnk_file.flag("has_link_info") else None
|
67
|
+
)
|
68
|
+
|
69
|
+
if local_base_path and common_path_suffix:
|
70
|
+
lnk_full_path = target.fs.path(local_base_path + common_path_suffix)
|
71
|
+
elif local_base_path and not common_path_suffix:
|
72
|
+
lnk_full_path = target.fs.path(local_base_path)
|
73
|
+
else:
|
74
|
+
lnk_full_path = None
|
75
|
+
|
76
|
+
if lnk_file.flag("has_link_info"):
|
77
|
+
if lnk_file.linkinfo.flag("common_network_relative_link_and_pathsuffix"):
|
78
|
+
lnk_net_name = (
|
79
|
+
lnk_file.linkinfo.common_network_relative_link.net_name.decode()
|
80
|
+
if lnk_file.linkinfo.common_network_relative_link.net_name
|
81
|
+
else None
|
82
|
+
)
|
83
|
+
lnk_device_name = (
|
84
|
+
lnk_file.linkinfo.common_network_relative_link.device_name.decode()
|
85
|
+
if lnk_file.linkinfo.common_network_relative_link.device_name
|
86
|
+
else None
|
87
|
+
)
|
88
|
+
try:
|
89
|
+
machine_id = lnk_file.extradata.TRACKER_PROPS.machine_id.decode(codepage).strip("\x00")
|
90
|
+
except AttributeError:
|
91
|
+
machine_id = None
|
92
|
+
|
93
|
+
target_mtime = ts.wintimestamp(lnk_file.link_header.write_time)
|
94
|
+
target_atime = ts.wintimestamp(lnk_file.link_header.access_time)
|
95
|
+
target_ctime = ts.wintimestamp(lnk_file.link_header.creation_time)
|
96
|
+
|
97
|
+
return LnkRecord(
|
98
|
+
lnk_path=lnk_path,
|
99
|
+
lnk_name=lnk_name,
|
100
|
+
lnk_mtime=lnk_mtime,
|
101
|
+
lnk_atime=lnk_atime,
|
102
|
+
lnk_ctime=lnk_ctime,
|
103
|
+
lnk_relativepath=lnk_relativepath,
|
104
|
+
lnk_workdir=lnk_workdir,
|
105
|
+
lnk_iconlocation=lnk_iconlocation,
|
106
|
+
lnk_arguments=lnk_arguments,
|
107
|
+
local_base_path=local_base_path,
|
108
|
+
common_path_suffix=common_path_suffix,
|
109
|
+
lnk_full_path=lnk_full_path,
|
110
|
+
lnk_net_name=lnk_net_name,
|
111
|
+
lnk_device_name=lnk_device_name,
|
112
|
+
machine_id=machine_id,
|
113
|
+
target_mtime=target_mtime,
|
114
|
+
target_atime=target_atime,
|
115
|
+
target_ctime=target_ctime,
|
116
|
+
_target=target,
|
117
|
+
)
|
118
|
+
|
119
|
+
|
37
120
|
class LnkPlugin(Plugin):
|
38
121
|
def __init__(self, target: Target) -> None:
|
39
122
|
super().__init__(target)
|
@@ -74,97 +157,9 @@ class LnkPlugin(Plugin):
|
|
74
157
|
target_ctime (datetime): Creation time of the target (linked) file.
|
75
158
|
"""
|
76
159
|
|
77
|
-
# we need to get the active codepage from the system to properly decode some values
|
78
|
-
codepage = self.target.codepage or "ascii"
|
79
|
-
|
80
160
|
for entry in self.lnk_entries(path):
|
81
161
|
lnk_file = Lnk(entry.open())
|
82
|
-
|
83
|
-
|
84
|
-
if lnk_file.link_header:
|
85
|
-
lnk_path = entry
|
86
|
-
lnk_name = lnk_file.stringdata.name_string.string if lnk_file.flag("has_name") else None
|
87
|
-
|
88
|
-
lnk_mtime = ts.from_unix(entry.stat().st_mtime)
|
89
|
-
lnk_atime = ts.from_unix(entry.stat().st_atime)
|
90
|
-
lnk_ctime = ts.from_unix(entry.stat().st_ctime)
|
91
|
-
|
92
|
-
lnk_relativepath = (
|
93
|
-
self.target.fs.path(lnk_file.stringdata.relative_path.string)
|
94
|
-
if lnk_file.flag("has_relative_path")
|
95
|
-
else None
|
96
|
-
)
|
97
|
-
lnk_workdir = (
|
98
|
-
self.target.fs.path(lnk_file.stringdata.working_dir.string)
|
99
|
-
if lnk_file.flag("has_working_dir")
|
100
|
-
else None
|
101
|
-
)
|
102
|
-
lnk_iconlocation = (
|
103
|
-
self.target.fs.path(lnk_file.stringdata.icon_location.string)
|
104
|
-
if lnk_file.flag("has_icon_location")
|
105
|
-
else None
|
106
|
-
)
|
107
|
-
lnk_arguments = (
|
108
|
-
lnk_file.stringdata.command_line_arguments.string if lnk_file.flag("has_arguments") else None
|
109
|
-
)
|
110
|
-
local_base_path = (
|
111
|
-
lnk_file.linkinfo.local_base_path.decode(codepage)
|
112
|
-
if lnk_file.flag("has_link_info") and lnk_file.linkinfo.flag("volumeid_and_local_basepath")
|
113
|
-
else None
|
114
|
-
)
|
115
|
-
common_path_suffix = (
|
116
|
-
lnk_file.linkinfo.common_path_suffix.decode(codepage) if lnk_file.flag("has_link_info") else None
|
117
|
-
)
|
118
|
-
|
119
|
-
if local_base_path and common_path_suffix:
|
120
|
-
lnk_full_path = self.target.fs.path(local_base_path + common_path_suffix)
|
121
|
-
elif local_base_path and not common_path_suffix:
|
122
|
-
lnk_full_path = self.target.fs.path(local_base_path)
|
123
|
-
else:
|
124
|
-
lnk_full_path = None
|
125
|
-
|
126
|
-
if lnk_file.flag("has_link_info"):
|
127
|
-
if lnk_file.linkinfo.flag("common_network_relative_link_and_pathsuffix"):
|
128
|
-
lnk_net_name = (
|
129
|
-
lnk_file.linkinfo.common_network_relative_link.net_name.decode()
|
130
|
-
if lnk_file.linkinfo.common_network_relative_link.net_name
|
131
|
-
else None
|
132
|
-
)
|
133
|
-
lnk_device_name = (
|
134
|
-
lnk_file.linkinfo.common_network_relative_link.device_name.decode()
|
135
|
-
if lnk_file.linkinfo.common_network_relative_link.device_name
|
136
|
-
else None
|
137
|
-
)
|
138
|
-
try:
|
139
|
-
machine_id = lnk_file.extradata.TRACKER_PROPS.machine_id.decode(codepage).strip("\x00")
|
140
|
-
except AttributeError:
|
141
|
-
machine_id = None
|
142
|
-
|
143
|
-
target_mtime = ts.wintimestamp(lnk_file.link_header.write_time)
|
144
|
-
target_atime = ts.wintimestamp(lnk_file.link_header.access_time)
|
145
|
-
target_ctime = ts.wintimestamp(lnk_file.link_header.creation_time)
|
146
|
-
|
147
|
-
yield LnkRecord(
|
148
|
-
lnk_path=lnk_path,
|
149
|
-
lnk_name=lnk_name,
|
150
|
-
lnk_mtime=lnk_mtime,
|
151
|
-
lnk_atime=lnk_atime,
|
152
|
-
lnk_ctime=lnk_ctime,
|
153
|
-
lnk_relativepath=lnk_relativepath,
|
154
|
-
lnk_workdir=lnk_workdir,
|
155
|
-
lnk_iconlocation=lnk_iconlocation,
|
156
|
-
lnk_arguments=lnk_arguments,
|
157
|
-
local_base_path=local_base_path,
|
158
|
-
common_path_suffix=common_path_suffix,
|
159
|
-
lnk_full_path=lnk_full_path,
|
160
|
-
lnk_net_name=lnk_net_name,
|
161
|
-
lnk_device_name=lnk_device_name,
|
162
|
-
machine_id=machine_id,
|
163
|
-
target_mtime=target_mtime,
|
164
|
-
target_atime=target_atime,
|
165
|
-
target_ctime=target_ctime,
|
166
|
-
_target=self.target,
|
167
|
-
)
|
162
|
+
yield parse_lnk_file(self.target, lnk_file, entry)
|
168
163
|
|
169
164
|
def lnk_entries(self, path: Optional[str] = None) -> Iterator[TargetPath]:
|
170
165
|
if path:
|