Skip to content

Commit b51fd8c

Browse files
authored
Merge branch 'main' into feature/rewrite-nginx-plugin
2 parents 1e549b6 + bb9a488 commit b51fd8c

File tree

18 files changed

+683
-34
lines changed

18 files changed

+683
-34
lines changed

dissect/target/filesystem.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1168,10 +1168,10 @@ def makedirs(self, path: str) -> VirtualDirectory:
11681168

11691169
return directory
11701170

1171-
def map_fs(self, vfspath: str, fs: Filesystem) -> None:
1171+
def map_fs(self, vfspath: str, fs: Filesystem, base: str = "/") -> None:
11721172
"""Mount a dissect filesystem to a directory in the VFS"""
11731173
directory = self.makedirs(vfspath)
1174-
directory.top = fs.get("/")
1174+
directory.top = fs.get(base)
11751175

11761176
mount = map_fs
11771177

dissect/target/helpers/configutil.py

Lines changed: 29 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from configparser import ConfigParser, MissingSectionHeaderError
1010
from dataclasses import dataclass
1111
from fnmatch import fnmatch
12+
from pathlib import Path
1213
from types import TracebackType
1314
from typing import (
1415
Any,
@@ -18,9 +19,7 @@
1819
Iterator,
1920
KeysView,
2021
Literal,
21-
Optional,
2222
TextIO,
23-
Union,
2423
)
2524

2625
from defusedxml import ElementTree
@@ -107,7 +106,7 @@ class ConfigurationParser:
107106

