go-gemini/response.go

250 lines
6.2 KiB
Go
Raw Normal View History

2020-10-24 13:15:32 -06:00
package gemini
2020-10-21 15:07:28 -06:00
import (
"bufio"
2021-02-17 22:07:43 -07:00
"fmt"
2020-10-27 17:16:55 -06:00
"io"
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
// 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.
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
// 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
}
// 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-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 {
return nil, err
2020-10-21 15:07:28 -06:00
}
status, err := strconv.Atoi(string(statusB))
if err != nil {
return nil, ErrInvalidResponse
2020-10-21 15:07:28 -06: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 {
return nil, err
2020-10-21 15:07:28 -06:00
} else if b != ' ' {
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 {
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 {
return nil, ErrInvalidResponse
2020-10-21 15:07:28 -06: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 {
return nil, err
2020-10-21 15:07:28 -06:00
} else if b != '\n' {
return nil, ErrInvalidResponse
2020-10-21 15:07:28 -06:00
}
if resp.Status.Class() == StatusSuccess {
2020-10-27 17:16:55 -06:00
resp.Body = newReadCloserBody(br, rc)
} else {
resp.Body = nopReadCloser{}
rc.Close()
2020-10-21 15:07:28 -06:00
}
return resp, nil
2020-10-21 15:07:28 -06:00
}
2020-10-27 17:16:55 -06: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-17 22:07:43 -07:00
// Write writes r to w in the Gemini response format, including the
// header and body.
//
// This method consults the Status, Meta, and Body fields of the response.
// The Response Body is closed after it is sent.
func (r *Response) Write(w io.Writer) error {
if _, err := fmt.Fprintf(w, "%02d %s\r\n", r.Status, r.Meta); err != nil {
return err
}
if r.Body != nil {
defer r.Body.Close()
if _, err := io.Copy(w, r.Body); err != nil {
return err
}
}
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-09 07:45:10 -07:00
type ResponseWriter interface {
// SetMediaType sets the media type that will be sent by Write for a
2021-02-17 11:36:16 -07:00
// 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.
SetMediaType(string)
2021-02-09 07:45:10 -07:00
2021-02-17 11:36:16 -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.
2021-02-17 11:36:16 -07:00
// If no media type was set, Write uses a default media type of
// "text/gemini; charset=utf-8".
Write([]byte) (int, error)
2021-02-09 07:45:10 -07:00
2021-02-17 11:36:16 -07:00
// WriteHeader sends a Gemini response header with the provided
// status code and meta.
2021-02-09 07:45:10 -07:00
//
2021-02-17 11:36:16 -07:00
// 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.
2021-02-09 07:45:10 -07:00
//
2021-02-17 11:36:16 -07:00
// 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.
WriteHeader(status Status, meta string)
2021-02-09 07:45:10 -07:00
// Flush sends any buffered data to the client.
2021-02-09 07:45:10 -07:00
Flush() error
}
type responseWriter struct {
2021-01-09 22:50:35 -07:00
b *bufio.Writer
2021-02-17 11:36:16 -07:00
mediatype string
2021-01-09 22:50:35 -07:00
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 newResponseWriter(w)
}
func newResponseWriter(w io.Writer) *responseWriter {
2021-02-09 07:45:10 -07:00
return &responseWriter{
2021-01-09 22:50:35 -07:00
b: bufio.NewWriter(w),
}
}
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-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 {
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)
}
func (w *responseWriter) WriteHeader(status Status, meta string) {
if w.wroteHeader {
return
}
if status.Class() == StatusSuccess {
2021-01-09 22:50:35 -07:00
w.bodyAllowed = true
}
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-09 07:45:10 -07:00
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
}
// Write errors from writeHeader will be returned here.
return w.b.Flush()
}