zakirullin--files.md
933 行
20 KiB
Go
933 行
20 KiB
Go
package fs
|
||
|
||
import (
|
||
"errors"
|
||
"fmt"
|
||
"os"
|
||
"path"
|
||
"strings"
|
||
"testing"
|
||
|
||
"github.com/spf13/afero"
|
||
"github.com/stretchr/testify/require"
|
||
)
|
||
|
||
func init() {
|
||
Ctime = func(fi os.FileInfo) int64 {
|
||
return 0
|
||
}
|
||
Mtime = func(fi os.FileInfo) int64 { return 0 }
|
||
}
|
||
|
||
func TestIsChecklistItem(t *testing.T) {
|
||
r := require.New(t)
|
||
|
||
r.False(IsChecklistItem("-checklist-"))
|
||
r.True(IsChecklistItem("-checklist-item"))
|
||
}
|
||
|
||
func TestDisplayName(t *testing.T) {
|
||
r := require.New(t)
|
||
|
||
title := DisplayName("filename")
|
||
r.Equal("Filename", title)
|
||
}
|
||
|
||
func TestDisplayNameWithSpace(t *testing.T) {
|
||
r := require.New(t)
|
||
|
||
title := DisplayName(" filename ")
|
||
r.Equal("Filename", title)
|
||
}
|
||
|
||
func TestMD5(t *testing.T) {
|
||
r := require.New(t)
|
||
|
||
fs, _ := NewFS("/", afero.NewMemMapFs())
|
||
res := fs.md5("First task.md")
|
||
|
||
r.Equal("0824149b387", res)
|
||
}
|
||
|
||
func TestExcludeChecklists(t *testing.T) {
|
||
r := require.New(t)
|
||
|
||
noChecklists := ExcludeChecklists([]File{{Name: "not-a-checklist"}, {Name: "_checklist_"}})
|
||
|
||
r.Equal([]File{{Name: "not-a-checklist"}}, noChecklists)
|
||
}
|
||
|
||
func TestExcludeSystemDirs(t *testing.T) {
|
||
r := require.New(t)
|
||
|
||
noChecklists := ExcludeSystemDirs([]File{{Name: "not-a-system-dir"}, {Name: "media"}, {Name: "archive"}, {Name: "journal"}})
|
||
|
||
r.Equal([]File{{Name: "not-a-system-dir"}}, noChecklists)
|
||
}
|
||
|
||
func TestIsMultiline(t *testing.T) {
|
||
r := require.New(t)
|
||
|
||
fs, _ := NewFS("/", afero.NewMemMapFs())
|
||
err := fs.Write("today", "First task.md", "")
|
||
r.NoError(err)
|
||
|
||
isMultiline, err := fs.IsMultiline("today", "First task.md")
|
||
r.NoError(err)
|
||
r.False(isMultiline)
|
||
|
||
err = fs.Write("today", "Second task.md", "c")
|
||
r.NoError(err)
|
||
|
||
isMultiline, err = fs.IsMultiline("today", "Second task.md")
|
||
r.NoError(err)
|
||
r.True(isMultiline)
|
||
|
||
err = fs.Write("today", "Third task.md", " \n ")
|
||
r.NoError(err)
|
||
|
||
isMultiline, err = fs.IsMultiline("today", "Third task.md")
|
||
r.NoError(err)
|
||
r.False(isMultiline)
|
||
}
|
||
|
||
func TestGetFilesInDir(t *testing.T) {
|
||
r := require.New(t)
|
||
|
||
fs, _ := NewFS("/", afero.NewMemMapFs())
|
||
err := fs.Write("today", "First task.md", "")
|
||
r.NoError(err)
|
||
|
||
files, err := fs.FilesAndDirs("today")
|
||
r.NoError(err)
|
||
r.Len(files, 1)
|
||
r.Equal("First task.md", files[0].Name)
|
||
}
|
||
|
||
func TestCreateBaseDirs(t *testing.T) {
|
||
r := require.New(t)
|
||
|
||
fs, err := NewFS("/", afero.NewMemMapFs())
|
||
r.NoError(err)
|
||
r.NoError(fs.CreateSystemDirs())
|
||
|
||
err = fs.CreateSystemDirs()
|
||
r.NoError(err)
|
||
|
||
dirs, err := fs.FilesAndDirs("/")
|
||
r.NoError(err)
|
||
dirs = OnlyDirs(dirs)
|
||
dirNames := OnlyFilenames(dirs)
|
||
|
||
r.ElementsMatch([]string{"archive", "media", "journal"}, dirNames)
|
||
}
|
||
|
||
func TestSortByCtimeDesc(t *testing.T) {
|
||
r := require.New(t)
|
||
|
||
saved := Ctime
|
||
defer func() {
|
||
Ctime = saved
|
||
}()
|
||
Ctime = func(fi os.FileInfo) int64 {
|
||
if fi.Name() == "b.md" {
|
||
return 1
|
||
}
|
||
|
||
return 2
|
||
}
|
||
|
||
fs, _ := NewFS("/", afero.NewMemMapFs())
|
||
err := fs.Write("today", "b.md", "")
|
||
r.NoError(err)
|
||
|
||
err = fs.Write("today", "a.md", "")
|
||
r.NoError(err)
|
||
|
||
entries, err := fs.FilesAndDirs("today")
|
||
r.NoError(err)
|
||
|
||
r.Equal([]string{"a.md", "b.md"}, OnlyFilenames(SortByCtimeDesc(entries)))
|
||
}
|
||
|
||
func TestExcludeEverythingButUserDirs(t *testing.T) {
|
||
r := require.New(t)
|
||
|
||
fs, _ := NewFS("/", afero.NewMemMapFs())
|
||
err := fs.Write("", "a.md", "")
|
||
r.NoError(err)
|
||
|
||
err = fs.MakeDir("dir")
|
||
r.NoError(err)
|
||
|
||
entries, err := fs.FilesAndDirs("/")
|
||
r.NoError(err)
|
||
|
||
dirs := OnlyDirs(ExcludeSystemDirs(entries))
|
||
r.Len(dirs, 1)
|
||
r.Equal("dir", dirs[0].Name)
|
||
}
|
||
|
||
func TestOnlyFiles(t *testing.T) {
|
||
r := require.New(t)
|
||
|
||
fs, _ := NewFS("/", afero.NewMemMapFs())
|
||
err := fs.Write("", "a.md", "")
|
||
r.NoError(err)
|
||
|
||
err = fs.MakeDir("dir")
|
||
r.NoError(err)
|
||
|
||
entries, err := fs.FilesAndDirs("/")
|
||
r.NoError(err)
|
||
|
||
dirs := OnlyUserMDFiles(entries)
|
||
r.Len(dirs, 1)
|
||
r.Equal("a.md", dirs[0].Name)
|
||
}
|
||
|
||
func TestOnlyChecklists(t *testing.T) {
|
||
r := require.New(t)
|
||
|
||
fs, _ := NewFS("/", afero.NewMemMapFs())
|
||
r.NoError(fs.Write("today", "a.md", ""))
|
||
r.NoError(fs.Write("/", "list_.md", ""))
|
||
// A non-markdown file whose name happens to end in "_" must NOT be
|
||
// treated as a checklist.
|
||
r.NoError(fs.Write("/", "Molchanov_.mobi", ""))
|
||
|
||
entries, err := fs.FilesAndDirs("/")
|
||
r.NoError(err)
|
||
|
||
dirs := OnlyChecklists(entries)
|
||
r.Len(dirs, 1)
|
||
r.Equal("list_.md", dirs[0].Name)
|
||
}
|
||
|
||
func TestFSTouchNew(t *testing.T) {
|
||
r := require.New(t)
|
||
|
||
fs, _ := NewFS("/", afero.NewMemMapFs())
|
||
exists, err := fs.Exists("today", "a.md")
|
||
r.NoError(err)
|
||
r.False(exists)
|
||
|
||
err = fs.Touch("today", "a.md")
|
||
r.NoError(err)
|
||
|
||
exists, err = fs.Exists("today", "a.md")
|
||
r.NoError(err)
|
||
r.True(exists)
|
||
}
|
||
|
||
func TestFSTouchExisting(t *testing.T) {
|
||
r := require.New(t)
|
||
fs, _ := NewFS("/", afero.NewMemMapFs())
|
||
err := fs.Write("today", "a.md", "A")
|
||
r.NoError(err)
|
||
|
||
err = fs.Touch("today", "a.md")
|
||
r.NoError(err)
|
||
|
||
content, err := fs.Read("today", "a.md")
|
||
r.NoError(err)
|
||
r.Equal("A", content)
|
||
}
|
||
|
||
func TestFSGetAllNotesInMatchingDir(t *testing.T) {
|
||
r := require.New(t)
|
||
fs, _ := NewFS("/", afero.NewMemMapFs())
|
||
err := fs.Touch("brain", "a.md")
|
||
r.NoError(err)
|
||
err = fs.Touch("today", "b.md")
|
||
r.NoError(err)
|
||
err = fs.Touch("non-matching-dir", "c.md")
|
||
r.NoError(err)
|
||
|
||
notes, err := fs.SearchFilesByName("BRAIN")
|
||
r.NoError(err)
|
||
r.Len(notes, 1)
|
||
r.Equal("a.md", notes[0].Name)
|
||
}
|
||
|
||
func TestFSGetAllMatchingNotesInMatchingDir(t *testing.T) {
|
||
r := require.New(t)
|
||
fs, _ := NewFS("/", afero.NewMemMapFs())
|
||
err := fs.Touch("brain", "a.md")
|
||
|
||
r.NoError(err)
|
||
err = fs.Touch("brain", "b.md")
|
||
r.NoError(err)
|
||
err = fs.Touch("today", "c.md")
|
||
r.NoError(err)
|
||
|
||
notes, err := fs.SearchFilesByName("BRAIN A")
|
||
r.NoError(err)
|
||
r.Len(notes, 1)
|
||
r.Equal("a.md", notes[0].Name)
|
||
}
|
||
|
||
func TestFSGetAllNotesInAllMatchingDirs(t *testing.T) {
|
||
r := require.New(t)
|
||
fs, _ := NewFS("/", afero.NewMemMapFs())
|
||
err := fs.Touch("brain", "a.md")
|
||
r.NoError(err)
|
||
err = fs.Touch("brain", "b.md")
|
||
r.NoError(err)
|
||
err = fs.Touch("today", "c.md")
|
||
r.NoError(err)
|
||
|
||
notes, err := fs.SearchFilesByName("brain")
|
||
r.NoError(err)
|
||
r.Len(notes, 2)
|
||
|
||
var noteFilenames []string
|
||
for _, note := range notes {
|
||
noteFilenames = append(noteFilenames, note.Name)
|
||
}
|
||
|
||
r.ElementsMatch([]string{"a.md", "b.md"}, noteFilenames)
|
||
}
|
||
|
||
func TestFSGetAllMatchingNotesInAllMatchingDirs(t *testing.T) {
|
||
r := require.New(t)
|
||
fs, _ := NewFS("/", afero.NewMemMapFs())
|
||
err := fs.Touch("brain", "a.md")
|
||
r.NoError(err)
|
||
err = fs.Touch("brain", "ab.md")
|
||
r.NoError(err)
|
||
err = fs.Touch("brain", "b.md")
|
||
r.NoError(err)
|
||
err = fs.Touch("today", "c.md")
|
||
r.NoError(err)
|
||
|
||
notes, err := fs.SearchFilesByName("brain a")
|
||
r.NoError(err)
|
||
r.Len(notes, 2)
|
||
|
||
var noteFilenames []string
|
||
for _, note := range notes {
|
||
noteFilenames = append(noteFilenames, note.Name)
|
||
}
|
||
|
||
r.ElementsMatch([]string{"a.md", "ab.md"}, noteFilenames)
|
||
}
|
||
|
||
func TestFSGetAllNotesInAllDirsForEmptyQuery(t *testing.T) {
|
||
r := require.New(t)
|
||
fs, _ := NewFS("/", afero.NewMemMapFs())
|
||
err := fs.Touch("brain", "a.md")
|
||
r.NoError(err)
|
||
err = fs.Touch("b", "b.md")
|
||
r.NoError(err)
|
||
|
||
notes, err := fs.SearchFilesByName("")
|
||
r.NoError(err)
|
||
r.Len(notes, 2)
|
||
|
||
var noteFilenames []string
|
||
for _, note := range notes {
|
||
noteFilenames = append(noteFilenames, note.Name)
|
||
}
|
||
|
||
r.ElementsMatch([]string{"a.md", "b.md"}, noteFilenames)
|
||
}
|
||
|
||
func TestFSPathTraversalAttack(t *testing.T) {
|
||
r := require.New(t)
|
||
|
||
fs, _ := NewFS("/abc", afero.NewMemMapFs())
|
||
fs.rootPath = "/abc"
|
||
|
||
path, err := fs.SafePath("../root/.ssh/", "authorized_keys")
|
||
r.Error(err)
|
||
r.Equal("", path)
|
||
|
||
path, err = fs.SafePath("note", "../root/.ssh/authorized_keys")
|
||
r.NoError(err)
|
||
r.Equal("/abc/root/.ssh/authorized_keys", path)
|
||
|
||
path, err = fs.SafePath("note", "../../root/.ssh/authorized_keys")
|
||
r.Error(err)
|
||
r.Empty(path)
|
||
}
|
||
|
||
func TestFSOnlyUserDirs(t *testing.T) {
|
||
r := require.New(t)
|
||
|
||
fs, err := NewFS("/", afero.NewMemMapFs())
|
||
r.NoError(err)
|
||
|
||
err = fs.MakeDir("str")
|
||
r.NoError(err)
|
||
|
||
err = fs.MakeDir("123")
|
||
r.NoError(err)
|
||
|
||
err = fs.MakeDir("123.56")
|
||
r.NoError(err)
|
||
|
||
dirs, _ := fs.FilesAndDirs("/")
|
||
userDirs := OnlyUserDirs(dirs)
|
||
|
||
r.Len(userDirs, 1)
|
||
r.Equal("123", userDirs[0].Name)
|
||
}
|
||
|
||
func TestIsSafeWrongRoot(t *testing.T) {
|
||
r := require.New(t)
|
||
|
||
fs, _ := NewFS("/a", afero.NewMemMapFs())
|
||
p, err := fs.SafePath("b", "")
|
||
r.NoError(err)
|
||
r.Equal("/a/b", p)
|
||
|
||
}
|
||
|
||
func TestIsSafePathTraversalAttack(t *testing.T) {
|
||
r := require.New(t)
|
||
|
||
fs, _ := NewFS("/a", afero.NewMemMapFs())
|
||
p, err := fs.SafePath("a/../b", "")
|
||
r.NoError(err)
|
||
r.Equal("/a/b", p)
|
||
|
||
p, err = fs.SafePath("/a/../../b", "")
|
||
r.Error(err)
|
||
r.Empty(p)
|
||
|
||
p, err = fs.SafePath("./a/../b", "")
|
||
r.NoError(err)
|
||
r.Equal("/a/b", p)
|
||
|
||
p, err = fs.SafePath("./a/../../b", "")
|
||
r.Error(err)
|
||
r.Empty(p)
|
||
}
|
||
|
||
func TestSafePath(t *testing.T) {
|
||
r := require.New(t)
|
||
|
||
memFS := afero.NewMemMapFs()
|
||
memFS.Mkdir("/app-secret", 0o755)
|
||
|
||
fs, _ := NewFS("/app", memFS)
|
||
|
||
p, err := fs.SafePath("subdir", "file")
|
||
r.NoError(err)
|
||
r.Equal("/app/subdir/file", p)
|
||
|
||
p, err = fs.SafePath("subdir", "../file")
|
||
r.NoError(err)
|
||
r.Equal("/app/file", p)
|
||
|
||
p, err = fs.SafePath("../app-secret", "file")
|
||
r.Error(err)
|
||
r.Equal("", p)
|
||
|
||
}
|
||
|
||
func TestIsSafePathTraversalAttackWithRelativePaths(t *testing.T) {
|
||
r := require.New(t)
|
||
|
||
fs, _ := NewFS(".", afero.NewMemMapFs())
|
||
p, err := fs.SafePath("./a/../b", "")
|
||
r.NoError(err)
|
||
r.Equal("b", p)
|
||
|
||
p, err = fs.SafePath("./a/../../b", "")
|
||
r.Error(err)
|
||
r.Empty(p)
|
||
}
|
||
|
||
func TestUnhashRootDirectory(t *testing.T) {
|
||
r := require.New(t)
|
||
|
||
fs, err := NewFS(".", afero.NewMemMapFs())
|
||
r.NoError(err)
|
||
// TODO is it used at all? What a strange behaviour?
|
||
unhashed, err := fs.Unhash("/", "/")
|
||
r.NoError(err)
|
||
|
||
r.Equal("/", unhashed)
|
||
}
|
||
|
||
func TestSanitizeFilename(t *testing.T) {
|
||
r := require.New(t)
|
||
|
||
r.Equal("ab", SanitizeFilename("a\x00b"))
|
||
r.Equal("a/b", SanitizeFilename("a/b"))
|
||
r.Equal("a\b", SanitizeFilename("a\\b"))
|
||
r.Equal("a/b\", SanitizeFilename("\x00a\x00/b\\"))
|
||
}
|
||
|
||
func TestUnsanitizeFilename(t *testing.T) {
|
||
r := require.New(t)
|
||
|
||
r.Equal("a/b", UnsanitizeFilename("a/b"))
|
||
r.Equal("a\\b", UnsanitizeFilename("a\b"))
|
||
r.Equal("a/b\\", UnsanitizeFilename("a/b\"))
|
||
}
|
||
|
||
func TestExists(t *testing.T) {
|
||
r := require.New(t)
|
||
|
||
fs, _ := NewFS("/", afero.NewMemMapFs())
|
||
err := fs.Write("today", "First task.md", "")
|
||
r.NoError(err)
|
||
|
||
exists, err := fs.Exists("today", "First task.md")
|
||
r.NoError(err)
|
||
r.True(exists)
|
||
}
|
||
|
||
func TestExistsRoot(t *testing.T) {
|
||
r := require.New(t)
|
||
|
||
fs, _ := NewFS("/", afero.NewMemMapFs())
|
||
err := fs.Write("today", "First task.md", "")
|
||
r.NoError(err)
|
||
|
||
exists, err := fs.Exists("/", "")
|
||
r.NoError(err)
|
||
r.True(exists)
|
||
}
|
||
|
||
func TestWriteAndReadFile(t *testing.T) {
|
||
r := require.New(t)
|
||
|
||
fs, _ := NewFS("/", afero.NewMemMapFs())
|
||
err := fs.Write("today", "test.md", "Test content")
|
||
r.NoError(err)
|
||
|
||
content, err := fs.Read("today", "test.md")
|
||
r.NoError(err)
|
||
r.Equal("Test content", content)
|
||
}
|
||
|
||
func TestWriteUnsafePath(t *testing.T) {
|
||
r := require.New(t)
|
||
|
||
fs, _ := NewFS("/1", afero.NewMemMapFs())
|
||
err := fs.Write("../unsafe", "test.md", "Test content")
|
||
r.Error(err)
|
||
r.Contains(err.Error(), "unsafe filePath")
|
||
}
|
||
|
||
func TestReadNonExistentFile(t *testing.T) {
|
||
r := require.New(t)
|
||
|
||
fs, _ := NewFS("/", afero.NewMemMapFs())
|
||
content, err := fs.Read("today", "nonexistent.md")
|
||
r.Error(err)
|
||
r.Equal("", content)
|
||
}
|
||
|
||
func TestDelFile(t *testing.T) {
|
||
r := require.New(t)
|
||
|
||
fs, _ := NewFS("/", afero.NewMemMapFs())
|
||
err := fs.Write("today", "delete.md", "To be deleted")
|
||
r.NoError(err)
|
||
|
||
err = fs.Del("today", "delete.md")
|
||
r.NoError(err)
|
||
|
||
exists, err := fs.Exists("today", "delete.md")
|
||
r.NoError(err)
|
||
r.False(exists)
|
||
}
|
||
|
||
func TestDelNonExistentFile(t *testing.T) {
|
||
r := require.New(t)
|
||
|
||
fs, _ := NewFS("/", afero.NewMemMapFs())
|
||
err := fs.Del("today", "nonexistent.md")
|
||
r.Error(err)
|
||
r.Contains(err.Error(), "can't remove")
|
||
}
|
||
|
||
func TestRenameFile(t *testing.T) {
|
||
r := require.New(t)
|
||
|
||
fs, _ := NewFS("/", afero.NewMemMapFs())
|
||
err := fs.Write("today", "oldname.md", "Old content")
|
||
r.NoError(err)
|
||
|
||
err = fs.Rename("today", "oldname.md", "today", "newname.md")
|
||
r.NoError(err)
|
||
|
||
exists, err := fs.Exists("today", "newname.md")
|
||
r.NoError(err)
|
||
r.True(exists)
|
||
|
||
content, err := fs.Read("today", "newname.md")
|
||
r.NoError(err)
|
||
r.Equal("Old content", content)
|
||
}
|
||
|
||
func TestRenameUnsafePath(t *testing.T) {
|
||
r := require.New(t)
|
||
|
||
fs, _ := NewFS("/1", afero.NewMemMapFs())
|
||
err := fs.Write("today", "oldname.md", "Old content")
|
||
r.NoError(err)
|
||
|
||
err = fs.Rename("../unsafe", "oldname.md", "today", "newname.md")
|
||
r.Error(err)
|
||
r.Contains(err.Error(), "unsafe path")
|
||
}
|
||
|
||
func TestMakeDir(t *testing.T) {
|
||
r := require.New(t)
|
||
|
||
fs, _ := NewFS("/", afero.NewMemMapFs())
|
||
err := fs.MakeDir("newdir")
|
||
r.NoError(err)
|
||
|
||
exists, err := fs.Exists("newdir", "")
|
||
r.NoError(err)
|
||
r.True(exists)
|
||
}
|
||
|
||
func TestMakeDirUnsafePath(t *testing.T) {
|
||
r := require.New(t)
|
||
|
||
fs, _ := NewFS("/1", afero.NewMemMapFs())
|
||
err := fs.MakeDir("../unsafe")
|
||
r.Error(err)
|
||
r.Contains(err.Error(), "unsafe path")
|
||
}
|
||
|
||
func TestUnsafePathSanitization(t *testing.T) {
|
||
r := require.New(t)
|
||
|
||
fs, _ := NewFS("/1", afero.NewMemMapFs())
|
||
|
||
p, err := fs.SafePath("../", "test.md")
|
||
r.Error(err)
|
||
r.Empty(p)
|
||
|
||
p, err = fs.SafePath("safe", "../unsafe.md")
|
||
r.NoError(err)
|
||
r.Equal("/1/unsafe.md", p)
|
||
}
|
||
|
||
func TestUnhash(t *testing.T) {
|
||
r := require.New(t)
|
||
|
||
fs, _ := NewFS("/", afero.NewMemMapFs())
|
||
err := fs.Touch("today", "hashedfile.md")
|
||
r.NoError(err)
|
||
|
||
hash := fs.md5("hashedfile.md")
|
||
unhashed, err := fs.Unhash("today", hash)
|
||
r.NoError(err)
|
||
r.Equal("hashedfile.md", unhashed)
|
||
}
|
||
|
||
func TestUnhashNonExistentFile(t *testing.T) {
|
||
r := require.New(t)
|
||
|
||
fs, _ := NewFS("/", afero.NewMemMapFs())
|
||
_, err := fs.Unhash("today", "nonexistenthash")
|
||
r.Error(err)
|
||
r.Contains(err.Error(), "can't unhash")
|
||
}
|
||
|
||
func TestSanitizeAndUnsanitizeFilename(t *testing.T) {
|
||
r := require.New(t)
|
||
|
||
sanitized := SanitizeFilename("test/file:name\\with/special\\chars")
|
||
r.Equal("test/file꞉name\with/special\chars", sanitized)
|
||
|
||
unsanitized := UnsanitizeFilename(sanitized)
|
||
r.Equal("test/file:name\\with/special\\chars", unsanitized)
|
||
}
|
||
|
||
func TestFilesAndDirs(t *testing.T) {
|
||
r := require.New(t)
|
||
|
||
fs, _ := NewFS("/", afero.NewMemMapFs())
|
||
err := fs.MakeDir("dir1")
|
||
r.NoError(err)
|
||
|
||
err = fs.Write("dir1", "file1.md", "File content")
|
||
r.NoError(err)
|
||
|
||
files, err := fs.FilesAndDirs("dir1")
|
||
r.NoError(err)
|
||
r.Len(files, 1)
|
||
r.Equal("file1.md", files[0].Name)
|
||
}
|
||
|
||
func TestDirs(t *testing.T) {
|
||
r := require.New(t)
|
||
|
||
fs, _ := NewFS("/", afero.NewMemMapFs())
|
||
err := fs.MakeDir("dir1")
|
||
r.NoError(err)
|
||
|
||
err = fs.Write("dir1", "file1.md", "File content")
|
||
r.NoError(err)
|
||
|
||
dirs, err := fs.Dirs()
|
||
r.NoError(err)
|
||
r.Len(dirs, 1)
|
||
r.Equal("dir1", dirs[0].Name)
|
||
}
|
||
|
||
func TestTouchFile(t *testing.T) {
|
||
r := require.New(t)
|
||
|
||
fs, _ := NewFS("/", afero.NewMemMapFs())
|
||
err := fs.Touch("today", "touchfile.md")
|
||
r.NoError(err)
|
||
|
||
exists, err := fs.Exists("today", "touchfile.md")
|
||
r.NoError(err)
|
||
r.True(exists)
|
||
}
|
||
|
||
func FuzzWrite(f *testing.F) {
|
||
f.Add("valid_dir", "valid_file.txt", "This is valid content")
|
||
f.Add("valid_dir", "../../unsafe_file.txt", "Unsafe path content")
|
||
f.Add("valid_dir/subdir", "valid_file.md", "Nested content")
|
||
f.Add("invalid<>|*?dir", "invalid_file.txt", "Invalid dir")
|
||
f.Add("valid_dir", "file_with_emoji_🀀.txt", "Unicode content")
|
||
f.Add("../", "file.md", "content")
|
||
f.Add("../../", "file.md", "content")
|
||
f.Add("../../../", "file.md", "content")
|
||
f.Add("dir", "../file.md", "content")
|
||
f.Add("dir", "../../file.md", "content")
|
||
f.Add("dir", "../../../file.md", "content")
|
||
|
||
f.Fuzz(func(t *testing.T, dir, filename, content string) {
|
||
filename = filename + ".md"
|
||
|
||
r := require.New(t)
|
||
|
||
fs := afero.NewMemMapFs()
|
||
_ = fs.Mkdir("/user", 0o755)
|
||
|
||
userFS, err := NewFS("/user", fs)
|
||
r.NoError(err)
|
||
|
||
err = userFS.Write(dir, filename, content)
|
||
|
||
unsafePath := path.Join("/user/", dir, filename)
|
||
otherUserDir := !strings.HasPrefix(unsafePath, "/user/")
|
||
if unsafePath == "/user" {
|
||
otherUserDir = false
|
||
}
|
||
if otherUserDir || strings.Contains(unsafePath, "../") {
|
||
if !errors.Is(err, ErrUnsafePath) {
|
||
t.Errorf("Expected unsafe path error for dir: '%s', filename: '%s', calculated path: '%s', got: '%v'", dir, filename, unsafePath, err)
|
||
}
|
||
return
|
||
}
|
||
|
||
r.NoError(err, "Unexpected error for valid inputs dir: '%s', filename: '%s'", dir, filename)
|
||
|
||
filePath, err := userFS.SafePath(dir, filename)
|
||
r.NoError(err)
|
||
|
||
actualContent, readErr := afero.ReadFile(userFS.backend, filePath)
|
||
r.NoError(readErr, "Error reading file: %s", filePath)
|
||
r.Equal(content, string(actualContent), "Content mismatch for file: %s", filePath)
|
||
|
||
// Check that a file is indeed created in user fs, and not outside
|
||
files, err := userFS.FilesAndDirs("/")
|
||
r.NoError(err, "Unexpected error for valid inputs dir: '%s', filename: '%s', calculated path: '%s", dir, filename, unsafePath)
|
||
r.Len(files, 1, "File has been written outside of user dir. Provided dir: '%s', filename: '%s', unsafe path: '%s", dir, filename, filePath)
|
||
})
|
||
}
|
||
|
||
func TestCtimes(t *testing.T) {
|
||
r := require.New(t)
|
||
|
||
saved := Mtime
|
||
defer func() {
|
||
Mtime = saved
|
||
}()
|
||
|
||
Mtime = func(fi os.FileInfo) int64 {
|
||
switch fi.Name() {
|
||
case "file1.md":
|
||
return 1000
|
||
case "file2.md":
|
||
return 2000
|
||
case "nested.md":
|
||
return 3000
|
||
default:
|
||
return 0
|
||
}
|
||
}
|
||
|
||
fs, err := NewFS("/", afero.NewMemMapFs())
|
||
r.NoError(err)
|
||
|
||
err = fs.Write("/", "file1.md", "content1")
|
||
r.NoError(err)
|
||
err = fs.Write("today", "file2.md", "content2")
|
||
r.NoError(err)
|
||
err = fs.Write("/", "not-markdown.txt", "should be ignored")
|
||
r.NoError(err)
|
||
err = fs.MakeDir("today/subdir")
|
||
r.NoError(err)
|
||
err = fs.Write("today/subdir", "nested.md", "nested content")
|
||
r.NoError(err)
|
||
|
||
// Create hidden file (should be ignored)
|
||
err = fs.Write("today", ".hidden.md", "hidden content")
|
||
r.NoError(err)
|
||
|
||
// Test Ctimes
|
||
ctimes, err := fs.Mtimes("/", ".md")
|
||
r.NoError(err)
|
||
|
||
fmt.Println(ctimes)
|
||
r.Len(ctimes, 3)
|
||
r.Equal(int64(1000), ctimes["file1.md"])
|
||
r.Equal(int64(2000), ctimes["today/file2.md"])
|
||
r.Equal(int64(3000), ctimes["today/subdir/nested.md"])
|
||
|
||
// Should not include non-markdown files or hidden files
|
||
_, exists := ctimes["today/not-markdown.txt"]
|
||
r.False(exists)
|
||
|
||
_, exists = ctimes["today/.hidden.md"]
|
||
r.False(exists)
|
||
}
|
||
|
||
func TestMtimesAllExtensions(t *testing.T) {
|
||
r := require.New(t)
|
||
|
||
saved := Mtime
|
||
defer func() {
|
||
Mtime = saved
|
||
}()
|
||
|
||
Mtime = func(fi os.FileInfo) int64 {
|
||
switch fi.Name() {
|
||
case "file1.md":
|
||
return 1000
|
||
case "file2.md":
|
||
return 2000
|
||
case "nested.md":
|
||
return 3000
|
||
case "file.txt":
|
||
return 3000
|
||
default:
|
||
return 0
|
||
}
|
||
}
|
||
|
||
fs, err := NewFS("/", afero.NewMemMapFs())
|
||
r.NoError(err)
|
||
|
||
err = fs.Write("/", "file1.md", "content1")
|
||
r.NoError(err)
|
||
err = fs.Write("today", "file2.md", "content2")
|
||
r.NoError(err)
|
||
err = fs.Write("/", "file.txt", "should be ignored")
|
||
r.NoError(err)
|
||
err = fs.MakeDir("today/subdir")
|
||
r.NoError(err)
|
||
err = fs.Write("today/subdir", "nested.md", "nested content")
|
||
r.NoError(err)
|
||
|
||
// Create hidden file (should be ignored)
|
||
err = fs.Write("today", ".hidden.md", "hidden content")
|
||
r.NoError(err)
|
||
|
||
ctimes, err := fs.Mtimes("/")
|
||
r.NoError(err)
|
||
|
||
r.Len(ctimes, 4)
|
||
r.Equal(int64(1000), ctimes["file1.md"])
|
||
r.Equal(int64(2000), ctimes["today/file2.md"])
|
||
r.Equal(int64(3000), ctimes["today/subdir/nested.md"])
|
||
r.Equal(int64(3000), ctimes["file.txt"])
|
||
|
||
// Should not include non-markdown files or hidden files
|
||
_, exists := ctimes["today/not-markdown.txt"]
|
||
r.False(exists)
|
||
|
||
_, exists = ctimes["today/.hidden.md"]
|
||
r.False(exists)
|
||
}
|
||
|
||
func TestMtimesInSubDir(t *testing.T) {
|
||
r := require.New(t)
|
||
|
||
saved := Mtime
|
||
defer func() {
|
||
Mtime = saved
|
||
}()
|
||
|
||
Mtime = func(fi os.FileInfo) int64 {
|
||
switch fi.Name() {
|
||
case "file1.md":
|
||
return 1000
|
||
case "file2.md":
|
||
return 2000
|
||
case "nested.md":
|
||
return 3000
|
||
default:
|
||
return 0
|
||
}
|
||
}
|
||
|
||
fs, err := NewFS("/", afero.NewMemMapFs())
|
||
r.NoError(err)
|
||
|
||
err = fs.Write("/", "rootfile.md", "content1")
|
||
r.NoError(err)
|
||
err = fs.Write("today", "file1.md", "content1")
|
||
r.NoError(err)
|
||
err = fs.Write("today", "file2.md", "content2")
|
||
r.NoError(err)
|
||
err = fs.Write("today", "not-markdown.txt", "should be ignored")
|
||
r.NoError(err)
|
||
err = fs.MakeDir("today/subdir")
|
||
r.NoError(err)
|
||
err = fs.Write("today/subdir", "nested.md", "nested content")
|
||
r.NoError(err)
|
||
|
||
// Create hidden file (should be ignored)
|
||
err = fs.Write("today", ".hidden.md", "hidden content")
|
||
r.NoError(err)
|
||
|
||
// Test Ctimes
|
||
ctimes, err := fs.Mtimes("today", ".md")
|
||
r.NoError(err)
|
||
|
||
// Should only include .md files, not .txt or hidden files
|
||
r.Len(ctimes, 3)
|
||
r.Equal(int64(1000), ctimes["file1.md"])
|
||
r.Equal(int64(2000), ctimes["file2.md"])
|
||
r.Equal(int64(3000), ctimes["subdir/nested.md"])
|
||
|
||
// Should not include non-markdown files or hidden files
|
||
_, exists := ctimes["today/not-markdown.txt"]
|
||
r.False(exists)
|
||
|
||
_, exists = ctimes["today/.hidden.md"]
|
||
r.False(exists)
|
||
}
|
||
|
||
func TestCtimesEmptyDir(t *testing.T) {
|
||
r := require.New(t)
|
||
|
||
fs, err := NewFS("/", afero.NewMemMapFs())
|
||
r.NoError(err)
|
||
|
||
err = fs.MakeDir("empty")
|
||
r.NoError(err)
|
||
|
||
ctimes, err := fs.Mtimes("empty", ".md")
|
||
r.NoError(err)
|
||
r.Empty(ctimes)
|
||
}
|