2016-10-01 02:40:24 +00:00
|
|
|
package gopherproxy
|
|
|
|
|
|
|
|
import (
|
2019-07-31 17:30:57 +00:00
|
|
|
"bytes"
|
2016-10-01 02:40:24 +00:00
|
|
|
"fmt"
|
|
|
|
"html/template"
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"log"
|
|
|
|
"net/http"
|
|
|
|
"net/url"
|
|
|
|
"strings"
|
|
|
|
|
2019-07-31 17:30:57 +00:00
|
|
|
gopher "github.com/prologic/go-gopher"
|
2018-04-03 21:32:43 +00:00
|
|
|
"github.com/temoto/robotstxt"
|
2016-10-01 02:40:24 +00:00
|
|
|
)
|
|
|
|
|
2017-10-15 20:16:23 +00:00
|
|
|
type Item struct {
|
2016-10-01 02:40:24 +00:00
|
|
|
Link template.URL
|
|
|
|
Type string
|
|
|
|
Text string
|
|
|
|
}
|
|
|
|
|
|
|
|
func renderDirectory(w http.ResponseWriter, tpl *template.Template, hostport string, d gopher.Directory) error {
|
2017-06-26 08:48:07 +00:00
|
|
|
var title string
|
|
|
|
|
2017-10-15 20:16:23 +00:00
|
|
|
out := make([]Item, len(d.Items))
|
2017-06-26 08:48:07 +00:00
|
|
|
|
|
|
|
for i, x := range d.Items {
|
|
|
|
if x.Type == gopher.INFO && x.Selector == "TITLE" {
|
|
|
|
title = x.Description
|
|
|
|
continue
|
|
|
|
}
|
2016-10-01 02:40:24 +00:00
|
|
|
|
2017-10-15 20:16:23 +00:00
|
|
|
tr := Item{
|
2016-10-01 02:40:24 +00:00
|
|
|
Text: x.Description,
|
|
|
|
Type: x.Type.String(),
|
|
|
|
}
|
|
|
|
|
|
|
|
if x.Type == gopher.INFO {
|
|
|
|
out[i] = tr
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if strings.HasPrefix(x.Selector, "URL:") {
|
|
|
|
tr.Link = template.URL(x.Selector[4:])
|
|
|
|
} else {
|
|
|
|
var hostport string
|
|
|
|
if x.Port == 70 {
|
|
|
|
hostport = x.Host
|
|
|
|
} else {
|
|
|
|
hostport = fmt.Sprintf("%s:%d", x.Host, x.Port)
|
|
|
|
}
|
2017-06-13 08:29:12 +00:00
|
|
|
path := url.PathEscape(x.Selector)
|
2016-10-01 02:40:24 +00:00
|
|
|
path = strings.Replace(path, "%2F", "/", -1)
|
|
|
|
tr.Link = template.URL(
|
|
|
|
fmt.Sprintf(
|
|
|
|
"/%s/%s%s",
|
|
|
|
hostport,
|
|
|
|
string(byte(x.Type)),
|
|
|
|
path,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
out[i] = tr
|
|
|
|
}
|
|
|
|
|
2017-06-26 08:48:07 +00:00
|
|
|
if title == "" {
|
|
|
|
title = hostport
|
|
|
|
}
|
|
|
|
|
2016-10-01 02:40:24 +00:00
|
|
|
return tpl.Execute(w, struct {
|
2019-07-31 17:30:57 +00:00
|
|
|
Title string
|
|
|
|
Lines []Item
|
|
|
|
Gophermap bool
|
|
|
|
}{title, out, true})
|
2016-10-01 02:40:24 +00:00
|
|
|
}
|
|
|
|
|
2018-04-03 21:32:43 +00:00
|
|
|
// GopherHandler returns a Handler that proxies requests
|
2016-10-01 02:40:24 +00:00
|
|
|
// to the specified Gopher server as denoated by the first argument
|
|
|
|
// to the request path and renders the content using the provided template.
|
2018-04-03 21:32:43 +00:00
|
|
|
// The optional robots parameters points to a robotstxt.RobotsData struct
|
|
|
|
// to test user agents against a configurable robotst.txt file.
|
|
|
|
func GopherHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, uri string) http.HandlerFunc {
|
2016-10-01 02:40:24 +00:00
|
|
|
return func(w http.ResponseWriter, req *http.Request) {
|
2018-04-03 21:32:43 +00:00
|
|
|
agent := req.UserAgent()
|
|
|
|
path := strings.TrimPrefix(req.URL.Path, "/")
|
|
|
|
|
|
|
|
if robotsdata != nil && !robotsdata.TestAgent(path, agent) {
|
|
|
|
log.Printf("UserAgent %s ignored robots.txt", agent)
|
|
|
|
}
|
|
|
|
|
|
|
|
parts := strings.Split(path, "/")
|
2016-10-01 02:40:24 +00:00
|
|
|
hostport := parts[0]
|
|
|
|
|
|
|
|
if len(hostport) == 0 {
|
|
|
|
http.Redirect(w, req, "/"+uri, http.StatusFound)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-06-13 08:29:12 +00:00
|
|
|
var qs string
|
|
|
|
|
|
|
|
if req.URL.RawQuery != "" {
|
|
|
|
qs = fmt.Sprintf("?%s", url.QueryEscape(req.URL.RawQuery))
|
|
|
|
}
|
|
|
|
|
2018-04-03 21:32:43 +00:00
|
|
|
uri, err := url.QueryUnescape(strings.Join(parts[1:], "/"))
|
2016-10-01 02:40:24 +00:00
|
|
|
if err != nil {
|
|
|
|
io.WriteString(w, fmt.Sprintf("<b>Error:</b><pre>%s</pre>", err))
|
|
|
|
return
|
|
|
|
}
|
2017-06-13 08:29:12 +00:00
|
|
|
|
2016-10-01 02:40:24 +00:00
|
|
|
res, err := gopher.Get(
|
|
|
|
fmt.Sprintf(
|
2017-06-13 08:29:12 +00:00
|
|
|
"gopher://%s/%s%s",
|
2016-10-01 02:40:24 +00:00
|
|
|
hostport,
|
|
|
|
uri,
|
2017-06-13 08:29:12 +00:00
|
|
|
qs,
|
2016-10-01 02:40:24 +00:00
|
|
|
),
|
|
|
|
)
|
2017-06-13 08:29:12 +00:00
|
|
|
|
2016-10-01 02:40:24 +00:00
|
|
|
if err != nil {
|
|
|
|
io.WriteString(w, fmt.Sprintf("<b>Error:</b><pre>%s</pre>", err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if res.Body != nil {
|
2019-07-31 17:30:57 +00:00
|
|
|
if strings.HasSuffix(uri, ".md") {
|
|
|
|
// handle markdown
|
|
|
|
tpl.Execute(w, struct {
|
|
|
|
Title string
|
|
|
|
MdText template.HTML
|
|
|
|
Gophermap bool
|
|
|
|
Pre bool
|
|
|
|
}{uri, renderMd(res.Body), false, false})
|
|
|
|
|
|
|
|
} else if strings.HasSuffix(uri, ".txt") {
|
|
|
|
// handle .txt files
|
|
|
|
buf := new(bytes.Buffer)
|
|
|
|
buf.ReadFrom(res.Body)
|
|
|
|
tpl.Execute(w, struct {
|
|
|
|
Title string
|
|
|
|
MdText string
|
|
|
|
Gophermap bool
|
|
|
|
Pre bool
|
|
|
|
}{uri, buf.String(), false, true})
|
|
|
|
} else {
|
|
|
|
io.Copy(w, res.Body)
|
|
|
|
}
|
2016-10-01 02:40:24 +00:00
|
|
|
} else {
|
|
|
|
if err := renderDirectory(w, tpl, hostport, res.Dir); err != nil {
|
|
|
|
io.WriteString(w, fmt.Sprintf("<b>Error:</b><pre>%s</pre>", err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-03 21:32:43 +00:00
|
|
|
// RobotsTxtHandler returns the contents of the robots.txt file
|
|
|
|
// if configured and valid.
|
|
|
|
func RobotsTxtHandler(robotstxtdata []byte) http.HandlerFunc {
|
|
|
|
return func(w http.ResponseWriter, req *http.Request) {
|
|
|
|
if robotstxtdata == nil {
|
|
|
|
http.Error(w, "Not Found", http.StatusNotFound)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
w.Header().Set("Content-Type", "text/plain")
|
|
|
|
w.Write(robotstxtdata)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-01 02:40:24 +00:00
|
|
|
// ListenAndServe creates a listening HTTP server bound to
|
|
|
|
// the interface specified by bind and sets up a Gopher to HTTP
|
|
|
|
// proxy proxying requests as requested and by default will prozy
|
|
|
|
// to a Gopher server address specified by uri if no servers is
|
2018-04-03 21:32:43 +00:00
|
|
|
// specified by the request. The robots argument is a pointer to
|
|
|
|
// a robotstxt.RobotsData struct for testing user agents against
|
|
|
|
// a configurable robots.txt file.
|
|
|
|
func ListenAndServe(bind, robotsfile, uri string) error {
|
|
|
|
var (
|
|
|
|
tpl *template.Template
|
|
|
|
robotsdata *robotstxt.RobotsData
|
|
|
|
)
|
|
|
|
|
|
|
|
robotstxtdata, err := ioutil.ReadFile(robotsfile)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("error reading robots.txt: %s", err)
|
|
|
|
robotstxtdata = nil
|
|
|
|
} else {
|
|
|
|
robotsdata, err = robotstxt.FromBytes(robotstxtdata)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("error reading robots.txt: %s", err)
|
|
|
|
robotstxtdata = nil
|
|
|
|
}
|
|
|
|
}
|
2016-10-01 02:40:24 +00:00
|
|
|
|
|
|
|
tpldata, err := ioutil.ReadFile(".template")
|
|
|
|
if err == nil {
|
|
|
|
tpltext = string(tpldata)
|
|
|
|
}
|
|
|
|
|
|
|
|
tpl, err = template.New("gophermenu").Parse(tpltext)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2018-04-03 21:32:43 +00:00
|
|
|
http.HandleFunc("/", GopherHandler(tpl, robotsdata, uri))
|
|
|
|
http.HandleFunc("/robots.txt", RobotsTxtHandler(robotstxtdata))
|
|
|
|
|
2016-10-01 02:40:24 +00:00
|
|
|
return http.ListenAndServe(bind, nil)
|
|
|
|
}
|