package gopherproxy import ( "bytes" "fmt" "html/template" "io" "io/ioutil" "log" "net/http" "net/url" "strings" gopher "github.com/prologic/go-gopher" "github.com/temoto/robotstxt" ) type Item struct { Link template.URL Type string Text string } func renderDirectory(w http.ResponseWriter, tpl *template.Template, hostport string, d gopher.Directory) error { var title string out := make([]Item, len(d.Items)) for i, x := range d.Items { if x.Type == gopher.INFO && x.Selector == "TITLE" { title = x.Description continue } tr := Item{ 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) } path := url.PathEscape(x.Selector) path = strings.Replace(path, "%2F", "/", -1) tr.Link = template.URL( fmt.Sprintf( "/%s/%s%s", hostport, string(byte(x.Type)), path, ), ) } out[i] = tr } if title == "" { title = hostport } return tpl.Execute(w, struct { Title string Lines []Item Gophermap bool }{title, out, true}) } // GopherHandler returns a Handler that proxies requests // to the specified Gopher server as denoated by the first argument // to the request path and renders the content using the provided template. // 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 { return func(w http.ResponseWriter, req *http.Request) { 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, "/") hostport := parts[0] if len(hostport) == 0 { http.Redirect(w, req, "/"+uri, http.StatusFound) return } var qs string if req.URL.RawQuery != "" { qs = fmt.Sprintf("?%s", url.QueryEscape(req.URL.RawQuery)) } uri, err := url.QueryUnescape(strings.Join(parts[1:], "/")) if err != nil { io.WriteString(w, fmt.Sprintf("Error:
%s
", err)) return } res, err := gopher.Get( fmt.Sprintf( "gopher://%s/%s%s", hostport, uri, qs, ), ) if err != nil { io.WriteString(w, fmt.Sprintf("Error:
%s
", err)) return } if res.Body != nil { 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) } } else { if err := renderDirectory(w, tpl, hostport, res.Dir); err != nil { io.WriteString(w, fmt.Sprintf("Error:
%s
", err)) return } } } } // 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) } } // 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 // 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 } } tpldata, err := ioutil.ReadFile(".template") if err == nil { tpltext = string(tpldata) } tpl, err = template.New("gophermenu").Parse(tpltext) if err != nil { log.Fatal(err) } http.HandleFunc("/", GopherHandler(tpl, robotsdata, uri)) http.HandleFunc("/robots.txt", RobotsTxtHandler(robotstxtdata)) return http.ListenAndServe(bind, nil) }