From 62ff7614637d6833fa7ea0da34af6c87ff359551 Mon Sep 17 00:00:00 2001 From: Urban Wallasch Date: Sun, 7 Apr 2019 12:28:06 +0200 Subject: [PATCH] * Added options to specify header and footer files to include in generated pages. * 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. --- README.md | 12 ++++-- config.go | 75 ++++++++++++++++++++++-------------- gogopherd.cfg.example | 18 +++++---- gogopherd.go | 90 ++++++++++++++++++++++++++++--------------- 4 files changed, 126 insertions(+), 69 deletions(-) diff --git a/README.md b/README.md index 5795a6a..fa90c32 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,12 @@ The `gogopherd` executable recognizes the following command line 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") @@ -69,9 +75,9 @@ options: 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 diff --git a/config.go b/config.go index 6e4817a..83c1cc5 100644 --- a/config.go +++ b/config.go @@ -21,21 +21,25 @@ var cfg = struct { 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, } @@ -64,15 +68,19 @@ func parseConfigFile(filename string) error { 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) } @@ -87,12 +95,14 @@ func initialize() { 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 { @@ -113,24 +123,31 @@ func initialize() { } 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 */ diff --git a/gogopherd.cfg.example b/gogopherd.cfg.example index d9a6464..1d67c7f 100644 --- a/gogopherd.cfg.example +++ b/gogopherd.cfg.example @@ -1,7 +1,7 @@ -# 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 @@ -11,13 +11,17 @@ fqdn = "localhost" # 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 diff --git a/gogopherd.go b/gogopherd.go index 29669f5..2e63a0d 100644 --- a/gogopherd.go +++ b/gogopherd.go @@ -32,7 +32,7 @@ func createIndex(dirname string, selector string) (string, error) { 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 { @@ -84,20 +84,21 @@ func createIndex(dirname string, selector string) (string, error) { } 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 } @@ -112,14 +113,43 @@ func sendFile(conn net.Conn, path string) (int64, error) { 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 } @@ -133,45 +163,45 @@ func handleRequest(conn net.Conn) { 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) @@ -179,25 +209,25 @@ func handleRequest(conn net.Conn) { } 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.") -- 2.30.2