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,28 +1,30 @@
1
1
  #!/usr/bin/env python3
2
2
  import dataclasses
3
+ import logging
3
4
  import plistlib
4
5
  import xml.etree.ElementTree as ET
5
6
  from contextlib import closing
6
7
  from pathlib import Path
8
+ from typing import Optional
7
9
 
8
10
  import click
9
11
  import inquirer3
10
12
  import requests
13
+ from requests.structures import CaseInsensitiveDict
11
14
 
12
15
  from pymobiledevice3.exceptions import MobileActivationException
13
- from pymobiledevice3.lockdown import create_using_usbmux
14
16
  from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
15
17
 
16
- ACTIVATION_USER_AGENT_IOS = 'iOS Device Activator (MobileActivation-20 built on Jan 15 2012 at 19:07:28)'
17
- ACTIVATION_DEFAULT_URL = 'https://albert.apple.com/deviceservices/deviceActivation'
18
- ACTIVATION_DRM_HANDSHAKE_DEFAULT_URL = 'https://albert.apple.com/deviceservices/drmHandshake'
18
+ ACTIVATION_USER_AGENT_IOS = "iOS Device Activator (MobileActivation-20 built on Jan 15 2012 at 19:07:28)"
19
+ ACTIVATION_DEFAULT_URL = "https://albert.apple.com/deviceservices/deviceActivation"
20
+ ACTIVATION_DRM_HANDSHAKE_DEFAULT_URL = "https://albert.apple.com/deviceservices/drmHandshake"
19
21
  DEFAULT_HEADERS = {
20
- 'Accept': 'application/xml',
21
- 'User-Agent': ACTIVATION_USER_AGENT_IOS,
22
- 'Expect': '100-continue',
22
+ "Accept": "application/xml",
23
+ "User-Agent": ACTIVATION_USER_AGENT_IOS,
24
+ "Expect": "100-continue",
23
25
  }
24
26
 
25
- ACTIVATION_REQUESTS_SUBDIR = Path('offline_requests')
27
+ ACTIVATION_REQUESTS_SUBDIR = Path("offline_requests")
26
28
  NONCE_CYCLE_INTERVAL = 60 * 5
27
29
 
28
30
 
@@ -49,104 +51,185 @@ class MobileActivationService:
49
51
  There is no point in inheriting from BaseService since we'll need a new lockdown connection
50
52
  for each request.
