GOPHERSPACE.DE - P H O X Y
gophering on gopher.mills.io
// Package gopher provides an implementation of the Gopher protocol (RFC 1436)
//
// Much of the API is similar in design to the net/http package of the
// standard library. To build custom Gopher servers implement handler
// functions or the `Handler{}` interface. Implementing a client is as
// simple as calling `gopher.Get(uri)` and passing in a `uri` such as
// `"gopher://gopher.floodgap.com/"`.
package gopher

import (
"bufio"
"bytes"
"crypto/rand"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"net"
"net/http"
"net/url"
"os"
"path"
"path/filepath"
"sort"
"strconv"
"strings"
"sync"

"golang.org/x/net/context"
)

// Item Types
const (
FILE        = ItemType('0') // Item is a file
DIRECTORY   = ItemType('1') // Item is a directory
PHONEBOOK   = ItemType('2') // Item is a CSO phone-book server
ERROR       = ItemType('3') // Error
BINHEX      = ItemType('4') // Item is a BinHexed Macintosh file.
DOSARCHIVE  = ItemType('5') // Item is DOS binary archive of some sort. (*)
UUENCODED   = ItemType('6') // Item is a UNIX uuencoded file.
INDEXSEARCH = ItemType('7') // Item is an Index-Search server.
TELNET      = ItemType('8') // Item points to a text-based telnet session.
BINARY      = ItemType('9') // Item is a binary file! (*)

// (*) Client must read until the TCP connection is closed.

REDUNDANT = ItemType('+') // Item is a redundant server
TN3270    = ItemType('T') // Item points to a text-based tn3270 session.
GIF       = ItemType('g') // Item is a GIF format graphics file.
IMAGE     = ItemType('I') // Item is some kind of image file.

// non-standard
INFO  = ItemType('i') // Item is an informational message
HTML  = ItemType('h') // Item is a HTML document
AUDIO = ItemType('s') // Item is an Audio file
PNG   = ItemType('p') // Item is a PNG Image
DOC   = ItemType('d') // Item is a Document
)

const (
// END represents the terminator used in directory responses
END = byte('.')

// TAB is the delimiter used to separate item response parts
TAB = byte('\t')

// CRLF is the delimiter used per line of response item
CRLF = "\r\n"

// DEFAULT is the default item type
DEFAULT = BINARY
)

// contextKey is a value for use with context.WithValue. It's used as
// a pointer so it fits in an interface{} without allocation.
type contextKey struct {
name string
}

func (k *contextKey) String() string {
return "gopher context value " + k.name
}

var (
// ServerContextKey is a context key. It can be used in Gopher
// handlers with context.WithValue to access the server that
// started the handler. The associated value will be of type *Server.
ServerContextKey = &contextKey{"gopher-server"}

// LocalAddrContextKey is a context key. It can be used in
// Gopher handlers with context.WithValue to access the address
// the local address the connection arrived on.
// The associated value will be of type net.Addr.
LocalAddrContextKey = &contextKey{"local-addr"}
)

// ItemType represents the type of an item
type ItemType byte

// MarshalJSON returns a JSON mashaled byte array of an ItemType
func (it ItemType) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf("%q", string(byte(it)))), nil
}

// Return a human friendly represation of an ItemType
func (it ItemType) String() string {
switch it {
case FILE:
return "TXT"
case DIRECTORY:
return "DIR"
case PHONEBOOK:
return "PHO"
case ERROR:
return "ERR"
case BINHEX:
return "HEX"
case DOSARCHIVE:
return "ARC"
case UUENCODED:
return "UUE"
case INDEXSEARCH:
return "QRY"
case TELNET:
return "TEL"
case BINARY:
return "BIN"
case REDUNDANT:
return "DUP"
case TN3270:
return "TN3"
case GIF:
return "GIF"
case IMAGE:
return "IMG"
case INFO:
return "NFO"
case HTML:
return "HTM"
case AUDIO:
return "SND"
case PNG:
return "PNG"
case DOC:
return "DOC"
default:
return "???"
}
}

