@@ -57,6 +57,28 @@ type File interface {
5757 io.Writer
5858}
5959
60+ type namedFileInfo struct {
61+ os.FileInfo
62+ name string
63+ }
64+
65+ func (fi namedFileInfo ) Name () string {
66+ return fi .name
67+ }
68+
69+ type osDirFile struct {
70+ * os.File
71+ statName string
72+ }
73+
74+ func (f * osDirFile ) Stat () (os.FileInfo , error ) {
75+ info , err := f .File .Stat ()
76+ if err != nil {
77+ return nil , err
78+ }
79+ return namedFileInfo {FileInfo : info , name : f .statName }, nil
80+ }
81+
6082type webdavFile struct {
6183 File
6284 name string
@@ -130,54 +152,195 @@ func (d Dir) resolve(name string) string {
130152 return filepath .Join (dir , filepath .FromSlash (slashClean (name )))
131153}
132154
155+ func (d Dir ) rootPath () (string , error ) {
156+ dir := string (d )
157+ if dir == "" {
158+ dir = "."
159+ }
160+ root , err := filepath .Abs (dir )
161+ if err != nil {
162+ return "" , err
163+ }
164+ return filepath .EvalSymlinks (root )
165+ }
166+
167+ func splitVirtualPath (name string ) []string {
168+ switch name {
169+ case "" , "." , "/" :
170+ return nil
171+ }
172+ name = strings .TrimPrefix (name , "/" )
173+ if name == "" {
174+ return nil
175+ }
176+ return strings .Split (name , "/" )
177+ }
178+
179+ func joinVirtualPath (base string , segments []string ) string {
180+ for _ , segment := range segments {
181+ base = filepath .Join (base , filepath .FromSlash (segment ))
182+ }
183+ return base
184+ }
185+
186+ func isWithinRoot (root , target string ) bool {
187+ rel , err := filepath .Rel (root , target )
188+ if err != nil {
189+ return false
190+ }
191+ return rel == "." || (rel != ".." && ! strings .HasPrefix (rel , ".." + string (filepath .Separator )))
192+ }
193+
194+ func virtualName (name string ) string {
195+ return path .Base (slashClean (name ))
196+ }
197+
198+ func (d Dir ) resolveBoundedPath (name string , followLeaf , allowMissingLeaf , allowMissingTail bool ) (string , error ) {
199+ if d .resolve (name ) == "" {
200+ return "" , os .ErrNotExist
201+ }
202+ root , err := d .rootPath ()
203+ if err != nil {
204+ return "" , err
205+ }
206+
207+ current := root
208+ segments := splitVirtualPath (slashClean (name ))
209+ symlinks := 0
210+
211+ for len (segments ) > 0 {
212+ segment := segments [0 ]
213+ segments = segments [1 :]
214+
215+ next := filepath .Join (current , filepath .FromSlash (segment ))
216+ final := len (segments ) == 0
217+
218+ info , err := os .Lstat (next )
219+ if err != nil {
220+ if os .IsNotExist (err ) && (allowMissingTail || (allowMissingLeaf && final )) {
221+ return joinVirtualPath (current , append ([]string {segment }, segments ... )), nil
222+ }
223+ return "" , err
224+ }
225+
226+ if info .Mode ()& os .ModeSymlink == 0 {
227+ current = next
228+ continue
229+ }
230+
231+ if ! followLeaf && final {
232+ return next , nil
233+ }
234+
235+ symlinks ++
236+ if symlinks > 255 {
237+ return "" , os .ErrInvalid
238+ }
239+
240+ target , err := os .Readlink (next )
241+ if err != nil {
242+ return "" , err
243+ }
244+ if ! filepath .IsAbs (target ) {
245+ target = filepath .Join (current , target )
246+ }
247+ target = filepath .Clean (target )
248+
249+ if ! isWithinRoot (root , target ) {
250+ return "" , os .ErrPermission
251+ }
252+
253+ relTarget , err := filepath .Rel (root , target )
254+ if err != nil {
255+ return "" , os .ErrPermission
256+ }
257+ targetSegments := splitVirtualPath (filepath .ToSlash (relTarget ))
258+
259+ current = root
260+ segments = append (targetSegments , segments ... )
261+ }
262+
263+ return current , nil
264+ }
265+
133266func (d Dir ) Mkdir (ctx context.Context , name string , perm os.FileMode ) error {
134- if name = d .resolve (name ); name == "" {
135- return os .ErrNotExist
267+ name , err := d .resolveBoundedPath (name , false , true , false )
268+ if err != nil {
269+ return err
136270 }
137271 return os .Mkdir (name , perm )
138272}
139273
140274func (d Dir ) OpenFile (ctx context.Context , name string , flag int , perm os.FileMode ) (File , error ) {
141- if name = d .resolve (name ); name == "" {
142- return nil , os .ErrNotExist
275+ resolvedName , err := d .resolveBoundedPath (name , true , flag & os .O_CREATE != 0 , false )
276+ if err != nil {
277+ return nil , err
143278 }
144- f , err := os .OpenFile (name , flag , perm )
279+ f , err := os .OpenFile (resolvedName , flag , perm )
145280 if err != nil {
146281 return nil , err
147282 }
148- return & webdavFile {f , name }, nil
283+ return & webdavFile {
284+ File : & osDirFile {
285+ File : f ,
286+ statName : virtualName (name ),
287+ },
288+ name : resolvedName ,
289+ }, nil
149290}
150291
151292func (d Dir ) RemoveAll (ctx context.Context , name string ) error {
152- if name = d .resolve (name ); name == "" {
293+ resolvedName := d .resolve (name )
294+ if resolvedName == "" {
153295 return os .ErrNotExist
154296 }
155- if name == filepath .Clean (string (d )) {
297+ if resolvedName == filepath .Clean (string (d )) {
156298 // Prohibit removing the virtual root directory.
157299 return os .ErrInvalid
158300 }
159- return os .RemoveAll (name )
301+ var err error
302+ resolvedName , err = d .resolveBoundedPath (name , false , false , true )
303+ if err != nil {
304+ return err
305+ }
306+ return os .RemoveAll (resolvedName )
160307}
161308
162309func (d Dir ) Rename (ctx context.Context , oldName , newName string ) error {
163- if oldName = d .resolve (oldName ); oldName == "" {
310+ resolvedOldName := d .resolve (oldName )
311+ if resolvedOldName == "" {
164312 return os .ErrNotExist
165313 }
166- if newName = d .resolve (newName ); newName == "" {
314+ resolvedNewName := d .resolve (newName )
315+ if resolvedNewName == "" {
167316 return os .ErrNotExist
168317 }
169- if root := filepath .Clean (string (d )); root == oldName || root == newName {
318+ if root := filepath .Clean (string (d )); root == resolvedOldName || root == resolvedNewName {
170319 // Prohibit renaming from or to the virtual root directory.
171320 return os .ErrInvalid
172321 }
173- return os .Rename (oldName , newName )
322+ var err error
323+ resolvedOldName , err = d .resolveBoundedPath (oldName , false , false , false )
324+ if err != nil {
325+ return err
326+ }
327+ resolvedNewName , err = d .resolveBoundedPath (newName , false , true , false )
328+ if err != nil {
329+ return err
330+ }
331+ return os .Rename (resolvedOldName , resolvedNewName )
174332}
175333
176334func (d Dir ) Stat (ctx context.Context , name string ) (os.FileInfo , error ) {
177- if name = d .resolve (name ); name == "" {
178- return nil , os .ErrNotExist
335+ resolvedName , err := d .resolveBoundedPath (name , true , false , false )
336+ if err != nil {
337+ return nil , err
338+ }
339+ info , err := os .Stat (resolvedName )
340+ if err != nil {
341+ return nil , err
179342 }
180- return os . Stat (name )
343+ return namedFileInfo { FileInfo : info , name : virtualName (name )}, nil
181344}
182345
183346// NewMemFS returns a new in-memory FileSystem implementation.
0 commit comments