File: path.go

package info (click to toggle)
singularity-container 4.1.5%2Bds4-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 43,876 kB
  • sloc: asm: 14,840; sh: 3,190; ansic: 1,751; awk: 414; makefile: 413; python: 99
file content (111 lines) | stat: -rw-r--r-- 3,343 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
// This code mostly comes from <https://github.com/cyphar/filepath-securejoin>.

// Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved.
// Copyright (C) 2017-2024 SUSE LLC. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package contenthash

import (
	"os"
	"path/filepath"
	"strings"

	"github.com/pkg/errors"
)

var errTooManyLinks = errors.New("too many links")

const maxSymlinkLimit = 255

type onSymlinkFunc func(string, string) error

// rootPath joins a path with a root, evaluating and bounding any symlink to
// the root directory. This is a slightly modified version of SecureJoin from
// github.com/cyphar/filepath-securejoin, with a callback which we call after
// each symlink resolution.
func rootPath(root, unsafePath string, followTrailing bool, cb onSymlinkFunc) (string, error) {
	if unsafePath == "" {
		return root, nil
	}

	unsafePath = filepath.FromSlash(unsafePath)
	var (
		currentPath string
		linksWalked int
	)
	for unsafePath != "" {
		// Windows-specific: remove any drive letters from the path.
		if v := filepath.VolumeName(unsafePath); v != "" {
			unsafePath = unsafePath[len(v):]
		}

		// Remove any unnecessary trailing slashes.
		unsafePath = strings.TrimSuffix(unsafePath, string(filepath.Separator))

		// Get the next path component.
		var part string
		if i := strings.IndexRune(unsafePath, filepath.Separator); i == -1 {
			part, unsafePath = unsafePath, ""
		} else {
			part, unsafePath = unsafePath[:i], unsafePath[i+1:]
		}

		// Apply the component lexically to the path we are building. path does
		// not contain any symlinks, and we are lexically dealing with a single
		// component, so it's okay to do filepath.Clean here.
		nextPath := filepath.Join(string(filepath.Separator), currentPath, part)
		if nextPath == string(filepath.Separator) {
			// If we end up back at the root, we don't need to re-evaluate /.
			currentPath = ""
			continue
		}
		fullPath := root + string(filepath.Separator) + nextPath

		// Figure out whether the path is a symlink.
		fi, err := os.Lstat(fullPath)
		if err != nil && !errors.Is(err, os.ErrNotExist) {
			return "", err
		}
		// Treat non-existent path components the same as non-symlinks (we
		// can't do any better here).
		if errors.Is(err, os.ErrNotExist) || fi.Mode()&os.ModeSymlink == 0 {
			currentPath = nextPath
			continue
		}
		// Don't resolve the final component with !followTrailing.
		if !followTrailing && unsafePath == "" {
			currentPath = nextPath
			break
		}

		// It's a symlink, so get its contents and expand it by prepending it
		// to the yet-unparsed path.
		linksWalked++
		if linksWalked > maxSymlinkLimit {
			return "", errTooManyLinks
		}

		dest, err := os.Readlink(fullPath)
		if err != nil {
			return "", err
		}
		if cb != nil {
			if err := cb(nextPath, dest); err != nil {
				return "", err
			}
		}

		unsafePath = dest + string(filepath.Separator) + unsafePath
		// Absolute symlinks reset any work we've already done.
		if filepath.IsAbs(dest) {
			currentPath = ""
		}
	}

	// There should be no lexical components left in path here, but just for
	// safety do a filepath.Clean before the join.
	finalPath := filepath.Join(string(filepath.Separator), currentPath)
	return filepath.Join(root, finalPath), nil
}