Skip to content

Commit dff0f56

Browse files
committed
webrepl: implement file transfer protocol in python
1 parent 47e1338 commit dff0f56

File tree

3 files changed

+99
-29
lines changed

3 files changed

+99
-29
lines changed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
class LegacyFileTransfer:
2+
def __init__(self):
3+
self.opbuf = bytearray(82)
4+
self.opptr = 0
5+
self.op = 0
6+
7+
def handle(self, buf, sock):
8+
if self.op == 2:
9+
import struct
10+
ret = self.file.readinto(memoryview(self.filebuf)[2:])
11+
memoryview(self.filebuf)[0:2] = struct.pack("<h", ret)
12+
sock.ioctl(9, 2)
13+
sock.write(memoryview(self.filebuf)[0:(2+ret)])
14+
if ret == 0:
15+
sock.write(b"WB\x00\x00")
16+
self.op = 0
17+
self.filebuf = None
18+
sock.ioctl(9, 1)
19+
return
20+
self.opbuf[self.opptr] = buf[0]
21+
self.opptr += 1
22+
if self.opptr != 82: # or bytes(buf[0:2]) != b"WA":
23+
return
24+
self.opptr = 0
25+
sock.ioctl(9, 2)
26+
sock.write(b"WB\x00\x00")
27+
sock.ioctl(9, 1)
28+
type = self.opbuf[2]
29+
if type == 2: # GET_FILE
30+
self.op = type
31+
name = self.opbuf[18:82].rstrip(b"\x00")
32+
self.filebuf = bytearray(2+256)
33+
self.file = open(name.decode(), "rb");
34+
elif type == 1: # PUT_FILE
35+
import struct
36+
name = self.opbuf[18:82].rstrip(b"\x00")
37+
size = struct.unpack("<I", self.opbuf[12:16])[0]
38+
filebuf = bytearray(512)
39+
with open(name.decode(), "wb") as file:
40+
while size > 0:
41+
ret = sock.readinto(filebuf)
42+
if ret is None:
43+
continue
44+
if ret > 0:
45+
file.write(memoryview(filebuf)[0:ret])
46+
size -= ret
47+
elif ret < 0:
48+
break
49+
sock.ioctl(9, 2)
50+
sock.write(b"WB\x00\x00")
51+
sock.ioctl(9, 1)

micropython/net/webrepl/manifest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
metadata(description="WebREPL server.", version="1.0.0")
22

33
module("webrepl.py", opt=3)
4+
module("legacy_file_transfer.py", opt=3)
45
module("webrepl_setup.py", opt=3)

micropython/net/webrepl/webrepl.py

Lines changed: 47 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,18 @@
1313

1414
DEBUG = 0
1515

16-
_DEFAULT_STATIC_HOST = const("https://felix.dogcraft.de/webrepl/")
16+
_DEFAULT_STATIC_HOST = const("https://micropython.org/webrepl/")
1717
_WELCOME_PROMPT = const("\r\nWebREPL connected\r\n>>> ")
1818
static_host = _DEFAULT_STATIC_HOST
1919
webrepl_pass = None
2020

21+
legacy = LegacyFileTransfer()
22+
23+
2124
class WebreplWrapper(io.IOBase):
2225
def __init__(self, sock):
2326
self.sock = sock
24-
self.sock.ioctl(9, 2)
27+
self.sock.ioctl(9, 1 if legacy else 2)
2528
if webrepl_pass is not None:
2629
self.pw = bytearray(16)
2730
self.pwPos = 0
@@ -32,14 +35,14 @@ def __init__(self, sock):
3235

3336
def readinto(self, buf):
3437
if self.pw is not None:
35-
buf = bytearray(1)
38+
buf1 = bytearray(1)
3639
while True:
37-
l = self.sock.readinto(buf)
40+
l = self.sock.readinto(buf1)
3841
if l is None:
3942
continue
4043
if l <= 0:
4144
return l
42-
if buf[0] == 10 or buf[0] == 13:
45+
if buf1[0] == 10 or buf1[0] == 13:
4346
print("Authenticating with:")
4447
print(self.pw[0:self.pwPos])
4548
if bytes(self.pw[0:self.pwPos]) == webrepl_pass:
@@ -54,9 +57,21 @@ def readinto(self, buf):
5457
return 0
5558
else:
5659
if self.pwPos < len(self.pw):
57-
self.pw[self.pwPos] = buf[0]
60+
self.pw[self.pwPos] = buf1[0]
5861
self.pwPos = self.pwPos + 1
59-
return self.sock.readinto(buf)
62+
ret = None
63+
while True:
64+
ret = self.sock.readinto(buf)
65+
if ret is None or ret <= 0:
66+
break
67+
# ignore any non-data frames
68+
if self.sock.ioctl(8) >= 8:
69+
continue
70+
if self.sock.ioctl(8) == 2 and legacy:
71+
legacy.handle(buf, self.sock)
72+
continue
73+
break
74+
return ret
6075

