pymobiledevice3 4.14.6__py3-none-any.whl → 7.0.6__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.
- misc/plist_sniffer.py +15 -15
- misc/remotexpc_sniffer.py +29 -28
- misc/understanding_idevice_protocol_layers.md +15 -10
- pymobiledevice3/__main__.py +317 -127
- pymobiledevice3/_version.py +22 -4
- pymobiledevice3/bonjour.py +358 -113
- pymobiledevice3/ca.py +253 -16
- pymobiledevice3/cli/activation.py +31 -23
- pymobiledevice3/cli/afc.py +49 -40
- pymobiledevice3/cli/amfi.py +16 -21
- pymobiledevice3/cli/apps.py +87 -42
- pymobiledevice3/cli/backup.py +160 -90
- pymobiledevice3/cli/bonjour.py +44 -40
- pymobiledevice3/cli/cli_common.py +204 -198
- pymobiledevice3/cli/companion_proxy.py +14 -14
- pymobiledevice3/cli/crash.py +105 -56
- pymobiledevice3/cli/developer/__init__.py +62 -0
- pymobiledevice3/cli/developer/accessibility/__init__.py +65 -0
- pymobiledevice3/cli/developer/accessibility/settings.py +43 -0
- pymobiledevice3/cli/developer/arbitration.py +50 -0
- pymobiledevice3/cli/developer/condition.py +33 -0
- pymobiledevice3/cli/developer/core_device.py +294 -0
- pymobiledevice3/cli/developer/debugserver.py +244 -0
- pymobiledevice3/cli/developer/dvt/__init__.py +438 -0
- pymobiledevice3/cli/developer/dvt/core_profile_session.py +295 -0
- pymobiledevice3/cli/developer/dvt/simulate_location.py +56 -0
- pymobiledevice3/cli/developer/dvt/sysmon/__init__.py +69 -0
- pymobiledevice3/cli/developer/dvt/sysmon/process.py +188 -0
- pymobiledevice3/cli/developer/fetch_symbols.py +108 -0
- pymobiledevice3/cli/developer/simulate_location.py +51 -0
- pymobiledevice3/cli/diagnostics/__init__.py +75 -0
- pymobiledevice3/cli/diagnostics/battery.py +47 -0
- pymobiledevice3/cli/idam.py +42 -0
- pymobiledevice3/cli/lockdown.py +108 -103
- pymobiledevice3/cli/mounter.py +158 -99
- pymobiledevice3/cli/notification.py +38 -26
- pymobiledevice3/cli/pcap.py +45 -24
- pymobiledevice3/cli/power_assertion.py +18 -17
- pymobiledevice3/cli/processes.py +17 -23
- pymobiledevice3/cli/profile.py +165 -109
- pymobiledevice3/cli/provision.py +35 -34
- pymobiledevice3/cli/remote.py +217 -129
- pymobiledevice3/cli/restore.py +159 -143
- pymobiledevice3/cli/springboard.py +63 -53
- pymobiledevice3/cli/syslog.py +193 -86
- pymobiledevice3/cli/usbmux.py +73 -33
- pymobiledevice3/cli/version.py +5 -7
- pymobiledevice3/cli/webinspector.py +376 -214
- pymobiledevice3/common.py +3 -1
- pymobiledevice3/exceptions.py +182 -58
- pymobiledevice3/irecv.py +52 -53
- pymobiledevice3/irecv_devices.py +1489 -464
- pymobiledevice3/lockdown.py +473 -275
- pymobiledevice3/lockdown_service_provider.py +15 -8
- pymobiledevice3/osu/os_utils.py +27 -9
- pymobiledevice3/osu/posix_util.py +34 -15
- pymobiledevice3/osu/win_util.py +14 -8
- pymobiledevice3/pair_records.py +102 -21
- pymobiledevice3/remote/common.py +8 -4
- pymobiledevice3/remote/core_device/app_service.py +94 -67
- pymobiledevice3/remote/core_device/core_device_service.py +17 -14
- pymobiledevice3/remote/core_device/device_info.py +5 -5
- pymobiledevice3/remote/core_device/diagnostics_service.py +19 -4
- pymobiledevice3/remote/core_device/file_service.py +53 -23
- pymobiledevice3/remote/remote_service_discovery.py +79 -45
- pymobiledevice3/remote/remotexpc.py +73 -44
- pymobiledevice3/remote/tunnel_service.py +442 -317
- pymobiledevice3/remote/utils.py +14 -13
- pymobiledevice3/remote/xpc_message.py +145 -125
- pymobiledevice3/resources/dsc_uuid_map.py +19 -19
- pymobiledevice3/resources/firmware_notifications.py +20 -16
- pymobiledevice3/resources/notifications.txt +144 -0
- pymobiledevice3/restore/asr.py +27 -27
- pymobiledevice3/restore/base_restore.py +110 -21
- pymobiledevice3/restore/consts.py +87 -66
- pymobiledevice3/restore/device.py +59 -12
- pymobiledevice3/restore/fdr.py +46 -48
- pymobiledevice3/restore/ftab.py +19 -19
- pymobiledevice3/restore/img4.py +163 -0
- pymobiledevice3/restore/mbn.py +587 -0
- pymobiledevice3/restore/recovery.py +151 -151
- pymobiledevice3/restore/restore.py +562 -544
- pymobiledevice3/restore/restore_options.py +131 -110
- pymobiledevice3/restore/restored_client.py +51 -31
- pymobiledevice3/restore/tss.py +385 -267
- pymobiledevice3/service_connection.py +252 -59
- pymobiledevice3/services/accessibilityaudit.py +202 -120
- pymobiledevice3/services/afc.py +962 -365
- pymobiledevice3/services/amfi.py +24 -30
- pymobiledevice3/services/companion.py +23 -19
- pymobiledevice3/services/crash_reports.py +71 -47
- pymobiledevice3/services/debugserver_applist.py +3 -3
- pymobiledevice3/services/device_arbitration.py +8 -8
- pymobiledevice3/services/device_link.py +101 -79
- pymobiledevice3/services/diagnostics.py +973 -967
- pymobiledevice3/services/dtfetchsymbols.py +8 -8
- pymobiledevice3/services/dvt/dvt_secure_socket_proxy.py +4 -4
- pymobiledevice3/services/dvt/dvt_testmanaged_proxy.py +4 -4
- pymobiledevice3/services/dvt/instruments/activity_trace_tap.py +85 -74
- pymobiledevice3/services/dvt/instruments/application_listing.py +2 -3
- pymobiledevice3/services/dvt/instruments/condition_inducer.py +7 -6
- pymobiledevice3/services/dvt/instruments/core_profile_session_tap.py +466 -384
- pymobiledevice3/services/dvt/instruments/device_info.py +20 -11
- pymobiledevice3/services/dvt/instruments/energy_monitor.py +1 -1
- pymobiledevice3/services/dvt/instruments/graphics.py +1 -1
- pymobiledevice3/services/dvt/instruments/location_simulation.py +1 -1
- pymobiledevice3/services/dvt/instruments/location_simulation_base.py +10 -10
- pymobiledevice3/services/dvt/instruments/network_monitor.py +17 -17
- pymobiledevice3/services/dvt/instruments/notifications.py +1 -1
- pymobiledevice3/services/dvt/instruments/process_control.py +35 -10
- pymobiledevice3/services/dvt/instruments/screenshot.py +2 -2
- pymobiledevice3/services/dvt/instruments/sysmontap.py +15 -15
- pymobiledevice3/services/dvt/testmanaged/xcuitest.py +42 -52
- pymobiledevice3/services/file_relay.py +10 -10
- pymobiledevice3/services/heartbeat.py +9 -8
- pymobiledevice3/services/house_arrest.py +16 -15
- pymobiledevice3/services/idam.py +20 -0
- pymobiledevice3/services/installation_proxy.py +173 -81
- pymobiledevice3/services/lockdown_service.py +20 -10
- pymobiledevice3/services/misagent.py +22 -19
- pymobiledevice3/services/mobile_activation.py +147 -64
- pymobiledevice3/services/mobile_config.py +331 -294
- pymobiledevice3/services/mobile_image_mounter.py +141 -113
- pymobiledevice3/services/mobilebackup2.py +203 -145
- pymobiledevice3/services/notification_proxy.py +11 -11
- pymobiledevice3/services/os_trace.py +134 -74
- pymobiledevice3/services/pcapd.py +314 -302
- pymobiledevice3/services/power_assertion.py +10 -9
- pymobiledevice3/services/preboard.py +4 -4
- pymobiledevice3/services/remote_fetch_symbols.py +21 -14
- pymobiledevice3/services/remote_server.py +176 -146
- pymobiledevice3/services/restore_service.py +16 -16
- pymobiledevice3/services/screenshot.py +15 -12
- pymobiledevice3/services/simulate_location.py +7 -7
- pymobiledevice3/services/springboard.py +15 -15
- pymobiledevice3/services/syslog.py +5 -5
- pymobiledevice3/services/web_protocol/alert.py +11 -11
- pymobiledevice3/services/web_protocol/automation_session.py +251 -239
- pymobiledevice3/services/web_protocol/cdp_screencast.py +46 -37
- pymobiledevice3/services/web_protocol/cdp_server.py +19 -19
- pymobiledevice3/services/web_protocol/cdp_target.py +411 -373
- pymobiledevice3/services/web_protocol/driver.py +114 -111
- pymobiledevice3/services/web_protocol/element.py +124 -111
- pymobiledevice3/services/web_protocol/inspector_session.py +106 -102
- pymobiledevice3/services/web_protocol/selenium_api.py +49 -49
- pymobiledevice3/services/web_protocol/session_protocol.py +18 -12
- pymobiledevice3/services/web_protocol/switch_to.py +30 -27
- pymobiledevice3/services/webinspector.py +189 -155
- pymobiledevice3/tcp_forwarder.py +87 -69
- pymobiledevice3/tunneld/__init__.py +0 -0
- pymobiledevice3/tunneld/api.py +63 -0
- pymobiledevice3/tunneld/server.py +603 -0
- pymobiledevice3/usbmux.py +198 -147
- pymobiledevice3/utils.py +14 -11
- {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/METADATA +55 -28
- pymobiledevice3-7.0.6.dist-info/RECORD +188 -0
- {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/WHEEL +1 -1
- pymobiledevice3/cli/developer.py +0 -1215
- pymobiledevice3/cli/diagnostics.py +0 -99
- pymobiledevice3/tunneld.py +0 -524
- pymobiledevice3-4.14.6.dist-info/RECORD +0 -168
- {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/entry_points.txt +0 -0
- {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info/licenses}/LICENSE +0 -0
- {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/top_level.txt +0 -0
pymobiledevice3/cli/apps.py
CHANGED
|
@@ -1,59 +1,104 @@
|
|
|
1
|
-
import
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Annotated, Literal
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
from
|
|
5
|
-
|
|
4
|
+
import typer
|
|
5
|
+
from typer_injector import InjectingTyper
|
|
6
|
+
|
|
7
|
+
from pymobiledevice3.cli.cli_common import ServiceProviderDep, print_json
|
|
6
8
|
from pymobiledevice3.services.house_arrest import HouseArrestService
|
|
7
9
|
from pymobiledevice3.services.installation_proxy import InstallationProxyService
|
|
8
10
|
|
|
11
|
+
cli = InjectingTyper(
|
|
12
|
+
name="apps",
|
|
13
|
+
help="List, query, install, uninstall, and inspect apps on the device.",
|
|
14
|
+
no_args_is_help=True,
|
|
15
|
+
)
|
|
16
|
+
|
|
9
17
|
|
|
10
|
-
@
|
|
11
|
-
def
|
|
12
|
-
|
|
18
|
+
@cli.command("list")
|
|
19
|
+
def apps_list(
|
|
20
|
+
service_provider: ServiceProviderDep,
|
|
21
|
+
app_type: Annotated[
|
|
22
|
+
Literal["System", "User", "Hidden", "Any"],
|
|
23
|
+
typer.Option(
|
|
24
|
+
"--type",
|
|
25
|
+
"-t",
|
|
26
|
+
help="Filter by application type (System/User/Hidden/Any).",
|
|
27
|
+
),
|
|
28
|
+
] = "Any",
|
|
29
|
+
calculate_sizes: Annotated[
|
|
30
|
+
bool,
|
|
31
|
+
typer.Option(help="Include app size information (slower)."),
|
|
32
|
+
] = False,
|
|
33
|
+
) -> None:
|
|
34
|
+
"""List installed apps."""
|
|
35
|
+
print_json(
|
|
36
|
+
InstallationProxyService(lockdown=service_provider).get_apps(
|
|
37
|
+
application_type=app_type, calculate_sizes=calculate_sizes
|
|
38
|
+
)
|
|
39
|
+
)
|
|
13
40
|
|
|
14
41
|
|
|
15
|
-
@cli.
|
|
16
|
-
def
|
|
17
|
-
|
|
18
|
-
|
|
42
|
+
@cli.command("query")
|
|
43
|
+
def apps_query(
|
|
44
|
+
service_provider: ServiceProviderDep,
|
|
45
|
+
bundle_identifiers: list[str],
|
|
46
|
+
calculate_sizes: Annotated[
|
|
47
|
+
bool,
|
|
48
|
+
typer.Option(help="Include app size information (slower)."),
|
|
49
|
+
] = False,
|
|
50
|
+
) -> None:
|
|
51
|
+
"""Return metadata for specific bundle identifiers."""
|
|
52
|
+
print_json(
|
|
53
|
+
InstallationProxyService(lockdown=service_provider).get_apps(
|
|
54
|
+
calculate_sizes=calculate_sizes, bundle_identifiers=bundle_identifiers
|
|
55
|
+
)
|
|
56
|
+
)
|
|
19
57
|
|
|
20
58
|
|
|
21
|
-
@
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def apps_list(service_provider: LockdownServiceProvider, app_type: str, calculate_sizes: bool) -> None:
|
|
26
|
-
""" list installed apps """
|
|
27
|
-
print_json(InstallationProxyService(lockdown=service_provider).get_apps(application_type=app_type,
|
|
28
|
-
calculate_sizes=calculate_sizes))
|
|
59
|
+
@cli.command("uninstall")
|
|
60
|
+
def uninstall(service_provider: ServiceProviderDep, bundle_id: str) -> None:
|
|
61
|
+
"""Uninstall an app by bundle identifier."""
|
|
62
|
+
InstallationProxyService(lockdown=service_provider).uninstall(bundle_id)
|
|
29
63
|
|
|
30
64
|
|
|
31
|
-
@
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
65
|
+
@cli.command("install")
|
|
66
|
+
def install(
|
|
67
|
+
service_provider: ServiceProviderDep,
|
|
68
|
+
package: Annotated[
|
|
69
|
+
Path,
|
|
70
|
+
typer.Argument(exists=True),
|
|
71
|
+
],
|
|
72
|
+
developer: Annotated[
|
|
73
|
+
bool,
|
|
74
|
+
typer.Option(help="Install developer package"),
|
|
75
|
+
] = False,
|
|
76
|
+
) -> None:
|
|
77
|
+
"""Install a local .ipa/.app/.ipcc package."""
|
|
78
|
+
InstallationProxyService(lockdown=service_provider).install_from_local(package, developer=developer)
|
|
38
79
|
|
|
39
80
|
|
|
40
|
-
@
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
81
|
+
@cli.command("afc")
|
|
82
|
+
def afc(
|
|
83
|
+
service_provider: ServiceProviderDep, bundle_id: str, documents: Annotated[bool, typer.Option()] = False
|
|
84
|
+
) -> None:
|
|
85
|
+
"""Open an AFC shell into the app container; pass --documents for Documents-only."""
|
|
86
|
+
HouseArrestService(lockdown=service_provider, bundle_id=bundle_id, documents_only=documents).shell()
|
|
45
87
|
|
|
46
88
|
|
|
47
|
-
@
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
InstallationProxyService(lockdown=service_provider).install_from_local(package)
|
|
89
|
+
@cli.command("pull")
|
|
90
|
+
def pull(service_provider: ServiceProviderDep, bundle_id: str, remote_file: Path, local_file: Path) -> None:
|
|
91
|
+
"""Pull a file from an app container to a local path."""
|
|
92
|
+
HouseArrestService(lockdown=service_provider, bundle_id=bundle_id).pull(str(remote_file), str(local_file))
|
|
52
93
|
|
|
53
94
|
|
|
54
|
-
@
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
95
|
+
@cli.command("push")
|
|
96
|
+
def push(service_provider: ServiceProviderDep, bundle_id: str, local_file: Path, remote_file: Path) -> None:
|
|
97
|
+
"""Push a local file into an app container."""
|
|
98
|
+
HouseArrestService(lockdown=service_provider, bundle_id=bundle_id).push(str(local_file), str(remote_file))
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@cli.command("rm")
|
|
102
|
+
def rm(service_provider: ServiceProviderDep, bundle_id: str, remote_file: Path) -> None:
|
|
103
|
+
"""Delete a file from an app container."""
|
|
104
|
+
HouseArrestService(lockdown=service_provider, bundle_id=bundle_id).rm(str(remote_file))
|
pymobiledevice3/cli/backup.py
CHANGED
|
@@ -1,37 +1,65 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Annotated, Literal
|
|
2
4
|
|
|
3
|
-
import
|
|
5
|
+
import typer
|
|
4
6
|
from tqdm import tqdm
|
|
7
|
+
from typer_injector import InjectingTyper
|
|
5
8
|
|
|
6
|
-
from pymobiledevice3.cli.cli_common import
|
|
7
|
-
from pymobiledevice3.lockdown import LockdownClient
|
|
9
|
+
from pymobiledevice3.cli.cli_common import ServiceProviderDep
|
|
8
10
|
from pymobiledevice3.services.mobilebackup2 import Mobilebackup2Service
|
|
9
11
|
|
|
10
|
-
source_option = click.option('--source', default='', help='The UDID of the source device.')
|
|
11
|
-
password_option = click.option('-p', '--password', default='', help='Backup password.')
|
|
12
|
-
backup_directory_arg = click.argument('backup-directory', type=click.Path(exists=True, file_okay=False))
|
|
13
|
-
backup_directory_option = click.option('-b', '--backup-directory', type=click.Path(exists=True, file_okay=False),
|
|
14
|
-
default='.')
|
|
15
|
-
|
|
16
12
|
logger = logging.getLogger(__name__)
|
|
17
13
|
|
|
18
14
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
15
|
+
cli = InjectingTyper(
|
|
16
|
+
name="backup2",
|
|
17
|
+
help="Create, inspect, and restore MobileBackup2 backups.",
|
|
18
|
+
no_args_is_help=True,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
SourceOption = Annotated[
|
|
23
|
+
str,
|
|
24
|
+
typer.Option(help="The UDID of the source device."),
|
|
25
|
+
]
|
|
26
|
+
PasswordOption = Annotated[
|
|
27
|
+
str,
|
|
28
|
+
typer.Option(
|
|
29
|
+
"--password",
|
|
30
|
+
"-p",
|
|
31
|
+
help="Backup password.",
|
|
32
|
+
),
|
|
33
|
+
]
|
|
34
|
+
BackupDirectoryArg = Annotated[
|
|
35
|
+
Path,
|
|
36
|
+
typer.Argument(
|
|
37
|
+
exists=True,
|
|
38
|
+
file_okay=False,
|
|
39
|
+
),
|
|
40
|
+
]
|
|
41
|
+
BackupDirectoryOption = Annotated[
|
|
42
|
+
Path,
|
|
43
|
+
typer.Option(
|
|
44
|
+
"--backup-directory",
|
|
45
|
+
"-b",
|
|
46
|
+
exists=True,
|
|
47
|
+
file_okay=False,
|
|
48
|
+
),
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@cli.command()
|
|
53
|
+
def backup(
|
|
54
|
+
service_provider: ServiceProviderDep,
|
|
55
|
+
backup_directory: BackupDirectoryArg,
|
|
56
|
+
full: Annotated[
|
|
57
|
+
bool,
|
|
58
|
+
typer.Option(
|
|
59
|
+
help="Whether to do a full backup. If full is True, any previous backup attempts will be discarded.",
|
|
60
|
+
),
|
|
61
|
+
],
|
|
62
|
+
) -> None:
|
|
35
63
|
"""
|
|
36
64
|
Backup device.
|
|
37
65
|
|
|
@@ -39,23 +67,45 @@ def backup(service_provider: LockdownClient, backup_directory, full):
|
|
|
39
67
|
"""
|
|
40
68
|
backup_client = Mobilebackup2Service(service_provider)
|
|
41
69
|
with tqdm(total=100, dynamic_ncols=True) as pbar:
|
|
42
|
-
|
|
70
|
+
|
|
71
|
+
def update_bar(percentage) -> None:
|
|
43
72
|
pbar.n = percentage
|
|
44
73
|
pbar.refresh()
|
|
45
74
|
|
|
46
|
-
backup_client.backup(full=full, backup_directory=backup_directory, progress_callback=update_bar)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
@
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
75
|
+
backup_client.backup(full=full, backup_directory=str(backup_directory), progress_callback=update_bar)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@cli.command()
|
|
79
|
+
def restore(
|
|
80
|
+
service_provider: ServiceProviderDep,
|
|
81
|
+
backup_directory: BackupDirectoryArg,
|
|
82
|
+
system: Annotated[
|
|
83
|
+
bool,
|
|
84
|
+
typer.Option(help="Restore system files."),
|
|
85
|
+
] = False,
|
|
86
|
+
reboot: Annotated[
|
|
87
|
+
bool,
|
|
88
|
+
typer.Option(help="Reboot the device when done."),
|
|
89
|
+
] = True,
|
|
90
|
+
copy: Annotated[
|
|
91
|
+
bool,
|
|
92
|
+
typer.Option(help="Create a copy of backup folder before restoring."),
|
|
93
|
+
] = False,
|
|
94
|
+
settings: Annotated[
|
|
95
|
+
bool,
|
|
96
|
+
typer.Option(help="Restore device settings."),
|
|
97
|
+
] = True,
|
|
98
|
+
remove: Annotated[
|
|
99
|
+
bool,
|
|
100
|
+
typer.Option(help="Remove items which aren't being restored."),
|
|
101
|
+
] = False,
|
|
102
|
+
skip_apps: Annotated[
|
|
103
|
+
bool,
|
|
104
|
+
typer.Option(help="Do not trigger re-installation of apps after restore."),
|
|
105
|
+
] = False,
|
|
106
|
+
password: PasswordOption = "",
|
|
107
|
+
source: SourceOption = "",
|
|
108
|
+
) -> None:
|
|
59
109
|
"""
|
|
60
110
|
Restore a backup to a device.
|
|
61
111
|
|
|
@@ -63,56 +113,68 @@ def restore(service_provider: LockdownClient, backup_directory, system, reboot,
|
|
|
63
113
|
"""
|
|
64
114
|
backup_client = Mobilebackup2Service(service_provider)
|
|
65
115
|
with tqdm(total=100, dynamic_ncols=True) as pbar:
|
|
66
|
-
|
|
116
|
+
|
|
117
|
+
def update_bar(percentage) -> None:
|
|
67
118
|
pbar.n = percentage
|
|
68
119
|
pbar.refresh()
|
|
69
120
|
|
|
70
|
-
backup_client.restore(
|
|
71
|
-
|
|
72
|
-
|
|
121
|
+
backup_client.restore(
|
|
122
|
+
backup_directory=str(backup_directory),
|
|
123
|
+
progress_callback=update_bar,
|
|
124
|
+
system=system,
|
|
125
|
+
reboot=reboot,
|
|
126
|
+
copy=copy,
|
|
127
|
+
settings=settings,
|
|
128
|
+
remove=remove,
|
|
129
|
+
password=password,
|
|
130
|
+
source=source,
|
|
131
|
+
skip_apps=skip_apps,
|
|
132
|
+
)
|
|
73
133
|
|
|
74
134
|
|
|
75
|
-
@
|
|
76
|
-
|
|
77
|
-
@source_option
|
|
78
|
-
def info(service_provider: LockdownClient, backup_directory, source):
|
|
135
|
+
@cli.command()
|
|
136
|
+
def info(service_provider: ServiceProviderDep, backup_directory: BackupDirectoryArg, source: SourceOption = "") -> None:
|
|
79
137
|
"""
|
|
80
138
|
Print information about a backup.
|
|
81
139
|
"""
|
|
82
140
|
backup_client = Mobilebackup2Service(service_provider)
|
|
83
|
-
print(backup_client.info(backup_directory=backup_directory, source=source))
|
|
141
|
+
print(backup_client.info(backup_directory=str(backup_directory), source=source))
|
|
84
142
|
|
|
85
143
|
|
|
86
|
-
@
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
144
|
+
@cli.command("list")
|
|
145
|
+
def list_(
|
|
146
|
+
service_provider: ServiceProviderDep, backup_directory: BackupDirectoryArg, source: SourceOption = ""
|
|
147
|
+
) -> None:
|
|
90
148
|
"""
|
|
91
149
|
List all file in the backup in a CSV format.
|
|
92
150
|
"""
|
|
93
151
|
backup_client = Mobilebackup2Service(service_provider)
|
|
94
|
-
print(backup_client.list(backup_directory=backup_directory, source=source))
|
|
152
|
+
print(backup_client.list(backup_directory=str(backup_directory), source=source))
|
|
95
153
|
|
|
96
154
|
|
|
97
|
-
@
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
155
|
+
@cli.command()
|
|
156
|
+
def unback(
|
|
157
|
+
service_provider: ServiceProviderDep,
|
|
158
|
+
backup_directory: BackupDirectoryArg,
|
|
159
|
+
password: PasswordOption = "",
|
|
160
|
+
source: SourceOption = "",
|
|
161
|
+
) -> None:
|
|
102
162
|
"""
|
|
103
163
|
Convert all files in the backup to the correct directory hierarchy.
|
|
104
164
|
"""
|
|
105
165
|
backup_client = Mobilebackup2Service(service_provider)
|
|
106
|
-
backup_client.unback(backup_directory=backup_directory, password=password, source=source)
|
|
166
|
+
backup_client.unback(backup_directory=str(backup_directory), password=password, source=source)
|
|
107
167
|
|
|
108
168
|
|
|
109
|
-
@
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
169
|
+
@cli.command()
|
|
170
|
+
def extract(
|
|
171
|
+
service_provider: ServiceProviderDep,
|
|
172
|
+
domain_name: str,
|
|
173
|
+
relative_path: str,
|
|
174
|
+
backup_directory: BackupDirectoryArg,
|
|
175
|
+
password: PasswordOption = "",
|
|
176
|
+
source: SourceOption = "",
|
|
177
|
+
) -> None:
|
|
116
178
|
"""
|
|
117
179
|
Extract a file from the backup.
|
|
118
180
|
|
|
@@ -120,15 +182,22 @@ def extract(service_provider: LockdownClient, domain_name, relative_path, backup
|
|
|
120
182
|
will be extracted to the BACKUP_DIRECTORY.
|
|
121
183
|
"""
|
|
122
184
|
backup_client = Mobilebackup2Service(service_provider)
|
|
123
|
-
backup_client.extract(
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
@
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
185
|
+
backup_client.extract(
|
|
186
|
+
domain_name, relative_path, backup_directory=str(backup_directory), password=password, source=source
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
@cli.command()
|
|
191
|
+
def encryption(
|
|
192
|
+
service_provider: ServiceProviderDep,
|
|
193
|
+
*,
|
|
194
|
+
backup_directory: BackupDirectoryOption = Path("."),
|
|
195
|
+
mode: Annotated[
|
|
196
|
+
Literal["on", "off"],
|
|
197
|
+
typer.Argument(case_sensitive=False),
|
|
198
|
+
],
|
|
199
|
+
password: str,
|
|
200
|
+
) -> None:
|
|
132
201
|
"""
|
|
133
202
|
Set backup encryption on / off.
|
|
134
203
|
|
|
@@ -136,36 +205,37 @@ def encryption(service_provider: LockdownClient, backup_directory, mode, passwor
|
|
|
136
205
|
When off, PASSWORD is the current backup password.
|
|
137
206
|
"""
|
|
138
207
|
backup_client = Mobilebackup2Service(service_provider)
|
|
139
|
-
should_encrypt = mode.lower() ==
|
|
208
|
+
should_encrypt = mode.lower() == "on"
|
|
140
209
|
if should_encrypt == backup_client.will_encrypt:
|
|
141
|
-
logger.error(
|
|
210
|
+
logger.error("Encryption already " + ("on!" if should_encrypt else "off!"))
|
|
142
211
|
return
|
|
143
212
|
if should_encrypt:
|
|
144
|
-
backup_client.change_password(backup_directory, new=password)
|
|
213
|
+
backup_client.change_password(str(backup_directory), new=password)
|
|
145
214
|
else:
|
|
146
|
-
backup_client.change_password(backup_directory, old=password)
|
|
215
|
+
backup_client.change_password(str(backup_directory), old=password)
|
|
147
216
|
|
|
148
217
|
|
|
149
|
-
@
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
218
|
+
@cli.command()
|
|
219
|
+
def change_password(
|
|
220
|
+
service_provider: ServiceProviderDep,
|
|
221
|
+
old_password: str,
|
|
222
|
+
new_password: str,
|
|
223
|
+
backup_directory: BackupDirectoryOption = Path("."),
|
|
224
|
+
) -> None:
|
|
154
225
|
"""
|
|
155
226
|
Change the backup password.
|
|
156
227
|
"""
|
|
157
228
|
backup_client = Mobilebackup2Service(service_provider)
|
|
158
229
|
if not backup_client.will_encrypt:
|
|
159
|
-
logger.error(
|
|
230
|
+
logger.error("Encryption is not turned on!")
|
|
160
231
|
return
|
|
161
|
-
backup_client.change_password(backup_directory, old=old_password, new=new_password)
|
|
232
|
+
backup_client.change_password(str(backup_directory), old=old_password, new=new_password)
|
|
162
233
|
|
|
163
234
|
|
|
164
|
-
@
|
|
165
|
-
|
|
166
|
-
def erase_device(service_provider: LockdownClient, backup_directory):
|
|
235
|
+
@cli.command()
|
|
236
|
+
def erase_device(service_provider: ServiceProviderDep, backup_directory: BackupDirectoryArg) -> None:
|
|
167
237
|
"""
|
|
168
238
|
Erase all data on the device.
|
|
169
239
|
"""
|
|
170
240
|
backup_client = Mobilebackup2Service(service_provider)
|
|
171
|
-
backup_client.erase_device(backup_directory)
|
|
241
|
+
backup_client.erase_device(str(backup_directory))
|
pymobiledevice3/cli/bonjour.py
CHANGED
|
@@ -1,75 +1,79 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
import plistlib
|
|
3
2
|
from pathlib import Path
|
|
3
|
+
from typing import Annotated, Optional
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import typer
|
|
6
|
+
from typer_injector import InjectingTyper
|
|
6
7
|
|
|
7
8
|
from pymobiledevice3.bonjour import DEFAULT_BONJOUR_TIMEOUT, browse_remotepairing, browse_remotepairing_manual_pairing
|
|
8
|
-
from pymobiledevice3.cli.cli_common import
|
|
9
|
+
from pymobiledevice3.cli.cli_common import print_json
|
|
9
10
|
from pymobiledevice3.cli.remote import browse_rsd
|
|
10
11
|
from pymobiledevice3.lockdown import get_mobdev2_lockdowns
|
|
11
12
|
|
|
13
|
+
cli = InjectingTyper(
|
|
14
|
+
name="bonjour",
|
|
15
|
+
help="Browse devices over bonjour",
|
|
16
|
+
no_args_is_help=True,
|
|
17
|
+
)
|
|
12
18
|
|
|
13
|
-
@click.group()
|
|
14
|
-
def cli() -> None:
|
|
15
|
-
pass
|
|
16
19
|
|
|
17
|
-
|
|
18
|
-
@cli.group('bonjour')
|
|
19
|
-
def bonjour_cli() -> None:
|
|
20
|
-
""" Browse devices over bonjour """
|
|
21
|
-
pass
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
async def cli_mobdev2_task(timeout: float, pair_records: str) -> None:
|
|
25
|
-
records = []
|
|
26
|
-
if pair_records is not None:
|
|
27
|
-
for record in Path(pair_records).glob('*.plist'):
|
|
28
|
-
records.append(plistlib.loads(record.read_bytes()))
|
|
20
|
+
async def cli_mobdev2_task(timeout: float, pair_records: Optional[Path]) -> None:
|
|
29
21
|
output = []
|
|
30
|
-
async for ip, lockdown in get_mobdev2_lockdowns(timeout=timeout):
|
|
22
|
+
async for ip, lockdown in get_mobdev2_lockdowns(timeout=timeout, pair_records=pair_records):
|
|
31
23
|
short_info = lockdown.short_info
|
|
32
|
-
short_info[
|
|
24
|
+
short_info["ip"] = ip
|
|
33
25
|
output.append(short_info)
|
|
34
26
|
print_json(output)
|
|
35
27
|
|
|
36
28
|
|
|
37
|
-
@
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
29
|
+
@cli.command("mobdev2")
|
|
30
|
+
def cli_mobdev2(
|
|
31
|
+
timeout: Annotated[float, typer.Option()] = DEFAULT_BONJOUR_TIMEOUT,
|
|
32
|
+
pair_records: Annotated[
|
|
33
|
+
Optional[Path],
|
|
34
|
+
typer.Option(
|
|
35
|
+
exists=True,
|
|
36
|
+
dir_okay=True,
|
|
37
|
+
file_okay=True,
|
|
38
|
+
help="pair records to attempt validation with",
|
|
39
|
+
),
|
|
40
|
+
] = None,
|
|
41
|
+
) -> None:
|
|
42
|
+
"""browse for mobdev2 devices over bonjour"""
|
|
43
43
|
asyncio.run(cli_mobdev2_task(timeout, pair_records))
|
|
44
44
|
|
|
45
45
|
|
|
46
46
|
async def cli_remotepairing_task(timeout: float) -> None:
|
|
47
47
|
output = []
|
|
48
48
|
for answer in await browse_remotepairing(timeout=timeout):
|
|
49
|
-
for
|
|
50
|
-
output.append({
|
|
49
|
+
for address in answer.addresses:
|
|
50
|
+
output.append({"hostname": address.full_ip, "port": answer.port})
|
|
51
51
|
print_json(output)
|
|
52
52
|
|
|
53
53
|
|
|
54
|
-
@
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
""" browse for remotepairing devices over bonjour (without attempting pair verification) """
|
|
54
|
+
@cli.command("remotepairing")
|
|
55
|
+
def cli_remotepairing(timeout: Annotated[float, typer.Option()] = DEFAULT_BONJOUR_TIMEOUT) -> None:
|
|
56
|
+
"""browse for remotepairing devices over bonjour (without attempting pair verification)"""
|
|
58
57
|
asyncio.run(cli_remotepairing_task(timeout=timeout))
|
|
59
58
|
|
|
60
59
|
|
|
61
60
|
async def cli_remotepairing_manual_pairing_task(timeout: float) -> None:
|
|
62
61
|
output = []
|
|
63
62
|
for answer in await browse_remotepairing_manual_pairing(timeout=timeout):
|
|
64
|
-
for
|
|
65
|
-
output.append({
|
|
63
|
+
for address in answer.addresses:
|
|
64
|
+
output.append({
|
|
65
|
+
"hostname": address.full_ip,
|
|
66
|
+
"port": answer.port,
|
|
67
|
+
"name": answer.properties[b"name"].decode(),
|
|
68
|
+
})
|
|
66
69
|
print_json(output)
|
|
67
70
|
|
|
68
71
|
|
|
69
|
-
@
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
72
|
+
@cli.command("remotepairing-manual-pairing")
|
|
73
|
+
def cli_remotepairing_manual_pairing(
|
|
74
|
+
timeout: Annotated[float, typer.Option()] = DEFAULT_BONJOUR_TIMEOUT,
|
|
75
|
+
) -> None:
|
|
76
|
+
"""browse for remotepairing-manual-pairing devices over bonjour"""
|
|
73
77
|
asyncio.run(cli_remotepairing_manual_pairing_task(timeout=timeout))
|
|
74
78
|
|
|
75
79
|
|
|
@@ -77,7 +81,7 @@ async def cli_browse_rsd() -> None:
|
|
|
77
81
|
print_json(await browse_rsd())
|
|
78
82
|
|
|
79
83
|
|
|
80
|
-
@
|
|
84
|
+
@cli.command("rsd")
|
|
81
85
|
def cli_rsd() -> None:
|
|
82
|
-
"""
|
|
86
|
+
"""browse RemoteXPC devices using bonjour"""
|
|
83
87
|
asyncio.run(cli_browse_rsd(), debug=True)
|