Introducing Gophersnake
=======================

*Written: 2020-04-22 00:44:00 UTC*

I finally took the time to read about the Gopher protocol and it turns out that
I love it. It was even easier than I thought it would be. I made a *super*
basic gopher server which can be found on my [rawtext gitea][1]. Though, I can
put the entire contents in here and it's really not that bad. Because at the
moment it's only 63 lines. See?

---

   #!/usr/bin/env python3

   import asyncio
   import os.path
   import stat
   from pathlib import Path
   GOPHER_ROOT = os.environ.get('GOPHER_ROOT', '/var/gopher')


   def get_file(filename):
       # Relative paths are bad. normpath strips interior `/../` out
       filename = Path(GOPHER_ROOT, os.path.normpath(filename.rstrip()).lstrip('/')).resolve()
       gophermap = filename / 'gophermap'
       #print(filename)
       #print(gophermap)
       result = None
       if filename.exists() and filename.is_file():
           result = filename
       elif gophermap.exists():
           result = gophermap
       #print(result)

       # If there was a result, but it's not world readable, unset the
       # result.
       #print(stat.filemode(result.stat().st_mode))
       if result and stat.filemode(result.stat().st_mode)[-3] != 'r':
           result = None
       return result


   async def handle_message(reader, writer):
       data = await reader.read(1024)
       request = data.decode()
       addr = writer.get_extra_info('peername')
       if not request.strip():
           resource = get_file('')
       else:
           resource = get_file(request)

       print('returning', resource)
       if resource is None:
           writer.write(b'Error: File or directory not found!')
       else:
           writer.write(resource.read_bytes())
       await writer.drain()
       writer.close()


   loop = asyncio.get_event_loop()
   coro = asyncio.start_server(handle_message, '0.0.0.0', int(os.environ.get('GOPHER_PORT', 70)), loop=loop)
   server = loop.run_until_complete(coro)

   print('serving forever')
   try:
       loop.run_forever()
   except KeyboardInterrupt:
       print('\r^C caught. goodbye!')
       pass


   server.close()
   loop.run_until_complete(server.wait_closed())
   loop.close()

---

*How cool is that??* Like... I knew gopher was simple, but I didn't know just
how simple. Yeah, that's not going to autogen your directory listing, but
I could probably add that in 10 lines or so. For my purposes there I really
didn't want to do that, though, since it's just going to be a super basic
server for my own personal gopher hole.

All gopher really is is asking a server for what lives at a path. That's it.
There's not even a domain involved. Which is kind of sad because you can't host
multiple different domains on one server, but... that's kind of OK.

Anyway, this is a server that exists and if it is helpful for you at all,
great! If not... well, I hope you learned anything?


[1]: https://git.rawtext.club/wangofett/gophersnake