kopia lustrzana https://github.com/micropython/micropython-lib
webrepl: Implement file transfer protocol in python.
Signed-off-by: Felix Dörre <felix@dogcraft.de>pull/814/head
rodzic
47e13387ca
commit
772d240f81
|
@ -0,0 +1,53 @@
|
||||||
|
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("<h", ret)
|
||||||
|
sock.ioctl(9, 2)
|
||||||
|
sock.write(memoryview(self.filebuf)[0 : (2 + ret)])
|
||||||
|
if ret == 0:
|
||||||
|
sock.write(b"WB\x00\x00")
|
||||||
|
self.op = 0
|
||||||
|
self.filebuf = None
|
||||||
|
sock.ioctl(9, 1)
|
||||||
|
return
|
||||||
|
self.opbuf[self.opptr] = buf[0]
|
||||||
|
self.opptr += 1
|
||||||
|
if self.opptr != 82: # or bytes(buf[0:2]) != b"WA":
|
||||||
|
return
|
||||||
|
self.opptr = 0
|
||||||
|
sock.ioctl(9, 2)
|
||||||
|
sock.write(b"WB\x00\x00")
|
||||||
|
sock.ioctl(9, 1)
|
||||||
|
type = self.opbuf[2]
|
||||||
|
if type == 2: # GET_FILE
|
||||||
|
self.op = type
|
||||||
|
name = self.opbuf[18:82].rstrip(b"\x00")
|
||||||
|
self.filebuf = bytearray(2 + 256)
|
||||||
|
self.file = open(name.decode(), "rb")
|
||||||
|
elif type == 1: # PUT_FILE
|
||||||
|
import struct
|
||||||
|
|
||||||
|
name = self.opbuf[18:82].rstrip(b"\x00")
|
||||||
|
size = struct.unpack("<I", self.opbuf[12:16])[0]
|
||||||
|
filebuf = bytearray(512)
|
||||||
|
with open(name.decode(), "wb") as file:
|
||||||
|
while size > 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)
|
|
@ -1,4 +1,5 @@
|
||||||
metadata(description="WebREPL server.", version="1.0.0")
|
metadata(description="WebREPL server.", version="1.0.0")
|
||||||
|
|
||||||
module("webrepl.py", opt=3)
|
module("webrepl.py", opt=3)
|
||||||
|
module("legacy_file_transfer.py", opt=3)
|
||||||
module("webrepl_setup.py", opt=3)
|
module("webrepl_setup.py", opt=3)
|
||||||
|
|
|
@ -7,56 +7,73 @@ import socket
|
||||||
import sys
|
import sys
|
||||||
import websocket
|
import websocket
|
||||||
import io
|
import io
|
||||||
|
from micropython import const
|
||||||
|
from legacy_file_transfer import LegacyFileTransfer
|
||||||
|
|
||||||
listen_s = None
|
listen_s = None
|
||||||
client_s = None
|
client_s = None
|
||||||
|
|
||||||
DEBUG = 0
|
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>>> ")
|
_WELCOME_PROMPT = const("\r\nWebREPL connected\r\n>>> ")
|
||||||
static_host = _DEFAULT_STATIC_HOST
|
static_host = _DEFAULT_STATIC_HOST
|
||||||
webrepl_pass = None
|
webrepl_pass = None
|
||||||
|
|
||||||
|
legacy = LegacyFileTransfer()
|
||||||
|
|
||||||
|
|
||||||
class WebreplWrapper(io.IOBase):
|
class WebreplWrapper(io.IOBase):
|
||||||
def __init__(self, sock):
|
def __init__(self, sock):
|
||||||
self.sock = sock
|
self.sock = sock
|
||||||
self.sock.ioctl(9, 2)
|
self.sock.ioctl(9, 1 if legacy else 2)
|
||||||
if webrepl_pass is not None:
|
if webrepl_pass is not None:
|
||||||
self.pw = bytearray(16)
|
self.pw = bytearray(16)
|
||||||
self.pwPos = 0
|
self.pwPos = 0
|
||||||
self.sock.write("Password: ")
|
self.sock.write("Password: ")
|
||||||
else:
|
else:
|
||||||
self.pw = None
|
self.pw = None
|
||||||
self.sock.write(_WELCOME_PROMPT);
|
self.sock.write(_WELCOME_PROMPT)
|
||||||
|
|
||||||
def readinto(self, buf):
|
def readinto(self, buf):
|
||||||
if self.pw is not None:
|
if self.pw is not None:
|
||||||
buf = bytearray(1)
|
buf1 = bytearray(1)
|
||||||
while True:
|
while True:
|
||||||
l = self.sock.readinto(buf)
|
l = self.sock.readinto(buf1)
|
||||||
if l is None:
|
if l is None:
|
||||||
continue
|
continue
|
||||||
if l <= 0:
|
if l <= 0:
|
||||||
return l
|
return l
|
||||||
if buf[0] == 10 or buf[0] == 13:
|
if buf1[0] == 10 or buf1[0] == 13:
|
||||||
print("Authenticating with:")
|
print("Authenticating with:")
|
||||||
print(self.pw[0:self.pwPos])
|
print(self.pw[0 : self.pwPos])
|
||||||
if bytes(self.pw[0:self.pwPos]) == webrepl_pass:
|
if bytes(self.pw[0 : self.pwPos]) == webrepl_pass:
|
||||||
self.pw = None
|
self.pw = None
|
||||||
del self.pwPos
|
del self.pwPos
|
||||||
self.sock.write(_WELCOME_PROMPT)
|
self.sock.write(_WELCOME_PROMPT)
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
print(bytes(self.pw[0:self.pwPos]))
|
print(bytes(self.pw[0 : self.pwPos]))
|
||||||
print(webrepl_pass)
|
print(webrepl_pass)
|
||||||
self.sock.write("\r\nAccess denied\r\n")
|
self.sock.write("\r\nAccess denied\r\n")
|
||||||
return 0
|
return 0
|
||||||
else:
|
else:
|
||||||
if self.pwPos < len(self.pw):
|
if self.pwPos < len(self.pw):
|
||||||
self.pw[self.pwPos] = buf[0]
|
self.pw[self.pwPos] = buf1[0]
|
||||||
self.pwPos = self.pwPos + 1
|
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):
|
def write(self, buf):
|
||||||
if self.pw is not None:
|
if self.pw is not None:
|
||||||
|
@ -72,8 +89,8 @@ class WebreplWrapper(io.IOBase):
|
||||||
def close(self):
|
def close(self):
|
||||||
self.sock.close()
|
self.sock.close()
|
||||||
|
|
||||||
def server_handshake(cl):
|
|
||||||
req = cl.makefile("rwb", 0)
|
def server_handshake(req):
|
||||||
# Skip HTTP GET line.
|
# Skip HTTP GET line.
|
||||||
l = req.readline()
|
l = req.readline()
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
|
@ -115,30 +132,35 @@ def server_handshake(cl):
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
print("respkey:", respkey)
|
print("respkey:", respkey)
|
||||||
|
|
||||||
cl.send(
|
req.write(
|
||||||
b"""\
|
b"""\
|
||||||
HTTP/1.1 101 Switching Protocols\r
|
HTTP/1.1 101 Switching Protocols\r
|
||||||
Upgrade: websocket\r
|
Upgrade: websocket\r
|
||||||
Connection: Upgrade\r
|
Connection: Upgrade\r
|
||||||
Sec-WebSocket-Accept: """
|
Sec-WebSocket-Accept: """
|
||||||
)
|
)
|
||||||
cl.send(respkey)
|
req.write(respkey)
|
||||||
cl.send("\r\n\r\n")
|
req.write("\r\n\r\n")
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def send_html(cl):
|
def send_html(cl):
|
||||||
cl.send(
|
cl.write(
|
||||||
b"""\
|
b"""\
|
||||||
HTTP/1.0 200 OK\r
|
HTTP/1.0 200 OK\r
|
||||||
\r
|
\r
|
||||||
<base href=\""""
|
<base href=\""""
|
||||||
)
|
)
|
||||||
cl.send(static_host)
|
cl.write(static_host)
|
||||||
cl.send(
|
cl.write(
|
||||||
b"""\"></base>\r
|
b"""\"></base>\r
|
||||||
<script src="webreplv2_content.js"></script>\r
|
<script src="webrepl"""
|
||||||
|
)
|
||||||
|
if not legacy:
|
||||||
|
cl.write("v2")
|
||||||
|
cl.write(
|
||||||
|
b"""_content.js"></script>\r
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
cl.close()
|
cl.close()
|
||||||
|
@ -149,10 +171,7 @@ def setup_conn(port, accept_handler):
|
||||||
listen_s = socket.socket()
|
listen_s = socket.socket()
|
||||||
listen_s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
listen_s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
|
||||||
ai = socket.getaddrinfo("0.0.0.0", port)
|
listen_s.bind(("", port))
|
||||||
addr = ai[0][4]
|
|
||||||
|
|
||||||
listen_s.bind(addr)
|
|
||||||
listen_s.listen(1)
|
listen_s.listen(1)
|
||||||
if accept_handler:
|
if accept_handler:
|
||||||
listen_s.setsockopt(socket.SOL_SOCKET, 20, accept_handler)
|
listen_s.setsockopt(socket.SOL_SOCKET, 20, accept_handler)
|
||||||
|
@ -164,11 +183,14 @@ def setup_conn(port, accept_handler):
|
||||||
|
|
||||||
|
|
||||||
def accept_conn(listen_sock):
|
def accept_conn(listen_sock):
|
||||||
global client_s
|
global client_s, webrepl_ssl_context
|
||||||
cl, remote_addr = listen_sock.accept()
|
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):
|
if not server_handshake(cl):
|
||||||
send_html(cl)
|
send_html(sock)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
prev = os.dupterm(None)
|
prev = os.dupterm(None)
|
||||||
|
@ -180,13 +202,13 @@ def accept_conn(listen_sock):
|
||||||
print("\nWebREPL connection from:", remote_addr)
|
print("\nWebREPL connection from:", remote_addr)
|
||||||
client_s = cl
|
client_s = cl
|
||||||
|
|
||||||
ws = websocket.websocket(cl, True)
|
sock = websocket.websocket(sock)
|
||||||
ws = WebreplWrapper(ws)
|
sock = WebreplWrapper(sock)
|
||||||
cl.setblocking(False)
|
cl.setblocking(False)
|
||||||
# notify REPL on socket incoming data (ESP32/ESP8266-only)
|
# notify REPL on socket incoming data (ESP32/ESP8266-only)
|
||||||
if hasattr(os, "dupterm_notify"):
|
if hasattr(os, "dupterm_notify"):
|
||||||
cl.setsockopt(socket.SOL_SOCKET, 20, os.dupterm_notify)
|
cl.setsockopt(socket.SOL_SOCKET, 20, os.dupterm_notify)
|
||||||
os.dupterm(ws)
|
os.dupterm(sock)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -200,9 +222,10 @@ def stop():
|
||||||
listen_s.close()
|
listen_s.close()
|
||||||
|
|
||||||
|
|
||||||
def start(port=8266, password=None, accept_handler=accept_conn):
|
def start(port=8266, password=None, ssl_context=None, accept_handler=accept_conn):
|
||||||
global static_host, webrepl_pass
|
global static_host, webrepl_pass, webrepl_ssl_context
|
||||||
stop()
|
stop()
|
||||||
|
webrepl_ssl_context = ssl_context
|
||||||
webrepl_pass = password
|
webrepl_pass = password
|
||||||
if password is None:
|
if password is None:
|
||||||
try:
|
try:
|
||||||
|
@ -230,5 +253,5 @@ def start(port=8266, password=None, accept_handler=accept_conn):
|
||||||
print("Started webrepl in manual override mode")
|
print("Started webrepl in manual override mode")
|
||||||
|
|
||||||
|
|
||||||
def start_foreground(port=8266, password=None):
|
def start_foreground(port=8266, password=None, ssl_context=None):
|
||||||
start(port, password, None)
|
start(port, password, ssl_context=ssl_context, accept_handler=None)
|
||||||
|
|
Ładowanie…
Reference in New Issue