import socketserver import subprocess import os host = "localhost" port = 70 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 sendFile(file, context): with open(file, "r") as fd: sendFileArray(fd.readlines(), context) 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): if not gph: sendFileArray(cgi(file, query, context), context) else: sendFileArray(gopherRenderer(cgi(file, query, context)), context) elif fileSendable(file): if not gph: sendFile(file, context) else: sendFileArray(gopherRenderer([i.rstrip() for i in open(file).readlines()]), context) else: notFound(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()