// Item describes an entry in a directory listing.
type Item struct {
Type        ItemType
Description string
Selector    string
Host        string
Port        int

// non-standard extensions (ignored by standard clients)
Extras []string
}

// MarshalText serializes an Item into an array of bytes
func (i Item) MarshalText() ([]byte, error) {
b := []byte{}
b = append(b, byte(i.Type))
b = append(b, []byte(i.Description)...)
b = append(b, TAB)
b = append(b, []byte(i.Selector)...)
b = append(b, TAB)
b = append(b, []byte(i.Host)...)
b = append(b, TAB)
b = append(b, []byte(strconv.Itoa(i.Port))...)

for _, s := range i.Extras {
b = append(b, TAB)
b = append(b, []byte(s)...)
}

b = append(b, []byte(CRLF)...)

return b, nil
}

func (i *Item) parse(line string) error {
parts := strings.Split(line, "\t")

if len(parts[0]) < 1 {
return errors.New("no item type: " + string(line))
}

i.Type = ItemType(parts[0][0])
i.Description = string(parts[0][1:])

if len(parts) > 1 {
i.Selector = string(parts[1])
} else {
i.Selector = ""
}

if len(parts) > 2 {
i.Host = string(parts[2])
} else {
i.Host = "null.host"
}

if len(parts) > 3 {
port, err := strconv.Atoi(string(parts[3]))
if err != nil {
// Ignore parsing errors for bad servers for INFO types
if i.Type != INFO {
return err
}
i.Port = 0
}
i.Port = port
} else {
i.Port = 0
}

if len(parts) >= 4 {
for _, v := range parts[4:] {
i.Extras = append(i.Extras, string(v))
}
}

return nil
}

func (i *Item) isDirectoryLike() bool {
switch i.Type {
case DIRECTORY:
return true
case INDEXSEARCH:
return true
default:
return false
}
}

// Directory representes a Gopher Menu of Items
type Directory []Item

// ToJSON returns the Directory as JSON bytes
func (d *Directory) ToJSON() ([]byte, error) {
jsonBytes, err := json.Marshal(d)
return jsonBytes, err
}

// ToText returns the Directory as UTF-8 encoded bytes
func (d *Directory) ToText() ([]byte, error) {
var buffer bytes.Buffer
for _, i := range *d {
val, err := i.MarshalText()
if err != nil {
return nil, err
}
buffer.Write(val)
}
return buffer.Bytes(), nil
}

// Response represents a Gopher resource that
// Items contains a non-empty array of Item(s)
// for directory types, otherwise the Body
// contains the fetched resource (file, image, etc).
type Response struct {
Type ItemType
Dir  Directory
Body io.Reader
}

// Get fetches a Gopher resource by URI
func Get(uri string) (*Response, error) {
u, err := url.Parse(uri)
if err != nil {
return nil, err
}

if u.Scheme != "gopher" {
return nil, errors.New("invalid scheme for uri")
}

var (
host string
port int
)

hostport := strings.Split(u.Host, ":")
if len(hostport) == 2 {
host = hostport[0]
n, err := strconv.ParseInt(hostport[1], 10, 32)
if err != nil {
return nil, err
}
port = int(n)
} else {
host, port = hostport[0], 70
}

var (
Type     ItemType
Selector string
)

path := strings.TrimPrefix(u.Path, "/")
if len(path) > 2 {
Type = ItemType(path[0])
Selector = path[1:]
if u.RawQuery != "" {
Selector += "\t" + u.RawQuery
}
} else if len(path) == 1 {
Type = ItemType(path[0])
Selector = ""
} else {
Type = ItemType(DIRECTORY)
Selector = ""
}

i := Item{Type: Type, Selector: Selector, Host: host, Port: port}
res := Response{Type: i.Type}

if i.isDirectoryLike() {
d, err := i.FetchDirectory()
if err != nil {
return nil, err
}

res.Dir = d
} else {
reader, err := i.FetchFile()
if err != nil {
return nil, err
}

res.Body = reader
}

return &res, nil
}

