This repository has been archived on 2023-05-01. You can view files and clone it, but cannot push or open issues or pull requests.
gus/gopher/response.go

163 lines
3.7 KiB
Go

package gopher
import (
"bytes"
"fmt"
"io"
"sync"
"tildegit.org/tjp/gus"
)
// The Canonical gopher item types.
const (
TextFileType gus.Status = '0'
MenuType gus.Status = '1'
CSOPhoneBookType gus.Status = '2'
ErrorType gus.Status = '3'
MacBinHexType gus.Status = '4'
DosBinType gus.Status = '5'
UuencodedType gus.Status = '6'
SearchType gus.Status = '7'
TelnetSessionType gus.Status = '8'
BinaryFileType gus.Status = '9'
MirrorServerType gus.Status = '+'
GifFileType gus.Status = 'g'
ImageFileType gus.Status = 'I'
Telnet3270Type gus.Status = 'T'
)
// The gopher+ types.
const (
BitmapType gus.Status = ':'
MovieFileType gus.Status = ';'
SoundFileType gus.Status = '<'
)
// The various non-canonical gopher types.
const (
DocumentType gus.Status = 'd'
HTMLType gus.Status = 'h'
InfoMessageType gus.Status = 'i'
PngImageFileType gus.Status = 'p'
RtfDocumentType gus.Status = 'r'
WavSoundFileType gus.Status = 's'
PdfDocumentType gus.Status = 'P'
XmlDocumentType gus.Status = 'X'
)
// MapItem is a single item in a gophermap.
type MapItem struct {
Type gus.Status
Display string
Selector string
Hostname string
Port string
}
// String serializes the item into a gophermap CRLF-terminated text line.
func (mi MapItem) String() string {
return fmt.Sprintf(
"%s%s\t%s\t%s\t%s\r\n",
[]byte{byte(mi.Type)},
mi.Display,
mi.Selector,
mi.Hostname,
mi.Port,
)
}
// Response builds a response which contains just this single MapItem.
//
// Meta in the response will be a pointer to the MapItem.
func (mi *MapItem) Response() *gus.Response {
return &gus.Response{
Status: mi.Type,
Meta: &mi,
Body: bytes.NewBufferString(mi.String() + ".\r\n"),
}
}
// MapDocument is a list of map items which can print out a full gophermap document.
type MapDocument []MapItem
// String serializes the document into gophermap format.
func (md MapDocument) String() string {
return md.serialize().String()
}
// Response builds a gopher response containing the gophermap.
//
// Meta will be the MapDocument itself.
func (md MapDocument) Response() *gus.Response {
return &gus.Response{
Status: DocumentType,
Meta: md,
Body: md.serialize(),
}
}
func (md MapDocument) serialize() *bytes.Buffer {
buf := &bytes.Buffer{}
for _, mi := range md {
_, _ = buf.WriteString(mi.String())
}
_, _ = buf.WriteString(".\r\n")
return buf
}
// Error builds an error message MapItem.
func Error(err error) *MapItem {
return &MapItem{
Type: ErrorType,
Display: err.Error(),
Hostname: "none",
Port: "0",
}
}
// File builds a minimal response delivering a file's contents.
//
// Meta is nil and Status is 0 in this response.
func File(status gus.Status, contents io.Reader) *gus.Response {
return &gus.Response{Status: status, Body: contents}
}
// NewResponseReader produces a reader which supports reading gopher protocol responses.
func NewResponseReader(response *gus.Response) gus.ResponseReader {
return &responseReader{
Response: response,
once: &sync.Once{},
}
}
type responseReader struct {
*gus.Response
reader io.Reader
once *sync.Once
}
func (rdr *responseReader) Read(b []byte) (int, error) {
rdr.ensureReader()
return rdr.reader.Read(b)
}
func (rdr *responseReader) WriteTo(dst io.Writer) (int64, error) {
rdr.ensureReader()
return rdr.reader.(io.WriterTo).WriteTo(dst)
}
func (rdr *responseReader) ensureReader() {
rdr.once.Do(func() {
if _, ok := rdr.Body.(io.WriterTo); ok {
rdr.reader = rdr.Body
return
}
// rdr.reader needs to implement WriterTo, so in this case
// we borrow an implementation in terms of io.Reader from
// io.MultiReader.
rdr.reader = io.MultiReader(rdr.Body)
})
}