diff options
author | tjpcc <tjp@ctrl-c.club> | 2023-01-28 14:52:35 -0700 |
---|---|---|
committer | tjpcc <tjp@ctrl-c.club> | 2023-01-28 15:01:41 -0700 |
commit | 66a1b1f39a1e1d5499b548b36d18c8daa872d7da (patch) | |
tree | 96471dbd5486ede1a908790ac23e0c55b226dfad /gopher/response.go | |
parent | a27b879accb191b6a6c6e76a6251ed751967f73a (diff) |
gopher support.
Some of the contrib packages were originally built gemini-specific and
had to be refactored into generic core functionality and thin
protocol-specific wrappers for each of gemini and gopher.
Diffstat (limited to 'gopher/response.go')
-rw-r--r-- | gopher/response.go | 162 |
1 files changed, 162 insertions, 0 deletions
diff --git a/gopher/response.go b/gopher/response.go new file mode 100644 index 0000000..c600b10 --- /dev/null +++ b/gopher/response.go @@ -0,0 +1,162 @@ +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) + }) +} |