// FetchFile fetches data, not directory information.
// Calling this on a DIRECTORY Item type
// or unsupported type will return an error.
func (i *Item) FetchFile() (io.Reader, error) {
if i.Type == DIRECTORY {
return nil, errors.New("cannot fetch a directory as a file")
}

conn, err := net.Dial("tcp", i.Host+":"+strconv.Itoa(i.Port))
if err != nil {
return nil, err
}

_, err = conn.Write([]byte(i.Selector + CRLF))
if err != nil {
conn.Close()
return nil, err
}

return conn, nil
}

// FetchDirectory fetches directory information, not data.
// Calling this on an Item whose type is not DIRECTORY will return an error.
func (i *Item) FetchDirectory() (Directory, error) {
if !i.isDirectoryLike() {
return nil, errors.New("cannot fetch a file as a directory")
}

conn, err := net.Dial("tcp", i.Host+":"+strconv.Itoa(i.Port))
if err != nil {
return nil, err
}

_, err = conn.Write([]byte(i.Selector + CRLF))
if err != nil {
return nil, err
}

var d Directory

reader := bufio.NewReader(conn)
scanner := bufio.NewScanner(reader)
scanner.Split(bufio.ScanLines)

for scanner.Scan() {
line := strings.Trim(scanner.Text(), "\r\n")

if len(line) == 0 {
continue
}

if len(line) == 1 && line[0] == END {
break
}

var i Item
err := i.parse(line)
if err != nil {
log.Printf("Error parsing %q: %q", line, err)
continue
}
d = append(d, i)
}

return d, nil
}

// Request repsesnts an inbound request to a listening server.
// LocalHost and LocalPort may be used by the Handler for local links.
// These are specified in the call to ListenAndServe.
type Request struct {
conn      net.Conn
Selector  string
LocalHost string
LocalPort int
}

// A Handler responds to a Gopher request.
//
// ServeGopher should write data or items to the ResponseWriter
// and then return. Returning signals that the request is finished; it
// is not valid to use the ResponseWriter concurrently with the completion
// of the ServeGopher call.
//
// Handlers should not modify the provided request.
//
// If ServeGopher panics, the server (the caller of ServeGopher) assumes
// that the effect of the panic was isolated to the active request.
// It recovers the panic, logs a stack trace to the server error log,
// and hangs up the connection.
type Handler interface {
ServeGopher(ResponseWriter, *Request)
}

// FileExtensions defines a mapping of known file extensions to gopher types
var FileExtensions = map[string]ItemType{
".txt":  FILE,
".gif":  GIF,
".jpg":  IMAGE,
".jpeg": IMAGE,
".png":  IMAGE,
".html": HTML,
".ogg":  AUDIO,
".mp3":  AUDIO,
".wav":  AUDIO,
".mod":  AUDIO,
".it":   AUDIO,
".xm":   AUDIO,
".mid":  AUDIO,
".vgm":  AUDIO,
".s":    FILE,
".c":    FILE,
".py":   FILE,
".h":    FILE,
".md":   FILE,
".go":   FILE,
".fs":   FILE,
}

// MimeTypes defines a mapping of known mimetypes to gopher types
var MimeTypes = map[string]ItemType{
"text/html": HTML,
"text/*":    FILE,

"image/gif": GIF,
"image/*":   IMAGE,

"audio/*": AUDIO,

"application/x-tar":  DOSARCHIVE,
"application/x-gtar": DOSARCHIVE,

"application/x-xz":    DOSARCHIVE,
"application/x-zip":   DOSARCHIVE,
"application/x-gzip":  DOSARCHIVE,
"application/x-bzip2": DOSARCHIVE,
}

func matchExtension(f os.FileInfo) ItemType {
extension := strings.ToLower(filepath.Ext(f.Name()))
k, ok := FileExtensions[extension]
if !ok {
return DEFAULT
}
return k
}

func matchMimeType(mimeType string) ItemType {
for k, v := range MimeTypes {
matched, err := filepath.Match(k, mimeType)
if !matched || (err != nil) {
continue
}
return v
}
return DEFAULT
}