51
53
  """
52
- SERVICE_NAME = 'com.apple.mobileactivationd'
54
+
55
+ SERVICE_NAME = "com.apple.mobileactivationd"
53
56
 
54
57
  def __init__(self, lockdown: LockdownServiceProvider) -> None:
55
58
  self.lockdown = lockdown
59
+ self.logger = logging.getLogger(__name__)
56
60
 
57
61
  @property
58
62
  def state(self):
59
- return self.send_command('GetActivationStateRequest')['Value']
63
+ try:
64
+ return self.send_command("GetActivationStateRequest")["Value"]
65
+ except Exception:
66
+ return self.lockdown.get_value(key="ActivationState")
60
67
 
61
68
  def wait_for_activation_session(self):
62
- blob = self.create_activation_session_info()
63
- handshake_request_message = blob['HandshakeRequestMessage']
64
- while handshake_request_message == blob['HandshakeRequestMessage']:
69
+ try:
70
+ blob = self.create_activation_session_info()
71
+ except Exception:
72
+ return
73
+ handshake_request_message = blob["HandshakeRequestMessage"]
74
+ while handshake_request_message == blob["HandshakeRequestMessage"]:
65
75
  blob = self.create_activation_session_info()
66
76
 
67
77
  @staticmethod
68
78
  def _get_activation_form_from_response(content: str) -> ActivationForm:
69
79
  root = ET.fromstring(content)
70
- title = root.find('page/navigationBar').get('title')
71
- description = root.find('page/tableView/section/footer').text
80
+ title = root.find("page/navigationBar").get("title")
81
+ description = root.find("page/tableView/section/footer").text
72
82
  fields = []
73
- for editable in root.findall('page//editableTextRow'):
83
+ for editable in root.findall("page//editableTextRow"):
74
84
  fields.append(
75
- Field(id=editable.get('id'), label=editable.get('label'), placeholder=editable.get('placeholder'),
76
- secure=bool(editable.get('secure', False))))
85
+ Field(
86
+ id=editable.get("id"),
87
+ label=editable.get("label"),
88
+ placeholder=editable.get("placeholder"),
89
+ secure=bool(editable.get("secure", False)),
90
+ )
91
+ )
77
92
  server_info = {}
78
- for k, v in root.find('serverInfo').items():
93
+ for k, v in root.find("serverInfo").items():
79
94
  server_info[k] = v
80
95
  return ActivationForm(title=title, description=description, fields=fields, server_info=server_info)
81
96
 
82
97
  def activate(self, skip_apple_id_query: bool = False) -> None:
83
- blob = self.create_activation_session_info()
98
+ if self.state != "Unactivated":
99
+ self.logger.error("Device is already activated!")
100
+ return
101
+
102
+ try:
103
+ blob = self.create_activation_session_info()
104
+ session_mode = True
105
+ except Exception:
106
+ session_mode = False
84
107
 
85
108
  # create drmHandshake request with blob from device
86
- headers = {'Content-Type': 'application/x-apple-plist'}
109
+ headers = {"Content-Type": "application/x-apple-plist"}
87
110
  headers.update(DEFAULT_HEADERS)
88
- content, headers = self.post(ACTIVATION_DRM_HANDSHAKE_DEFAULT_URL, data=plistlib.dumps(blob), headers=headers)
89
-
90
- activation_info = self.create_activation_info_with_session(content)
91
-
92
- content, headers = self.post(ACTIVATION_DEFAULT_URL, data={'activation-info': plistlib.dumps(activation_info)})
93
- content_type = headers['Content-Type']
94
-
95
- if content_type == 'application/x-buddyml':
111
+ if session_mode:
112
+ content, headers = self.post(
113
+ ACTIVATION_DRM_HANDSHAKE_DEFAULT_URL, data=plistlib.dumps(blob), headers=headers
114
+ )
115
+
116
+ activation_request = {}
117
+ if session_mode:
118
+ activation_info = self.create_activation_info_with_session(content)
119
+ else:
120
+ activation_info = self.lockdown.get_value(key="ActivationInfo")
121
+ activation_request.update({
122
+ "InStoreActivation": False,
123
+ "AppleSerialNumber": self.lockdown.get_value(key="SerialNumber"),
124
+ })
125
+ if self.lockdown.all_values.get("TelephonyCapability"):
126
+ req_pair = {
127
+ "IMEI": "InternationalMobileEquipmentIdentity",
128
+ "MEID": "MobileEquipmentIdentifier",
129
+ "IMSI": "InternationalMobileSubscriberIdentity",
130
+ "ICCID": "IntegratedCircuitCardIdentity",
131
+ }
132
+
133
+ has_meid = False
134
+ for k, v in req_pair.items():
135
+ lv = self.lockdown.all_values.get(v)
136
+ if lv is not None:
137
+ activation_request.update({k: lv})
138
+ continue
139
+ else:
140
+ self.logger.warn(f"Unable to get {k} from lockdownd")
141
+ if k == "MEID" and has_meid:
142
+ # Something is wrong if both IMEI & MEID is missing
143
+ raise MobileActivationException("Unable to obtain both IMEI and MEID")
144
+
145
+ # Either IMEI or MEID, or both
146
+ if k == "IMEI":
147
+ has_meid = lv is None
148
+ activation_request.update({"activation-info": plistlib.dumps(activation_info)})
149
+
150
+ content, headers = self.post(ACTIVATION_DEFAULT_URL, data=activation_request)
151
+ content_type = headers["Content-Type"]
152
+
153
+ if content_type == "application/x-buddyml":
96
154
  if skip_apple_id_query:
97
- raise MobileActivationException('Device is iCloud locked')
98
- activation_form = self._get_activation_form_from_response(content.decode())
99
- click.secho(activation_form.title, bold=True)
100
- click.secho(activation_form.description)
101
- fields = []
102
- for field in activation_form.fields:
103
- if field.secure:
104
- fields.append(inquirer3.Password(name=field.id, message=f'{field.label}'))
105
- else:
106
- fields.append(inquirer3.Text(name=field.id, message=f'{field.label}'))
107
- data = inquirer3.prompt(fields)
108
- data.update(activation_form.server_info)
109
- content, headers = self.post(ACTIVATION_DEFAULT_URL, data=data)
110
- content_type = headers['Content-Type']
111
-
112
- assert content_type == 'text/xml'
113
- self.activate_with_session(content, headers)
155
+ raise MobileActivationException("Device is iCloud locked")
156
+ try:
157
+ activation_form = self._get_activation_form_from_response(content.decode())
158
+ except Exception as e:
159
+ raise MobileActivationException("Activation server response is invalid") from e
160
+ else:
161
+ click.secho(activation_form.title, bold=True)
162
+ click.secho(activation_form.description)
163
+ fields = []
164
+ for field in activation_form.fields:
165
+ if field.secure:
166
+ fields.append(inquirer3.Password(name=field.id, message=f"{field.label}"))
167
+ else:
168
+ fields.append(inquirer3.Text(name=field.id, message=f"{field.label}"))
169
+ data = inquirer3.prompt(fields)
170
+ data.update(activation_form.server_info)
171
+ content, headers = self.post(ACTIVATION_DEFAULT_URL, data=data)
172
+ content_type = headers["Content-Type"]
173
+
174
+ assert content_type == "text/xml"
175
+ if session_mode:
176
+ self.activate_with_session(content, headers)
177
+ else:
178
+ self.activate_with_lockdown(content)
179
+
180
+ # set ActivationStateAcknowledged if we succeeded
181
+ self.lockdown.set_value(True, key="ActivationStateAcknowledged")
114
182
 
115
183
  def deactivate(self):
116
- return self.send_command('DeactivateRequest')
184
+ try:
185
+ return self.send_command("DeactivateRequest")
186
+ except Exception:
187
+ return self.lockdown._request("Deactivate")
117
188
 
118
189
  def create_activation_session_info(self):
119
- response = self.send_command('CreateTunnel1SessionInfoRequest')
120
- error = response.get('Error')
190
+ response = self.send_command("CreateTunnel1SessionInfoRequest")
191
+ error = response.get("Error")
121
192
  if error is not None:
122
- raise MobileActivationException(f'Mobile activation can not be done due to: {response}')
123
- return response['Value']
193
+ raise MobileActivationException(f"Mobile activation can not be done due to: {response}")
194
+ return response["Value"]
124
195
 
125
196
  def create_activation_info_with_session(self, handshake_response):
126
- response = self.send_command('CreateTunnel1ActivationInfoRequest', handshake_response)
127
- error = response.get('Error')
197
+ response = self.send_command("CreateTunnel1ActivationInfoRequest", handshake_response)
198
+ error = response.get("Error")
128
199
  if error is not None:
129
- raise MobileActivationException(f'Mobile activation can not be done due to: {response}')
130
- return response['Value']
200
+ raise MobileActivationException(f"Mobile activation can not be done due to: {response}")
201
+ return response["Value"]
202
+
203
+ def activate_with_lockdown(self, activation_record):
204
+ record = plistlib.loads(activation_record)
205
+ node = record.get("iphone-activation")
206
+ if node is None:
207
+ node = record.get("device-activation")
208
+ if node is None:
209
+ raise MobileActivationException("Activation record received is invalid")
210
+
211
+ self.lockdown._request("Activate", {"ActivationRecord": node.get("activation-record")})
131
212
 
132
213
  def activate_with_session(self, activation_record, headers):
133
214
  data = {
134
- 'Command': 'HandleActivationInfoWithSessionRequest',
135
- 'Value': activation_record,
215
+ "Command": "HandleActivationInfoWithSessionRequest",
216
+ "Value": activation_record,
136
217
  }
137
218
  if headers:
138
- data['ActivationResponseHeaders'] = dict(headers)
139
- with closing(create_using_usbmux(self.lockdown.udid).start_lockdown_service(self.SERVICE_NAME)) as service:
219
+ data["ActivationResponseHeaders"] = dict(headers)
220
+ with closing(self.lockdown.start_lockdown_service(self.SERVICE_NAME)) as service:
140
221
  return service.send_recv_plist(data)
141
222
 
142
- def send_command(self, command, value=''):
143
- data = {'Command': command}
223
+ def send_command(self, command: str, value: str = ""):
224
+ data = {"Command": command}
144
225
  if value:
145
- data['Value'] = value
146
- with closing(create_using_usbmux(self.lockdown.udid).start_lockdown_service(self.SERVICE_NAME)) as service:
226
+ data["Value"] = value
227
+ with closing(self.lockdown.start_lockdown_service(self.SERVICE_NAME)) as service:
147
228
  return service.send_recv_plist(data)
148
229
 
149
- def post(self, url, data, headers=None):
230
+ def post(
231
+ self, url: str, data: dict, headers: Optional[CaseInsensitiveDict[str, str]] = None
232
+ ) -> tuple[bytes, CaseInsensitiveDict[str]]:
150
233
  if headers is None:
151
234
  headers = DEFAULT_HEADERS
152
235