Skip to content

Commit 57e9d46

Browse files
committed
os: add DirFS
1 parent cd39edf commit 57e9d46

File tree

2 files changed

+118
-1
lines changed

2 files changed

+118
-1
lines changed

src/os/file_go_116.go

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,64 @@ package os
55
import (
66
"io"
77
"io/fs"
8+
"runtime"
89
)
910

1011
type (
1112
FileMode = fs.FileMode
1213
FileInfo = fs.FileInfo
1314
)
1415

15-
// The followings are copied from Go 1.16 official implementation:
16+
// The followings are copied from Go 1.16 or 1.17 official implementation:
1617
// https://github.com/golang/go/blob/go1.16/src/os/file.go
1718

19+
// DirFS returns a file system (an fs.FS) for the tree of files rooted at the directory dir.
20+
//
21+
// Note that DirFS("/prefix") only guarantees that the Open calls it makes to the
22+
// operating system will begin with "/prefix": DirFS("/prefix").Open("file") is the
23+
// same as os.Open("/prefix/file"). So if /prefix/file is a symbolic link pointing outside
24+
// the /prefix tree, then using DirFS does not stop the access any more than using
25+
// os.Open does. DirFS is therefore not a general substitute for a chroot-style security
26+
// mechanism when the directory tree contains arbitrary content.
27+
func DirFS(dir string) fs.FS {
28+
return dirFS(dir)
29+
}
30+
31+
func containsAny(s, chars string) bool {
32+
for i := 0; i < len(s); i++ {
33+
for j := 0; j < len(chars); j++ {
34+
if s[i] == chars[j] {
35+
return true
36+
}
37+
}
38+
}
39+
return false
40+
}
41+
42+
type dirFS string
43+
44+
func (dir dirFS) Open(name string) (fs.File, error) {
45+
if !fs.ValidPath(name) || runtime.GOOS == "windows" && containsAny(name, `\:`) {
46+
return nil, &PathError{Op: "open", Path: name, Err: ErrInvalid}
47+
}
48+
f, err := Open(string(dir) + "/" + name)
49+
if err != nil {
50+
return nil, err // nil fs.File
51+
}
52+
return f, nil
53+
}
54+
55+
func (dir dirFS) Stat(name string) (fs.FileInfo, error) {
56+
if !fs.ValidPath(name) || runtime.GOOS == "windows" && containsAny(name, `\:`) {
57+
return nil, &PathError{Op: "stat", Path: name, Err: ErrInvalid}
58+
}
59+
f, err := Stat(string(dir) + "/" + name)
60+
if err != nil {
61+
return nil, err
62+
}
63+
return f, nil
64+
}
65+
1866
// ReadFile reads the named file and returns the contents.
1967
// A successful call returns err == nil, not err == EOF.
2068
// Because ReadFile reads the whole file, it does not treat an EOF from Read

src/os/file_go_116_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Copyright 2009 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// +build go1.16,!baremetal,!js,!wasi
6+
7+
package os_test
8+
9+
import (
10+
"io/fs"
11+
"os"
12+
. "os"
13+
"path/filepath"
14+
"runtime"
15+
"testing"
16+
"testing/fstest"
17+
)
18+
19+
func TestDirFS(t *testing.T) {
20+
if runtime.GOOS == "windows" {
21+
t.Log("TODO: implement Readdir for Windows")
22+
return
23+
}
24+
if err := fstest.TestFS(DirFS("./testdata/dirfs"), "a", "b", "dir/x"); err != nil {
25+
t.Fatal(err)
26+
}
27+
28+
// Test that Open does not accept backslash as separator.
29+
d := DirFS(".")
30+
_, err := d.Open(`testdata\dirfs`)
31+
if err == nil {
32+
t.Fatalf(`Open testdata\dirfs succeeded`)
33+
}
34+
}
35+
36+
func TestDirFSPathsValid(t *testing.T) {
37+
if runtime.GOOS == "windows" {
38+
t.Log("skipping on Windows")
39+
return
40+
}
41+
42+
// TODO: switch back to t.TempDir once it's implemented
43+
d, err := MkdirTemp("", "TestDirFSPathsValid")
44+
if err != nil {
45+
t.Fatal(err)
46+
}
47+
defer Remove(d)
48+
if err := os.WriteFile(filepath.Join(d, "control.txt"), []byte(string("Hello, world!")), 0644); err != nil {
49+
t.Fatal(err)
50+
}
51+
defer Remove(filepath.Join(d, "control.txt"))
52+
if err := os.WriteFile(filepath.Join(d, `e:xperi\ment.txt`), []byte(string("Hello, colon and backslash!")), 0644); err != nil {
53+
t.Fatal(err)
54+
}
55+
defer Remove(filepath.Join(d, `e:xperi\ment.txt`))
56+
57+
fsys := os.DirFS(d)
58+
err = fs.WalkDir(fsys, ".", func(path string, e fs.DirEntry, err error) error {
59+
if fs.ValidPath(e.Name()) {
60+
t.Logf("%q ok", e.Name())
61+
} else {
62+
t.Errorf("%q INVALID", e.Name())
63+
}
64+
return nil
65+
})
66+
if err != nil {
67+
t.Fatal(err)
68+
}
69+
}

0 commit comments

Comments
 (0)