diff options
| author | adnano <[email protected]> | 2020-09-25 19:07:40 -0400 |
|---|---|---|
| committer | adnano <[email protected]> | 2020-09-25 19:07:40 -0400 |
| commit | d36131356d308285a587a51cdd9be400454cb769 (patch) | |
| tree | eb16ac4f5dab63a516ac8a443bad1648706ea668 /gemini.go | |
| parent | Move server code to its own file (diff) | |
| download | go-gemini-d36131356d308285a587a51cdd9be400454cb769.tar.xz go-gemini-d36131356d308285a587a51cdd9be400454cb769.zip | |
Rename gemini.go to client.go
Diffstat (limited to 'gemini.go')
| -rw-r--r-- | gemini.go | 236 |
1 files changed, 0 insertions, 236 deletions
diff --git a/gemini.go b/gemini.go deleted file mode 100644 index 941969d..0000000 --- a/gemini.go +++ /dev/null @@ -1,236 +0,0 @@ -// Package gemini implements the Gemini protocol. -package gemini - -import ( - "bufio" - "crypto/tls" - "errors" - "io/ioutil" - "net" - "net/url" - "strconv" -) - -// Status codes. -const ( - StatusInput = 10 - StatusSensitiveInput = 11 - StatusSuccess = 20 - StatusRedirectTemporary = 30 - StatusRedirectPermanent = 31 - StatusTemporaryFailure = 40 - StatusServerUnavailable = 41 - StatusCGIError = 42 - StatusProxyError = 43 - StatusSlowDown = 44 - StatusPermanentFailure = 50 - StatusNotFound = 51 - StatusGone = 52 - StatusProxyRequestRefused = 53 - StatusBadRequest = 59 - StatusClientCertificateRequired = 60 - StatusCertificateNotAuthorised = 61 - StatusCertificateNotValid = 62 -) - -// Status code categories. -const ( - StatusClassInput = 1 - StatusClassSuccess = 2 - StatusClassRedirect = 3 - StatusClassTemporaryFailure = 4 - StatusClassPermanentFailure = 5 - StatusClassClientCertificateRequired = 6 -) - -// Errors. -var ( - ErrProtocol = errors.New("gemini: protocol error") - ErrInvalidURL = errors.New("gemini: requested URL is invalid") - ErrBodyNotAllowed = errors.New("gemini: response status code does not allow for body") -) - -var ( - crlf = []byte("\r\n") -) - -// Request is a Gemini request. -// -// A Request can optionally be configured with a client certificate. Example: -// -// req := NewRequest(url) -// cert, err := tls.LoadX509KeyPair("client.crt", "client.key") -// if err != nil { -// panic(err) -// } -// req.TLSConfig.Certificates = append(req.TLSConfig.Certificates, cert) -// -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. - // If this field is empty, the host will be inferred from the URL. - // This field is ignored by the server. - Host string - - // The certificate to use for the request. - 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 recieved. - // This field is ignored by the client. - TLS tls.ConnectionState -} - -// NewRequest returns a new request. The host is inferred from the provided url. -func NewRequest(rawurl string) (*Request, error) { - u, err := url.Parse(rawurl) - if err != nil { - return nil, err - } - - host := u.Host - - // If there is no port, use the default port of 1965 - if u.Port() == "" { - host += ":1965" - } - - return &Request{ - Host: host, - URL: u, - }, nil -} - -// NewProxyRequest returns a new request using the provided host. -// The provided host must contain a port. -func NewProxyRequest(host, rawurl string) (*Request, error) { - u, err := url.Parse(rawurl) - if err != nil { - return nil, err - } - - return &Request{ - Host: host, - URL: u, - }, nil -} - -// write writes the Gemini request to the provided buffered writer. -func (r *Request) write(w *bufio.Writer) error { - url := r.URL.String() - // User is invalid - if r.URL.User != nil || len(url) > 1024 { - return ErrInvalidURL - } - if _, err := w.WriteString(url); err != nil { - return err - } - if _, err := w.Write(crlf); err != nil { - return err - } - return nil -} - -// Response is a Gemini 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 -} - -// Do sends a Gemini request and returns a Gemini response. -func Do(req *Request) (*Response, error) { - // Connect to the host - config := &tls.Config{ - InsecureSkipVerify: true, - Certificates: []tls.Certificate{req.Certificate}, - } - conn, err := tls.Dial("tcp", req.Host, config) - if err != nil { - return nil, err - } - defer conn.Close() - - // Write the request - // TODO: Is buffered I/O necessary here? - w := bufio.NewWriter(conn) - req.write(w) - if err := w.Flush(); err != nil { - return nil, err - } - - // Read the response status - r := bufio.NewReader(conn) - statusB := make([]byte, 2) - if _, err := r.Read(statusB); err != nil { - return nil, err - } - status, err := strconv.Atoi(string(statusB)) - if err != nil { - return nil, err - } - - // Read one space - if b, err := r.ReadByte(); err != nil { - return nil, err - } else if b != ' ' { - return nil, ErrProtocol - } - - // Read the meta - meta, err := r.ReadString('\r') - if err != nil { - return nil, err - } - - // Read terminating newline - if b, err := r.ReadByte(); err != nil { - return nil, err - } else if b != '\n' { - return nil, ErrProtocol - } - - // Trim carriage return - meta = meta[:len(meta)-1] - - // Ensure meta is less than or equal to 1024 bytes - if len(meta) > 1024 { - return nil, ErrProtocol - } - - // Read response body - var body []byte - if status/10 == StatusClassSuccess { - var err error - body, err = ioutil.ReadAll(r) - if err != nil { - return nil, err - } - } - - return &Response{ - Status: status, - Meta: meta, - Body: body, - TLS: conn.ConnectionState(), - }, nil -} |