90 lines
2.9 KiB
Python
90 lines
2.9 KiB
Python
|
"""
|
||
|
A guestbook application that accepts user messages using the INPUT response type
|
||
|
and stores messages in a simple SQLite database file.
|
||
|
"""
|
||
|
import asyncio
|
||
|
import sqlite3
|
||
|
import typing
|
||
|
import urllib.parse
|
||
|
from datetime import datetime
|
||
|
|
||
|
import jetforce
|
||
|
|
||
|
|
||
|
class GuestbookApplication(jetforce.BaseApplication):
|
||
|
|
||
|
db_file = "guestbook.sql"
|
||
|
|
||
|
def connect_db(self) -> typing.Tuple[sqlite3.Connection, sqlite3.Cursor]:
|
||
|
db = sqlite3.connect(self.db_file, detect_types=sqlite3.PARSE_DECLTYPES)
|
||
|
cursor = db.cursor()
|
||
|
cursor.execute(
|
||
|
"""
|
||
|
CREATE TABLE IF NOT EXISTS guestbook (
|
||
|
id int PRIMARY KEY,
|
||
|
message text,
|
||
|
created timestamp,
|
||
|
ip_address text);
|
||
|
"""
|
||
|
)
|
||
|
db.commit()
|
||
|
return db, cursor
|
||
|
|
||
|
def __call__(
|
||
|
self, environ: dict, send_status: typing.Callable
|
||
|
) -> typing.Iterator[bytes]:
|
||
|
url = environ["URL"]
|
||
|
url_parts = urllib.parse.urlparse(url)
|
||
|
|
||
|
error_message = self.block_proxy_requests(url, environ["HOSTNAME"])
|
||
|
if error_message:
|
||
|
return send_status(jetforce.STATUS_PROXY_REQUEST_REFUSED, error_message)
|
||
|
|
||
|
if url_parts.path in ("", "/"):
|
||
|
send_status(jetforce.STATUS_SUCCESS, "text/gemini")
|
||
|
yield from self.list_messages()
|
||
|
elif url_parts.path == "/submit":
|
||
|
if url_parts.query:
|
||
|
self.save_message(url_parts.query, environ["REMOTE_ADDR"])
|
||
|
return send_status(jetforce.STATUS_REDIRECT_TEMPORARY, "/")
|
||
|
else:
|
||
|
return send_status(
|
||
|
jetforce.STATUS_INPUT, "Enter your message (max 256 characters)"
|
||
|
)
|
||
|
else:
|
||
|
return send_status(jetforce.STATUS_NOT_FOUND, "Invalid address")
|
||
|
|
||
|
def save_message(self, message: str, ip_address: str) -> None:
|
||
|
message = message[:256]
|
||
|
created = datetime.utcnow()
|
||
|
|
||
|
db, cursor = self.connect_db()
|
||
|
sql = "INSERT INTO guestbook(message, created, ip_address) VALUES (?, ?, ?)"
|
||
|
cursor.execute(sql, (message, created, ip_address))
|
||
|
db.commit()
|
||
|
|
||
|
def list_messages(self) -> typing.Iterator[bytes]:
|
||
|
yield "Guestbook\n=>/submit Leave a Message\n".encode()
|
||
|
|
||
|
db, cursor = self.connect_db()
|
||
|
cursor.execute("SELECT created, message FROM guestbook ORDER BY created DESC")
|
||
|
for row in cursor.fetchall():
|
||
|
yield f"\n[{row[0]:%Y-%m-%d %I:%M %p}]\n{row[1]}\n".encode()
|
||
|
|
||
|
yield "\n...\n".encode()
|
||
|
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
parser = jetforce.build_argument_parser()
|
||
|
args = parser.parse_args()
|
||
|
app = GuestbookApplication()
|
||
|
server = jetforce.GeminiServer(
|
||
|
host=args.host,
|
||
|
port=args.port,
|
||
|
certfile=args.certfile,
|
||
|
keyfile=args.keyfile,
|
||
|
hostname=args.hostname,
|
||
|
app=app,
|
||
|
)
|
||
|
asyncio.run(server.run())
|