gemproto

package module
v0.0.0-...-d471ebf Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 14, 2024 License: ISC Imports: 24 Imported by: 0

README

gemproto - Gemini Protocol for Go

GoDoc Go Report Card

Overview

Package gemproto provides client and server implementations for the Gemini protocol.

Install

go get -u github.com/askeladdk/gemproto

Quickstart

License

Package gemproto is released under the terms of the ISC license.

Some code was taken and modified from the Go source code and is licensed to The Go Authors.

Documentation

Overview

Package gemproto provides client and server implementations for the Gemini protocol.

Index

Constants

View Source
const (
	StatusInput                          = 10
	StatusSensitiveInput                 = 11
	StatusOK                             = 20
	StatusTemporaryRedirect              = 30
	StatusPermanentRedirect              = 31
	StatusTemporaryFailure               = 40
	StatusServerUnavailable              = 41
	StatusCGIError                       = 42
	StatusProxyError                     = 43
	StatusSlowDown                       = 44
	StatusPermanentFailure               = 50
	StatusNotFound                       = 51
	StatusGone                           = 52
	StatusProxyRequestRefused            = 53
	StatusBadRequest                     = 59
	StatusClientCertificateRequired      = 60
	StatusClientCertificateNotAuthorized = 61
	StatusClientCertificateNotValid      = 62
)

Gemini status codes as described in the specification. See: https://geminiprotocol.net/docs/specification.gmi

Variables

View Source
var ErrCertificateNotTrusted = errors.New("gemproto: certificate not trusted")
View Source
var ErrInvalidResponse = errors.New("gemproto: invalid response")

ErrInvalidResponse is returned by Client if it received an invalid response.

View Source
var ErrServerClosed = errors.New("gemproto: server closed")

ErrServerClosed is returned by Listen when the server has been closed.

Functions

func Input

func Input(prompt string) func(Handler) Handler

Input responds with 10 INPUT if the query string is empty.

func NotFound

func NotFound(w ResponseWriter, r *Request)

NotFound responds with 51 Not Found.

func Redirect

func Redirect(w ResponseWriter, r *Request, url string, code int)

Redirect responds with a 3x redirection to the given URL.

Types

type Client

type Client struct {
	// ConnectTimeout sets the idle timeout.
	ConnectTimeout time.Duration

	// ReadTimeout sets the read timeout.
	ReadTimeout time.Duration

	// WriteTimeout sets the write timeout.
	WriteTimeout time.Duration

	// HostsFile is optional and specifies to verify hosts.
	HostsFile *HostsFile

	// GetCertificate is optional and maps hostnames to client certificates.
	GetCertificate GetCertificateFunc
}

Client implements the client side of the Gemini protocol.

The client must close the response body when done with it:

