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
@@ -78,6 +78,8 @@ func TestPortableNotesExcludeApplicationData(t *testing.T) {
}
// As in the real process, export temporary files live inside the application data directory.
t.Setenv("TMPDIR", filepath.Join(data, "temp"))
t.Setenv("TMP", filepath.Join(data, "temp"))
t.Setenv("TEMP", filepath.Join(data, "temp"))
res = f.do(f.admin, "POST", "/api/v1/maintenance/notes/export", "", nil)
defer res.Body.Close()
assertOK(t, res)
+4 -3
View File
@@ -9,6 +9,7 @@ import (
"net/http"
"os"
"path/filepath"
"rawmark/internal/fileutil"
"rawmark/internal/i18n"
"regexp"
"strconv"
@@ -65,11 +66,11 @@ func saveUpload(dir string, meta maintenanceUpload) error {
return os.WriteFile(filepath.Join(dir, "upload.json"), data, 0600)
}
func enoughSpace(dir string, size int64) error {
var stat syscall.Statfs_t
if err := syscall.Statfs(dir, &stat); err != nil {
available, err := fileutil.AvailableBytes(dir)
if err != nil {
return err
}
if size < 0 || uint64(size)+(32<<20) > stat.Bavail*uint64(stat.Bsize) {
if size < 0 || uint64(size)+(32<<20) > available {
return i18n.New("not_enough_disk_space_w_a39b20", syscall.ENOSPC)
}
return nil
+3 -2
View File
@@ -14,6 +14,7 @@ import (
"os"
"path"
"path/filepath"
"rawmark/internal/fileutil"
"rawmark/internal/i18n"
"regexp"
"strings"
@@ -42,7 +43,7 @@ type pluginInfo struct {
}
func validPluginID(id string) bool {
return pluginIDPattern.MatchString(id) && !strings.HasPrefix(id, "rawmark-")
return pluginIDPattern.MatchString(id) && fileutil.IsLocalPath(id) && !strings.HasPrefix(id, "rawmark-")
}
func parsePluginManifest(data []byte) (pluginManifest, error) {
@@ -181,7 +182,7 @@ func unpackPlugin(data []byte, destination string) (pluginInfo, error) {
var total int64
for _, file := range archive.File {
name := strings.TrimSuffix(file.Name, "/")
if !fs.ValidPath(name) || strings.ContainsAny(name, "\\:\x00") || strings.HasPrefix(name, ".") || name != path.Clean(name) || seen[name] || (!file.FileInfo().IsDir() && !file.Mode().IsRegular()) {
if !fs.ValidPath(name) || !fileutil.IsLocalPath(name) || strings.ContainsAny(name, "\\:\x00") || strings.HasPrefix(name, ".") || name != path.Clean(name) || seen[name] || (!file.FileInfo().IsDir() && !file.Mode().IsRegular()) {
return info, i18n.New("plugin_contains_invalid_paths_duplicate_6a1e05")
}
seen[name] = true
+38
View File
@@ -0,0 +1,38 @@
package api
import (
"archive/zip"
"bytes"
"testing"
)
func TestWindowsPluginPaths(t *testing.T) {
if validPluginID("con") {
t.Fatal("reserved Windows plugin ID accepted")
}
for _, name := range []string{"main.js.", "dir /module.js", "NUL.js", "module.js:stream"} {
t.Run(name, func(t *testing.T) {
var data bytes.Buffer
archive := zip.NewWriter(&data)
for _, entry := range []struct{ name, content string }{
{"manifest.json", `{"id":"portable","name":"Portable","version":"1","apiVersion":1}`},
{"main.js", "export default {}"},
{name, "must not overwrite"},
} {
file, err := archive.Create(entry.name)
if err != nil {
t.Fatal(err)
}
if _, err := file.Write([]byte(entry.content)); err != nil {
t.Fatal(err)
}
}
if err := archive.Close(); err != nil {
t.Fatal(err)
}
if _, err := unpackPlugin(data.Bytes(), t.TempDir()); err == nil {
t.Fatal("unsafe Windows archive path accepted")
}
})
}
}