TestNewFs verifies that NewFs picks the right implementation and that the follow-external-symlinks toggle flips whether a symlink pointing outside the scope is honored.
(t *testing.T)
| 12 | // follow-external-symlinks toggle flips whether a symlink pointing outside the |
| 13 | // scope is honored. |
| 14 | func TestNewFs(t *testing.T) { |
| 15 | base := t.TempDir() |
| 16 | scope := filepath.Join(base, "srv") |
| 17 | outside := filepath.Join(base, "outside") |
| 18 | for _, d := range []string{scope, outside} { |
| 19 | if err := os.MkdirAll(d, 0o755); err != nil { |
| 20 | t.Fatal(err) |
| 21 | } |
| 22 | } |
| 23 | if err := os.WriteFile(filepath.Join(outside, "secret.txt"), []byte("secret"), 0o644); err != nil { |
| 24 | t.Fatal(err) |
| 25 | } |
| 26 | // A symlink lexically inside the scope whose target resolves outside it. |
| 27 | if err := os.Symlink(outside, filepath.Join(scope, "escape")); err != nil { |
| 28 | t.Skipf("cannot create symlink: %v", err) |
| 29 | } |
| 30 | |
| 31 | t.Run("disabled returns a ScopedFs that rejects the escaping symlink", func(t *testing.T) { |
| 32 | fs := NewFs(afero.NewOsFs(), scope, false) |
| 33 | if _, ok := fs.(*ScopedFs); !ok { |
| 34 | t.Fatalf("expected *ScopedFs, got %T", fs) |
| 35 | } |
| 36 | if _, err := fs.Stat("/escape"); !os.IsPermission(err) { |
| 37 | t.Fatalf("expected stat of escaping symlink to be rejected, got %v", err) |
| 38 | } |
| 39 | }) |
| 40 | |
| 41 | t.Run("enabled returns a BasePathFs that follows the escaping symlink", func(t *testing.T) { |
| 42 | fs := NewFs(afero.NewOsFs(), scope, true) |
| 43 | if _, ok := fs.(*afero.BasePathFs); !ok { |
| 44 | t.Fatalf("expected *afero.BasePathFs, got %T", fs) |
| 45 | } |
| 46 | if _, err := fs.Stat("/escape"); err != nil { |
| 47 | t.Fatalf("expected escaping symlink to be followed, got %v", err) |
| 48 | } |
| 49 | b, err := afero.ReadFile(fs, "/escape/secret.txt") |
| 50 | if err != nil { |
| 51 | t.Fatalf("expected to read through escaping symlink, got %v", err) |
| 52 | } |
| 53 | if string(b) != "secret" { |
| 54 | t.Fatalf("got %q, want %q", b, "secret") |
| 55 | } |
| 56 | |
| 57 | // The link must also appear in a directory listing (the symptom in #5998). |
| 58 | entries, err := afero.ReadDir(fs, "/") |
| 59 | if err != nil { |
| 60 | t.Fatal(err) |
| 61 | } |
| 62 | var found bool |
| 63 | for _, e := range entries { |
| 64 | if e.Name() == "escape" { |
| 65 | found = true |
| 66 | } |
| 67 | } |
| 68 | if !found { |
| 69 | t.Fatal("expected escaping symlink to appear in the listing") |
| 70 | } |
| 71 | }) |