// GetItemType returns the Gopher Type of the given path
func GetItemType(p string) ItemType {
fi, err := os.Stat(p)
if err != nil {
return DEFAULT
}

if fi.IsDir() {
return DIRECTORY
}

f, err := os.Open(p)
if err != nil {
return matchExtension(fi)
}

b := make([]byte, 512)
n, err := io.ReadAtLeast(f, b, 512)
if (err != nil) || (n != 512) {
return matchExtension(fi)
}

mimeType := http.DetectContentType(b)
mimeParts := strings.Split(mimeType, ";")
return matchMimeType(mimeParts[0])
}

// Server defines parameters for running a Gopher server.
// A zero value for Server is valid configuration.
type Server struct {
Addr    string  // TCP address to listen on, ":gopher" if empty
Handler Handler // handler to invoke, gopher.DefaultServeMux if nil

Hostname string // FQDN Hostname to reach this server on

// ErrorLog specifies an optional logger for errors accepting
// connections and unexpected behavior from handlers.
// If nil, logging goes to os.Stderr via the log package's
// standard logger.
ErrorLog *log.Logger
}

// serverHandler delegates to either the server's Handler or
// DefaultServeMux and also handles "OPTIONS *" requests.
type serverHandler struct {
s *Server
}

func (sh serverHandler) ServeGopher(rw ResponseWriter, req *Request) {
handler := sh.s.Handler
if handler == nil {
handler = DefaultServeMux
}
handler.ServeGopher(rw, req)
}

// ListenAndServe starts serving gopher requests using the given Handler.
// The address passed to ListenAndServe should be an internet-accessable
// domain name, optionally followed by a colon and the port number.
//
// If the address is not a FQDN, LocalHost as passed to the Handler
// may not be accessible to clients, so links may not work.
func (s *Server) ListenAndServe() error {
addr := s.Addr
if addr == "" {
addr = ":70"
}

ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}

return s.Serve(ln)
}

// ListenAndServeTLS listens on the TCP network address srv.Addr and
// then calls Serve to handle requests on incoming TLS connections.
// Accepted connections are configured to enable TCP keep-alives.
//
// Filenames containing a certificate and matching private key for the
// server must be provided if neither the Server's TLSConfig.Certificates
// nor TLSConfig.GetCertificate are populated. If the certificate is
// signed by a certificate authority, the certFile should be the
// concatenation of the server's certificate, any intermediates, and
// the CA's certificate.
//
// If srv.Addr is blank, ":gophers" is used (port 73).
//
// ListenAndServeTLS always returns a non-nil error.
func (s *Server) ListenAndServeTLS(certFile, keyFile string) error {
addr := s.Addr
if addr == "" {
addr = ":73"
}

cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
log.Fatalf("server: loadkeys: %s", err)
}
config := tls.Config{Certificates: []tls.Certificate{cert}}
config.Rand = rand.Reader

ln, err := tls.Listen("tcp", addr, &config)
if err != nil {
log.Fatalf("server: listen: %s", err)
}

return s.Serve(ln)
}

// Serve ...
func (s *Server) Serve(l net.Listener) error {
defer l.Close()

ctx := context.Background()
ctx = context.WithValue(ctx, ServerContextKey, s)
ctx = context.WithValue(ctx, LocalAddrContextKey, l.Addr())

for {
rw, err := l.Accept()
if err != nil {
fmt.Errorf("error acceptig new client: %s", err)
return err
}

c := s.newConn(rw)
go c.serve(ctx)
}
}

// A conn represents the server side of a Gopher connection.
type conn struct {
// server is the server on which the connection arrived.
// Immutable; never nil.
server *Server

// rwc is the underlying network connection.
// This is never wrapped by other types and is the value given out
// to CloseNotifier callers. It is usually of type *net.TCPConn or
// *tls.Conn.
rwc net.Conn

// remoteAddr is rwc.RemoteAddr().String(). It is not populated synchronously
// inside the Listener's Accept goroutine, as some implementations block.
// It is populated immediately inside the (*conn).serve goroutine.
// This is the value of a Handler's (*Request).RemoteAddr.
remoteAddr string

// tlsState is the TLS connection state when using TLS.
// nil means not TLS.
tlsState *tls.ConnectionState

// mu guards hijackedv, use of bufr, (*response).closeNotifyCh.
mu sync.Mutex
}

