#!/usr/bin/python # Software written by Juhani Haverinen (nortti). Influenced in idea and # some implementation details by https://github.com/puckipedia/pyGopher/ # ----------------------------------------------------------------------- # This is free and unencumbered software released into the public domain. # # Anyone is free to copy, modify, publish, use, compile, sell, or # distribute this software, either in source code form or as a compiled # binary, for any purpose, commercial or non-commercial, and by any # means. # # In jurisdictions that recognize copyright laws, the author or authors # of this software dedicate any and all copyright interest in the # software to the public domain. We make this dedication for the benefit # of the public at large and to the detriment of our heirs and # successors. We intend this dedication to be an overt act of # relinquishment in perpetuity of all present and future rights to this # software under copyright law. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR # OTHER DEALINGS IN THE SOFTWARE. # ----------------------------------------------------------------------- # NOTE: Requires python 2 due to python 3's braindeadness. import os import socket import stat import subprocess import threading # Config port = 7070 gopherroot = os.environ['HOME']+'/gopher' # Set up socket sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #DEBG sock.bind(('', port)) sock.listen(1) # Helper functions def exist(path): return os.access(path, os.F_OK) def isdir(path): st = os.stat(path) return stat.S_ISDIR(st.st_mode) def isexecutable(path): return os.access(path, os.X_OK) def normalizepath(path): path = path.split('/') while '' in path: path.remove('') while '..' in path: i = path.index('..') if i == 0: return None # Attempted to access something outside gopherroot path = path[:i-1] + path[i+1:] return '/'.join(path) # Error handling def notfounderror(conn, path): conn.sendall('3"%s" does not exist\t/\t(null)\t0\n' % path) def notallowederror(conn, path): conn.sendall('3Access denied\t/\t(null)\t0\n') # Server implementation def getrequest(conn): ishttp = False data = '' while True: chunk = conn.recv(1024) if not chunk: return None, ishttp data += chunk if data[-1] == '\n': break while len(data) > 0 and data[-1] in ('\r', '\n'): data = data[:-1] # Minimal HTTP support if len(data) >= 4 and data[:4] == 'GET ': data = data.split()[1] ishttp = True return data.split('\t'), ishttp def getselector(request): # If a HTTP request with selector is used, this extracts the selector if len(request) < 1: return (request, None) req = request[0].split('/') while '' in req: req.remove('') args = request[1:] if len(req) >= 1 and req[0] in ['0', '1', '5', '9', 'g', 'h', 'I', 's']: # Supported selectors reqpath = '/'.join(req[1:]) selector = req[0] elif len(req) == 0: # Root is by default of type 1 reqpath = '/' selector = '1' else: reqpath = '/'.join(req) selector = None return ([reqpath] + args, selector) def sendheader(conn, ishttp, selector): if ishttp: # All others can safely be made text/plain contenttypes = {'5': 'application/octet-stream', '9': 'application/octet-stream', 'g': 'image/gif', 'h': 'text/html; charset=utf-8', 'I': 'application/octet-stream', 's': 'application/octet-stream'} if selector is not None and selector in contenttypes: contenttype = contenttypes[selector] else: contenttype = 'text/plain; charset=utf-8' # Default to text/plain conn.sendall('HTTP/1.1 200 OK\r\n' 'Content-type: %s\r\n' '\r\n' % contenttype) def serveurlredirect(conn, path): path = path[4:] conn.sendall('\n' '\n' '\t\n' '\t\t\n' '\t\n' '\t\n' '\t\t

Redirect to %s

\n' '\t\n' '' % (path, path, path)) def servecommon(conn, fd): for line in fd: conn.sendall(line) fd.close() def servecgi(conn, path): proc = subprocess.Popen([path], stdout=subprocess.PIPE) servecommon(conn, proc.stdout) def servefile(conn, path): f = open(path, 'r') servecommon(conn, f) def serverequest(conn, request): # URL link extension if len(request[0]) >= 4 and request[0][:4] == 'URL:': return serveurlredirect(conn, request[0]) reqpath = normalizepath(request[0]) if reqpath == None: return notallowederror(conn, reqpath) path = gopherroot + '/' + reqpath if not exist(path): return notfounderror(conn, reqpath) if isdir(path): if exist(path + '/gophermap'): path = path + '/gophermap' else: return notfounderror(conn, reqpath) if isexecutable(path): servecgi(conn, path) else: servefile(conn, path) class Serve(threading.Thread): def __init__(self, conn): self.conn = conn threading.Thread.__init__(self) def run(self): try: (request, ishttp) = getrequest(self.conn) if not request: self.conn.shutdown(socket.SHUT_RDWR) self.conn.close() return if ishttp: request, selector = getselector(request) else: selector = None sendheader(conn, ishttp, selector) serverequest(self.conn, request) self.conn.shutdown(socket.SHUT_RDWR) self.conn.close() except socket.error: self.conn.close() while True: conn, addr = sock.accept() Serve(conn).start()