返回 DeepSeek-Reasonix
local_path_source.go
根目录 / desktop / local_path_source.go
1 package main
2
3 import (
4 "net/url"
5 "os"
6 "path/filepath"
7 "runtime"
8 "strings"
9 )
10
11 // localPathSource parses Markdown image sources, whose relative path is URL
12 // encoded by the Markdown pipeline. Chat references use localChatPathSource so
13 // ordinary filesystem characters are never mistaken for URL syntax.
14 func localPathSource(source string) (string, error) {
15 source = strings.TrimSpace(source)
16 if source == "" || strings.ContainsRune(source, 0) {
17 return "", os.ErrInvalid
18 }
19 // Raw Windows drive/UNC paths are valid sources even though net/url would
20 // otherwise interpret the drive letter as a URL scheme.
21 if filepath.IsAbs(source) && !strings.ContainsAny(source, "?#") {
22 return filepath.Clean(source), nil
23 }
24 if strings.HasPrefix(strings.ToLower(source), "file:") {
25 u, err := url.Parse(source)
26 if err != nil || !strings.EqualFold(u.Scheme, "file") || u.User != nil || u.RawQuery != "" || u.Fragment != "" {
27 return "", os.ErrInvalid
28 }
29 if u.Host != "" && !strings.EqualFold(u.Host, "localhost") {
30 return "", os.ErrPermission
31 }
32 path := u.Path
33 if strings.ContainsRune(path, 0) {
34 return "", os.ErrInvalid
35 }
36 if runtime.GOOS == "windows" && len(path) >= 3 && path[0] == '/' && path[2] == ':' {
37 path = path[1:]
38 }
39 return filepath.FromSlash(path), nil
40 }
41 u, err := url.Parse(source)
42 if err != nil || u.Scheme != "" {
43 return "", os.ErrInvalid
44 }
45 path := u.Path
46 if strings.ContainsRune(path, 0) {
47 return "", os.ErrInvalid
48 }
49 return filepath.FromSlash(path), nil
50 }
51
52 // localChatPathSource follows Harness' file-resource boundary: an ordinary
53 // path reaches the owning host unchanged, while an explicit file URL is decoded
54 // exactly once. In particular, %, ? and # are legal raw filename characters on
55 // POSIX and must not select a different file.
56 func localChatPathSource(source string) (string, error) {
57 source = strings.TrimSpace(source)
58 if source == "" || strings.ContainsRune(source, 0) {
59 return "", os.ErrInvalid
60 }
61 if strings.HasPrefix(strings.ToLower(source), "file:") {
62 return localPathSource(source)
63 }
64 return filepath.Clean(source), nil
65 }
66
67 // canonicalPathWithin resolves symlinks on both sides before comparing, so a
68 // link inside the root cannot be used to reach a file outside it.
69 //
70 // It returns the resolved root as well as the resolved candidate: a caller that
71 // derives a display path must measure from the same resolved root, or a
72 // symlinked workspace (macOS /tmp, a linked home) would produce a ../ chain
73 // instead of the relative path the file tree uses.
74 func canonicalPathWithin(root, candidate string) (resolved, resolvedRoot string, err error) {
75 realRoot, err := filepath.EvalSymlinks(root)
76 if err != nil {
77 return "", "", err
78 }
79 realCandidate, err := filepath.EvalSymlinks(candidate)
80 if err != nil {
81 return "", "", err
82 }
83 rel, err := filepath.Rel(realRoot, realCandidate)
84 if err != nil || rel == ".." || strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
85 return "", "", os.ErrPermission
86 }
87 return filepath.Clean(realCandidate), filepath.Clean(realRoot), nil
88 }
89
90 func localFileHref(path string) string {
91 slash := filepath.ToSlash(path)
92 if runtime.GOOS == "windows" && len(slash) >= 2 && slash[1] == ':' {
93 slash = "/" + slash
94 }
95 return (&url.URL{Scheme: "file", Path: slash}).String()
96 }
97
97 lines GO