Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
alist-org
GitHub Repository: alist-org/alist
Path: blob/main/internal/archive/tool/securepath.go
2310 views
1
package tool
2
3
import (
4
"errors"
5
"fmt"
6
"os"
7
"path"
8
"path/filepath"
9
"strings"
10
)
11
12
// ErrArchiveIllegalPath indicates an archive entry path is unsafe for extraction.
13
var ErrArchiveIllegalPath = errors.New("archive entry has illegal path")
14
15
// SecureJoin returns a safe extraction path for an archive entry.
16
// It rejects absolute paths, traversal, Windows drive/UNC paths, and NUL bytes.
17
func SecureJoin(baseDir, entryName string) (string, error) {
18
if strings.Contains(entryName, "\x00") {
19
return "", fmt.Errorf("%w: %s", ErrArchiveIllegalPath, entryName)
20
}
21
22
normalized := strings.ReplaceAll(entryName, "\\", "/")
23
if strings.HasPrefix(normalized, "//") {
24
return "", fmt.Errorf("%w: %s", ErrArchiveIllegalPath, entryName)
25
}
26
cleaned := path.Clean(normalized)
27
28
if cleaned == "." || cleaned == ".." || strings.HasPrefix(cleaned, "../") {
29
return "", fmt.Errorf("%w: %s", ErrArchiveIllegalPath, entryName)
30
}
31
if strings.HasPrefix(cleaned, "/") {
32
return "", fmt.Errorf("%w: %s", ErrArchiveIllegalPath, entryName)
33
}
34
35
rel := filepath.FromSlash(cleaned)
36
if filepath.IsAbs(rel) || filepath.VolumeName(rel) != "" {
37
return "", fmt.Errorf("%w: %s", ErrArchiveIllegalPath, entryName)
38
}
39
if strings.HasPrefix(rel, `\\`) {
40
return "", fmt.Errorf("%w: %s", ErrArchiveIllegalPath, entryName)
41
}
42
43
base := filepath.Clean(baseDir)
44
dst := filepath.Join(base, rel)
45
46
baseAbs, err := filepath.Abs(base)
47
if err != nil {
48
return "", fmt.Errorf("%w: %s (%v)", ErrArchiveIllegalPath, entryName, err)
49
}
50
dstAbs, err := filepath.Abs(dst)
51
if err != nil {
52
return "", fmt.Errorf("%w: %s (%v)", ErrArchiveIllegalPath, entryName, err)
53
}
54
55
relCheck, err := filepath.Rel(baseAbs, dstAbs)
56
if err != nil {
57
return "", fmt.Errorf("%w: %s (%v)", ErrArchiveIllegalPath, entryName, err)
58
}
59
if relCheck == ".." || strings.HasPrefix(relCheck, ".."+string(os.PathSeparator)) {
60
return "", fmt.Errorf("%w: %s", ErrArchiveIllegalPath, entryName)
61
}
62
return dst, nil
63
}
64
65