dissect.apfs 1.2.dev1__tar.gz → 1.2.dev2__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.
Files changed (63) hide show
  1. {dissect_apfs-1.2.dev1/dissect.apfs.egg-info → dissect_apfs-1.2.dev2}/PKG-INFO +1 -1
  2. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/dissect/apfs/stream.py +14 -10
  3. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2/dissect.apfs.egg-info}/PKG-INFO +1 -1
  4. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/dissect.apfs.egg-info/SOURCES.txt +1 -0
  5. dissect_apfs-1.2.dev2/tests/_data/large.bin.gz +0 -0
  6. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/tests/test_apfs.py +25 -0
  7. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/COPYRIGHT +0 -0
  8. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/LICENSE +0 -0
  9. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/MANIFEST.in +0 -0
  10. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/README.md +0 -0
  11. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/dissect/apfs/__init__.py +0 -0
  12. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/dissect/apfs/apfs.py +0 -0
  13. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/dissect/apfs/c_apfs.py +0 -0
  14. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/dissect/apfs/c_apfs.pyi +0 -0
  15. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/dissect/apfs/cursor.py +0 -0
  16. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/dissect/apfs/exception.py +0 -0
  17. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/dissect/apfs/objects/__init__.py +0 -0
  18. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/dissect/apfs/objects/base.py +0 -0
  19. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/dissect/apfs/objects/btree.py +0 -0
  20. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/dissect/apfs/objects/btree_node.py +0 -0
  21. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/dissect/apfs/objects/checkpoint_map.py +0 -0
  22. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/dissect/apfs/objects/efi_jumpstart.py +0 -0
  23. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/dissect/apfs/objects/er_recovery_block.py +0 -0
  24. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/dissect/apfs/objects/er_state.py +0 -0
  25. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/dissect/apfs/objects/fs.py +0 -0
  26. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/dissect/apfs/objects/gbitmap.py +0 -0
  27. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/dissect/apfs/objects/gbitmap_block.py +0 -0
  28. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/dissect/apfs/objects/integrity_meta.py +0 -0
  29. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/dissect/apfs/objects/keybag.py +0 -0
  30. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/dissect/apfs/objects/nx_fusion_wbc.py +0 -0
  31. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/dissect/apfs/objects/nx_fusion_wbc_list.py +0 -0
  32. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/dissect/apfs/objects/nx_reap_list.py +0 -0
  33. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/dissect/apfs/objects/nx_reaper.py +0 -0
  34. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/dissect/apfs/objects/nx_superblock.py +0 -0
  35. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/dissect/apfs/objects/omap.py +0 -0
  36. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/dissect/apfs/objects/snap_meta_ext.py +0 -0
  37. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/dissect/apfs/objects/spaceman.py +0 -0
  38. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/dissect/apfs/objects/spaceman_bitmap.py +0 -0
  39. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/dissect/apfs/objects/spaceman_cab.py +0 -0
  40. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/dissect/apfs/objects/spaceman_cib.py +0 -0
  41. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/dissect/apfs/util.py +0 -0
  42. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/dissect.apfs.egg-info/dependency_links.txt +0 -0
  43. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/dissect.apfs.egg-info/requires.txt +0 -0
  44. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/dissect.apfs.egg-info/top_level.txt +0 -0
  45. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/pyproject.toml +0 -0
  46. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/setup.cfg +0 -0
  47. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/tests/__init__.py +0 -0
  48. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/tests/_data/case_insensitive.bin.gz +0 -0
  49. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/tests/_data/case_insensitive_beta.bin.gz +0 -0
  50. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/tests/_data/case_sensitive.bin.gz +0 -0
  51. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/tests/_data/case_sensitive_beta.bin.gz +0 -0
  52. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/tests/_data/corrupt.bin.gz +0 -0
  53. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/tests/_data/encrypted.bin.gz +0 -0
  54. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/tests/_data/jhfs_converted.bin.gz +0 -0
  55. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/tests/_data/jhfs_encrypted.bin.gz +0 -0
  56. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/tests/_data/snapshot.bin.gz +0 -0
  57. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/tests/_docs/Makefile +0 -0
  58. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/tests/_docs/__init__.py +0 -0
  59. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/tests/_docs/conf.py +0 -0
  60. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/tests/_docs/index.rst +0 -0
  61. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/tests/conftest.py +0 -0
  62. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/tests/test_exception.py +0 -0
  63. {dissect_apfs-1.2.dev1 → dissect_apfs-1.2.dev2}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dissect.apfs
