Search code examples
gowebserverhttprequest

Does it make a difference where I root a FileServer?


At https://www.alexedwards.net/blog/serving-static-sites-with-go, there's an example of a static file server serving sites in a single directory: static.

File: app.go
...
func main() {
  fs := http.FileServer(http.Dir("static"))
  http.Handle("/static/", http.StripPrefix("/static/", fs))

  log.Println("Listening...")
  http.ListenAndServe(":3000", nil)
}

However, I've found that I can get the same results with the following.

func main() {
  fs := http.FileServer(http.Dir(".")) // root at the root directory.
  http.Handle("/static/", fs) //leave off the StripPrefix call.

  log.Println("Listening...")
  http.ListenAndServe(":3000", nil)
}

Are there any (performanace or security) downsides to doing it this way? I can see that I'd have to use StripPrefix if my location of the files on the filesystem did not match the URL they were served at, but in this case it seems as though the call to StripPrefix is unnecessary.

Edit: I forgot to mention, but I've had a look into this myself. Performance-wise, it doesn't seem to be a problem, since the call to FileServer doesn't actually load the files into memory; it just stores away the address. Security-wise, this seems to be exactly the same: I attempted a directory-traversal attack using something like the following.

$ curl -i --path-as-is 'http://localhost:3000/static/../sensitive.txt'

but I got a 301 response with both versions, which surprised me a little bit.


Solution

  • It's same when you use http.ServeMux handler

    http.ServeMux will call cleanPath function before match path

    https://github.com/golang/go/blob/master/src/net/http/server.go#L2305

    func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
    // snip
        path := cleanPath(r.URL.Path)
    //snip
    }  
    

    cleanPath function return the canonical path for p, eliminating . and .. elements.

    https://github.com/golang/go/blob/master/src/net/http/server.go#L2174-L2193

    // cleanPath returns 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 != "/" {
            // Fast path for common case of p being the string we want:
            if len(p) == len(np)+1 && strings.HasPrefix(p, np) {
                np = p
            } else {
                np += "/"
            }
        }
        return np
    }