6176
def write(self, buf):
6277
if self.pw is not None:
@@ -72,8 +87,7 @@ def ioctl(self, kind, arg):
7287
def close(self):
7388
self.sock.close()
7489

75-
def server_handshake(cl):
76-
req = cl.makefile("rwb", 0)
90+
def server_handshake(req):
7791
# Skip HTTP GET line.
7892
l = req.readline()
7993
if DEBUG:
@@ -115,30 +129,33 @@ def server_handshake(cl):
115129
if DEBUG:
116130
print("respkey:", respkey)
117131

118-
cl.send(
132+
req.write(
119133
b"""\
120134
HTTP/1.1 101 Switching Protocols\r
121135
Upgrade: websocket\r
122136
Connection: Upgrade\r
123137
Sec-WebSocket-Accept: """
124138
)
125-
cl.send(respkey)
126-
cl.send("\r\n\r\n")
139+
req.write(respkey)
140+
req.write("\r\n\r\n")
127141

128142
return True
129143

130144

131145
def send_html(cl):
132-
cl.send(
146+
cl.write(
133147
b"""\
134148
HTTP/1.0 200 OK\r
135149
\r
136150
<base href=\""""
137151
)
138-
cl.send(static_host)
139-
cl.send(
152+
cl.write(static_host)
153+
cl.write(
140154
b"""\"></base>\r
141-
<script src="webreplv2_content.js"></script>\r
155+
<script src="webrepl""")
156+
if not legacy:
157+
cl.write("v2")
158+
cl.write(b"""_content.js"></script>\r
142159
"""
143160
)
144161
cl.close()
@@ -149,10 +166,7 @@ def setup_conn(port, accept_handler):
149166
listen_s = socket.socket()
150167
listen_s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
151168

152-
ai = socket.getaddrinfo("0.0.0.0", port)
153-
addr = ai[0][4]
154-
155-
listen_s.bind(addr)
169+
listen_s.bind(("", port))
156170
listen_s.listen(1)
157171
if accept_handler:
158172
listen_s.setsockopt(socket.SOL_SOCKET, 20, accept_handler)
@@ -164,11 +178,14 @@ def setup_conn(port, accept_handler):
164178

165179

166180
def accept_conn(listen_sock):
167-
global client_s
181+
global client_s, webrepl_ssl_context
168182
cl, remote_addr = listen_sock.accept()
183+
sock = cl
184+
if webrepl_ssl_context is not None:
185+
sock = webrepl_ssl_context.wrap_socket(sock)
169186

170187
if not server_handshake(cl):
171-
send_html(cl)
188+
send_html(sock)
172189
return False
173190

174191
prev = os.dupterm(None)
@@ -180,13 +197,13 @@ def accept_conn(listen_sock):
180197
print("\nWebREPL connection from:", remote_addr)
181198
client_s = cl
182199

183-
ws = websocket.websocket(cl, True)
184-
ws = WebreplWrapper(ws)
200+
sock = websocket.websocket(sock)
201+
sock = WebreplWrapper(sock)
185202
cl.setblocking(False)
186203
# notify REPL on socket incoming data (ESP32/ESP8266-only)
187204
if hasattr(os, "dupterm_notify"):
188205
cl.setsockopt(socket.SOL_SOCKET, 20, os.dupterm_notify)
189-
os.dupterm(ws)
206+
os.dupterm(sock)
190207

191208
return True
192209

@@ -200,9 +217,10 @@ def stop():
200217
listen_s.close()
201218

202219

203-
def start(port=8266, password=None, accept_handler=accept_conn):
204-
global static_host, webrepl_pass
220+
def start(port=8266, password=None, ssl_context = None, accept_handler=accept_conn):
221+
global static_host, webrepl_pass, webrepl_ssl_context
205222
stop()
223+
webrepl_ssl_context = ssl_context
206224
webrepl_pass = password
207225
if password is None:
208226
try:
@@ -230,5 +248,5 @@ def start(port=8266, password=None, accept_handler=accept_conn):
230248
print("Started webrepl in manual override mode")
231249

232250

233-
def start_foreground(port=8266, password=None):
234-
start(port, password, None)
251+
def start_foreground(port=8266, password=None, ssl_context=None):
252+
start(port, password, ssl_context=ssl_context, accept_handler=None)

0 commit comments

Comments
 (0)