* Fixed path canonicalize logic. v0.1
authorUrban Wallasch <urban.wallasch@freenet.de>
Fri, 5 Apr 2019 07:48:43 +0000 (09:48 +0200)
committerUrban Wallasch <urban.wallasch@freenet.de>
Fri, 5 Apr 2019 07:48:43 +0000 (09:48 +0200)
* Added option to follow symbolic links (default: don't).
* Fixed default value for -f FQDN option.
* Renamed -d option to -r.
* Replaced os.File.Readdir() by ioutil.ReadDir() to get a sorted list.
* Removed dot-dot entry from top level menu.
* Removed HOME reference from directory listings.
* Several small fixes and minor improvements.

.gitignore
gogopherd.go

index e4e58c64e126893712c87ecf841c72fa3a59ea11..e656622b95170055fa7da65296ba9c6c441ee6c9 100644 (file)
@@ -1,2 +1 @@
 gogopherd
-stuff
index 1523026b9a67e37df9683af3b114687ea44acf27..7f303aa6c9bfeef1b6aad028fc6d8599fe98fbaa 100644 (file)
@@ -41,25 +41,34 @@ var cfg = struct {
        port    string
        fqdn    string
        docRoot string
-       header  string
+       message string
+       fsymln  bool
        verbose bool
 }{
        iface:   "localhost",
        port:    "7070",
        fqdn:    "localhost",
        docRoot: ".",
-       header: "igogopherd on localhost                        \r\n",
+       message: "",
+       fsymln:  false,
        verbose: false,
 }
 
 func initialize() {
+       help := false
        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.port, "fully qualified domain name")
-       flag.StringVar(&cfg.docRoot, "d", cfg.docRoot, "document root directory")
-       flag.StringVar(&cfg.header, "H", cfg.header, "index header")
+       flag.StringVar(&cfg.fqdn, "f", cfg.fqdn, "fully qualified domain name")
+       flag.StringVar(&cfg.docRoot, "r", cfg.docRoot, "document root directory")
+       flag.StringVar(&cfg.message, "M", cfg.message, "index greeter message")
+       flag.BoolVar(&help, "h", help, "show this help page")
+       flag.BoolVar(&cfg.fsymln, "s", cfg.fsymln, "follow symbolic links")
        flag.BoolVar(&cfg.verbose, "v", cfg.verbose, "produce verbose output")
        flag.Parse()
+       if help {
+               flag.Usage()
+               os.Exit(1)
+       }
        if 0 != len(flag.Args()) {
                fmt.Println("unrecognized options: ", flag.Args())
                flag.Usage()
@@ -78,11 +87,16 @@ func initialize() {
        cfg.docRoot, err = canonicalizePath(cfg.docRoot)
        checkFatal(err, "canonicalizePath "+cfg.docRoot)
 
+       if cfg.message != "" {
+               cfg.message = fmt.Sprintf("i%s\t\t\t\r\n", cfg.message)
+       }
+
        tracer.Print("interface: ", cfg.iface)
        tracer.Print("TCP port:  ", cfg.port)
        tracer.Print("doc root:  ", cfg.docRoot)
+       tracer.Print("fsymlinks: ", cfg.fsymln)
        tracer.Print("fqdn:      ", cfg.fqdn)
-       tracer.Print("header:    ", cfg.header)
+       tracer.Print("message:   ", cfg.message)
        tracer.Print("verbose:   ", cfg.verbose)
 }
 
@@ -101,26 +115,22 @@ func check(err error, msg string) error {
 }
 
 func canonicalizePath(path string) (string, error) {
-       evp, err := filepath.EvalSymlinks(path)
-       if check(err, "EvalSymlinks "+path) != nil {
-               return path, err
-       }
-       abs, err := filepath.Abs(evp)
-       if check(err, "Abs "+evp) != nil {
+       abs, err := filepath.Abs(path)
+       if check(err, "Abs "+path) != nil {
                return path, err
        }
        return abs, err
 }
 
-func validatePath(relpath string) (string, error) {
-       path, err := canonicalizePath(cfg.docRoot + pathSep + relpath)
-       if check(err, "canonicalizePath "+relpath) != nil {
+func validatePath(root string, path string) (string, error) {
+       cpath, err := canonicalizePath(path)
+       if check(err, "canonicalizePath "+path) != nil {
                return path, err
        }
-       if len(path) < len(cfg.docRoot) || path[:len(cfg.docRoot)] != cfg.docRoot {
-               return path, errors.New("Path outside docRoot")
+       if len(cpath) < len(root) || cpath[:len(root)] != root {
+               return cpath, errors.New("Path outside root")
        }
-       return path, err
+       return cpath, err
 }
 
 func guessFiletype(path string) (string, error) {
@@ -133,7 +143,7 @@ func guessFiletype(path string) (string, error) {
        // Get the content
        buffer := make([]byte, 512)
        n, err := f.Read(buffer)
-       if check(err, "Read "+path) != nil {
+       if err != io.EOF && check(err, "Read "+path) != nil {
                return "i", err
        }
        buffer = buffer[:n]
@@ -172,19 +182,16 @@ func humanSize(bytes int64) string {
 
 func createIndex(selector string) (string, error) {
        dirname := cfg.docRoot + selector
-       d, err := os.Open(dirname)
-       defer d.Close()
-       if check(err, "Open "+dirname) != nil {
-               return "", err
-       }
-       fi, err := d.Readdir(-1)
+       fi, err := ioutil.ReadDir(dirname)
        if check(err, "Readdir "+dirname) != nil {
                return "", err
        }
        loc := "\t" + cfg.fqdn + "\t" + cfg.port + "\r\n"
-       list := "1HOME\t/" + loc
+       list := ""
        updir, _ := filepath.Split(selector)
-       list += "1..\t" + updir + loc
+       if dirname != cfg.docRoot {
+               list += "1..\t" + updir + loc
+       }
        for _, fi := range fi {
                fmode := fi.Mode()
                if fmode.IsDir() {
@@ -194,12 +201,14 @@ func createIndex(selector string) (string, error) {
                        // create a file reference
                        ftype, _ := guessFiletype(dirname + pathSep + fi.Name())
                        list += ftype + fi.Name() + " (" + humanSize(fi.Size()) + ")\t" + selector + pathSep + fi.Name() + loc
-               } else if fmode&os.ModeSymlink != 0 {
-                       // create a reference according to link target
+               } else if cfg.fsymln == true && fmode&os.ModeSymlink != 0 {
+                       // create a reference with a type matching the link target
                        linktarget, _ := os.Readlink(dirname + pathSep + fi.Name())
-                       path, err := validatePath(linktarget)
-                       if check(err, "validatePath "+path) == nil {
-                               tracer.Print("path: '", path, "'")
+                       if linktarget[:1] != pathSep {
+                               linktarget = dirname + pathSep + linktarget
+                       }
+                       path, err := canonicalizePath(linktarget)
+                       if check(err, "canonicalizePath "+path) == nil {
                                lfi, err := os.Stat(path)
                                if check(err, "Stat "+path) == nil {
                                        if lfi.IsDir() {
@@ -228,14 +237,25 @@ func handleRequest(conn net.Conn) {
        }
        req = strings.TrimSpace(req)
        tracer.Print("request: '", req, "'")
-       // evaluate, canonicalize, and validate referenced path
-       path, err := validatePath(req)
+       // canonicalize, and validate referenced path
+       path, err := validatePath(cfg.docRoot, cfg.docRoot+pathSep+req)
        if check(err, "validatePath "+path) != nil {
                return
        }
-       tracer.Print("path: '", path, "'")
+       tracer.Print("request path: '", path, "'")
+       // check for symbolic link
+       if cfg.fsymln == false {
+               fi, err := os.Lstat(path)
+               if check(err, "request path Lstat "+path) != nil {
+                       return
+               }
+               if fi.Mode()&os.ModeSymlink != 0 {
+                       return
+               }
+       }
+       // stat the file
        fi, err := os.Stat(path)
-       if check(err, "Stat "+path) != nil {
+       if check(err, "request path Stat "+path) != nil {
                return
        }
        fmode := fi.Mode()
@@ -248,7 +268,7 @@ func handleRequest(conn net.Conn) {
                if check(err, "makeIndex "+path) != nil {
                        return
                }
-               nb, err := conn.Write([]byte(cfg.header + diridx))
+               nb, err := conn.Write([]byte(cfg.message + diridx))
                if check(err, "Write") != nil {
                        return
                }
@@ -264,8 +284,7 @@ func handleRequest(conn net.Conn) {
                        return
                }
        } else {
-               // TODO: handle other cases?
-               nbytes = -1
+               return
        }
        tracer.Print(strconv.FormatInt(nbytes, 10) + " bytes sent.")
        return