pymobiledevice3 4.27.4__py3-none-any.whl → 5.1.2__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
- pymobiledevice3/__main__.py +123 -98
- pymobiledevice3/_version.py +2 -2
- pymobiledevice3/bonjour.py +351 -117
- pymobiledevice3/ca.py +32 -24
- pymobiledevice3/cli/activation.py +7 -7
- pymobiledevice3/cli/afc.py +19 -19
- pymobiledevice3/cli/amfi.py +4 -4
- pymobiledevice3/cli/apps.py +51 -39
- pymobiledevice3/cli/backup.py +58 -32
- pymobiledevice3/cli/bonjour.py +27 -20
- pymobiledevice3/cli/cli_common.py +112 -81
- pymobiledevice3/cli/companion_proxy.py +4 -4
- pymobiledevice3/cli/completions.py +10 -10
- pymobiledevice3/cli/crash.py +37 -31
- pymobiledevice3/cli/developer.py +601 -519
- pymobiledevice3/cli/diagnostics.py +38 -33
- pymobiledevice3/cli/lockdown.py +82 -72
- pymobiledevice3/cli/mounter.py +84 -67
- pymobiledevice3/cli/notification.py +10 -10
- pymobiledevice3/cli/pcap.py +19 -14
- pymobiledevice3/cli/power_assertion.py +12 -10
- pymobiledevice3/cli/processes.py +10 -10
- pymobiledevice3/cli/profile.py +88 -77
- pymobiledevice3/cli/provision.py +17 -17
- pymobiledevice3/cli/remote.py +188 -111
- pymobiledevice3/cli/restore.py +43 -40
- pymobiledevice3/cli/springboard.py +30 -28
- pymobiledevice3/cli/syslog.py +85 -58
- pymobiledevice3/cli/usbmux.py +21 -20
- pymobiledevice3/cli/version.py +3 -2
- pymobiledevice3/cli/webinspector.py +156 -78
- pymobiledevice3/common.py +1 -1
- pymobiledevice3/exceptions.py +154 -60
- pymobiledevice3/irecv.py +49 -53
- pymobiledevice3/irecv_devices.py +1489 -492
- pymobiledevice3/lockdown.py +400 -251
- pymobiledevice3/lockdown_service_provider.py +5 -7
- pymobiledevice3/osu/os_utils.py +18 -9
- pymobiledevice3/osu/posix_util.py +28 -15
- pymobiledevice3/osu/win_util.py +14 -8
- pymobiledevice3/pair_records.py +19 -19
- pymobiledevice3/remote/common.py +4 -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 +10 -8
- pymobiledevice3/remote/core_device/file_service.py +47 -33
- pymobiledevice3/remote/remote_service_discovery.py +53 -35
- pymobiledevice3/remote/remotexpc.py +64 -42
- pymobiledevice3/remote/tunnel_service.py +383 -297
- 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 +16 -16
- pymobiledevice3/restore/asr.py +27 -27
- pymobiledevice3/restore/base_restore.py +90 -47
- pymobiledevice3/restore/consts.py +87 -66
- pymobiledevice3/restore/device.py +11 -11
- pymobiledevice3/restore/fdr.py +46 -46
- pymobiledevice3/restore/ftab.py +19 -19
- pymobiledevice3/restore/img4.py +130 -133
- pymobiledevice3/restore/mbn.py +587 -0
- pymobiledevice3/restore/recovery.py +125 -135
- pymobiledevice3/restore/restore.py +535 -523
- pymobiledevice3/restore/restore_options.py +122 -115
- pymobiledevice3/restore/restored_client.py +25 -22
- pymobiledevice3/restore/tss.py +378 -270
- pymobiledevice3/service_connection.py +50 -46
- pymobiledevice3/services/accessibilityaudit.py +137 -127
- pymobiledevice3/services/afc.py +352 -292
- pymobiledevice3/services/amfi.py +21 -18
- pymobiledevice3/services/companion.py +23 -19
- pymobiledevice3/services/crash_reports.py +61 -47
- pymobiledevice3/services/debugserver_applist.py +3 -3
- pymobiledevice3/services/device_arbitration.py +8 -8
- pymobiledevice3/services/device_link.py +56 -48
- pymobiledevice3/services/diagnostics.py +971 -968
- 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 +11 -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 +25 -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 +8 -7
- pymobiledevice3/services/house_arrest.py +12 -15
- pymobiledevice3/services/installation_proxy.py +119 -100
- pymobiledevice3/services/lockdown_service.py +12 -5
- pymobiledevice3/services/misagent.py +22 -19
- pymobiledevice3/services/mobile_activation.py +84 -72
- pymobiledevice3/services/mobile_config.py +331 -301
- pymobiledevice3/services/mobile_image_mounter.py +137 -116
- pymobiledevice3/services/mobilebackup2.py +188 -150
- pymobiledevice3/services/notification_proxy.py +11 -11
- pymobiledevice3/services/os_trace.py +128 -74
- pymobiledevice3/services/pcapd.py +306 -306
- pymobiledevice3/services/power_assertion.py +10 -9
- pymobiledevice3/services/preboard.py +4 -4
- pymobiledevice3/services/remote_fetch_symbols.py +16 -14
- pymobiledevice3/services/remote_server.py +176 -146
- pymobiledevice3/services/restore_service.py +16 -16
- pymobiledevice3/services/screenshot.py +13 -10
- 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 +3 -3
- pymobiledevice3/services/web_protocol/automation_session.py +183 -179
- pymobiledevice3/services/web_protocol/cdp_screencast.py +44 -36
- pymobiledevice3/services/web_protocol/cdp_server.py +19 -19
- pymobiledevice3/services/web_protocol/cdp_target.py +411 -373
- pymobiledevice3/services/web_protocol/driver.py +47 -45
- pymobiledevice3/services/web_protocol/element.py +74 -63
- pymobiledevice3/services/web_protocol/inspector_session.py +106 -102
- pymobiledevice3/services/web_protocol/selenium_api.py +3 -3
- pymobiledevice3/services/web_protocol/session_protocol.py +15 -10
- pymobiledevice3/services/web_protocol/switch_to.py +11 -12
- pymobiledevice3/services/webinspector.py +142 -116
- pymobiledevice3/tcp_forwarder.py +35 -22
- pymobiledevice3/tunneld/api.py +20 -15
- pymobiledevice3/tunneld/server.py +310 -193
- pymobiledevice3/usbmux.py +197 -148
- pymobiledevice3/utils.py +14 -11
- {pymobiledevice3-4.27.4.dist-info → pymobiledevice3-5.1.2.dist-info}/METADATA +1 -2
- pymobiledevice3-5.1.2.dist-info/RECORD +173 -0
- pymobiledevice3-4.27.4.dist-info/RECORD +0 -172
- {pymobiledevice3-4.27.4.dist-info → pymobiledevice3-5.1.2.dist-info}/WHEEL +0 -0
- {pymobiledevice3-4.27.4.dist-info → pymobiledevice3-5.1.2.dist-info}/entry_points.txt +0 -0
- {pymobiledevice3-4.27.4.dist-info → pymobiledevice3-5.1.2.dist-info}/licenses/LICENSE +0 -0
- {pymobiledevice3-4.27.4.dist-info → pymobiledevice3-5.1.2.dist-info}/top_level.txt +0 -0
misc/plist_sniffer.py
CHANGED
|
@@ -7,8 +7,8 @@ import click
|
|
|
7
7
|
from scapy.packet import Packet, Raw
|
|
8
8
|
from scapy.sendrecv import sniff
|
|
9
9
|
|
|
10
|
-
BPLIST_MAGIC = b
|
|
11
|
-
PLIST_MAGIC = b
|
|
10
|
+
BPLIST_MAGIC = b"bplist"
|
|
11
|
+
PLIST_MAGIC = b"<plist"
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class PcapSniffer:
|
|
@@ -20,13 +20,13 @@ class PcapSniffer:
|
|
|
20
20
|
|
|
21
21
|
if BPLIST_MAGIC in packet:
|
|
22
22
|
try:
|
|
23
|
-
plist = plistlib.loads(packet[packet.find(BPLIST_MAGIC):])
|
|
23
|
+
plist = plistlib.loads(packet[packet.find(BPLIST_MAGIC) :])
|
|
24
24
|
self.report(plist)
|
|
25
25
|
except plistlib.InvalidFileException:
|
|
26
26
|
pass
|
|
27
27
|
if PLIST_MAGIC in packet:
|
|
28
28
|
try:
|
|
29
|
-
plist = plistlib.loads(packet[packet.find(PLIST_MAGIC):])
|
|
29
|
+
plist = plistlib.loads(packet[packet.find(PLIST_MAGIC) :])
|
|
30
30
|
self.report(plist)
|
|
31
31
|
except xml.parsers.expat.ExpatError:
|
|
32
32
|
pass
|
|
@@ -35,37 +35,37 @@ class PcapSniffer:
|
|
|
35
35
|
try:
|
|
36
36
|
print(plist)
|
|
37
37
|
if self.file is not None:
|
|
38
|
-
self.file.write(
|
|
38
|
+
self.file.write("---\n")
|
|
39
39
|
self.file.write(pprint.pformat(plist))
|
|
40
|
-
self.file.write(
|
|
40
|
+
self.file.write("\n---\n")
|
|
41
41
|
except ValueError:
|
|
42
|
-
print(
|
|
42
|
+
print("failed to print plist")
|
|
43
43
|
|
|
44
44
|
|
|
45
45
|
@click.group()
|
|
46
46
|
def cli():
|
|
47
|
-
"""
|
|
47
|
+
"""Parse RemoteXPC traffic"""
|
|
48
48
|
pass
|
|
49
49
|
|
|
50
50
|
|
|
51
51
|
@cli.command()
|
|
52
|
-
@click.argument(
|
|
53
|
-
@click.option(
|
|
52
|
+
@click.argument("pcap", type=click.Path(exists=True, file_okay=True, dir_okay=False))
|
|
53
|
+
@click.option("-o", "--out", type=click.File("wt"))
|
|
54
54
|
def offline(pcap: str, out: IO):
|
|
55
|
-
"""
|
|
55
|
+
"""Parse plists traffic from a .pcap file"""
|
|
56
56
|
sniffer = PcapSniffer(out)
|
|
57
57
|
for p in sniff(offline=pcap):
|
|
58
58
|
sniffer.process_packet(p)
|
|
59
59
|
|
|
60
60
|
|
|
61
61
|
@cli.command()
|
|
62
|
-
@click.argument(
|
|
63
|
-
@click.option(
|
|
62
|
+
@click.argument("iface")
|
|
63
|
+
@click.option("-o", "--out", type=click.File("wt"))
|
|
64
64
|
def live(iface: str, out: IO):
|
|
65
|
-
"""
|
|
65
|
+
"""Parse plists live from a given network interface"""
|
|
66
66
|
sniffer = PcapSniffer(out)
|
|
67
67
|
sniff(iface=iface, prn=sniffer.process_packet)
|
|
68
68
|
|
|
69
69
|
|
|
70
|
-
if __name__ ==
|
|
70
|
+
if __name__ == "__main__":
|
|
71
71
|
cli()
|
misc/remotexpc_sniffer.py
CHANGED
|
@@ -22,7 +22,7 @@ FRAME_HEADER_SIZE = 9
|
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
def create_stream_key(src: str, sport: int, dst: str, dport: int) -> str:
|
|
25
|
-
return f
|
|
25
|
+
return f"{src}/{sport}//{dst}/{dport}"
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
class TCPStream:
|
|
@@ -37,7 +37,7 @@ class TCPStream:
|
|
|
37
37
|
self.segments = {} # data segments to add later
|
|
38
38
|
|
|
39
39
|
def __repr__(self) -> str:
|
|
40
|
-
return f
|
|
40
|
+
return f"Stream<{self.key}>"
|
|
41
41
|
|
|
42
42
|
def __len__(self) -> int:
|
|
43
43
|
return len(self.data)
|
|
@@ -58,12 +58,12 @@ class TCPStream:
|
|
|
58
58
|
return False
|
|
59
59
|
else:
|
|
60
60
|
# if this data is in order (has a place to be inserted)
|
|
61
|
-
self.data[seq_offset:seq_offset + data_len] = data
|
|
61
|
+
self.data[seq_offset : seq_offset + data_len] = data
|
|
62
62
|
# check if there are any waiting data segments to add
|
|
63
63
|
for seq_offset in sorted(self.segments.keys()):
|
|
64
64
|
if seq_offset <= len(self.data): # if we can add this segment to the stream
|
|
65
65
|
segment_payload = self.segments[seq_offset]
|
|
66
|
-
self.data[seq_offset:seq_offset + len(segment_payload)] = segment_payload
|
|
66
|
+
self.data[seq_offset : seq_offset + len(segment_payload)] = segment_payload
|
|
67
67
|
self.segments.pop(seq_offset)
|
|
68
68
|
else:
|
|
69
69
|
break # short circuit because list is sorted
|
|
@@ -72,12 +72,12 @@ class TCPStream:
|
|
|
72
72
|
|
|
73
73
|
class H2Stream(TCPStream):
|
|
74
74
|
def pop_frames(self) -> list[Frame]:
|
|
75
|
-
"""
|
|
75
|
+
"""Pop all available H2Frames"""
|
|
76
76
|
|
|
77
77
|
# If self.data starts with the http/2 magic bytes, pop them off
|
|
78
78
|
if self.data.startswith(HTTP2_MAGIC):
|
|
79
|
-
logger.debug(
|
|
80
|
-
self.data = self.data[len(HTTP2_MAGIC):]
|
|
79
|
+
logger.debug("HTTP/2 magic bytes")
|
|
80
|
+
self.data = self.data[len(HTTP2_MAGIC) :]
|
|
81
81
|
self.seq += len(HTTP2_MAGIC)
|
|
82
82
|
|
|
83
83
|
frames = []
|
|
@@ -120,13 +120,14 @@ class RemoteXPCSniffer:
|
|
|
120
120
|
tcp_pkt = pkt[TCP]
|
|
121
121
|
stream_key = create_stream_key(net_pkt.src, tcp_pkt.sport, net_pkt.dst, tcp_pkt.dport)
|
|
122
122
|
stream = self._h2_streams.setdefault(
|
|
123
|
-
stream_key, H2Stream(net_pkt.src, tcp_pkt.sport, net_pkt.dst, tcp_pkt.dport)
|
|
123
|
+
stream_key, H2Stream(net_pkt.src, tcp_pkt.sport, net_pkt.dst, tcp_pkt.dport)
|
|
124
|
+
)
|
|
124
125
|
stream_finished_assembling = stream.add(tcp_pkt)
|
|
125
126
|
if stream_finished_assembling: # if we just added something in order
|
|
126
127
|
self._process_stream(stream)
|
|
127
128
|
|
|
128
129
|
def _handle_data_frame(self, stream: H2Stream, frame: DataFrame) -> None:
|
|
129
|
-
previous_frame_data = self._previous_frame_data.get(stream.key, b
|
|
130
|
+
previous_frame_data = self._previous_frame_data.get(stream.key, b"")
|
|
130
131
|
try:
|
|
131
132
|
payload = XpcWrapper.parse(previous_frame_data + frame.data).message.payload
|
|
132
133
|
if payload is None:
|
|
@@ -134,10 +135,11 @@ class RemoteXPCSniffer:
|
|
|
134
135
|
xpc_message = decode_xpc_object(payload.obj)
|
|
135
136
|
except ConstError: # if we don't know what this payload is
|
|
136
137
|
logger.debug(
|
|
137
|
-
f
|
|
138
|
+
f"New Data frame {stream.src}->{stream.dst} on HTTP/2 stream {frame.stream_id} TCP port {stream.dport}"
|
|
139
|
+
)
|
|
138
140
|
hexdump(frame.data[:64])
|
|
139
141
|
if len(frame.data) > 64:
|
|
140
|
-
logger.debug(f
|
|
142
|
+
logger.debug(f"... {len(frame.data)} bytes")
|
|
141
143
|
return
|
|
142
144
|
except StreamError:
|
|
143
145
|
self._previous_frame_data[stream.key] = previous_frame_data + frame.data
|
|
@@ -149,27 +151,26 @@ class RemoteXPCSniffer:
|
|
|
149
151
|
if xpc_message is None:
|
|
150
152
|
return
|
|
151
153
|
|
|
152
|
-
logger.info(f
|
|
154
|
+
logger.info(f"As Python Object (#{frame.stream_id}): {pformat(xpc_message)}")
|
|
153
155
|
|
|
154
156
|
# print `pairingData` if exists, since it contains an inner struct
|
|
155
|
-
if
|
|
157
|
+
if "value" not in xpc_message:
|
|
156
158
|
return
|
|
157
|
-
message = xpc_message[
|
|
158
|
-
if
|
|
159
|
+
message = xpc_message["value"]["message"]
|
|
160
|
+
if "plain" not in message:
|
|
159
161
|
return
|
|
160
|
-
plain = message[
|
|
161
|
-
if
|
|
162
|
+
plain = message["plain"]["_0"]
|
|
163
|
+
if "event" not in plain:
|
|
162
164
|
return
|
|
163
|
-
pairing_data = plain[
|
|
165
|
+
pairing_data = plain["event"]["_0"]["pairingData"]["_0"]["data"]
|
|
164
166
|
logger.info(PairingDataComponentTLVBuf.parse(pairing_data))
|
|
165
167
|
|
|
166
168
|
def _handle_single_frame(self, stream: H2Stream, frame: Frame) -> None:
|
|
167
|
-
logger.debug(f
|
|
169
|
+
logger.debug(f"New HTTP/2 frame: {stream.key} ({frame})")
|
|
168
170
|
if isinstance(frame, HeadersFrame):
|
|
169
|
-
logger.debug(
|
|
170
|
-
f'{stream.src} opening stream {frame.stream_id} for communication on port {stream.dport}')
|
|
171
|
+
logger.debug(f"{stream.src} opening stream {frame.stream_id} for communication on port {stream.dport}")
|
|
171
172
|
elif isinstance(frame, GoAwayFrame):
|
|
172
|
-
logger.debug(f
|
|
173
|
+
logger.debug(f"{stream.src} closing stream {frame.stream_id} on port {stream.sport}")
|
|
173
174
|
elif isinstance(frame, DataFrame):
|
|
174
175
|
self._handle_data_frame(stream, frame)
|
|
175
176
|
|
|
@@ -180,27 +181,27 @@ class RemoteXPCSniffer:
|
|
|
180
181
|
|
|
181
182
|
@click.group()
|
|
182
183
|
def cli():
|
|
183
|
-
"""
|
|
184
|
+
"""Parse RemoteXPC traffic"""
|
|
184
185
|
pass
|
|
185
186
|
|
|
186
187
|
|
|
187
188
|
@cli.command()
|
|
188
|
-
@click.argument(
|
|
189
|
+
@click.argument("file", type=click.Path(exists=True, file_okay=True, dir_okay=False))
|
|
189
190
|
def offline(file: str):
|
|
190
|
-
"""
|
|
191
|
+
"""Parse RemoteXPC traffic from a .pcap file"""
|
|
191
192
|
sniffer = RemoteXPCSniffer()
|
|
192
193
|
for p in sniff(offline=file):
|
|
193
194
|
sniffer.process_packet(p)
|
|
194
195
|
|
|
195
196
|
|
|
196
197
|
@cli.command()
|
|
197
|
-
@click.argument(
|
|
198
|
+
@click.argument("iface")
|
|
198
199
|
def live(iface: str):
|
|
199
|
-
"""
|
|
200
|
+
"""Parse RemoteXPC live from a given network interface"""
|
|
200
201
|
sniffer = RemoteXPCSniffer()
|
|
201
202
|
sniff(iface=iface, prn=sniffer.process_packet)
|
|
202
203
|
|
|
203
204
|
|
|
204
|
-
if __name__ ==
|
|
205
|
+
if __name__ == "__main__":
|
|
205
206
|
coloredlogs.install(level=logging.DEBUG)
|
|
206
207
|
cli()
|
pymobiledevice3/__main__.py
CHANGED
|
@@ -13,34 +13,53 @@ import click
|
|
|
13
13
|
import coloredlogs
|
|
14
14
|
|
|
15
15
|
from pymobiledevice3.cli.cli_common import TUNNEL_ENV_VAR, isatty
|
|
16
|
-
from pymobiledevice3.exceptions import
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
from pymobiledevice3.exceptions import (
|
|
17
|
+
AccessDeniedError,
|
|
18
|
+
CloudConfigurationAlreadyPresentError,
|
|
19
|
+
ConnectionFailedError,
|
|
20
|
+
ConnectionFailedToUsbmuxdError,
|
|
21
|
+
DeprecationError,
|
|
22
|
+
DeveloperModeError,
|
|
23
|
+
DeveloperModeIsNotEnabledError,
|
|
24
|
+
DeviceHasPasscodeSetError,
|
|
25
|
+
DeviceNotFoundError,
|
|
26
|
+
FeatureNotSupportedError,
|
|
27
|
+
InternalError,
|
|
28
|
+
InvalidServiceError,
|
|
29
|
+
MessageNotSupportedError,
|
|
30
|
+
MissingValueError,
|
|
31
|
+
NoDeviceConnectedError,
|
|
32
|
+
NotEnoughDiskSpaceError,
|
|
33
|
+
NotPairedError,
|
|
34
|
+
OSNotSupportedError,
|
|
35
|
+
PairingDialogResponsePendingError,
|
|
36
|
+
PasswordRequiredError,
|
|
37
|
+
QuicProtocolNotSupportedError,
|
|
38
|
+
RSDRequiredError,
|
|
39
|
+
SetProhibitedError,
|
|
40
|
+
TunneldConnectionError,
|
|
41
|
+
UserDeniedPairingError,
|
|
42
|
+
)
|
|
23
43
|
from pymobiledevice3.lockdown import retry_create_using_usbmux
|
|
24
44
|
from pymobiledevice3.osu.os_utils import get_os_utils
|
|
25
45
|
|
|
26
46
|
coloredlogs.install(level=logging.INFO)
|
|
27
47
|
|
|
28
|
-
logging.getLogger(
|
|
29
|
-
logging.getLogger(
|
|
30
|
-
logging.getLogger(
|
|
31
|
-
logging.getLogger(
|
|
32
|
-
logging.getLogger(
|
|
33
|
-
logging.getLogger(
|
|
34
|
-
logging.getLogger(
|
|
35
|
-
logging.getLogger(
|
|
36
|
-
logging.getLogger('urllib3.connectionpool').disabled = True
|
|
48
|
+
logging.getLogger("quic").disabled = True
|
|
49
|
+
logging.getLogger("asyncio").disabled = True
|
|
50
|
+
logging.getLogger("parso.cache").disabled = True
|
|
51
|
+
logging.getLogger("parso.cache.pickle").disabled = True
|
|
52
|
+
logging.getLogger("parso.python.diff").disabled = True
|
|
53
|
+
logging.getLogger("humanfriendly.prompts").disabled = True
|
|
54
|
+
logging.getLogger("blib2to3.pgen2.driver").disabled = True
|
|
55
|
+
logging.getLogger("urllib3.connectionpool").disabled = True
|
|
37
56
|
|
|
38
57
|
logger = logging.getLogger(__name__)
|
|
39
58
|
|
|
40
59
|
# For issue https://github.com/doronz88/pymobiledevice3/issues/1217, details: https://bugs.python.org/issue37373
|
|
41
|
-
if sys.platform ==
|
|
60
|
+
if sys.platform == "win32":
|
|
42
61
|
with warnings.catch_warnings():
|
|
43
|
-
warnings.simplefilter(
|
|
62
|
+
warnings.simplefilter("ignore", category=DeprecationWarning)
|
|
44
63
|
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
|
45
64
|
|
|
46
65
|
INVALID_SERVICE_MESSAGE = """Failed to start service. Possible reasons are:
|
|
@@ -62,36 +81,36 @@ INVALID_SERVICE_MESSAGE = """Failed to start service. Possible reasons are:
|
|
|
62
81
|
https://github.com/doronz88/pymobiledevice3/issues/new?assignees=&labels=&projects=&template=bug_report.md&title=
|
|
63
82
|
"""
|
|
64
83
|
|
|
65
|
-
CONTEXT_SETTINGS =
|
|
84
|
+
CONTEXT_SETTINGS = {"help_option_names": ["-h", "--help"], "max_content_width": 400}
|
|
66
85
|
|
|
67
86
|
# Mapping of index options to import file names
|
|
68
87
|
CLI_GROUPS = {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
88
|
+
"activation": "activation",
|
|
89
|
+
"afc": "afc",
|
|
90
|
+
"amfi": "amfi",
|
|
91
|
+
"apps": "apps",
|
|
92
|
+
"backup2": "backup",
|
|
93
|
+
"bonjour": "bonjour",
|
|
94
|
+
"companion": "companion_proxy",
|
|
95
|
+
"crash": "crash",
|
|
96
|
+
"developer": "developer",
|
|
97
|
+
"diagnostics": "diagnostics",
|
|
98
|
+
"lockdown": "lockdown",
|
|
99
|
+
"mounter": "mounter",
|
|
100
|
+
"notification": "notification",
|
|
101
|
+
"pcap": "pcap",
|
|
102
|
+
"power-assertion": "power_assertion",
|
|
103
|
+
"processes": "processes",
|
|
104
|
+
"profile": "profile",
|
|
105
|
+
"provision": "provision",
|
|
106
|
+
"remote": "remote",
|
|
107
|
+
"restore": "restore",
|
|
108
|
+
"springboard": "springboard",
|
|
109
|
+
"syslog": "syslog",
|
|
110
|
+
"usbmux": "usbmux",
|
|
111
|
+
"webinspector": "webinspector",
|
|
112
|
+
"version": "version",
|
|
113
|
+
"install-completions": "completions",
|
|
95
114
|
}
|
|
96
115
|
|
|
97
116
|
# Set if used the `--reconnect` option
|
|
@@ -103,26 +122,26 @@ class Pmd3Cli(click.Group):
|
|
|
103
122
|
return CLI_GROUPS.keys()
|
|
104
123
|
|
|
105
124
|
def get_command(self, ctx: click.Context, name: str) -> click.Command:
|
|
106
|
-
if name not in CLI_GROUPS
|
|
125
|
+
if name not in CLI_GROUPS:
|
|
107
126
|
self.handle_invalid_command(ctx, name)
|
|
108
127
|
return self.import_and_get_command(ctx, name)
|
|
109
128
|
|
|
110
129
|
def handle_invalid_command(self, ctx: click.Context, name: str) -> None:
|
|
111
130
|
suggested_commands = self.search_commands(name)
|
|
112
131
|
suggestion = self.format_suggestions(suggested_commands)
|
|
113
|
-
ctx.fail(f
|
|
132
|
+
ctx.fail(f"No such command {name!r}{suggestion}")
|
|
114
133
|
|
|
115
134
|
@staticmethod
|
|
116
135
|
def format_suggestions(suggestions: list[str]) -> str:
|
|
117
136
|
if not suggestions:
|
|
118
|
-
return
|
|
119
|
-
cmds = textwrap.indent(
|
|
120
|
-
return f
|
|
137
|
+
return ""
|
|
138
|
+
cmds = textwrap.indent("\n".join(suggestions), " " * 4)
|
|
139
|
+
return f"\nDid you mean this?\n{cmds}"
|
|
121
140
|
|
|
122
141
|
@staticmethod
|
|
123
142
|
def import_and_get_command(ctx: click.Context, name: str) -> click.Command:
|
|
124
|
-
module_name = f
|
|
125
|
-
mod = __import__(module_name, None, None, [
|
|
143
|
+
module_name = f"pymobiledevice3.cli.{CLI_GROUPS[name]}"
|
|
144
|
+
mod = __import__(module_name, None, None, ["cli"])
|
|
126
145
|
command = mod.cli.get_command(ctx, name)
|
|
127
146
|
if not command:
|
|
128
147
|
command_name = mod.cli.list_commands(ctx)[0]
|
|
@@ -131,20 +150,20 @@ class Pmd3Cli(click.Group):
|
|
|
131
150
|
|
|
132
151
|
@staticmethod
|
|
133
152
|
def highlight_keyword(text: str, keyword: str) -> str:
|
|
134
|
-
return re.sub(f
|
|
153
|
+
return re.sub(f"({keyword})", click.style("\\1", bold=True), text, flags=re.IGNORECASE)
|
|
135
154
|
|
|
136
155
|
@staticmethod
|
|
137
156
|
def collect_commands(command: click.Command) -> Union[str, list[str]]:
|
|
138
157
|
commands = []
|
|
139
158
|
if isinstance(command, click.Group):
|
|
140
|
-
for
|
|
159
|
+
for _k, v in command.commands.items():
|
|
141
160
|
cmd = Pmd3Cli.collect_commands(v)
|
|
142
161
|
if isinstance(cmd, list):
|
|
143
|
-
commands.extend([f
|
|
162
|
+
commands.extend([f"{command.name} {c}" for c in cmd])
|
|
144
163
|
else:
|
|
145
|
-
commands.append(f
|
|
164
|
+
commands.append(f"{command.name} {cmd}")
|
|
146
165
|
return commands
|
|
147
|
-
return f
|
|
166
|
+
return f"{command.name}"
|
|
148
167
|
|
|
149
168
|
@staticmethod
|
|
150
169
|
def search_commands(pattern: str) -> list[str]:
|
|
@@ -159,9 +178,9 @@ class Pmd3Cli(click.Group):
|
|
|
159
178
|
@staticmethod
|
|
160
179
|
def load_all_commands() -> list[str]:
|
|
161
180
|
all_commands = []
|
|
162
|
-
for key in CLI_GROUPS
|
|
163
|
-
module_name = f
|
|
164
|
-
mod = __import__(module_name, None, None, [
|
|
181
|
+
for key in CLI_GROUPS:
|
|
182
|
+
module_name = f"pymobiledevice3.cli.{CLI_GROUPS[key]}"
|
|
183
|
+
mod = __import__(module_name, None, None, ["cli"])
|
|
165
184
|
cmd = Pmd3Cli.collect_commands(mod.cli.commands[key])
|
|
166
185
|
if isinstance(cmd, list):
|
|
167
186
|
all_commands.extend(cmd)
|
|
@@ -171,7 +190,7 @@ class Pmd3Cli(click.Group):
|
|
|
171
190
|
|
|
172
191
|
|
|
173
192
|
@click.command(cls=Pmd3Cli, context_settings=CONTEXT_SETTINGS)
|
|
174
|
-
@click.option(
|
|
193
|
+
@click.option("--reconnect", is_flag=True, default=False, help="Reconnect to device when disconnected.")
|
|
175
194
|
def cli(reconnect: bool) -> None:
|
|
176
195
|
"""
|
|
177
196
|
\b
|
|
@@ -191,81 +210,87 @@ def invoke_cli_with_error_handling() -> bool:
|
|
|
191
210
|
try:
|
|
192
211
|
cli()
|
|
193
212
|
except NoDeviceConnectedError:
|
|
194
|
-
logger.error(
|
|
213
|
+
logger.error("Device is not connected")
|
|
195
214
|
return True
|
|
196
215
|
except ConnectionAbortedError:
|
|
197
|
-
logger.error(
|
|
216
|
+
logger.error("Device was disconnected")
|
|
198
217
|
return True
|
|
199
218
|
except NotPairedError:
|
|
200
|
-
logger.error(
|
|
219
|
+
logger.error("Device is not paired")
|
|
201
220
|
except UserDeniedPairingError:
|
|
202
|
-
logger.error(
|
|
221
|
+
logger.error("User refused to trust this computer")
|
|
203
222
|
except PairingDialogResponsePendingError:
|
|
204
|
-
logger.error(
|
|
223
|
+
logger.error("Waiting for user dialog approval")
|
|
205
224
|
except SetProhibitedError:
|
|
206
|
-
logger.error(
|
|
225
|
+
logger.error("lockdownd denied the access")
|
|
207
226
|
except MissingValueError:
|
|
208
|
-
logger.error(
|
|
227
|
+
logger.error("No such value")
|
|
209
228
|
except DeviceHasPasscodeSetError:
|
|
210
|
-
logger.error(
|
|
211
|
-
except DeveloperModeError
|
|
212
|
-
logger.error(
|
|
229
|
+
logger.error("Cannot enable developer-mode when passcode is set")
|
|
230
|
+
except DeveloperModeError:
|
|
231
|
+
logger.error("Failed to enable developer-mode.")
|
|
213
232
|
except ConnectionFailedToUsbmuxdError:
|
|
214
|
-
logger.error(
|
|
233
|
+
logger.error("Failed to connect to usbmuxd socket. Make sure it's running.")
|
|
215
234
|
except ConnectionFailedError:
|
|
216
|
-
logger.error(
|
|
235
|
+
logger.error("Failed to connect to service port.")
|
|
217
236
|
return True
|
|
218
237
|
except MessageNotSupportedError:
|
|
219
|
-
logger.error(
|
|
238
|
+
logger.error("Message not supported for this iOS version")
|
|
220
239
|
traceback.print_exc()
|
|
221
240
|
except InternalError:
|
|
222
|
-
logger.error(
|
|
241
|
+
logger.error("Internal Error")
|
|
223
242
|
except DeveloperModeIsNotEnabledError:
|
|
224
|
-
logger.error(
|
|
225
|
-
|
|
243
|
+
logger.error(
|
|
244
|
+
"Developer Mode is disabled. You can try to enable it using: "
|
|
245
|
+
"python3 -m pymobiledevice3 amfi enable-developer-mode"
|
|
246
|
+
)
|
|
226
247
|
except (InvalidServiceError, RSDRequiredError) as e:
|
|
227
248
|
should_retry_over_tunneld = False
|
|
228
249
|
if isinstance(e, RSDRequiredError):
|
|
229
|
-
logger.warning(
|
|
250
|
+
logger.warning("Trying again over tunneld since RSD is required for this command")
|
|
230
251
|
should_retry_over_tunneld = True
|
|
231
|
-
elif (e.identifier is not None) and (
|
|
232
|
-
logger.warning(
|
|
252
|
+
elif (e.identifier is not None) and ("developer" in sys.argv) and ("--tunnel" not in sys.argv):
|
|
253
|
+
logger.warning("Got an InvalidServiceError. Trying again over tunneld since it is a developer command")
|
|
233
254
|
should_retry_over_tunneld = True
|
|
234
255
|
if should_retry_over_tunneld:
|
|
235
256
|
# use a single space because click will ignore envvars of empty strings
|
|
236
|
-
os.environ[TUNNEL_ENV_VAR] = e.identifier or
|
|
257
|
+
os.environ[TUNNEL_ENV_VAR] = e.identifier or " "
|
|
237
258
|
return main()
|
|
238
259
|
logger.error(INVALID_SERVICE_MESSAGE)
|
|
239
260
|
except PasswordRequiredError:
|
|
240
|
-
logger.error(
|
|
261
|
+
logger.error("Device is password protected. Please unlock and retry")
|
|
241
262
|
except AccessDeniedError:
|
|
242
263
|
logger.error(get_os_utils().access_denied_error)
|
|
243
264
|
except BrokenPipeError:
|
|
244
265
|
traceback.print_exc()
|
|
245
266
|
except TunneldConnectionError:
|
|
246
267
|
logger.error(
|
|
247
|
-
|
|
248
|
-
|
|
268
|
+
"Unable to connect to Tunneld. You can start one using:\nsudo python3 -m pymobiledevice3 remote tunneld"
|
|
269
|
+
)
|
|
249
270
|
except DeviceNotFoundError as e:
|
|
250
|
-
logger.error(f
|
|
271
|
+
logger.error(f"Device not found: {e.udid}")
|
|
251
272
|
except NotEnoughDiskSpaceError:
|
|
252
|
-
logger.error(
|
|
273
|
+
logger.error("Not enough disk space")
|
|
253
274
|
except DeprecationError:
|
|
254
|
-
logger.error(
|
|
275
|
+
logger.error("failed to query MobileGestalt, MobileGestalt deprecated (iOS >= 17.4).")
|
|
255
276
|
except OSNotSupportedError as e:
|
|
256
277
|
logger.error(
|
|
257
|
-
f
|
|
258
|
-
f
|
|
278
|
+
f"Unsupported OS - {e.os_name}. To add support, consider contributing at "
|
|
279
|
+
f"https://github.com/doronz88/pymobiledevice3."
|
|
280
|
+
)
|
|
259
281
|
except CloudConfigurationAlreadyPresentError:
|
|
260
|
-
logger.error(
|
|
261
|
-
|
|
262
|
-
|
|
282
|
+
logger.error(
|
|
283
|
+
"A cloud configuration is already present on device. You must first erase the device in order "
|
|
284
|
+
"to install new one:\n"
|
|
285
|
+
"> pymobiledevice3 profile erase-device"
|
|
286
|
+
)
|
|
263
287
|
except FeatureNotSupportedError as e:
|
|
264
288
|
logger.error(
|
|
265
|
-
f
|
|
266
|
-
f
|
|
267
|
-
|
|
268
|
-
|
|
289
|
+
f"Missing implementation of `{e.feature}` on `{e.os_name}`. To add support, consider contributing at "
|
|
290
|
+
f"https://github.com/doronz88/pymobiledevice3."
|
|
291
|
+
)
|
|
292
|
+
except QuicProtocolNotSupportedError:
|
|
293
|
+
logger.error("Encountered a QUIC protocol error.")
|
|
269
294
|
|
|
270
295
|
return False
|
|
271
296
|
|
|
@@ -282,9 +307,9 @@ def main() -> None:
|
|
|
282
307
|
lockdown = retry_create_using_usbmux(None)
|
|
283
308
|
lockdown.close()
|
|
284
309
|
except KeyboardInterrupt:
|
|
285
|
-
print(
|
|
310
|
+
print("Aborted.")
|
|
286
311
|
break
|
|
287
312
|
|
|
288
313
|
|
|
289
|
-
if __name__ ==
|
|
314
|
+
if __name__ == "__main__":
|
|
290
315
|
main()
|
pymobiledevice3/_version.py
CHANGED
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '
|
|
32
|
-
__version_tuple__ = version_tuple = (
|
|
31
|
+
__version__ = version = '5.1.2'
|
|
32
|
+
__version_tuple__ = version_tuple = (5, 1, 2)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|