pymobiledevice3 5.0.1__py3-none-any.whl → 5.0.3__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.
Potentially problematic release.
This version of pymobiledevice3 might be problematic. Click here for more details.
- misc/plist_sniffer.py +15 -15
- misc/remotexpc_sniffer.py +29 -28
- pymobiledevice3/__main__.py +128 -102
- pymobiledevice3/_version.py +2 -2
- pymobiledevice3/bonjour.py +36 -59
- 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 +25 -18
- 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 +602 -520
- pymobiledevice3/cli/diagnostics.py +38 -33
- pymobiledevice3/cli/lockdown.py +79 -74
- pymobiledevice3/cli/mounter.py +85 -68
- 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 +186 -110
- 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 +157 -79
- pymobiledevice3/common.py +1 -1
- pymobiledevice3/exceptions.py +154 -60
- pymobiledevice3/irecv.py +49 -53
- pymobiledevice3/irecv_devices.py +1489 -492
- pymobiledevice3/lockdown.py +396 -242
- 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 +371 -293
- pymobiledevice3/remote/utils.py +12 -11
- 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 +35 -54
- pymobiledevice3/restore/recovery.py +125 -135
- pymobiledevice3/restore/restore.py +524 -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 +350 -291
- pymobiledevice3/services/amfi.py +21 -18
- pymobiledevice3/services/companion.py +23 -19
- pymobiledevice3/services/crash_reports.py +60 -46
- 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 +442 -421
- 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 +330 -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 +69 -51
- 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 +129 -117
- pymobiledevice3/tcp_forwarder.py +35 -22
- pymobiledevice3/tunneld/api.py +20 -15
- pymobiledevice3/tunneld/server.py +212 -133
- pymobiledevice3/usbmux.py +183 -138
- pymobiledevice3/utils.py +14 -11
- {pymobiledevice3-5.0.1.dist-info → pymobiledevice3-5.0.3.dist-info}/METADATA +1 -1
- pymobiledevice3-5.0.3.dist-info/RECORD +173 -0
- pymobiledevice3-5.0.1.dist-info/RECORD +0 -173
- {pymobiledevice3-5.0.1.dist-info → pymobiledevice3-5.0.3.dist-info}/WHEEL +0 -0
- {pymobiledevice3-5.0.1.dist-info → pymobiledevice3-5.0.3.dist-info}/entry_points.txt +0 -0
- {pymobiledevice3-5.0.1.dist-info → pymobiledevice3-5.0.3.dist-info}/licenses/LICENSE +0 -0
- {pymobiledevice3-5.0.1.dist-info → pymobiledevice3-5.0.3.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,33 +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(
|
|
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
|
|
36
56
|
|
|
37
57
|
logger = logging.getLogger(__name__)
|
|
38
58
|
|
|
39
59
|
# For issue https://github.com/doronz88/pymobiledevice3/issues/1217, details: https://bugs.python.org/issue37373
|
|
40
|
-
if sys.platform ==
|
|
60
|
+
if sys.platform == "win32":
|
|
41
61
|
with warnings.catch_warnings():
|
|
42
|
-
warnings.simplefilter(
|
|
62
|
+
warnings.simplefilter("ignore", category=DeprecationWarning)
|
|
43
63
|
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
|
44
64
|
|
|
45
65
|
INVALID_SERVICE_MESSAGE = """Failed to start service. Possible reasons are:
|
|
@@ -61,36 +81,36 @@ INVALID_SERVICE_MESSAGE = """Failed to start service. Possible reasons are:
|
|
|
61
81
|
https://github.com/doronz88/pymobiledevice3/issues/new?assignees=&labels=&projects=&template=bug_report.md&title=
|
|
62
82
|
"""
|
|
63
83
|
|
|
64
|
-
CONTEXT_SETTINGS =
|
|
84
|
+
CONTEXT_SETTINGS = {"help_option_names": ["-h", "--help"], "max_content_width": 400}
|
|
65
85
|
|
|
66
86
|
# Mapping of index options to import file names
|
|
67
87
|
CLI_GROUPS = {
|
|
68
|
-
|
|
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
|
-
|
|
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",
|
|
94
114
|
}
|
|
95
115
|
|
|
96
116
|
# Set if used the `--reconnect` option
|
|
@@ -102,26 +122,26 @@ class Pmd3Cli(click.Group):
|
|
|
102
122
|
return CLI_GROUPS.keys()
|
|
103
123
|
|
|
104
124
|
def get_command(self, ctx: click.Context, name: str) -> click.Command:
|
|
105
|
-
if name not in CLI_GROUPS
|
|
125
|
+
if name not in CLI_GROUPS:
|
|
106
126
|
self.handle_invalid_command(ctx, name)
|
|
107
127
|
return self.import_and_get_command(ctx, name)
|
|
108
128
|
|
|
109
129
|
def handle_invalid_command(self, ctx: click.Context, name: str) -> None:
|
|
110
130
|
suggested_commands = self.search_commands(name)
|
|
111
131
|
suggestion = self.format_suggestions(suggested_commands)
|
|
112
|
-
ctx.fail(f
|
|
132
|
+
ctx.fail(f"No such command {name!r}{suggestion}")
|
|
113
133
|
|
|
114
134
|
@staticmethod
|
|
115
135
|
def format_suggestions(suggestions: list[str]) -> str:
|
|
116
136
|
if not suggestions:
|
|
117
|
-
return
|
|
118
|
-
cmds = textwrap.indent(
|
|
119
|
-
return f
|
|
137
|
+
return ""
|
|
138
|
+
cmds = textwrap.indent("\n".join(suggestions), " " * 4)
|
|
139
|
+
return f"\nDid you mean this?\n{cmds}"
|
|
120
140
|
|
|
121
141
|
@staticmethod
|
|
122
142
|
def import_and_get_command(ctx: click.Context, name: str) -> click.Command:
|
|
123
|
-
module_name = f
|
|
124
|
-
mod = __import__(module_name, None, None, [
|
|
143
|
+
module_name = f"pymobiledevice3.cli.{CLI_GROUPS[name]}"
|
|
144
|
+
mod = __import__(module_name, None, None, ["cli"])
|
|
125
145
|
command = mod.cli.get_command(ctx, name)
|
|
126
146
|
if not command:
|
|
127
147
|
command_name = mod.cli.list_commands(ctx)[0]
|
|
@@ -130,20 +150,20 @@ class Pmd3Cli(click.Group):
|
|
|
130
150
|
|
|
131
151
|
@staticmethod
|
|
132
152
|
def highlight_keyword(text: str, keyword: str) -> str:
|
|
133
|
-
return re.sub(f
|
|
153
|
+
return re.sub(f"({keyword})", click.style("\\1", bold=True), text, flags=re.IGNORECASE)
|
|
134
154
|
|
|
135
155
|
@staticmethod
|
|
136
156
|
def collect_commands(command: click.Command) -> Union[str, list[str]]:
|
|
137
157
|
commands = []
|
|
138
158
|
if isinstance(command, click.Group):
|
|
139
|
-
for
|
|
159
|
+
for _k, v in command.commands.items():
|
|
140
160
|
cmd = Pmd3Cli.collect_commands(v)
|
|
141
161
|
if isinstance(cmd, list):
|
|
142
|
-
commands.extend([f
|
|
162
|
+
commands.extend([f"{command.name} {c}" for c in cmd])
|
|
143
163
|
else:
|
|
144
|
-
commands.append(f
|
|
164
|
+
commands.append(f"{command.name} {cmd}")
|
|
145
165
|
return commands
|
|
146
|
-
return f
|
|
166
|
+
return f"{command.name}"
|
|
147
167
|
|
|
148
168
|
@staticmethod
|
|
149
169
|
def search_commands(pattern: str) -> list[str]:
|
|
@@ -158,9 +178,9 @@ class Pmd3Cli(click.Group):
|
|
|
158
178
|
@staticmethod
|
|
159
179
|
def load_all_commands() -> list[str]:
|
|
160
180
|
all_commands = []
|
|
161
|
-
for key in CLI_GROUPS
|
|
162
|
-
module_name = f
|
|
163
|
-
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"])
|
|
164
184
|
cmd = Pmd3Cli.collect_commands(mod.cli.commands[key])
|
|
165
185
|
if isinstance(cmd, list):
|
|
166
186
|
all_commands.extend(cmd)
|
|
@@ -170,7 +190,7 @@ class Pmd3Cli(click.Group):
|
|
|
170
190
|
|
|
171
191
|
|
|
172
192
|
@click.command(cls=Pmd3Cli, context_settings=CONTEXT_SETTINGS)
|
|
173
|
-
@click.option(
|
|
193
|
+
@click.option("--reconnect", is_flag=True, default=False, help="Reconnect to device when disconnected.")
|
|
174
194
|
def cli(reconnect: bool) -> None:
|
|
175
195
|
"""
|
|
176
196
|
\b
|
|
@@ -190,81 +210,87 @@ def invoke_cli_with_error_handling() -> bool:
|
|
|
190
210
|
try:
|
|
191
211
|
cli()
|
|
192
212
|
except NoDeviceConnectedError:
|
|
193
|
-
logger.
|
|
213
|
+
logger.exception("Device is not connected")
|
|
194
214
|
return True
|
|
195
215
|
except ConnectionAbortedError:
|
|
196
|
-
logger.
|
|
216
|
+
logger.exception("Device was disconnected")
|
|
197
217
|
return True
|
|
198
218
|
except NotPairedError:
|
|
199
|
-
logger.
|
|
219
|
+
logger.exception("Device is not paired")
|
|
200
220
|
except UserDeniedPairingError:
|
|
201
|
-
logger.
|
|
221
|
+
logger.exception("User refused to trust this computer")
|
|
202
222
|
except PairingDialogResponsePendingError:
|
|
203
|
-
logger.
|
|
223
|
+
logger.exception("Waiting for user dialog approval")
|
|
204
224
|
except SetProhibitedError:
|
|
205
|
-
logger.
|
|
225
|
+
logger.exception("lockdownd denied the access")
|
|
206
226
|
except MissingValueError:
|
|
207
|
-
logger.
|
|
227
|
+
logger.exception("No such value")
|
|
208
228
|
except DeviceHasPasscodeSetError:
|
|
209
|
-
logger.
|
|
210
|
-
except DeveloperModeError
|
|
211
|
-
logger.
|
|
229
|
+
logger.exception("Cannot enable developer-mode when passcode is set")
|
|
230
|
+
except DeveloperModeError:
|
|
231
|
+
logger.exception("Failed to enable developer-mode.")
|
|
212
232
|
except ConnectionFailedToUsbmuxdError:
|
|
213
|
-
logger.
|
|
233
|
+
logger.exception("Failed to connect to usbmuxd socket. Make sure it's running.")
|
|
214
234
|
except ConnectionFailedError:
|
|
215
|
-
logger.
|
|
235
|
+
logger.exception("Failed to connect to service port.")
|
|
216
236
|
return True
|
|
217
237
|
except MessageNotSupportedError:
|
|
218
|
-
logger.
|
|
238
|
+
logger.exception("Message not supported for this iOS version")
|
|
219
239
|
traceback.print_exc()
|
|
220
240
|
except InternalError:
|
|
221
|
-
logger.
|
|
241
|
+
logger.exception("Internal Error")
|
|
222
242
|
except DeveloperModeIsNotEnabledError:
|
|
223
|
-
logger.
|
|
224
|
-
|
|
243
|
+
logger.exception(
|
|
244
|
+
"Developer Mode is disabled. You can try to enable it using: "
|
|
245
|
+
"python3 -m pymobiledevice3 amfi enable-developer-mode"
|
|
246
|
+
)
|
|
225
247
|
except (InvalidServiceError, RSDRequiredError) as e:
|
|
226
248
|
should_retry_over_tunneld = False
|
|
227
249
|
if isinstance(e, RSDRequiredError):
|
|
228
|
-
logger.warning(
|
|
250
|
+
logger.warning("Trying again over tunneld since RSD is required for this command")
|
|
229
251
|
should_retry_over_tunneld = True
|
|
230
|
-
elif (e.identifier is not None) and (
|
|
231
|
-
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")
|
|
232
254
|
should_retry_over_tunneld = True
|
|
233
255
|
if should_retry_over_tunneld:
|
|
234
256
|
# use a single space because click will ignore envvars of empty strings
|
|
235
|
-
os.environ[TUNNEL_ENV_VAR] = e.identifier or
|
|
257
|
+
os.environ[TUNNEL_ENV_VAR] = e.identifier or " "
|
|
236
258
|
return main()
|
|
237
|
-
logger.
|
|
259
|
+
logger.exception(INVALID_SERVICE_MESSAGE)
|
|
238
260
|
except PasswordRequiredError:
|
|
239
|
-
logger.
|
|
261
|
+
logger.exception("Device is password protected. Please unlock and retry")
|
|
240
262
|
except AccessDeniedError:
|
|
241
|
-
logger.
|
|
263
|
+
logger.exception(get_os_utils().access_denied_error)
|
|
242
264
|
except BrokenPipeError:
|
|
243
265
|
traceback.print_exc()
|
|
244
266
|
except TunneldConnectionError:
|
|
245
|
-
logger.
|
|
246
|
-
|
|
247
|
-
|
|
267
|
+
logger.exception(
|
|
268
|
+
"Unable to connect to Tunneld. You can start one using:\nsudo python3 -m pymobiledevice3 remote tunneld"
|
|
269
|
+
)
|
|
248
270
|
except DeviceNotFoundError as e:
|
|
249
|
-
logger.
|
|
271
|
+
logger.exception(f"Device not found: {e.udid}")
|
|
250
272
|
except NotEnoughDiskSpaceError:
|
|
251
|
-
logger.
|
|
273
|
+
logger.exception("Not enough disk space")
|
|
252
274
|
except DeprecationError:
|
|
253
|
-
logger.
|
|
275
|
+
logger.exception("failed to query MobileGestalt, MobileGestalt deprecated (iOS >= 17.4).")
|
|
254
276
|
except OSNotSupportedError as e:
|
|
255
|
-
logger.
|
|
256
|
-
f
|
|
257
|
-
f
|
|
277
|
+
logger.exception(
|
|
278
|
+
f"Unsupported OS - {e.os_name}. To add support, consider contributing at "
|
|
279
|
+
f"https://github.com/doronz88/pymobiledevice3."
|
|
280
|
+
)
|
|
258
281
|
except CloudConfigurationAlreadyPresentError:
|
|
259
|
-
logger.
|
|
260
|
-
|
|
261
|
-
|
|
282
|
+
logger.exception(
|
|
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
|
+
)
|
|
262
287
|
except FeatureNotSupportedError as e:
|
|
263
|
-
logger.
|
|
264
|
-
f
|
|
265
|
-
f
|
|
266
|
-
|
|
267
|
-
|
|
288
|
+
logger.exception(
|
|
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.exception("Encountered a QUIC protocol error.")
|
|
268
294
|
|
|
269
295
|
return False
|
|
270
296
|
|
|
@@ -281,9 +307,9 @@ def main() -> None:
|
|
|
281
307
|
lockdown = retry_create_using_usbmux(None)
|
|
282
308
|
lockdown.close()
|
|
283
309
|
except KeyboardInterrupt:
|
|
284
|
-
print(
|
|
310
|
+
print("Aborted.")
|
|
285
311
|
break
|
|
286
312
|
|
|
287
313
|
|
|
288
|
-
if __name__ ==
|
|
314
|
+
if __name__ == "__main__":
|
|
289
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 = '5.0.
|
|
32
|
-
__version_tuple__ = version_tuple = (5, 0,
|
|
31
|
+
__version__ = version = '5.0.3'
|
|
32
|
+
__version_tuple__ = version_tuple = (5, 0, 3)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|