1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
|
package gopher
import (
"bytes"
"fmt"
"io"
"sync"
"tildegit.org/tjp/sliderule/internal/types"
)
// The Canonical gopher item types.
const (
TextFileType types.Status = '0'
MenuType types.Status = '1'
CSOPhoneBookType types.Status = '2'
ErrorType types.Status = '3'
MacBinHexType types.Status = '4'
DosBinType types.Status = '5'
UuencodedType types.Status = '6'
SearchType types.Status = '7'
TelnetSessionType types.Status = '8'
BinaryFileType types.Status = '9'
MirrorServerType types.Status = '+'
GifFileType types.Status = 'g'
ImageFileType types.Status = 'I'
Telnet3270Type types.Status = 'T'
)
// The gopher+ types.
const (
BitmapType types.Status = ':'
MovieFileType types.Status = ';'
SoundFileType types.Status = '<'
)
// The various non-canonical gopher types.
const (
DocumentType types.Status = 'd'
HTMLType types.Status = 'h'
InfoMessageType types.Status = 'i'
PngImageFileType types.Status = 'p'
RtfDocumentType types.Status = 'r'
WavSoundFileType types.Status = 's'
PdfDocumentType types.Status = 'P'
XmlDocumentType types.Status = 'X'
)
// MapItem is a single item in a gophermap.
type MapItem struct {
Type types.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() *types.Response {
return &types.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() *types.Response {
return &types.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 types.Status, contents io.Reader) *types.Response {
return &types.Response{Status: status, Body: contents}
}
// NewResponseReader produces a reader which supports reading gopher protocol responses.
func NewResponseReader(response *types.Response) types.ResponseReader {
return &responseReader{
Response: response,
once: &sync.Once{},
}
}
type responseReader struct {
*types.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)
})
}
|