2020-10-24 13:15:32 -06:00
|
|
|
package gemini
|
2020-10-21 15:07:28 -06:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"crypto/tls"
|
2020-10-27 17:16:55 -06:00
|
|
|
"io"
|
2020-10-21 15:07:28 -06:00
|
|
|
"strconv"
|
|
|
|
)
|
|
|
|
|
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
|
|
|
|
// as the Body field is read.
|
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-09 07:41:36 -07:00
|
|
|
Status int
|
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-01-15 13:15:16 -07:00
|
|
|
// Body represents the response body.
|
|
|
|
//
|
2021-02-14 14:23:38 -07:00
|
|
|
// The response body is streamed on demand as the Body field
|
|
|
|
// is read. If the network connection fails or the server
|
|
|
|
// terminates the response, Body.Read calls return an error.
|
|
|
|
//
|
|
|
|
// The Gemini client guarantees that Body is always
|
|
|
|
// non-nil, even on responses without a body or responses with
|
|
|
|
// a zero-length body. It is the caller's responsibility to
|
|
|
|
// close Body.
|
2020-10-27 17:16:55 -06:00
|
|
|
Body io.ReadCloser
|
2020-10-21 15:07:28 -06:00
|
|
|
|
2021-02-14 14:23:38 -07:00
|
|
|
// TLS contains information about the TLS connection on which the
|
|
|
|
// response was received. It is nil for unencrypted responses.
|
2021-02-08 10:32:47 -07:00
|
|
|
TLS *tls.ConnectionState
|
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 {
|
2020-12-17 23:41:14 -07:00
|
|
|
return nil, err
|
2020-10-21 15:07:28 -06:00
|
|
|
}
|
2021-02-09 07:41:36 -07:00
|
|
|
resp.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
|
|
|
}
|
2020-10-31 18:32:38 -06:00
|
|
|
// Default mime type of text/gemini; charset=utf-8
|
2021-02-14 16:57:05 -07:00
|
|
|
if StatusClass(status) == StatusSuccess && meta == "" {
|
2020-10-31 18:32:38 -06:00
|
|
|
meta = "text/gemini; charset=utf-8"
|
|
|
|
}
|
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-14 16:57:05 -07:00
|
|
|
if StatusClass(status) == StatusSuccess {
|
2020-10-27 17:16:55 -06:00
|
|
|
resp.Body = newReadCloserBody(br, rc)
|
2020-12-22 17:21:59 -07:00
|
|
|
} else {
|
2021-01-15 13:15:16 -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-01-15 13:15:16 -07:00
|
|
|
type nopReadCloser struct{}
|
|
|
|
|
|
|
|
func (nopReadCloser) Read(p []byte) (int, error) {
|
|
|
|
return 0, io.EOF
|
|
|
|
}
|
|
|
|
|
|
|
|
func (nopReadCloser) Close() error {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-10-27 17:16:55 -06:00
|
|
|
type readCloserBody struct {
|
|
|
|
br *bufio.Reader // used until empty
|
|
|
|
io.ReadCloser
|
|
|
|
}
|
|
|
|
|
|
|
|
func newReadCloserBody(br *bufio.Reader, rc io.ReadCloser) io.ReadCloser {
|
|
|
|
body := &readCloserBody{ReadCloser: rc}
|
|
|
|
if br.Buffered() != 0 {
|
|
|
|
body.br = br
|
|
|
|
}
|
|
|
|
return body
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *readCloserBody) Read(p []byte) (n int, err error) {
|
|
|
|
if b.br != nil {
|
|
|
|
if n := b.br.Buffered(); len(p) > n {
|
|
|
|
p = p[:n]
|
|
|
|
}
|
|
|
|
n, err = b.br.Read(p)
|
|
|
|
if b.br.Buffered() == 0 {
|
|
|
|
b.br = nil
|
|
|
|
}
|
|
|
|
return n, err
|
|
|
|
}
|
|
|
|
return b.ReadCloser.Read(p)
|
|
|
|
}
|
2021-01-09 22:50:35 -07:00
|
|
|
|
2021-02-09 07:45:10 -07:00
|
|
|
// A ResponseWriter interface is used by a Gemini handler
|
|
|
|
// to construct a Gemini response.
|
|
|
|
type ResponseWriter interface {
|
|
|
|
// Header sets the response header.
|
|
|
|
Header(status int, meta string)
|
|
|
|
|
|
|
|
// Status sets the response status code.
|
|
|
|
// It also sets the response meta to Meta(status).
|
|
|
|
Status(status int)
|
|
|
|
|
|
|
|
// Meta sets the response meta.
|
|
|
|
//
|
|
|
|
// For successful responses, meta should contain the media type of the response.
|
|
|
|
// For failure responses, meta should contain a short description of the failure.
|
|
|
|
// The response meta should not be greater than 1024 bytes.
|
|
|
|
Meta(meta string)
|
|
|
|
|
|
|
|
// Write writes data to the connection as part of the response body.
|
|
|
|
// If the response status does not allow for a response body, Write returns
|
|
|
|
// ErrBodyNotAllowed.
|
|
|
|
//
|
|
|
|
// Write writes the response header if it has not already been written.
|
|
|
|
// It writes a successful status code if one is not set.
|
|
|
|
Write([]byte) (int, error)
|
|
|
|
|
|
|
|
// Flush writes any buffered data to the underlying io.Writer.
|
|
|
|
//
|
|
|
|
// Flush writes the response header if it has not already been written.
|
|
|
|
// It writes a failure status code if one is not set.
|
|
|
|
Flush() error
|
|
|
|
}
|
|
|
|
|
|
|
|
type responseWriter struct {
|
2021-01-09 22:50:35 -07:00
|
|
|
b *bufio.Writer
|
2021-02-09 07:41:36 -07:00
|
|
|
status int
|
2021-01-09 22:50:35 -07:00
|
|
|
meta string
|
|
|
|
wroteHeader bool
|
|
|
|
bodyAllowed bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewResponseWriter returns a ResponseWriter that uses the provided io.Writer.
|
2021-02-09 07:45:10 -07:00
|
|
|
func NewResponseWriter(w io.Writer) ResponseWriter {
|
|
|
|
return &responseWriter{
|
2021-01-09 22:50:35 -07:00
|
|
|
b: bufio.NewWriter(w),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-09 07:45:10 -07:00
|
|
|
func (w *responseWriter) Header(status int, meta string) {
|
2021-01-09 22:50:35 -07:00
|
|
|
w.status = status
|
|
|
|
w.meta = meta
|
|
|
|
}
|
|
|
|
|
2021-02-09 07:45:10 -07:00
|
|
|
func (w *responseWriter) Status(status int) {
|
2021-01-09 22:50:35 -07:00
|
|
|
w.status = status
|
2021-02-09 07:41:36 -07:00
|
|
|
w.meta = Meta(status)
|
2021-01-09 22:50:35 -07:00
|
|
|
}
|
|
|
|
|
2021-02-09 07:45:10 -07:00
|
|
|
func (w *responseWriter) Meta(meta string) {
|
2021-01-09 22:50:35 -07:00
|
|
|
w.meta = meta
|
|
|
|
}
|
|
|
|
|
2021-02-09 07:45:10 -07:00
|
|
|
func (w *responseWriter) Write(b []byte) (int, error) {
|
2021-01-09 22:50:35 -07:00
|
|
|
if !w.wroteHeader {
|
|
|
|
w.writeHeader(StatusSuccess)
|
|
|
|
}
|
|
|
|
if !w.bodyAllowed {
|
|
|
|
return 0, ErrBodyNotAllowed
|
|
|
|
}
|
|
|
|
return w.b.Write(b)
|
|
|
|
}
|
|
|
|
|
2021-02-09 07:45:10 -07:00
|
|
|
func (w *responseWriter) writeHeader(defaultStatus int) {
|
2021-01-09 22:50:35 -07:00
|
|
|
status := w.status
|
|
|
|
if status == 0 {
|
|
|
|
status = defaultStatus
|
|
|
|
}
|
|
|
|
|
|
|
|
meta := w.meta
|
2021-02-14 14:01:37 -07:00
|
|
|
if StatusClass(status) == StatusSuccess {
|
2021-01-09 22:50:35 -07:00
|
|
|
w.bodyAllowed = true
|
|
|
|
|
|
|
|
if meta == "" {
|
|
|
|
meta = "text/gemini"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-09 07:41:36 -07:00
|
|
|
w.b.WriteString(strconv.Itoa(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-09 07:45:10 -07:00
|
|
|
func (w *responseWriter) Flush() error {
|
2021-01-09 22:50:35 -07:00
|
|
|
if !w.wroteHeader {
|
|
|
|
w.writeHeader(StatusTemporaryFailure)
|
|
|
|
}
|
|
|
|
// Write errors from writeHeader will be returned here.
|
|
|
|
return w.b.Flush()
|
|
|
|
}
|