From 98a6ed8059c121fe244b31287a4399d2436418b3 Mon Sep 17 00:00:00 2001 From: Dan Kegel Date: Sun, 23 Jan 2022 12:56:09 -0800 Subject: [PATCH] os: add DirFS, which is used by many programs to access readdir. It's wafer-thin :-) Includes smoke test from upstream. TODO: once t.TempDir is implemented, add io/fs to the list of standard library tests to run; that's a better test. --- src/os/file_go_116.go | 50 ++++++++++++++++++++++++++- src/os/file_go_116_test.go | 71 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 src/os/file_go_116_test.go diff --git a/src/os/file_go_116.go b/src/os/file_go_116.go index 807c2598..4b59e202 100644 --- a/src/os/file_go_116.go +++ b/src/os/file_go_116.go @@ -5,6 +5,7 @@ package os import ( "io" "io/fs" + "runtime" ) type ( @@ -12,9 +13,56 @@ type ( FileInfo = fs.FileInfo ) -// The followings are copied from Go 1.16 official implementation: +// The followings are copied from Go 1.16 or 1.17 official implementation: // https://github.com/golang/go/blob/go1.16/src/os/file.go +// DirFS returns a file system (an fs.FS) for the tree of files rooted at the directory dir. +// +// Note that DirFS("/prefix") only guarantees that the Open calls it makes to the +// operating system will begin with "/prefix": DirFS("/prefix").Open("file") is the +// same as os.Open("/prefix/file"). So if /prefix/file is a symbolic link pointing outside +// the /prefix tree, then using DirFS does not stop the access any more than using +// os.Open does. DirFS is therefore not a general substitute for a chroot-style security +// mechanism when the directory tree contains arbitrary content. +func DirFS(dir string) fs.FS { + return dirFS(dir) +} + +func containsAny(s, chars string) bool { + for i := 0; i < len(s); i++ { + for j := 0; j < len(chars); j++ { + if s[i] == chars[j] { + return true + } + } + } + return false +} + +type dirFS string + +func (dir dirFS) Open(name string) (fs.File, error) { + if !fs.ValidPath(name) || runtime.GOOS == "windows" && containsAny(name, `\:`) { + return nil, &PathError{Op: "open", Path: name, Err: ErrInvalid} + } + f, err := Open(string(dir) + "/" + name) + if err != nil { + return nil, err // nil fs.File + } + return f, nil +} + +func (dir dirFS) Stat(name string) (fs.FileInfo, error) { + if !fs.ValidPath(name) || runtime.GOOS == "windows" && containsAny(name, `\:`) { + return nil, &PathError{Op: "stat", Path: name, Err: ErrInvalid} + } + f, err := Stat(string(dir) + "/" + name) + if err != nil { + return nil, err + } + return f, nil +} + // ReadFile reads the named file and returns the contents. // A successful call returns err == nil, not err == EOF. // Because ReadFile reads the whole file, it does not treat an EOF from Read diff --git a/src/os/file_go_116_test.go b/src/os/file_go_116_test.go new file mode 100644 index 00000000..fa582e21 --- /dev/null +++ b/src/os/file_go_116_test.go @@ -0,0 +1,71 @@ +// Copyright 2009 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. + +// +build go1.16,!baremetal,!js,!wasi + +// DirFS tests copied verbatim from upstream os_test.go, and adjusted minimally to fit tinygo. + +package os_test + +import ( + "io/fs" + "os" + . "os" + "path/filepath" + "runtime" + "testing" + "testing/fstest" +) + +func TestDirFS(t *testing.T) { + if runtime.GOOS == "windows" { + t.Log("TODO: implement Readdir for Windows") + return + } + if err := fstest.TestFS(DirFS("./testdata/dirfs"), "a", "b", "dir/x"); err != nil { + t.Fatal(err) + } + + // Test that Open does not accept backslash as separator. + d := DirFS(".") + _, err := d.Open(`testdata\dirfs`) + if err == nil { + t.Fatalf(`Open testdata\dirfs succeeded`) + } +} + +func TestDirFSPathsValid(t *testing.T) { + if runtime.GOOS == "windows" { + t.Log("skipping on Windows") + return + } + + // TODO: switch back to t.TempDir once it's implemented + d, err := MkdirTemp("", "TestDirFSPathsValid") + if err != nil { + t.Fatal(err) + } + defer Remove(d) + if err := os.WriteFile(filepath.Join(d, "control.txt"), []byte(string("Hello, world!")), 0644); err != nil { + t.Fatal(err) + } + defer Remove(filepath.Join(d, "control.txt")) + if err := os.WriteFile(filepath.Join(d, `e:xperi\ment.txt`), []byte(string("Hello, colon and backslash!")), 0644); err != nil { + t.Fatal(err) + } + defer Remove(filepath.Join(d, `e:xperi\ment.txt`)) + + fsys := os.DirFS(d) + err = fs.WalkDir(fsys, ".", func(path string, e fs.DirEntry, err error) error { + if fs.ValidPath(e.Name()) { + t.Logf("%q ok", e.Name()) + } else { + t.Errorf("%q INVALID", e.Name()) + } + return nil + }) + if err != nil { + t.Fatal(err) + } +}