fix(platform): support Windows storage and native filesystem boundaries

This commit is contained in:
eezd committed 2026-10-27 14:47:25 +08:00
1 parent 3726423338
commit e239fff253
24 files changed
+355 -51

No files matched your search

+2
View File
@@ -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)
}
+15
View File
@@ -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)
}
}
}
+8 -10
View File
@@ -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)
}
+3 -7
View File
@@ -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"})
+2 -1
View File
@@ -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, "/") {
+7 -2
View File
@@ -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)
}