66 "io/fs"
77 "os"
88 "path/filepath"
9- "slices"
10- "strings"
119)
1210
1311type moduleEntry struct {
@@ -25,163 +23,6 @@ type moduleRequest struct {
2523
2624const moduleKeySeparator = "::"
2725
28- func parseModuleRequest (name string ) (moduleRequest , error ) {
29- trimmed := strings .TrimSpace (name )
30- if trimmed == "" {
31- return moduleRequest {}, fmt .Errorf ("require: module name must be non-empty" )
32- }
33- normalizedName := strings .ReplaceAll (trimmed , "\\ " , string (filepath .Separator ))
34- normalizedName = strings .ReplaceAll (normalizedName , "/" , string (filepath .Separator ))
35-
36- request := moduleRequest {
37- raw : name ,
38- explicitRelative : isExplicitRelativeModulePath (trimmed ),
39- }
40- if filepath .Ext (normalizedName ) == "" {
41- normalizedName += ".vibe"
42- }
43-
44- request .normalized = filepath .Clean (normalizedName )
45- if request .normalized == "." {
46- return moduleRequest {}, fmt .Errorf ("require: module name %q resolves to current directory" , name )
47- }
48- if filepath .IsAbs (request .normalized ) {
49- return moduleRequest {}, fmt .Errorf ("require: module name %q must be relative" , name )
50- }
51- if ! request .explicitRelative && containsPathTraversal (request .normalized ) {
52- return moduleRequest {}, fmt .Errorf ("require: module name %q escapes search paths" , name )
53- }
54-
55- return request , nil
56- }
57-
58- func isExplicitRelativeModulePath (name string ) bool {
59- return strings .HasPrefix (name , "./" ) ||
60- strings .HasPrefix (name , "../" ) ||
61- strings .HasPrefix (name , ".\\ " ) ||
62- strings .HasPrefix (name , "..\\ " )
63- }
64-
65- func containsPathTraversal (cleanPath string ) bool {
66- normalized := strings .ReplaceAll (filepath .Clean (cleanPath ), "\\ " , "/" )
67- return slices .Contains (strings .Split (normalized , "/" ), ".." )
68- }
69-
70- func moduleCacheKey (root , relative string ) string {
71- return filepath .Clean (root ) + moduleKeySeparator + filepath .Clean (relative )
72- }
73-
74- func moduleKeyDisplay (key string ) string {
75- idx := strings .LastIndex (key , moduleKeySeparator )
76- if idx < 0 {
77- return key
78- }
79- display := key [idx + len (moduleKeySeparator ):]
80- if display == "" {
81- return key
82- }
83- return display
84- }
85-
86- func moduleDisplayName (key string ) string {
87- display := filepath .ToSlash (moduleKeyDisplay (key ))
88- return strings .TrimSuffix (display , ".vibe" )
89- }
90-
91- func moduleRelativePath (root , fullPath string ) (string , error ) {
92- rel , err := moduleRelativePathLexical (root , fullPath )
93- if err != nil {
94- return "" , err
95- }
96- cleanRoot := filepath .Clean (root )
97- cleanPath := filepath .Clean (fullPath )
98-
99- resolvedRoot , err := resolvedExistingPath (cleanRoot )
100- if err != nil {
101- return "" , err
102- }
103- resolvedPath , err := resolvedPathWithMissing (cleanPath )
104- if err != nil {
105- return "" , err
106- }
107- resolvedRel , err := filepath .Rel (resolvedRoot , resolvedPath )
108- if err != nil {
109- return "" , err
110- }
111- resolvedRel = filepath .Clean (resolvedRel )
112- sep := string (filepath .Separator )
113- if resolvedRel == ".." || strings .HasPrefix (resolvedRel , ".." + sep ) || filepath .IsAbs (resolvedRel ) {
114- return "" , fmt .Errorf ("require: module path %q escapes module root %q" , cleanPath , cleanRoot )
115- }
116- return rel , nil
117- }
118-
119- func moduleRelativePathLexical (root , fullPath string ) (string , error ) {
120- cleanRoot := filepath .Clean (root )
121- cleanPath := filepath .Clean (fullPath )
122-
123- rel , err := filepath .Rel (cleanRoot , cleanPath )
124- if err != nil {
125- return "" , err
126- }
127- rel = filepath .Clean (rel )
128- sep := string (filepath .Separator )
129- if rel == ".." || strings .HasPrefix (rel , ".." + sep ) || filepath .IsAbs (rel ) {
130- return "" , fmt .Errorf ("require: module path %q escapes module root %q" , cleanPath , cleanRoot )
131- }
132- return rel , nil
133- }
134-
135- func resolvedExistingPath (path string ) (string , error ) {
136- absPath , err := filepath .Abs (path )
137- if err != nil {
138- return "" , err
139- }
140- resolvedPath , err := filepath .EvalSymlinks (absPath )
141- if err != nil {
142- return "" , err
143- }
144- return filepath .Clean (resolvedPath ), nil
145- }
146-
147- func resolvedPathWithMissing (path string ) (string , error ) {
148- absPath , err := filepath .Abs (path )
149- if err != nil {
150- return "" , err
151- }
152- cleanPath := filepath .Clean (absPath )
153-
154- existing := cleanPath
155- suffix := make ([]string , 0 , 4 )
156-
157- for {
158- _ , statErr := os .Lstat (existing )
159- if statErr == nil {
160- break
161- }
162- if ! errors .Is (statErr , fs .ErrNotExist ) {
163- return "" , statErr
164- }
165- parent := filepath .Dir (existing )
166- if parent == existing {
167- return "" , statErr
168- }
169- suffix = append (suffix , filepath .Base (existing ))
170- existing = parent
171- }
172-
173- resolvedExisting , err := filepath .EvalSymlinks (existing )
174- if err != nil {
175- return "" , err
176- }
177-
178- resolved := filepath .Clean (resolvedExisting )
179- for i := len (suffix ) - 1 ; i >= 0 ; i -- {
180- resolved = filepath .Join (resolved , suffix [i ])
181- }
182- return resolved , nil
183- }
184-
18526func (e * Engine ) getCachedModule (key string ) (moduleEntry , bool ) {
18627 e .modMu .RLock ()
18728 entry , ok := e .modules [key ]
0 commit comments