2020-10-24 13:15:32 -06:00
|
|
|
package gemini
|
2020-10-21 15:07:28 -06:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
2021-02-23 14:36:17 -07:00
|
|
|
"crypto/tls"
|
2020-10-27 17:16:55 -06:00
|
|
|
"io"
|
2021-02-23 14:36:17 -07:00
|
|
|
"net"
|
2020-10-21 15:07:28 -06:00
|
|
|
"strconv"
|
|
|
|
)
|
|
|
|
|
2021-02-17 11:36:16 -07:00
|
|
|
// The default media type for responses.
|
|
|
|
const defaultMediaType = "text/gemini; charset=utf-8"
|
|
|
|
|
2021-02-14 14:23:38 -07:00
|
|
|
// Response represents the response from a Gemini request.
|
|
|
|
//
|
|
|
|
// The Client returns Responses from servers once the response
|
|
|
|
// header has been received. The response body is streamed on demand
|
2021-02-23 15:50:47 -07:00
|
|
|
// as the response is read. If the network connection fails or the server
|
|
|
|
// terminates the response, Read calls return an error.
|
|
|
|
//
|
|
|
|
// It is the caller's responsibility to close the response.
|
2020-10-21 15:07:28 -06:00
|
|
|
type Response struct {
|
2020-10-31 18:32:38 -06:00
|
|
|
// Status contains the response status code.
|
2021-02-20 14:15:26 -07:00
|
|
|
Status Status
|
2020-10-21 15:07:28 -06:00
|
|
|
|
|
|
|
// Meta contains more information related to the response status.
|
2021-01-09 23:07:38 -07:00
|
|
|
// For successful responses, Meta should contain the media type of the response.
|
2020-10-21 15:07:28 -06:00
|
|
|
// For failure responses, Meta should contain a short description of the failure.
|
|
|
|
// Meta should not be longer than 1024 bytes.
|
|
|
|
Meta string
|
|
|
|
|
2021-02-23 15:50:47 -07:00
|
|
|
body io.ReadCloser
|
2021-02-23 14:36:17 -07:00
|
|
|
conn net.Conn
|
2020-10-21 15:07:28 -06:00
|
|
|
}
|
|
|
|
|
2020-12-17 23:41:14 -07:00
|
|
|
// ReadResponse reads a Gemini response from the provided io.ReadCloser.
|
|
|
|
func ReadResponse(rc io.ReadCloser) (*Response, error) {
|
|
|
|
resp := &Response{}
|
2020-10-27 17:16:55 -06:00
|
|
|
br := bufio.NewReader(rc)
|
2020-12-17 23:41:14 -07:00
|
|
|
|
2020-10-21 15:07:28 -06:00
|
|
|
// Read the status
|
|
|
|
statusB := make([]byte, 2)
|
2020-10-27 17:16:55 -06:00
|
|
|
if _, err := br.Read(statusB); err != nil {
|
2020-12-17 23:41:14 -07:00
|
|
|
return nil, err
|
2020-10-21 15:07:28 -06:00
|
|
|
}
|
|
|
|
status, err := strconv.Atoi(string(statusB))
|
|
|
|
if err != nil {
|
2021-02-15 17:18:21 -07:00
|
|
|
return nil, ErrInvalidResponse
|
2020-10-21 15:07:28 -06:00
|
|
|
}
|
2021-02-20 14:15:26 -07:00
|
|
|
resp.Status = Status(status)
|
2020-10-21 15:07:28 -06:00
|
|
|
|
|
|
|
// Read one space
|
2020-10-27 17:16:55 -06:00
|
|
|
if b, err := br.ReadByte(); err != nil {
|
2020-12-17 23:41:14 -07:00
|
|
|
return nil, err
|
2020-10-21 15:07:28 -06:00
|
|
|
} else if b != ' ' {
|
2020-12-17 23:41:14 -07:00
|
|
|
return nil, ErrInvalidResponse
|
2020-10-21 15:07:28 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// Read the meta
|
2020-10-27 17:16:55 -06:00
|
|
|
meta, err := br.ReadString('\r')
|
2020-10-21 15:07:28 -06:00
|
|
|
if err != nil {
|
2020-12-17 23:41:14 -07:00
|
|
|
return nil, err
|
2020-10-21 15:07:28 -06:00
|
|
|
}
|
|
|
|
// Trim carriage return
|
|
|
|
meta = meta[:len(meta)-1]
|
|
|
|
// Ensure meta is less than or equal to 1024 bytes
|
|
|
|
if len(meta) > 1024 {
|
2020-12-17 23:41:14 -07:00
|
|
|
return nil, ErrInvalidResponse
|
2020-10-21 15:07:28 -06:00
|
|
|
}
|
2021-02-20 14:15:26 -07:00
|
|
|
if resp.Status.Class() == StatusSuccess && meta == "" {
|
2021-02-17 11:36:16 -07:00
|
|
|
// Use default media type
|
|
|
|
meta = defaultMediaType
|
2020-10-31 18:32:38 -06:00
|
|
|
}
|
2020-10-21 15:07:28 -06:00
|
|
|
resp.Meta = meta
|
|
|
|
|
|
|
|
// Read terminating newline
|
2020-10-27 17:16:55 -06:00
|
|
|
if b, err := br.ReadByte(); err != nil {
|
2020-12-17 23:41:14 -07:00
|
|
|
return nil, err
|
2020-10-21 15:07:28 -06:00
|
|
|
} else if b != '\n' {
|
2020-12-17 23:41:14 -07:00
|
|
|
return nil, ErrInvalidResponse
|
2020-10-21 15:07:28 -06:00
|
|
|
}
|
|
|
|
|
2021-02-20 14:15:26 -07:00
|
|
|
if resp.Status.Class() == StatusSuccess {
|
2021-02-23 18:49:42 -07:00
|
|
|
resp.body = newBufReadCloser(br, rc)
|
2020-12-22 17:21:59 -07:00
|
|
|
} else {
|
2021-02-23 15:50:47 -07:00
|
|
|
resp.body = nopReadCloser{}
|
2020-12-22 17:21:59 -07:00
|
|
|
rc.Close()
|
2020-10-21 15:07:28 -06:00
|
|
|
}
|
2020-12-17 23:41:14 -07:00
|
|
|
return resp, nil
|
2020-10-21 15:07:28 -06:00
|
|
|
}
|
2020-10-27 17:16:55 -06:00
|
|
|
|
2021-02-23 15:50:47 -07:00
|
|
|
// Read reads data from the response body.
|
|
|
|
// The response body is streamed on demand as Read is called.
|
|
|
|
func (r *Response) Read(p []byte) (n int, err error) {
|
|
|
|
return r.body.Read(p)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close closes the response body.
|
|
|
|
func (r *Response) Close() error {
|
|
|
|
return r.body.Close()
|
2021-02-17 22:07:43 -07:00
|
|
|
}
|
|
|
|
|
2021-02-23 14:36:17 -07:00
|
|
|
// Conn returns the network connection on which the response was received.
|
|
|
|
func (r *Response) Conn() net.Conn {
|
|
|
|
return r.conn
|
|
|
|
}
|
|
|
|
|
|
|
|
// TLS returns information about the TLS connection on which the
|
|
|
|
// response was received.
|
|
|
|
func (r *Response) TLS() *tls.ConnectionState {
|
|
|
|
if tlsConn, ok := r.conn.(*tls.Conn); ok {
|
|
|
|
state := tlsConn.ConnectionState()
|
|
|
|
return &state
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-02-17 11:36:16 -07:00
|
|
|
// A ResponseWriter interface is used by a Gemini handler to construct
|
|
|
|
// a Gemini response.
|
|
|
|
//
|
|
|
|
// A ResponseWriter may not be used after the Handler.ServeGemini method
|
|
|
|
// has returned.
|
2021-02-23 18:40:22 -07:00
|
|
|
type ResponseWriter struct {
|
2021-01-09 22:50:35 -07:00
|
|
|
b *bufio.Writer
|
2021-02-23 15:32:23 -07:00
|
|
|
closer io.Closer
|
2021-02-17 11:36:16 -07:00
|
|
|
mediatype string
|
2021-01-09 22:50:35 -07:00
|
|
|
wroteHeader bool
|
|
|
|
bodyAllowed bool
|
|
|
|
}
|
|
|
|
|
2021-02-23 16:45:58 -07:00
|
|
|
// NewResponseWriter returns a ResponseWriter that uses the provided io.WriteCloser.
|
2021-02-23 18:40:22 -07:00
|
|
|
func NewResponseWriter(wc io.WriteCloser) *ResponseWriter {
|
|
|
|
return &ResponseWriter{
|
2021-02-23 15:32:23 -07:00
|
|
|
b: bufio.NewWriter(wc),
|
|
|
|
closer: wc,
|
2021-01-09 22:50:35 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-23 18:40:22 -07:00
|
|
|
// SetMediaType sets the media type that will be sent by Write for a
|
|
|
|
// successful response. If no media type is set, a default of
|
|
|
|
// "text/gemini; charset=utf-8" will be used.
|
|
|
|
//
|
|
|
|
// Setting the media type after a call to Write or WriteHeader has
|
|
|
|
// no effect.
|
|
|
|
func (w *ResponseWriter) SetMediaType(mediatype string) {
|
2021-02-17 11:36:16 -07:00
|
|
|
w.mediatype = mediatype
|
2021-01-09 22:50:35 -07:00
|
|
|
}
|
|
|
|
|
2021-02-23 18:40:22 -07:00
|
|
|
// Write writes the data to the connection as part of a Gemini response.
|
|
|
|
//
|
|
|
|
// If WriteHeader has not yet been called, Write calls WriteHeader with
|
|
|
|
// StatusSuccess and the media type set in SetMediaType before writing the data.
|
|
|
|
// If no media type was set, Write uses a default media type of
|
|
|
|
// "text/gemini; charset=utf-8".
|
|
|
|
func (w *ResponseWriter) Write(b []byte) (int, error) {
|
2021-01-09 22:50:35 -07:00
|
|
|
if !w.wroteHeader {
|
2021-02-17 11:36:16 -07:00
|
|
|
meta := w.mediatype
|
|
|
|
if meta == "" {
|
|
|
|
// Use default media type
|
|
|
|
meta = defaultMediaType
|
|
|
|
}
|
|
|
|
w.WriteHeader(StatusSuccess, meta)
|
2021-01-09 22:50:35 -07:00
|
|
|
}
|
|
|
|
if !w.bodyAllowed {
|
|
|
|
return 0, ErrBodyNotAllowed
|
|
|
|
}
|
|
|
|
return w.b.Write(b)
|
|
|
|
}
|
|
|
|
|
2021-02-23 18:40:22 -07:00
|
|
|
// WriteHeader sends a Gemini response header with the provided
|
|
|
|
// status code and meta.
|
|
|
|
//
|
|
|
|
// If WriteHeader is not called explicitly, the first call to Write
|
|
|
|
// will trigger an implicit call to WriteHeader with a successful
|
|
|
|
// status code and the media type set in SetMediaType.
|
|
|
|
//
|
|
|
|
// The provided code must be a valid Gemini status code.
|
|
|
|
// The provided meta must not be longer than 1024 bytes.
|
|
|
|
// Only one header may be written.
|
|
|
|
func (w *ResponseWriter) WriteHeader(status Status, meta string) {
|
2021-02-17 21:05:22 -07:00
|
|
|
if w.wroteHeader {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-02-20 14:15:26 -07:00
|
|
|
if status.Class() == StatusSuccess {
|
2021-01-09 22:50:35 -07:00
|
|
|
w.bodyAllowed = true
|
|
|
|
}
|
|
|
|
|
2021-02-20 14:15:26 -07:00
|
|
|
w.b.WriteString(strconv.Itoa(int(status)))
|
2021-01-09 22:50:35 -07:00
|
|
|
w.b.WriteByte(' ')
|
|
|
|
w.b.WriteString(meta)
|
|
|
|
w.b.Write(crlf)
|
|
|
|
w.wroteHeader = true
|
|
|
|
}
|
|
|
|
|
2021-02-23 18:40:22 -07:00
|
|
|
// Flush sends any buffered data to the client.
|
|
|
|
func (w *ResponseWriter) Flush() error {
|
2021-01-09 22:50:35 -07:00
|
|
|
if !w.wroteHeader {
|
2021-02-17 11:36:16 -07:00
|
|
|
w.WriteHeader(StatusTemporaryFailure, "Temporary failure")
|
2021-01-09 22:50:35 -07:00
|
|
|
}
|
2021-02-23 16:45:58 -07:00
|
|
|
// Write errors from WriteHeader will be returned here.
|
2021-01-09 22:50:35 -07:00
|
|
|
return w.b.Flush()
|
|
|
|
}
|
2021-02-23 15:32:23 -07:00
|
|
|
|
2021-02-23 18:40:22 -07:00
|
|
|
// Close closes the connection.
|
|
|
|
// Any blocked Write operations will be unblocked and return errors.
|
|
|
|
func (w *ResponseWriter) Close() error {
|
2021-02-23 15:32:23 -07:00
|
|
|
return w.closer.Close()
|
|
|
|
}
|