返回 DeepSeek-Reasonix
confinedread_windows.go
根目录 / internal / fileutil / confinedread_windows.go
1 //go:build windows
2
3 package fileutil
4
5 import (
6 "fmt"
7 "os"
8 "path/filepath"
9 "strings"
10
11 "golang.org/x/sys/windows"
12 )
13
14 const maxConfinedFinalPathUTF16 = 1 << 16
15
16 // OpenFileBeneath validates the final path represented by the same handle that
17 // is returned for reading. Directory junction or symlink swaps after CreateFile
18 // therefore cannot redirect the subsequent read.
19 func OpenFileBeneath(root, rel string) (*os.File, error) {
20 root = filepath.Clean(strings.TrimSpace(root))
21 if root == "" || root == "." {
22 return nil, fmt.Errorf("workspace root is empty")
23 }
24 rel, err := confinedRelativePath(rel)
25 if err != nil {
26 return nil, err
27 }
28 rootFile, err := os.Open(root)
29 if err != nil {
30 return nil, fmt.Errorf("open workspace root: %w", err)
31 }
32 defer rootFile.Close()
33 rootFinal, err := FinalWindowsPath(windows.Handle(rootFile.Fd()))
34 if err != nil {
35 return nil, fmt.Errorf("resolve workspace root: %w", err)
36 }
37
38 file, err := os.Open(filepath.Join(root, rel))
39 if err != nil {
40 return nil, err
41 }
42 targetFinal, err := FinalWindowsPath(windows.Handle(file.Fd()))
43 if err != nil {
44 file.Close()
45 return nil, fmt.Errorf("resolve workspace file: %w", err)
46 }
47 relFinal, err := filepath.Rel(rootFinal, targetFinal)
48 if err != nil || relFinal == ".." || strings.HasPrefix(relFinal, ".."+string(filepath.Separator)) {
49 file.Close()
50 return nil, fmt.Errorf("file resolves outside workspace root")
51 }
52 return file, nil
53 }
54
55 // FinalWindowsPath returns the DOS/UNC path of an open file or directory,
56 // resolving directory junctions through the authoritative Windows handle.
57 func FinalWindowsPath(handle windows.Handle) (string, error) {
58 size := uint32(256)
59 for {
60 buf := make([]uint16, size)
61 n, err := windows.GetFinalPathNameByHandle(handle, &buf[0], size, 0)
62 if err != nil {
63 return "", err
64 }
65 if n < size {
66 path := windows.UTF16ToString(buf[:n])
67 path = strings.TrimPrefix(path, `\\?\`)
68 if strings.HasPrefix(strings.ToUpper(path), `UNC\`) {
69 path = `\\` + path[len(`UNC\`):]
70 }
71 return filepath.Clean(path), nil
72 }
73 if n >= maxConfinedFinalPathUTF16 {
74 return "", fmt.Errorf("resolved path is too large")
75 }
76 size = n + 1
77 }
78 }
79
79 lines GO