* Added options to specify header and footer files to include in generated pages.
authorUrban Wallasch <urban.wallasch@freenet.de>
Sun, 7 Apr 2019 10:28:06 +0000 (12:28 +0200)
committerUrban Wallasch <urban.wallasch@freenet.de>
Sun, 7 Apr 2019 10:28:06 +0000 (12:28 +0200)
* 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
config.go
gogopherd.cfg.example
gogopherd.go

index 5795a6a97cb86a1cf16cd7d11e90fdbae2a09b54..fa90c325084f04f40b49e2cb020a80fae43ef604 100644 (file)
--- 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
index 6e4817adf135a1da1f375d72a7d51772a8d8402c..83c1cc590411c9fe5b0def89bdeeea660dfece28 100644 (file)
--- 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 */
index d9a6464daabcf4da82fe40637eb738bb19db7939..1d67c7f38a239c38c201550bebe7c8b41da81af2 100644 (file)
@@ -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
index 29669f545c669e066d93bd2ce88df118ab784d88..2e63a0d489e7831c0e44cbdb598f11af07ae2269 100644 (file)
@@ -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.")