Skip to content

Commit 24457d8

Browse files
committed
Add regression test for RustPython#7891
1 parent 68ada20 commit 24457d8

1 file changed

Lines changed: 246 additions & 0 deletions

File tree

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
import os
2+
import socket
3+
import ssl
4+
import sys
5+
import threading
6+
import time
7+
import urllib.request
8+
9+
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
10+
CERTFILE = os.path.join(ROOT_DIR, "Lib/test/certdata/keycert.pem")
11+
BODY = b"x" * 407_676
12+
13+
# TLS record body sizes observed from https://crates.io/api/v1/crates/tokio.
14+
TLS_RECORD_BODY_SIZES = [
15+
2855,
16+
281,
17+
53,
18+
218,
19+
1095,
20+
1395,
21+
1395,
22+
483,
23+
1395,
24+
1395,
25+
1395,
26+
1395,
27+
48,
28+
1360,
29+
1354,
30+
1395,
31+
1395,
32+
1395,
33+
1367,
34+
1395,
35+
1395,
36+
1395,
37+
1395,
38+
1326,
39+
1395,
40+
1395,
41+
1395,
42+
47,
43+
1395,
44+
1395,
45+
1395,
46+
1395,
47+
95,
48+
1395,
49+
1332,
50+
1287,
51+
1388,
52+
1395,
53+
1395,
54+
1374,
55+
1395,
56+
1380,
57+
794,
58+
791,
59+
1395,
60+
1381,
61+
1395,
62+
1395,
63+
1395,
64+
1333,
65+
1395,
66+
1395,
67+
1395,
68+
1395,
69+
1395,
70+
1395,
71+
965,
72+
16401,
73+
3914,
74+
2526,
75+
1041,
76+
8209,
77+
9233,
78+
16401,
79+
11650,
80+
10262,
81+
7486,
82+
3468,
83+
692,
84+
1041,
85+
16401,
86+
12242,
87+
9466,
88+
1041,
89+
8209,
90+
9233,
91+
8209,
92+
9233,
93+
16401,
94+
1041,
95+
8209,
96+
9233,
97+
6161,
98+
2065,
99+
9233,
100+
16401,
101+
16358,
102+
10806,
103+
1041,
104+
8209,
105+
16401,
106+
3914,
107+
16401,
108+
16401,
109+
3089,
110+
9233,
111+
4642,
112+
478,
113+
8209,
114+
3140,
115+
1752,
116+
9233,
117+
8209,
118+
8209,
119+
16401,
120+
16064,
121+
14676,
122+
13288,
123+
2065,
124+
16401,
125+
1041,
126+
8209,
127+
16401,
128+
1041,
129+
6374,
130+
1007,
131+
]
132+
133+
server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
134+
server_context.load_cert_chain(CERTFILE)
135+
listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
136+
listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
137+
listener.bind(("127.0.0.1", 0))
138+
listener.listen(1)
139+
addr, port = listener.getsockname()
140+
server_errors = []
141+
finished = False
142+
143+
144+
def guard_timeout():
145+
time.sleep(20)
146+
if not finished:
147+
print(
148+
"stdlib_urllib_https_misaligned_recv.py timed out",
149+
file=sys.stderr,
150+
flush=True,
151+
)
152+
os.abort()
153+
154+
155+
threading.Thread(target=guard_timeout, daemon=True).start()
156+
157+
158+
def drain_outgoing(outgoing, conn):
159+
while True:
160+
try:
161+
data = outgoing.read()
162+
except ssl.SSLWantReadError:
163+
return
164+
if not data:
165+
return
166+
conn.sendall(data)
167+
168+
169+
def run_server():
170+
try:
171+
conn, _ = listener.accept()
172+
conn.settimeout(5.0)
173+
conn.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
174+
175+
incoming = ssl.MemoryBIO()
176+
outgoing = ssl.MemoryBIO()
177+
tls = server_context.wrap_bio(incoming, outgoing, server_side=True)
178+
179+
while True:
180+
try:
181+
tls.do_handshake()
182+
break
183+
except ssl.SSLWantReadError:
184+
drain_outgoing(outgoing, conn)
185+
incoming.write(conn.recv(65536))
186+
except ssl.SSLWantWriteError:
187+
pass
188+
drain_outgoing(outgoing, conn)
189+
190+
request = b""
191+
while b"\r\n\r\n" not in request:
192+
try:
193+
request += tls.read(65536)
194+
except ssl.SSLWantReadError:
195+
drain_outgoing(outgoing, conn)
196+
incoming.write(conn.recv(65536))
197+
drain_outgoing(outgoing, conn)
198+
199+
response = (
200+
b"HTTP/1.1 200 OK\r\n"
201+
b"Connection: close\r\n"
202+
+ b"Content-Length: "
203+
+ str(len(BODY)).encode()
204+
+ b"\r\n"
205+
+ b"Content-Type: application/json\r\n"
206+
+ b"\r\n"
207+
+ BODY
208+
)
209+
plaintext_sizes = [max(1, n - 17) for n in TLS_RECORD_BODY_SIZES]
210+
pos = 0
211+
while pos < len(response):
212+
size = plaintext_sizes.pop(0) if plaintext_sizes else 16384
213+
end = min(len(response), pos + size)
214+
while pos < end:
215+
try:
216+
pos += tls.write(response[pos:end])
217+
except ssl.SSLWantWriteError:
218+
pass
219+
drain_outgoing(outgoing, conn)
220+
conn.close()
221+
except BaseException as exc:
222+
server_errors.append(exc)
223+
finally:
224+
listener.close()
225+
226+
227+
thread = threading.Thread(target=run_server)
228+
thread.start()
229+
230+
client_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
231+
client_context.check_hostname = False
232+
client_context.verify_mode = ssl.CERT_NONE
233+
opener = urllib.request.build_opener(
234+
urllib.request.ProxyHandler({}),
235+
urllib.request.HTTPSHandler(context=client_context),
236+
)
237+
try:
238+
with opener.open(f"https://{addr}:{port}/", timeout=5.0) as response:
239+
body = response.read()
240+
241+
thread.join(10.0)
242+
assert not thread.is_alive(), "server thread did not stop"
243+
assert not server_errors, server_errors
244+
assert body == BODY
245+
finally:
246+
finished = True

0 commit comments

Comments
 (0)