163 lines
3.7 KiB
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)
|
|
})
|
|
}
|