* Include header and footer in error pages and generated directory indexes.
* Improved error page appearance.
* Lots of refactoring, especially WRT to configuration options and variable naming.
options:
```
+ -F string
+ footer page to append to generated pages
+
+ -H string
+ header page to prepend to generated pages
+
-I string
directory index page (default "index.goph")
produce verbose output
```
-An example configuration file, `gogopherd.cfg`, is included. Any options
-provided on the command line will override the respective settings as
-read from the configuration file.
+A commented example configuration file, `gogopherd.cfg`, is included
+for your convenience. Any options provided on the command line will
+override the respective settings in the configuration file.
## Release History
iface string
port string
fqdn string
- docRoot string
- dirIdx string
+ docroot string
+ idxpage string
+ header string
+ footer string
fsymln bool
showdot bool
- noidx bool
+ indexes bool
verbose bool
}{
iface: "localhost",
port: "7070",
fqdn: "localhost",
- docRoot: ".",
- dirIdx: "index.goph",
+ docroot: ".",
+ idxpage: "index.goph",
+ header: "",
+ footer: "",
fsymln: false,
showdot: false,
- noidx: false,
+ indexes: false,
verbose: false,
}
case "fqdn":
cfg.fqdn = val
case "docroot":
- cfg.docRoot = val
- case "diridx":
- cfg.dirIdx = val
+ cfg.docroot = val
+ case "idxpage":
+ cfg.idxpage = val
+ case "header":
+ cfg.header = val
+ case "footer":
+ cfg.footer = val
case "fsymln":
cfg.fsymln = strToBool(val)
case "showdot":
cfg.showdot = strToBool(val)
- case "noidx":
- cfg.noidx = strToBool(val)
+ case "indexes":
+ cfg.indexes = strToBool(val)
default:
logger.Print("ignoring unknown config item: " + name)
}
flag.StringVar(&cfg.iface, "i", cfg.iface, "interface to bind to")
flag.StringVar(&cfg.port, "p", cfg.port, "TCP port to listen on")
flag.StringVar(&cfg.fqdn, "f", cfg.fqdn, "fully qualified domain name")
- flag.StringVar(&cfg.docRoot, "r", cfg.docRoot, "document root directory")
- flag.StringVar(&cfg.dirIdx, "I", cfg.dirIdx, "directory index page")
+ flag.StringVar(&cfg.docroot, "r", cfg.docroot, "document root directory")
+ flag.StringVar(&cfg.idxpage, "I", cfg.idxpage, "directory index page")
+ flag.StringVar(&cfg.header, "H", cfg.header, "header file to prepend to generated pages")
+ flag.StringVar(&cfg.footer, "F", cfg.footer, "footer file to append to generated pages")
flag.BoolVar(&help, "h", help, "show this help page")
flag.BoolVar(&cfg.fsymln, "s", cfg.fsymln, "follow symbolic links")
flag.BoolVar(&cfg.showdot, "d", cfg.showdot, "allow access to dotfiles")
- flag.BoolVar(&cfg.noidx, "l", cfg.noidx, "allow directory listings")
+ flag.BoolVar(&cfg.indexes, "l", cfg.indexes, "allow directory listings")
flag.BoolVar(&cfg.verbose, "v", cfg.verbose, "produce verbose output")
flag.Parse()
if help {
}
var err error
- cfg.docRoot, err = canonicalizePath(cfg.docRoot)
- checkFatal(err, "canonicalizePath "+cfg.docRoot)
-
- cfg.dirIdx = filepath.Base(strings.TrimSpace(cfg.dirIdx))
- if cfg.dirIdx == "." || cfg.dirIdx == pathSep {
- cfg.dirIdx = ""
+ cfg.docroot, err = canonicalizePath(cfg.docroot)
+ checkFatal(err, "canonicalizePath "+cfg.docroot)
+ if cfg.header[:1] != pathSep {
+ cfg.header = cfg.docroot + pathSep + cfg.header
+ }
+ if cfg.footer[:1] != pathSep {
+ cfg.footer = cfg.docroot + pathSep + cfg.footer
+ }
+ cfg.idxpage = filepath.Base(strings.TrimSpace(cfg.idxpage))
+ if cfg.idxpage == "." || cfg.idxpage == pathSep {
+ cfg.idxpage = ""
}
tracer.Print("Version ", version)
- tracer.Print("fqdn: ", cfg.fqdn)
- tracer.Print("interface: ", cfg.iface)
- tracer.Print("TCP port: ", cfg.port)
- tracer.Print("doc root: ", cfg.docRoot)
- tracer.Print("dir index: ", cfg.dirIdx)
- tracer.Print("fsymlinks: ", cfg.fsymln)
- tracer.Print("showdot: ", cfg.showdot)
- tracer.Print("noidx: ", cfg.noidx)
- tracer.Print("verbose: ", cfg.verbose)
+ tracer.Print("fqdn: ", cfg.fqdn)
+ tracer.Print("iface: ", cfg.iface)
+ tracer.Print("port: ", cfg.port)
+ tracer.Print("docroot: ", cfg.docroot)
+ tracer.Print("idxpage: ", cfg.idxpage)
+ tracer.Print("header: ", cfg.header)
+ tracer.Print("footer: ", cfg.footer)
+ tracer.Print("fsymln: ", cfg.fsymln)
+ tracer.Print("showdot: ", cfg.showdot)
+ tracer.Print("indexes: ", cfg.indexes)
+ tracer.Print("verbose: ", cfg.verbose)
}
/* EOF */
-# Example configuration for gogopherd.
-# Command line options will override values set here.
+# Example configuration file for gogopherd.
+# Command line options will override the respective values set here.
-# Network interface to bind to, empty means all.
+# Network interface to bind to, empty means any.
iface = ""
# TCP port to listen on.
port = 7070
# Root directory of document hierarchy.
docroot = "."
# Name of directory index files.
-diridx = "index.goph"
+idxpage = "index.goph"
+# Path to header file to prepend to generated pages; absolute or relative to docroot.
+header = ""
+# Path to footer file to append to generated pages; absolute or relative to docroot.
+footer = ""
# If true, follow symbolic links.
fsymln = true
-# Show dot files.
+# If true, show dot files.
showdot = false
-# Do not generate directory listings when no index file is present.
-noidx = false
+# If true, generate directory listings where no index file is found.
+indexes = false
# EOF
loc := "\t" + cfg.fqdn + "\t" + cfg.port + "\r\n"
list := ""
updir, _ := filepath.Split(selector)
- if dirname != cfg.docRoot {
+ if dirname != cfg.docroot {
list += "1..\t" + updir + loc
}
for _, fi := range fi {
}
func sendIndex(conn net.Conn, path string, selector string) (int64, error) {
- var nbytes int
- nbytes = 0
- diridx, err := createIndex(path, selector)
+ nbytes, _ := sendHeader(conn)
+ idxpage, err := createIndex(path, selector)
if err == nil {
+ tracer.Print("send generated directory index")
if selector == "" {
selector = pathSep
}
- page := ""
- // TODO append banner
- page += "iIndex of " + selector + "\tErr\t" + cfg.fqdn + "\t" + cfg.port + "\r\n"
- page += diridx
- // TODO append footer
- nbytes, err = conn.Write([]byte(page))
+ page := "iIndex of " + selector + "\tErr\t" + cfg.fqdn + "\t" + cfg.port + "\r\n"
+ page += idxpage
+ var nb1 int
+ nb1, err = conn.Write([]byte(page))
+ nbytes += int64(nb1)
}
+ nb2, _ := sendFooter(conn)
+ nbytes += nb2
return int64(nbytes), err
}
return nbytes, err
}
-func replyErr(conn net.Conn, msg string) {
- tracer.Print("sending error reply: " + msg)
- s := "3Gopher Meditation: " + msg + "\tErr\t" + cfg.fqdn + "\t" + cfg.port + "\r\n"
- nb, err := conn.Write([]byte(s))
- if check(err, "replyErr Write ") != nil {
+func sendHeader(conn net.Conn) (int64, error) {
+ if cfg.header != "" {
+ tracer.Print("send header: ", cfg.header)
+ return sendFile(conn, cfg.header)
+ }
+ return 0, nil
+}
+
+func sendFooter(conn net.Conn) (int64, error) {
+ if cfg.footer != "" {
+ tracer.Print("send footer: ", cfg.footer)
+ return sendFile(conn, cfg.footer)
+ }
+ return 0, nil
+}
+
+func sendError(conn net.Conn, msg string) {
+ emsg := ""
+ switch msg {
+ case "403":
+ emsg = " Forbidden"
+ case "404":
+ emsg = " Not Found"
+ case "418":
+ emsg = " I'm a teapot"
+ }
+ nbytes, _ := sendHeader(conn)
+ tracer.Print("send error page: ", msg)
+ s := "3Gopher Meditation: " + msg + emsg + "\tErr\t" + cfg.fqdn + "\t" + cfg.port + "\r\n"
+ nb1, err := conn.Write([]byte(s))
+ if check(err, "sendError Write ") != nil {
return
}
- tracer.Printf("%d bytes sent.", nb)
+ nbytes += int64(nb1)
+ nb2, _ := sendFooter(conn)
+ nbytes += nb2
+ tracer.Printf("%d bytes sent.", nbytes)
return
}
req = strings.TrimSpace(req)
tracer.Print("request: '", req, "'")
// canonicalize, and validate referenced path
- path, err := validatePath(cfg.docRoot, cfg.docRoot+pathSep+req)
+ path, err := validatePath(cfg.docroot, cfg.docroot+pathSep+req)
if check(err, "validatePath "+path) != nil {
- replyErr(conn, "404")
+ sendError(conn, "404")
return
}
tracer.Print("request path: '", path, "'")
if !cfg.showdot && isDotfile(path) {
tracer.Print("skip dotfile")
- replyErr(conn, "404")
+ sendError(conn, "404")
return
}
// check for symbolic link
if !cfg.fsymln {
fi, err := os.Lstat(path)
if check(err, "request path Lstat "+path) != nil {
- replyErr(conn, "404")
+ sendError(conn, "404")
return
}
if fi.Mode()&os.ModeSymlink != 0 {
- replyErr(conn, "404")
+ sendError(conn, "404")
return
}
}
// stat the file
fi, err := os.Stat(path)
if check(err, "request path Stat "+path) != nil {
- replyErr(conn, "404")
+ sendError(conn, "404")
return
}
fmode := fi.Mode()
// send appropriate resource (file or directory index)
- selector := path[len(cfg.docRoot):]
+ selector := path[len(cfg.docroot):]
tracer.Print("selector: '", selector, "'")
var nbytes int64
if fmode.IsDir() {
err = nil
- if cfg.dirIdx != "" {
+ if cfg.idxpage != "" {
// send directory index file
- idxpath := path + pathSep + cfg.dirIdx
+ idxpath := path + pathSep + cfg.idxpage
nbytes, err = sendFile(conn, idxpath)
if err != nil {
tracer.Print("no index file in " + path)
}
if err != nil {
// failed to send directory index file
- if cfg.noidx {
- replyErr(conn, "403")
+ if !cfg.indexes {
+ sendError(conn, "403")
return
}
// generate and send directory listing
nbytes, err = sendIndex(conn, path, selector)
if check(err, "sendIndex ") != nil {
- replyErr(conn, "403")
+ sendError(conn, "403")
return
}
}
} else if fmode.IsRegular() {
nbytes, err = sendFile(conn, path)
if check(err, "sendFile "+path) != nil {
- replyErr(conn, "404")
+ sendError(conn, "404")
return
}
} else {
- replyErr(conn, "404")
+ sendError(conn, "404")
return
}
tracer.Print(strconv.FormatInt(nbytes, 10) + " bytes sent.")