// Create new connection from rwc.
func (s *Server) newConn(rwc net.Conn) *conn {
c := &conn{
server: s,
rwc:    rwc,
}
return c
}

func (c *conn) serve(ctx context.Context) {
c.remoteAddr = c.rwc.RemoteAddr().String()

w, err := c.readRequest(ctx)

if err != nil {
if err == io.EOF {
return // don't reply
}
if neterr, ok := err.(net.Error); ok && neterr.Timeout() {
return // don't reply
}
io.WriteString(c.rwc, "3\tbad request\terror.host\t0")
return
}

serverHandler{c.server}.ServeGopher(w, w.req)
w.End()
}

func readRequest(rwc net.Conn) (req *Request, err error) {
reader := bufio.NewReader(rwc)
scanner := bufio.NewScanner(reader)
scanner.Split(bufio.ScanLines)
scanner.Scan()

req = &Request{
Selector: scanner.Text(),
}

// If empty selector, assume /
if req.Selector == "" {
req.Selector = "/"
}

// If no leading / prefix, add one
if !strings.HasPrefix(req.Selector, "/") {
req.Selector = "/" + req.Selector
}

return req, nil
}

func (c *conn) close() (err error) {
c.mu.Lock() // while using bufr
err = c.rwc.Close()
c.mu.Unlock()
return
}

func (c *conn) readRequest(ctx context.Context) (w *response, err error) {
c.mu.Lock() // while using bufr
req, err := readRequest(c.rwc)
c.mu.Unlock()
if err != nil {
return nil, err
}

localaddr := ctx.Value(LocalAddrContextKey).(*net.TCPAddr)
host, port, err := net.SplitHostPort(localaddr.String())
if err != nil {
return nil, err
}

n, err := strconv.ParseInt(port, 10, 32)
if err != nil {
return nil, err
}

server := ctx.Value(ServerContextKey).(*Server)
if server.Hostname == "" {
req.LocalHost = host
req.LocalPort = int(n)
} else {
req.LocalHost = server.Hostname
// TODO: Parse this from -bind option
req.LocalPort = int(n)
}

w = &response{
conn: c,
req:  req,
}
w.w = bufio.NewWriter(c.rwc)

return w, nil
}

func (s *Server) logf(format string, args ...interface{}) {
if s.ErrorLog != nil {
s.ErrorLog.Printf(format, args...)
} else {
log.Printf(format, args...)
}
}

// ListenAndServe listens on the TCP network address addr
// and then calls Serve with handler to handle requests
// on incoming connections.
//
// A trivial example server is:
//
//    package main
//
//    import (
//        "io"
//        "log"
//
//        "github.com/prologic/go-gopher"
//    )
//
//    // hello world, the gopher server
//    func HelloServer(w gopher.ResponseWriter, req *gopher.Request) {
//        w.WriteInfo("hello, world!")
//    }
//
//    func main() {
//        gopher.HandleFunc("/hello", HelloServer)
//        log.Fatal(gopher.ListenAndServe(":7000", nil))
//    }
//
// ListenAndServe always returns a non-nil error.
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}

// ListenAndServeTLS acts identically to ListenAndServe, except that it
// expects TLS connections. Additionally, files containing a certificate and
// matching private key for the server must be provided. If the certificate
// is signed by a certificate authority, the certFile should be the
// concatenation of the server's certificate, any intermediates,
// and the CA's certificate.
//
// A trivial example server is:
//
//    import (
//        "log"
//
//        "github.com/prologic/go-gopher",
//    )
//
//    func HelloServer(w gopher.ResponseWriter, req *gopher.Request) {
//        w.WriteInfo("hello, world!")
//    }
//
//    func main() {
//        gopher.HandleFunc("/", handler)
//        log.Printf("About to listen on 73. Go to gophers://127.0.0.1:73/")
//        err := gopher.ListenAndServeTLS(":73", "cert.pem", "key.pem", nil)
//        log.Fatal(err)
//    }
//
// One can use generate_cert.go in crypto/tls to generate cert.pem and key.pem.
//
// ListenAndServeTLS always returns a non-nil error.
func ListenAndServeTLS(addr, certFile, keyFile string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServeTLS(certFile, keyFile)
}