108107
def __init__(
109108
self,
110-
collapse: Union[bool, Iterable[str]] = False,
109+
collapse: bool | Iterable[str] = False,
111110
collapse_inverse: bool = False,
112111
separator: tuple[str] = ("=",),
113112
comment_prefixes: tuple[str] = (";", "#"),
@@ -120,7 +119,7 @@ def __init__(
120119
self.comment_prefixes = comment_prefixes
121120
self.parsed_data = {}
122121

123-
def __getitem__(self, item: Any) -> Union[dict, str]:
122+
def __getitem__(self, item: Any) -> dict | str:
124123
return self.parsed_data[item]
125124

126125
def __contains__(self, item: str) -> bool:
@@ -157,7 +156,7 @@ def parse_file(self, fh: TextIO) -> None:
157156
"""
158157
raise NotImplementedError()
159158

160-
def get(self, item: str, default: Optional[Any] = None) -> Any:
159+
def get(self, item: str, default: Any | None = None) -> Any:
161160
return self.parsed_data.get(item, default)
162161

163162
def read_file(self, fh: TextIO | io.BytesIO) -> None:
@@ -388,7 +387,7 @@ class ListUnwrapper:
388387
"""Provides utility functions to unwrap dictionary objects out of lists."""
389388

390389
@staticmethod
391-
def unwrap(data: Union[dict, list]) -> Union[dict, list]:
390+
def unwrap(data: dict | list) -> dict | list:
392391
"""Transforms a list with dictionaries to a dictionary.
393392
394393
The order of the list is preserved. If no dictionary is found, the list remains untouched:
@@ -409,7 +408,7 @@ def unwrap(data: Union[dict, list]) -> Union[dict, list]:
409408
return ListUnwrapper._unwrap_dict(orig)
410409

411410
@staticmethod
412-
def _unwrap_dict(data: Union[dict, list]) -> Union[dict, list]:
411+
def _unwrap_dict(data: dict | list) -> dict | list:
413412
"""Looks for dictionaries and unwraps its values."""
414413

415414
if not isinstance(data, dict):
@@ -425,7 +424,7 @@ def _unwrap_dict(data: Union[dict, list]) -> Union[dict, list]:
425424
return root
426425

427426
@staticmethod
428-
def _unwrap_dict_list(data: Union[dict, list]) -> Union[dict, list]:
427+
def _unwrap_dict_list(data: dict | list) -> dict | list:
429428
"""Unwraps a list containing dictionaries."""
430429
if not isinstance(data, list) or not any(isinstance(obj, dict) for obj in data):
431430
return data
@@ -559,9 +558,9 @@ def __enter__(self) -> ScopeManager:
559558

560559
def __exit__(
561560
self,
562-
type: Optional[type[BaseException]],
563-
value: Optional[BaseException],
564-
traceback: Optional[TracebackType],
561+
type: type[BaseException] | None,
562+
value: BaseException | None,
563+
traceback: TracebackType | None,
565564
) -> None:
566565
self.clean()
567566

@@ -636,7 +635,7 @@ def _change_scope(
636635
manager: ScopeManager,
637636
line: str,
638637
key: str,
639-
next_line: Optional[str] = None,
638+
next_line: str | None = None,
640639
) -> bool:
641640
"""A function to check whether to create a new scope, or go back to a previous one.
642641
@@ -722,7 +721,7 @@ def _change_scope(
722721
manager: ScopeManager,
723722
line: str,
724723
key: str,
725-
next_line: Optional[str] = None,
724+
next_line: str | None = None,
726725
) -> bool:
727726
scope_char = ("[", "]")
728727
changed = False
@@ -785,22 +784,22 @@ def _update_continued_values(self, func: Callable, key, values: list[str]) -> tu
785784

786785
@dataclass(frozen=True)
787786
class ParserOptions:
788-
collapse: Optional[Union[bool, set]] = None
789-
collapse_inverse: Optional[bool] = None
790-
separator: Optional[tuple[str]] = None
791-
comment_prefixes: Optional[tuple[str]] = None
787+
collapse: bool | set | None = None
788+
collapse_inverse: bool | None = None
789+
separator: tuple[str] | None = None
790+
comment_prefixes: tuple[str] | None = None
792791

793792

794793
@dataclass(frozen=True)
795794
class ParserConfig:
796795
parser: type[ConfigurationParser] = Default
797-
collapse: Optional[Union[bool, set]] = None
798-
collapse_inverse: Optional[bool] = None
799-
separator: Optional[tuple[str]] = None
800-
comment_prefixes: Optional[tuple[str]] = None
801-
fields: Optional[tuple[str]] = None
796+
collapse: bool | set | None = None
797+
collapse_inverse: bool | None = None
798+
separator: tuple[str] | None = None
799+
comment_prefixes: tuple[str] | None = None
800+
fields: tuple[str] | None = None
802801

803-
def create_parser(self, options: Optional[ParserOptions] = None) -> ConfigurationParser:
802+
def create_parser(self, options: ParserOptions | None = None) -> ConfigurationParser:
804803
kwargs = {}
805804

806805
for field_name in ["collapse", "collapse_inverse", "separator", "comment_prefixes", "fields"]:
@@ -890,7 +889,7 @@ def create_parser(self, options: Optional[ParserOptions] = None) -> Configuratio
890889
}
891890

892891

893-
def parse(path: Union[FilesystemEntry, TargetPath], hint: Optional[str] = None, *args, **kwargs) -> ConfigurationParser:
892+
def parse(path: FilesystemEntry | TargetPath | Path, hint: str | None = None, *args, **kwargs) -> ConfigurationParser:
894893
"""Parses the content of an ``path`` or ``entry`` to a dictionary.
895894
896895
Args:
@@ -909,7 +908,7 @@ def parse(path: Union[FilesystemEntry, TargetPath], hint: Optional[str] = None,
909908
if isinstance(path, TargetPath):
910909
entry = path.get()
911910

912-
if not entry.is_file(follow_symlinks=True):
911+
if not isinstance(entry, Path) and not entry.is_file(follow_symlinks=True):
913912
raise FileNotFoundError(f"Could not parse {path} as a dictionary.")
914913

915914
options = ParserOptions(*args, **kwargs)
@@ -918,14 +917,14 @@ def parse(path: Union[FilesystemEntry, TargetPath], hint: Optional[str] = None,
918917

919918

920919
def parse_config(
921-
entry: FilesystemEntry,
922-
hint: Optional[str] = None,
923-
options: Optional[ParserOptions] = None,
920+
entry: FilesystemEntry | Path,
921+
hint: str | None = None,
922+
options: ParserOptions | None = None,
924923
) -> ConfigurationParser:
925924
parser_type = _select_parser(entry, hint)
926925

927926
parser = parser_type.create_parser(options)
928-
with entry.open() as fh:
927+
with entry.open("rb") if isinstance(entry, Path) else entry.open() as fh:
929928
if not isinstance(parser, Bin):
930929
open_file = io.TextIOWrapper(fh, encoding="utf-8")
931930
else:
@@ -935,7 +934,7 @@ def parse_config(
935934
return parser
936935

937936

938-
def _select_parser(entry: FilesystemEntry, hint: Optional[str] = None) -> ParserConfig:
937+
def _select_parser(entry: FilesystemEntry, hint: str | None = None) -> ParserConfig:
939938
if hint and (parser_type := CONFIG_MAP.get(hint)):
940939
return parser_type
941940

dissect/target/helpers/utils.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,15 +149,29 @@ def year_rollover_helper(
149149
# We have to append the current_year to strptime instead of adding it using replace later.
150150
# This prevents DeprecationWarnings on cpython >= 3.13 and Exceptions on cpython >= 3.15.
151151
# See https://github.com/python/cpython/issues/70647 and https://github.com/python/cpython/pull/117107.
152-
compare_ts = datetime.strptime(f"{timestamp.group(0)};1900", f"{ts_format};%Y")
152+
# Use 1904 instead of 1900 to include leap days (29 Feb).
153+
try:
154+
compare_ts = datetime.strptime(f"{timestamp.group(0)};1904", f"{ts_format};%Y")
155+
except ValueError as e:
156+
log.warning("Unable to create comparison timestamp for %r in line %r: %s", timestamp.group(0), line, e)
157+
log.debug("", exc_info=e)
158+
continue
159+
153160
if last_seen_month and compare_ts.month > last_seen_month:
154161
current_year -= 1
155162
last_seen_month = compare_ts.month
156163

157164
try:
158165
relative_ts = datetime.strptime(f"{timestamp.group(0)};{current_year}", f"{ts_format};%Y")
159166
except ValueError as e:
160-
log.warning("Timestamp '%s' does not match format '%s', skipping line.", timestamp.group(0), ts_format)
167+
log.warning(
168+
"Timestamp '%s;%s' does not match format '%s;%%Y', skipping line %r: %s",
169+
timestamp.group(0),
170+
current_year,
171+
ts_format,
172+
line,
173+
e,
174+
)
161175
log.debug("", exc_info=e)
162176
continue
163177

dissect/target/loader.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ def open(item: str | Path, *args, **kwargs) -> Loader:
219219
register("tanium", "TaniumLoader")
220220
register("itunes", "ITunesLoader")
221221
register("ab", "AndroidBackupLoader")
222+
register("cellebrite", "CellebriteLoader")
222223
register("target", "TargetLoader")
223224
register("log", "LogLoader")
224225
# Disabling ResLoader because of DIS-536

0 commit comments

Comments
 (0)