diff options
-rw-r--r-- | cmd/gopherproxy/main.go | 19 | ||||
-rw-r--r-- | gopherproxy.go | 131 | ||||
-rw-r--r-- | main.go | 126 | ||||
-rw-r--r-- | template.go | 2 |
4 files changed, 151 insertions, 127 deletions
diff --git a/cmd/gopherproxy/main.go b/cmd/gopherproxy/main.go new file mode 100644 index 0000000..bc5050e --- /dev/null +++ b/cmd/gopherproxy/main.go | |||
@@ -0,0 +1,19 @@ | |||
1 | package main | ||
2 | |||
3 | import ( | ||
4 | "flag" | ||
5 | "log" | ||
6 | |||
7 | "github.com/prologic/gopherproxy" | ||
8 | ) | ||
9 | |||
10 | var ( | ||
11 | bind = flag.String("bind", ":80", "[int]:port to bind to") | ||
12 | uri = flag.String("uri", "127.0.0.1:70", "<host>:[port] to proxy to") | ||
13 | ) | ||
14 | |||
15 | func main() { | ||
16 | flag.Parse() | ||
17 | |||
18 | log.Fatal(gopherproxy.ListenAndServe(*bind, *uri)) | ||
19 | } | ||
diff --git a/gopherproxy.go b/gopherproxy.go new file mode 100644 index 0000000..4a2255f --- /dev/null +++ b/gopherproxy.go | |||
@@ -0,0 +1,131 @@ | |||
1 | package gopherproxy | ||
2 | |||
3 | import ( | ||
4 | "fmt" | ||
5 | "html/template" | ||
6 | "io" | ||
7 | "io/ioutil" | ||
8 | "log" | ||
9 | "net/http" | ||
10 | "net/url" | ||
11 | "strings" | ||
12 | |||
13 | "github.com/prologic/go-gopher" | ||
14 | ) | ||
15 | |||
16 | type tplRow struct { | ||
17 | Link template.URL | ||
18 | Type string | ||
19 | Text string | ||
20 | } | ||
21 | |||
22 | // Handler is an aliased type for the standard HTTP handler functions | ||
23 | type Handler func(w http.ResponseWriter, req *http.Request) | ||
24 | |||
25 | func renderDirectory(w http.ResponseWriter, tpl *template.Template, hostport string, d gopher.Directory) error { | ||
26 | out := make([]tplRow, len(d)) | ||
27 | |||
28 | for i, x := range d { | ||
29 | tr := tplRow{ | ||
30 | Text: x.Description, | ||
31 | Type: x.Type.String(), | ||
32 | } | ||
33 | |||
34 | if x.Type == gopher.INFO { | ||
35 | out[i] = tr | ||
36 | continue | ||
37 | } | ||
38 | |||
39 | if strings.HasPrefix(x.Selector, "URL:") { | ||
40 | tr.Link = template.URL(x.Selector[4:]) | ||
41 | } else { | ||
42 | var hostport string | ||
43 | if x.Port == 70 { | ||
44 | hostport = x.Host | ||
45 | } else { | ||
46 | hostport = fmt.Sprintf("%s:%d", x.Host, x.Port) | ||
47 | } | ||
48 | path := url.QueryEscape(x.Selector) | ||
49 | path = strings.Replace(path, "%2F", "/", -1) | ||
50 | tr.Link = template.URL( | ||
51 | fmt.Sprintf( | ||
52 | "/%s/%s%s", | ||
53 | hostport, | ||
54 | string(byte(x.Type)), | ||
55 | path, | ||
56 | ), | ||
57 | ) | ||
58 | } | ||
59 | |||
60 | out[i] = tr | ||
61 | } | ||
62 | |||
63 | return tpl.Execute(w, struct { | ||
64 | Title string | ||
65 | Lines []tplRow | ||
66 | }{hostport, out}) | ||
67 | } | ||
68 | |||
69 | // MakeGopherProxyHandler returns a Handler that proxies requests | ||
70 | // to the specified Gopher server as denoated by the first argument | ||
71 | // to the request path and renders the content using the provided template. | ||
72 | func MakeGopherProxyHandler(tpl *template.Template, uri string) Handler { | ||
73 | return func(w http.ResponseWriter, req *http.Request) { | ||
74 | parts := strings.Split(strings.TrimPrefix(req.URL.Path, "/"), "/") | ||
75 | hostport := parts[0] | ||
76 | path := strings.Join(parts[1:], "/") | ||
77 | |||
78 | if len(hostport) == 0 { | ||
79 | http.Redirect(w, req, "/"+uri, http.StatusFound) | ||
80 | return | ||
81 | } | ||
82 | |||
83 | uri, err := url.QueryUnescape(path) | ||
84 | if err != nil { | ||
85 | io.WriteString(w, fmt.Sprintf("<b>Error:</b><pre>%s</pre>", err)) | ||
86 | return | ||
87 | } | ||
88 | res, err := gopher.Get( | ||
89 | fmt.Sprintf( | ||
90 | "gopher://%s/%s", | ||
91 | hostport, | ||
92 | uri, | ||
93 | ), | ||
94 | ) | ||
95 | if err != nil { | ||
96 | io.WriteString(w, fmt.Sprintf("<b>Error:</b><pre>%s</pre>", err)) | ||
97 | return | ||
98 | } | ||
99 | |||
100 | if res.Body != nil { | ||
101 | io.Copy(w, res.Body) | ||
102 | } else { | ||
103 | if err := renderDirectory(w, tpl, hostport, res.Dir); err != nil { | ||
104 | io.WriteString(w, fmt.Sprintf("<b>Error:</b><pre>%s</pre>", err)) | ||
105 | return | ||
106 | } | ||
107 | } | ||
108 | } | ||
109 | } | ||
110 | |||
111 | // ListenAndServe creates a listening HTTP server bound to | ||
112 | // the interface specified by bind and sets up a Gopher to HTTP | ||
113 | // proxy proxying requests as requested and by default will prozy | ||
114 | // to a Gopher server address specified by uri if no servers is | ||
115 | // specified by the request. | ||
116 | func ListenAndServe(bind, uri string) error { | ||
117 | var tpl *template.Template | ||
118 | |||
119 | tpldata, err := ioutil.ReadFile(".template") | ||
120 | if err == nil { | ||
121 | tpltext = string(tpldata) | ||
122 | } | ||
123 | |||
124 | tpl, err = template.New("gophermenu").Parse(tpltext) | ||
125 | if err != nil { | ||
126 | log.Fatal(err) | ||
127 | } | ||
128 | |||
129 | http.HandleFunc("/", MakeGopherProxyHandler(tpl, uri)) | ||
130 | return http.ListenAndServe(bind, nil) | ||
131 | } | ||
diff --git a/main.go b/main.go deleted file mode 100644 index 1da5f8e..0000000 --- a/main.go +++ /dev/null | |||
@@ -1,126 +0,0 @@ | |||
1 | package main | ||
2 | |||
3 | import ( | ||
4 | "flag" | ||
5 | "fmt" | ||
6 | "html/template" | ||
7 | "io" | ||
8 | "io/ioutil" | ||
9 | "log" | ||
10 | "net/http" | ||
11 | "net/url" | ||
12 | "strings" | ||
13 | |||
14 | "github.com/prologic/go-gopher" | ||
15 | ) | ||
16 | |||
17 | var ( | ||
18 | bind = flag.String("bind", ":80", "[int]:port to bind to") | ||
19 | uri = flag.String("uri", "127.0.0.1:70", "<host>:[port] to proxy to") | ||
20 | |||
21 | tpl *template.Template | ||
22 | ) | ||
23 | |||
24 | type tplRow struct { | ||
25 | Link template.URL | ||
26 | Type string | ||
27 | Text string | ||
28 | } | ||
29 | |||
30 | func renderDirectory(w http.ResponseWriter, tpl *template.Template, hostport string, d gopher.Directory) error { | ||
31 | out := make([]tplRow, len(d)) | ||
32 | |||
33 | for i, x := range d { | ||
34 | tr := tplRow{ | ||
35 | Text: x.Description, | ||
36 | Type: x.Type.String(), | ||
37 | } | ||
38 | |||
39 | if x.Type == gopher.INFO { | ||
40 | out[i] = tr | ||
41 | continue | ||
42 | } | ||
43 | |||
44 | if strings.HasPrefix(x.Selector, "URL:") { | ||
45 | tr.Link = template.URL(x.Selector[4:]) | ||
46 | } else { | ||
47 | var hostport string | ||
48 | if x.Port == 70 { | ||
49 | hostport = x.Host | ||
50 | } else { | ||
51 | hostport = fmt.Sprintf("%s:%d", x.Host, x.Port) | ||
52 | } | ||
53 | path := url.QueryEscape(x.Selector) | ||
54 | path = strings.Replace(path, "%2F", "/", -1) | ||
55 | tr.Link = template.URL( | ||
56 | fmt.Sprintf( | ||
57 | "/%s/%s%s", | ||
58 | hostport, | ||
59 | string(byte(x.Type)), | ||
60 | path, | ||
61 | ), | ||
62 | ) | ||
63 | } | ||
64 | |||
65 | out[i] = tr | ||
66 | } | ||
67 | |||
68 | return tpl.Execute(w, struct { | ||
69 | Title string | ||
70 | Lines []tplRow | ||
71 | }{hostport, out}) | ||
72 | } | ||
73 | |||
74 | func proxy(w http.ResponseWriter, req *http.Request) { | ||
75 | parts := strings.Split(strings.TrimPrefix(req.URL.Path, "/"), "/") | ||
76 | hostport := parts[0] | ||
77 | path := strings.Join(parts[1:], "/") | ||
78 | |||
79 | if len(hostport) == 0 { | ||
80 | http.Redirect(w, req, "/"+*uri, http.StatusFound) | ||
81 | return | ||
82 | } | ||
83 | |||
84 | uri, err := url.QueryUnescape(path) | ||
85 | if err != nil { | ||
86 | io.WriteString(w, fmt.Sprintf("<b>Error:</b><pre>%s</pre>", err)) | ||
87 | return | ||
88 | } | ||
89 | res, err := gopher.Get( | ||
90 | fmt.Sprintf( | ||
91 | "gopher://%s/%s", | ||
92 | hostport, | ||
93 | uri, | ||
94 | ), | ||
95 | ) | ||
96 | if err != nil { | ||
97 | io.WriteString(w, fmt.Sprintf("<b>Error:</b><pre>%s</pre>", err)) | ||
98 | return | ||
99 | } | ||
100 | |||
101 | if res.Body != nil { | ||
102 | io.Copy(w, res.Body) | ||
103 | } else { | ||
104 | if err := renderDirectory(w, tpl, hostport, res.Dir); err != nil { | ||
105 | io.WriteString(w, fmt.Sprintf("<b>Error:</b><pre>%s</pre>", err)) | ||
106 | return | ||
107 | } | ||
108 | } | ||
109 | } | ||
110 | |||
111 | func main() { | ||
112 | flag.Parse() | ||
113 | |||
114 | tpldata, err := ioutil.ReadFile(".template") | ||
115 | if err == nil { | ||
116 | tpltext = string(tpldata) | ||
117 | } | ||
118 | |||
119 | tpl, err = template.New("gophermenu").Parse(tpltext) | ||
120 | if err != nil { | ||
121 | log.Fatal(err) | ||
122 | } | ||
123 | |||
124 | http.HandleFunc("/", proxy) | ||
125 | log.Fatal(http.ListenAndServe(*bind, nil)) | ||
126 | } | ||
diff --git a/template.go b/template.go index 514ff30..604f380 100644 --- a/template.go +++ b/template.go | |||
@@ -1,4 +1,4 @@ | |||
1 | package main | 1 | package gopherproxy |
2 | 2 | ||
3 | var tpltext = `<!doctype html> | 3 | var tpltext = `<!doctype html> |
4 | <html> | 4 | <html> |