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.
- {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/PKG-INFO +13 -3
- {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/pyiosbackup/__main__.py +13 -8
- {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/pyiosbackup/backup.py +21 -9
- {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/pyiosbackup/exceptions.py +5 -0
- {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/pyiosbackup/keybag.py +1 -2
- {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/pyiosbackup/manifest_dbs/mbdb.py +4 -4
- {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/pyiosbackup.egg-info/PKG-INFO +13 -3
- {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/pyproject.toml +4 -1
- {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/tests/test_backup.py +4 -4
- {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/LICENSE +0 -0
- {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/README.md +0 -0
- {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/pyiosbackup/__init__.py +0 -0
- {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/pyiosbackup/entry.py +3 -3
- {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/pyiosbackup/manifest_dbs/__init__.py +0 -0
- {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/pyiosbackup/manifest_dbs/factory.py +1 -1
- {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/pyiosbackup/manifest_dbs/manifest_db_interface.py +0 -0
- {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/pyiosbackup/manifest_dbs/sqlite3.py +3 -3
- {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/pyiosbackup/manifest_plist.py +1 -1
- {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/pyiosbackup.egg-info/SOURCES.txt +0 -0
- {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/pyiosbackup.egg-info/dependency_links.txt +0 -0
- {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/pyiosbackup.egg-info/entry_points.txt +0 -0
- {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/pyiosbackup.egg-info/requires.txt +0 -0
- {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/pyiosbackup.egg-info/top_level.txt +0 -0
- {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/requirements.txt +0 -0
- {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/setup.cfg +0 -0
- {pyiosbackup-0.2.3 → pyiosbackup-0.2.4}/tests/test_keybag.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: pyiosbackup
|
|
3
|
-
Version: 0.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>
|
|
@@ -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
|
[](https://github.com/matan1008/pyiosbackup/actions/workflows/python-app.yml "Python application action")
|
|
700
710
|
[](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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
6
|
-
|
|
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
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: pyiosbackup
|
|
3
|
-
Version: 0.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>
|
|
@@ -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
|
[](https://github.com/matan1008/pyiosbackup/actions/workflows/python-app.yml "Python application action")
|
|
700
710
|
[](https://pypi.org/project/pyiosbackup/ "PyPi package")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "pyiosbackup"
|
|
3
|
-
version = "0.2.
|
|
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
|
|
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
|
|
File without changes
|
|
@@ -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:
|
|
File without changes
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
|
|
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
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|