pyiosbackup 0.2.3__tar.gz → 0.2.4__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 (26) hide show
  1. {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/PKG-INFO +13 -3
  2. {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/pyiosbackup/__main__.py +13 -8
  3. {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/pyiosbackup/backup.py +21 -9
  4. {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/pyiosbackup/exceptions.py +5 -0
  5. {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/pyiosbackup/keybag.py +1 -2
  6. {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/pyiosbackup/manifest_dbs/mbdb.py +4 -4
  7. {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/pyiosbackup.egg-info/PKG-INFO +13 -3
  8. {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/pyproject.toml +4 -1
  9. {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/tests/test_backup.py +4 -4
  10. {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/LICENSE +0 -0
  11. {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/README.md +0 -0
  12. {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/pyiosbackup/__init__.py +0 -0
  13. {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/pyiosbackup/entry.py +3 -3
  14. {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/pyiosbackup/manifest_dbs/__init__.py +0 -0
  15. {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/pyiosbackup/manifest_dbs/factory.py +1 -1
  16. {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/pyiosbackup/manifest_dbs/manifest_db_interface.py +0 -0
  17. {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/pyiosbackup/manifest_dbs/sqlite3.py +3 -3
  18. {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/pyiosbackup/manifest_plist.py +1 -1
  19. {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/pyiosbackup.egg-info/SOURCES.txt +0 -0
  20. {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/pyiosbackup.egg-info/dependency_links.txt +0 -0
  21. {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/pyiosbackup.egg-info/entry_points.txt +0 -0
  22. {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/pyiosbackup.egg-info/requires.txt +0 -0
  23. {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/pyiosbackup.egg-info/top_level.txt +0 -0
  24. {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/requirements.txt +0 -0
  25. {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/setup.cfg +0 -0
  26. {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/tests/test_keybag.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: pyiosbackup
3
- Version: 0.2.3
3
+ Version: 0.2.4
4
4
  Summary: A python parser for iOS backups
5
5
  Author-email: Matan Perelman <matan1008@gmail.com>
6
6
  Maintainer-email: Matan Perelman <matan1008@gmail.com>
@@ -690,11 +690,21 @@ Classifier: Programming Language :: Python :: 3.8
690
690
  Classifier: Programming Language :: Python :: 3.9
691
691
  Classifier: Programming Language :: Python :: 3.10
692
692
  Classifier: Programming Language :: Python :: 3.11
693
+ Classifier: Programming Language :: Python :: 3.12
694
+ Classifier: Programming Language :: Python :: 3.13
695
+ Classifier: Programming Language :: Python :: 3.14
693
696
  Classifier: Programming Language :: Python :: 3 :: Only
694
697
  Requires-Python: >=3.7
695
698
  Description-Content-Type: text/markdown
696
- Provides-Extra: test
697
699
  License-File: LICENSE
700
+ Requires-Dist: bpylist2>=4.0.1
701
+ Requires-Dist: cryptography>=35.0.0
702
+ Requires-Dist: packaging
703
+ Requires-Dist: construct
704
+ Requires-Dist: click
705
+ Provides-Extra: test
706
+ Requires-Dist: pytest; extra == "test"
707
+ Dynamic: license-file
698
708
 
699
709
  [![Python application](https://github.com/matan1008/pyiosbackup/workflows/Python%20application/badge.svg)](https://github.com/matan1008/pyiosbackup/actions/workflows/python-app.yml "Python application action")
700
710
  [![Pypi version](https://img.shields.io/pypi/v/pyiosbackup.svg)](https://pypi.org/project/pyiosbackup/ "PyPi package")
@@ -20,6 +20,7 @@ backup_path_argument = click.argument('backup_path', type=click.Path(exists=True
20
20
  password_argument = click.argument('password')
21
21
  password_option = click.option('-p', '--password', default='')
22
22
  target_option = click.option('--target', type=click.Path(), default='.')
23
+ strict_option = click.option('--strict', is_flag=True)
23
24
  verbosity = click.option('-v', '--verbosity', count=True, callback=set_verbosity, expose_value=False)
24
25
 
25
26
 
@@ -34,11 +35,12 @@ def cli():
34
35
  @click.argument('relative_path')
35
36
  @password_option
36
37
  @target_option
38
+ @strict_option
37
39
  @verbosity
38
- def extract_domain_path(backup_path, domain, relative_path, password, target):
40
+ def extract_domain_path(backup_path, domain, relative_path, password, target, strict):
39
41
  """ Extract a file from backup, given its domain and relative path."""
40
42
  backup = Backup.from_path(backup_path, password)
41
- backup.extract_domain_and_path(domain, relative_path, target)
43
+ backup.extract_domain_and_path(domain, relative_path, target, strict)
42
44
 
43
45
 
44
46
  @cli.command()
@@ -46,33 +48,36 @@ def extract_domain_path(backup_path, domain, relative_path, password, target):
46
48
  @click.argument('file_id')
47
49
  @password_option
48
50
  @target_option
51
+ @strict_option
49
52
  @verbosity
50
- def extract_id(backup_path, file_id, password, target):
53
+ def extract_id(backup_path, file_id, password, target, strict):
51
54
  """ Extract a file from backup, given its file ID."""
52
55
  backup = Backup.from_path(backup_path, password)
53
- backup.extract_file_id(file_id, target)
56
+ backup.extract_file_id(file_id, target, strict)
54
57
 
55
58
 
56
59
  @cli.command()
57
60
  @backup_path_argument
58
61
  @password_argument
59
62
  @target_option
63
+ @strict_option
60
64
  @verbosity
61
- def extract_all(backup_path, password, target):
65
+ def extract_all(backup_path, password, target, strict):
62
66
  """ Decrypt all files in a backup."""
63
67
  backup = Backup.from_path(backup_path, password)
64
- backup.extract_all(target)
68
+ backup.extract_all(target, strict)
65
69
 
66
70
 
67
71
  @cli.command()
68
72
  @backup_path_argument
69
73
  @password_argument
70
74
  @target_option
75
+ @strict_option
71
76
  @verbosity
72
- def unback(backup_path, password, target):
77
+ def unback(backup_path, password, target, strict):
73
78
  """ Decrypt all files in a backup to a filesystem layout."""
74
79
  backup = Backup.from_path(backup_path, password)
75
- backup.unback(target)
80
+ backup.unback(target, strict)
76
81
 
77
82
 
78
83
  @cli.command()
@@ -6,7 +6,7 @@ from pathlib import Path
6
6
  from packaging.version import Version
7
7
 
8
8
  from pyiosbackup.entry import Entry
9
- from pyiosbackup.exceptions import BackupPasswordIsRequired
9
+ from pyiosbackup.exceptions import BackupPasswordIsRequired, CorruptedEntryError
10
10
  from pyiosbackup.keybag import Keybag
11
11
  from pyiosbackup.manifest_dbs.factory import from_path as manifest_db_from_path
12
12
  from pyiosbackup.manifest_dbs.manifest_db_interface import ManifestDb
@@ -98,10 +98,11 @@ class Backup:
98
98
  def is_encrypted(self) -> bool:
99
99
  return self._manifest_plist.is_encrypted
100
100
 
101
- def unback(self, path='.'):
101
+ def unback(self, path='.', strict: bool = False):
102
102
  """
103
103
  Extract all decrypted files from a backup in a filesystem layout
104
104
  :param path: Path to destination directory.
105
+ :param strict: Raise exception on extracting errors.
105
106
  """
106
107
  logger.info(f'Extracting backup to {path}')
107
108
  dest_dir = Path(path)
@@ -110,12 +111,13 @@ class Backup:
110
111
  dest_file = dest_dir / file.domain / file.relative_path
111
112
  logger.debug(f'Extracting file {file.filename} to {dest_file}')
112
113
  dest_file.parent.mkdir(exist_ok=True, parents=True)
113
- dest_file.write_bytes(file.read_bytes())
114
+ self._extract_and_write_entry_content(file, dest_file, strict)
114
115
 
115
- def extract_all(self, path='.'):
116
+ def extract_all(self, path='.', strict: bool = False):
116
117
  """
117
118
  Extract all decrypted files from a backup.
118
119
  :param path: Path to destination directory.
120
+ :param strict: Raise exception on extracting errors.
119
121
  """
120
122
  logger.info(f'Extracting backup to {path}')
121
123
  dest_dir = Path(path)
@@ -130,34 +132,36 @@ class Backup:
130
132
  dest_file = dest_dir / file.hash_path
131
133
  logger.debug(f'Extracting file {file.filename} to {dest_file}')
132
134
  dest_file.parent.mkdir(exist_ok=True, parents=True)
133
- dest_file.write_bytes(file.read_bytes())
135
+ self._extract_and_write_entry_content(file, dest_file, strict)
134
136
 
135
- def extract_file_id(self, file_id: str, path='.'):
137
+ def extract_file_id(self, file_id: str, path='.', strict: bool = False):
136
138
  """
137
139
  Extract a file by its id.
138
140
  :param file_id: File ID.
139
141
  :param path: Path to destination directory.
142
+ :param strict: Raise exception on extracting errors.
140
143
  """
141
144
  entry = self.get_entry_by_id(file_id)
142
145
  dest = Path(path)
143
146
  if dest.is_dir():
144
147
  dest /= entry.name
145
148
  dest.parent.mkdir(exist_ok=True, parents=True)
146
- dest.write_bytes(entry.read_bytes())
149
+ self._extract_and_write_entry_content(entry, dest, strict)
147
150
 
148
- def extract_domain_and_path(self, domain: str, relative_path: str, path='.'):
151
+ def extract_domain_and_path(self, domain: str, relative_path: str, path='.', strict: bool = False):
149
152
  """
150
153
  Extract a file by its domain and path.
151
154
  :param domain: File's domain, e.g. 'RootDomain'.
152
155
  :param relative_path: File's relative path, e.g. 'Library/Preferences/com.apple.backupd.plist'.
153
156
  :param path: Path to destination directory.
157
+ :param strict: Raise exception on extracting errors.
154
158
  """
155
159
  entry = self.get_entry_by_domain_and_path(domain, relative_path)
156
160
  dest = Path(path)
157
161
  if dest.is_dir():
158
162
  dest /= entry.name
159
163
  dest.parent.mkdir(exist_ok=True, parents=True)
160
- dest.write_bytes(entry.read_bytes())
164
+ self._extract_and_write_entry_content(entry, dest, strict)
161
165
 
162
166
  def get_entry_by_id(self, file_id: str) -> Entry:
163
167
  """
@@ -219,3 +223,11 @@ class Backup:
219
223
  'size': size,
220
224
  'is_encrypted': self._manifest_plist.is_encrypted,
221
225
  }
226
+
227
+ def _extract_and_write_entry_content(self, entry: Entry, dest: Path, strict: bool):
228
+ try:
229
+ dest.write_bytes(entry.read_bytes())
230
+ except ValueError:
231
+ logger.warning(f'Could not extract content for {entry.relative_path}')
232
+ if strict:
233
+ raise CorruptedEntryError()
@@ -10,3 +10,8 @@ class BackupPasswordIsRequired(PyIosBackupException):
10
10
  class MissingEntryError(PyIosBackupException):
11
11
  """ Raise when trying to access an entry that doesn't exist. """
12
12
  pass
13
+
14
+
15
+ class CorruptedEntryError(PyIosBackupException):
16
+ """ Raise when trying to extract and decrypt an entry fails. """
17
+ pass
@@ -2,8 +2,7 @@ import hashlib
2
2
  import logging
3
3
  import math
4
4
 
5
- from construct import Bytes, this, Int32ub, GreedyRange, IfThenElse
6
- from construct import Struct, GreedyBytes, Int32ul
5
+ from construct import Bytes, GreedyBytes, GreedyRange, IfThenElse, Int32ub, Int32ul, Struct, this
7
6
  from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
8
7
  from cryptography.hazmat.primitives.keywrap import aes_key_unwrap
9
8
  from packaging.version import Version
@@ -1,9 +1,9 @@
1
- from pathlib import Path
2
- from datetime import timezone, datetime
3
1
  import hashlib
2
+ from datetime import datetime, timezone
3
+ from pathlib import Path
4
4
 
5
- from construct import Struct, Const, Bytes, GreedyRange, Int16ub, IfThenElse, Computed, this, \
6
- Int32ub, Int64ub, Byte, Array, PaddedString
5
+ from construct import Array, Byte, Bytes, Computed, Const, GreedyRange, IfThenElse, Int16ub, Int32ub, Int64ub, \
6
+ PaddedString, Struct, this
7
7
 
8
8
  from pyiosbackup.exceptions import MissingEntryError
9
9
  from pyiosbackup.manifest_dbs.manifest_db_interface import ManifestDb
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: pyiosbackup
3
- Version: 0.2.3
3
+ Version: 0.2.4
4
4
  Summary: A python parser for iOS backups
5
5
  Author-email: Matan Perelman <matan1008@gmail.com>
6
6
  Maintainer-email: Matan Perelman <matan1008@gmail.com>
@@ -690,11 +690,21 @@ Classifier: Programming Language :: Python :: 3.8
690
690
  Classifier: Programming Language :: Python :: 3.9
691
691
  Classifier: Programming Language :: Python :: 3.10
692
692
  Classifier: Programming Language :: Python :: 3.11
693
+ Classifier: Programming Language :: Python :: 3.12
694
+ Classifier: Programming Language :: Python :: 3.13
695
+ Classifier: Programming Language :: Python :: 3.14
693
696
  Classifier: Programming Language :: Python :: 3 :: Only
694
697
  Requires-Python: >=3.7
695
698
  Description-Content-Type: text/markdown
696
- Provides-Extra: test
697
699
  License-File: LICENSE
700
+ Requires-Dist: bpylist2>=4.0.1
701
+ Requires-Dist: cryptography>=35.0.0
702
+ Requires-Dist: packaging
703
+ Requires-Dist: construct
704
+ Requires-Dist: click
705
+ Provides-Extra: test
706
+ Requires-Dist: pytest; extra == "test"
707
+ Dynamic: license-file
698
708
 
699
709
  [![Python application](https://github.com/matan1008/pyiosbackup/workflows/Python%20application/badge.svg)](https://github.com/matan1008/pyiosbackup/actions/workflows/python-app.yml "Python application action")
700
710
  [![Pypi version](https://img.shields.io/pypi/v/pyiosbackup.svg)](https://pypi.org/project/pyiosbackup/ "PyPi package")
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pyiosbackup"
3
- version = "0.2.3"
3
+ version = "0.2.4"
4
4
  description = "A python parser for iOS backups"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.7"
@@ -21,6 +21,9 @@ classifiers = [
21
21
  "Programming Language :: Python :: 3.9",
22
22
  "Programming Language :: Python :: 3.10",
23
23
  "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Programming Language :: Python :: 3.13",
26
+ "Programming Language :: Python :: 3.14",
24
27
  "Programming Language :: Python :: 3 :: Only",
25
28
  ]
26
29
  dynamic = ["dependencies"]
@@ -1,15 +1,15 @@
1
+ import plistlib
1
2
  from datetime import datetime, timezone
2
3
  from pathlib import Path
3
- import plistlib
4
4
 
5
5
  import pytest
6
6
 
7
7
  from pyiosbackup import Backup
8
+ from pyiosbackup.backup import INFO_PLIST_PATH, STATUS_PLIST_PATH
8
9
  from pyiosbackup.exceptions import BackupPasswordIsRequired, MissingEntryError
9
- from pyiosbackup.backup import STATUS_PLIST_PATH, INFO_PLIST_PATH
10
- from pyiosbackup.manifest_plist import ManifestPlist
11
- from pyiosbackup.manifest_dbs.sqlite3 import ManifestDbSqlite3
12
10
  from pyiosbackup.manifest_dbs.mbdb import ManifestDbMbdb
11
+ from pyiosbackup.manifest_dbs.sqlite3 import ManifestDbSqlite3
12
+ from pyiosbackup.manifest_plist import ManifestPlist
13
13
 
14
14
 
15
15
  @pytest.fixture(scope='function')
File without changes
File without changes
@@ -1,10 +1,10 @@
1
- from dataclasses import dataclass
2
- from datetime import datetime
3
1
  import pathlib
4
2
  import posixpath
3
+ from dataclasses import dataclass
4
+ from datetime import datetime
5
5
 
6
- from packaging.version import Version
7
6
  from cryptography.hazmat.primitives import padding
7
+ from packaging.version import Version
8
8
 
9
9
  FILE_DATA_PAD_BITS = 128 # Files data is 128 bits (16 bytes) padded.
10
10
  MODE_TYPE_MASK = 0xE000
@@ -3,8 +3,8 @@ from pathlib import Path
3
3
  from packaging.version import Version
4
4
 
5
5
  from pyiosbackup.manifest_dbs.manifest_db_interface import ManifestDb
6
- from pyiosbackup.manifest_dbs.sqlite3 import ManifestDbSqlite3
7
6
  from pyiosbackup.manifest_dbs.mbdb import ManifestDbMbdb
7
+ from pyiosbackup.manifest_dbs.sqlite3 import ManifestDbSqlite3
8
8
 
9
9
 
10
10
  def from_path(backup_path: Path, manifest, keybag) -> ManifestDb:
@@ -1,9 +1,9 @@
1
- from pathlib import Path
2
- from datetime import datetime, timezone
1
+ import logging
3
2
  import sqlite3
4
3
  import tempfile
5
- import logging
6
4
  from dataclasses import dataclass
5
+ from datetime import datetime, timezone
6
+ from pathlib import Path
7
7
 
8
8
  from bpylist2 import archiver
9
9
 
@@ -1,5 +1,5 @@
1
- from pathlib import Path
2
1
  import plistlib
2
+ from pathlib import Path
3
3
 
4
4
  from packaging.version import Version
5
5
 
File without changes