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.
Files changed (164) hide show
  1. misc/plist_sniffer.py +15 -15
  2. misc/remotexpc_sniffer.py +29 -28
  3. misc/understanding_idevice_protocol_layers.md +15 -10
  4. pymobiledevice3/__main__.py +317 -127
  5. pymobiledevice3/_version.py +22 -4
  6. pymobiledevice3/bonjour.py +358 -113
  7. pymobiledevice3/ca.py +253 -16
  8. pymobiledevice3/cli/activation.py +31 -23
  9. pymobiledevice3/cli/afc.py +49 -40
  10. pymobiledevice3/cli/amfi.py +16 -21
  11. pymobiledevice3/cli/apps.py +87 -42
  12. pymobiledevice3/cli/backup.py +160 -90
  13. pymobiledevice3/cli/bonjour.py +44 -40
  14. pymobiledevice3/cli/cli_common.py +204 -198
  15. pymobiledevice3/cli/companion_proxy.py +14 -14
  16. pymobiledevice3/cli/crash.py +105 -56
  17. pymobiledevice3/cli/developer/__init__.py +62 -0
  18. pymobiledevice3/cli/developer/accessibility/__init__.py +65 -0
  19. pymobiledevice3/cli/developer/accessibility/settings.py +43 -0
  20. pymobiledevice3/cli/developer/arbitration.py +50 -0
  21. pymobiledevice3/cli/developer/condition.py +33 -0
  22. pymobiledevice3/cli/developer/core_device.py +294 -0
  23. pymobiledevice3/cli/developer/debugserver.py +244 -0
  24. pymobiledevice3/cli/developer/dvt/__init__.py +438 -0
  25. pymobiledevice3/cli/developer/dvt/core_profile_session.py +295 -0
  26. pymobiledevice3/cli/developer/dvt/simulate_location.py +56 -0
  27. pymobiledevice3/cli/developer/dvt/sysmon/__init__.py +69 -0
  28. pymobiledevice3/cli/developer/dvt/sysmon/process.py +188 -0
  29. pymobiledevice3/cli/developer/fetch_symbols.py +108 -0
  30. pymobiledevice3/cli/developer/simulate_location.py +51 -0
  31. pymobiledevice3/cli/diagnostics/__init__.py +75 -0
  32. pymobiledevice3/cli/diagnostics/battery.py +47 -0
  33. pymobiledevice3/cli/idam.py +42 -0
  34. pymobiledevice3/cli/lockdown.py +108 -103
  35. pymobiledevice3/cli/mounter.py +158 -99
  36. pymobiledevice3/cli/notification.py +38 -26
  37. pymobiledevice3/cli/pcap.py +45 -24
  38. pymobiledevice3/cli/power_assertion.py +18 -17
  39. pymobiledevice3/cli/processes.py +17 -23
  40. pymobiledevice3/cli/profile.py +165 -109
  41. pymobiledevice3/cli/provision.py +35 -34
  42. pymobiledevice3/cli/remote.py +217 -129
  43. pymobiledevice3/cli/restore.py +159 -143
  44. pymobiledevice3/cli/springboard.py +63 -53
  45. pymobiledevice3/cli/syslog.py +193 -86
  46. pymobiledevice3/cli/usbmux.py +73 -33
  47. pymobiledevice3/cli/version.py +5 -7
  48. pymobiledevice3/cli/webinspector.py +376 -214
  49. pymobiledevice3/common.py +3 -1
  50. pymobiledevice3/exceptions.py +182 -58
  51. pymobiledevice3/irecv.py +52 -53
  52. pymobiledevice3/irecv_devices.py +1489 -464
  53. pymobiledevice3/lockdown.py +473 -275
  54. pymobiledevice3/lockdown_service_provider.py +15 -8
  55. pymobiledevice3/osu/os_utils.py +27 -9
  56. pymobiledevice3/osu/posix_util.py +34 -15
  57. pymobiledevice3/osu/win_util.py +14 -8
  58. pymobiledevice3/pair_records.py +102 -21
  59. pymobiledevice3/remote/common.py +8 -4
  60. pymobiledevice3/remote/core_device/app_service.py +94 -67
  61. pymobiledevice3/remote/core_device/core_device_service.py +17 -14
  62. pymobiledevice3/remote/core_device/device_info.py +5 -5
  63. pymobiledevice3/remote/core_device/diagnostics_service.py +19 -4
  64. pymobiledevice3/remote/core_device/file_service.py +53 -23
  65. pymobiledevice3/remote/remote_service_discovery.py +79 -45
  66. pymobiledevice3/remote/remotexpc.py +73 -44
  67. pymobiledevice3/remote/tunnel_service.py +442 -317
  68. pymobiledevice3/remote/utils.py +14 -13
  69. pymobiledevice3/remote/xpc_message.py +145 -125
  70. pymobiledevice3/resources/dsc_uuid_map.py +19 -19
  71. pymobiledevice3/resources/firmware_notifications.py +20 -16
  72. pymobiledevice3/resources/notifications.txt +144 -0
  73. pymobiledevice3/restore/asr.py +27 -27
  74. pymobiledevice3/restore/base_restore.py +110 -21
  75. pymobiledevice3/restore/consts.py +87 -66
  76. pymobiledevice3/restore/device.py +59 -12
  77. pymobiledevice3/restore/fdr.py +46 -48
  78. pymobiledevice3/restore/ftab.py +19 -19
  79. pymobiledevice3/restore/img4.py +163 -0
  80. pymobiledevice3/restore/mbn.py +587 -0
  81. pymobiledevice3/restore/recovery.py +151 -151
  82. pymobiledevice3/restore/restore.py +562 -544
  83. pymobiledevice3/restore/restore_options.py +131 -110
  84. pymobiledevice3/restore/restored_client.py +51 -31
  85. pymobiledevice3/restore/tss.py +385 -267
  86. pymobiledevice3/service_connection.py +252 -59
  87. pymobiledevice3/services/accessibilityaudit.py +202 -120
  88. pymobiledevice3/services/afc.py +962 -365
  89. pymobiledevice3/services/amfi.py +24 -30
  90. pymobiledevice3/services/companion.py +23 -19
  91. pymobiledevice3/services/crash_reports.py +71 -47
  92. pymobiledevice3/services/debugserver_applist.py +3 -3
  93. pymobiledevice3/services/device_arbitration.py +8 -8
  94. pymobiledevice3/services/device_link.py +101 -79
  95. pymobiledevice3/services/diagnostics.py +973 -967
  96. pymobiledevice3/services/dtfetchsymbols.py +8 -8
  97. pymobiledevice3/services/dvt/dvt_secure_socket_proxy.py +4 -4
  98. pymobiledevice3/services/dvt/dvt_testmanaged_proxy.py +4 -4
  99. pymobiledevice3/services/dvt/instruments/activity_trace_tap.py +85 -74
  100. pymobiledevice3/services/dvt/instruments/application_listing.py +2 -3
  101. pymobiledevice3/services/dvt/instruments/condition_inducer.py +7 -6
  102. pymobiledevice3/services/dvt/instruments/core_profile_session_tap.py +466 -384
  103. pymobiledevice3/services/dvt/instruments/device_info.py +20 -11
  104. pymobiledevice3/services/dvt/instruments/energy_monitor.py +1 -1
  105. pymobiledevice3/services/dvt/instruments/graphics.py +1 -1
  106. pymobiledevice3/services/dvt/instruments/location_simulation.py +1 -1
  107. pymobiledevice3/services/dvt/instruments/location_simulation_base.py +10 -10
  108. pymobiledevice3/services/dvt/instruments/network_monitor.py +17 -17
  109. pymobiledevice3/services/dvt/instruments/notifications.py +1 -1
  110. pymobiledevice3/services/dvt/instruments/process_control.py +35 -10
  111. pymobiledevice3/services/dvt/instruments/screenshot.py +2 -2
  112. pymobiledevice3/services/dvt/instruments/sysmontap.py +15 -15
  113. pymobiledevice3/services/dvt/testmanaged/xcuitest.py +42 -52
  114. pymobiledevice3/services/file_relay.py +10 -10
  115. pymobiledevice3/services/heartbeat.py +9 -8
  116. pymobiledevice3/services/house_arrest.py +16 -15
  117. pymobiledevice3/services/idam.py +20 -0
  118. pymobiledevice3/services/installation_proxy.py +173 -81
  119. pymobiledevice3/services/lockdown_service.py +20 -10
  120. pymobiledevice3/services/misagent.py +22 -19
  121. pymobiledevice3/services/mobile_activation.py +147 -64
  122. pymobiledevice3/services/mobile_config.py +331 -294
  123. pymobiledevice3/services/mobile_image_mounter.py +141 -113
  124. pymobiledevice3/services/mobilebackup2.py +203 -145
  125. pymobiledevice3/services/notification_proxy.py +11 -11
  126. pymobiledevice3/services/os_trace.py +134 -74
  127. pymobiledevice3/services/pcapd.py +314 -302
  128. pymobiledevice3/services/power_assertion.py +10 -9
  129. pymobiledevice3/services/preboard.py +4 -4
  130. pymobiledevice3/services/remote_fetch_symbols.py +21 -14
  131. pymobiledevice3/services/remote_server.py +176 -146
  132. pymobiledevice3/services/restore_service.py +16 -16
  133. pymobiledevice3/services/screenshot.py +15 -12
  134. pymobiledevice3/services/simulate_location.py +7 -7
  135. pymobiledevice3/services/springboard.py +15 -15
  136. pymobiledevice3/services/syslog.py +5 -5
  137. pymobiledevice3/services/web_protocol/alert.py +11 -11
  138. pymobiledevice3/services/web_protocol/automation_session.py +251 -239
  139. pymobiledevice3/services/web_protocol/cdp_screencast.py +46 -37
  140. pymobiledevice3/services/web_protocol/cdp_server.py +19 -19
  141. pymobiledevice3/services/web_protocol/cdp_target.py +411 -373
  142. pymobiledevice3/services/web_protocol/driver.py +114 -111
  143. pymobiledevice3/services/web_protocol/element.py +124 -111
  144. pymobiledevice3/services/web_protocol/inspector_session.py +106 -102
  145. pymobiledevice3/services/web_protocol/selenium_api.py +49 -49
  146. pymobiledevice3/services/web_protocol/session_protocol.py +18 -12
  147. pymobiledevice3/services/web_protocol/switch_to.py +30 -27
  148. pymobiledevice3/services/webinspector.py +189 -155
  149. pymobiledevice3/tcp_forwarder.py +87 -69
  150. pymobiledevice3/tunneld/__init__.py +0 -0
  151. pymobiledevice3/tunneld/api.py +63 -0
  152. pymobiledevice3/tunneld/server.py +603 -0
  153. pymobiledevice3/usbmux.py +198 -147
  154. pymobiledevice3/utils.py +14 -11
  155. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/METADATA +55 -28
  156. pymobiledevice3-7.0.6.dist-info/RECORD +188 -0
  157. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/WHEEL +1 -1
  158. pymobiledevice3/cli/developer.py +0 -1215
  159. pymobiledevice3/cli/diagnostics.py +0 -99
  160. pymobiledevice3/tunneld.py +0 -524
  161. pymobiledevice3-4.14.6.dist-info/RECORD +0 -168
  162. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/entry_points.txt +0 -0
  163. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info/licenses}/LICENSE +0 -0
  164. {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 = 'VendContainer'
7
- VEND_DOCUMENTS = 'VendDocuments'
6
+ VEND_CONTAINER = "VendContainer"
7
+ VEND_DOCUMENTS = "VendDocuments"
8
8
 
9
- DOCUMENTS_ROOT = '/Documents'
9
+ DOCUMENTS_ROOT = "/Documents"
10
10
 
11
11
 
12
12
  class HouseArrestService(AfcService):
13
- SERVICE_NAME = 'com.apple.mobile.house_arrest'
14
- RSD_SERVICE_NAME = 'com.apple.mobile.house_arrest.shim.remote'
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 = 'VendContainer') -> None:
32
- response = self.service.send_recv_plist({'Command': cmd, 'Identifier': bundle_id})
33
- error = response.get('Error')
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
- raise PyMobileDevice3Exception(error)
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 = {'ReturnAttributes': ['CFBundleIdentifier', 'StaticDiskUsage', 'DynamicDiskUsage']}
17
+ GET_APPS_ADDITIONAL_INFO = {"ReturnAttributes": ["CFBundleIdentifier", "StaticDiskUsage", "DynamicDiskUsage"]}
16
18
 
