Skip to content

Commit 772d240

Browse files
committed
webrepl: Implement file transfer protocol in python.
Signed-off-by: Felix Dörre <felix@dogcraft.de>
1 parent 47e1338 commit 772d240

File tree

3 files changed

+110
-33
lines changed

3 files changed

+110
-33
lines changed
Lines changed: 53 additions & 0 deletions
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

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: 56 additions & 33 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy