2025-01-12 21:36:58 -08:00
#!/usr/bin/python3
2025-01-12 22:31:01 -08:00
import asyncio , traceback , socket , ssl
2025-01-13 20:28:54 -08:00
from urllib . parse import urlparse , quote
2025-01-13 16:27:15 -08:00
from flask import Flask , request , redirect , send_file , Response
2025-01-12 21:36:58 -08:00
from hypercorn . config import Config
from hypercorn . asyncio import serve
app = Flask ( __name__ )
@app.route ( " / " )
def root ( ) :
2025-01-13 21:31:01 -08:00
return send_file ( " home.html " )
2025-01-13 15:53:40 -08:00
@app.route ( " /external.png " )
def external ( ) :
return send_file ( " external.png " )
@app.route ( " /cross-server.png " )
2025-01-13 15:55:48 -08:00
def crosserver ( ) :
return send_file ( " cross-server.png " )
@app.route ( " /style.css " )
2025-01-13 16:00:57 -08:00
def style ( ) :
2025-01-13 16:13:58 -08:00
return send_file ( " style.css " )
2025-01-12 21:51:59 -08:00
@app.route ( " /gem " )
def relay ( ) :
url = request . args . get ( ' gemini ' )
2025-01-13 20:27:09 -08:00
queries = request . args . get ( ' query ' )
2025-01-12 21:51:59 -08:00
if url == None :
return redirect ( " / " )
2025-01-12 22:00:24 -08:00
code = " <h1>Something went wrong...</h1> \n "
2025-01-12 21:36:58 -08:00
title = " Something went wrong... "
try :
2025-01-12 22:31:01 -08:00
gsocket = socket . socket ( socket . AF_INET , socket . SOCK_STREAM )
2025-01-13 20:27:09 -08:00
fulladdr = " gemini:// " + url + ( " ? " + queries if queries != None else " " )
2025-01-12 22:39:23 -08:00
gemsocket = ssl . _create_unverified_context ( ) . wrap_socket ( gsocket , server_hostname = urlparse ( fulladdr ) . hostname )
2025-01-12 22:37:07 -08:00
gemsocket . connect ( ( urlparse ( fulladdr ) . hostname , 1965 ) )
2025-01-13 20:27:09 -08:00
gemsocket . send ( bytes ( fulladdr + " \r \n " , " UTF-8 " ) )
2025-01-12 22:31:01 -08:00
received = " "
2025-01-13 21:52:44 -08:00
mimetype = " "
gemraw = bytearray ( )
num = 0
2025-01-13 21:57:21 -08:00
ok = False
2025-01-12 22:31:01 -08:00
while True :
gemresponse = gemsocket . recv ( 2048 )
2025-01-13 16:37:23 -08:00
try :
2025-01-13 21:53:30 -08:00
if num == 0 :
2025-01-13 21:52:44 -08:00
try :
mimetype = gemresponse . decode ( ) . replace ( " \r " , " " ) . split ( " \n " ) [ 0 ] . split ( " " ) [ 1 ] . split ( " ; " ) [ 0 ]
2025-01-13 21:56:20 -08:00
ok = gemresponse . decode ( ) . replace ( " \r " , " " ) . split ( " \n " ) [ 0 ] . split ( " " ) [ 0 ] [ 0 ] == " 2 "
2025-01-13 21:57:21 -08:00
print ( " Non-ordinary type! " + mimetype )
2025-01-13 21:52:44 -08:00
except :
pass
2025-01-13 21:57:21 -08:00
elif num == 1 and ok and not mimetype in [ " text/gemini " , " " ] :
2025-01-13 21:52:44 -08:00
pass
2025-01-13 21:57:21 -08:00
elif num > = 2 and ok and not mimetype in [ " text/gemini " , " " ] :
2025-01-13 21:52:44 -08:00
for i in gemresponse :
gemraw . append ( i )
2025-01-13 21:57:44 -08:00
if ok and mimetype in [ " text/gemini " , " " ] and gemresponse . decode ( ) != " " :
2025-01-13 16:37:23 -08:00
received + = gemresponse . decode ( )
else :
break
2025-01-13 21:52:44 -08:00
num + = 1
2025-01-13 16:37:23 -08:00
except :
2025-01-13 16:39:33 -08:00
return f ' <!DOCTYPE html> \n <html><head><meta charset= " UTF-8 " ><link rel= " stylesheet " href= " /style.css " ><title>Something went wrong...</title></head><body><h1>Something went wrong...</h1><p>The file that the Gemini server sent couldn \' t be decoded by Gem2Browser.</p></body></html> '
2025-01-13 21:54:37 -08:00
print ( mimetype )
2025-01-13 21:57:21 -08:00
if ok and not mimetype . lower ( ) in [ " text/gemini " , " " ] :
2025-01-13 21:52:44 -08:00
return bytes ( gemraw )
2025-01-12 22:31:01 -08:00
received = received . replace ( " \r " , " " )
firstline = True
redirected = False
gemtext = True
code = " "
2025-01-13 20:12:31 -08:00
escaped = False
2025-01-12 22:31:01 -08:00
for i in received . split ( " \n " ) :
2025-01-13 18:35:32 -08:00
i = i . replace ( " < " , " < " )
2025-01-12 22:31:01 -08:00
if firstline :
if i . split ( " " ) [ 0 ] [ 0 ] == " 3 " :
2025-01-13 20:28:54 -08:00
return redirect ( " /gem?gemini= " + quote ( i . split ( " " ) [ 1 ] [ 9 : ] , safe = ' ' ) )
2025-01-13 20:27:09 -08:00
elif i . split ( " " ) [ 0 ] [ 0 ] == " 1 " :
2025-01-13 20:32:43 -08:00
return f ' <!DOCTYPE html> \n <html><head><meta charset= " UTF-8 " ><link rel= " stylesheet " href= " /style.css " ><title>Input required</title></head><body><h1>Input required</h1><p>The specified Gemini server wants more data: <pre> { i } </pre></p><form action= " /gem " ><input hidden class= " input " value= " { url } " type= " text " name= " gemini " ><input class= " input " type= " text " name= " query " ><br><input type= " submit " class= " go " value= " Go! " ><br><br></form></body></html> '
2025-01-12 22:31:01 -08:00
elif i . split ( " " ) [ 0 ] [ 0 ] != " 2 " :
2025-01-13 16:19:28 -08:00
return f ' <!DOCTYPE html> \n <html><head><meta charset= " UTF-8 " ><link rel= " stylesheet " href= " /style.css " ><title>Something went wrong...</title></head><body><h1>Something went wrong...</h1><p>The specified Gemini server returned a status of: { i } </p></body></html> '
2025-01-12 22:31:01 -08:00
else :
firstline = False
2025-01-13 16:28:55 -08:00
if i . split ( " " ) [ 1 ] . split ( " ; " ) [ 0 ] != " text/gemini " :
2025-01-13 16:28:25 -08:00
print ( " Unrecognised type: " + i . split ( " " ) [ 1 ] )
2025-01-13 16:27:15 -08:00
return Response ( " " . join ( received . split ( " \n " ) [ 1 : ] ) , mimetype = i . split ( " " ) [ 1 ] )
2025-01-12 22:31:01 -08:00
else :
2025-01-13 20:11:59 -08:00
if escaped :
if i [ 0 : 3 ] == " ``` " :
code + = " </pre> \n "
escaped = False
2025-01-13 15:53:40 -08:00
else :
2025-01-13 20:11:59 -08:00
code + = i + " \n "
2025-01-12 22:31:01 -08:00
else :
2025-01-13 20:11:59 -08:00
if i [ 0 : 2 ] == " # " :
if title == " Something went wrong... " :
title = i [ 2 : ]
temp = i [ 2 : ]
code + = f " <h1> { temp } </h1> \n "
elif i [ 0 : 3 ] == " ## " :
temp = i [ 3 : ]
code + = f " <h2> { temp } </h2> \n "
elif i [ 0 : 3 ] == " ``` " :
code + = " <pre> \n "
escaped = True
elif i [ 0 : 4 ] == " ### " :
temp = i [ 4 : ]
code + = f " <h3> { temp } </h3> \n "
elif i [ 0 : 2 ] == " * " :
temp = i [ 2 : ]
code + = f " <ul><li> { temp } </li></ul> \n "
elif i [ 0 : 2 ] == " => " :
temp = " " . join ( i [ 2 : ] . strip ( ) . replace ( " " , " " ) . split ( " " ) )
goto = temp . split ( " " ) [ 0 ]
prse = urlparse ( goto )
extra = " "
extracomment = goto
if prse . netloc == " " and prse . scheme == " " :
isdir = url [ len ( url ) - 1 ] == " / "
isabs = goto [ 0 ] == " / " or goto [ 0 : 2 ] == " // "
dubleslash = goto [ 0 : 2 ] == " // "
if isabs :
2025-01-13 20:15:40 -08:00
tempurl = urlparse ( fulladdr ) . hostname + ( goto [ 1 : ] if dubleslash else goto )
2025-01-13 20:11:59 -08:00
elif isdir :
tempurl = url + goto
else :
tempurl = " / " . join ( url . split ( " / " ) [ : - 1 ] ) + " / " + goto
goto = f " /gem?gemini= { tempurl } "
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 } ) "
else :
if prse . hostname != urlparse ( fulladdr ) . hostname :
extra = " <img height= \" 19 \" src= \" /cross-server.png \" > "
extracomment = f " This link points to an address that isn ' t from the server you ' re currently connecting to ( { prse . hostname } ) "
goto = goto . replace ( " gemini:// " , " " )
goto = f " /gem?gemini= { goto } "
if temp . split ( " " ) == 1 :
comment = goto
else :
comment = " " . join ( temp . split ( " " ) [ 1 : ] )
code + = f " <p title= \" { extracomment } \" ><a href= \" { goto } \" > { comment } { extra } </a></p> \n "
elif i [ 0 : 2 ] == " > " :
code + = f " <p class= ' greentext ' > { i } </p> \n "
else :
code + = f " <p> { i } </p> \n "
2025-01-12 22:31:01 -08:00
if title == " Something went wrong... " :
title = " gemini:// " + url
2025-01-12 21:36:58 -08:00
except :
2025-01-13 16:24:58 -08:00
code + = " <pre> " + traceback . format_exc ( ) + " </pre> "
2025-01-13 15:55:48 -08:00
return f ' <!DOCTYPE html> \n <html><head><meta charset= " UTF-8 " ><link rel= " stylesheet " href= " /style.css " ><title> { title } </title></head><body> { code } </body></html> '
2025-01-12 21:36:58 -08:00
# Run the Hypercorn ASGI server
2025-01-12 21:49:36 -08:00
conf = Config ( )
conf . bind = " 0.0.0.0:2009 "
asyncio . run ( serve ( app , conf ) )