Skip to content

Commit 772d240

Browse files
committed
webrepl: Implement file transfer protocol in python.
Signed-off-by: Felix Dörre <[email protected]>
1 parent 47e1338 commit 772d240

File tree

3 files changed

+110
-33
lines changed

3 files changed

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

micropython/net/webrepl/manifest.py

+1
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

+56-33
Original file line numberDiff line numberDiff line change
@@ -7,56 +7,73 @@
77
import sys
88
import websocket
99
import io
10+
from micropython import const
11+
from legacy_file_transfer import LegacyFileTransfer
1012

1113
listen_s = None
1214
client_s = None
1315

1416
DEBUG = 0
1517

16-
_DEFAULT_STATIC_HOST = const("https://felix.dogcraft.de/webrepl/")
18+
_DEFAULT_STATIC_HOST = const("https://micropython.org/webrepl/")
1719
_WELCOME_PROMPT = const("\r\nWebREPL connected\r\n>>> ")
1820
static_host = _DEFAULT_STATIC_HOST
1921
webrepl_pass = None
2022

23+
legacy = LegacyFileTransfer()
24+
25+
2126
class WebreplWrapper(io.IOBase):
2227
def __init__(self, sock):
2328
self.sock = sock
24-
self.sock.ioctl(9, 2)
29+
self.sock.ioctl(9, 1 if legacy else 2)
2530
if webrepl_pass is not None:
2631
self.pw = bytearray(16)
2732
self.pwPos = 0
2833
self.sock.write("Password: ")
2934
else:
3035
self.pw = None
31-
self.sock.write(_WELCOME_PROMPT);
36+
self.sock.write(_WELCOME_PROMPT)
3237

3338
def readinto(self, buf):
3439
if self.pw is not None:
35-
buf = bytearray(1)
40+
buf1 = bytearray(1)
3641
while True:
37-
l = self.sock.readinto(buf)
42+
l = self.sock.readinto(buf1)
3843
if l is None:
3944
continue
4045
if l <= 0:
4146
return l
42-
if buf[0] == 10 or buf[0] == 13:
47+
if buf1[0] == 10 or buf1[0] == 13:
4348
print("Authenticating with:")
44-
print(self.pw[0:self.pwPos])
45-
if bytes(self.pw[0:self.pwPos]) == webrepl_pass:
49+
print(self.pw[0 : self.pwPos])
50+
if bytes(self.pw[0 : self.pwPos]) == webrepl_pass:
4651
self.pw = None
4752
del self.pwPos
4853
self.sock.write(_WELCOME_PROMPT)
4954
break
5055
else:
51-
print(bytes(self.pw[0:self.pwPos]))
56+
print(bytes(self.pw[0 : self.pwPos]))
5257
print(webrepl_pass)
5358
self.sock.write("\r\nAccess denied\r\n")
5459
return 0
5560
else:
5661
if self.pwPos < len(self.pw):
57-
self.pw[self.pwPos] = buf[0]
62+
self.pw[self.pwPos] = buf1[0]
5863
self.pwPos = self.pwPos + 1
59-
return self.sock.readinto(buf)
64+
ret = None
65+
while True:
66+
ret = self.sock.readinto(buf)
67+
if ret is None or ret <= 0:
68+
break
69+
# ignore any non-data frames
70+
if self.sock.ioctl(8) >= 8:
71+
continue
72+
if self.sock.ioctl(8) == 2 and legacy:
73+
legacy.handle(buf, self.sock)
74+
continue
75+
break
76+
return ret
6077

6178
def write(self, buf):
6279
if self.pw is not None:
@@ -72,8 +89,8 @@ def ioctl(self, kind, arg):
7289
def close(self):
7390
self.sock.close()
7491

75-
def server_handshake(cl):
76-
req = cl.makefile("rwb", 0)
92+
93+
def server_handshake(req):
7794
# Skip HTTP GET line.
7895
l = req.readline()
7996
if DEBUG:
@@ -115,30 +132,35 @@ def server_handshake(cl):
115132
if DEBUG:
116133
print("respkey:", respkey)
117134

118-
cl.send(
135+
req.write(
119136
b"""\
120137
HTTP/1.1 101 Switching Protocols\r
121138
Upgrade: websocket\r
122139
Connection: Upgrade\r
123140
Sec-WebSocket-Accept: """
124141
)
125-
cl.send(respkey)
126-
cl.send("\r\n\r\n")
142+
req.write(respkey)
143+
req.write("\r\n\r\n")
127144

128145
return True
129146

130147

131148
def send_html(cl):
132-
cl.send(
149+
cl.write(
133150
b"""\
134151
HTTP/1.0 200 OK\r
135152
\r
136153
<base href=\""""
137154
)
138-
cl.send(static_host)
139-
cl.send(
155+
cl.write(static_host)
156+
cl.write(
140157
b"""\"></base>\r
141-
<script src="webreplv2_content.js"></script>\r
158+
<script src="webrepl"""
159+
)
160+
if not legacy:
161+
cl.write("v2")
162+
cl.write(
163+
b"""_content.js"></script>\r
142164
"""
143165
)
144166
cl.close()
@@ -149,10 +171,7 @@ def setup_conn(port, accept_handler):
149171
listen_s = socket.socket()
150172
listen_s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
151173

152-
ai = socket.getaddrinfo("0.0.0.0", port)
153-
addr = ai[0][4]
154-
155-
listen_s.bind(addr)
174+
listen_s.bind(("", port))
156175
listen_s.listen(1)
157176
if accept_handler:
158177
listen_s.setsockopt(socket.SOL_SOCKET, 20, accept_handler)
@@ -164,11 +183,14 @@ def setup_conn(port, accept_handler):
164183

165184

166185
def accept_conn(listen_sock):
167-
global client_s
186+
global client_s, webrepl_ssl_context
168187
cl, remote_addr = listen_sock.accept()
188+
sock = cl
189+
if webrepl_ssl_context is not None:
190+
sock = webrepl_ssl_context.wrap_socket(sock)
169191

170192
if not server_handshake(cl):
171-
send_html(cl)
193+
send_html(sock)
172194
return False
173195

174196
prev = os.dupterm(None)
@@ -180,13 +202,13 @@ def accept_conn(listen_sock):
180202
print("\nWebREPL connection from:", remote_addr)
181203
client_s = cl
182204

183-
ws = websocket.websocket(cl, True)
184-
ws = WebreplWrapper(ws)
205+
sock = websocket.websocket(sock)
206+
sock = WebreplWrapper(sock)
185207
cl.setblocking(False)
186208
# notify REPL on socket incoming data (ESP32/ESP8266-only)
187209
if hasattr(os, "dupterm_notify"):
188210
cl.setsockopt(socket.SOL_SOCKET, 20, os.dupterm_notify)
189-
os.dupterm(ws)
211+
os.dupterm(sock)
190212

191213
return True
192214

@@ -200,9 +222,10 @@ def stop():
200222
listen_s.close()
201223

202224

203-
def start(port=8266, password=None, accept_handler=accept_conn):
204-
global static_host, webrepl_pass
225+
def start(port=8266, password=None, ssl_context=None, accept_handler=accept_conn):
226+
global static_host, webrepl_pass, webrepl_ssl_context
205227
stop()
228+
webrepl_ssl_context = ssl_context
206229
webrepl_pass = password
207230
if password is None:
208231
try:
@@ -230,5 +253,5 @@ def start(port=8266, password=None, accept_handler=accept_conn):
230253
print("Started webrepl in manual override mode")
231254

232255

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

0 commit comments

Comments
 (0)