3
- Version: 1.2.dev1
3
+ Version: 1.2.dev2
4
4
  Summary: A Dissect module implementing a parser for the APFS file system, a commonly used Apple file system
5
5
  Author-email: Dissect Team <dissect@fox-it.com>
6
6
  License-Expression: AGPL-3.0-or-later
@@ -88,26 +88,30 @@ class FileStream(AlignedStream):
88
88
 
89
89
  while length:
90
90
  logical_address, physical_address, extent_length, crypto_id = self._lookup(offset)
91
- block = self.volume.container._read_block(physical_address, extent_length // self.align)
91
+
92
+ offset_in_extent = offset - logical_address
93
+ if offset_in_extent >= extent_length:
94
+ raise Error(
95
+ f"Offset {offset:#x} is out of bounds for extent ({logical_address:#x}, {extent_length:#x})"
96
+ )
97
+
98
+ block_in_extent = offset_in_extent // self.align
99
+ read_length = min(extent_length - offset_in_extent, length)
100
+ block = self.volume.container._read_block(physical_address + block_in_extent, read_length // self.align)
92
101
 
93
102
  if self.volume.is_encrypted:
94
103
  if not self.volume._cipher:
95
104
  raise Error("Volume is encrypted, unlock it first")
96
105
 
97
106
  if self.volume.is_onekey:
98
- block = self.volume._cipher.decrypt(block, crypto_id * self.volume.container.sectors_per_block)
107
+ sector = (crypto_id + block_in_extent) * self.volume.container.sectors_per_block
108
+ block = self.volume._cipher.decrypt(block, sector)
99
109
  else:
100
110
  raise Error("Multi-key encryption is not supported yet")
101
111
 
102
- if offset_in_extent := offset - logical_address:
103
- block = block[offset_in_extent:]
104
-
105
- if length < len(block):
106
- block = block[:length]
107
-
108
112
  result.append(block)
109
- offset += min(extent_length, length)
110
- length -= min(extent_length, length)
113
+ offset += read_length
114
+ length -= read_length
111
115
 
112
116
  return b"".join(result)
113
117
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dissect.apfs
3
- Version: 1.2.dev1
3
+ Version: 1.2.dev2
4
4
  Summary: A Dissect module implementing a parser for the APFS file system, a commonly used Apple file system
5
5
  Author-email: Dissect Team <dissect@fox-it.com>
6
6
  License-Expression: AGPL-3.0-or-later
@@ -53,6 +53,7 @@ tests/_data/corrupt.bin.gz
53
53
  tests/_data/encrypted.bin.gz
54
54
  tests/_data/jhfs_converted.bin.gz
55
55
  tests/_data/jhfs_encrypted.bin.gz
56
+ tests/_data/large.bin.gz
56
57
  tests/_data/snapshot.bin.gz
57
58
  tests/_docs/Makefile
58
59
  tests/_docs/__init__.py
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  import gzip
4
4
  import hashlib
5
5
  from typing import TYPE_CHECKING
6
+ from unittest.mock import patch
6
7
 
7
8
  import pytest
8
9
 
@@ -379,3 +380,27 @@ def test_corrupt_checkpoints(caplog: pytest.LogCaptureFixture) -> None:
379
380
 
380
381
  assert caplog.messages[0] == "Skipping superblock xid=304: invalid OMAP"
381
382
  assert caplog.messages[1] == "Skipping superblock xid=303: Invalid nx_superblock checksum"
383
+
384
+
385
+ def test_large_extents() -> None:
386
+ """Test APFS volumes with large extents."""
387
+ with gzip.open(absolute_path("_data/large.bin.gz"), "rb") as fh:
388
+ container = APFS(fh)
389
+ assert len(container.volumes) == 1
390
+
391
+ volume = container.volumes[0]
392
+ assert volume.name == "Large"
393
+
394
+ node = volume.get("yomomma.bin")
395
+ assert node.size == 512 * 1024 * 1024
396
+
397
+ fh = node.open()
398
+
399
+ # First extent is 128MiB
400
+ assert fh._lookup(0) == (0, 1070, 128 * 1024 * 1024, 0)
401
+
402
+ with patch.object(volume.container, "_read_block", wraps=volume.container._read_block) as mock_read_block:
403
+ assert fh.read(512) == b"\x67" * 512
404
+
405
+ # Check that we only read one block, not the entire 128MiB extent
406
+ mock_read_block.assert_called_once_with(1070, 1)
File without changes
File without changes