// ServeMux is a Gopher request multiplexer.
// It matches the URL of each incoming request against a list of registered
// patterns and calls the handler for the pattern that
// most closely matches the URL.
//
// Patterns name fixed, rooted paths, like "/favicon.ico",
// or rooted subtrees, like "/images/" (note the trailing slash).
// Longer patterns take precedence over shorter ones, so that
// if there are handlers registered for both "/images/"
// and "/images/thumbnails/", the latter handler will be
// called for paths beginning "/images/thumbnails/" and the
// former will receive requests for any other paths in the
// "/images/" subtree.
//
// Note that since a pattern ending in a slash names a rooted subtree,
// the pattern "/" matches all paths not matched by other registered
// patterns, not just the URL with Path == "/".
//
// If a subtree has been registered and a request is received naming the
// subtree root without its trailing slash, ServeMux redirects that
// request to the subtree root (adding the trailing slash). This behavior can
// be overridden with a separate registration for the path without
// the trailing slash. For example, registering "/images/" causes ServeMux
// to redirect a request for "/images" to "/images/", unless "/images" has
// been registered separately.
//
// ServeMux also takes care of sanitizing the URL request path,
// redirecting any request containing . or .. elements or repeated slashes
// to an equivalent, cleaner URL.
type ServeMux struct {
mu sync.RWMutex
m  map[string]muxEntry
}

type muxEntry struct {
explicit bool
h        Handler
pattern  string
}

// NewServeMux allocates and returns a new ServeMux.
func NewServeMux() *ServeMux { return new(ServeMux) }

// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux

var defaultServeMux ServeMux

// Does selector match pattern?
func selectorMatch(pattern, selector string) bool {
if len(pattern) == 0 {
// should not happen
return false
}
n := len(pattern)
if pattern[n-1] != '/' {
return pattern == selector
}
return len(selector) >= n && selector[0:n] == pattern
}

// Return the canonical path for p, eliminating . and .. elements.
func cleanPath(p string) string {
if p == "" {
return "/"
}
if p[0] != '/' {
p = "/" + p
}
np := path.Clean(p)
// path.Clean removes trailing slash except for root;
// put the trailing slash back if necessary.
if p[len(p)-1] == '/' && np != "/" {
np += "/"
}
return np
}

// Find a handler on a handler map given a path string
// Most-specific (longest) pattern wins
func (mux *ServeMux) match(selector string) (h Handler, pattern string) {
var n = 0
for k, v := range mux.m {
if !selectorMatch(k, selector) {
continue
}
if h == nil || len(k) > n {
n = len(k)
h = v.h
pattern = v.pattern
}
}
return
}

// Handler returns the handler to use for the given request,
// consulting r.Selector. It always returns
// a non-nil handler.
//
// Handler also returns the registered pattern that matches the request.
//
// If there is no registered handler that applies to the request,
// Handler returns a ``resource not found'' handler and an empty pattern.
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
return mux.handler(r.Selector)
}

// handler is the main implementation of Handler.
func (mux *ServeMux) handler(selector string) (h Handler, pattern string) {
mux.mu.RLock()
defer mux.mu.RUnlock()

h, pattern = mux.match(selector)
if h == nil {
h, pattern = NotFoundHandler(), ""
}

return
}

// ServeGopher dispatches the request to the handler whose
// pattern most closely matches the request URL.
func (mux *ServeMux) ServeGopher(w ResponseWriter, r *Request) {
h, _ := mux.Handler(r)
h.ServeGopher(w, r)
}

// Handle registers the handler for the given pattern.
// If a handler already exists for pattern, Handle panics.
func (mux *ServeMux) Handle(pattern string, handler Handler) {
mux.mu.Lock()
defer mux.mu.Unlock()

if pattern == "" {
panic("gopher: invalid pattern " + pattern)
}
if handler == nil {
panic("gopher: nil handler")
}
if mux.m[pattern].explicit {
panic("gopher: multiple registrations for " + pattern)
}

if mux.m == nil {
mux.m = make(map[string]muxEntry)
}
mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern}
}

// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
mux.Handle(pattern, HandlerFunc(handler))
}

// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as Gopher handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler that calls f.
type HandlerFunc func(ResponseWriter, *Request)

// ServeGopher calls f(w, r).
func (f HandlerFunc) ServeGopher(w ResponseWriter, r *Request) {
f(w, r)
}

// A ResponseWriter interface is used by a Gopher handler to
// construct an Gopher response.
//
// A ResponseWriter may not be used after the Handler.ServeGopher method
// has returned.
type ResponseWriter interface {
// Server returns the connection's server instance
Server() *Server

// End ends the document by writing the terminating period and crlf
End() error

// Write writes the data to the connection as part of a Gopher reply.
//
Write([]byte) (int, error)

// WriteError writes an error item
WriteError(err string) error

// WriteInfo writes an informational item
WriteInfo(msg string) error

// WriteItem writes an item
WriteItem(i Item) error
}

// A response represents the server side of a Gopher response.
type response struct {
conn *conn

req *Request // request for this response

w *bufio.Writer // buffers output

rt int
}

func (w *response) Server() *Server {
return w.conn.server
}

func (w *response) Write(b []byte) (int, error) {
if w.rt == 0 {
w.rt = 1
}

if w.rt != 1 {
return 0, errors.New("cannot write document data to a directory")
}

return w.w.Write(b)
}

func (w *response) WriteError(err string) error {
if w.rt == 0 {
w.rt = 2
}

if w.rt != 2 {
_, e := w.w.Write([]byte(err))
return e
}

i := Item{
Type:        ERROR,
Description: err,
Host:        "error.host",
Port:        1,
}

return w.WriteItem(i)
}

func (w *response) WriteInfo(msg string) error {
if w.rt == 0 {
w.rt = 2
}

if w.rt != 2 {
_, e := w.w.Write([]byte(msg))
return e
}

i := Item{
Type:        INFO,
Description: msg,
Host:        "error.host",
Port:        1,
}

return w.WriteItem(i)
}

func (w *response) WriteItem(i Item) error {
if w.rt == 0 {
w.rt = 2
}

if w.rt != 2 {
return errors.New("cannot write directory data to a document")
}

if i.Host == "" && i.Port == 0 {
i.Host = w.req.LocalHost
i.Port = w.req.LocalPort
}

b, err := i.MarshalText()
if err != nil {
return err
}

_, err = w.w.Write(b)
if err != nil {
return err
}

return nil
}

func (w *response) End() (err error) {
if w.rt == 2 {
_, err = w.w.Write(append([]byte{END}, CRLF...))
if err != nil {
return
}
}

err = w.w.Flush()
if err != nil {
return
}

err = w.conn.close()
if err != nil {
return
}

return
}

// Helper handlers

// Error replies to the request with the specified error message.
// It does not otherwise end the request; the caller should ensure no further
// writes are done to w.
// The error message should be plain text.
func Error(w ResponseWriter, error string) {
w.WriteError(error)
}

// NotFound replies to the request with an resouce not found error item.
func NotFound(w ResponseWriter, r *Request) {
Error(w, "resource not found")
}

// NotFoundHandler returns a simple request handler
// that replies to each request with a ``resource page not found'' reply.
func NotFoundHandler() Handler { return HandlerFunc(NotFound) }

type fileHandler struct {
root FileSystem
}

// FileServer returns a handler that serves Gopher requests
// with the contents of the file system rooted at root.
//
// To use the operating system's file system implementation,
// use gopher.Dir:
//
//     gopher.Handle("/", gopher.FileServer(gopher.Dir("/tmp")))
func FileServer(root FileSystem) Handler {
return &fileHandler{root}
}

func (f *fileHandler) ServeGopher(w ResponseWriter, r *Request) {
upath := r.Selector
if !strings.HasPrefix(upath, "/") {
upath = "/" + upath
r.Selector = upath
}
serveFile(w, r, f.root, path.Clean(upath))
}

