acquire 3.5.dev8__tar.gz → 3.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {acquire-3.5.dev8/acquire.egg-info → acquire-3.6}/PKG-INFO +9 -3
- {acquire-3.5.dev8 → acquire-3.6}/README.md +8 -2
- {acquire-3.5.dev8 → acquire-3.6}/acquire/acquire.py +109 -10
- {acquire-3.5.dev8 → acquire-3.6}/acquire/collector.py +9 -3
- {acquire-3.5.dev8 → acquire-3.6}/acquire/utils.py +36 -1
- acquire-3.6/acquire/version.py +4 -0
- {acquire-3.5.dev8 → acquire-3.6/acquire.egg-info}/PKG-INFO +9 -3
- {acquire-3.5.dev8 → acquire-3.6}/acquire.egg-info/SOURCES.txt +5 -1
- {acquire-3.5.dev8 → acquire-3.6}/pyproject.toml +0 -1
- acquire-3.6/tests/conftest.py +30 -0
- acquire-3.6/tests/docs/Makefile +24 -0
- acquire-3.6/tests/docs/conf.py +34 -0
- acquire-3.6/tests/docs/index.rst +8 -0
- {acquire-3.5.dev8 → acquire-3.6}/tests/test_collector.py +72 -34
- {acquire-3.5.dev8 → acquire-3.6}/tests/test_utils.py +90 -0
- {acquire-3.5.dev8 → acquire-3.6}/tox.ini +21 -0
- acquire-3.5.dev8/acquire/version.py +0 -4
- {acquire-3.5.dev8 → acquire-3.6}/COPYRIGHT +0 -0
- {acquire-3.5.dev8 → acquire-3.6}/LICENSE +0 -0
- {acquire-3.5.dev8 → acquire-3.6}/MANIFEST.in +0 -0
- {acquire-3.5.dev8 → acquire-3.6}/acquire/__init__.py +0 -0
- {acquire-3.5.dev8 → acquire-3.6}/acquire/crypt.py +0 -0
- {acquire-3.5.dev8 → acquire-3.6}/acquire/dynamic/__init__.py +0 -0
- {acquire-3.5.dev8 → acquire-3.6}/acquire/dynamic/windows/__init__.py +0 -0
- {acquire-3.5.dev8 → acquire-3.6}/acquire/dynamic/windows/collect.py +0 -0
- {acquire-3.5.dev8 → acquire-3.6}/acquire/dynamic/windows/exceptions.py +0 -0
- {acquire-3.5.dev8 → acquire-3.6}/acquire/dynamic/windows/handles.py +0 -0
- {acquire-3.5.dev8 → acquire-3.6}/acquire/dynamic/windows/named_objects.py +0 -0
- {acquire-3.5.dev8 → acquire-3.6}/acquire/dynamic/windows/ntdll.py +0 -0
- {acquire-3.5.dev8 → acquire-3.6}/acquire/dynamic/windows/types.py +0 -0
- {acquire-3.5.dev8 → acquire-3.6}/acquire/esxi.py +0 -0
- {acquire-3.5.dev8 → acquire-3.6}/acquire/hashes.py +0 -0
- {acquire-3.5.dev8 → acquire-3.6}/acquire/log.py +0 -0
- {acquire-3.5.dev8 → acquire-3.6}/acquire/outputs/__init__.py +0 -0
- {acquire-3.5.dev8 → acquire-3.6}/acquire/outputs/base.py +0 -0
- {acquire-3.5.dev8 → acquire-3.6}/acquire/outputs/dir.py +0 -0
- {acquire-3.5.dev8 → acquire-3.6}/acquire/outputs/tar.py +0 -0
- {acquire-3.5.dev8 → acquire-3.6}/acquire/tools/__init__.py +0 -0
- {acquire-3.5.dev8 → acquire-3.6}/acquire/tools/decrypter.py +0 -0
- {acquire-3.5.dev8 → acquire-3.6}/acquire/uploaders/__init__.py +0 -0
- {acquire-3.5.dev8 → acquire-3.6}/acquire/uploaders/minio.py +0 -0
- {acquire-3.5.dev8 → acquire-3.6}/acquire/uploaders/plugin.py +0 -0
- {acquire-3.5.dev8 → acquire-3.6}/acquire/uploaders/plugin_registry.py +0 -0
- {acquire-3.5.dev8 → acquire-3.6}/acquire.egg-info/dependency_links.txt +0 -0
- {acquire-3.5.dev8 → acquire-3.6}/acquire.egg-info/entry_points.txt +0 -0
- {acquire-3.5.dev8 → acquire-3.6}/acquire.egg-info/requires.txt +0 -0
- {acquire-3.5.dev8 → acquire-3.6}/acquire.egg-info/top_level.txt +0 -0
- {acquire-3.5.dev8 → acquire-3.6}/setup.cfg +0 -0
- {acquire-3.5.dev8 → acquire-3.6}/tests/__init__.py +0 -0
- {acquire-3.5.dev8 → acquire-3.6}/tests/test_acquire_command.py +0 -0
- {acquire-3.5.dev8 → acquire-3.6}/tests/test_esxi_memory.py +0 -0
- {acquire-3.5.dev8 → acquire-3.6}/tests/test_file_sorting.py +0 -0
- {acquire-3.5.dev8 → acquire-3.6}/tests/test_minio_uploader.py +0 -0
- {acquire-3.5.dev8 → acquire-3.6}/tests/test_plugin.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: acquire
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.6
|
|
4
4
|
Summary: A tool to quickly gather forensic artifacts from disk images or a live system into a lightweight container
|
|
5
5
|
Author-email: Dissect Team <dissect@fox-it.com>
|
|
6
6
|
License: Affero General Public License v3
|
|
@@ -36,6 +36,12 @@ However, there are some options available to use the operating system as a fallb
|
|
|
36
36
|
|
|
37
37
|
For more information, please see [the documentation](https://docs.dissect.tools/en/latest/projects/acquire/index.html).
|
|
38
38
|
|
|
39
|
+
## Requirements
|
|
40
|
+
|
|
41
|
+
This project is part of the Dissect framework and requires Python.
|
|
42
|
+
|
|
43
|
+
Information on the supported Python versions can be found in the Getting Started section of [the documentation](https://docs.dissect.tools/en/latest/index.html#getting-started).
|
|
44
|
+
|
|
39
45
|
## Installation
|
|
40
46
|
|
|
41
47
|
`acquire` is available on [PyPI](https://pypi.org/project/acquire/).
|
|
@@ -63,12 +69,12 @@ tox
|
|
|
63
69
|
```
|
|
64
70
|
|
|
65
71
|
For a more elaborate explanation on how to build and test the project, please see [the
|
|
66
|
-
documentation](https://docs.dissect.tools/en/latest/contributing/
|
|
72
|
+
documentation](https://docs.dissect.tools/en/latest/contributing/tooling.html).
|
|
67
73
|
|
|
68
74
|
## Contributing
|
|
69
75
|
|
|
70
76
|
The Dissect project encourages any contribution to the codebase. To make your contribution fit into the project, please
|
|
71
|
-
refer to [the
|
|
77
|
+
refer to [the development guide](https://docs.dissect.tools/en/latest/contributing/developing.html).
|
|
72
78
|
|
|
73
79
|
## Copyright and license
|
|
74
80
|
|
|
@@ -20,6 +20,12 @@ However, there are some options available to use the operating system as a fallb
|
|
|
20
20
|
|
|
21
21
|
For more information, please see [the documentation](https://docs.dissect.tools/en/latest/projects/acquire/index.html).
|
|
22
22
|
|
|
23
|
+
## Requirements
|
|
24
|
+
|
|
25
|
+
This project is part of the Dissect framework and requires Python.
|
|
26
|
+
|
|
27
|
+
Information on the supported Python versions can be found in the Getting Started section of [the documentation](https://docs.dissect.tools/en/latest/index.html#getting-started).
|
|
28
|
+
|
|
23
29
|
## Installation
|
|
24
30
|
|
|
25
31
|
`acquire` is available on [PyPI](https://pypi.org/project/acquire/).
|
|
@@ -47,12 +53,12 @@ tox
|
|
|
47
53
|
```
|
|
48
54
|
|
|
49
55
|
For a more elaborate explanation on how to build and test the project, please see [the
|
|
50
|
-
documentation](https://docs.dissect.tools/en/latest/contributing/
|
|
56
|
+
documentation](https://docs.dissect.tools/en/latest/contributing/tooling.html).
|
|
51
57
|
|
|
52
58
|
## Contributing
|
|
53
59
|
|
|
54
60
|
The Dissect project encourages any contribution to the codebase. To make your contribution fit into the project, please
|
|
55
|
-
refer to [the
|
|
61
|
+
refer to [the development guide](https://docs.dissect.tools/en/latest/contributing/developing.html).
|
|
56
62
|
|
|
57
63
|
## Copyright and license
|
|
58
64
|
|
|
@@ -48,6 +48,7 @@ from acquire.utils import (
|
|
|
48
48
|
get_utc_now,
|
|
49
49
|
get_utc_now_str,
|
|
50
50
|
is_user_admin,
|
|
51
|
+
normalize_path,
|
|
51
52
|
parse_acquire_args,
|
|
52
53
|
persist_execution_report,
|
|
53
54
|
)
|
|
@@ -313,11 +314,10 @@ class NTFS(Module):
|
|
|
313
314
|
|
|
314
315
|
@classmethod
|
|
315
316
|
def collect_usnjrnl(cls, collector: Collector, fs, name: str) -> None:
|
|
316
|
-
usnjrnl_path = fs.path("$Extend/$Usnjrnl")
|
|
317
|
-
|
|
318
317
|
try:
|
|
318
|
+
usnjrnl_path = fs.path("$Extend/$Usnjrnl:$J")
|
|
319
319
|
entry = usnjrnl_path.get()
|
|
320
|
-
journal = entry.
|
|
320
|
+
journal = entry.open()
|
|
321
321
|
|
|
322
322
|
i = 0
|
|
323
323
|
while journal.runlist[i][0] is None:
|
|
@@ -344,10 +344,10 @@ class NTFS(Module):
|
|
|
344
344
|
|
|
345
345
|
@classmethod
|
|
346
346
|
def collect_ntfs_secure(cls, collector: Collector, fs, name: str) -> None:
|
|
347
|
-
secure_path = fs.path("$Secure")
|
|
348
347
|
try:
|
|
348
|
+
secure_path = fs.path("$Secure:$SDS")
|
|
349
349
|
entry = secure_path.get()
|
|
350
|
-
sds = entry.
|
|
350
|
+
sds = entry.open()
|
|
351
351
|
collector.output.write(
|
|
352
352
|
f"{collector.base}/{name}/$Secure:$SDS",
|
|
353
353
|
sds,
|
|
@@ -826,6 +826,7 @@ class Misc(Module):
|
|
|
826
826
|
("dir", "sysvol/Windows/System32/WDI/LogFiles/StartupInfo"),
|
|
827
827
|
("dir", "sysvol/windows/sysvol/domain/policies/"),
|
|
828
828
|
("dir", "sysvol/windows/system32/GroupPolicy/DataStore/"),
|
|
829
|
+
("dir", "sysvol/ProgramData/Microsoft/Group Policy/History/"),
|
|
829
830
|
("glob", "sysvol/Windows/System32/LogFiles/SUM/*.mdb"),
|
|
830
831
|
]
|
|
831
832
|
|
|
@@ -1101,6 +1102,51 @@ class History(Module):
|
|
|
1101
1102
|
("glob", "/Users/*/Library/Application Support/Chromium/*/Archived History"),
|
|
1102
1103
|
("glob", "/Users/*/Library/Application Support/Chromium/*/Last Session"),
|
|
1103
1104
|
("glob", "/Users/*/Library/Application Support/Chromium/*/Last Tabs"),
|
|
1105
|
+
# Chrome - RHEL/Ubuntu - DNF
|
|
1106
|
+
("glob", ".config/google-chrome/*/Bookmarks", from_user_home),
|
|
1107
|
+
("glob", ".config/google-chrome/*/Favicons", from_user_home),
|
|
1108
|
+
("glob", ".config/google-chrome/*/History", from_user_home),
|
|
1109
|
+
("glob", ".config/google-chrome/*/Login Data", from_user_home),
|
|
1110
|
+
("glob", ".config/google-chrome/*/Login Data For Account", from_user_home),
|
|
1111
|
+
("glob", ".config/google-chrome/*/Shortcuts", from_user_home),
|
|
1112
|
+
("glob", ".config/google-chrome/*/Top Sites", from_user_home),
|
|
1113
|
+
("glob", ".config/google-chrome/*/Web Data", from_user_home),
|
|
1114
|
+
# Chrome - RHEL/Ubuntu - Flatpak
|
|
1115
|
+
("glob", ".var/app/com.google.Chrome/config/google-chrome/*/Bookmarks", from_user_home),
|
|
1116
|
+
("glob", ".var/app/com.google.Chrome/config/google-chrome/*/Favicons", from_user_home),
|
|
1117
|
+
("glob", ".var/app/com.google.Chrome/config/google-chrome/*/History", from_user_home),
|
|
1118
|
+
("glob", ".var/app/com.google.Chrome/config/google-chrome/*/Login Data", from_user_home),
|
|
1119
|
+
("glob", ".var/app/com.google.Chrome/config/google-chrome/*/Login Data For Account", from_user_home),
|
|
1120
|
+
("glob", ".var/app/com.google.Chrome/config/google-chrome/*/Shortcuts", from_user_home),
|
|
1121
|
+
("glob", ".var/app/com.google.Chrome/config/google-chrome/*/Top Sites", from_user_home),
|
|
1122
|
+
("glob", ".var/app/com.google.Chrome/config/google-chrome/*/Web Data", from_user_home),
|
|
1123
|
+
# Chromium - RHEL/Ubuntu - DNF/apt
|
|
1124
|
+
("glob", ".config/chromium/*/Bookmarks", from_user_home),
|
|
1125
|
+
("glob", ".config/chromium/*/Favicons", from_user_home),
|
|
1126
|
+
("glob", ".config/chromium/*/History", from_user_home),
|
|
1127
|
+
("glob", ".config/chromium/*/Login Data", from_user_home),
|
|
1128
|
+
("glob", ".config/chromium/*/Login Data For Account", from_user_home),
|
|
1129
|
+
("glob", ".config/chromium/*/Shortcuts", from_user_home),
|
|
1130
|
+
("glob", ".config/chromium/*/Top Sites", from_user_home),
|
|
1131
|
+
("glob", ".config/chromium/*/Web Data", from_user_home),
|
|
1132
|
+
# Chromium - RHEL/Ubuntu - Flatpak
|
|
1133
|
+
("glob", ".var/app/org.chromium.Chromium/config/chromium/*/Bookmarks", from_user_home),
|
|
1134
|
+
("glob", ".var/app/org.chromium.Chromium/config/chromium/*/Favicons", from_user_home),
|
|
1135
|
+
("glob", ".var/app/org.chromium.Chromium/config/chromium/*/History", from_user_home),
|
|
1136
|
+
("glob", ".var/app/org.chromium.Chromium/config/chromium/*/Login Data", from_user_home),
|
|
1137
|
+
("glob", ".var/app/org.chromium.Chromium/config/chromium/*/Login Data For Account", from_user_home),
|
|
1138
|
+
("glob", ".var/app/org.chromium.Chromium/config/chromium/*/Shortcuts", from_user_home),
|
|
1139
|
+
("glob", ".var/app/org.chromium.Chromium/config/chromium/*/Top Sites", from_user_home),
|
|
1140
|
+
("glob", ".var/app/org.chromium.Chromium/config/chromium/*/Web Data", from_user_home),
|
|
1141
|
+
# Chromium - RHEL/Ubuntu - snap
|
|
1142
|
+
("glob", "snap/chromium/common/chromium/*/Bookmarks", from_user_home),
|
|
1143
|
+
("glob", "snap/chromium/common/chromium/*/Favicons", from_user_home),
|
|
1144
|
+
("glob", "snap/chromium/common/chromium/*/History", from_user_home),
|
|
1145
|
+
("glob", "snap/chromium/common/chromium/*/Login Data", from_user_home),
|
|
1146
|
+
("glob", "snap/chromium/common/chromium/*/Login Data For Account", from_user_home),
|
|
1147
|
+
("glob", "snap/chromium/common/chromium/*/Shortcuts", from_user_home),
|
|
1148
|
+
("glob", "snap/chromium/common/chromium/*/Top Sites", from_user_home),
|
|
1149
|
+
("glob", "snap/chromium/common/chromium/*/Web Data", from_user_home),
|
|
1104
1150
|
# Edge
|
|
1105
1151
|
("glob", "AppData/Local/Microsoft/Edge/User Data/*/Bookmarks", from_user_home),
|
|
1106
1152
|
("glob", "AppData/Local/Microsoft/Edge/User Data/*/Extension Cookies", from_user_home),
|
|
@@ -1165,12 +1211,37 @@ class History(Module):
|
|
|
1165
1211
|
("glob", "/Users/*/Library/Application Support/Microsoft Edge/*/Shortcuts"),
|
|
1166
1212
|
("glob", "/Users/*/Library/Application Support/Microsoft Edge/*/Top Sites"),
|
|
1167
1213
|
("glob", "/Users/*/Library/Application Support/Microsoft Edge/*/Web Data"),
|
|
1168
|
-
#
|
|
1214
|
+
# Edge - RHEL/Ubuntu - DNF/apt
|
|
1215
|
+
("glob", ".config/microsoft-edge/*/Bookmarks", from_user_home),
|
|
1216
|
+
("glob", ".config/microsoft-edge/*/Favicons", from_user_home),
|
|
1217
|
+
("glob", ".config/microsoft-edge/*/History", from_user_home),
|
|
1218
|
+
("glob", ".config/microsoft-edge/*/Login Data", from_user_home),
|
|
1219
|
+
("glob", ".config/microsoft-edge/*/Login Data For Account", from_user_home),
|
|
1220
|
+
("glob", ".config/microsoft-edge/*/Shortcuts", from_user_home),
|
|
1221
|
+
("glob", ".config/microsoft-edge/*/Top Sites", from_user_home),
|
|
1222
|
+
("glob", ".config/microsoft-edge/*/Web Data", from_user_home),
|
|
1223
|
+
# Edge - RHEL/Ubuntu - Flatpak
|
|
1224
|
+
("glob", ".var/app/com.microsoft.Edge/config/microsoft-edge/*/Bookmarks", from_user_home),
|
|
1225
|
+
("glob", ".var/app/com.microsoft.Edge/config/microsoft-edge/*/Favicons", from_user_home),
|
|
1226
|
+
("glob", ".var/app/com.microsoft.Edge/config/microsoft-edge/*/History", from_user_home),
|
|
1227
|
+
("glob", ".var/app/com.microsoft.Edge/config/microsoft-edge/*/Login Data", from_user_home),
|
|
1228
|
+
("glob", ".var/app/com.microsoft.Edge/config/microsoft-edge/*/Login Data For Account", from_user_home),
|
|
1229
|
+
("glob", ".var/app/com.microsoft.Edge/config/microsoft-edge/*/Shortcuts", from_user_home),
|
|
1230
|
+
("glob", ".var/app/com.microsoft.Edge/config/microsoft-edge/*/Top Sites", from_user_home),
|
|
1231
|
+
("glob", ".var/app/com.microsoft.Edge/config/microsoft-edge/*/Web Data", from_user_home),
|
|
1232
|
+
# Firefox - Windows
|
|
1169
1233
|
("glob", "AppData/Local/Mozilla/Firefox/Profiles/*/*.sqlite*", from_user_home),
|
|
1170
1234
|
("glob", "AppData/Roaming/Mozilla/Firefox/Profiles/*/*.sqlite*", from_user_home),
|
|
1171
1235
|
("glob", "Application Data/Mozilla/Firefox/Profiles/*/*.sqlite*", from_user_home),
|
|
1236
|
+
# Firefox - macOS
|
|
1172
1237
|
("glob", "/Users/*/Library/Application Support/Firefox/Profiles/*/*.sqlite*"),
|
|
1173
|
-
#
|
|
1238
|
+
# Firefox - RHEL/Ubuntu - Flatpak
|
|
1239
|
+
("glob", ".var/app/org.mozilla.firefox/.mozilla/firefox/*/*.sqlite", from_user_home),
|
|
1240
|
+
# Firefox - RHEL/Ubuntu - DNF/apt
|
|
1241
|
+
("glob", ".mozilla/firefox/*/*.sqlite", from_user_home),
|
|
1242
|
+
# Firefox - RHEL/Ubuntu - snap
|
|
1243
|
+
("glob", "snap/firefox/common/.mozilla/firefox/*/*.sqlite", from_user_home),
|
|
1244
|
+
# Safari - macOS
|
|
1174
1245
|
("glob", "/Users/*/Library/Safari/Bookmarks.plist"),
|
|
1175
1246
|
("glob", "/Users/*/Library/Safari/Downloads.plist"),
|
|
1176
1247
|
("glob", "/Users/*/Library/Safari/Extensions/Extensions.plist"),
|
|
@@ -1551,17 +1622,34 @@ def print_volumes_overview(target):
|
|
|
1551
1622
|
log.info("")
|
|
1552
1623
|
|
|
1553
1624
|
|
|
1625
|
+
def print_acquire_warning(target: Target) -> None:
|
|
1626
|
+
if target.os != "windows":
|
|
1627
|
+
log.warning("========================================== WARNING ==========================================")
|
|
1628
|
+
log.warning("")
|
|
1629
|
+
log.warning(
|
|
1630
|
+
"The support for operating system '%s' is experimental. Some artifacts may not yet be included and some ",
|
|
1631
|
+
target.os,
|
|
1632
|
+
)
|
|
1633
|
+
log.warning("features may not work as expected. Please notify upstream for any missing artifacts or features.")
|
|
1634
|
+
log.warning("")
|
|
1635
|
+
log.warning("========================================== WARNING ==========================================")
|
|
1636
|
+
|
|
1637
|
+
|
|
1554
1638
|
def acquire_target(target: Target, args: argparse.Namespace, output_ts: Optional[str] = None):
|
|
1555
1639
|
output_ts = output_ts or get_utc_now_str()
|
|
1556
1640
|
if args.log_to_dir:
|
|
1557
1641
|
log_file = args.log_path.joinpath(format_output_name("Unknown", output_ts, "log"))
|
|
1642
|
+
# This will also rename the log file on disk, which was opened in main(), if the name is different
|
|
1558
1643
|
reconfigure_log_file(log, log_file, delay=True)
|
|
1559
1644
|
else:
|
|
1560
1645
|
log_file = args.log_path
|
|
1561
1646
|
|
|
1562
1647
|
files = []
|
|
1648
|
+
skip_list = set()
|
|
1563
1649
|
if log_file:
|
|
1564
1650
|
files.append(log_file)
|
|
1651
|
+
if target.path.name == "local":
|
|
1652
|
+
skip_list.add(normalize_path(target, log_file, resolve=True))
|
|
1565
1653
|
|
|
1566
1654
|
print_disks_overview(target)
|
|
1567
1655
|
print_volumes_overview(target)
|
|
@@ -1595,6 +1683,8 @@ def acquire_target(target: Target, args: argparse.Namespace, output_ts: Optional
|
|
|
1595
1683
|
if target.os == "windows":
|
|
1596
1684
|
mount_all_ntfs_filesystems(target)
|
|
1597
1685
|
|
|
1686
|
+
print_acquire_warning(target)
|
|
1687
|
+
|
|
1598
1688
|
modules_selected = {}
|
|
1599
1689
|
modules_successful = []
|
|
1600
1690
|
modules_failed = {}
|
|
@@ -1638,14 +1728,19 @@ def acquire_target(target: Target, args: argparse.Namespace, output_ts: Optional
|
|
|
1638
1728
|
# Prepare log file and output file names
|
|
1639
1729
|
if log_file_handler and args.log_to_dir:
|
|
1640
1730
|
log_file = format_output_name(target.name, output_ts, "log")
|
|
1731
|
+
# This will also rename the log file on disk, which was opened and written previously.
|
|
1641
1732
|
log_file_handler.set_filename(log_file)
|
|
1642
|
-
|
|
1733
|
+
log_path = Path(log_file_handler.baseFilename).resolve()
|
|
1734
|
+
log.info("Logging to file %s", log_path)
|
|
1643
1735
|
files = [log_file_handler.baseFilename]
|
|
1736
|
+
if target.path.name == "local":
|
|
1737
|
+
skip_list = {normalize_path(target, log_path, resolve=True)}
|
|
1644
1738
|
|
|
1645
1739
|
output_path = args.output
|
|
1646
1740
|
if output_path.is_dir():
|
|
1647
1741
|
output_dir = format_output_name(target.name, output_ts)
|
|
1648
|
-
output_path = output_path.joinpath(output_dir)
|
|
1742
|
+
output_path = output_path.joinpath(output_dir)
|
|
1743
|
+
output_path = output_path.resolve()
|
|
1649
1744
|
|
|
1650
1745
|
output = OUTPUTS[args.output_type](
|
|
1651
1746
|
output_path,
|
|
@@ -1654,15 +1749,19 @@ def acquire_target(target: Target, args: argparse.Namespace, output_ts: Optional
|
|
|
1654
1749
|
public_key=args.public_key,
|
|
1655
1750
|
)
|
|
1656
1751
|
files.append(output.path)
|
|
1752
|
+
if target.path.name == "local":
|
|
1753
|
+
skip_list.add(normalize_path(target, output.path, resolve=True))
|
|
1657
1754
|
|
|
1658
1755
|
log.info("Writing output to %s", output.path)
|
|
1756
|
+
if skip_list:
|
|
1757
|
+
log.info("Skipping own files: %s", ", ".join(skip_list))
|
|
1659
1758
|
log.info("")
|
|
1660
1759
|
|
|
1661
1760
|
dir_base = "fs"
|
|
1662
1761
|
if target.os != "windows":
|
|
1663
1762
|
dir_base = "fs/$rootfs$"
|
|
1664
1763
|
|
|
1665
|
-
with Collector(target, output, base=dir_base) as collector:
|
|
1764
|
+
with Collector(target, output, base=dir_base, skip_list=skip_list) as collector:
|
|
1666
1765
|
# Acquire specified files
|
|
1667
1766
|
if args.file or args.directory or args.glob:
|
|
1668
1767
|
log.info("*** Acquiring specified paths")
|
|
@@ -20,7 +20,7 @@ from dissect.target.exceptions import (
|
|
|
20
20
|
)
|
|
21
21
|
from dissect.target.helpers import fsutil
|
|
22
22
|
|
|
23
|
-
from acquire.utils import StrEnum, get_formatted_exception
|
|
23
|
+
from acquire.utils import StrEnum, get_formatted_exception, normalize_path
|
|
24
24
|
|
|
25
25
|
if TYPE_CHECKING:
|
|
26
26
|
from acquire.outputs.base import Output
|
|
@@ -179,10 +179,11 @@ class Collector:
|
|
|
179
179
|
METADATA_BASE = "$metadata$"
|
|
180
180
|
COMMAND_OUTPUT_BASE = f"{METADATA_BASE}/command-output"
|
|
181
181
|
|
|
182
|
-
def __init__(self, target: Target, output: Output, base="fs"):
|
|
182
|
+
def __init__(self, target: Target, output: Output, base: str = "fs", skip_list: Optional[set] = None):
|
|
183
183
|
self.target = target
|
|
184
184
|
self.output = output
|
|
185
185
|
self.base = base
|
|
186
|
+
self.skip_list = skip_list or set()
|
|
186
187
|
|
|
187
188
|
self.report = CollectionReport()
|
|
188
189
|
self.bound_module_name = None
|
|
@@ -358,7 +359,7 @@ class Collector:
|
|
|
358
359
|
glob_is_empty = True
|
|
359
360
|
for entry in self.target.fs.path("/").glob(pattern.lstrip("/")):
|
|
360
361
|
glob_is_empty = False
|
|
361
|
-
self.collect_path(entry)
|
|
362
|
+
self.collect_path(entry, module_name=module_name)
|
|
362
363
|
except Exception:
|
|
363
364
|
log.error("- Failed to collect glob %s", pattern, exc_info=True)
|
|
364
365
|
self.report.add_glob_failed(module_name, pattern)
|
|
@@ -383,6 +384,11 @@ class Collector:
|
|
|
383
384
|
if not isinstance(path, fsutil.TargetPath):
|
|
384
385
|
path = self.target.fs.path(path)
|
|
385
386
|
|
|
387
|
+
if self.skip_list and normalize_path(self.target, path) in self.skip_list:
|
|
388
|
+
self.report.add_path_failed(module_name, path)
|
|
389
|
+
log.error("- Skipping collection of %s, path is on the skip list", path)
|
|
390
|
+
return
|
|
391
|
+
|
|
386
392
|
try:
|
|
387
393
|
# If a path does not exist, is_dir(), is_file() and is_symlink() will return False (and not raise an
|
|
388
394
|
# exception), so we need to explicitly trigger an exception for this using path.get().
|
|
@@ -4,6 +4,8 @@ import datetime
|
|
|
4
4
|
import getpass
|
|
5
5
|
import json
|
|
6
6
|
import os
|
|
7
|
+
import pathlib
|
|
8
|
+
import re
|
|
7
9
|
import sys
|
|
8
10
|
import textwrap
|
|
9
11
|
import traceback
|
|
@@ -13,6 +15,7 @@ from pathlib import Path
|
|
|
13
15
|
from stat import S_IRGRP, S_IROTH, S_IRUSR
|
|
14
16
|
from typing import Any, Optional
|
|
15
17
|
|
|
18
|
+
from dissect.target import Target
|
|
16
19
|
from dissect.util.stream import AlignedStream
|
|
17
20
|
|
|
18
21
|
from acquire.outputs import OUTPUTS
|
|
@@ -63,7 +66,20 @@ class VolatileStream(AlignedStream):
|
|
|
63
66
|
return False
|
|
64
67
|
|
|
65
68
|
def _read(self, offset: int, length: int) -> bytes:
|
|
66
|
-
|
|
69
|
+
result = []
|
|
70
|
+
while length:
|
|
71
|
+
try:
|
|
72
|
+
buf = os.read(self.fd, min(length, self.size - offset))
|
|
73
|
+
except BlockingIOError:
|
|
74
|
+
break
|
|
75
|
+
|
|
76
|
+
if not buf:
|
|
77
|
+
break
|
|
78
|
+
|
|
79
|
+
result.append(buf)
|
|
80
|
+
offset += len(buf)
|
|
81
|
+
length -= len(buf)
|
|
82
|
+
return b"".join(result)
|
|
67
83
|
|
|
68
84
|
|
|
69
85
|
class StrEnum(str, Enum):
|
|
@@ -379,3 +395,22 @@ def format_output_name(prefix: str, postfix: Optional[str] = None, ext: Optional
|
|
|
379
395
|
def persist_execution_report(path: Path, report_data: dict) -> Path:
|
|
380
396
|
with open(path, "w") as f:
|
|
381
397
|
f.write(json.dumps(report_data, sort_keys=True, indent=4))
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
SYSVOL_SUBST = re.compile(r"^(/\?\?/)?[cC]:")
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def normalize_path(target: Target, path: pathlib.Path, resolve: bool = False) -> str:
|
|
404
|
+
if resolve:
|
|
405
|
+
path = path.resolve()
|
|
406
|
+
|
|
407
|
+
path = path.as_posix()
|
|
408
|
+
|
|
409
|
+
if not target.fs.case_sensitive:
|
|
410
|
+
path = path.lower()
|
|
411
|
+
|
|
412
|
+
if target.os == "windows":
|
|
413
|
+
# As dissect.target always maps c: onto sysvol, we can do this substitution here.
|
|
414
|
+
path = SYSVOL_SUBST.sub("sysvol", path)
|
|
415
|
+
|
|
416
|
+
return path
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: acquire
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.6
|
|
4
4
|
Summary: A tool to quickly gather forensic artifacts from disk images or a live system into a lightweight container
|
|
5
5
|
Author-email: Dissect Team <dissect@fox-it.com>
|
|
6
6
|
License: Affero General Public License v3
|
|
@@ -36,6 +36,12 @@ However, there are some options available to use the operating system as a fallb
|
|
|
36
36
|
|
|
37
37
|
For more information, please see [the documentation](https://docs.dissect.tools/en/latest/projects/acquire/index.html).
|
|
38
38
|
|
|
39
|
+
## Requirements
|
|
40
|
+
|
|
41
|
+
This project is part of the Dissect framework and requires Python.
|
|
42
|
+
|
|
43
|
+
Information on the supported Python versions can be found in the Getting Started section of [the documentation](https://docs.dissect.tools/en/latest/index.html#getting-started).
|
|
44
|
+
|
|
39
45
|
## Installation
|
|
40
46
|
|
|
41
47
|
`acquire` is available on [PyPI](https://pypi.org/project/acquire/).
|
|
@@ -63,12 +69,12 @@ tox
|
|
|
63
69
|
```
|
|
64
70
|
|
|
65
71
|
For a more elaborate explanation on how to build and test the project, please see [the
|
|
66
|
-
documentation](https://docs.dissect.tools/en/latest/contributing/
|
|
72
|
+
documentation](https://docs.dissect.tools/en/latest/contributing/tooling.html).
|
|
67
73
|
|
|
68
74
|
## Contributing
|
|
69
75
|
|
|
70
76
|
The Dissect project encourages any contribution to the codebase. To make your contribution fit into the project, please
|
|
71
|
-
refer to [the
|
|
77
|
+
refer to [the development guide](https://docs.dissect.tools/en/latest/contributing/developing.html).
|
|
72
78
|
|
|
73
79
|
## Copyright and license
|
|
74
80
|
|
|
@@ -38,10 +38,14 @@ acquire/uploaders/minio.py
|
|
|
38
38
|
acquire/uploaders/plugin.py
|
|
39
39
|
acquire/uploaders/plugin_registry.py
|
|
40
40
|
tests/__init__.py
|
|
41
|
+
tests/conftest.py
|
|
41
42
|
tests/test_acquire_command.py
|
|
42
43
|
tests/test_collector.py
|
|
43
44
|
tests/test_esxi_memory.py
|
|
44
45
|
tests/test_file_sorting.py
|
|
45
46
|
tests/test_minio_uploader.py
|
|
46
47
|
tests/test_plugin.py
|
|
47
|
-
tests/test_utils.py
|
|
48
|
+
tests/test_utils.py
|
|
49
|
+
tests/docs/Makefile
|
|
50
|
+
tests/docs/conf.py
|
|
51
|
+
tests/docs/index.rst
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from unittest.mock import Mock
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from dissect.target import Target
|
|
5
|
+
from dissect.target.filesystem import VirtualFile, VirtualFilesystem, VirtualSymlink
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@pytest.fixture
|
|
9
|
+
def mock_file() -> Mock:
|
|
10
|
+
return Mock()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest.fixture
|
|
14
|
+
def mock_fs(mock_file) -> VirtualFilesystem:
|
|
15
|
+
fs = VirtualFilesystem(case_sensitive=False)
|
|
16
|
+
fs.makedirs("/foo/bar")
|
|
17
|
+
fs.map_file_entry("/foo/bar/some-file", VirtualFile(fs, "some-file", mock_file))
|
|
18
|
+
fs.map_file_entry("/foo/bar/own-file", VirtualFile(fs, "own-file", mock_file))
|
|
19
|
+
fs.map_file_entry("/foo/bar/some-symlink", VirtualSymlink(fs, "some-symlink", "/foo/bar/some-file"))
|
|
20
|
+
fs.map_file_entry("/foo/own-symlink", VirtualSymlink(fs, "own-symlink", "/foo/bar/own-file"))
|
|
21
|
+
return fs
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@pytest.fixture
|
|
25
|
+
def mock_target(mock_fs) -> Target:
|
|
26
|
+
target = Target()
|
|
27
|
+
target.fs.mount("/", mock_fs)
|
|
28
|
+
target.filesystems.add(mock_fs)
|
|
29
|
+
target.os = "mock"
|
|
30
|
+
return target
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Minimal makefile for Sphinx documentation
|
|
2
|
+
#
|
|
3
|
+
|
|
4
|
+
# You can set these variables from the command line, and also
|
|
5
|
+
# from the environment for the first two.
|
|
6
|
+
SPHINXOPTS ?= -jauto
|
|
7
|
+
SPHINXBUILD ?= sphinx-build
|
|
8
|
+
SOURCEDIR = .
|
|
9
|
+
BUILDDIR = build
|
|
10
|
+
|
|
11
|
+
# Put it first so that "make" without argument is like "make help".
|
|
12
|
+
help:
|
|
13
|
+
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
|
14
|
+
|
|
15
|
+
.PHONY: clean help Makefile
|
|
16
|
+
|
|
17
|
+
clean: Makefile
|
|
18
|
+
rm -rf api
|
|
19
|
+
@$(SPHINXBUILD) -M clean "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
|
20
|
+
|
|
21
|
+
# Catch-all target: route all unknown targets to Sphinx using the new
|
|
22
|
+
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
|
23
|
+
%: Makefile
|
|
24
|
+
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
extensions = [
|
|
2
|
+
"autoapi.extension",
|
|
3
|
+
"sphinx.ext.autodoc",
|
|
4
|
+
"sphinx.ext.autosectionlabel",
|
|
5
|
+
"sphinx.ext.doctest",
|
|
6
|
+
"sphinx.ext.napoleon",
|
|
7
|
+
"sphinx_argparse_cli",
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
exclude_patterns = []
|
|
11
|
+
|
|
12
|
+
html_theme = "furo"
|
|
13
|
+
|
|
14
|
+
autoapi_type = "python"
|
|
15
|
+
autoapi_dirs = ["../../acquire/"]
|
|
16
|
+
autoapi_ignore = ["*tests*", "*.tox*", "*venv*", "*examples*"]
|
|
17
|
+
autoapi_python_use_implicit_namespaces = True
|
|
18
|
+
autoapi_add_toctree_entry = False
|
|
19
|
+
autoapi_root = "api"
|
|
20
|
+
autoapi_options = [
|
|
21
|
+
"members",
|
|
22
|
+
"undoc-members",
|
|
23
|
+
"show-inheritance",
|
|
24
|
+
"show-module-summary",
|
|
25
|
+
"special-members",
|
|
26
|
+
"imported-members",
|
|
27
|
+
]
|
|
28
|
+
autoapi_keep_files = True
|
|
29
|
+
autoapi_template_dir = "_templates/autoapi"
|
|
30
|
+
|
|
31
|
+
autodoc_typehints = "signature"
|
|
32
|
+
autodoc_member_order = "groupwise"
|
|
33
|
+
|
|
34
|
+
autosectionlabel_prefix_document = True
|
|
@@ -9,7 +9,7 @@ from dissect.target.exceptions import (
|
|
|
9
9
|
NotASymlinkError,
|
|
10
10
|
SymlinkRecursionError,
|
|
11
11
|
)
|
|
12
|
-
from dissect.target.filesystem import
|
|
12
|
+
from dissect.target.filesystem import VirtualFilesystem
|
|
13
13
|
|
|
14
14
|
from acquire.collector import Collector
|
|
15
15
|
|
|
@@ -36,28 +36,6 @@ def test_collector() -> None:
|
|
|
36
36
|
assert not mock_log.info.call_args.args[0] == "- Collecting file %s: Skipped (DEDUP)"
|
|
37
37
|
|
|
38
38
|
|
|
39
|
-
@pytest.fixture
|
|
40
|
-
def mock_file() -> Mock:
|
|
41
|
-
return Mock()
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
@pytest.fixture
|
|
45
|
-
def mock_fs(mock_file) -> VirtualFilesystem:
|
|
46
|
-
fs = VirtualFilesystem(case_sensitive=False)
|
|
47
|
-
fs.makedirs("/foo/bar")
|
|
48
|
-
fs.map_file_entry("/foo/bar/some-file", VirtualFile(fs, "some-file", mock_file))
|
|
49
|
-
fs.map_file_entry("/foo/bar/some-symlink", VirtualSymlink(fs, "some-symlink", "/foo/bar/some_file"))
|
|
50
|
-
return fs
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
@pytest.fixture
|
|
54
|
-
def mock_target(mock_fs) -> Target:
|
|
55
|
-
target = Target()
|
|
56
|
-
target.fs.mount("/", mock_fs)
|
|
57
|
-
target.filesystems.add(mock_fs)
|
|
58
|
-
return target
|
|
59
|
-
|
|
60
|
-
|
|
61
39
|
@pytest.fixture
|
|
62
40
|
def mock_collector(mock_target) -> Collector:
|
|
63
41
|
collector = Collector(mock_target, Mock())
|
|
@@ -106,24 +84,84 @@ def test_collector_collect_path_file(mock_collector) -> None:
|
|
|
106
84
|
|
|
107
85
|
def test_collector_collect_path_symlink(mock_collector) -> None:
|
|
108
86
|
with patch.object(mock_collector, "collect_symlink", autospec=True):
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
87
|
+
with patch.object(mock_collector, "collect_file", autospec=True):
|
|
88
|
+
mock_collector.collect_path(
|
|
89
|
+
"/foo/bar/some-symlink",
|
|
90
|
+
follow=False,
|
|
91
|
+
seen_paths=MOCK_SEEN_PATHS,
|
|
92
|
+
module_name=MOCK_MODULE_NAME,
|
|
93
|
+
)
|
|
94
|
+
mock_collector.collect_symlink.assert_called()
|
|
95
|
+
mock_collector.collect_file.assert_not_called()
|
|
116
96
|
|
|
117
97
|
|
|
118
98
|
def test_collector_collect_path_symlink_follow(mock_collector) -> None:
|
|
119
99
|
with patch.object(mock_collector, "collect_symlink", autospec=True):
|
|
120
|
-
|
|
100
|
+
with patch.object(mock_collector, "collect_file", autospec=True):
|
|
101
|
+
mock_collector.collect_path(
|
|
102
|
+
"/foo/bar/some-symlink",
|
|
103
|
+
follow=True,
|
|
104
|
+
seen_paths=MOCK_SEEN_PATHS,
|
|
105
|
+
module_name=MOCK_MODULE_NAME,
|
|
106
|
+
)
|
|
107
|
+
mock_collector.collect_symlink.assert_called()
|
|
108
|
+
mock_collector.collect_file.assert_called()
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@pytest.mark.parametrize(
|
|
112
|
+
"path, symlink_called, file_called",
|
|
113
|
+
[
|
|
114
|
+
(
|
|
115
|
+
"/foo/bar/own-file",
|
|
116
|
+
False,
|
|
117
|
+
False,
|
|
118
|
+
),
|
|
119
|
+
(
|
|
120
|
+
"/foo/own-symlink",
|
|
121
|
+
True,
|
|
122
|
+
False,
|
|
123
|
+
),
|
|
124
|
+
(
|
|
125
|
+
"/foo/bar/some-file",
|
|
126
|
+
False,
|
|
127
|
+
True,
|
|
128
|
+
),
|
|
129
|
+
(
|
|
121
130
|
"/foo/bar/some-symlink",
|
|
122
|
-
|
|
123
|
-
|
|
131
|
+
True,
|
|
132
|
+
True,
|
|
133
|
+
),
|
|
134
|
+
],
|
|
135
|
+
)
|
|
136
|
+
def test_collector_collect_path_skip_list(mock_collector, path, symlink_called, file_called) -> None:
|
|
137
|
+
with patch.object(mock_collector, "skip_list", new={"/foo/bar/own-file"}):
|
|
138
|
+
with patch.object(mock_collector, "collect_symlink", autospec=True):
|
|
139
|
+
with patch.object(mock_collector, "collect_file", autospec=True):
|
|
140
|
+
mock_collector.collect_path(
|
|
141
|
+
path,
|
|
142
|
+
follow=True,
|
|
143
|
+
seen_paths=MOCK_SEEN_PATHS,
|
|
144
|
+
module_name=MOCK_MODULE_NAME,
|
|
145
|
+
)
|
|
146
|
+
if symlink_called:
|
|
147
|
+
mock_collector.collect_symlink.assert_called()
|
|
148
|
+
else:
|
|
149
|
+
mock_collector.collect_symlink.assert_not_called()
|
|
150
|
+
|
|
151
|
+
if file_called:
|
|
152
|
+
mock_collector.collect_file.assert_called()
|
|
153
|
+
else:
|
|
154
|
+
mock_collector.collect_file.assert_not_called()
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def test_collector_collect_glob(mock_collector) -> None:
|
|
158
|
+
with patch.object(mock_collector, "collect_file", autospec=True):
|
|
159
|
+
mock_collector.collect_glob(
|
|
160
|
+
"/foo/bar/*",
|
|
124
161
|
module_name=MOCK_MODULE_NAME,
|
|
125
162
|
)
|
|
126
|
-
mock_collector.
|
|
163
|
+
assert len(mock_collector.collect_file.mock_calls) == 3
|
|
164
|
+
assert mock_collector.collect_file.call_args.kwargs.get("module_name", None) == MOCK_MODULE_NAME
|
|
127
165
|
|
|
128
166
|
|
|
129
167
|
def test_collector_collect_path_non_existing_file(mock_collector) -> None:
|
|
@@ -3,12 +3,14 @@ import pathlib
|
|
|
3
3
|
from unittest.mock import MagicMock, patch
|
|
4
4
|
|
|
5
5
|
import pytest
|
|
6
|
+
from dissect.target import Target
|
|
6
7
|
|
|
7
8
|
from acquire.acquire import MODULES, PROFILES
|
|
8
9
|
from acquire.utils import (
|
|
9
10
|
check_and_set_acquire_args,
|
|
10
11
|
check_and_set_log_args,
|
|
11
12
|
create_argument_parser,
|
|
13
|
+
normalize_path,
|
|
12
14
|
)
|
|
13
15
|
|
|
14
16
|
|
|
@@ -290,3 +292,91 @@ def test_check_and_set_acquire_args_cagent():
|
|
|
290
292
|
|
|
291
293
|
assert args.cagent_key == cagent_key
|
|
292
294
|
assert args.cagent_certificate == cagent_certificate
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
@pytest.mark.parametrize(
|
|
298
|
+
"path, resolve, norm_path, case_sensitive, os",
|
|
299
|
+
[
|
|
300
|
+
(
|
|
301
|
+
pathlib.Path("/foo/bar"),
|
|
302
|
+
False,
|
|
303
|
+
"/foo/bar",
|
|
304
|
+
True,
|
|
305
|
+
"dummy",
|
|
306
|
+
),
|
|
307
|
+
(
|
|
308
|
+
pathlib.Path("/foo/BAR"),
|
|
309
|
+
False,
|
|
310
|
+
"/foo/bar",
|
|
311
|
+
False,
|
|
312
|
+
"dummy",
|
|
313
|
+
),
|
|
314
|
+
(
|
|
315
|
+
pathlib.Path("/foo/BAR"),
|
|
316
|
+
False,
|
|
317
|
+
"/foo/BAR",
|
|
318
|
+
True,
|
|
319
|
+
"dummy",
|
|
320
|
+
),
|
|
321
|
+
(
|
|
322
|
+
pathlib.Path("/foo/../bar"),
|
|
323
|
+
False,
|
|
324
|
+
"/foo/../bar",
|
|
325
|
+
True,
|
|
326
|
+
"dummy",
|
|
327
|
+
),
|
|
328
|
+
(
|
|
329
|
+
pathlib.Path("/foo/../foo/bar"),
|
|
330
|
+
True,
|
|
331
|
+
"/foo/bar",
|
|
332
|
+
True,
|
|
333
|
+
"dummy",
|
|
334
|
+
),
|
|
335
|
+
(
|
|
336
|
+
pathlib.PureWindowsPath("c:\\foo\\bar"),
|
|
337
|
+
False,
|
|
338
|
+
"sysvol/foo/bar",
|
|
339
|
+
False,
|
|
340
|
+
"windows",
|
|
341
|
+
),
|
|
342
|
+
(
|
|
343
|
+
pathlib.PureWindowsPath("C:\\foo\\bar"),
|
|
344
|
+
False,
|
|
345
|
+
"sysvol/foo/bar",
|
|
346
|
+
False,
|
|
347
|
+
"windows",
|
|
348
|
+
),
|
|
349
|
+
(
|
|
350
|
+
pathlib.PureWindowsPath("\\??\\C:\\foo\\bar"),
|
|
351
|
+
False,
|
|
352
|
+
"sysvol/foo/bar",
|
|
353
|
+
False,
|
|
354
|
+
"windows",
|
|
355
|
+
),
|
|
356
|
+
(
|
|
357
|
+
pathlib.PureWindowsPath("\\??\\c:\\foo\\bar"),
|
|
358
|
+
False,
|
|
359
|
+
"sysvol/foo/bar",
|
|
360
|
+
False,
|
|
361
|
+
"windows",
|
|
362
|
+
),
|
|
363
|
+
(
|
|
364
|
+
pathlib.PureWindowsPath("D:\\foo\\bar"),
|
|
365
|
+
False,
|
|
366
|
+
"d:/foo/bar",
|
|
367
|
+
False,
|
|
368
|
+
"windows",
|
|
369
|
+
),
|
|
370
|
+
],
|
|
371
|
+
)
|
|
372
|
+
def test_utils_normalize_path(
|
|
373
|
+
mock_target: Target,
|
|
374
|
+
path: pathlib.Path,
|
|
375
|
+
resolve: bool,
|
|
376
|
+
norm_path: str,
|
|
377
|
+
case_sensitive: bool,
|
|
378
|
+
os: str,
|
|
379
|
+
) -> None:
|
|
380
|
+
with patch.object(mock_target, "os", new=os):
|
|
381
|
+
with patch.object(mock_target.fs, "_case_sensitive", new=case_sensitive):
|
|
382
|
+
assert normalize_path(mock_target, path, resolve=resolve) == norm_path
|
|
@@ -56,3 +56,24 @@ extend-ignore =
|
|
|
56
56
|
# See https://github.com/PyCQA/pycodestyle/issues/373
|
|
57
57
|
E203,
|
|
58
58
|
statistics = True
|
|
59
|
+
exclude = acquire/version.py
|
|
60
|
+
|
|
61
|
+
[testenv:docs-build]
|
|
62
|
+
allowlist_externals = make
|
|
63
|
+
deps =
|
|
64
|
+
sphinx
|
|
65
|
+
sphinx-autoapi
|
|
66
|
+
sphinx_argparse_cli
|
|
67
|
+
sphinx-copybutton
|
|
68
|
+
sphinx-design
|
|
69
|
+
furo
|
|
70
|
+
commands =
|
|
71
|
+
make -C tests/docs clean
|
|
72
|
+
make -C tests/docs html
|
|
73
|
+
|
|
74
|
+
[testenv:docs-linkcheck]
|
|
75
|
+
allowlist_externals = make
|
|
76
|
+
deps = {[testenv:docs-build]deps}
|
|
77
|
+
commands =
|
|
78
|
+
make -C tests/docs clean
|
|
79
|
+
make -C tests/docs linkcheck
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|