-- Leo's gemini proxy

-- Connecting to alpha.lyk.so:1965...

-- Connected

-- Sending request

-- Meta line: 20 text/gemini;

spartan


Spartan protocol is simpler than


Gemini protocol


and does not use TLS. This makes it significantly less computationally expensive and thus friendlier to very old machines. It also simplifies the whole codebase for clients and servers. TLS is difficult to get right and is seen by some as a source of "hidden complexity" in Gemini. Spartan thus sits between Gemini and Gopher in the same way that Gemini sits between Gopher and the Web.


Be aware that the lack of TLS also means there is no encryption protecting anything sent over the Spartan network. One should *not* send, e.g., credit card numbers over this protocol.


This document is meant to be complementary to


the official Spartan specification


and contains much of the same information, simply stated differently. The "hands-on learning" section is, at the time of writing, novel, but the rest is meant to aid understanding of the official document by doing little more than rephrasing it. If this document and the official specification differ in any way, defer to the official specification.


technical description


client-server interaction


Spartan servers listen for TCP connections on port 300 by default. They accept requests in this form:


<hostname> <path> <data length>
[data]

A line break, as contained in the example above, is defined as a carriage return followed by a line feed. This two-character string is referred to in the Spartan specification (and in this document from this point on) as CRLF, and may, in many programming languages, be represented by the string "\r\n".


At length: a request to a Spartan server is defined as the host name, followed by a single space, followed by the path requested, followed by another space, followed by the length in bytes of the "data block" being submitted to the server (zero if the client is submitting no data block), followed by a CRLF, then optionally followed by the data the client wishes to submit to the server.


The server will then send a response in this form and close the connection once it is done sending data:


<status code> <status data>
[response body]

At length: the server will respond with a single-digit status code, followed by a single space, followed by whatever data may be relevant to the response code, followed by a CRLF, and then, if the status code is "2", followed by the data requested by the client.


There are only four status codes, numbered two through five.


2 indicates success. The "status data" component will contain the mimetype of the data contained in the "response body," and optionally may also include the response body's character encoding in the format "; charset=<encoding>". E.g., a gemtext document successfully retrieved may have as the first line of the response (i.e., the "response header"): "2 text/gemini; charset=utf-8".

3 indicates a redirect. The "status data" component will be the absolute path to which the client should redirect. Cross-host redirects are not supported.

4 indicates a client error. The "status data" component will contain a human-readable error message.

5 indicates a server error. The "status data" component will contain a human-readable error message.


ASCII is the only encoding supported for request and response headers. Unicode may not be included in request or response headers.


default document markup


While any data whatsoever may be served over Spartan, the default document format is an extension of


gemtext markup.


There is one additional line type in Spartan's version of gemtext markup, the "prompt line." This line type removes the need for Gemini's "10 INPUT" response code and is meant to function similarly to a single-input "form" element in HTML:


=:<whitespace><url>[<whitespace><human-friendly text>]

At length: a "prompt line" is defined as the string "=:", followed by an arbitrary number of whitespace characters, followed by a URL, optionally followed by more whitespace and then some human-readable text indicating the purpose of the input field.


url schema


Spartan URLs begin with "spartan://" and have the same structure as the HTTP URLs you may already be familiar with from the Web. The full structure may be defined as:


<scheme>://[<user>@]<host>[:<port>]/<path>[;<parameters>][?<query>][#<fragment>]

The "fragment", "user", and "parameters" components are allowed, but they aren't used. The "query" component should be used by clients to populate the "data block" portion of the request. The "host", "port", and "path" components should be used by the client for the "host", "port", and "path" portions of the Spartan request, as one might expect.


The Spartan specification has some good examples which will not be reproduced here.


hands-on learning


Spartan is simple enough that writing requests by hand is not an entirely unreasonable thing to do, and doing so may help with improving your understanding of the protocol. If something is unclear, experimenting with a Spartan server somewhere can help clear things up.


For these examples, I'll be using netcat. Netcat is a common utility which allows sending raw, arbitrary data to a server over a TCP connection. Installing it on your machine should provide you with the "nc" command used in the following examples (which are meant to be run in your terminal).


If you're running Debian or one of its derivatives (e.g., Ubuntu, Linux Mint, Pop_OS!, etc.) you may be able to install netcat (if it's not already installed) with this command:


sudo apt install netcat-openbsd

Netcat can take input on stdin (piped or typed) and forward it to the server and port given as arguments.


As our first experiment, we'll make a request equivalent to loading the URL "spartan://mozz.us":


printf "mozz.us / 0\r\n" | nc mozz.us 300

We're using printf here because it does not by default print anything except what is in the argument given it.


The server ought to respond to this request with a header line starting with status code 2, followed by the body of mozz.us's Spartan site (a gemtext document at the time this was written).


Requesting a non-existent document should return a header line with status code 4:


printf "mozz.us /non-extistent 0\r\n" | nc mozz.us 300

And requesting a directory without a trailing slash from the server running at mozz.us should, at the time of writing, return a redirect:


printf "mozz.us /ufo 0\r\n" | nc mozz.us 300

Finally, we can sign the mozz.us guestbook by sending some data to spartan://mozz.us/guestbook/sign:


printf "mozz.us /guestbook/sign 18\r\nHello from netcat!" | nc mozz.us 300

(The string "Hello from netcat!" is 18 bytes long, which is where the "18" comes from in the command above.)


You can confirm that you have successfully signed the guestbook by requesting the guestbook page and looking at the latest entry (which should be at the bottom of the page):


printf "mozz.us /guestbook/ 0\r\n" | nc mozz.us 300

We've now demonstrated all the types of requests and responses possible under the Spartan protocol. What follows is the "Backus-Naur form" (BNF) grammar for the protocol's syntax, a formal specification which may act as a compact reference for those building Spartan clients and servers. It should simply state, using far fewer characters, what has already been stated more verbosely in this document.


appendix: bnf grammar


As copied from the specification:


request           = request-line [data-block]
request-line      = host SP path-absolute SP content-length CRLF

reply             = success / redirect / client-error / server-error

success           = '2' SP mimetype      CRLF body
redirect          = '3' SP path-absolute CRLF
client-error      = '4' SP errormsg      CRLF
server-error      = '5' SP errormsg      CRLF

content-length    = 1*DIGIT
data-block        = *OCTET

mimetype          = type '/' subtype *(';' parameter)
body              = *OCTET
errormsg          = 1*(WSP / VCHAR)

; host            from RFC 3986
; path-absolute   from RFC 3986, excluding empty string ""

; type            from RFC 2045
; subtype         from RFC 2045
; parameter       from RFC 2045

; CRLF            from RFC 5234
; DIGIT           from RFC 5234
; OCTET           from RFC 5234
; SP              from RFC 5234
; WSP             from RFC 5234
; VCHAR           from RFC 5234

-- Response ended

-- Page fetched on Fri May 3 12:59:19 2024