summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--garcon.py50
-rw-r--r--helld.py93
2 files changed, 129 insertions, 14 deletions
diff --git a/garcon.py b/garcon.py
index 9eee43d..040b045 100644
--- a/garcon.py
+++ b/garcon.py
@@ -1,22 +1,31 @@
-# assumes utf8
-# began writing this on 2024-02-20
-# found mozz-archiver on 2024-02-21
-
+from sys import stdin, stdout, stderr
import datetime
+import hashlib
import socket
import ssl
-import uuid
-import hashlib
import urllib.parse
-from sys import argv, stdout
+import uuid
+import traceback
# based on:
# https://tildegit.org/solderpunk/gemini-demo-1/src/branch/master/gemini-demo.py
# https://tildegit.org/solderpunk/AV-98/src/branch/master/src/av98/client.py
# TODO ciphers etc
+SIZE_LIMIT = 4 * 1024 * 1024 # 4MB seems reasonable
+
outf = stdout.buffer
+# directly stolen from gemini-demo
+def absolutise_url(base, relative):
+ # Absolutise relative links
+ if "://" not in relative:
+ # Python's URL tools somehow only work with known schemes?
+ base = base.replace("gemini://","http://")
+ relative = urllib.parse.urljoin(base, relative)
+ relative = relative.replace("http://", "gemini://")
+ return relative
+
def header(k, v):
v = str(v)
assert '\n' not in v
@@ -52,7 +61,9 @@ def request_raw(host, port, url):
cert = s.getpeercert(True)
fp = s.makefile("rb")
- payload = fp.read()
+ payload = fp.read(SIZE_LIMIT)
+ truncated = fp.read() != b''
+ print(truncated)
fp.close()
s.close()
@@ -70,12 +81,14 @@ def request_raw(host, port, url):
header("WARC-IP-Address", peername[0])
header("WARC-Target-URI", url)
header("Content-Type", "application/gemini; msgtype=response") # as in mozz-archiver
+ if trunacted:
+ header("WARC-Truncated", "length")
# my extensions
header("X-Server-Fingerprint", 'sha256:' + hashlib.sha256(cert).hexdigest())
outf.write(b'\r\n')
- outf.write(payload)
+#outf.write(payload)
outf.write(b'\r\n\r\n')
# TODO check for close_notify
@@ -87,14 +100,23 @@ def request_url(url):
return request_raw(p.hostname, p.port or 1965, url)
def request_url_loop(url):
- while True:
+ # i only allow 3 redirects, so detecting loops isn't really necessary
+ for _ in range(3):
res = request_url(url)
header = res.split(b'\r\n')[0]
if 2 + 1 + 1024 < len(header): break
- if header[0] == ord('3'):
- url = header.split(b' ', 2)[1].decode('utf-8')
+ if len(header) > 0 and header[0] == ord('3'):
+ newurl = header.split(b' ', 2)[1].decode('utf-8')
+ url = absolutise_url(url, newurl)
else:
break
-warcinfo()
-request_url_loop(argv[1])
+if __name__ == '__main__':
+ warcinfo()
+ outf.flush()
+ for line in stdin:
+ try:
+ request_url_loop(line.rstrip('\r\n').rstrip('\n'))
+ outf.flush()
+ except:
+ print(traceback.format_exc(), file=stderr)
diff --git a/helld.py b/helld.py
new file mode 100644
index 0000000..68d24ac
--- /dev/null
+++ b/helld.py
@@ -0,0 +1,93 @@
+# Hellish Gemini daemon, in both behaviour and code quality.
+#
+# openssl req -newkey rsa:4096 -nodes -keyout key.pem -nodes -x509 -out cert.pem
+# python3 helld.py cert.pem key.pem
+
+import socket
+import threading
+import socketserver
+import ssl
+import sys
+import time
+import random
+from urllib.parse import urlparse
+
+handlers = []
+
+def register(addr):
+ def dec(fn):
+ handlers.append((addr, fn))
+ return dec
+
+@register('/normal')
+def normal(s, _):
+ s.sendall(b'20 text/gemini\r\n# hello\nnothing strange going on here\n')
+
+@register('/noclose')
+def noclose(s, rh):
+ s.sendall(b'20 text/gemini\r\nquick before the connection is killed i need to tell you tha')
+ close(rh.request)
+
+@register('/sleep1')
+def sleep1(s, rh):
+ s.sendall(b'20 text/gemini\r\nabout to take a nap')
+ time.sleep(120)
+
+@register('/sleep2')
+def sleep(s, rh):
+ time.sleep(120)
+ s.sendall(b'20 text/gemini\r\ntook a nap')
+
+@register('/128mb')
+def lotsofdata(s, rh):
+ s.sendall(b'20 text/gemini\r\n')
+ for n in range(128):
+ s.sendall(b'lol ' * (1024 * 1024 // 4))
+
+@register('/4mb')
+def sensibleamountofdata(s, rh):
+ buffer = b'20 text/gemini\r\n' + b'lol ' * (1024 * 1024)
+ buffer = buffer[:4*1024*1024]
+ s.sendall(buffer)
+
+@register('/over4mb')
+def nonsensibleamountofdata(s, rh):
+ buffer = b'20 text/gemini\r\n' + b'lol ' * (1024 * 1024)
+ buffer = buffer[:4*1024*1024+1]
+ s.sendall(buffer)
+
+@register('/loop')
+def loop(s, rh):
+ s.sendall(f'30 /loop/{random.randint(0, 2**32)}\r\n'.encode('utf-8'))
+
+@register('/index')
+def index(s, rh):
+ s.sendall(b'20 text/gemini\r\n')
+ for addr, _ in handlers:
+ s.sendall(f'=> {addr}\n'.encode('utf-8'))
+
+@register('')
+def toindex(s, rh):
+ s.sendall(b'30 /index\r\n')
+
+class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):
+ def handle(self):
+ s = context.wrap_socket(self.request, server_side=True)
+ req = s.read().decode('utf-8').rstrip('\r\n')
+ print(req)
+ req = urlparse(req)
+ for addr, fn in handlers:
+ if req.path.startswith(addr):
+ fn(s, self)
+ break
+
+class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
+ pass
+
+if __name__ == "__main__":
+ context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
+ context.load_cert_chain(sys.argv[1], sys.argv[2])
+
+ ThreadedTCPServer.allow_reuse_address = True
+ server = ThreadedTCPServer(('0.0.0.0', 1965), ThreadedTCPRequestHandler)
+ server.serve_forever()