gemini

package module
v0.1.1-0...-405819b Latest Latest
Warning

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

Go to latest
Published: Oct 26, 2020 License: BSD-3-Clause, MIT Imports: 28 Imported by: 0

README

go-gemini

GoDoc

Package gemini implements the Gemini protocol in Go.

It aims to provide an API similar to that of net/http to make it easy to develop Gemini clients and servers.

Usage

import "git.sr.ht/~adnano/go-gemini"

Examples

There are a few examples provided in the examples directory. To run an example:

go run examples/server.go

Contributing

Send patches and questions to ~adnano/go-gemini-devel.

Subscribe to release announcements on ~adnano/go-gemini-announce.

Documentation

Overview

Package gemini implements the Gemini protocol.

Send makes a Gemini request with the default client:

req := gemini.NewRequest("gemini://example.com")
resp, err := gemini.Send(req)
if err != nil {
	// handle error
}
// ...

For control over client behavior, create a custom Client:

var client gemini.Client
resp, err := client.Send(req)
if err != nil {
	// handle error
}
// ...

The default client loads known hosts from "$XDG_DATA_HOME/gemini/known_hosts". Custom clients can load their own list of known hosts:

err := client.KnownHosts.Load("path/to/my/known_hosts")
if err != nil {
	// handle error
}

Clients can control when to trust certificates with TrustCertificate:

client.TrustCertificate = func(hostname string, cert *x509.Certificate, knownHosts *gemini.KnownHosts) error {
	return knownHosts.Lookup(hostname, cert)
}

If a server responds with StatusCertificateRequired, the default client will generate a certificate and resend the request with it. Custom clients can do so in GetCertificate:

client.GetCertificate = func(hostname string, store *gemini.CertificateStore) *tls.Certificate {
	// If the certificate is in the store, return it
	if cert, err := store.Lookup(hostname); err == nil {
		return &cert
	}
	// Otherwise, generate a certificate
	duration := time.Hour
	cert, err := gemini.NewCertificate(hostname, duration)
	if err != nil {
		return nil
	}
	// Store and return the certificate
	store.Add(hostname, cert)
	return &cert
}

Server is a Gemini server.

var server gemini.Server

Servers must be configured with certificates:

err := server.CertificateStore.Load("/var/lib/gemini/certs")
if err != nil {
	// handle error
}

Servers can accept requests for multiple hosts and schemes:

server.RegisterFunc("example.com", func(w *gemini.ResponseWriter, r *gemini.Request) {
	fmt.Fprint(w, "Welcome to example.com")
})
server.RegisterFunc("example.org", func(w *gemini.ResponseWriter, r *gemini.Request) {
	fmt.Fprint(w, "Welcome to example.org")
})
server.RegisterFunc("http://example.net", func(w *gemini.ResponseWriter, r *gemini.Request) {
	fmt.Fprint(w, "Proxied content from http://example.net")
})

To start the server, call ListenAndServe:

err := server.ListenAndServe()
if err != nil {
	// handle error
}

Index

Constants

View Source
const (
	StatusInput                    = 10
	StatusSensitiveInput           = 11
	StatusSuccess                  = 20
	StatusRedirect                 = 30
	StatusRedirectPermanent        = 31
	StatusTemporaryFailure         = 40
	StatusServerUnavailable        = 41
	StatusCGIError                 = 42
	StatusProxyError               = 43
	StatusSlowDown                 = 44
	StatusPermanentFailure         = 50
	StatusNotFound                 = 51
	StatusGone                     = 52
	StatusProxyRequestRefused      = 53
	StatusBadRequest               = 59
	StatusCertificateRequired      = 60
	StatusCertificateNotAuthorized = 61
	StatusCertificateNotValid      = 62
)

Status codes.

View Source
const (
	StatusClassInput               = 1
	StatusClassSuccess             = 2
	StatusClassRedirect            = 3
	StatusClassTemporaryFailure    = 4
	StatusClassPermanentFailure    = 5
	StatusClassCertificateRequired = 6
)

Status code categories.

Variables

View Source
var (
	ErrInvalidURL            = errors.New("gemini: invalid URL")
	ErrInvalidResponse       = errors.New("gemini: invalid response")
	ErrCertificateUnknown    = errors.New("gemini: unknown certificate")
	ErrCertificateExpired    = errors.New("gemini: certificate expired")
	ErrCertificateNotTrusted = errors.New("gemini: certificate is not trusted")
	ErrNotAFile              = errors.New("gemini: not a file")
	ErrBodyNotAllowed        = errors.New("gemini: response status code does not allow for body")
)

