fix(platform): support Windows storage and native filesystem boundaries
This commit is contained in:
1 parent
3726423338
commit
e239fff253
24 files changed
+355
-51
No files matched your search
@@ -62,6 +62,8 @@ func TestExportNotesKeepsMarkdownAndReportsMissingAssets(t *testing.T) {
|
||||
func TestExportNotesExcludesItsOwnOutputInsideVault(t *testing.T) {
|
||||
v := newVault(t)
|
||||
t.Setenv("TMPDIR", v.Root())
|
||||
t.Setenv("TMP", v.Root())
|
||||
t.Setenv("TEMP", v.Root())
|
||||
if _, err := v.Write("note.md", []byte("source"), Precondition{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package vault
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRejectWindowsDeviceAndAliasPaths(t *testing.T) {
|
||||
v := newVault(t)
|
||||
for _, name := range []string{"C:escape.md", "note.md:stream", "CON", "dir/NUL.md", "dir./note.md", "dir /note.md"} {
|
||||
if _, err := v.Write(name, []byte("must not write"), Precondition{}); !errors.Is(err, ErrInvalidPath) {
|
||||
t.Errorf("unsafe Windows path %q: %v", name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,15 +6,15 @@ import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"rawmark/internal/testutil"
|
||||
"runtime"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestTrashPurgeFailurePreservesMetadata(t *testing.T) {
|
||||
if os.Geteuid() == 0 {
|
||||
t.Skip("目录写权限测试需以普通用户运行;root 可绕过文件权限")
|
||||
}
|
||||
|
||||
for _, operation := range []string{"single", "all", "expired"} {
|
||||
for _, blocked := range []string{"payload", "parent"} {
|
||||
t.Run(operation+"/"+blocked, func(t *testing.T) {
|
||||
@@ -47,11 +47,11 @@ func TestTrashPurgeFailurePreservesMetadata(t *testing.T) {
|
||||
locked := filepath.Join(v.trash, item.ID, "folder", "locked")
|
||||
if blocked == "parent" {
|
||||
locked = v.trash
|
||||
if runtime.GOOS == "windows" {
|
||||
locked = filepath.Join(v.trash, item.ID)
|
||||
}
|
||||
}
|
||||
if err := os.Chmod(locked, 0500); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() { os.Chmod(locked, 0755) })
|
||||
allowRemove := testutil.DenyRemove(t, locked)
|
||||
purge := func() error {
|
||||
if operation == "single" {
|
||||
return v.Purge(item.ID)
|
||||
@@ -80,9 +80,7 @@ func TestTrashPurgeFailurePreservesMetadata(t *testing.T) {
|
||||
if err != nil || len(items) != 1 || items[0].ID != item.ID {
|
||||
t.Fatalf("删除失败的条目必须仍可列出: %+v %v", items, err)
|
||||
}
|
||||
if err := os.Chmod(locked, 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
allowRemove()
|
||||
if err := purge(); err != nil {
|
||||
t.Fatalf("修复权限后应可继续清理: %v", err)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"rawmark/internal/testutil"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -145,9 +146,7 @@ func TestTreeIconValidationAndCorruptMetadata(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestTreeIconWriteFailureRollsBack(t *testing.T) {
|
||||
if os.Geteuid() == 0 {
|
||||
t.Skip("目录写权限测试需以普通用户运行;root 可绕过文件权限")
|
||||
}
|
||||
|
||||
for _, operation := range []string{"set", "move", "delete", "restore"} {
|
||||
t.Run(operation, func(t *testing.T) {
|
||||
v := newVault(t)
|
||||
@@ -169,10 +168,7 @@ func TestTreeIconWriteFailureRollsBack(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Chmod(filepath.Join(v.root, metaDir), 0555); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Chmod(filepath.Join(v.root, metaDir), 0755)
|
||||
testutil.DenyReplace(t, filepath.Join(v.root, metaDir, "tree-icons.json"))
|
||||
switch operation {
|
||||
case "set":
|
||||
err = v.SetTreeIcon("a.md", &TreeIcon{"builtin", "folder"})
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"rawmark/internal/fileutil"
|
||||
"rawmark/internal/i18n"
|
||||
"regexp"
|
||||
"sort"
|
||||
@@ -97,7 +98,7 @@ func Rev(content []byte) string {
|
||||
// CleanPath validates client-relative paths: reject absolute paths, empty segments, "."/"..", and hidden segments starting with ".".
|
||||
// Hidden segments include .rawmark metadata and .git; none are exposed through the API.
|
||||
func CleanPath(p string) (string, error) {
|
||||
if p == "" || strings.HasPrefix(p, "/") || strings.ContainsAny(p, "\\\x00") {
|
||||
if !fileutil.IsLocalPath(p) || strings.ContainsAny(p, "\\\x00") {
|
||||
return "", ErrInvalidPath
|
||||
}
|
||||
for _, seg := range strings.Split(p, "/") {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
@@ -130,7 +131,11 @@ func TestRestoreRollsBackWhenAssetsAreMissing(t *testing.T) {
|
||||
|
||||
func TestAssetLinksWithReservedNotePath(t *testing.T) {
|
||||
v := newVault(t)
|
||||
link, err := v.SaveAsset("目录 #?%20/n.md", "x.png", strings.NewReader("image"), 100)
|
||||
folder := "目录 #?%20"
|
||||
if runtime.GOOS == "windows" {
|
||||
folder = "目录 #%20"
|
||||
}
|
||||
link, err := v.SaveAsset(folder+"/n.md", "x.png", strings.NewReader("image"), 100)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -138,7 +143,7 @@ func TestAssetLinksWithReservedNotePath(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
file, _, err := v.OpenRaw(filepath.ToSlash(filepath.Join("目录 #?%20", decoded)))
|
||||
file, _, err := v.OpenRaw(filepath.ToSlash(filepath.Join(folder, decoded)))
|
||||
if err != nil {
|
||||
t.Fatalf("资源链接未指向实际文件: %s %v", link, err)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user