
Fixes bug for resolving symlinks which allowed fully resolving an existing symlink to a path, causing some symlinks to get overridden as symlinks to self. Updates logic to split name into parent path, resolve the parent path, then safely join back with the base name. Uses the split code to ensure parent directories are created in all cases. Replaces `rootJoin` with filepath.Join to the root, which already correctly cleans relative symlinks to the root. Signed-off-by: Derek McGowan <derek@mcgstyle.net>
108 lines
2.4 KiB
Go
108 lines
2.4 KiB
Go
package archive
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
var (
|
|
errTooManyLinks = errors.New("too many links")
|
|
)
|
|
|
|
// rootPath joins a path with a root, evaluating and bounding any
|
|
// symlink to the root directory.
|
|
// TODO(dmcgowan): Expose and move to fs package or continuity path driver
|
|
func rootPath(root, path string) (string, error) {
|
|
if path == "" {
|
|
return root, nil
|
|
}
|
|
var linksWalked int // to protect against cycles
|
|
for {
|
|
i := linksWalked
|
|
newpath, err := walkLinks(root, path, &linksWalked)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
path = newpath
|
|
if i == linksWalked {
|
|
newpath = filepath.Join("/", newpath)
|
|
if path == newpath {
|
|
return filepath.Join(root, newpath), nil
|
|
}
|
|
path = newpath
|
|
}
|
|
}
|
|
}
|
|
|
|
func walkLink(root, path string, linksWalked *int) (newpath string, islink bool, err error) {
|
|
if *linksWalked > 255 {
|
|
return "", false, errTooManyLinks
|
|
}
|
|
|
|
path = filepath.Join("/", path)
|
|
if path == "/" {
|
|
return path, false, nil
|
|
}
|
|
realPath := filepath.Join(root, path)
|
|
|
|
fi, err := os.Lstat(realPath)
|
|
if err != nil {
|
|
// If path does not yet exist, treat as non-symlink
|
|
if os.IsNotExist(err) {
|
|
return path, false, nil
|
|
}
|
|
return "", false, err
|
|
}
|
|
if fi.Mode()&os.ModeSymlink == 0 {
|
|
return path, false, nil
|
|
}
|
|
newpath, err = os.Readlink(realPath)
|
|
if err != nil {
|
|
return "", false, err
|
|
}
|
|
if filepath.IsAbs(newpath) && strings.HasPrefix(newpath, root) {
|
|
newpath = newpath[:len(root)]
|
|
if !strings.HasPrefix(newpath, "/") {
|
|
newpath = "/" + newpath
|
|
}
|
|
}
|
|
*linksWalked++
|
|
return newpath, true, nil
|
|
}
|
|
|
|
func walkLinks(root, path string, linksWalked *int) (string, error) {
|
|
switch dir, file := filepath.Split(path); {
|
|
case dir == "":
|
|
newpath, _, err := walkLink(root, file, linksWalked)
|
|
return newpath, err
|
|
case file == "":
|
|
if os.IsPathSeparator(dir[len(dir)-1]) {
|
|
if dir == "/" {
|
|
return dir, nil
|
|
}
|
|
return walkLinks(root, dir[:len(dir)-1], linksWalked)
|
|
}
|
|
newpath, _, err := walkLink(root, dir, linksWalked)
|
|
return newpath, err
|
|
default:
|
|
newdir, err := walkLinks(root, dir, linksWalked)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
newpath, islink, err := walkLink(root, filepath.Join(newdir, file), linksWalked)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if !islink {
|
|
return newpath, nil
|
|
}
|
|
if filepath.IsAbs(newpath) {
|
|
return newpath, nil
|
|
}
|
|
return filepath.Join(newdir, newpath), nil
|
|
}
|
|
}
|