Errors.

Functions

func Certificate

func Certificate(w *ResponseWriter, r *Request) (*x509.Certificate, bool)

Certificate returns the request certificate. If one is not provided, it returns nil and responds with StatusCertificateRequired.

func CertificateNotAuthorized

func CertificateNotAuthorized(w *ResponseWriter, r *Request)

CertificateNotAuthorized responds to the request with the CertificateNotAuthorized status code.

func CertificateRequired

func CertificateRequired(w *ResponseWriter, r *Request)

CertificateRequired responds to the request with the CertificateRequired status code.

func Fingerprint

func Fingerprint(cert *x509.Certificate) string

Fingerprint returns the SHA-512 fingerprint of the provided certificate.

func Gone

func Gone(w *ResponseWriter, r *Request)

Gone replies to the request with the Gone status code.

func Input

func Input(w *ResponseWriter, r *Request, prompt string) (string, bool)

Input returns the request query. If no input is provided, it responds with StatusInput.

func NewCertificate

func NewCertificate(host string, duration time.Duration) (tls.Certificate, error)

NewCertificate creates and returns a new parsed certificate.

func NotFound

func NotFound(w *ResponseWriter, r *Request)

NotFound replies to the request with the NotFound status code.

func PermanentRedirect

func PermanentRedirect(w *ResponseWriter, r *Request, url string)

PermanentRedirect replies to the request with a permanent redirect to the given URL.

func Redirect

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

Redirect replies to the request with a redirect to the given URL.

func SensitiveInput

func SensitiveInput(w *ResponseWriter, r *Request, prompt string) (string, bool)

SensitiveInput returns the request query. If no input is provided, it responds with StatusSensitiveInput.

Types

type CertificateStore

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

CertificateStore maps hostnames to certificates. The zero value of CertificateStore is an empty store ready to use.

func (*CertificateStore) Add

func (c *CertificateStore) Add(hostname string, cert tls.Certificate)

Add adds a certificate for the given hostname to the store. It tries to parse the certificate if it is not already parsed.

func (*CertificateStore) Load

func (c *CertificateStore) Load(path string) error

Load loads certificates from the given path. The path should lead to a directory containing certificates and private keys in the form hostname.crt and hostname.key. For example, the hostname "localhost" would have the corresponding files localhost.crt (certificate) and localhost.key (private key).

func (*CertificateStore) Lookup

func (c *CertificateStore) Lookup(hostname string) (*tls.Certificate, error)

Lookup returns the certificate for the given hostname.

type Client

type Client struct {
	// KnownHosts is a list of known hosts that the client trusts.
	KnownHosts KnownHosts

	// CertificateStore maps hostnames to certificates.
	// It is used to determine which certificate to use when the server requests
	// a certificate.
	CertificateStore CertificateStore

	// GetCertificate, if not nil, will be called when a server requests a certificate.
	// The returned certificate will be used when sending the request again.
	// If the certificate is nil, the request will not be sent again and
	// the response will be returned.
	GetCertificate func(hostname string, store *CertificateStore) *tls.Certificate

	// TrustCertificate, if not nil, will be called to determine whether the
	// client should trust the given certificate.
	// If error is not nil, the connection will be aborted.
	TrustCertificate func(hostname string, cert *x509.Certificate, knownHosts *KnownHosts) error
}

Client represents a Gemini client.

var DefaultClient Client

DefaultClient is the default client. It is used by Send.

On the first request, DefaultClient will load the default list of known hosts.

func (*Client) Send

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

Send sends a Gemini request and returns a Gemini response.

type Dir

type Dir string

Dir implements FS using the native filesystem restricted to a specific directory.

func (Dir) Open

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

Open tries to open the file with the given name. If the file is a directory, it tries to open the index file in that directory.

type FS

type FS interface {
	Open(name string) (File, error)
}

TODO: replace with io/fs.FS when available

type File

type File interface {
	Stat() (os.FileInfo, error)
	Read([]byte) (int, error)
	Close() error
}

TODO: replace with io/fs.File when available

type KnownHosts

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

KnownHosts represents a list of known hosts. The zero value for KnownHosts is an empty list ready to use.

func (*KnownHosts) Add

func (k *KnownHosts) Add(hostname string, cert *x509.Certificate)

Add adds a certificate to the list of known hosts. If KnownHosts was loaded from a file, Add will append to the file.

func (*KnownHosts) AddTemporary

func (k *KnownHosts) AddTemporary(hostname string, cert *x509.Certificate)

AddTemporary adds a certificate to the list of known hosts without writing it to the known hosts file.

func (*KnownHosts) Load