17
- TEMP_REMOTE_IPA_FILE = '/PublicStaging/pymobiledevice3.ipa'
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
- TEMP_REMOTE_IPCC_FOLDER = '/PublicStaging/pymobiledevice3.ipcc'
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 = 'Payload/' + os.path.basename(directory)
36
+ payload_prefix = "Payload/" + os.path.basename(directory)
24
37
  with TemporaryDirectory() as temp_dir:
25
- zip_path = Path(temp_dir) / 'ipa'
26
- with ZipFile(zip_path, 'w', ZIP_DEFLATED) as zip_file:
27
- for root, dirs, files in os.walk(directory):
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 = 'com.apple.mobile.installation_proxy'
38
- RSD_SERVICE_NAME = 'com.apple.mobile.installation_proxy.shim.remote'
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('Error')
83
+ error = response.get("Error")
52
84
  if error:
53
- raise AppInstallError(f'{error}: {response.get("ErrorDescription")}')
54
- completion = response.get('PercentComplete')
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('calling handler')
89
+ self.logger.debug("calling handler")
58
90
  handler(completion, *args)
59
- self.logger.info(f'{response.get("PercentComplete")}% Complete')
60
- if response.get('Status') == 'Complete':
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('Installation succeed.')
96
+ self.logger.info("Installation succeed.")
65
97
  return
