aboutsummaryrefslogtreecommitdiffstats
path: root/internal/port/gopher.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/port/gopher.go')
-rw-r--r--internal/port/gopher.go217
1 files changed, 217 insertions, 0 deletions
diff --git a/internal/port/gopher.go b/internal/port/gopher.go
new file mode 100644
index 0000000..ebeb213
--- /dev/null
+++ b/internal/port/gopher.go
@@ -0,0 +1,217 @@
1package port
2
3import (
4 "bytes"
5 "fmt"
6 "html/template"
7 "io"
8 "log"
9 "net"
10 "net/http"
11 "net/url"
12 "strings"
13
14 "git.vulpes.one/Feuerfuchs/port/port/libgopher"
15
16 "github.com/davidbyttow/govips/pkg/vips"
17 "github.com/temoto/robotstxt"
18)
19
20type Item struct {
21 Link template.URL
22 Type string
23 Text string
24}
25
26func renderGopherDirectory(w http.ResponseWriter, tpl *template.Template, assetList AssetList, uri string, hostport string, d libgopher.Directory) error {
27 var title string
28
29 out := make([]Item, len(d.Items))
30
31 for i, x := range d.Items {
32 if x.Type == libgopher.INFO && x.Selector == "TITLE" {
33 title = x.Description
34 continue
35 }
36
37 tr := Item{
38 Text: x.Description,
39 Type: x.Type.String(),
40 }
41
42 if x.Type == libgopher.INFO {
43 out[i] = tr
44 continue
45 }
46
47 if strings.HasPrefix(x.Selector, "URL:") || strings.HasPrefix(x.Selector, "/URL:") {
48 link := strings.TrimPrefix(strings.TrimPrefix(x.Selector, "/"), "URL:")
49 if strings.HasPrefix(link, "gemini://") {
50 link = fmt.Sprintf(
51 "/gemini/%s",
52 strings.TrimPrefix(link, "gemini://"),
53 )
54 } else if strings.HasPrefix(link, "gopher://") {
55 link = fmt.Sprintf(
56 "/gopher/%s",
57 strings.TrimPrefix(link, "gopher://"),
58 )
59 }
60 tr.Link = template.URL(link)
61 } else {
62 var linkHostport string
63 if x.Port != "70" {
64 linkHostport = net.JoinHostPort(x.Host, x.Port)
65 } else {
66 linkHostport = x.Host
67 }
68
69 path := url.PathEscape(x.Selector)
70 path = strings.Replace(path, "%2F", "/", -1)
71 tr.Link = template.URL(
72 fmt.Sprintf(
73 "/gopher/%s/%s%s",
74 linkHostport,
75 string(byte(x.Type)),
76 path,
77 ),
78 )
79 }
80
81 out[i] = tr
82 }
83
84 if title == "" {
85 if uri != "" {
86 title = fmt.Sprintf("%s/%s", hostport, uri)
87 } else {
88 title = hostport
89 }
90 }
91
92 return tpl.Execute(w, TemplateVariables{
93 Title: title,
94 URI: fmt.Sprintf("%s/%s", hostport, uri),
95 Assets: assetList,
96 Lines: out,
97 Protocol: "gopher",
98 })
99}
100
101// GopherHandler returns a Handler that proxies requests
102// to the specified Gopher server as denoated by the first argument
103// to the request path and renders the content using the provided template.
104// The optional robots parameters points to a robotstxt.RobotsData struct
105// to test user agents against a configurable robotst.txt file.
106func GopherHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, assetList AssetList, robotsdebug bool) http.HandlerFunc {
107 return func(w http.ResponseWriter, req *http.Request) {
108 agent := req.UserAgent()
109 path := strings.TrimPrefix(req.URL.Path, "/gopher/")
110
111 if robotsdata != nil && robotsdebug && !robotsdata.TestAgent(path, agent) {
112 log.Printf("UserAgent %s ignored robots.txt", agent)
113 }
114
115 parts := strings.Split(path, "/")
116 hostport := parts[0]
117
118 if len(hostport) == 0 {
119 http.Redirect(w, req, "/", http.StatusFound)
120 return
121 }
122
123 title := hostport
124
125 var qs string
126
127 if req.URL.RawQuery != "" {
128 qs = fmt.Sprintf("?%s", url.QueryEscape(req.URL.RawQuery))
129 }
130
131 uri, err := url.QueryUnescape(strings.Join(parts[1:], "/"))
132 if err != nil {
133 if e := tpl.Execute(w, TemplateVariables{
134 Title: title,
135 URI: hostport,
136 Assets: assetList,
137 RawText: fmt.Sprintf("Error: %s", err),
138 Error: true,
139 Protocol: "gopher",
140 }); e != nil {
141 log.Println("Template error: " + e.Error())
142 log.Println(err.Error())
143 }
144 return
145 }
146
147 if uri != "" {
148 title = fmt.Sprintf("%s/%s", hostport, uri)
149 }
150
151 res, err := libgopher.Get(
152 fmt.Sprintf(
153 "gopher://%s/%s%s",
154 hostport,
155 uri,
156 qs,
157 ),
158 )
159
160 if err != nil {
161 if e := tpl.Execute(w, TemplateVariables{
162 Title: title,
163 URI: fmt.Sprintf("%s/%s", hostport, uri),
164 Assets: assetList,
165 RawText: fmt.Sprintf("Error: %s", err),
166 Error: true,
167 Protocol: "gopher",
168 }); e != nil {
169 log.Println("Template error: " + e.Error())
170 }
171 return
172 }
173
174 if res.Body != nil {
175 if len(parts) < 2 {
176 io.Copy(w, res.Body)
177 } else if strings.HasPrefix(parts[1], "0") && !strings.HasSuffix(uri, ".xml") && !strings.HasSuffix(uri, ".asc") {
178 buf := new(bytes.Buffer)
179 buf.ReadFrom(res.Body)
180
181 if err := tpl.Execute(w, TemplateVariables{
182 Title: title,
183 URI: fmt.Sprintf("%s/%s", hostport, uri),
184 Assets: assetList,
185 RawText: buf.String(),
186 Protocol: "gopher",
187 }); err != nil {
188 log.Println("Template error: " + err.Error())
189 }
190 } else if strings.HasPrefix(parts[1], "T") {
191 _, _, err = vips.NewTransform().
192 Load(res.Body).
193 ResizeStrategy(vips.ResizeStrategyAuto).
194 ResizeWidth(160).
195 Quality(75).
196 Output(w).
197 Apply()
198 } else {
199 io.Copy(w, res.Body)
200 }
201 } else {
202 if err := renderGopherDirectory(w, tpl, assetList, uri, hostport, res.Dir); err != nil {
203 if e := tpl.Execute(w, TemplateVariables{
204 Title: title,
205 URI: fmt.Sprintf("%s/%s", hostport, uri),
206 Assets: assetList,
207 RawText: fmt.Sprintf("Error: %s", err),
208 Error: true,
209 Protocol: "gopher",
210 }); e != nil {
211 log.Println("Template error: " + e.Error())
212 log.Println(e.Error())
213 }
214 }
215 }
216 }
217}