diff options
Diffstat (limited to 'internal/port/gopher.go')
-rw-r--r-- | internal/port/gopher.go | 284 |
1 files changed, 0 insertions, 284 deletions
diff --git a/internal/port/gopher.go b/internal/port/gopher.go deleted file mode 100644 index fc7b754..0000000 --- a/internal/port/gopher.go +++ /dev/null | |||
@@ -1,284 +0,0 @@ | |||
1 | package port | ||
2 | |||
3 | import ( | ||
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/pkg/libgopher" | ||
15 | |||
16 | "github.com/davidbyttow/govips/pkg/vips" | ||
17 | "github.com/temoto/robotstxt" | ||
18 | ) | ||
19 | |||
20 | type gopherTemplateVariables struct { | ||
21 | Title string | ||
22 | URL string | ||
23 | Assets AssetList | ||
24 | Lines []GopherItem | ||
25 | Nav []GopherNavItem | ||
26 | IsPlain bool | ||
27 | } | ||
28 | |||
29 | type GopherNavItem struct { | ||
30 | Label string | ||
31 | URL string | ||
32 | Current bool | ||
33 | } | ||
34 | |||
35 | type GopherItem struct { | ||
36 | Link template.URL | ||
37 | Type string | ||
38 | Text string | ||
39 | } | ||
40 | |||
41 | func trimLeftChars(s string, n int) string { | ||
42 | m := 0 | ||
43 | for i := range s { | ||
44 | if m >= n { | ||
45 | return s[i:] | ||
46 | } | ||
47 | m++ | ||
48 | } | ||
49 | return s[:0] | ||
50 | } | ||
51 | |||
52 | func urlToGopherNav(url string) (items []GopherNavItem) { | ||
53 | partialURL := "/gopher" | ||
54 | parts := strings.Split(url, "/") | ||
55 | |||
56 | if len(parts) != 0 && parts[len(parts)-1] == "" { | ||
57 | parts = parts[:len(parts)-1] | ||
58 | } | ||
59 | |||
60 | for i, part := range parts { | ||
61 | if i == 1 { | ||
62 | partialURL = partialURL + "/1" | ||
63 | part = trimLeftChars(part, 1) | ||
64 | |||
65 | if part == "" { | ||
66 | continue | ||
67 | } | ||
68 | } else { | ||
69 | partialURL = partialURL + "/" + part | ||
70 | } | ||
71 | |||
72 | items = append(items, GopherNavItem{ | ||
73 | Label: part, | ||
74 | URL: partialURL, | ||
75 | Current: false, | ||
76 | }) | ||
77 | } | ||
78 | |||
79 | items[len(items)-1].Current = true | ||
80 | |||
81 | return | ||
82 | } | ||
83 | |||
84 | func renderGopherDirectory(w http.ResponseWriter, tpl *template.Template, assetList AssetList, uri string, hostport string, d libgopher.Directory) error { | ||
85 | var title string | ||
86 | |||
87 | out := make([]GopherItem, len(d.Items)) | ||
88 | |||
89 | for i, x := range d.Items { | ||
90 | if x.Type == libgopher.INFO && x.Selector == "TITLE" { | ||
91 | title = x.Description | ||
92 | continue | ||
93 | } | ||
94 | |||
95 | tr := GopherItem{ | ||
96 | Text: x.Description, | ||
97 | Type: x.Type.String(), | ||
98 | } | ||
99 | |||
100 | if x.Type == libgopher.INFO { | ||
101 | out[i] = tr | ||
102 | continue | ||
103 | } | ||
104 | |||
105 | if strings.HasPrefix(x.Selector, "URL:") || strings.HasPrefix(x.Selector, "/URL:") { | ||
106 | link := strings.TrimPrefix(strings.TrimPrefix(x.Selector, "/"), "URL:") | ||
107 | if strings.HasPrefix(link, "gemini://") { | ||
108 | link = fmt.Sprintf( | ||
109 | "/gemini/%s", | ||
110 | strings.TrimPrefix(link, "gemini://"), | ||
111 | ) | ||
112 | } else if strings.HasPrefix(link, "gopher://") { | ||
113 | link = fmt.Sprintf( | ||
114 | "/gopher/%s", | ||
115 | strings.TrimPrefix(link, "gopher://"), | ||
116 | ) | ||
117 | } | ||
118 | tr.Link = template.URL(link) | ||
119 | } else { | ||
120 | var linkHostport string | ||
121 | if x.Port != "70" { | ||
122 | linkHostport = net.JoinHostPort(x.Host, x.Port) | ||
123 | } else { | ||
124 | linkHostport = x.Host | ||
125 | } | ||
126 | |||
127 | path := url.PathEscape(x.Selector) | ||
128 | path = strings.Replace(path, "%2F", "/", -1) | ||
129 | tr.Link = template.URL( | ||
130 | fmt.Sprintf( | ||
131 | "/gopher/%s/%s%s", | ||
132 | linkHostport, | ||
133 | string(byte(x.Type)), | ||
134 | path, | ||
135 | ), | ||
136 | ) | ||
137 | } | ||
138 | |||
139 | out[i] = tr | ||
140 | } | ||
141 | |||
142 | if title == "" { | ||
143 | if uri != "" { | ||
144 | title = fmt.Sprintf("%s/%s", hostport, uri) | ||
145 | } else { | ||
146 | title = hostport | ||
147 | } | ||
148 | } | ||
149 | |||
150 | return tpl.Execute(w, gopherTemplateVariables{ | ||
151 | Title: title, | ||
152 | URL: fmt.Sprintf("%s/%s", hostport, uri), | ||
153 | Assets: assetList, | ||
154 | Lines: out, | ||
155 | Nav: urlToGopherNav(fmt.Sprintf("%s/%s", hostport, uri)), | ||
156 | }) | ||
157 | } | ||
158 | |||
159 | // GopherHandler returns a Handler that proxies requests | ||
160 | // to the specified Gopher server as denoated by the first argument | ||
161 | // to the request path and renders the content using the provided template. | ||
162 | // The optional robots parameters points to a robotstxt.RobotsData struct | ||
163 | // to test user agents against a configurable robotst.txt file. | ||
164 | func GopherHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, assetList AssetList, robotsdebug bool) http.HandlerFunc { | ||
165 | return func(w http.ResponseWriter, req *http.Request) { | ||
166 | agent := req.UserAgent() | ||
167 | path := strings.TrimPrefix(req.URL.Path, "/gopher/") | ||
168 | |||
169 | if robotsdata != nil && robotsdebug && !robotsdata.TestAgent(path, agent) { | ||
170 | log.Printf("UserAgent %s ignored robots.txt", agent) | ||
171 | } | ||
172 | |||
173 | parts := strings.Split(path, "/") | ||
174 | hostport := parts[0] | ||
175 | |||
176 | if len(hostport) == 0 { | ||
177 | http.Redirect(w, req, "/", http.StatusFound) | ||
178 | return | ||
179 | } | ||
180 | |||
181 | title := hostport | ||
182 | |||
183 | var qs string | ||
184 | |||
185 | if req.URL.RawQuery != "" { | ||
186 | qs = fmt.Sprintf("?%s", url.QueryEscape(req.URL.RawQuery)) | ||
187 | } | ||
188 | |||
189 | uri, err := url.QueryUnescape(strings.Join(parts[1:], "/")) | ||
190 | if err != nil { | ||
191 | if e := tpl.Execute(w, gopherTemplateVariables{ | ||
192 | Title: title, | ||
193 | URL: hostport, | ||
194 | Assets: assetList, | ||
195 | Lines: []GopherItem{{ | ||
196 | Text: fmt.Sprintf("Error: %s", err), | ||
197 | }}, | ||
198 | Nav: urlToGopherNav(hostport), | ||
199 | IsPlain: true, | ||
200 | }); e != nil { | ||
201 | log.Println("Template error: " + e.Error()) | ||
202 | log.Println(err.Error()) | ||
203 | } | ||
204 | return | ||
205 | } | ||
206 | |||
207 | if uri != "" { | ||
208 | title = fmt.Sprintf("%s/%s", hostport, uri) | ||
209 | } | ||
210 | |||
211 | res, err := libgopher.Get( | ||
212 | fmt.Sprintf( | ||
213 | "gopher://%s/%s%s", | ||
214 | hostport, | ||
215 | uri, | ||
216 | qs, | ||
217 | ), | ||
218 | ) | ||
219 | |||
220 | if err != nil { | ||
221 | if e := tpl.Execute(w, gopherTemplateVariables{ | ||
222 | Title: title, | ||
223 | URL: fmt.Sprintf("%s/%s", hostport, uri), | ||
224 | Assets: assetList, | ||
225 | Lines: []GopherItem{{ | ||
226 | Text: fmt.Sprintf("Error: %s", err), | ||
227 | }}, | ||
228 | Nav: urlToGopherNav(fmt.Sprintf("%s/%s", hostport, uri)), | ||
229 | IsPlain: true, | ||
230 | }); e != nil { | ||
231 | log.Println("Template error: " + e.Error()) | ||
232 | } | ||
233 | return | ||
234 | } | ||
235 | |||
236 | if res.Body != nil { | ||
237 | if len(parts) < 2 { | ||
238 | io.Copy(w, res.Body) | ||
239 | } else if strings.HasPrefix(parts[1], "0") && !strings.HasSuffix(uri, ".xml") && !strings.HasSuffix(uri, ".asc") { | ||
240 | buf := new(bytes.Buffer) | ||
241 | buf.ReadFrom(res.Body) | ||
242 | |||
243 | if err := tpl.Execute(w, gopherTemplateVariables{ | ||
244 | Title: title, | ||
245 | URL: fmt.Sprintf("%s/%s", hostport, uri), | ||
246 | Assets: assetList, | ||
247 | Lines: []GopherItem{{ | ||
248 | Text: buf.String(), | ||
249 | }}, | ||
250 | Nav: urlToGopherNav(fmt.Sprintf("%s/%s", hostport, uri)), | ||
251 | IsPlain: true, | ||
252 | }); err != nil { | ||
253 | log.Println("Template error: " + err.Error()) | ||
254 | } | ||
255 | } else if strings.HasPrefix(parts[1], "T") { | ||
256 | _, _, err = vips.NewTransform(). | ||
257 | Load(res.Body). | ||
258 | ResizeStrategy(vips.ResizeStrategyAuto). | ||
259 | ResizeWidth(160). | ||
260 | Quality(75). | ||
261 | Output(w). | ||
262 | Apply() | ||
263 | } else { | ||
264 | io.Copy(w, res.Body) | ||
265 | } | ||
266 | } else { | ||
267 | if err := renderGopherDirectory(w, tpl, assetList, uri, hostport, res.Dir); err != nil { | ||
268 | if e := tpl.Execute(w, gopherTemplateVariables{ | ||
269 | Title: title, | ||
270 | URL: fmt.Sprintf("%s/%s", hostport, uri), | ||
271 | Assets: assetList, | ||
272 | Lines: []GopherItem{{ | ||
273 | Text: fmt.Sprintf("Error: %s", err), | ||
274 | }}, | ||
275 | Nav: urlToGopherNav(fmt.Sprintf("%s/%s", hostport, uri)), | ||
276 | IsPlain: false, | ||
277 | }); e != nil { | ||
278 | log.Println("Template error: " + e.Error()) | ||
279 | log.Println(e.Error()) | ||
280 | } | ||
281 | } | ||
282 | } | ||
283 | } | ||
284 | } | ||