Merge pull request 'Implement Titan support' (#1) from titan-implemenatation into main
Some checks failed
Test web app / Check syntax (push) Successful in 12s
Test web app / Test server with Flask Dev Mode (push) Successful in 14s
Test web app / Test server with Gunicorn (WSGI) (push) Successful in 13s
Test web app / Test server with Hypercorn (ASGI) (push) Has been cancelled

Reviewed-on: #1
This commit is contained in:
Mohammed Qureshi (Nova Cat) 2025-02-16 21:27:52 -08:00
commit 682f30a330
2 changed files with 121 additions and 1 deletions

122
server.py
View file

@ -25,6 +25,9 @@ def external():
@app.route("/cross-server.png")
def crosserver():
return send_file("cross-server.png")
@app.route("/write.png")
def write():
return send_file("write.png")
@app.route("/loadcert")
def loadcert():
return send_file("loadcert.html")
@ -100,9 +103,118 @@ def favicon():
@app.route("/logo.png")
def logo():
return send_file("gem2browser.png")
@app.route("/titan", methods=['GET', 'POST'])
def titanedit():
url = request.args.get('url')
queries = request.args.get('query')
certfile = request.cookies.get('certname')
code = "<h1>Something went wrong...</h1>\n"
title = "Something went wrong..."
try:
if request.method == "POST":
mimetype = request.form.get("mimetype")
token = request.form.get("token")
value = request.form.get("value")
gsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
fulladdr = "titan://" + url
contx = ssl.create_default_context()
contx.check_hostname = False
contx.verify_mode = ssl.CERT_NONE
if certfile:
contx.load_cert_chain(certfile=homefolder + "/certs/" + certfile + "-chain.pem", keyfile=homefolder + "/certs/" + certfile + "-privkey.pem");
gemsocket = contx.wrap_socket(gsocket, server_hostname=urlparse(fulladdr).hostname)
gemsocket.connect((urlparse(fulladdr).hostname, 1965))
gemsocket.send(bytes(fulladdr + (";mime=" + mimetype if mimetype else "") + (";token=" + token if token else "") + f";size={len(value)}" + "\r\n" + value, "UTF-8"))
gemraw = bytearray()
allraw = []
num = 0
ok = False
binary = False
while True:
gemresponse = gemsocket.recv(2048)
allraw.append(gemresponse)
if len(gemresponse) != 0:
for i in gemresponse:
gemraw.append(i)
else:
break
found = False
mtype = bytearray()
for i in bytes(gemraw):
try:
if not found:
mtype.append(i)
if "\n" in bytes(mtype).decode() and not found:
found = True
ok = bytes(mtype).decode().split(" ")[0][0] == "3"
code = bytes(mtype).decode()
break
except:
pass
if not ok:
resp = make_response(f'<!DOCTYPE html>\n<html><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><link rel="stylesheet" href="/style.css"><title>Something went wrong...</title></head><body><h1>Something went wrong...</h1><p>I submitted the Titan request, I expected a redirect, but got: <pre>{code}</pre></p></body></html>', 500)
return resp
else:
ref = bytes(mtype).decode().split(" ")[1]
return redirect("/gem?gemini=" + quote((ref[9:] if "gemini://" in ref else ref).replace("\r", "").replace("\n", ""), safe=''))
else:
mimetype_edit = ""
if ";edit" in url:
gsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
fulladdr = "titan://" + url
contx = ssl.create_default_context()
contx.check_hostname = False
contx.verify_mode = ssl.CERT_NONE
if certfile:
contx.load_cert_chain(certfile=homefolder + "/certs/" + certfile + "-chain.pem", keyfile=homefolder + "/certs/" + certfile + "-privkey.pem");
gemsocket = contx.wrap_socket(gsocket, server_hostname=urlparse(fulladdr).hostname)
gemsocket.connect((urlparse(fulladdr).hostname, 1965))
gemsocket.send(bytes(fulladdr + "\r\n", "UTF-8"))
mimetype = ""
gemraw = bytearray()
num = 0
ok = False
binary = False
while True:
gemresponse = gemsocket.recv(2048)
if len(gemresponse) != 0:
for i in gemresponse:
gemraw.append(i)
else:
break
found = False
gemcontent_binary = bytearray()
mtype = bytearray()
for i in bytes(gemraw):
try:
if not found:
mtype.append(i)
else:
gemcontent_binary.append(i)
if "\n" in bytes(mtype).decode() and not found:
found = True
ok = bytes(mtype).decode().split(" ")[0][0] == "2"
code = bytes(mtype).decode()
mimetype_edit = bytes(mtype).decode().replace("\r", "").split("\n")[0].split(" ")[1].split(";")[0]
except:
pass
if not ok:
resp = make_response(f'<!DOCTYPE html>\n<html><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><link rel="stylesheet" href="/style.css"><title>Something went wrong...</title></head><body><h1>Something went wrong...</h1><p>Gemini server wanted to use Titan editor, but it returned <code>{code}</code></p></body></html>', 500)
return resp
val = gemcontent_binary.decode()
else:
val = ""
if ";mimetype" in url:
mimetype_edit = url.split(";mimetype=")[1].split(";")[0]
ifquery = "&query=" + queries if queries != None else ""
url = url.replace(";edit", "")
return f'<!DOCTYPE html>\n<html><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><link rel="stylesheet" href="/style.css"><title>Advanced Editor (Titan)</title></head><body><h1>Advanced Editor (Titan)</h1><p>The gemini server wants more data, but using the Titan protocol, you may edit here.</p><br><form action="/titan?url={url}{ifquery}" method=post enctype=multipart/form-data><p>Mimetype: <input type="text" name="mimetype" class="input" value="{mimetype_edit}"></p><p>Token (Not required for edits): <input type="text" name="token" class="input"></p><br><textarea width="90vw" height="75vh" class="input" name="value" style="font-style: monospace;">{val}</textarea><br><br><input type="submit" class="go" value="Done"></body></html>'
except:
code += "<pre>" + traceback.format_exc() + "</pre>"
resp = make_response(f'<!DOCTYPE html>\n<html><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><link rel="stylesheet" href="/style.css"><title>{title}</title></head><body>{code}</body></html>', 500)
return resp
@app.route("/gem")
def relay():
print(request.headers.get('User-Agent'))
url = request.args.get('gemini')
queries = request.args.get('query')
certfile = request.cookies.get('certname')
@ -237,6 +349,14 @@ def relay():
tempurl = "/".join(url.split("/")[:-1]) + "/" + goto
tempurl = quote(tempurl, safe='')
goto = f"/gem?gemini={tempurl}"
elif prse.scheme == "titan":
extra = "<img height=\"19\" src=\"/write.png\">"
extracomment = "This is a titan link, you may edit text using this link."
q = ""
if "?" in goto:
q = goto.split("?")[1]
goto = goto.replace(f"?{q}", "")
goto = f"/titan?url=" + goto[8:] + ("&query=" + q if q else "")
elif prse.scheme != "gemini":
extra = "<img height=\"19\" src=\"/external.png\">"
extracomment = f"This link points to an address that isn't gemini ({prse.scheme})"

BIN
write.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 859 B