client := gemproto.Client{}
resp, err := client.Get("gemini://gemini.circumlunar.space")
if err != nil {
  // handle error
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
// ...

Client can optionally provide a certificate to authenticate itself to hosts:

cert, err := gemcert.LoadX509KeyPair("client.crt", "client.key")
if err != nil {
  // handle error
}
client := gemproto.Client{
  GetCertificate: gemproto.SingleClientCertificate(cert),
}
// ...

Client can optionally verify and record host certificates using the TOFU mechanism by creating a HostsFile:

hostsfile, f, err := gemproto.OpenHostsFile("./hostsfile")
if err != nil {
  // handle error
}
defer f.Close()
client := gemproto.Client{
  HostsFile: hostsfile,
}
// ...

func (*Client) Do

func (c *Client) Do(req *Request) (*Response, error)

Do sends a request and returns a response.

func (*Client) Get

func (c *Client) Get(rawURL string) (*Response, error)

Get issues a request to the specified URL.

type Dir

type Dir string

Dir implements fs.FS for the local file system.

func (Dir) Open

func (d Dir) Open(name string) (fs.File, error)

Open implements fs.FS using os.Open, opening files for reading rooted and relative to the directory d.

type FileServerFlags

type FileServerFlags int

FileServerFlags enumerates all FileServer capability flags.

const (
	// ListDirs enables directory listing.
	ListDirs FileServerFlags = 1 << iota

	// ShowHiddenFiles enables dot-files to be listed.
	ShowHiddenFiles

	// UseMetaFile enables the .meta file to be scanned.
	UseMetaFile
)

type GetCertificateFunc

type GetCertificateFunc func(hostname string) (tls.Certificate, bool)

GetCertificateFunc is a function that maps a hostname to a certificate.

func SingleClientCertificate

func SingleClientCertificate(cert tls.Certificate) GetCertificateFunc

SingleClientCertificate returns the same certificate regardless of hostname.

type Handler

type Handler interface {
	ServeGemini(ResponseWriter, *Request)
}

Handler responds to a Gemini request.

func FileServer

func FileServer(root fs.FS, flags FileServerFlags) Handler

FileServer returns a handler that serves Gemini requests with the contents of the file system rooted at root.

As a special case, the returned file server redirects any request ending in "/index.gmi" to the same path, without the final "index.gmi".

To use the operating system's file system implementation, use gemproto.Dir:

serveMux.Handle("/", gemproto.FileServer(gemproto.Dir("/tmp", 0)))

Flags

FileServer accepts flags to enable certain capabilities.

ListDirs enables listing directory contents if there is no index.gmi in the directory.

ShowHiddenFiles enables hidden files and directories to be accessed.

UseMetaFile enables parsing the .meta file to customize the metadata of any files accessed in the same directory as the .meta file.

The .meta file has the following format:

- Empty lines and lines starting with a '#' are ignored.

- All other lines must have the form <pattern>:<metadata>, where <pattern> is a file pattern and metadata is either a mimetype or a valid Gemini response line. Mimetypes starting with ';' are appended. Response lines have the form <2digitcode><space><metadata>.

func NotFoundHandler

func NotFoundHandler() Handler

NotFoundHandler returns a Handler that responds with 51 Not Found.

func RedirectHandler

func RedirectHandler(url string, code int) Handler

RedirectHandler returns a Handler that redirects to the given URL.

func StripPrefix

func StripPrefix(prefix string, h Handler) Handler

StripPrefix returns a handler that serves Gemini requests by removing the given prefix from the request URL's Path (and RawPath if set) and invoking the handler h. StripPrefix handles a request for a path that doesn't begin with prefix by replying with 51 Not Found. The prefix must match exactly: if the prefix in the request contains escaped characters the reply is also 51 Not Found.

type HandlerFunc

type HandlerFunc func(ResponseWriter, *Request)

HandlerFunc adapts a function to the Handler interface.

func (HandlerFunc) ServeGemini

func (f HandlerFunc) ServeGemini(w ResponseWriter, r *Request)

ServeGemini implements Handler.

type Host

type Host struct {
	// Addr is the domain:port of the remote host.
	Addr string

	// Algorithm is the algorithm used to compute the fingerprint.
	Algorithm string

	// Fingerprint is a hash of the host's certificate.
	Fingerprint string

	// NotAfter is the expiry time of the certificate.
	NotAfter time.Time
}

Host is an entry in HostsFile.

type HostsFile

type HostsFile struct {
	// contains filtered or unexported fields
}

HostsFile implements the Trust-On-First-Use (TOFU) mechanism by maintaining a set of known hosts in an append-only hostsfile.

HostsFile is intended to be used with Client by setting the TrustCertificate field. TrustCertificate applies the TOFU algorithm and updates the hostsfile as needed.

The hostsfile is append-only but HostsFile only stores the latest entries in memory. Older entries are retained for auditing purposes.

HostsFile is safe to use concurrently.

File Format

Each line in the hostsfile is an entry. An entry consists of four fields separated by spaces and delimited by a newline:

address<SPACE>algorithm<SPACE>fingerprint<SPACE>expiry<LF>

  • address is the domain:port of the remote host.
  • port is the port number of the remote host.
  • algorithm is the hashing algorithm used to compute the fingerprint.
  • fingerprint is the base64 encoding of the hash of the certificate's Subject Public Key Info (SPKI) section.
  • expiry is the expiration date of the certificate.

Later entries overwrite older entries. Lines that do not conform to this format are ignored.

func NewHostsFile

func NewHostsFile(w io.Writer) *HostsFile

NewHostsFile returns a new HostsFile.

New entries are written to w and flushed if w implements `Flush() error`.

func OpenHostsFile

func OpenHostsFile(name string) (*HostsFile, *os.File, error)

OpenHostsFile is a shorthand for opening and reading a hostsfile. The file is opened in append mode and is created if it does not exist yet. The callee is responsible for calling os/File.Close to close the file.

func (*HostsFile) Host

func (hf *HostsFile) Host(addr string) (h Host, exists bool)

Host returns the Host associated with the domain:port address.

func (*HostsFile) ReadFrom

func (hf *HostsFile) ReadFrom(r io.Reader) (n int64, err error)

ReadFrom parses a hostsfile and stores the entries in memory. Later entries overwrite earlier ones.

func (*HostsFile) SetHost

func (hf *HostsFile) SetHost(h Host) error

SetHost sets the host entry and writes it to the Writer set by NewHostsFile.

func (*HostsFile) TrustCertificate

func (hf *HostsFile) TrustCertificate(cert *x509.Certificate, addr string) error

TrustCertificate applies the Trust On First Use algorithm to the given certificate and remote host address.

type Logger

type Logger interface {
	Printf(format string, v ...any)
}

Logger provides a simple interface for the Server to log to.

type RedirectError

type RedirectError struct {
	// LastURL is the last URL that the client was redirected from.
	LastURL string

	// NextURL is the next URL that the client was redirected to.
	NextURL string
}

RedirectError is returned by Client.Do if the maximum number of redirects has been exceeded.

func (RedirectError) Error

func (err RedirectError) Error() string

Error implements the error interface.

type Request

type Request struct {
	// URL is the url requested by the client.
	URL *url.URL

	// RequestURI is set by Server and holds the raw URL requested by the client.
	RequestURI string

	// RemoteAddr is set by Server and holds the remote address of the client.
	RemoteAddr string

	// Host is the Server Name Indication (SNI) passed by the client.
	// It is automatically set by Server when it receives a request.
	// It must be set manually to use SNI in Client requests,
	// otherwise it defaults to URL.Host.
	Host string

	// TLS holds the basic TLS connection details.
	TLS *tls.ConnectionState
	// contains filtered or unexported fields
}

Request represents a request that has been received by the server.

func NewRequest

func NewRequest(rawURL string) (*Request, error)

NewRequest creates a new request with the default context.

func NewRequestWithContext

func NewRequestWithContext(ctx context.Context, rawURL string) (*Request, error)

NewRequestWithContext creates a new request with a context.

func (*Request) Context

func (r *Request) Context() context.Context

Context returns the request context.

func (*Request) GetInput

func (r *Request) GetInput() (string, bool)

GetInput returns the unescaped query string.

type Response

type Response struct {
	// URL is the absolute URL that sent the response.
	// It can be different from the request URL if the request was redirected.
	URL *url.URL

	// StatusCode is the response code.
	StatusCode int

	// Meta is the response metadata.
	// It is interpreted differently depending on the status code.
	Meta string

	// Body is the request body.
	// It is never nil and must be Closed.
	Body io.ReadCloser

	// TLS holds the basic TLS connection details.
	TLS *tls.ConnectionState
}

Response is the response received from a server.

type ResponseWriter

type ResponseWriter interface {
	io.Writer
	WriteHeader(statusCode int, meta string)
}

ResponseWriter is used to construct the response.

WriteHeader sets the response header. It is not actually written until the first call to Write. The header will not be written if statusCode is set to a value lower than 10. This can be used to create CGI handlers that write the header manually.

type ServeMux

type ServeMux struct {
	// contains filtered or unexported fields
}

ServeMux is an Gemini request multiplexer. It matches the URL of each incoming request against a list of registered patterns and calls the handler for the pattern that most closely matches the URL.

It functions just like http.ServeMux.

func NewServeMux

func NewServeMux() *ServeMux

NewServeMux returns a fresh ServeMux.

func (*ServeMux) Handle

func (mux *ServeMux) Handle(pattern string, handler Handler)

Handle registers the handler for the given pattern. If a handler already exists for pattern, Handle panics.

func (*ServeMux) HandleFunc

func (mux *ServeMux) HandleFunc(pattern string, handler HandlerFunc)

HandleFunc registers the handler function for the given pattern.

func (*ServeMux) Handler

func (mux *ServeMux) Handler(r *Request) (handler Handler, pattern string)

Handler returns the handler to use for the given request, consulting r.Host and r.URL.Path. It always returns a non-nil handler. If the path is not in its canonical form, the handler will be an internally-generated handler that redirects to the canonical path. If the host contains a port, it is ignored when matching handlers.

Handler also returns the registered pattern that matches the request or, in the case of internally-generated redirects, the pattern that will match after following the redirect.

If there is no registered handler that applies to the request, Handler returns the handler set by NotFound.

func (*ServeMux) Mount

func (mux *ServeMux) Mount(pattern string, handler Handler)

Mount attaches a handler as a subrouter along a routing path. The prefixed pattern is stripped from the route.

func (*ServeMux) NotFound

func (mux *ServeMux) NotFound(h HandlerFunc)

NotFound sets the handler to use when a requested resource is not found. It defaults to the NotFound function.

func (*ServeMux) Route

func (mux *ServeMux) Route(pattern string, fn func(*ServeMux))

Route creates a fresh ServeMux and attaches it along the routing path.

func (*ServeMux) ServeGemini

func (mux *ServeMux) ServeGemini(w ResponseWriter, r *Request)

ServeGemini implements Handler.

type Server

type Server struct {
	// Addr is the address to listen on.
	// Defaults to :1965 if empty.
	Addr string

	// Handler is invoked to handle all requests.
	Handler Handler

	// Logger logs various diagnostics if it is not nil.
	Logger Logger

	// TLSConfig configures the TLS.
	TLSConfig *tls.Config

	// ReadTimeout sets the maximum duration for reading an incoming request.
	ReadTimeout time.Duration

	// WriteTimeout sets the maximum duration before
	// timing out on writing an outgoing response.
	WriteTimeout time.Duration

	// Insecure disables TLS.
	// It should only be set if the server is behind a reverse proxy.
	// Insecure servers do not support Server Name Indication (SNI).
	Insecure bool
}

Server defines parameters for running a Gemini server.

The zero value for Server is not a valid configuration. The TLSConfig must be set and must contain at least one certificate or non-nil GetCertificate.

func (*Server) ListenAndServe

func (srv *Server) ListenAndServe(ctx context.Context) error

ListenAndServe starts the server loop. The server loop ends when the passed context is cancelled.

func (*Server) Serve

func (srv *Server) Serve(ctx context.Context, l net.Listener) error

Serve starts the server loop and listens on a custom listener. The server loop ends when the passed context is cancelled.

Directories

Path Synopsis
cmd
Package gemcert provides utilities for creating certificates.
Package gemcert provides utilities for creating certificates.
Package gemtest contains utilities for writing tests.
Package gemtest contains utilities for writing tests.
Package gemtext contains utilities for handling gemtext files.
Package gemtext contains utilities for handling gemtext files.
internal

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL