aboutsummaryrefslogtreecommitdiffstats
path: root/gopherproxy.go
diff options
context:
space:
mode:
Diffstat (limited to 'gopherproxy.go')
-rw-r--r--gopherproxy.go261
1 files changed, 221 insertions, 40 deletions
diff --git a/gopherproxy.go b/gopherproxy.go
index 87a1ad0..9a60507 100644
--- a/gopherproxy.go
+++ b/gopherproxy.go
@@ -1,6 +1,7 @@
1package gopherproxy 1package gopherproxy
2 2
3import ( 3import (
4 "bufio"
4 "bytes" 5 "bytes"
5 "crypto/md5" 6 "crypto/md5"
6 "fmt" 7 "fmt"
@@ -25,6 +26,11 @@ import (
25 "github.com/NYTimes/gziphandler" 26 "github.com/NYTimes/gziphandler"
26) 27)
27 28
29const (
30 ITEM_TYPE_GEMINI_LINE = ""
31 ITEM_TYPE_GEMINI_LINK = " =>"
32)
33
28type Item struct { 34type Item struct {
29 Link template.URL 35 Link template.URL
30 Type string 36 Type string
@@ -40,7 +46,7 @@ type AssetList struct {
40 PropFontW2 string 46 PropFontW2 string
41} 47}
42 48
43func renderDirectory(w http.ResponseWriter, tpl *template.Template, assetList AssetList, uri string, hostport string, d gopher.Directory) error { 49func renderGopherDirectory(w http.ResponseWriter, tpl *template.Template, assetList AssetList, uri string, hostport string, d gopher.Directory) error {
44 var title string 50 var title string
45 51
46 out := make([]Item, len(d.Items)) 52 out := make([]Item, len(d.Items))
@@ -76,7 +82,7 @@ func renderDirectory(w http.ResponseWriter, tpl *template.Template, assetList As
76 path = strings.Replace(path, "%2F", "/", -1) 82 path = strings.Replace(path, "%2F", "/", -1)
77 tr.Link = template.URL( 83 tr.Link = template.URL(
78 fmt.Sprintf( 84 fmt.Sprintf(
79 "/%s/%s%s", 85 "/gopher/%s/%s%s",
80 hostport, 86 hostport,
81 string(byte(x.Type)), 87 string(byte(x.Type)),
82 path, 88 path,
@@ -92,13 +98,81 @@ func renderDirectory(w http.ResponseWriter, tpl *template.Template, assetList As
92 } 98 }
93 99
94 return tpl.Execute(w, struct { 100 return tpl.Execute(w, struct {
95 Title string 101 Title string
96 URI string 102 URI string
97 Assets AssetList 103 Assets AssetList
98 Lines []Item 104 Lines []Item
99 RawText string 105 RawText string
100 Error bool 106 Error bool
101 }{title, fmt.Sprintf("%s/%s", hostport, uri), assetList, out, "", false}) 107 Protocol string
108 }{title, fmt.Sprintf("%s/%s", hostport, uri), assetList, out, "", false, "gopher"})
109}
110
111func parseGeminiDocument(response *GeminiResponse, uri string, hostport string) (items []Item) {
112 scanner := bufio.NewScanner(response.Body)
113 scanner.Split(bufio.ScanLines)
114
115 baseUrl, err := url.Parse(fmt.Sprintf(
116 "gemini://%s/%s",
117 hostport,
118 uri,
119 ))
120 if err != nil {
121 return []Item{}
122 }
123
124 for scanner.Scan() {
125 line := strings.Trim(scanner.Text(), "\r\n")
126
127 item := Item{
128 Type: ITEM_TYPE_GEMINI_LINE,
129 Text: line,
130 }
131
132 linkMatch := GeminiLinkPattern.FindStringSubmatch(line)
133 if len(linkMatch) != 0 && linkMatch[0] != "" {
134 link := linkMatch[1]
135
136 if strings.HasPrefix(link, "//") {
137 link = "/gemini/" + strings.TrimPrefix(link, "//")
138 } else if strings.HasPrefix(link, "gemini://") {
139 link = "/gemini/" + strings.TrimPrefix(link, "gemini://")
140 } else if strings.HasPrefix(link, "gopher://") {
141 link = "/gopher/" + strings.TrimPrefix(link, "gopher://")
142 } else {
143 linkUrl, err := url.Parse(link)
144 if err != nil {
145 continue
146 }
147 adjustedUrl := baseUrl.ResolveReference(linkUrl)
148 if adjustedUrl.Scheme == "gemini" {
149 link = "/gemini/" + adjustedUrl.Host + adjustedUrl.Path
150 } else if adjustedUrl.Scheme == "gopher" {
151 link = "/gopher/" + adjustedUrl.Host + adjustedUrl.Path
152 } else {
153 link = adjustedUrl.String()
154 }
155 }
156
157 item.Type = ITEM_TYPE_GEMINI_LINK
158 item.Link = template.URL(link)
159 if linkMatch[2] != "" {
160 item.Text = linkMatch[2]
161 } else {
162 item.Text = linkMatch[1]
163 }
164 }
165
166 items = append(items, item)
167 }
168
169 return
170}
171
172func DefaultHandler(tpl *template.Template, uri string) http.HandlerFunc {
173 return func(w http.ResponseWriter, req *http.Request) {
174 http.Redirect(w, req, "/"+uri, http.StatusFound)
175 }
102} 176}
103 177
104// GopherHandler returns a Handler that proxies requests 178// GopherHandler returns a Handler that proxies requests
@@ -109,7 +183,7 @@ func renderDirectory(w http.ResponseWriter, tpl *template.Template, assetList As
109func GopherHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, assetList AssetList, robotsdebug bool, uri string) http.HandlerFunc { 183func GopherHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, assetList AssetList, robotsdebug bool, uri string) http.HandlerFunc {
110 return func(w http.ResponseWriter, req *http.Request) { 184 return func(w http.ResponseWriter, req *http.Request) {
111 agent := req.UserAgent() 185 agent := req.UserAgent()
112 path := strings.TrimPrefix(req.URL.Path, "/") 186 path := strings.TrimPrefix(req.URL.Path, "/gopher/")
113 187
114 if robotsdata != nil && robotsdebug && !robotsdata.TestAgent(path, agent) { 188 if robotsdata != nil && robotsdebug && !robotsdata.TestAgent(path, agent) {
115 log.Printf("UserAgent %s ignored robots.txt", agent) 189 log.Printf("UserAgent %s ignored robots.txt", agent)
@@ -132,13 +206,14 @@ func GopherHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, ass
132 uri, err := url.QueryUnescape(strings.Join(parts[1:], "/")) 206 uri, err := url.QueryUnescape(strings.Join(parts[1:], "/"))
133 if err != nil { 207 if err != nil {
134 tpl.Execute(w, struct { 208 tpl.Execute(w, struct {
135 Title string 209 Title string
136 URI string 210 URI string
137 Assets AssetList 211 Assets AssetList
138 RawText string 212 RawText string
139 Lines []Item 213 Lines []Item
140 Error bool 214 Error bool
141 }{"", hostport, assetList, fmt.Sprintf("Error: %s", err), nil, true}) 215 Protocol string
216 }{"", hostport, assetList, fmt.Sprintf("Error: %s", err), nil, true, "gopher"})
142 return 217 return
143 } 218 }
144 219
@@ -153,13 +228,14 @@ func GopherHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, ass
153 228
154 if err != nil { 229 if err != nil {
155 tpl.Execute(w, struct { 230 tpl.Execute(w, struct {
156 Title string 231 Title string
157 URI string 232 URI string
158 Assets AssetList 233 Assets AssetList
159 RawText string 234 RawText string
160 Lines []Item 235 Lines []Item
161 Error bool 236 Error bool
162 }{uri, fmt.Sprintf("%s/%s", hostport, uri), assetList, fmt.Sprintf("Error: %s", err), nil, true}) 237 Protocol string
238 }{uri, fmt.Sprintf("%s/%s", hostport, uri), assetList, fmt.Sprintf("Error: %s", err), nil, true, "gopher"})
163 return 239 return
164 } 240 }
165 241
@@ -171,13 +247,14 @@ func GopherHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, ass
171 buf := new(bytes.Buffer) 247 buf := new(bytes.Buffer)
172 buf.ReadFrom(res.Body) 248 buf.ReadFrom(res.Body)
173 tpl.Execute(w, struct { 249 tpl.Execute(w, struct {
174 Title string 250 Title string
175 URI string 251 URI string
176 Assets AssetList 252 Assets AssetList
177 RawText string 253 RawText string
178 Lines []Item 254 Lines []Item
179 Error bool 255 Error bool
180 }{uri, fmt.Sprintf("%s/%s", hostport, uri), assetList, buf.String(), nil, false}) 256 Protocol string
257 }{uri, fmt.Sprintf("%s/%s", hostport, uri), assetList, buf.String(), nil, false, "gopher"})
181 } else if strings.HasPrefix(parts[1], "T") { 258 } else if strings.HasPrefix(parts[1], "T") {
182 _, _, err = vips.NewTransform(). 259 _, _, err = vips.NewTransform().
183 Load(res.Body). 260 Load(res.Body).
@@ -190,21 +267,123 @@ func GopherHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, ass
190 io.Copy(w, res.Body) 267 io.Copy(w, res.Body)
191 } 268 }
192 } else { 269 } else {
193 if err := renderDirectory(w, tpl, assetList, uri, hostport, res.Dir); err != nil { 270 if err := renderGopherDirectory(w, tpl, assetList, uri, hostport, res.Dir); err != nil {
194 tpl.Execute(w, struct { 271 tpl.Execute(w, struct {
195 Title string 272 Title string
196 URI string 273 URI string
197 Assets AssetList 274 Assets AssetList
198 RawText string 275 RawText string
199 Lines []Item 276 Lines []Item
200 Error bool 277 Error bool
201 }{uri, fmt.Sprintf("%s/%s", hostport, uri), assetList, fmt.Sprintf("Error: %s", err), nil, true}) 278 Protocol string
279 }{uri, fmt.Sprintf("%s/%s", hostport, uri), assetList, fmt.Sprintf("Error: %s", err), nil, true, "gopher"})
202 return 280 return
203 } 281 }
204 } 282 }
205 } 283 }
206} 284}
207 285
286func GeminiHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, assetList AssetList, robotsdebug bool, uri string) http.HandlerFunc {
287 return func(w http.ResponseWriter, req *http.Request) {
288 agent := req.UserAgent()
289 path := strings.TrimPrefix(req.URL.Path, "/gemini/")
290
291 if robotsdata != nil && robotsdebug && !robotsdata.TestAgent(path, agent) {
292 log.Printf("UserAgent %s ignored robots.txt", agent)
293 }
294
295 parts := strings.Split(path, "/")
296 hostport := parts[0]
297
298 if len(hostport) == 0 {
299 http.Redirect(w, req, "/"+uri, http.StatusFound)
300 return
301 }
302
303 var qs string
304
305 if req.URL.RawQuery != "" {
306 qs = fmt.Sprintf("?%s", url.QueryEscape(req.URL.RawQuery))
307 }
308
309 uri, err := url.QueryUnescape(strings.Join(parts[1:], "/"))
310 if err != nil {
311 tpl.Execute(w, struct {
312 Title string
313 URI string
314 Assets AssetList
315 RawText string
316 Lines []Item
317 Error bool
318 Protocol string
319 }{"", hostport, assetList, fmt.Sprintf("Error: %s", err), nil, true, "gemini"})
320 return
321 }
322
323 res, err := GeminiGet(
324 fmt.Sprintf(
325 "gemini://%s/%s%s",
326 hostport,
327 uri,
328 qs,
329 ),
330 )
331
332 if err != nil {
333 tpl.Execute(w, struct {
334 Title string
335 URI string
336 Assets AssetList
337 RawText string
338 Lines []Item
339 Error bool
340 Protocol string
341 }{uri, fmt.Sprintf("%s/%s", hostport, uri), assetList, fmt.Sprintf("Error: %s", err), nil, true, "gemini"})
342 return
343 }
344
345 if int(res.Header.Status/10) != 2 {
346 tpl.Execute(w, struct {
347 Title string
348 URI string
349 Assets AssetList
350 RawText string
351 Lines []Item
352 Error bool
353 Protocol string
354 }{uri, fmt.Sprintf("%s/%s", hostport, uri), assetList, fmt.Sprintf("Error %d: %s", res.Header.Status, res.Header.Meta), nil, true, "gemini"})
355 return
356 }
357
358 if strings.HasPrefix(res.Header.Meta, MIME_GEMINI) {
359 items := parseGeminiDocument(res, uri, hostport)
360 tpl.Execute(w, struct {
361 Title string
362 URI string
363 Assets AssetList
364 RawText string
365 Lines []Item
366 Error bool
367 Protocol string
368 }{uri, fmt.Sprintf("%s/%s", hostport, uri), assetList, "", items, false, "gemini"})
369 } else if strings.HasPrefix(res.Header.Meta, "text/") {
370 buf := new(bytes.Buffer)
371 buf.ReadFrom(res.Body)
372 tpl.Execute(w, struct {
373 Title string
374 URI string
375 Assets AssetList
376 RawText string
377 Lines []Item
378 Error bool
379 Protocol string
380 }{uri, fmt.Sprintf("%s/%s", hostport, uri), assetList, buf.String(), nil, false, "gemini"})
381 } else {
382 io.Copy(w, res.Body)
383 }
384 }
385}
386
208// RobotsTxtHandler returns the contents of the robots.txt file 387// RobotsTxtHandler returns the contents of the robots.txt file
209// if configured and valid. 388// if configured and valid.
210func RobotsTxtHandler(robotstxtdata []byte) http.HandlerFunc { 389func RobotsTxtHandler(robotstxtdata []byte) http.HandlerFunc {
@@ -386,7 +565,9 @@ func ListenAndServe(bind, robotsfile string, robotsdebug bool, vipsconcurrency i
386 ConcurrencyLevel: vipsconcurrency, 565 ConcurrencyLevel: vipsconcurrency,
387 }) 566 })
388 567
389 http.Handle("/", gziphandler.GzipHandler(GopherHandler(tpl, robotsdata, AssetList{styleAsset, jsAsset, fontwAsset, fontw2Asset, propfontwAsset, propfontw2Asset}, robotsdebug, uri))) 568 http.Handle("/", gziphandler.GzipHandler(DefaultHandler(tpl, uri)))
569 http.Handle("/gopher/", gziphandler.GzipHandler(GopherHandler(tpl, robotsdata, AssetList{styleAsset, jsAsset, fontwAsset, fontw2Asset, propfontwAsset, propfontw2Asset}, robotsdebug, uri)))
570 http.Handle("/gemini/", gziphandler.GzipHandler(GeminiHandler(tpl, robotsdata, AssetList{styleAsset, jsAsset, fontwAsset, fontw2Asset, propfontwAsset, propfontw2Asset}, robotsdebug, uri)))
390 http.Handle("/robots.txt", gziphandler.GzipHandler(RobotsTxtHandler(robotstxtdata))) 571 http.Handle("/robots.txt", gziphandler.GzipHandler(RobotsTxtHandler(robotstxtdata)))
391 http.Handle("/favicon.ico", gziphandler.GzipHandler(FaviconHandler(favicondata))) 572 http.Handle("/favicon.ico", gziphandler.GzipHandler(FaviconHandler(favicondata)))
392 http.Handle(styleAsset, gziphandler.GzipHandler(StyleHandler(styledata))) 573 http.Handle(styleAsset, gziphandler.GzipHandler(StyleHandler(styledata)))