// A Dir implements FileSystem using the native file system restricted to a
// specific directory tree.
//
// While the FileSystem.Open method takes '/'-separated paths, a Dir's string
// value is a filename on the native file system, not a URL, so it is separated
// by filepath.Separator, which isn't necessarily '/'.
//
// An empty Dir is treated as ".".
type Dir string

// Name returns the directory
func (d Dir) Name() string {
return string(d)
}

// Open opens the directory
func (d Dir) Open(name string) (File, error) {
if filepath.Separator != '/' &&
strings.ContainsRune(name, filepath.Separator) ||
strings.Contains(name, "\x00") {
return nil, errors.New("gopher: invalid character in file path")
}
dir := string(d)
if dir == "" {
dir = "."
}
f, err := os.Open(
filepath.Join(dir, filepath.FromSlash(path.Clean("/"+name))),
)
if err != nil {
return nil, err
}
return f, nil
}

// A FileSystem implements access to a collection of named files.
// The elements in a file path are separated by slash ('/', U+002F)
// characters, regardless of host operating system convention.
type FileSystem interface {
Name() string
Open(name string) (File, error)
}

// A File is returned by a FileSystem's Open method and can be
// served by the FileServer implementation.
//
// The methods should behave the same as those on an *os.File.
type File interface {
io.Closer
io.Reader
io.Seeker
Readdir(count int) ([]os.FileInfo, error)
Stat() (os.FileInfo, error)
}

func dirList(w ResponseWriter, r *Request, f File, fs FileSystem) {
root := fs.Name()

fullpath := f.(*os.File).Name()

files, err := f.Readdir(-1)
if err != nil {
// TODO: log err.Error() to the Server.ErrorLog, once it's possible
// for a handler to get at its Server via the ResponseWriter.
Error(w, "Error reading directory")
return
}
sort.Sort(byName(files))

for _, file := range files {
if file.Name()[0] == '.' {
continue
}
if file.Mode()&os.ModeDir != 0 {
pathname, err := filepath.Rel(
root,
path.Join(fullpath, file.Name()),
)
if err != nil {
Error(w, "Error reading directory")
return
}
w.WriteItem(
Item{
Type:        DIRECTORY,
Description: file.Name(),
Selector:    pathname,
Host:        r.LocalHost,
Port:        r.LocalPort,
},
)
} else if file.Mode()&os.ModeType == 0 {
pathname, err := filepath.Rel(
root,
path.Join(fullpath, file.Name()),
)
if err != nil {
Error(w, "Error reading directory")
return
}

itemtype := GetItemType(path.Join(fullpath, file.Name()))

w.WriteItem(
Item{
Type:        itemtype,
Description: file.Name(),
Selector:    pathname,
Host:        r.LocalHost,
Port:        r.LocalPort,
},
)
}
}
}

type byName []os.FileInfo

func (s byName) Len() int           { return len(s) }
func (s byName) Less(i, j int) bool { return s[i].Name() < s[j].Name() }
func (s byName) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }

// name is '/'-separated, not filepath.Separator.
func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string) {
const gophermapFile = "/gophermap"

f, err := fs.Open(name)
if err != nil {
Error(w, err.Error())
return
}
defer f.Close()

d, err := f.Stat()
if err != nil {
Error(w, err.Error())
return
}

// use contents of gophermap for directory, if present
if d.IsDir() {
gophermap := strings.TrimSuffix(name, "/") + gophermapFile
ff, err := fs.Open(gophermap)
if err == nil {
defer ff.Close()
dd, err := ff.Stat()
if err == nil {
name = gophermap
d = dd
f = ff
}
}
}

// Still a directory? (we didn't find a gophermap file)
if d.IsDir() {
dirList(w, r, f, fs)
return
}

serveContent(w, r, f)
}

// content must be seeked to the beginning of the file.
func serveContent(w ResponseWriter, r *Request, content io.ReadSeeker) {
io.Copy(w, content)
}

// Handle registers the handler for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func Handle(pattern string, handler Handler) {
DefaultServeMux.Handle(pattern, handler)
}

// HandleFunc registers the handler function for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}