66
98
  raise AppInstallError()
67
99
 
68
- def send_cmd_for_bundle_identifier(self, bundle_identifier: str, cmd: str = 'Archive',
69
- options: Optional[dict] = None,
70
- handler: Optional[dict] = None, *args) -> None:
71
- """ send a low-level command to installation relay """
72
- cmd = {'Command': cmd,
73
- 'ApplicationIdentifier': bundle_identifier}
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({'ClientOptions': options})
114
+ cmd.update({"ClientOptions": options})
79
115
  self.service.send_plist(cmd)
80
116
  self._watch_completion(handler, *args)
81
117
 
82
- def install(self, package_path: str, options: Optional[dict] = None, handler: Callable = None, *args) -> None:
83
- """ install given ipa/ipcc from device path """
84
- self.install_from_local(package_path, 'Install', options, handler, args)
85
-
86
- def upgrade(self, ipa_path: str, options: Optional[dict] = None, handler: Callable = None, *args) -> None:
87
- """ upgrade given ipa from device path """
88
- self.install_from_local(ipa_path, 'Upgrade', options, handler, args)
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
- def restore(self, bundle_identifier: str, options: Optional[dict] = None, handler: Callable = None, *args) -> None:
91
- """ no longer supported on newer iOS versions """
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
- @str_to_path('package_path')
100
- def install_from_local(self, package_path: Path, cmd: str = 'Install', options: Optional[dict] = None,
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
- ipcc_mode = package_path.suffix == '.ipcc'
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['PackageType'] = 'CarrierBundle'
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.upload_ipcc_as_folder(package_path, afc)
200
+ self.upload_ipcc_from_path(package_path, afc)
125
201
 
126
- self.service.send_plist({'Command': cmd,
127
- 'ClientOptions': options,
128
- 'PackagePath': TEMP_REMOTE_IPCC_FOLDER if ipcc_mode
129
- else TEMP_REMOTE_IPA_FILE})
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 upload_ipcc_as_folder(self, file: Path, afc_client: AfcService) -> None:
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
- self.logger.info(f'Uploading {file.name} contents..')
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(file, 'r') as file_zip:
232
+ with ZipFile(file_stream, "r") as file_zip:
142
233
  for file_name in file_zip.namelist():
143
-
144
- if file_name.endswith(('/', '\\')):
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
- afc_client.set_file_contents(f'{TEMP_REMOTE_IPCC_FOLDER}/{file_name}', file_data)
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 = {'Command': 'CheckCapabilitiesMatch',
159
- 'ClientOptions': options}
248
+ cmd = {"Command": "CheckCapabilitiesMatch", "ClientOptions": options}
160
249
 
161
250
  if capabilities:
162
- cmd['Capabilities'] = capabilities
251
+ cmd["Capabilities"] = capabilities
163
252
 
164
- return self.service.send_recv_plist(cmd).get('LookupResult')
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['ReturnAttributes'] = attributes
259
+ options["ReturnAttributes"] = attributes
171
260
 
172
- cmd = {'Command': 'Browse',
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('CurrentList')
271
+ data = response.get("CurrentList")
184
272
  if data is not None:
185
273
  result += data
186
274
 
187
- if response.get('Status') == 'Complete':
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
- """ search installation database """
281
+ """search installation database"""
194
282
  if options is None:
195
283
  options = {}
196
- cmd = {'Command': 'Lookup', 'ClientOptions': options}
197
- return self.service.send_recv_plist(cmd).get('LookupResult')
198
-
199
- def get_apps(self, application_type: str = 'Any', calculate_sizes: bool = False,
200
- bundle_identifiers: Optional[list[str]] = None) -> dict[str, dict]:
201
- """ get applications according to given criteria """
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['BundleIDs'] = bundle_identifiers
296
+ options["BundleIDs"] = bundle_identifiers
205
297
 
206
- options['ApplicationType'] = application_type
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__(self, lockdown: LockdownServiceProvider, service_name: str, is_developer_service=False,
9
- service: ServiceConnection = None, include_escrow_bag: bool = False):
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 = lockdown.start_lockdown_developer_service if is_developer_service else \
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) -> 'LockdownService':
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'<?xml' + buf.split(b'<?xml', 1)[1]
14
- xml = xml.split(b'</plist>')[0] + b'</plist>'
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 = 'com.apple.misagent'
23
- RSD_SERVICE_NAME = 'com.apple.misagent.shim.remote'
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({'MessageType': 'Install',
33
- 'Profile': plist.read(),
34
- 'ProfileType': 'Provisioning'})
35
- if response['Status']:
36
- raise PyMobileDevice3Exception(f'invalid status: {response}')
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({'MessageType': 'Remove',
42
- 'ProfileID': profile_id,
43
- 'ProfileType': 'Provisioning'})
44
- if response['Status']:
45
- raise PyMobileDevice3Exception(f'invalid status: {response}')
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({'MessageType': 'CopyAll',
51
- 'ProfileType': 'Provisioning'})
52
- if response['Status']:
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['Payload']]
58
+ return [ProvisioningProfile(p) for p in response["Payload"]]