Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions local_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,36 @@ import (

const INDEX = "index.html"

// Named boolean values for the indexes parameter of LocalFile, so calls read
// clearly at the call site instead of using a bare true/false. For example:
//
// router.Use(static.Serve("/", static.LocalFile("/www", static.NoListDirectory)))
const (
// ListDirectory enables directory listing: requesting a directory returns
// a listing of the files it contains.
ListDirectory = true
// NoListDirectory disables directory listing: requesting a directory is
// served only when it contains an index.html file (which is then returned).
NoListDirectory = false
)

type localFileSystem struct {
http.FileSystem
root string
indexes bool
}

// LocalFile serves files from the local directory rooted at root.
//
// The indexes parameter controls how directories are handled:
// - when true (ListDirectory), directory listing is enabled and requesting a
// directory returns a listing of its files;
// - when false (NoListDirectory), directory listing is disabled and a
// directory is served only when it contains an index.html file, which is
// then returned.
//
// Note that an existing index.html is served in both cases; indexes only
// toggles the automatic directory listing.
func LocalFile(root string, indexes bool) *localFileSystem {
return &localFileSystem{
FileSystem: gin.Dir(root, indexes),
Expand Down
44 changes: 44 additions & 0 deletions local_file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,47 @@ func TestLocalFile(t *testing.T) {
w = PerformRequest(router, "GET", "/")
assert.Contains(t, w.Body.String(), `<a href="`+filename)
}

// TestLocalFileIndexConstants documents that the exported ListDirectory and
// NoListDirectory constants map to the historical true/false values of the
// indexes parameter, so existing call sites keep working.
func TestLocalFileIndexConstants(t *testing.T) {
assert.True(t, ListDirectory)
assert.False(t, NoListDirectory)

fs := LocalFile("/tmp", ListDirectory)
assert.True(t, fs.indexes)

fs = LocalFile("/tmp", NoListDirectory)
assert.False(t, fs.indexes)
}

// TestLocalFileNoListDirectory verifies the documented NoListDirectory
// behavior: directory listing is suppressed, but an index.html inside the
// directory is still served.
func TestLocalFileNoListDirectory(t *testing.T) {
dir, err := os.MkdirTemp("", "static-nolist")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
}
defer os.RemoveAll(dir)

if err := os.WriteFile(filepath.Join(dir, INDEX), []byte("Gin Web Framework"), 0o600); err != nil {
t.Fatalf("Failed to write index file: %v", err)
}
if err := os.WriteFile(filepath.Join(dir, "secret.txt"), []byte("top secret"), 0o600); err != nil {
t.Fatalf("Failed to write secret file: %v", err)
}

router := gin.New()
router.Use(Serve("/", LocalFile(dir, NoListDirectory)))

// The index.html is served for the directory root.
w := PerformRequest(router, "GET", "/")
assert.Equal(t, 200, w.Code)
assert.Equal(t, "Gin Web Framework", w.Body.String())

// The directory listing is not exposed: the sibling file name does not
// appear as an auto-generated listing link.
assert.NotContains(t, w.Body.String(), `<a href="secret.txt`)
}