func (k *KnownHosts) Load(path string) error

Load loads the known hosts from the provided path. It creates the path and any of its parent directories if they do not exist. KnownHosts will append to the file whenever a certificate is added.

func (*KnownHosts) LoadDefault

func (k *KnownHosts) LoadDefault() error

LoadDefault loads the known hosts from the default known hosts path, which is $XDG_DATA_HOME/gemini/known_hosts. It creates the path and any of its parent directories if they do not exist. KnownHosts will append to the file whenever a certificate is added.

func (*KnownHosts) Lookup

func (k *KnownHosts) Lookup(hostname string, cert *x509.Certificate) error

Lookup looks for the provided certificate in the list of known hosts. If the hostname is in the list, but the fingerprint differs, Lookup returns ErrCertificateNotTrusted. If the hostname is not in the list, Lookup returns ErrCertificateUnknown. If the certificate is found and the fingerprint matches, error will be nil.

func (*KnownHosts) Parse

func (k *KnownHosts) Parse(r io.Reader)

Parse parses the provided reader and adds the parsed known hosts to the list. Invalid lines are ignored.

func (*KnownHosts) Write

func (k *KnownHosts) Write(w io.Writer)

Write writes the known hosts to the provided io.Writer.

type Line

type Line interface {
	String() string
	// contains filtered or unexported methods
}

Line represents a line of a Gemini text response.

type LineHeading1

type LineHeading1 string

A first-level heading line.

func (LineHeading1) String

func (l LineHeading1) String() string

type LineHeading2

type LineHeading2 string

A second-level heading line.

func (LineHeading2) String

func (l LineHeading2) String() string

type LineHeading3

type LineHeading3 string

A third-level heading line.

func (LineHeading3) String

func (l LineHeading3) String() string

type LineImage

type LineImage struct {
	URL string
	Alt string
}

An image line.

func (LineImage) String

func (l LineImage) String() string
type LineLink struct {
	URL  string
	Name string
}

A link line.

func (LineLink) String

func (l LineLink) String() string

type LineListItem

type LineListItem string

An unordered list item line.

func (LineListItem) String

func (l LineListItem) String() string

type LinePreformattedText

type LinePreformattedText string

A preformatted text line.

func (LinePreformattedText) String

func (l LinePreformattedText) String() string

type LinePreformattingToggle

type LinePreformattingToggle string

A preformatting toggle line.

func (LinePreformattingToggle) String

func (l LinePreformattingToggle) String() string

type LineQuote

type LineQuote string

A quote line.

func (LineQuote) String

func (l LineQuote) String() string

type LineText

type LineText string

A text line.

func (LineText) String

func (l LineText) String() string

type Request

type Request struct {
	// URL specifies the URL being requested.
	URL *url.URL

	// For client requests, Host specifies the host on which the URL is sought.
	// Host must contain a port.
	// This field is ignored by the server.
	Host string

	// Certificate specifies the TLS certificate to use for the request.
	// Request certificates take precedence over client certificates.
	// This field is ignored by the server.
	Certificate *tls.Certificate

	// RemoteAddr allows servers and other software to record the network
	// address that sent the request.
	// This field is ignored by the client.
	RemoteAddr net.Addr

	// TLS allows servers and other software to record information about the TLS
	// connection on which the request was received.
	// This field is ignored by the client.
	TLS tls.ConnectionState
}

Request represents a Gemini request.

func NewRequest

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

NewRequest returns a new request. The host is inferred from the provided URL.

func NewRequestTo

func NewRequestTo(rawurl, host string) (*Request, error)

NewRequestTo returns a new request for the provided URL to the provided host. The host must contain a port.

type Responder

type Responder interface {
	// Respond accepts a Request and constructs a Response.
	Respond(*ResponseWriter, *Request)
}

A Responder responds to a Gemini request.

func FileServer

func FileServer(fsys FS) Responder

FileServer takes a filesystem and returns a Responder which uses that filesystem. The returned Responder sanitizes paths before handling them.

type ResponderFunc

type ResponderFunc func(*ResponseWriter, *Request)

ResponderFunc is a wrapper around a bare function that implements Responder.

func (ResponderFunc) Respond

func (f ResponderFunc) Respond(w *ResponseWriter, r *Request)

type Response

type Response struct {
	// Status represents the response status.
	Status int

	// Meta contains more information related to the response status.
	// For successful responses, Meta should contain the mimetype of the response.
	// For failure responses, Meta should contain a short description of the failure.
	// Meta should not be longer than 1024 bytes.
	Meta string

	// Body contains the response body.
	Body []byte

	// TLS contains information about the TLS connection on which the response
	// was received.
	TLS tls.ConnectionState
}

