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
|
@@ -1,38 +1,39 @@
|
|
|
1
|
-
from pymobiledevice3.exceptions import PyMobileDevice3Exception
|
|
1
|
+
from pymobiledevice3.exceptions import AppNotInstalledError, PyMobileDevice3Exception
|
|
2
2
|
from pymobiledevice3.lockdown import LockdownClient
|
|
3
3
|
from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
|
|
4
4
|
from pymobiledevice3.services.afc import AfcService, AfcShell
|
|
5
5
|
|
|
6
|
-
VEND_CONTAINER =
|
|
7
|
-
VEND_DOCUMENTS =
|
|
6
|
+
VEND_CONTAINER = "VendContainer"
|
|
7
|
+
VEND_DOCUMENTS = "VendDocuments"
|
|
8
8
|
|
|
9
|
-
DOCUMENTS_ROOT =
|
|
9
|
+
DOCUMENTS_ROOT = "/Documents"
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class HouseArrestService(AfcService):
|
|
13
|
-
SERVICE_NAME =
|
|
14
|
-
RSD_SERVICE_NAME =
|
|
13
|
+
SERVICE_NAME = "com.apple.mobile.house_arrest"
|
|
14
|
+
RSD_SERVICE_NAME = "com.apple.mobile.house_arrest.shim.remote"
|
|
15
15
|
|
|
16
16
|
def __init__(self, lockdown: LockdownServiceProvider, bundle_id: str, documents_only: bool = False):
|
|
17
17
|
if isinstance(lockdown, LockdownClient):
|
|
18
18
|
super().__init__(lockdown, self.SERVICE_NAME)
|
|
19
19
|
else:
|
|
20
20
|
super().__init__(lockdown, self.RSD_SERVICE_NAME)
|
|
21
|
-
if documents_only
|
|
22
|
-
cmd = VEND_DOCUMENTS
|
|
23
|
-
else:
|
|
24
|
-
cmd = VEND_CONTAINER
|
|
21
|
+
cmd = VEND_DOCUMENTS if documents_only else VEND_CONTAINER
|
|
25
22
|
self.documents_only = documents_only
|
|
26
23
|
try:
|
|
27
24
|
self.send_command(bundle_id, cmd)
|
|
28
25
|
except PyMobileDevice3Exception:
|
|
29
26
|
self.close()
|
|
27
|
+
raise
|
|
30
28
|
|
|
31
|
-
def send_command(self, bundle_id: str, cmd: str =
|
|
32
|
-
response = self.service.send_recv_plist({
|
|
33
|
-
error = response.get(
|
|
29
|
+
def send_command(self, bundle_id: str, cmd: str = "VendContainer") -> None:
|
|
30
|
+
response = self.service.send_recv_plist({"Command": cmd, "Identifier": bundle_id})
|
|
31
|
+
error = response.get("Error")
|
|
34
32
|
if error:
|
|
35
|
-
|
|
33
|
+
if error == "ApplicationLookupFailed":
|
|
34
|
+
raise AppNotInstalledError(f"No app with bundle id {bundle_id} found")
|
|
35
|
+
else:
|
|
36
|
+
raise PyMobileDevice3Exception(error)
|
|
36
37
|
|
|
37
38
|
def shell(self) -> None:
|
|
38
|
-
AfcShell.create(self.lockdown, service=self, auto_cd=DOCUMENTS_ROOT if self.documents_only else
|
|
39
|
+
AfcShell.create(self.lockdown, service=self, auto_cd=DOCUMENTS_ROOT if self.documents_only else "/")
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from pymobiledevice3.lockdown import LockdownClient
|
|
2
|
+
from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
|
|
3
|
+
from pymobiledevice3.services.lockdown_service import LockdownService
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class IDAMService(LockdownService):
|
|
7
|
+
RSD_SERVICE_NAME = "com.apple.idamd.shim.remote"
|
|
8
|
+
SERVICE_NAME = "com.apple.idamd"
|
|
9
|
+
|
|
10
|
+
def __init__(self, lockdown: LockdownServiceProvider) -> None:
|
|
11
|
+
if isinstance(lockdown, LockdownClient):
|
|
12
|
+
super().__init__(lockdown, self.SERVICE_NAME)
|
|
13
|
+
else:
|
|
14
|
+
super().__init__(lockdown, self.RSD_SERVICE_NAME)
|
|
15
|
+
|
|
16
|
+
def configuration_inquiry(self) -> dict:
|
|
17
|
+
return self.service.send_recv_plist({"Configuration Inquiry": True})
|
|
18
|
+
|
|
19
|
+
def set_idam_configuration(self, value: bool) -> None:
|
|
20
|
+
self.service.send_recv_plist({"Set IDAM Configuration": value})
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import os
|
|
2
|
+
from enum import Enum
|
|
3
|
+
from io import BytesIO
|
|
2
4
|
from pathlib import Path
|
|
3
5
|
from tempfile import TemporaryDirectory
|
|
4
6
|
from typing import Callable, Optional
|
|
5
|
-
from zipfile import ZIP_DEFLATED, ZipFile
|
|
7
|
+
from zipfile import ZIP_DEFLATED, BadZipFile, ZipFile
|
|
6
8
|
|
|
7
9
|
from parameter_decorators import str_to_path
|
|
8
10
|
|
|
@@ -12,30 +14,60 @@ from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
|
|
|
12
14
|
from pymobiledevice3.services.afc import AfcService
|
|
13
15
|
from pymobiledevice3.services.lockdown_service import LockdownService
|
|
14
16
|
|
|
15
|
-
GET_APPS_ADDITIONAL_INFO = {
|
|
17
|
+
GET_APPS_ADDITIONAL_INFO = {"ReturnAttributes": ["CFBundleIdentifier", "StaticDiskUsage", "DynamicDiskUsage"]}
|
|
16
18
|
|
|
17
|
-
|
|
19
|
+
TEMP_REMOTE_BASEDIR = "/PublicStaging"
|
|
20
|
+
TEMP_REMOTE_IPA_FILE = f"{TEMP_REMOTE_BASEDIR}/pymobiledevice3.ipa"
|
|
21
|
+
TEMP_REMOTE_IPCC_FOLDER = f"{TEMP_REMOTE_BASEDIR}/pymobiledevice3.ipcc"
|
|
18
22
|
|
|
19
|
-
|
|
23
|
+
|
|
24
|
+
class ZipFileType(Enum):
|
|
25
|
+
IPCC = "ipcc"
|
|
26
|
+
IPA = "ipa"
|
|
27
|
+
|
|
28
|
+
def is_ipcc(self) -> bool:
|
|
29
|
+
return self == ZipFileType.IPCC
|
|
30
|
+
|
|
31
|
+
def is_ipa(self) -> bool:
|
|
32
|
+
return self == ZipFileType.IPA
|
|
20
33
|
|
|
21
34
|
|
|
22
35
|
def create_ipa_contents_from_directory(directory: str) -> bytes:
|
|
23
|
-
payload_prefix =
|
|
36
|
+
payload_prefix = "Payload/" + os.path.basename(directory)
|
|
24
37
|
with TemporaryDirectory() as temp_dir:
|
|
25
|
-
zip_path = Path(temp_dir) /
|
|
26
|
-
with ZipFile(zip_path,
|
|
27
|
-
for root,
|
|
38
|
+
zip_path = Path(temp_dir) / "ipa"
|
|
39
|
+
with ZipFile(zip_path, "w", ZIP_DEFLATED) as zip_file:
|
|
40
|
+
for root, _dirs, files in os.walk(directory):
|
|
28
41
|
for file in files:
|
|
29
42
|
full_path = Path(root) / file
|
|
30
43
|
full_path.touch()
|
|
31
|
-
zip_file.write(full_path,
|
|
32
|
-
arcname=f'{payload_prefix}/{os.path.relpath(full_path, directory)}')
|
|
44
|
+
zip_file.write(full_path, arcname=f"{payload_prefix}/{os.path.relpath(full_path, directory)}")
|
|
33
45
|
return zip_path.read_bytes()
|
|
34
46
|
|
|
35
47
|
|
|
48
|
+
def classify_zip_file(zip_bytes: bytes) -> ZipFileType:
|
|
49
|
+
"""checks the zipped bytes if it's a .ipcc or .ipa"""
|
|
50
|
+
try:
|
|
51
|
+
with ZipFile(BytesIO(zip_bytes), "r") as zip_file:
|
|
52
|
+
# sometimes packages at first index don't have enough infos to check
|
|
53
|
+
dirs = zip_file.namelist()[1].split("/")
|
|
54
|
+
|
|
55
|
+
if dirs[0] != "Payload":
|
|
56
|
+
raise AppInstallError("package does not have a payload")
|
|
57
|
+
if dirs[1].endswith(".app"):
|
|
58
|
+
return ZipFileType.IPA
|
|
59
|
+
elif dirs[1].endswith(".bundle"):
|
|
60
|
+
return ZipFileType.IPCC
|
|
61
|
+
else:
|
|
62
|
+
raise AppInstallError("package does not have the appropriate folders structure")
|
|
63
|
+
|
|
64
|
+
except BadZipFile as e:
|
|
65
|
+
raise AppInstallError("Invalid bytes package") from e
|
|
66
|
+
|
|
67
|
+
|
|
36
68
|
class InstallationProxyService(LockdownService):
|
|
37
|
-
SERVICE_NAME =
|
|
38
|
-
RSD_SERVICE_NAME =
|
|
69
|
+
SERVICE_NAME = "com.apple.mobile.installation_proxy"
|
|
70
|
+
RSD_SERVICE_NAME = "com.apple.mobile.installation_proxy.shim.remote"
|
|
39
71
|
|
|
40
72
|
def __init__(self, lockdown: LockdownServiceProvider):
|
|
41
73
|
if isinstance(lockdown, LockdownClient):
|
|
@@ -43,71 +75,111 @@ class InstallationProxyService(LockdownService):
|
|
|
43
75
|
else:
|
|
44
76
|
super().__init__(lockdown, self.RSD_SERVICE_NAME)
|
|
45
77
|
|
|
46
|
-
def _watch_completion(self, handler: Callable = None, ipcc: bool = False, *args) -> None:
|
|
78
|
+
def _watch_completion(self, handler: Optional[Callable] = None, ipcc: bool = False, *args) -> None:
|
|
47
79
|
while True:
|
|
48
80
|
response = self.service.recv_plist()
|
|
49
81
|
if not response:
|
|
50
82
|
break
|
|
51
|
-
error = response.get(
|
|
83
|
+
error = response.get("Error")
|
|
52
84
|
if error:
|
|
53
|
-
raise AppInstallError(f
|
|
54
|
-
completion = response.get(
|
|
85
|
+
raise AppInstallError(f"{error}: {response.get('ErrorDescription')}")
|
|
86
|
+
completion = response.get("PercentComplete")
|
|
55
87
|
if completion:
|
|
56
88
|
if handler:
|
|
57
|
-
self.logger.debug(
|
|
89
|
+
self.logger.debug("calling handler")
|
|
58
90
|
handler(completion, *args)
|
|
59
|
-
self.logger.info(f
|
|
60
|
-
if response.get(
|
|
91
|
+
self.logger.info(f"{response.get('PercentComplete')}% Complete")
|
|
92
|
+
if response.get("Status") == "Complete":
|
|
61
93
|
if ipcc:
|
|
62
94
|
# there is no progress when installing a .ipcc file,
|
|
63
95
|
# so we just put a simple message indicating it's done
|
|
64
|
-
self.logger.info(
|
|
96
|
+
self.logger.info("Installation succeed.")
|
|
65
97
|
return
|
|
66
98
|
raise AppInstallError()
|
|
67
99
|
|
|
68
|
-
def send_cmd_for_bundle_identifier(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
100
|
+
def send_cmd_for_bundle_identifier(
|
|
101
|
+
self,
|
|
102
|
+
bundle_identifier: str,
|
|
103
|
+
cmd: str = "Archive",
|
|
104
|
+
options: Optional[dict] = None,
|
|
105
|
+
handler: Optional[dict] = None,
|
|
106
|
+
*args,
|
|
107
|
+
) -> None:
|
|
108
|
+
"""send a low-level command to installation relay"""
|
|
109
|
+
cmd = {"Command": cmd, "ApplicationIdentifier": bundle_identifier}
|
|
74
110
|
|
|
75
111
|
if options is None:
|
|
76
112
|
options = {}
|
|
77
113
|
|
|
78
|
-
cmd.update({
|
|
114
|
+
cmd.update({"ClientOptions": options})
|
|
79
115
|
self.service.send_plist(cmd)
|
|
80
116
|
self._watch_completion(handler, *args)
|
|
81
117
|
|
|
82
|
-
def install(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
118
|
+
def install(
|
|
119
|
+
self, package_path: str, options: Optional[dict] = None, handler: Optional[Callable] = None, *args
|
|
120
|
+
) -> None:
|
|
121
|
+
"""install given ipa/ipcc from device path"""
|
|
122
|
+
self.install_from_local(package_path, "Install", options, handler, args)
|
|
123
|
+
|
|
124
|
+
def upgrade(self, ipa_path: str, options: Optional[dict] = None, handler: Optional[Callable] = None, *args) -> None:
|
|
125
|
+
"""upgrade given ipa from device path"""
|
|
126
|
+
self.install_from_local(ipa_path, "Upgrade", options, handler, args)
|
|
127
|
+
|
|
128
|
+
def restore(
|
|
129
|
+
self, bundle_identifier: str, options: Optional[dict] = None, handler: Optional[Callable] = None, *args
|
|
130
|
+
) -> None:
|
|
131
|
+
"""no longer supported on newer iOS versions"""
|
|
132
|
+
self.send_cmd_for_bundle_identifier(bundle_identifier, "Restore", options, handler, args)
|
|
133
|
+
|
|
134
|
+
def uninstall(
|
|
135
|
+
self, bundle_identifier: str, options: Optional[dict] = None, handler: Optional[Callable] = None, *args
|
|
136
|
+
) -> None:
|
|
137
|
+
"""uninstall given bundle_identifier"""
|
|
138
|
+
self.send_cmd_for_bundle_identifier(bundle_identifier, "Uninstall", options, handler, args)
|
|
139
|
+
|
|
140
|
+
def install_from_bytes(
|
|
141
|
+
self,
|
|
142
|
+
package_bytes: bytes,
|
|
143
|
+
cmd: str = "Install",
|
|
144
|
+
options: Optional[dict] = None,
|
|
145
|
+
handler: Optional[Callable] = None,
|
|
146
|
+
*args,
|
|
147
|
+
) -> None:
|
|
148
|
+
"""upload given ipa/ipcc bytes object onto device and install it"""
|
|
149
|
+
ipcc_mode = classify_zip_file(package_bytes).is_ipcc()
|
|
89
150
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
self.send_cmd_for_bundle_identifier(bundle_identifier, 'Restore', options, handler, args)
|
|
93
|
-
|
|
94
|
-
def uninstall(self, bundle_identifier: str, options: Optional[dict] = None, handler: Callable = None,
|
|
95
|
-
*args) -> None:
|
|
96
|
-
""" uninstall given bundle_identifier """
|
|
97
|
-
self.send_cmd_for_bundle_identifier(bundle_identifier, 'Uninstall', options, handler, args)
|
|
151
|
+
if options is None:
|
|
152
|
+
options = {}
|
|
98
153
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
handler: Callable = None, *args) -> None:
|
|
102
|
-
""" upload given ipa/ipcc onto device and install it """
|
|
154
|
+
if ipcc_mode:
|
|
155
|
+
options["PackageType"] = "CarrierBundle"
|
|
103
156
|
|
|
104
|
-
|
|
157
|
+
with AfcService(self.lockdown) as afc:
|
|
158
|
+
if not ipcc_mode:
|
|
159
|
+
afc.set_file_contents(TEMP_REMOTE_IPA_FILE, package_bytes)
|
|
160
|
+
else:
|
|
161
|
+
self.upload_ipcc_from_bytes(package_bytes, afc)
|
|
162
|
+
|
|
163
|
+
self.send_package(cmd, options, handler, ipcc_mode, *args)
|
|
164
|
+
|
|
165
|
+
@str_to_path("package_path")
|
|
166
|
+
def install_from_local(
|
|
167
|
+
self,
|
|
168
|
+
package_path: Path,
|
|
169
|
+
cmd: str = "Install",
|
|
170
|
+
options: Optional[dict] = None,
|
|
171
|
+
handler: Optional[Callable] = None,
|
|
172
|
+
developer: bool = False,
|
|
173
|
+
*args,
|
|
174
|
+
) -> None:
|
|
175
|
+
"""upload given ipa/ipcc onto device and install it"""
|
|
176
|
+
ipcc_mode = package_path.suffix == ".ipcc"
|
|
105
177
|
|
|
106
178
|
if options is None:
|
|
107
179
|
options = {}
|
|
108
180
|
|
|
109
181
|
if ipcc_mode:
|
|
110
|
-
options[
|
|
182
|
+
options["PackageType"] = "CarrierBundle"
|
|
111
183
|
else:
|
|
112
184
|
if package_path.is_dir():
|
|
113
185
|
# treat as app, convert into an ipa
|
|
@@ -116,61 +188,77 @@ class InstallationProxyService(LockdownService):
|
|
|
116
188
|
# treat as ipa
|
|
117
189
|
ipa_contents = package_path.read_bytes()
|
|
118
190
|
|
|
191
|
+
if developer:
|
|
192
|
+
options["PackageType"] = "Developer"
|
|
193
|
+
|
|
119
194
|
with AfcService(self.lockdown) as afc:
|
|
120
195
|
if not ipcc_mode:
|
|
196
|
+
afc.makedirs(TEMP_REMOTE_BASEDIR)
|
|
121
197
|
afc.set_file_contents(TEMP_REMOTE_IPA_FILE, ipa_contents)
|
|
122
198
|
|
|
123
199
|
else:
|
|
124
|
-
self.
|
|
200
|
+
self.upload_ipcc_from_path(package_path, afc)
|
|
125
201
|
|
|
126
|
-
self.
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
202
|
+
self.send_package(cmd, options, handler, ipcc_mode, *args)
|
|
203
|
+
|
|
204
|
+
def send_package(self, cmd: str, options: Optional[dict], handler: Callable, ipcc_mode: bool = False, *args):
|
|
205
|
+
self.service.send_plist({
|
|
206
|
+
"Command": cmd,
|
|
207
|
+
"ClientOptions": options,
|
|
208
|
+
"PackagePath": (TEMP_REMOTE_IPCC_FOLDER if ipcc_mode else TEMP_REMOTE_IPA_FILE),
|
|
209
|
+
})
|
|
130
210
|
|
|
131
211
|
self._watch_completion(handler, ipcc_mode, args)
|
|
132
212
|
|
|
133
|
-
def
|
|
213
|
+
def upload_ipcc_from_path(self, file: Path, afc_client: AfcService) -> None:
|
|
134
214
|
"""Used to upload a .ipcc file to an iPhone as a folder"""
|
|
215
|
+
with file.open("rb") as fb:
|
|
216
|
+
file_name = file.name
|
|
217
|
+
file_stream = BytesIO(fb.read())
|
|
218
|
+
self._upload_ipcc(file_stream, afc_client, file_name)
|
|
219
|
+
|
|
220
|
+
def upload_ipcc_from_bytes(self, file_bytes: bytes, afc_client: AfcService) -> None:
|
|
221
|
+
"""Used to upload a .ipcc bytes array to an iPhone as a folder"""
|
|
222
|
+
file_stream = BytesIO(file_bytes)
|
|
223
|
+
file_name = "bytes"
|
|
224
|
+
self._upload_ipcc(file_stream, afc_client, file_name)
|
|
135
225
|
|
|
136
|
-
|
|
226
|
+
def _upload_ipcc(self, file_stream: BytesIO, afc_client: AfcService, file_name: str) -> None:
|
|
227
|
+
self.logger.info(f"Uploading {file_name} contents..")
|
|
137
228
|
|
|
138
229
|
afc_client.makedirs(TEMP_REMOTE_IPCC_FOLDER)
|
|
139
230
|
|
|
140
231
|
# we unpack it and upload it directly instead of saving it in a temp folder
|
|
141
|
-
with ZipFile(
|
|
232
|
+
with ZipFile(file_stream, "r") as file_zip:
|
|
142
233
|
for file_name in file_zip.namelist():
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
afc_client.makedirs(f'{TEMP_REMOTE_IPCC_FOLDER}/{file_name}')
|
|
234
|
+
if file_name.endswith(("/", "\\")):
|
|
235
|
+
afc_client.makedirs(f"{TEMP_REMOTE_IPCC_FOLDER}/{file_name}")
|
|
146
236
|
continue
|
|
147
237
|
|
|
148
238
|
with file_zip.open(file_name) as inside_file_zip:
|
|
149
239
|
file_data = inside_file_zip.read()
|
|
240
|
+
afc_client.makedirs(TEMP_REMOTE_BASEDIR)
|
|
241
|
+
afc_client.set_file_contents(f"{TEMP_REMOTE_IPCC_FOLDER}/{file_name}", file_data)
|
|
150
242
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
self.logger.info('Upload complete.')
|
|
243
|
+
self.logger.info("Upload complete.")
|
|
154
244
|
|
|
155
245
|
def check_capabilities_match(self, capabilities: Optional[dict] = None, options: Optional[dict] = None) -> dict:
|
|
156
246
|
if options is None:
|
|
157
247
|
options = {}
|
|
158
|
-
cmd = {
|
|
159
|
-
'ClientOptions': options}
|
|
248
|
+
cmd = {"Command": "CheckCapabilitiesMatch", "ClientOptions": options}
|
|
160
249
|
|
|
161
250
|
if capabilities:
|
|
162
|
-
cmd[
|
|
251
|
+
cmd["Capabilities"] = capabilities
|
|
163
252
|
|
|
164
|
-
return self.service.send_recv_plist(cmd).get(
|
|
253
|
+
return self.service.send_recv_plist(cmd).get("LookupResult")
|
|
165
254
|
|
|
166
|
-
def browse(self, options: Optional[dict] = None, attributes: list[str] = None) -> list[dict]:
|
|
255
|
+
def browse(self, options: Optional[dict] = None, attributes: Optional[list[str]] = None) -> list[dict]:
|
|
167
256
|
if options is None:
|
|
168
257
|
options = {}
|
|
169
258
|
if attributes:
|
|
170
|
-
options[
|
|
259
|
+
options["ReturnAttributes"] = attributes
|
|
171
260
|
|
|
172
|
-
cmd = {
|
|
173
|
-
'ClientOptions': options}
|
|
261
|
+
cmd = {"Command": "Browse", "ClientOptions": options}
|
|
174
262
|
|
|
175
263
|
self.service.send_plist(cmd)
|
|
176
264
|
|
|
@@ -180,30 +268,34 @@ class InstallationProxyService(LockdownService):
|
|
|
180
268
|
if not response:
|
|
181
269
|
break
|
|
182
270
|
|
|
183
|
-
data = response.get(
|
|
271
|
+
data = response.get("CurrentList")
|
|
184
272
|
if data is not None:
|
|
185
273
|
result += data
|
|
186
274
|
|
|
187
|
-
if response.get(
|
|
275
|
+
if response.get("Status") == "Complete":
|
|
188
276
|
break
|
|
189
277
|
|
|
190
278
|
return result
|
|
191
279
|
|
|
192
280
|
def lookup(self, options: Optional[dict] = None) -> dict:
|
|
193
|
-
"""
|
|
281
|
+
"""search installation database"""
|
|
194
282
|
if options is None:
|
|
195
283
|
options = {}
|
|
196
|
-
cmd = {
|
|
197
|
-
return self.service.send_recv_plist(cmd).get(
|
|
198
|
-
|
|
199
|
-
def get_apps(
|
|
200
|
-
|
|
201
|
-
|
|
284
|
+
cmd = {"Command": "Lookup", "ClientOptions": options}
|
|
285
|
+
return self.service.send_recv_plist(cmd).get("LookupResult")
|
|
286
|
+
|
|
287
|
+
def get_apps(
|
|
288
|
+
self,
|
|
289
|
+
application_type: str = "Any",
|
|
290
|
+
calculate_sizes: bool = False,
|
|
291
|
+
bundle_identifiers: Optional[list[str]] = None,
|
|
292
|
+
) -> dict[str, dict]:
|
|
293
|
+
"""get applications according to given criteria"""
|
|
202
294
|
options = {}
|
|
203
295
|
if bundle_identifiers is not None:
|
|
204
|
-
options[
|
|
296
|
+
options["BundleIDs"] = bundle_identifiers
|
|
205
297
|
|
|
206
|
-
options[
|
|
298
|
+
options["ApplicationType"] = application_type
|
|
207
299
|
result = self.lookup(options)
|
|
208
300
|
if calculate_sizes:
|
|
209
301
|
options.update(GET_APPS_ADDITIONAL_INFO)
|
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from typing_extensions import Self
|
|
2
5
|
|
|
3
6
|
from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
|
|
4
7
|
from pymobiledevice3.service_connection import ServiceConnection
|
|
5
8
|
|
|
6
9
|
|
|
7
10
|
class LockdownService:
|
|
8
|
-
def __init__(
|
|
9
|
-
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
lockdown: LockdownServiceProvider,
|
|
14
|
+
service_name: str,
|
|
15
|
+
is_developer_service: bool = False,
|
|
16
|
+
service: Optional[ServiceConnection] = None,
|
|
17
|
+
include_escrow_bag: bool = False,
|
|
18
|
+
) -> None:
|
|
10
19
|
"""
|
|
11
20
|
:param lockdown: server provider
|
|
12
21
|
:param service_name: wrapped service name - will attempt
|
|
@@ -15,19 +24,20 @@ class LockdownService:
|
|
|
15
24
|
"""
|
|
16
25
|
|
|
17
26
|
if service is None:
|
|
18
|
-
start_service =
|
|
19
|
-
lockdown.start_lockdown_service
|
|
27
|
+
start_service = (
|
|
28
|
+
lockdown.start_lockdown_developer_service if is_developer_service else lockdown.start_lockdown_service
|
|
29
|
+
)
|
|
20
30
|
service = start_service(service_name, include_escrow_bag=include_escrow_bag)
|
|
21
31
|
|
|
22
|
-
self.service_name = service_name
|
|
23
|
-
self.lockdown = lockdown
|
|
24
|
-
self.service = service
|
|
25
|
-
self.logger = logging.getLogger(self.__module__)
|
|
32
|
+
self.service_name: str = service_name
|
|
33
|
+
self.lockdown: LockdownServiceProvider = lockdown
|
|
34
|
+
self.service: ServiceConnection = service
|
|
35
|
+
self.logger: logging.Logger = logging.getLogger(self.__module__)
|
|
26
36
|
|
|
27
|
-
def __enter__(self):
|
|
37
|
+
def __enter__(self) -> Self:
|
|
28
38
|
return self
|
|
29
39
|
|
|
30
|
-
async def __aenter__(self) ->
|
|
40
|
+
async def __aenter__(self) -> Self:
|
|
31
41
|
return self
|
|
32
42
|
|
|
33
43
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
@@ -10,8 +10,8 @@ class ProvisioningProfile:
|
|
|
10
10
|
def __init__(self, buf: bytes):
|
|
11
11
|
self.buf = buf
|
|
12
12
|
|
|
13
|
-
xml = b
|
|
14
|
-
xml = xml.split(b
|
|
13
|
+
xml = b"<?xml" + buf.split(b"<?xml", 1)[1]
|
|
14
|
+
xml = xml.split(b"</plist>")[0] + b"</plist>"
|
|
15
15
|
self.plist = plistlib.loads(xml)
|
|
16
16
|
|
|
17
17
|
def __str__(self):
|
|
@@ -19,8 +19,8 @@ class ProvisioningProfile:
|
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
class MisagentService(LockdownService):
|
|
22
|
-
SERVICE_NAME =
|
|
23
|
-
RSD_SERVICE_NAME =
|
|
22
|
+
SERVICE_NAME = "com.apple.misagent"
|
|
23
|
+
RSD_SERVICE_NAME = "com.apple.misagent.shim.remote"
|
|
24
24
|
|
|
25
25
|
def __init__(self, lockdown: LockdownClient):
|
|
26
26
|
if isinstance(lockdown, LockdownClient):
|
|
@@ -29,27 +29,30 @@ class MisagentService(LockdownService):
|
|
|
29
29
|
super().__init__(lockdown, self.RSD_SERVICE_NAME)
|
|
30
30
|
|
|
31
31
|
def install(self, plist: BytesIO) -> dict:
|
|
32
|
-
response = self.service.send_recv_plist({
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
response = self.service.send_recv_plist({
|
|
33
|
+
"MessageType": "Install",
|
|
34
|
+
"Profile": plist.read(),
|
|
35
|
+
"ProfileType": "Provisioning",
|
|
36
|
+
})
|
|
37
|
+
if response["Status"]:
|
|
38
|
+
raise PyMobileDevice3Exception(f"invalid status: {response}")
|
|
37
39
|
|
|
38
40
|
return response
|
|
39
41
|
|
|
40
42
|
def remove(self, profile_id: str) -> dict:
|
|
41
|
-
response = self.service.send_recv_plist({
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
response = self.service.send_recv_plist({
|
|
44
|
+
"MessageType": "Remove",
|
|
45
|
+
"ProfileID": profile_id,
|
|
46
|
+
"ProfileType": "Provisioning",
|
|
47
|
+
})
|
|
48
|
+
if response["Status"]:
|
|
49
|
+
raise PyMobileDevice3Exception(f"invalid status: {response}")
|
|
46
50
|
|
|
47
51
|
return response
|
|
48
52
|
|
|
49
53
|
def copy_all(self) -> list[ProvisioningProfile]:
|
|
50
|
-
response = self.service.send_recv_plist({
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
raise PyMobileDevice3Exception(f'invalid status: {response}')
|
|
54
|
+
response = self.service.send_recv_plist({"MessageType": "CopyAll", "ProfileType": "Provisioning"})
|
|
55
|
+
if response["Status"]:
|
|
56
|
+
raise PyMobileDevice3Exception(f"invalid status: {response}")
|
|
54
57
|
|
|
55
|
-
return [ProvisioningProfile(p) for p in response[
|
|
58
|
+
return [ProvisioningProfile(p) for p in response["Payload"]]
|