Skip to content

Commit 8791c96

Browse files
authored
feat(fs): Export, test and document OnlyFilesFS (#3939)
1 parent b1c1e7b commit 8791c96

3 files changed

Lines changed: 100 additions & 23 deletions

File tree

‎fs.go‎

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,37 +9,43 @@ import (
99
"os"
1010
)
1111

12-
type onlyFilesFS struct {
13-
fs http.FileSystem
12+
// OnlyFilesFS implements an http.FileSystem without `Readdir` functionality.
13+
type OnlyFilesFS struct {
14+
FileSystem http.FileSystem
1415
}
1516

16-
type neuteredReaddirFile struct {
17+
// Open passes `Open` to the upstream implementation without `Readdir` functionality.
18+
func (o OnlyFilesFS) Open(name string) (http.File, error) {
19+
f, err := o.FileSystem.Open(name)
20+
21+
if err != nil {
22+
return nil, err
23+
}
24+
25+
return neutralizedReaddirFile{f}, nil
26+
}
27+
28+
// neutralizedReaddirFile wraps http.File with a specific implementation of `Readdir`.
29+
type neutralizedReaddirFile struct {
1730
http.File
1831
}
1932

20-
// Dir returns a http.FileSystem that can be used by http.FileServer(). It is used internally
21-
// in router.Static().
22-
// if listDirectory == true, then it works the same as http.Dir() otherwise it returns
23-
// a filesystem that prevents http.FileServer() to list the directory files.
33+
// Readdir overrides the http.File default implementation and always returns nil.
34+
func (n neutralizedReaddirFile) Readdir(_ int) ([]os.FileInfo, error) {
35+
// this disables directory listing
36+
return nil, nil
37+
}
38+
39+
// Dir returns an http.FileSystem that can be used by http.FileServer().
40+
// It is used internally in router.Static().
41+
// if listDirectory == true, then it works the same as http.Dir(),
42+
// otherwise it returns a filesystem that prevents http.FileServer() to list the directory files.
2443
func Dir(root string, listDirectory bool) http.FileSystem {
2544
fs := http.Dir(root)
45+
2646
if listDirectory {
2747
return fs
2848
}
29-
return &onlyFilesFS{fs}
30-
}
31-
32-
// Open conforms to http.Filesystem.
33-
func (fs onlyFilesFS) Open(name string) (http.File, error) {
34-
f, err := fs.fs.Open(name)
35-
if err != nil {
36-
return nil, err
37-
}
38-
return neuteredReaddirFile{f}, nil
39-
}
4049

41-
// Readdir overrides the http.File default implementation.
42-
func (f neuteredReaddirFile) Readdir(_ int) ([]os.FileInfo, error) {
43-
// this disables directory listing
44-
return nil, nil
50+
return &OnlyFilesFS{FileSystem: fs}
4551
}

‎fs_test.go‎

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package gin
2+
3+
import (
4+
"errors"
5+
"net/http"
6+
"os"
7+
"testing"
8+
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
type mockFileSystem struct {
13+
open func(name string) (http.File, error)
14+
}
15+
16+
func (m *mockFileSystem) Open(name string) (http.File, error) {
17+
return m.open(name)
18+
}
19+
20+
func TestOnlyFilesFS_Open(t *testing.T) {
21+
var testFile *os.File
22+
mockFS := &mockFileSystem{
23+
open: func(name string) (http.File, error) {
24+
return testFile, nil
25+
},
26+
}
27+
fs := &OnlyFilesFS{FileSystem: mockFS}
28+
29+
file, err := fs.Open("foo")
30+
31+
assert.NoError(t, err)
32+
assert.Equal(t, testFile, file.(neutralizedReaddirFile).File)
33+
}
34+
35+
func TestOnlyFilesFS_Open_err(t *testing.T) {
36+
testError := errors.New("mock")
37+
mockFS := &mockFileSystem{
38+
open: func(_ string) (http.File, error) {
39+
return nil, testError
40+
},
41+
}
42+
fs := &OnlyFilesFS{FileSystem: mockFS}
43+
44+
file, err := fs.Open("foo")
45+
46+
assert.ErrorIs(t, err, testError)
47+
assert.Nil(t, file)
48+
}
49+
50+
func Test_neuteredReaddirFile_Readdir(t *testing.T) {
51+
n := neutralizedReaddirFile{}
52+
53+
res, err := n.Readdir(0)
54+
55+
assert.NoError(t, err)
56+
assert.Nil(t, res)
57+
}
58+
59+
func TestDir_listDirectory(t *testing.T) {
60+
testRoot := "foo"
61+
fs := Dir(testRoot, true)
62+
63+
assert.Equal(t, http.Dir(testRoot), fs)
64+
}
65+
66+
func TestDir(t *testing.T) {
67+
testRoot := "foo"
68+
fs := Dir(testRoot, false)
69+
70+
assert.Equal(t, &OnlyFilesFS{FileSystem: http.Dir(testRoot)}, fs)
71+
}

‎routergroup.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileS
218218
fileServer := http.StripPrefix(absolutePath, http.FileServer(fs))
219219

220220
return func(c *Context) {
221-
if _, noListing := fs.(*onlyFilesFS); noListing {
221+
if _, noListing := fs.(*OnlyFilesFS); noListing {
222222
c.Writer.WriteHeader(http.StatusNotFound)
223223
}
224224

0 commit comments

Comments
 (0)