-
Notifications
You must be signed in to change notification settings - Fork 30
/
clean.go
128 lines (116 loc) · 3.55 KB
/
clean.go
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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
package fileutil
import (
"os"
"path/filepath"
errorutil "github.com/projectdiscovery/utils/errors"
)
var (
DefaultFilePermission = os.FileMode(0644)
)
// CleanPath cleans paths to migtigate any possible path traversal attacks.
// and it always returns an absolute path
func CleanPath(inputPath string) (string, error) {
// check if path is abs
if filepath.IsAbs(inputPath) {
// clean it using filepath.Abs
// Abs always calls Clean, so we don't need to call it again
return filepath.Abs(inputPath)
}
// get current working directory
cwd, err := os.Getwd()
if err != nil {
return "", err
}
// join cwd with inputPath
joined := filepath.Join(cwd, inputPath)
// clean it using filepath.Abs
return filepath.Abs(joined)
}
// CleanPathOrDefault cleans paths to migtigate any possible path traversal attacks.
func CleanPathOrDefault(inputPath string, defaultPath string) string {
if inputPath == "" {
return defaultPath
}
if val, err := CleanPath(inputPath); err == nil {
return val
}
return defaultPath
}
// ResolveNClean resolves the path and cleans it
// ex: a nuclei template can be either abs or relative to a specified directory
// this function uses given path as a base to resolve the path instead of cwd
func ResolveNClean(inputPath string, baseDir ...string) (string, error) {
// check if path is abs
if filepath.IsAbs(inputPath) {
// clean it using filepath.Abs
// Abs always calls Clean, so we don't need to call it again
return filepath.Abs(inputPath)
}
for _, dir := range baseDir {
// join cwd with inputPath
joined := filepath.Join(dir, inputPath)
// clean it using filepath.Abs
abs, err := filepath.Abs(joined)
if err == nil && FileOrFolderExists(abs) {
return abs, nil
}
}
return "", errorutil.NewWithErr(os.ErrNotExist).Msgf("failed to resolve path: %s", inputPath)
}
// ResolveNCleanOrDefault resolves the path and cleans it
func ResolveNCleanOrDefault(inputPath string, defaultPath string, baseDir ...string) string {
if inputPath == "" {
return defaultPath
}
if val, err := ResolveNClean(inputPath, baseDir...); err == nil {
return val
}
return defaultPath
}
// SafeOpen opens a file after cleaning the path
// in read mode
func SafeOpen(path string) (*os.File, error) {
abs, err := CleanPath(path)
if err != nil {
return nil, err
}
return os.Open(abs)
}
// SafeOpenAppend opens a file after cleaning the path
// in append mode and creates any missing directories in chain /path/to/file
func SafeOpenAppend(path string) (*os.File, error) {
abs, err := CleanPath(path)
if err != nil {
return nil, err
}
_ = FixMissingDirs(abs)
return os.OpenFile(abs, os.O_APPEND|os.O_CREATE|os.O_WRONLY, DefaultFilePermission)
}
// SafeOpenWrite opens a file after cleaning the path
// in write mode and creates any missing directories in chain /path/to/file
func SafeOpenWrite(path string) (*os.File, error) {
abs, err := CleanPath(path)
if err != nil {
return nil, err
}
_ = FixMissingDirs(abs)
return os.OpenFile(abs, os.O_CREATE|os.O_WRONLY, DefaultFilePermission)
}
// SafeWriteFile writes data to a file after cleaning the path
// in write mode and creates any missing directories in chain /path/to/file
func SafeWriteFile(path string, data []byte) error {
abs, err := CleanPath(path)
if err != nil {
return err
}
_ = FixMissingDirs(abs)
return os.WriteFile(abs, data, DefaultFilePermission)
}
// FixMissingDirs creates any missing directories in chain /path/to/file
func FixMissingDirs(path string) error {
abs, err := CleanPath(path)
if err != nil {
return err
}
return os.MkdirAll(filepath.Dir(abs), os.ModePerm)
}