From 2b39a771cb972aea6517947a5c54b478b1abddda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20D=C3=B6rre?= Date: Sat, 12 Oct 2024 16:45:00 +0000 Subject: [PATCH] webrepl: Implement file transfer protocol in python. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Felix Dörre --- .../net/webrepl/legacy_file_transfer.py | 51 ++++++++++++ micropython/net/webrepl/manifest.py | 1 + micropython/net/webrepl/webrepl.py | 80 ++++++++++++------- 3 files changed, 102 insertions(+), 30 deletions(-) create mode 100644 micropython/net/webrepl/legacy_file_transfer.py diff --git a/micropython/net/webrepl/legacy_file_transfer.py b/micropython/net/webrepl/legacy_file_transfer.py new file mode 100644 index 000000000..24260b263 --- /dev/null +++ b/micropython/net/webrepl/legacy_file_transfer.py @@ -0,0 +1,51 @@ +class LegacyFileTransfer: + def __init__(self): + self.opbuf = bytearray(82) + self.opptr = 0 + self.op = 0 + + def handle(self, buf, sock): + if self.op == 2: + import struct + ret = self.file.readinto(memoryview(self.filebuf)[2:]) + memoryview(self.filebuf)[0:2] = struct.pack(" 0: + ret = sock.readinto(filebuf) + if ret is None: + continue + if ret > 0: + file.write(memoryview(filebuf)[0:ret]) + size -= ret + elif ret < 0: + break + sock.ioctl(9, 2) + sock.write(b"WB\x00\x00") + sock.ioctl(9, 1) diff --git a/micropython/net/webrepl/manifest.py b/micropython/net/webrepl/manifest.py index 6674d06c1..ba7bf3c34 100644 --- a/micropython/net/webrepl/manifest.py +++ b/micropython/net/webrepl/manifest.py @@ -1,4 +1,5 @@ metadata(description="WebREPL server.", version="1.0.0") module("webrepl.py", opt=3) +module("legacy_file_transfer.py", opt=3) module("webrepl_setup.py", opt=3) diff --git a/micropython/net/webrepl/webrepl.py b/micropython/net/webrepl/webrepl.py index 4c07936e9..f256026ad 100644 --- a/micropython/net/webrepl/webrepl.py +++ b/micropython/net/webrepl/webrepl.py @@ -7,39 +7,44 @@ import sys import websocket import io +from micropython import const +from legacy_file_transfer import LegacyFileTransfer listen_s = None client_s = None DEBUG = 0 -_DEFAULT_STATIC_HOST = const("https://felix.dogcraft.de/webrepl/") +_DEFAULT_STATIC_HOST = const("https://micropython.org/webrepl/") _WELCOME_PROMPT = const("\r\nWebREPL connected\r\n>>> ") static_host = _DEFAULT_STATIC_HOST webrepl_pass = None +legacy = LegacyFileTransfer() + + class WebreplWrapper(io.IOBase): def __init__(self, sock): self.sock = sock - self.sock.ioctl(9, 2) + self.sock.ioctl(9, 1 if legacy else 2) if webrepl_pass is not None: self.pw = bytearray(16) self.pwPos = 0 self.sock.write("Password: ") else: self.pw = None - self.sock.write(_WELCOME_PROMPT); + self.sock.write(_WELCOME_PROMPT) def readinto(self, buf): if self.pw is not None: - buf = bytearray(1) + buf1 = bytearray(1) while True: - l = self.sock.readinto(buf) + l = self.sock.readinto(buf1) if l is None: continue if l <= 0: return l - if buf[0] == 10 or buf[0] == 13: + if buf1[0] == 10 or buf1[0] == 13: print("Authenticating with:") print(self.pw[0:self.pwPos]) if bytes(self.pw[0:self.pwPos]) == webrepl_pass: @@ -54,9 +59,21 @@ def readinto(self, buf): return 0 else: if self.pwPos < len(self.pw): - self.pw[self.pwPos] = buf[0] + self.pw[self.pwPos] = buf1[0] self.pwPos = self.pwPos + 1 - return self.sock.readinto(buf) + ret = None + while True: + ret = self.sock.readinto(buf) + if ret is None or ret <= 0: + break + # ignore any non-data frames + if self.sock.ioctl(8) >= 8: + continue + if self.sock.ioctl(8) == 2 and legacy: + legacy.handle(buf, self.sock) + continue + break + return ret def write(self, buf): if self.pw is not None: @@ -72,8 +89,7 @@ def ioctl(self, kind, arg): def close(self): self.sock.close() -def server_handshake(cl): - req = cl.makefile("rwb", 0) +def server_handshake(req): # Skip HTTP GET line. l = req.readline() if DEBUG: @@ -115,30 +131,33 @@ def server_handshake(cl): if DEBUG: print("respkey:", respkey) - cl.send( + req.write( b"""\ HTTP/1.1 101 Switching Protocols\r Upgrade: websocket\r Connection: Upgrade\r Sec-WebSocket-Accept: """ ) - cl.send(respkey) - cl.send("\r\n\r\n") + req.write(respkey) + req.write("\r\n\r\n") return True def send_html(cl): - cl.send( + cl.write( b"""\ HTTP/1.0 200 OK\r \r \r -\r +\r """ ) cl.close() @@ -149,10 +168,7 @@ def setup_conn(port, accept_handler): listen_s = socket.socket() listen_s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - ai = socket.getaddrinfo("0.0.0.0", port) - addr = ai[0][4] - - listen_s.bind(addr) + listen_s.bind(("", port)) listen_s.listen(1) if accept_handler: listen_s.setsockopt(socket.SOL_SOCKET, 20, accept_handler) @@ -164,11 +180,14 @@ def setup_conn(port, accept_handler): def accept_conn(listen_sock): - global client_s + global client_s, webrepl_ssl_context cl, remote_addr = listen_sock.accept() + sock = cl + if webrepl_ssl_context is not None: + sock = webrepl_ssl_context.wrap_socket(sock) if not server_handshake(cl): - send_html(cl) + send_html(sock) return False prev = os.dupterm(None) @@ -180,13 +199,13 @@ def accept_conn(listen_sock): print("\nWebREPL connection from:", remote_addr) client_s = cl - ws = websocket.websocket(cl, True) - ws = WebreplWrapper(ws) + sock = websocket.websocket(sock) + sock = WebreplWrapper(sock) cl.setblocking(False) # notify REPL on socket incoming data (ESP32/ESP8266-only) if hasattr(os, "dupterm_notify"): cl.setsockopt(socket.SOL_SOCKET, 20, os.dupterm_notify) - os.dupterm(ws) + os.dupterm(sock) return True @@ -200,9 +219,10 @@ def stop(): listen_s.close() -def start(port=8266, password=None, accept_handler=accept_conn): - global static_host, webrepl_pass +def start(port=8266, password=None, ssl_context = None, accept_handler=accept_conn): + global static_host, webrepl_pass, webrepl_ssl_context stop() + webrepl_ssl_context = ssl_context webrepl_pass = password if password is None: try: @@ -230,5 +250,5 @@ def start(port=8266, password=None, accept_handler=accept_conn): print("Started webrepl in manual override mode") -def start_foreground(port=8266, password=None): - start(port, password, None) +def start_foreground(port=8266, password=None, ssl_context=None): + start(port, password, ssl_context=ssl_context, accept_handler=None)