160 lines
4.1 KiB
Python
160 lines
4.1 KiB
Python
import socketserver
|
|
import subprocess
|
|
import os
|
|
|
|
host = "localhost"
|
|
port = 71
|
|
|
|
class GopherError(BaseException): pass
|
|
class RequestError(GopherError): pass
|
|
|
|
class GopherLine:
|
|
def __init__(self, line):
|
|
self.item = "i"
|
|
self.text = ""
|
|
self.location = "null"
|
|
self.host = "null.host"
|
|
self.port = "0"
|
|
self.line = line
|
|
self.parse()
|
|
def parse(self):
|
|
split = self.line.rstrip().split('\t')
|
|
location = False
|
|
if len(split) == 1:
|
|
self.text = split[0]
|
|
return
|
|
|
|
try: self.item = split[0][0]
|
|
except IndexError: pass
|
|
|
|
try: self.text = split[0][1:]
|
|
except IndexError: pass
|
|
|
|
try:
|
|
self.location = split[1]
|
|
location = True
|
|
except IndexError: pass
|
|
|
|
try: self.host = split[2]
|
|
except IndexError:
|
|
if location == True: self.host = host
|
|
|
|
try: self.port = split[3]
|
|
except IndexError:
|
|
if location == True: self.port = str(port)
|
|
def render(self):
|
|
return "{}{}\t{}\t{}\t{}".format(
|
|
self.item,
|
|
self.text,
|
|
self.location,
|
|
self.host,
|
|
self.port
|
|
)
|
|
|
|
def recieveRequest(context):
|
|
data = b""
|
|
while data[-2:] != b"\r\n":
|
|
data += context.request.recv(1)
|
|
decoded = data[:-2].decode("utf-8")
|
|
return decoded
|
|
|
|
def requestParser(request):
|
|
if ".." in request: raise RequestError
|
|
try:
|
|
if request[0] == "/": request = request[1:]
|
|
except IndexError: pass
|
|
request = request.replace("?", "\t").split("\t")
|
|
try: return (request[0], request[1])
|
|
except IndexError: return (request[0], "")
|
|
|
|
def returnRelative(file):
|
|
gph = False
|
|
if file == "":
|
|
ret = "gophermap"
|
|
gph = True
|
|
elif os.path.isdir(file):
|
|
ret = file + "/gophermap"
|
|
gph = True
|
|
elif os.path.isfile(file): ret = file
|
|
else: raise RequestError("unreachable state")
|
|
return (ret, gph)
|
|
|
|
def fileSendable(file): return os.access(file, os.F_OK|os.R_OK)
|
|
|
|
def fileCGI(file):
|
|
return os.access(file, os.F_OK|os.R_OK|os.X_OK)
|
|
|
|
def notFound(context):
|
|
context.request.sendall(b"3error: file not found!\r\n.\r\n")
|
|
|
|
def invalid(context):
|
|
context.request.sendall(b"3error: selector contains '..'!\r\n.\r\n")
|
|
|
|
def fileToFileArray(file):
|
|
fd = open(file, "r")
|
|
ret = [i.rstrip() for i in fd.readlines()]
|
|
fd.close()
|
|
return ret
|
|
|
|
def sendFileArray(arr, context):
|
|
[context.request.sendall(
|
|
(i.rstrip() + "\r\n").encode("utf-8")
|
|
) for i in arr]
|
|
context.request.sendall(b".\r\n")
|
|
|
|
def cgi(file, query, context):
|
|
env = {}
|
|
env["QUERY_STRING"] = query
|
|
env["SCRIPT_NAME"] = "/" + file
|
|
env["REMOTE_ADDR"] = context.client_address[0]
|
|
|
|
proc = subprocess.Popen(
|
|
[os.getcwd() + "/" + file],
|
|
stdout = subprocess.PIPE,
|
|
env = env
|
|
)
|
|
|
|
try: out, err = proc.communicate(timeout=10)
|
|
except TimeoutExpired:
|
|
proc.kill()
|
|
out, err = proc.communicate()
|
|
|
|
return out.decode("utf-8").replace("\r", "").split("\n")
|
|
|
|
def gopherRenderer(fileArray):
|
|
gopherlines = []
|
|
for i in fileArray:
|
|
gopherlines.append(GopherLine(i))
|
|
returned = []
|
|
for i in gopherlines:
|
|
returned.append(i.render())
|
|
return returned
|
|
|
|
def serveFile(file, query, gph, context):
|
|
if fileCGI(file): fa = cgi(file, query, context)
|
|
elif fileSendable(file): fa = fileToFileArray(file)
|
|
else:
|
|
notFound(context)
|
|
return
|
|
if gph: fa = gopherRenderer(fa)
|
|
sendFileArray(fa, context)
|
|
|
|
class gopherHandler(socketserver.BaseRequestHandler):
|
|
def handle(self):
|
|
decoded = recieveRequest(self)
|
|
try:
|
|
parsed = requestParser(decoded)
|
|
except RequestError:
|
|
invalid(self)
|
|
return
|
|
|
|
try: file = returnRelative(parsed[0])
|
|
except RequestError:
|
|
notFound(self)
|
|
return
|
|
serveFile(file[0], parsed[1], file[1], self)
|
|
|
|
if __name__ == "__main__":
|
|
with socketserver.TCPServer((host, port), gopherHandler) as server:
|
|
server.serve_forever()
|