Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

webrepl: Changes for more webrepl features while making it smaller. #814

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions micropython/net/webrepl/legacy_file_transfer.py
Original file line number Diff line number Diff line change
@@ -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)
3 changes: 2 additions & 1 deletion micropython/net/webrepl/manifest.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
metadata(description="WebREPL server.", version="0.1.0")
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)
131 changes: 105 additions & 26 deletions micropython/net/webrepl/webrepl.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,96 @@
# This module should be imported from REPL, not run from command line.
import binascii
import hashlib
from micropython import const
import network
import os
import socket
import sys
import websocket
import _webrepl
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://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, 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)

def readinto(self, buf):
if self.pw is not None:
buf1 = bytearray(1)
while True:
l = self.sock.readinto(buf1)
if l is None:
continue
if l <= 0:
return l
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:
self.pw = None
del self.pwPos
self.sock.write(_WELCOME_PROMPT)
break
else:
print(bytes(self.pw[0 : self.pwPos]))
print(webrepl_pass)
self.sock.write("\r\nAccess denied\r\n")
return 0
else:
if self.pwPos < len(self.pw):
self.pw[self.pwPos] = buf1[0]
self.pwPos = self.pwPos + 1
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:
return len(buf)
return self.sock.write(buf)

def ioctl(self, kind, arg):
if kind == 4:
self.sock.close()
return 0
return -1

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:
Expand Down Expand Up @@ -61,30 +132,35 @@ 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
<base href=\""""
)
cl.send(static_host)
cl.send(
cl.write(static_host)
cl.write(
b"""\"></base>\r
<script src="webrepl_content.js"></script>\r
<script src="webrepl"""
)
if not legacy:
cl.write("v2")
cl.write(
b"""_content.js"></script>\r
"""
)
cl.close()
Expand All @@ -95,10 +171,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)
Expand All @@ -110,11 +183,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)
Expand All @@ -126,13 +202,13 @@ def accept_conn(listen_sock):
print("\nWebREPL connection from:", remote_addr)
client_s = cl

ws = websocket.websocket(cl, True)
ws = _webrepl._webrepl(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

Expand All @@ -146,11 +222,12 @@ def stop():
listen_s.close()


def start(port=8266, password=None, accept_handler=accept_conn):
global static_host
def start(port=8266, password=None, ssl_context=None, accept_handler=accept_conn):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

start_foreground() needs to be changed to work with this new signature.

global static_host, webrepl_pass, webrepl_ssl_context
stop()
webrepl_ssl_context = ssl_context
webrepl_pass = password
if webrepl_pass is None:
if password is None:
try:
import webrepl_cfg

Expand All @@ -160,7 +237,9 @@ def start(port=8266, password=None, accept_handler=accept_conn):
except:
print("WebREPL is not configured, run 'import webrepl_setup'")

_webrepl.password(webrepl_pass)
if webrepl_pass is not None:
webrepl_pass = webrepl_pass.encode()

s = setup_conn(port, accept_handler)

if accept_handler is None:
Expand All @@ -174,5 +253,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)
Loading