├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── examples ├── get-version.py └── simple-command.py ├── scripts └── webreplcmd ├── setup.py ├── tests └── test.py └── webrepl.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - '2.7' 4 | - '3.4' 5 | - '3.5' 6 | - '3.6' 7 | - '3.7' 8 | - '3.8' 9 | - 3.8-dev 10 | - nightly 11 | install: 12 | # - pip install -r requirements.txt 13 | - pip install . 14 | #script: pytest 15 | script: webreplcmd --help 16 | deploy: 17 | provider: pypi 18 | user: __token__ 19 | password: 20 | secure: gbd+nUq68uWyvpi8Ih7Gx+e+7KDqRWZBAnH8exOSAfo94tigFAZlHMyKMKVBNlqjv3pSWcr2lD5Gd8QrGs4kFOGe4PbzzsTby53txY77TGR8FmvstXpvXg2xGSWS6UGkovwvTmCm49BRh5kTzirkV6f5J8pWpK98Rq62UfGADdBlDUqrcBEi8VDN6iBo9YZ81L5sb1a3V6rm2T1rWKyAXm6byCULvESbmaW/zZH/rxUQyKSlYiDtIbapcvSauS6idfKIlV21dFuKPr3+xSod25qgM7DJrLPke9j2EHfuOSGJY/cSwp7lCygAeMrc5hByfSXbqU+xuxYs28i4R1g1DO6chcNShQBZB+rn1Xh2QQVDn3Rlt29dFPTLiHOBoY1PmSyHc7FTaqNuKZR9U4+x3cePf+9WsaEZe0dXcKkWneFPF+KYYql04PUc0d283eDJerXravWk1mjndKsKPd4Y//lNi+JtwvOuC53L1126VO3NsHztV8hEhkpsuh95JN8fyszkEg8CC6ao2wMbkDQ3u3UjvxD04u/z323ibEE9P87Kwg4Tka8gBYRr4pk4vkSTfOz4LLgUKi+sIHtFN5whOWLr9rmy14NUZKYBI7v0XOOkMDVIZ6TVYObB2TFIP+xFTg0iOmrK+aTx/sHdVfbxVLS97asmyydlGDjQU/s5nII= 21 | skip_existing: true 22 | on: 23 | tags: true 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Damien P. George 4 | Copyright (c) 2016 Paul Sokolovsky 5 | Copyright (c) 2019 Vlatko Kosturjak 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/kost/webrepl-python.png)](https://travis-ci.org/kost/webrepl-python) 2 | 3 | webrepl 4 | ====== 5 | Python module to handle micropython websocket (WS) repl protocol (client side only). It is modified original implementation to automatize working with web repl. 6 | 7 | Examples 8 | ======== 9 | 10 | Simple example to get output of command: 11 | ``` 12 | import webrepl 13 | repl=webrepl.Webrepl(**{'host':'192.168.4.1','port':8266,'password':'ulx3s'}) 14 | resp=repl.sendcmd("import os; os.listdir()") 15 | print(resp.decode("ascii")) 16 | ``` 17 | 18 | Example to get version of webrepl on device: 19 | ``` 20 | import webrepl 21 | repl=webrepl.Webrepl(**{'host':'192.168.4.1','port':8266,'password':'ulx3s','debug':True}) 22 | ver=repl.get_ver() 23 | print(ver) 24 | ``` 25 | 26 | Requirements 27 | ============ 28 | 29 | It should work with both python2 and python3 with simple pip commands: 30 | ``` 31 | pip install webrepl 32 | ``` 33 | 34 | webreplcmd examples 35 | ======== 36 | 37 | Few webreplcmd examples: 38 | ``` 39 | 40 | webreplcmd --host 192.168.4.1 --password ulx3s ls 41 | webreplcmd --host 192.168.4.1 --password ulx3s get src-remote-file.txt dest-local-file.txt 42 | webreplcmd --host 192.168.4.1 --password ulx3s -v get src-remote-file.txt dest-local-file.txt 43 | webreplcmd --host 192.168.4.1 --password ulx3s put src-local-file.txt dest-remote-file.txt 44 | webreplcmd --host 192.168.4.1 --password ulx3s -v put src-local-file.txt dest-remote-file.txt 45 | webreplcmd --host 192.168.4.1 --password ulx3s cat main.py 46 | webreplcmd --host 192.168.4.1 --password ulx3s cmd 'import os; os.listdir()' 47 | webreplcmd --host 192.168.4.1 --password ulx3s rm uftpd.py 48 | ``` 49 | 50 | Note that you can also specify basic parameters using environment variables: 51 | ``` 52 | export WEBREPL_HOST=127.0.0.1 53 | export WEBREPL_PASSWORD=ulx3s 54 | export WEBREPL_PORT=8266 55 | ``` 56 | 57 | and then you can just specify command: 58 | ``` 59 | webreplcmd ls 60 | ``` 61 | 62 | All options are listed using --help: 63 | 64 | ``` 65 | webreplcmd --help 66 | ``` 67 | 68 | Requirements 69 | ============ 70 | 71 | It should work with both python2 and python3 with simple pip commands: 72 | ``` 73 | sudo apt-get update 74 | sudo apt-get install -y python3 python3-pip 75 | sudo pip3 install webrepl 76 | ``` 77 | 78 | Manual 79 | ====== 80 | 81 | ``` 82 | usage: webreplcmd [-h] [--host HOST] [--port PORT] [--verbose] [--debug] 83 | [--password PASSWORD] [--before BEFORE] [--cmd CMD] 84 | [--after AFTER] 85 | CMD [CMD ...] 86 | 87 | webrepl - connect to websocket webrepl 88 | 89 | positional arguments: 90 | CMD commands for repl 91 | 92 | optional arguments: 93 | -h, --help show this help message and exit 94 | --host HOST, -i HOST Host to connect to 95 | --port PORT, -P PORT Port to connect to 96 | --verbose, -v Verbose information 97 | --debug, -d Enable debugging messages 98 | --password PASSWORD, -p PASSWORD 99 | Password used to connect to 100 | --before BEFORE, -B BEFORE 101 | command to execute before 102 | --cmd CMD, -c CMD command to execute 103 | --after AFTER, -A AFTER 104 | command to execute after 105 | 106 | webreplcmd --host 192.168.4.1 --password ulx3s ls 107 | webreplcmd --host 192.168.4.1 --password ulx3s get src-remote-file.txt dest-local-file.txt 108 | webreplcmd --host 192.168.4.1 --password ulx3s put src-local-file.txt dest-remote-file.txt 109 | webreplcmd --host 192.168.4.1 --password ulx3s cat main.py 110 | webreplcmd --host 192.168.4.1 --password ulx3s cmd 'import os; os.listdir()' 111 | ``` 112 | 113 | # Credits 114 | 115 | webrepl is derivative of [micropython's webrepl](https://github.com/micropython/webrepl) which is: 116 | Copyright (c) 2016 Damien P. George 117 | Copyright (c) 2016 Paul Sokolovsky 118 | 119 | 120 | -------------------------------------------------------------------------------- /examples/get-version.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import webrepl 4 | repl=webrepl.Webrepl(**{'host':'192.168.4.1','port':8266,'password':'ulx3s','debug':True}) 5 | ver=repl.get_ver() 6 | print(ver) 7 | 8 | -------------------------------------------------------------------------------- /examples/simple-command.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import webrepl 4 | repl=webrepl.Webrepl(**{'host':'192.168.4.1','port':8266,'password':'ulx3s'}) 5 | resp=repl.sendcmd("import os; os.listdir()") 6 | print(resp.decode("ascii")) 7 | 8 | 9 | -------------------------------------------------------------------------------- /scripts/webreplcmd: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import argparse 4 | import os 5 | import sys 6 | 7 | import webrepl 8 | 9 | examples="""webreplcmd --host 192.168.4.1 --password ulx3s ls 10 | webreplcmd --host 192.168.4.1 --password ulx3s get src-remote-file.txt dest-local-file.txt 11 | webreplcmd --host 192.168.4.1 --password ulx3s put src-local-file.txt dest-remote-file.txt 12 | webreplcmd --host 192.168.4.1 --password ulx3s cat main.py 13 | webreplcmd --host 192.168.4.1 --password ulx3s cmd 'import os; os.listdir()' 14 | """ 15 | 16 | parser = argparse.ArgumentParser(description='webrepl - connect to websocket webrepl', 17 | formatter_class=argparse.RawDescriptionHelpFormatter, 18 | epilog=examples) 19 | parser.add_argument('--host', '-i', default=os.environ.get('WEBREPL_HOST', None), help="Host to connect to") 20 | parser.add_argument('--port', '-P', type=int, default=os.environ.get('WEBREPL_PORT', 8266), help="Port to connect to") 21 | parser.add_argument('--verbose', '-v', action='store_true', help="Verbose information") 22 | parser.add_argument('--debug', '-d', action='store_true', help="Enable debugging messages") 23 | parser.add_argument('--password', '-p', default=os.environ.get('WEBREPL_PASSWORD', None), help="Use following password to connect") 24 | parser.add_argument('--before', '-B', action='append', default=os.environ.get('WEBREPL_BEFORE', None), help="command to execute before") 25 | parser.add_argument('--cmd', '-c', action='append', default=os.environ.get('WEBREPL_CMD', None), help="command to execute") 26 | parser.add_argument('--after', '-A', action='append', default=os.environ.get('WEBREPL_AFTER', None), help="command to execute after") 27 | parser.add_argument('commands', metavar='CMD', type=str, nargs='+', 28 | help='commands for repl') 29 | 30 | args = parser.parse_args() 31 | 32 | if not args.host: 33 | print("You need to specify host") 34 | parser.print_usage() 35 | sys.exit() 36 | 37 | password='' 38 | if not args.password: 39 | print("You need to specify password") 40 | try: 41 | import getpass 42 | password = getpass.getpass() 43 | except: 44 | parser.print_usage() 45 | sys.exit() 46 | else: 47 | password = args.password 48 | 49 | if not args.commands or len(args.commands)<1: 50 | print("Command is not recognized/given") 51 | parser.print_usage() 52 | sys.exit() 53 | 54 | repl=webrepl.Webrepl(**{}) 55 | 56 | if args.debug: 57 | repl.debug = True 58 | 59 | if args.verbose: 60 | repl.verbose = True 61 | 62 | try: 63 | repl.connect(args.host, args.port) 64 | repl.login(password) 65 | except Exception as e: 66 | print("Error connecting to host",args.host,"at port",args.port,":",e) 67 | sys.exit() 68 | 69 | if not repl.connected: 70 | print("Not connected. Check your password!") 71 | sys.exit() 72 | 73 | if args.before: 74 | for cmd in args.before: 75 | print("[i] Issuing command: "+cmd) 76 | try: 77 | r=repl.send_cmd(cmd) 78 | print(r.decode()) 79 | except Exception as e: 80 | print("[e] Error running command:",cmd,":",e) 81 | 82 | cmd = args.commands[0].lower() 83 | 84 | if cmd == "ver" or cmd == "version": 85 | if args.verbose: 86 | print("[i] Getting version") 87 | ver=repl.get_ver() 88 | print("[i] Version ",ver[0], ver[1], ver[2]) 89 | elif cmd == "list" or cmd == "ls": 90 | print("[i] Listing") 91 | r=repl.sendcmd("import os; os.listdir()") 92 | print(r.decode()) 93 | elif cmd == "get" or cmd == "download": 94 | if len(args.commands)<3: 95 | sys.stderr.write("Not enough arguments for "+cmd+"\n") 96 | sys.exit() 97 | source=args.commands[1] 98 | dest=args.commands[2] 99 | if args.verbose: 100 | print("[i] Downloading "+source+" to "+dest) 101 | repl.get_file(source, dest) 102 | elif cmd == "put" or cmd == "upload": 103 | if len(args.commands)<3: 104 | sys.stderr.write("Not enough arguments for "+cmd+"\n") 105 | sys.exit() 106 | source=args.commands[1] 107 | dest=args.commands[2] 108 | if args.verbose: 109 | print("[i] Uploading "+source+" to "+dest) 110 | repl.put_file(source, dest) 111 | elif cmd == "del" or cmd == "rm" or cmd == "dele": 112 | if len(args.commands)<2: 113 | sys.stderr.write("Not enough arguments for "+cmd+"\n") 114 | sys.exit() 115 | filename=args.commands[1] 116 | if args.verbose: 117 | print("[i] Deleting "+filename) 118 | r=repl.sendcmd("import os; os.remove('"+filename+"')") 119 | print(r.decode()) 120 | elif cmd == "print" or cmd == "cat" or cmd == "type": 121 | if len(args.commands)<2: 122 | sys.stderr.write("Not enough arguments for "+cmd+"\n") 123 | sys.exit() 124 | source=args.commands[1] 125 | if args.verbose: 126 | print("[i] Content of file",source) 127 | r=repl.get_file_content(source) 128 | print(r.decode()) 129 | elif cmd == "command" or cmd == "cmd": 130 | if len(args.commands)<2: 131 | sys.stderr.write("Not enough arguments for "+cmd+"\n") 132 | sys.exit() 133 | cmd=args.commands[1] 134 | if args.verbose: 135 | print("[i] Executing "+cmd) 136 | r=repl.sendcmd(cmd) 137 | print(r.decode()) 138 | else: 139 | sys.stderr.write("Command not recognized\n") 140 | 141 | if args.cmd: 142 | for cmd in args.cmd: 143 | print("[i] Issuing command: "+cmd) 144 | try: 145 | r=repl.send_cmd(cmd) 146 | print(r.decode()) 147 | except Exception as e: 148 | print("[e] Error running command:",cmd,":",e) 149 | 150 | if args.after: 151 | for cmd in args.after: 152 | print("[i] Issuing command: "+cmd) 153 | try: 154 | r=repl.send_cmd(cmd) 155 | print(r.decode()) 156 | except Exception as e: 157 | print("[e] Error running command:",cmd,":",e) 158 | 159 | if args.verbose: 160 | print("[i] closing REPL/WS") 161 | repl.disconnect() 162 | 163 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | with open("README.md", "r") as fh: 4 | long_description = fh.read() 5 | 6 | setuptools.setup( 7 | name="webrepl", 8 | version="0.2.0", 9 | author="Vlatko Kosturjak", 10 | author_email="vlatko.kosturjak@gmail.com", 11 | description="Handle micropython web_repl", 12 | long_description=long_description, 13 | long_description_content_type="text/markdown", 14 | url="https://github.com/kost/webrepl-python", 15 | packages=setuptools.find_packages(), 16 | py_modules=['webrepl'], 17 | install_requires=[ 18 | "", 19 | ], 20 | classifiers=[ 21 | "Programming Language :: Python :: 2", 22 | "Programming Language :: Python :: 3", 23 | "License :: OSI Approved :: MIT License", 24 | "Operating System :: OS Independent", 25 | ], 26 | scripts=[ 27 | 'scripts/webreplcmd' 28 | ] 29 | ) 30 | 31 | -------------------------------------------------------------------------------- /tests/test.py: -------------------------------------------------------------------------------- 1 | from webrepl import Webrepl 2 | 3 | def test_class_instant(): 4 | ''' 5 | make sure class be instantize 6 | ''' 7 | 8 | ''' assert new(somePython.fahrToKelv(32) == 273.15, 'incorrect freezing point!' ''' 9 | -------------------------------------------------------------------------------- /webrepl.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from __future__ import print_function 3 | import sys 4 | import os 5 | import struct 6 | try: 7 | import usocket as socket 8 | except ImportError: 9 | import socket 10 | import sys 11 | try: 12 | import ubinascii as binascii 13 | except: 14 | import binascii 15 | try: 16 | import uhashlib as hashlib 17 | except: 18 | import hashlib 19 | 20 | # Define to 1 to use builtin "websocket" module of MicroPython 21 | USE_BUILTIN_WEBSOCKET = 0 22 | # Treat this remote directory as a root for file transfers 23 | SANDBOX = "" 24 | #SANDBOX = "/tmp/webrepl/" 25 | DEBUG = 0 26 | 27 | WEBREPL_REQ_S = "<2sBBQLH64s" 28 | WEBREPL_PUT_FILE = 1 29 | WEBREPL_GET_FILE = 2 30 | WEBREPL_GET_VER = 3 31 | 32 | 33 | if USE_BUILTIN_WEBSOCKET: 34 | from websocket import websocket 35 | else: 36 | class websocket: 37 | 38 | def __init__(self, s): 39 | self.s = s 40 | self.buf = b"" 41 | self.debug = False 42 | 43 | def writetext(self, data): 44 | l = len(data) 45 | if l < 126: 46 | hdr = struct.pack(">BB", 0x81, l) 47 | else: 48 | hdr = struct.pack(">BBH", 0x81, 126, l) 49 | self.s.send(hdr) 50 | self.s.send(data) 51 | 52 | def write(self, data): 53 | l = len(data) 54 | if l < 126: 55 | # TODO: hardcoded "binary" type 56 | hdr = struct.pack(">BB", 0x82, l) 57 | else: 58 | hdr = struct.pack(">BBH", 0x82, 126, l) 59 | self.s.send(hdr) 60 | self.s.send(data) 61 | 62 | def recvexactly(self, sz): 63 | res = b"" 64 | while sz: 65 | data = self.s.recv(sz) 66 | if not data: 67 | break 68 | res += data 69 | sz -= len(data) 70 | return res 71 | 72 | def debugmsg(self, msg): 73 | if self.debug: 74 | print(msg) 75 | 76 | def read(self, size, text_ok=False, size_match=True): 77 | if not self.buf: 78 | while True: 79 | hdr = self.recvexactly(2) 80 | assert len(hdr) == 2 81 | fl, sz = struct.unpack(">BB", hdr) 82 | if sz == 126: 83 | hdr = self.recvexactly(2) 84 | assert len(hdr) == 2 85 | (sz,) = struct.unpack(">H", hdr) 86 | if fl == 0x82: 87 | break 88 | if text_ok and fl == 0x81: 89 | break 90 | self.debugmsg("[i] Got unexpected websocket record of type %x, skipping it" % fl) 91 | while sz: 92 | skip = self.s.recv(sz) 93 | self.debugmsg("[i] Skip data: %s" % skip) 94 | sz -= len(skip) 95 | data = self.recvexactly(sz) 96 | assert len(data) == sz 97 | self.buf = data 98 | 99 | d = self.buf[:size] 100 | self.buf = self.buf[size:] 101 | if size_match: 102 | assert len(d) == size, len(d) 103 | return d 104 | 105 | def ioctl(self, req, val): 106 | assert req == 9 and val == 2 107 | 108 | class Webrepl: 109 | 110 | def __init__(self, **params): 111 | self.host = self.getkey(params,"host") 112 | self.port = self.getkey(params,"port") 113 | self.password = self.getkey(params,"password") 114 | self.debug = self.getkey(params,"debug") 115 | self.verbose = self.getkey(params,"verbose") 116 | self.noauto = self.getkey(params,"noauto") 117 | 118 | self.s=None 119 | self.ws=None 120 | 121 | self.connected=False 122 | 123 | if self.port == None: 124 | self.port = 8266 125 | 126 | if self.host != None and not self.noauto: 127 | self.connect(self.host, self.port) 128 | if self.password != None and self.ws != None and not self.noauto: 129 | self.login(self.password) 130 | 131 | def getkey(self, dict, key): 132 | if key in dict: 133 | return dict[key] 134 | return None 135 | 136 | def debugmsg(self, msg): 137 | if self.debug: 138 | print(msg) 139 | 140 | def client_handshake(self, sock): 141 | cl = sock.makefile("rwb", 0) 142 | cl.write(b"""\ 143 | GET / HTTP/1.1\r 144 | Host: echo.websocket.org\r 145 | Connection: Upgrade\r 146 | Upgrade: websocket\r 147 | Sec-WebSocket-Key: foo\r 148 | \r 149 | """) 150 | l = cl.readline() 151 | # print(l) 152 | while 1: 153 | l = cl.readline() 154 | if l == b"\r\n": 155 | break 156 | # sys.stdout.write(l) 157 | 158 | def connect(self, host, port): 159 | self.debugmsg("[d] connecting to %s %s" % (host,port)) 160 | self.s = socket.socket() 161 | ai = socket.getaddrinfo(host, port) 162 | addr = ai[0][4] 163 | #self.debugmsg("connecting to adr %r" % addr) 164 | 165 | self.s.connect(addr) 166 | #s = s.makefile("rwb") 167 | self.debugmsg("[d] handshake") 168 | self.client_handshake(self.s) 169 | self.ws = websocket(self.s) 170 | self.ws.debug = self.debug 171 | 172 | def disconnect (self): 173 | if self.s != None: 174 | self.s.close() 175 | self.s = None 176 | self.ws = None 177 | 178 | def login(self, passwd): 179 | self.debugmsg("[d] login as %s" % passwd) 180 | while True: 181 | c = self.ws.read(1, text_ok=True) 182 | if c == b":": 183 | assert self.ws.read(1, text_ok=True) == b" " 184 | break 185 | self.ws.write(passwd.encode("utf-8") + b"\r") 186 | self.debugmsg("[d] login sent %s" % passwd) 187 | resp = self.ws.read(64, text_ok=True, size_match=False) 188 | # b'\r\nWebREPL connected\r\n>>> ' 189 | # b'\r\nAccess denied\r\n' 190 | if b"WebREPL connected" in resp: 191 | self.connected=True 192 | self.debugmsg("[d] login resp %s" % resp) 193 | 194 | def sendcmd(self, cmd, size=1024): 195 | if not self.connected: 196 | return b"" 197 | self.debugmsg("[d] sending cmd %s" % cmd) 198 | self.ws.writetext(cmd.encode("utf-8") + b"\r\n") 199 | self.debugmsg("[d] getting response") 200 | resp = self.read_cmd(size) 201 | self.debugmsg("[d] got response %s" % resp) 202 | return resp 203 | 204 | def read_cmd(self, size): 205 | resp=b'' 206 | newline=False 207 | while True: 208 | r=self.ws.read(size, text_ok=True, size_match=False) 209 | # self.debugmsg("got %s %d" % (r, len(r))) 210 | if r == b'>>> ' and newline: 211 | break 212 | if r == b'\r\n': 213 | newline=True 214 | else: 215 | newline=False 216 | resp = resp + r 217 | return resp 218 | 219 | def read_resp(self): 220 | data = self.ws.read(4) 221 | sig, code = struct.unpack("<2sH", data) 222 | assert sig == b"WB" 223 | return code 224 | 225 | 226 | def send_req(self, op, sz=0, fname=b""): 227 | rec = struct.pack(WEBREPL_REQ_S, b"WA", op, 0, 0, sz, len(fname), fname) 228 | self.debugmsg("[d] Sent request %r %d" % (rec, len(rec))) 229 | self.ws.write(rec) 230 | 231 | def set_binary(self): 232 | # Set websocket to send data marked as "binary" 233 | self.ws.ioctl(9, 2) 234 | 235 | def get_ver(self): 236 | if self.connected: 237 | self.send_req(WEBREPL_GET_VER) 238 | d = self.ws.read(3) 239 | d = struct.unpack("