pyiosbackup 0.2.2__py3-none-any.whl → 0.2.4__py3-none-any.whl

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.
pyiosbackup/__main__.py CHANGED
@@ -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()
pyiosbackup/backup.py CHANGED
@@ -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()
pyiosbackup/entry.py CHANGED
@@ -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
pyiosbackup/exceptions.py CHANGED
@@ -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
pyiosbackup/keybag.py CHANGED
@@ -1,11 +1,11 @@
1
1
  import hashlib
2
2
  import logging
3
+ import math
3
4
 
4
- from construct import Bytes, this, Int32ub, GreedyRange, IfThenElse
5
- from packaging.version import Version
6
- from construct import Struct, GreedyBytes, Int32ul
7
- from cryptography.hazmat.primitives.keywrap import aes_key_unwrap
5
+ from construct import Bytes, GreedyBytes, GreedyRange, IfThenElse, Int32ub, Int32ul, Struct, this
8
6
  from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
7
+ from cryptography.hazmat.primitives.keywrap import aes_key_unwrap
8
+ from packaging.version import Version
9
9
 
10
10
  from pyiosbackup.manifest_plist import ManifestPlist
11
11
 
@@ -58,7 +58,8 @@ class Keybag:
58
58
  """
59
59
  keybag = keybag_struct.parse(manifest.keybag)
60
60
  # The class count excludes the root class (first class in the keybag) and is one based.
61
- class_count = [e.data for e in keybag if e.tag == b'CLAS'][0] - 1
61
+ first_class_index = [i for i in range(len(keybag)) if keybag[i].tag == b'CLAS'][0]
62
+ class_count = math.ceil((len(keybag) - first_class_index) / Keybag.CLASS_ELEMENTS_COUNT)
62
63
  logger.debug(f'Found {class_count} key classes')
63
64
  classes_index = len(keybag) - (Keybag.CLASS_ELEMENTS_COUNT * class_count)
64
65
  decryption_key = Keybag._decryption_key_from_password(password, keybag[:classes_index], manifest)
@@ -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 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,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
 
@@ -1,10 +1,10 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: pyiosbackup
3
- Version: 0.2.2
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>
7
- License: GNU GENERAL PUBLIC LICENSE
7
+ License: GNU GENERAL PUBLIC LICENSE
8
8
  Version 3, 29 June 2007
9
9
 
10
10
  Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
@@ -690,17 +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
699
  License-File: LICENSE
697
- Requires-Dist: bpylist2 (>=4.0.1)
698
- Requires-Dist: cryptography (>=35.0.0)
700
+ Requires-Dist: bpylist2>=4.0.1
701
+ Requires-Dist: cryptography>=35.0.0
699
702
  Requires-Dist: packaging
700
703
  Requires-Dist: construct
701
704
  Requires-Dist: click
702
705
  Provides-Extra: test
703
- Requires-Dist: pytest ; extra == 'test'
706
+ Requires-Dist: pytest; extra == "test"
707
+ Dynamic: license-file
704
708
 
705
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")
706
710
  [![Pypi version](https://img.shields.io/pypi/v/pyiosbackup.svg)](https://pypi.org/project/pyiosbackup/ "PyPi package")
@@ -0,0 +1,18 @@
1
+ pyiosbackup/__init__.py,sha256=1b_5ZMer7ZuuVI2WC9GCAYWP62DNvE3lh3RWHACoNos,38
2
+ pyiosbackup/__main__.py,sha256=O7LaLls0E88ZxSlc2Gqn0335UnlSQsz8vdmtvE37WcI,2442
3
+ pyiosbackup/backup.py,sha256=RKnWimS-IG8xpeuKA2GtxWCqXRQ2CAgBqHUtViMBXDc,8868
4
+ pyiosbackup/entry.py,sha256=1CR_r24YSoGfYBomKkZZwA4t3tUu7WLNLTQE5q09aVY,4603
5
+ pyiosbackup/exceptions.py,sha256=hXJLD1RToqvmGetHHtpQbKQidkJQx4TOlaSf2im7ers,464
6
+ pyiosbackup/keybag.py,sha256=ER7z_CJGZX-rzzXF_CxcntUxOuBES_pShYqpx9n3djU,4731
7
+ pyiosbackup/manifest_plist.py,sha256=WWozREPN3Vm5xdwY27gqX61Zr5Rg0Jq7QqPQpEUBn_w,942
8
+ pyiosbackup/manifest_dbs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ pyiosbackup/manifest_dbs/factory.py,sha256=lJNQ_5EiUriObZZn-EXr83k62VHtqHQ4xH3ITs8UX8w,781
10
+ pyiosbackup/manifest_dbs/manifest_db_interface.py,sha256=DNxuShVPSH1l_59IdqibiTv8f4Akz02jiuwMUCtxtro,522
11
+ pyiosbackup/manifest_dbs/mbdb.py,sha256=hqGvuZiYU7sJGEvWpSg6E-K4q6N6H2djTZB4mCqP3hQ,3562
12
+ pyiosbackup/manifest_dbs/sqlite3.py,sha256=YrPnVrld1naIZA2dF40mZsxiDYk08zPa4stmaR-KfTo,3490
13
+ pyiosbackup-0.2.4.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
14
+ pyiosbackup-0.2.4.dist-info/METADATA,sha256=I3luEmMCZ0bnyHHhD-UW8gEiG0aydyVZaZlDt0Xgsto,44271
15
+ pyiosbackup-0.2.4.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
16
+ pyiosbackup-0.2.4.dist-info/entry_points.txt,sha256=frMMD9_8u6eoiBHBriurbVQtuytO1DX9HUeYA3UDOXU,58
17
+ pyiosbackup-0.2.4.dist-info/top_level.txt,sha256=p19DE7G-wJEY49fcQHMZUFlxc2O4LmWhkDrhZcs3suo,12
18
+ pyiosbackup-0.2.4.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.40.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,18 +0,0 @@
1
- pyiosbackup/__init__.py,sha256=1b_5ZMer7ZuuVI2WC9GCAYWP62DNvE3lh3RWHACoNos,38
2
- pyiosbackup/__main__.py,sha256=Z3NKaeGUwpsidL7VGtDBsQB-11CVbNHtvrAWDDcZ4kg,2263
3
- pyiosbackup/backup.py,sha256=CIPh3MnZEWot7Z3EJ7ufWjTKuLv4hqvuZB1BLoTVaoc,8099
4
- pyiosbackup/entry.py,sha256=aAC6v7s1AUr3ydStLg1ipuoLMD6P_guUJ6vMX32Iaoo,4603
5
- pyiosbackup/exceptions.py,sha256=nJUEACYezGiS74eIM84U5mb3MR88SmE_83kgGoZgciM,335
6
- pyiosbackup/keybag.py,sha256=_a1SlqTUlnu8xKMegN9ep0yIG0nt-dwfIaTLx-Q3m1o,4626
7
- pyiosbackup/manifest_plist.py,sha256=3tu4nGMBTxtfazwK6k6sNVf2mWMTh_-4FVfhbbaoYCQ,942
8
- pyiosbackup/manifest_dbs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- pyiosbackup/manifest_dbs/factory.py,sha256=huaMbi0DQlro-39jGSgo2y25bd8vIPdUQJcU222xpYc,781
10
- pyiosbackup/manifest_dbs/manifest_db_interface.py,sha256=DNxuShVPSH1l_59IdqibiTv8f4Akz02jiuwMUCtxtro,522
11
- pyiosbackup/manifest_dbs/mbdb.py,sha256=g9tLb4TLHBgovnSceWki89vKZWFaWLTFNfPGy25f54I,3562
12
- pyiosbackup/manifest_dbs/sqlite3.py,sha256=l1eGzb7b---CxjrFu-hJB37AWVGqNZCdoCp_qHBCwtg,3490
13
- pyiosbackup-0.2.2.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
14
- pyiosbackup-0.2.2.dist-info/METADATA,sha256=hmn-yZ8X5hqd78JxzjPLBtFBP1S6Snmv5UhMvpa_SBA,44083
15
- pyiosbackup-0.2.2.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
16
- pyiosbackup-0.2.2.dist-info/entry_points.txt,sha256=frMMD9_8u6eoiBHBriurbVQtuytO1DX9HUeYA3UDOXU,58
17
- pyiosbackup-0.2.2.dist-info/top_level.txt,sha256=p19DE7G-wJEY49fcQHMZUFlxc2O4LmWhkDrhZcs3suo,12
18
- pyiosbackup-0.2.2.dist-info/RECORD,,