- Common Lisp 100%
This is intended to help with the case where the UTF-8 text from Gemini maps cleanly to ISO Latin-1, and the Gopher client supports Latin-1. Slightly experimental. |
||
|---|---|---|
| .gitignore | ||
| cosmarmot.asd | ||
| cosmarmot.ros | ||
| cosmarmot.service | ||
| LICENSE.md | ||
| package.lisp | ||
| pages.lisp | ||
| README.org | ||
| server.lisp | ||
| util.lisp | ||
Cosmarmot - a Gopher to Gemini gateway
Cosmarmot is a Gemini gateway for Gopher clients, written in Common Lisp. It runs as a server and accepts Gopher requests, translating them to Gemini requests and retrieving them. It renders gemtext pages as gophermaps, which is kind of a sin, but usually works pretty well.
Motivation
Although Gemini's simple protocol and easily to parse plain text markup seem naturally suited to retrocomputing, many older computers are unable to cope with TLS for one reason or another: either they are actually too slow to handle the encryption, or they are not capable of running an operating system with up-to-date cryptography libraries. However, most, if not all, of these machines are capable of running a Gopher client. Cosmarmot can be run on any TLS-capable computer (such as an old laptop or a single board computer) to fetch Gemini content for Gopher clients on its local network.
Usage
Installation
These installation instructions will use Roswell to set up and run Cosmarmot. You can definitely set it up without Roswell, if you have a basic familiarity with asdf and quicklisp.
- If you are going to be running a Cosmarmot server reachable from the internet, you should create a user specifically for running Cosmarmot, with no access to anywhere other than its home directory.
- Install Roswell system-wide, following the instructions on the Roswell website.
- As the user that will be running Cosmarmot, run 'ros setup' to set up your Roswell environment and Common Lisp compiler and runtime.
- As the user that will be running Cosmarmot, clone the 'gemtext' and 'cl-gemini-client' projects from this site into .roswell/local-projects/. You can clone them to someplace else that ASDF knows about, but that's the easiest. You should also probably put cosmarmot there and run it from there.
- Edit the 'cosmarmot.ros' script, changing the :host and :port keyword arguments to the values you need. The :host keyword will be used both for picking an address for the server to bind to, and to construct proxied URLs, so if you are opening your server up to the internet, it needs to be a resolvable hostname or a public IP address. If you're not running it as root (which you shouldn't be), you will need to run it on a port other than 70 (I recommend 7070), or grant that user capabilities allowing them to listen on low ports. That's beyond the scope of this README.
Running the server
There is a systemd service file provided, which starts Cosmarmot from the Roswell script, running as the user you installed it as - you will have to customize the user and paths. If your system does not use systemd and you want to run Cosmarmot as a system service, you'll have to write an init script appropriate for your system. The systemd service file is set up to enable running Cosmarmot on port 70 as a regular user, and also to disallow most filesystem access. If you are running it from a user home directory, you'll have to relax the protections on /home.
If you are simply running Cosmarmot as a single user for your own use, you can just run the 'cosmarmot.ros' script from a terminal, or screen, or tmux.
The first time you run Cosmarmot, it will download its dependencies using quicklisp, so might start up a bit slowly.
Using with a Gopher client
The Gopher client does not need to support proxies; Gemini pages can be fetched with the hostname or IP address of the Cosmarmot server, with a specially-constructed selector.
In URL form, you would use:
gopher://cosmarmot-host:port/1/gemini-host/gemini-path
In Gopher clients that don't support URLs, you need to use
cosmarmot-host as the host, cosmarmot-port as the port, 1 as
the item type, and /gemini-host/gemini-path as the selector. You
may need to enter this in a dialog, or write a gophermap with a
line with those elements.
Internally, Cosmarmot will rewrite Gemini URLs into Gopher items that go through Cosmarmot. Binary files and text files that are not recognized as gemtext will be passed through unchanged (though future support for downscaling and converting images is planned).
Limitations
Content-type guessing
Gophermaps need to be able to tell the client in advance what kind of media to expect (text file, subdirectory, image, etc.). This makes sense for Gopher, because Gophermaps are generally either written by hand (and the author knows what's on the other side of the link), or are indices of local directories (and the server can probe or guess their file types). A gemtext file does not have that information available, however. Like with HTTP, links don't have file type information; Gemini requests return the MIME type of the file as part of their response.
This means that when constructing a gophermap from a gemtext page, it is not always possible to guess the right item type for a particular line. If the URL of the gemtext link ends in a filename with an extension, Cosmarmot will guess based on that. If it looks like a directory, it will be handled as gemtext. For now, Cosmarmot will also handle anything it can't look up as text/gemini (on the basis that links to directories in Gemini URLs may not end in "/"), but this may change in the future.
In practice, I have not noticed any problems with this, but I'm sure someone will run into it.
Cosmarmot does not yet use every well-known item type (like Mac BinHex or uuencoded binary) even when they're appropriate. This is planned to be added.
Error handling
Gopher has no means to signal an error, which makes it somewhat harder to report Gemini errors back to a Gopher client. For type 0 and 1 items, Cosmarmot could report Gemini errors as a constructed text file or gophermap, but it does not yet do this.
Unicode
Most Gemini pages are in UTF-8. Many Gopher clients do not understand UTF-8. For pages in a Latin-alphabet language, this is rarely that significant, because UTF-8 is a superset of the ISO-8859-1 Latin-1 codepage. Some punctuation and emoji will show up as garbage, and that's fine, and may remind you of the bad old days where every script used its own codepage, and sometimes more than one, and you had to guess which one was correct. It's a bigger problem for, e.g., Cyrillic or CJK text. If your client supports UTF-8 (either a modern client, or a very naive old client on a modern terminal emulator), then you should be fine.
This is somewhat solvable for older clients: the Gemini response should include the main language of the page, and it would be possible to convert the document to an appropriate legacy codepage. But this will require some refactoring to implement, and also implementing it will probably ruin things for people using modern Gopher clients.
Input requests
Currently, Cosmarmot does not handle 1x responses (input requests) from Gemini. Handling them is complicated by the same impedance mismatch between Gopher item types and Gemini responses that affects content types. In the future, Cosmarmot will probably handle 1x responses by creating an interstitial Gopher page with the prompt and a type 7 item.
Client issues
Very old gopher clients
Some gopher clients are old enough not to support every common item type. In particular, some are even old enough not to support the "I" (informational) item type, which Cosmarmot uses to represent plain, preformatted, or quoted lines in gemtext. Such a client will only see the links from the gemtext. In the future, I plan to put a link to the raw original gemtext in each rendered gophermap, for the sake of such clients.
TurboGopher
The latest version of TurboGopher seems to ignore port numbers in Gopher selectors. If you're not running Cosmarmot on port 70 (I've been running it on 7070 in testing), you'll have to edit every link to make them work.
Lynx
Lynx does not seem to understand HTML items with a selector of the form
URL: http://foo.example.org/bar.html