Response is a Gemini response.

func Send

func Send(req *Request) (*Response, error)

Send sends a Gemini request and returns a Gemini response.

Send is a wrapper around DefaultClient.Send.

type ResponseWriter

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

ResponseWriter is used by a Gemini handler to construct a Gemini response.

func (*ResponseWriter) SetMimetype

func (w *ResponseWriter) SetMimetype(mimetype string)

SetMimetype sets the mimetype that will be written for a successful response. The provided mimetype will only be used if Write is called without calling WriteHeader. If the mimetype is not set, it will default to "text/gemini".

func (*ResponseWriter) Write

func (w *ResponseWriter) Write(b []byte) (int, error)

Write writes the response body. If the response status does not allow for a response body, Write returns ErrBodyNotAllowed.

If WriteHeader has not yet been called, Write calls WriteHeader(StatusSuccess, mimetype) where mimetype is the mimetype set in SetMimetype. If no mimetype is set, a default of "text/gemini" will be used.

func (*ResponseWriter) WriteHeader

func (w *ResponseWriter) WriteHeader(status int, meta string)

WriteHeader writes the response header. If the header has already been written, WriteHeader does nothing.

Meta contains more information related to the response status. For successful responses, Meta should contain the mimetype of the response. For failure responses, Meta should contain a short description of the failure. Meta should not be longer than 1024 bytes.

type ServeMux

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

ServeMux is a 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.

Patterns name fixed, rooted paths, like "/favicon.ico", or rooted subtrees, like "/images/" (note the trailing slash). Longer patterns take precedence over shorter ones, so that if there are handlers registered for both "/images/" and "/images/thumbnails/", the latter handler will be called for paths beginning "/images/thumbnails/" and the former will receive requests for any other paths in the "/images/" subtree.

Note that since a pattern ending in a slash names a rooted subtree, the pattern "/" matches all paths not matched by other registered patterns, not just the URL with Path == "/".

If a subtree has been registered and a request is received naming the subtree root without its trailing slash, ServeMux redirects that request to the subtree root (adding the trailing slash). This behavior can be overridden with a separate registration for the path without the trailing slash. For example, registering "/images/" causes ServeMux to redirect a request for "/images" to "/images/", unless "/images" has been registered separately.

ServeMux also takes care of sanitizing the URL request path and redirecting any request containing . or .. elements or repeated slashes to an equivalent, cleaner URL.

func (*ServeMux) Handle

func (mux *ServeMux) Handle(pattern string, responder Responder)

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

func (*ServeMux) HandleFunc

func (mux *ServeMux) HandleFunc(pattern string, responder func(*ResponseWriter, *Request))

HandleFunc registers the responder function for the given pattern.

func (*ServeMux) Respond

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

Respond dispatches the request to the responder whose pattern most closely matches the request URL.

type Server

type Server struct {
	// Addr specifies the address that the server should listen on.
	// If Addr is empty, the server will listen on the address ":1965".
	Addr string

	// CertificateStore contains the certificates used by the server.
	CertificateStore CertificateStore

	// GetCertificate, if not nil, will be called to retrieve the certificate
	// to use for a given hostname.
	// If the certificate is nil, the connection will be aborted.
	GetCertificate func(hostname string, store *CertificateStore) *tls.Certificate
	// contains filtered or unexported fields
}

Server is a Gemini server.

func (*Server) ListenAndServe

func (s *Server) ListenAndServe() error

ListenAndServe listens for requests at the server's configured address.

func (*Server) Register

func (s *Server) Register(pattern string, responder Responder)

Register registers a responder for the given pattern. Patterns must be in the form of scheme://hostname (e.g. gemini://example.com). If no scheme is specified, a default scheme of gemini:// is assumed. Wildcard patterns are supported (e.g. *.example.com).

func (*Server) RegisterFunc

func (s *Server) RegisterFunc(pattern string, responder func(*ResponseWriter, *Request))

RegisterFunc registers a responder function for the given pattern.

func (*Server) Serve

func (s *Server) Serve(l net.Listener) error

Serve listens for requests on the provided listener.

type Text

type Text []Line

Text represents a Gemini text response.

func Parse

func Parse(r io.Reader) Text

Parse parses Gemini text from the provided io.Reader.

func (Text) HTML

func (t Text) HTML() string

HTML returns the Gemini text response as HTML.

func (Text) String

func (t Text) String() string

String writes the Gemini text response to a string and returns it.

Jump to

Keyboard shortcuts

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