-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathindex.ts
124 lines (116 loc) · 4.4 KB
/
index.ts
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
import { normalize, join } from "node:path"
export interface SanitizeOptions {
decode?: ({
regex: RegExp
replacement: string
})[]
/**
* RegEx that filters out stuff like \..\ or /../
*/
parentDirectoryRegEx?: RegExp
/**
* Characters to be removed from paths because the file system doesn't support these.
*/
notAllowedRegEx?: RegExp
}
interface SanitizeOptionsComplete {
decode: ({
regex: RegExp
replacement: string
})[]
/**
* RegEx that filters out stuff like \..\ or /../
*/
parentDirectoryRegEx: RegExp
/**
* Characters to be removed from paths because the file system doesn't support these.
*/
notAllowedRegEx: RegExp
}
export const DEFAULT_OPTIONS: SanitizeOptionsComplete = {
decode: [
{
regex: /%2e/g,
replacement: '.'
},
{
regex: /%2f/g,
replacement: '/'
},
{
regex: /%5c/g,
replacement: '\\'
}
],
parentDirectoryRegEx: /[\/\\]\.\.[\/\\]/g,
notAllowedRegEx: /:|\$|!|'|"|@|\+|`|\||=/g
}
/**
* Sanitizes a portion of a path to avoid Path Traversal
*/
export default function sanitize(pathstr: string, options: SanitizeOptions = DEFAULT_OPTIONS): string {
if (!options)
options = DEFAULT_OPTIONS
if (typeof options !== 'object')
throw new Error('options must be an object')
if (!Array.isArray(options.decode))
options.decode = DEFAULT_OPTIONS.decode
if (!options.parentDirectoryRegEx)
options.parentDirectoryRegEx = DEFAULT_OPTIONS.parentDirectoryRegEx
if (!options.notAllowedRegEx)
options.notAllowedRegEx = DEFAULT_OPTIONS.notAllowedRegEx
if (typeof pathstr !== 'string') {
// Stringify the path
pathstr = `${pathstr}`
}
let sanitizedPath = pathstr
// ################################################################################################################
// Decode
options.decode.forEach(decode => {
sanitizedPath = sanitizedPath.replace(decode.regex, decode.replacement)
})
// Remove not allowed characters
sanitizedPath = sanitizedPath.replace(options.notAllowedRegEx, '')
// Replace backslashes with normal slashes
sanitizedPath = sanitizedPath.replace(/[\\]/g, '/')
// Replace /../ with /
sanitizedPath = sanitizedPath.replace(options.parentDirectoryRegEx, '/')
// Remove ../ at pos 0 and /.. at end
sanitizedPath = sanitizedPath.replace(/^\.\.[\/\\]/g, '/')
sanitizedPath = sanitizedPath.replace(/[\/\\]\.\.$/g, '/')
// Replace double (back)slashes with a single slash
sanitizedPath = sanitizedPath.replace(/[\/\\]+/g, '/')
// Normalize path
sanitizedPath = normalize(sanitizedPath)
// Remove / or \ in the end
while (sanitizedPath.endsWith('/') || sanitizedPath.endsWith('\\')) {
sanitizedPath = sanitizedPath.slice(0, -1)
}
// Remove / or \ in the beginning
while (sanitizedPath.startsWith('/') || sanitizedPath.startsWith('\\')) {
sanitizedPath = sanitizedPath.slice(1)
}
// Validate path
sanitizedPath = join('', sanitizedPath)
// Remove not allowed characters
sanitizedPath = sanitizedPath.replace(options.notAllowedRegEx, '')
// Again change all \ to /
sanitizedPath = sanitizedPath.replace(/[\\]/g, '/')
// Replace double (back)slashes with a single slash
sanitizedPath = sanitizedPath.replace(/[\/\\]+/g, '/')
// Replace /../ with /
sanitizedPath = sanitizedPath.replace(options.parentDirectoryRegEx, '/')
// Remove ./ or / at start
while (sanitizedPath.startsWith('/') || sanitizedPath.startsWith('./') || sanitizedPath.endsWith('/..') || sanitizedPath.endsWith('/../') || sanitizedPath.startsWith('../') || sanitizedPath.startsWith('/../')) {
sanitizedPath = sanitizedPath.replace(/^\.\//g, '') // ^./
sanitizedPath = sanitizedPath.replace(/^\//g, '') // ^/
// Remove ../ | /../ at pos 0 and /.. | /../ at end
sanitizedPath = sanitizedPath.replace(/^[\/\\]\.\.[\/\\]/g, '/')
sanitizedPath = sanitizedPath.replace(/^\.\.[\/\\]/g, '/')
sanitizedPath = sanitizedPath.replace(/[\/\\]\.\.$/g, '/')
sanitizedPath = sanitizedPath.replace(/[\/\\]\.\.\/$/g, '/')
}
// Make sure out is not "."
sanitizedPath = sanitizedPath.trim() === '.' ? '' : sanitizedPath
return sanitizedPath.trim()
}