minetest-api-py/mtapi.py

198 lines
4.7 KiB
Python

#!/usr/bin/env python3
import cbor, gzip, json, lzma, psycopg2, re, socket, time
DEFAULT_CONFIG = {
"db_blocks": {
"type": "postgre",
"info_postgre": {
"connect": "host=127.0.0.1 port=5432 user=minetest password=PASSWORD dbname=minetest-world"
}
},
"server": {
"listen_addr": "0.0.0.0",
"listen_port": 8060,
"write_passwords": []
}
}
CONFIG_PATH = "config.json"
RECBUF = 1024
AVAILABLE_FORMATS = {"json": "text/json", "cbor": "application/cbor"}
AVAILABLE_COMPRESSIONS = {"none": None, "gzip": "gzip", "lzma": "lzma"}
p_clen = re.compile("\r?\ncontent-length: *(\d+)\r?\n?", re.IGNORECASE)
p_stmt = re.compile("^(x|y|z)(<|>|=|<=|>=)-?\d{1,5}$")
encode = {
"json": lambda x: json.dumps(x).encode(),
"cbor": cbor.dumps
}
compress = {
"gzip": gzip.compress,
"lzma": lzma.compress
}
def send_response(client, code, resp, resp_format, resp_compression):
content_raw = encode[resp_format](resp)
compression_str = ""
if AVAILABLE_COMPRESSIONS[resp_compression]:
compression_str = "Content-Encoding: " + AVAILABLE_COMPRESSIONS[resp_compression] + "\r\n"
content_raw = compress[resp_compression](content_raw)
mime = AVAILABLE_FORMATS[resp_format]
client.sendall(("HTTP/1.1 "+code+"\r\nContent-type: "+mime+"; charset=UTF-8\r\n"+compression_str+"Access-Control-Allow-Origin: *\r\nContent-length: "+str(len(content_raw))+"\r\n\r\n").encode()+content_raw)
client.close()
if __name__ == "__main__":
# Read config
config_file = open(CONFIG_PATH, "r")
config = json.load(config_file)
config_file.close()
# Open DB
if config["db_blocks"]["type"] == "postgre":
conn = psycopg2.connect(CONFIG_DB_CONNECT)
# Start server
server_addr = CONFIG_LISTEN
if ":" in server_addr[0]: # IPv6
sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
else: # IPv4
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.settimeout(5)
server_addr = (config["server"]["listen_addr"], config["server"]["listen_port"])
sock.bind(server_addr)
sock.listen(1)
print("Server started at "+str(server_addr))
# Listen
while True:
try:
client, addr = sock.accept()
except socket.timeout:
continue
# Get request
paquet = b""
header = b""
content = b""
content_len = 0
resp = {}
resp_format = "json"
resp_compression = "none"
lf = 0
while True:
raw = client.recv(RECBUF)
if raw:
paquet += raw
if lf >= 0:
for c in raw:
if c == 10:# LF
lf += 1
elif c != 13:# CR
lf = 0
if lf > 1:
parts = paquet.split(b"\r\n\r\n")
header = parts[0]
content = parts[1]
try:
content_len = int(p_clen.search(header.decode()).group(1))
except (AttributeError, ValueError):
content_len = 0
break
if lf > 1:
break
else:
break
while len(content) < content_len:
raw = client.recv(RECBUF)
paquet += raw
content += raw
# Get URL
httpreq = paquet.split(b"\n")
try:
url = httpreq[0].split(b" ")[1].decode().split("/")
except IndexError:
send_response(client, "400 Bad Request", {"error": "bad_http"}, resp_format, resp_compression)
continue
while "" in url:
url.remove("")
urll = len(url)
if urll > 32:
send_response(client, "400 Bad Request", {"error": "too_many_args"}, resp_format, resp_compression)
continue
stmts = []
bad = False
token = None
for val in url:
if token:
if token == 1:
if not val in AVAILABLE_FORMATS:
bad = True
break
resp_format = val
token = None
continue
elif token == 2:
if not val in AVAILABLE_COMPRESSIONS:
bad = True
break
resp_compression = val
token = None
continue
bad = True
break
if val == "fmt":
token = 1
continue
elif val == "cpr":
token = 2
continue
if not p_stmt.match(val):
bad = True
break
stmts.append("pos"+val)
if bad:
send_response(client, "400 Bad Request", {"error": "bad_request"}, resp_format, resp_compression)
continue
if len(stmts) > 6:
send_response(client, "400 Bad Request", {"error": "too_many_statements"}, resp_format, resp_compression)
continue
if len(stmts) == 0:
send_response(client, "400 Bad Request", {"error": "no_statement"}, resp_format, resp_compression)
continue
req = " AND ".join(stmts)
cur = conn.cursor()
cur.execute("SELECT * FROM blocks WHERE "+req+" LIMIT 1000000;")
resp["blocks"] = []
while True:
block = cur.fetchone()
if not block:
break
resp["blocks"].append([block[0], block[1], block[2],
block[3].hex() if resp_format == "json" else block[3].tobytes()
])
print(req)
# Send response
send_response(client, "200 OK", resp, resp_format, resp_compression)
time.sleep(.2)