blob: e54f04ed04dfc2d653b50aac74f701e4211177a1 [file]
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package http_test
import (
"io/fs"
"log"
"net/http"
"os"
"slices"
"strings"
)
// containsDotFile reports whether name contains a path element starting with a period.
// The name is delimited by forward slashes, as guaranteed by the fs.FS interface.
func containsDotFile(name string) bool {
if name == "." {
return false // allow the root directory, ".".
}
for part := range strings.SplitSeq(name, "/") {
if strings.HasPrefix(part, ".") {
return true
}
}
return false
}
// dotFileHidingFile is the fs.File use in dotFileHidingFileSystem.
// It is used to wrap the Readdir method of fs.ReadDirFile so that we can
// remove files and directories that start with a period from its output.
type dotFileHidingFile struct {
fs.ReadDirFile
}
// Readdir is a wrapper around the Readdir method of the embedded File
// that filters out all files that start with a period in their name.
func (f dotFileHidingFile) ReadDir(n int) ([]fs.DirEntry, error) {
ents, err := f.ReadDirFile.ReadDir(n)
ents = slices.DeleteFunc(ents, func(ent fs.DirEntry) bool {
return strings.HasPrefix(ent.Name(), ".")
})
return ents, err
}
// dotFileHidingFileSystem is an http.FileSystem that hides
// hidden "dot files" from being served.
type dotFileHidingFileSystem struct {
fs.FS
}
// Open is a wrapper around the Open method of the embedded FileSystem
// that serves a 403 permission error when name has a file or directory
// with whose name starts with a period in its path.
func (fsys dotFileHidingFileSystem) Open(name string) (fs.File, error) {
if containsDotFile(name) { // If dot file, return 403 response
return nil, fs.ErrPermission
}
file, err := fsys.FS.Open(name)
if rdf, ok := file.(fs.ReadDirFile); ok {
file = dotFileHidingFile{rdf}
}
return file, err
}
// FileServerFS will serve files starting with a dot, which can expose sensitive
// directories such as .git or sensitive files such as .htpassword.
//
// This example demonstrates hiding dot files by wrapping the fs.FS.
func ExampleFileServerFS_dotFileHiding() {
root, err := os.OpenRoot("doc")
if err != nil {
log.Fatal(err)
}
fsys := dotFileHidingFileSystem{root.FS()}
handler := http.FileServerFS(fsys)
log.Fatal(http.ListenAndServe(":8080", handler))
}