traversessymlink.go 1.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
  1. // Copyright (C) 2016 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at https://mozilla.org/MPL/2.0/.
  6. package osutil
  7. import (
  8. "fmt"
  9. "path/filepath"
  10. "strings"
  11. "github.com/syncthing/syncthing/lib/fs"
  12. )
  13. // TraversesSymlinkError is an error indicating symlink traversal
  14. type TraversesSymlinkError struct {
  15. path string
  16. }
  17. func (e TraversesSymlinkError) Error() string {
  18. return fmt.Sprintf("traverses symlink: %s", e.path)
  19. }
  20. // NotADirectoryError is an error indicating an expected path is not a directory
  21. type NotADirectoryError struct {
  22. path string
  23. }
  24. func (e NotADirectoryError) Error() string {
  25. return fmt.Sprintf("not a directory: %s", e.path)
  26. }
  27. // TraversesSymlink returns an error if base and any path component of name up to and
  28. // including filepath.Join(base, name) traverses a symlink.
  29. // Base and name must both be clean and name must be relative to base.
  30. func TraversesSymlink(filesystem fs.Filesystem, name string) error {
  31. base := "."
  32. path := base
  33. info, err := filesystem.Lstat(path)
  34. if err != nil {
  35. return err
  36. }
  37. if !info.IsDir() {
  38. return &NotADirectoryError{
  39. path: base,
  40. }
  41. }
  42. if name == "." {
  43. // The result of calling TraversesSymlink("some/where", filepath.Dir("foo"))
  44. return nil
  45. }
  46. parts := strings.Split(name, string(fs.PathSeparator))
  47. for _, part := range parts {
  48. path = filepath.Join(path, part)
  49. info, err := filesystem.Lstat(path)
  50. if err != nil {
  51. if fs.IsNotExist(err) {
  52. return nil
  53. }
  54. return err
  55. }
  56. if info.IsSymlink() {
  57. return &TraversesSymlinkError{
  58. path: strings.TrimPrefix(path, base),
  59. }
  60. }
  61. if !info.IsDir() {
  62. return &NotADirectoryError{
  63. path: strings.TrimPrefix(path, base),
  64. }
  65. }
  66. }
  67. return nil
  68. }