initial commit

This commit is contained in:
mattx 2020-10-09 21:23:32 +02:00
commit b6ef339d89
5 changed files with 119 additions and 0 deletions

21
README.md Normal file
View File

@ -0,0 +1,21 @@
# pygem
Spaghetti Python gemini server
## FAQ
Q: Does this work?
A: Yes.
Q: How much does the spaghetti weigh?
A: About 2kg
Q: What does this not support?
A:
- Proxying
- CGI
- Directory indexes
- Multiple domains
- Redirects
- Input in any way
Q: This is too spaghetti
A: Can't do much about this.

2
gemini/index.gmi Normal file
View File

@ -0,0 +1,2 @@
# Hello world
It works!

12
pygem.ini Normal file
View File

@ -0,0 +1,12 @@
[listen]
host = 0.0.0.0
port = 1965
cert = cert.pem
key = key.pem
[server]
root = /absolute/path/to/geminiroot
[gemini]
domain = localhost
allowProxy = no

62
pygem.py Normal file
View File

@ -0,0 +1,62 @@
from server import TLS_ThreadingTCPServer
import socketserver
import configparser
from urllib.parse import urlparse
import os.path
import mimetypes
mimetypes.init()
mimetypes.add_type("text/gemini", ".gmi")
def is_safe_path(basedir, path, follow_symlinks=True):
# resolves symbolic links
if follow_symlinks:
return os.path.realpath(path).startswith(basedir)
return os.path.abspath(path).startswith(basedir)
config = configparser.ConfigParser()
config.read("pygem.ini")
class GeminiHandler(socketserver.StreamRequestHandler):
def handle(self):
self.url = urlparse(self.rfile.readline().strip())
if self.url.netloc == b"" and self.url.scheme == b"":
self.url = urlparse("gemini://%s" % self.rfile.readline().strip())
# thorough check of the URL
if self.url.scheme != b"gemini" and self.url.scheme != b"":
self.wfile.write(("59 URL scheme is not Gemini %s\r\n" % str(self.url)).encode("utf-8"))
return
if self.url.netloc.decode("utf-8") != config["gemini"]["domain"] and config["gemini"]["allowProxy"] == "no":
self.wfile.write("53 \r\n".encode("utf-8"))
return
elif self.url.netloc.decode("utf-8") != config["gemini"]["domain"]:
self.wfile.write("50 Proxying is not implemented yet.\r\n".encode("utf-8"))
return
# Actual serving
file_path = config["server"]["root"] + "/" + self.url.path.decode("utf-8")
if not is_safe_path(config["server"]["root"], file_path):
self.wfile.write("50 No.\r\n".encode("utf-8"))
return
if os.path.isdir(file_path):
file_path = file_path + "/index.gmi"
if not os.path.exists(file_path):
self.wfile.write("40 File not found.\r\n".encode("utf-8"))
return
with open(file_path) as f:
self.wfile.write(("20 %s\r\n" % mimetypes.guess_type(file_path)[0]).encode("utf-8"))
self.wfile.write(f.read().encode("utf-8"))
server = TLS_ThreadingTCPServer((config['listen']['host'], int(config['listen']['port'])),
GeminiHandler,
config['listen']['cert'],
config['listen']['key'])
try:
print("Starting server...")
server.serve_forever()
finally:
print("Server shutting down...")
server.shutdown()
server.server_close()

22
server.py Normal file
View File

@ -0,0 +1,22 @@
import ssl
import socketserver
class TLS_ThreadingTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
"""TCPServer wrapped in TLS and ThreadingMixIn"""
def __init__(self,
server_address,
RequestHandlerClass,
certfile,
keyfile,
bind_and_activate=True):
socketserver.TCPServer.__init__(self, server_address, RequestHandlerClass, bind_and_activate)
self.certfile = certfile
self.keyfile = keyfile
def get_request(self):
newsocket, fromaddr = self.socket.accept()
connstream = ssl.wrap_socket(newsocket,
server_side=True,
certfile=self.certfile,
keyfile=self.keyfile,
ssl_version = ssl.PROTOCOL_TLS_SERVER)
return connstream, fromaddr