123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081 |
- package symlink
- import (
- "fmt"
- "os"
- "path"
- "path/filepath"
- "strings"
- )
- const maxLoopCounter = 100
- // FollowSymlink will follow an existing link and scope it to the root
- // path provided.
- func FollowSymlinkInScope(link, root string) (string, error) {
- prev := "/"
- root, err := filepath.Abs(root)
- if err != nil {
- return "", err
- }
- link, err = filepath.Abs(link)
- if err != nil {
- return "", err
- }
- if !strings.HasPrefix(filepath.Dir(link), root) {
- return "", fmt.Errorf("%s is not within %s", link, root)
- }
- for _, p := range strings.Split(link, "/") {
- prev = filepath.Join(prev, p)
- prev = filepath.Clean(prev)
- loopCounter := 0
- for {
- loopCounter++
- if loopCounter >= maxLoopCounter {
- return "", fmt.Errorf("loopCounter reached MAX: %v", loopCounter)
- }
- if !strings.HasPrefix(prev, root) {
- // Don't resolve symlinks outside of root. For example,
- // we don't have to check /home in the below.
- //
- // /home -> usr/home
- // FollowSymlinkInScope("/home/bob/foo/bar", "/home/bob/foo")
- break
- }
- stat, err := os.Lstat(prev)
- if err != nil {
- if os.IsNotExist(err) {
- break
- }
- return "", err
- }
- if stat.Mode()&os.ModeSymlink == os.ModeSymlink {
- dest, err := os.Readlink(prev)
- if err != nil {
- return "", err
- }
- if path.IsAbs(dest) {
- prev = filepath.Join(root, dest)
- } else {
- prev, _ = filepath.Abs(prev)
- if prev = filepath.Clean(filepath.Join(filepath.Dir(prev), dest)); len(prev) < len(root) {
- prev = filepath.Join(root, filepath.Base(dest))
- }
- }
- } else {
- break
- }
- }
- }
- return prev, nil
- }
|