2021-02-28 15:24:14 +00:00
|
|
|
#!/usr/bin/env python3
|
2013-08-19 20:34:54 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
2021-02-28 15:24:14 +00:00
|
|
|
import asyncio
|
|
|
|
import dataclasses
|
2013-08-19 20:34:54 +00:00
|
|
|
import socket
|
|
|
|
import traceback
|
2021-02-28 15:24:14 +00:00
|
|
|
import typing
|
2013-08-19 20:34:54 +00:00
|
|
|
|
|
|
|
|
2021-02-28 15:24:14 +00:00
|
|
|
@dataclasses.dataclass
|
|
|
|
class ScgiRequest:
|
|
|
|
headers: typing.Dict[bytes, bytes]
|
|
|
|
body: bytes
|
|
|
|
|
|
|
|
|
|
|
|
async def parse_scgi_request(reader: asyncio.StreamReader) -> ScgiRequest:
|
|
|
|
hlen = int((await reader.readuntil(b':'))[:-1])
|
|
|
|
header_raw = await reader.readexactly(hlen + 1)
|
|
|
|
assert len(header_raw) >= 16, "invalid request: too short (< 16)"
|
|
|
|
assert header_raw[-2:] == b'\0,', f"Invalid request: missing header/netstring terminator '\\x00,', got {header_raw[-2:]!r}"
|
|
|
|
header_list = header_raw[:-2].split(b'\0')
|
|
|
|
assert len(header_list) % 2 == 0, f"Invalid request: odd numbers of header entries (must be pairs), got {len(header_list)}"
|
|
|
|
assert header_list[0] == b'CONTENT_LENGTH', f"Invalid request: first header entry must be 'CONTENT_LENGTH', got {header_list[0]!r}"
|
|
|
|
clen = int(header_list[1])
|
|
|
|
headers = {}
|
|
|
|
i = 0
|
|
|
|
while i < len(header_list):
|
|
|
|
key = header_list[i]
|
|
|
|
value = header_list[i+1]
|
|
|
|
i += 2
|
|
|
|
assert not key in headers, f"Invalid request: duplicate header key {key!r}"
|
|
|
|
headers[key] = value
|
|
|
|
assert headers.get(b'SCGI') == b'1', "Invalid request: missing SCGI=1 header"
|
|
|
|
body = await reader.readexactly(clen)
|
|
|
|
return ScgiRequest(headers=headers, body=body)
|
|
|
|
|
|
|
|
|
|
|
|
async def handle_scgi(reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
|
|
|
|
print(f"scgi-envcheck: Incoming connection", flush=True)
|
|
|
|
try:
|
|
|
|
req = await parse_scgi_request(reader)
|
|
|
|
envvar = req.headers[b'QUERY_STRING']
|
|
|
|
result = req.headers[envvar]
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
raise
|
|
|
|
except Exception as e:
|
|
|
|
print(traceback.format_exc())
|
|
|
|
writer.write(b"Status: 500\r\nContent-Type: text/plain\r\n\r\n" + str(e).encode('utf-8'))
|
|
|
|
else:
|
|
|
|
writer.write(b"Status: 200\r\nContent-Type: text/plain\r\n\r\n" + result)
|
|
|
|
await writer.drain()
|
|
|
|
writer.close()
|
|
|
|
await writer.wait_closed()
|
|
|
|
|
|
|
|
|
|
|
|
async def main():
|
|
|
|
sock = socket.socket(fileno=0)
|
|
|
|
if sock.type == socket.AF_UNIX:
|
|
|
|
start_server = asyncio.start_unix_server
|
|
|
|
else:
|
|
|
|
start_server = asyncio.start_server
|
|
|
|
|
|
|
|
server = await start_server(handle_scgi, sock=sock, start_serving=False)
|
|
|
|
|
|
|
|
addr = server.sockets[0].getsockname()
|
|
|
|
print(f'Serving on {addr}', flush=True)
|
|
|
|
|
|
|
|
async with server:
|
|
|
|
await server.serve_forever()
|
|
|
|
|
2013-08-19 20:34:54 +00:00
|
|
|
|
|
|
|
try:
|
2021-02-28 15:24:14 +00:00
|
|
|
asyncio.run(main())
|
2013-08-19 20:34:54 +00:00
|
|
|
except KeyboardInterrupt:
|
|
|
|
pass
|