219 lines
7.1 KiB
Python
Executable File
219 lines
7.1 KiB
Python
Executable File
#!/usr/bin/python3
|
|
|
|
import argparse
|
|
import cgi
|
|
from datetime import datetime
|
|
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
import json
|
|
import os
|
|
import re
|
|
from urllib.parse import parse_qs
|
|
|
|
from mswalletrpc import MultisigWallet
|
|
from web.urls import URLS
|
|
|
|
|
|
class WalletPool():
|
|
pool = {}
|
|
|
|
def add(self, name, wallet):
|
|
self.pool[name] = wallet
|
|
|
|
def get(self, name):
|
|
return self.pool.get(name)
|
|
|
|
def kill(self, name):
|
|
wallet = self.pool.get(name)
|
|
if not wallet:
|
|
return
|
|
wallet.rpc_stop()
|
|
del self.pool[name]
|
|
|
|
def __del__(self):
|
|
while self.pool:
|
|
self.pool.pop(0).delete()
|
|
|
|
|
|
class XMSRequestHandler(BaseHTTPRequestHandler):
|
|
settings = {
|
|
'daemon-address': 'localhost:18081',
|
|
'daemon-login': '',
|
|
'stagenet': False,
|
|
'testnet': False,
|
|
'proxy': '',
|
|
'timeout': 30,
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
self.cd_default_dir()
|
|
try:
|
|
for key, value in json.loads(open('settings.json').read()).items():
|
|
if key in self.settings:
|
|
self.settings[key] = value
|
|
except Exception:
|
|
...
|
|
return super().__init__(*args, **kwargs)
|
|
|
|
def save_settings(self):
|
|
self.cd_default_dir()
|
|
open('settings.json', 'w').write(json.dumps(self.settings))
|
|
|
|
def cd_default_dir(self):
|
|
os.chdir(self.default_path)
|
|
|
|
def get_wallets(self):
|
|
self.cd_default_dir()
|
|
paths = []
|
|
if os.path.isdir('wallets'):
|
|
for name in sorted(os.listdir('wallets')):
|
|
if os.path.isdir(os.path.join('wallets', name)):
|
|
paths.append(name)
|
|
if not paths:
|
|
return
|
|
wallets = {}
|
|
for p in paths:
|
|
wallet = {'name': p}
|
|
if os.path.exists(os.path.join('wallets', p, 'wallet.password.txt')):
|
|
fn = os.path.join('wallets', p, 'ms.round1.txt')
|
|
if os.path.exists(fn):
|
|
key1 = open(fn).read().strip()
|
|
wallet['key1'] = key1
|
|
wallet['created_at'] = datetime.fromtimestamp(
|
|
os.lstat(fn).st_ctime
|
|
).strftime('%Y-%m-%d %H:%M:%S')
|
|
items = {
|
|
'address': 'wallet.address.txt',
|
|
'label': 'wallet.label.txt',
|
|
'key2': 'ms.round2.txt',
|
|
'key3': 'ms.round3.txt',
|
|
'finalized': 'ms.finalized.txt',
|
|
}
|
|
for name, file in items.items():
|
|
fn = os.path.join('wallets', p, file)
|
|
if os.path.exists(fn):
|
|
value = open(fn).read().strip()
|
|
if value:
|
|
wallet[name] = value
|
|
if wallet.get('finalized'):
|
|
wallet['state'] = 'finalized'
|
|
elif wallet.get('key3'):
|
|
wallet['state'] = 'round3'
|
|
elif wallet.get('key2'):
|
|
wallet['state'] = 'round2'
|
|
elif wallet.get('key1'):
|
|
wallet['state'] = 'round1'
|
|
else:
|
|
wallet['state'] = 'new'
|
|
wallets[p] = wallet
|
|
return wallets
|
|
|
|
def get_wallet(self, name):
|
|
wallet = self.pool.get(name)
|
|
if not wallet:
|
|
wallet = MultisigWallet(
|
|
self.get_wallets().get(name)['name'],
|
|
self.settings
|
|
)
|
|
self.pool.add(name, wallet)
|
|
return wallet
|
|
|
|
def dispatch(self, method='get'):
|
|
for url, cls in URLS.items():
|
|
match = re.search(url, self.path)
|
|
if not match:
|
|
continue
|
|
kwargs = match.groupdict() or {}
|
|
resp = None
|
|
try:
|
|
if method.lower() == 'get':
|
|
resp = cls().get(self, **kwargs)
|
|
elif method.lower() == 'post':
|
|
content_length = int(self.headers.get('Content-Length', 0))
|
|
ctype, pdict = cgi.parse_header(self.headers.get('Content-Type') or '')
|
|
if content_length:
|
|
if ctype == 'application/json':
|
|
data = json.loads(self.rfile.read(content_length))
|
|
else:
|
|
data = parse_qs(self.rfile.read(content_length).decode())
|
|
else:
|
|
data = {}
|
|
resp = cls().post(self, data=cls.get_form_data(data), **kwargs)
|
|
else:
|
|
return 405, 'Method not allowed'
|
|
if resp:
|
|
if isinstance(resp, (list, tuple)) and len(resp) == 2:
|
|
return resp
|
|
return 200, resp
|
|
except NotImplementedError:
|
|
return 405, 'Method not allowed'
|
|
return 404, 'Not found'
|
|
|
|
def do_method(self, method='get'):
|
|
code, resp = self.dispatch(method)
|
|
self.send_response(code)
|
|
if code == 301:
|
|
self.send_header('Location', resp)
|
|
self.end_headers()
|
|
else:
|
|
if self.path.endswith('.js'):
|
|
self.send_header('Content-type', 'text/javascript')
|
|
self.end_headers()
|
|
for r in resp:
|
|
self.wfile.write(bytes(r, 'utf-8'))
|
|
elif self.path.endswith('.css'):
|
|
self.send_header('Content-type', 'text/css')
|
|
self.end_headers()
|
|
for r in resp:
|
|
self.wfile.write(bytes(r, 'utf-8'))
|
|
elif isinstance(resp, str):
|
|
self.send_header('Content-type', 'text/html')
|
|
self.end_headers()
|
|
for r in resp:
|
|
self.wfile.write(bytes(r, 'utf-8'))
|
|
else:
|
|
self.send_header('Content-type', 'application/json')
|
|
self.end_headers()
|
|
self.wfile.write(bytes(json.dumps(resp), 'utf-8'))
|
|
|
|
def do_GET(self):
|
|
return self.do_method('get')
|
|
|
|
def do_POST(self):
|
|
return self.do_method('post')
|
|
|
|
def redirect(self, url):
|
|
return 301, url
|
|
|
|
|
|
def add_attrs(obj, **kwargs):
|
|
for name, value in kwargs.items():
|
|
setattr(obj, name, value)
|
|
return obj
|
|
|
|
|
|
if __name__ == '__main__':
|
|
parser = argparse.ArgumentParser(add_help=True)
|
|
parser.add_argument(
|
|
'-H', '--hostname', type=str, default='127.0.0.1',
|
|
help='Server hostname, default: 127.0.0.1 (closed for not local connections). 0.0.0.0 to allow connect anyone.'
|
|
)
|
|
parser.add_argument('-p', '--port', type=int, help='Server port, default: 7779', default=7779)
|
|
args = parser.parse_args()
|
|
|
|
pool = WalletPool()
|
|
webServer = HTTPServer((args.hostname, args.port), add_attrs(
|
|
XMSRequestHandler,
|
|
pool=pool,
|
|
default_path=os.path.abspath(os.curdir)
|
|
))
|
|
print('Server started http://%s:%s' % (args.hostname, args.port))
|
|
|
|
try:
|
|
webServer.serve_forever()
|
|
except KeyboardInterrupt:
|
|
pass
|
|
|
|
webServer.server_close()
|
|
print('Server stopped.')
|
|
del pool
|