1
0
Fork 0
mirror of https://git.sr.ht/~adnano/go-gemini synced 2024-05-06 00:26:19 +02:00
go-gemini/response.go

236 lines
5.7 KiB
Go
Raw Normal View History

2020-10-24 21:15:32 +02:00
package gemini
2020-10-21 23:07:28 +02:00
import (
"bufio"
2024-03-10 14:35:32 +01:00
"bytes"
2021-02-23 22:36:17 +01:00
"crypto/tls"
2021-03-01 02:50:18 +01:00
"fmt"
2020-10-28 00:16:55 +01:00
"io"
2021-02-23 22:36:17 +01:00
"net"
2020-10-21 23:07:28 +02:00
"strconv"
)
2021-02-17 19:36:16 +01:00
// The default media type for responses.
const defaultMediaType = "text/gemini"
2021-02-17 19:36:16 +01:00
2021-02-14 22:23:38 +01: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 23:07:28 +02:00
type Response struct {
// Status is the response status code.
Status Status
2020-10-21 23:07:28 +02:00
// Meta returns the response meta.
// For successful responses, the meta should contain the media type of the response.
// For failure responses, the meta should contain a short description of the failure.
Meta string
// Body represents the response body.
//
// 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.
Body io.ReadCloser
conn net.Conn
2021-02-24 16:48:17 +01:00
}
// ReadResponse reads a Gemini response from the provided io.ReadCloser.
2021-02-24 03:51:28 +01:00
func ReadResponse(r io.ReadCloser) (*Response, error) {
resp := &Response{}
2021-03-20 19:01:45 +01:00
// Limit response header size
lr := io.LimitReader(r, 1029)
// Wrap the reader to remove the limit later on
wr := &struct{ io.Reader }{lr}
br := bufio.NewReader(wr)
2021-03-20 17:27:20 +01:00
// Read response header
b, err := br.ReadBytes('\n')
if err != nil {
if err == io.EOF {
return nil, ErrInvalidResponse
}
return nil, err
2020-10-21 23:07:28 +02:00
}
2021-03-20 17:27:20 +01:00
if len(b) < 3 {
return nil, ErrInvalidResponse
}
// Read the status
status, err := strconv.Atoi(string(b[:2]))
2020-10-21 23:07:28 +02:00
if err != nil {
return nil, ErrInvalidResponse
2020-10-21 23:07:28 +02:00
}
resp.Status = Status(status)
2020-10-21 23:07:28 +02:00
// Read one space
2021-03-20 17:27:20 +01:00
if b[2] != ' ' {
return nil, ErrInvalidResponse
2020-10-21 23:07:28 +02:00
}
// Read the meta
2024-03-10 14:35:32 +01:00
meta, ok := bytes.CutSuffix(b[3:], crlf)
2021-03-20 17:27:20 +01:00
if !ok {
return nil, ErrInvalidResponse
2020-10-21 23:07:28 +02:00
}
if len(meta) == 0 {
return nil, ErrInvalidResponse
2020-10-21 23:07:28 +02:00
}
2021-03-20 17:27:20 +01:00
resp.Meta = string(meta)
2020-10-21 23:07:28 +02:00
if resp.Status.Class() == StatusSuccess {
2021-03-20 19:01:45 +01:00
// Use unlimited reader
wr.Reader = r
2021-03-20 18:41:53 +01:00
type readCloser struct {
io.Reader
io.Closer
}
resp.Body = readCloser{br, r}
} else {
resp.Body = nopReadCloser{}
2021-02-24 03:51:28 +01:00
r.Close()
2020-10-21 23:07:28 +02:00
}
return resp, nil
2020-10-21 23:07:28 +02:00
}
2020-10-28 00:16:55 +01:00
2021-02-23 22:36:17 +01: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-03-01 04:21:54 +01:00
// WriteTo writes r to w in the Gemini response format, including the
2021-03-01 02:50:18 +01:00
// header and body.
//
// This method consults the Status, Meta, and Body fields of the response.
// The Response Body is closed after it is sent.
2021-03-01 04:21:54 +01:00
func (r *Response) WriteTo(w io.Writer) (int64, error) {
var wrote int64
n, err := fmt.Fprintf(w, "%02d %s\r\n", r.Status, r.Meta)
wrote += int64(n)
if err != nil {
return wrote, err
2021-03-01 02:50:18 +01:00
}
if r.Body != nil {
defer r.Body.Close()
2021-03-01 04:21:54 +01:00
n, err := io.Copy(w, r.Body)
wrote += n
if err != nil {
return wrote, err
2021-03-01 02:50:18 +01:00
}
}
2021-03-01 04:21:54 +01:00
return wrote, nil
2021-03-01 02:50:18 +01:00
}
2021-02-17 19:36:16 +01: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.
type ResponseWriter interface {
// SetMediaType sets the media type that will be sent by Write for a
// successful response. If no media type is set, a default media type of
// "text/gemini" will be used.
//
// Setting the media type after a call to Write or WriteHeader has
// no effect.
SetMediaType(mediatype string)
// 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".
Write([]byte) (int, error)
// 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.
WriteHeader(status Status, meta string)
// Flush sends any buffered data to the client.
Flush() error
}
type responseWriter struct {
2021-02-24 03:51:28 +01:00
bw *bufio.Writer
2021-02-17 19:36:16 +01:00
mediatype string
2021-01-10 06:50:35 +01:00
wroteHeader bool
bodyAllowed bool
}
2021-02-25 01:00:09 +01:00
func newResponseWriter(w io.Writer) *responseWriter {
return &responseWriter{
2021-02-24 03:51:28 +01:00
bw: bufio.NewWriter(w),
2021-01-10 06:50:35 +01:00
}
}
func (w *responseWriter) SetMediaType(mediatype string) {
2021-02-17 19:36:16 +01:00
w.mediatype = mediatype
2021-01-10 06:50:35 +01:00
}
func (w *responseWriter) Write(b []byte) (int, error) {
2021-01-10 06:50:35 +01:00
if !w.wroteHeader {
2021-02-17 19:36:16 +01:00
meta := w.mediatype
if meta == "" {
// Use default media type
meta = defaultMediaType
}
w.WriteHeader(StatusSuccess, meta)
2021-01-10 06:50:35 +01:00
}
if !w.bodyAllowed {
return 0, ErrBodyNotAllowed
}
2021-02-24 03:51:28 +01:00
return w.bw.Write(b)
2021-01-10 06:50:35 +01:00
}
func (w *responseWriter) WriteHeader(status Status, meta string) {
if w.wroteHeader {
return
}
if status.Class() == StatusSuccess {
2021-01-10 06:50:35 +01:00
w.bodyAllowed = true
}
2021-02-24 03:51:28 +01:00
w.bw.WriteString(strconv.Itoa(int(status)))
w.bw.WriteByte(' ')
w.bw.WriteString(meta)
w.bw.Write(crlf)
2021-01-10 06:50:35 +01:00
w.wroteHeader = true
}
func (w *responseWriter) Flush() error {
2021-01-10 06:50:35 +01:00
if !w.wroteHeader {
2021-02-17 19:36:16 +01:00
w.WriteHeader(StatusTemporaryFailure, "Temporary failure")
2021-01-10 06:50:35 +01:00
}
2021-02-24 00:45:58 +01:00
// Write errors from WriteHeader will be returned here.
2021-02-24 03:51:28 +01:00
return w.bw.Flush()
2021-01-10 06:50:35 +01:00
}