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
@@ -13,6 +13,7 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"rawmark/internal/fileutil"
|
||||
"rawmark/internal/i18n"
|
||||
"strings"
|
||||
"syscall"
|
||||
@@ -42,7 +43,7 @@ type Manifest struct {
|
||||
type Source struct{ Name, Path string }
|
||||
|
||||
func validPath(name string) bool {
|
||||
return name != "" && path.Clean(name) == name && name != "." && !strings.HasPrefix(name, "/") && name != ".." && !strings.HasPrefix(name, "../") && !strings.ContainsAny(name, "\\\x00")
|
||||
return fileutil.IsLocalPath(name) && path.Clean(name) == name && name != "." && !strings.ContainsAny(name, "\\\x00")
|
||||
}
|
||||
func validEntry(kind, name string) bool {
|
||||
if !validPath(name) {
|
||||
@@ -237,11 +238,11 @@ func Extract(ctx context.Context, archive, dest string) (Manifest, error) {
|
||||
}
|
||||
expanded += e.Size
|
||||
}
|
||||
var disk syscall.Statfs_t
|
||||
if err := syscall.Statfs(dest, &disk); err != nil {
|
||||
available, err := fileutil.AvailableBytes(dest)
|
||||
if err != nil {
|
||||
return manifest, err
|
||||
}
|
||||
if uint64(expanded)+(32<<20) > disk.Bavail*uint64(disk.Bsize) {
|
||||
if uint64(expanded)+(32<<20) > available {
|
||||
return manifest, i18n.New("not_enough_disk_space_w_a39b20", syscall.ENOSPC)
|
||||
}
|
||||
seen := map[string]bool{}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package backup
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRejectWindowsArchivePaths(t *testing.T) {
|
||||
for _, name := range []string{"C:escape", "vault/note.md:stream", "vault/CON", "vault/dir./note.md"} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
file := filepath.Join(t.TempDir(), "invalid.zip")
|
||||
f, err := os.Create(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
z := zip.NewWriter(f)
|
||||
if _, err := z.Create(name); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := errors.Join(z.Close(), f.Close()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := Extract(context.Background(), file, t.TempDir()); !errors.Is(err, ErrInvalidArchive) {
|
||||
t.Fatalf("unsafe archive path accepted: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
//go:build !windows
|
||||
|
||||
package backup
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func publishRecord(source, dest string) error {
|
||||
if err := os.Rename(source, dest); err != nil {
|
||||
return err
|
||||
}
|
||||
dir, err := os.Open(filepath.Dir(dest))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return errors.Join(dir.Sync(), dir.Close())
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package backup
|
||||
|
||||
import "golang.org/x/sys/windows"
|
||||
|
||||
func publishRecord(source, dest string) error {
|
||||
from, err := windows.UTF16PtrFromString(source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
to, err := windows.UTF16PtrFromString(dest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// The file was flushed before closing; request a completed on-disk replacement.
|
||||
return windows.MoveFileEx(from, to, windows.MOVEFILE_REPLACE_EXISTING|windows.MOVEFILE_WRITE_THROUGH)
|
||||
}
|
||||
@@ -51,15 +51,7 @@ func WriteRecord(data string, record RestoreRecord) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = os.Rename(f.Name(), dest); err != nil {
|
||||
return err
|
||||
}
|
||||
dir, err := os.Open(filepath.Dir(dest))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dir.Close()
|
||||
return dir.Sync()
|
||||
return publishRecord(f.Name(), dest)
|
||||
}
|
||||
func Pending(data string) (bool, error) {
|
||||
_, err := os.Stat(recordPath(data))
|
||||
|
||||
Reference in new issue
Block a user