diff options
Diffstat (limited to 'server/helpers/custom-validators')
-rw-r--r-- | server/helpers/custom-validators/misc.ts | 9 | ||||
-rw-r--r-- | server/helpers/custom-validators/plugins.ts | 82 |
2 files changed, 91 insertions, 0 deletions
diff --git a/server/helpers/custom-validators/misc.ts b/server/helpers/custom-validators/misc.ts index 3a3deab0c..f72513c1c 100644 --- a/server/helpers/custom-validators/misc.ts +++ b/server/helpers/custom-validators/misc.ts | |||
@@ -1,10 +1,18 @@ | |||
1 | import 'multer' | 1 | import 'multer' |
2 | import * as validator from 'validator' | 2 | import * as validator from 'validator' |
3 | import { sep } from 'path' | ||
3 | 4 | ||
4 | function exists (value: any) { | 5 | function exists (value: any) { |
5 | return value !== undefined && value !== null | 6 | return value !== undefined && value !== null |
6 | } | 7 | } |
7 | 8 | ||
9 | function isSafePath (p: string) { | ||
10 | return exists(p) && | ||
11 | (p + '').split(sep).every(part => { | ||
12 | return [ '', '.', '..' ].includes(part) === false | ||
13 | }) | ||
14 | } | ||
15 | |||
8 | function isArray (value: any) { | 16 | function isArray (value: any) { |
9 | return Array.isArray(value) | 17 | return Array.isArray(value) |
10 | } | 18 | } |
@@ -97,6 +105,7 @@ export { | |||
97 | isNotEmptyIntArray, | 105 | isNotEmptyIntArray, |
98 | isArray, | 106 | isArray, |
99 | isIdValid, | 107 | isIdValid, |
108 | isSafePath, | ||
100 | isUUIDValid, | 109 | isUUIDValid, |
101 | isIdOrUUIDValid, | 110 | isIdOrUUIDValid, |
102 | isDateValid, | 111 | isDateValid, |
diff --git a/server/helpers/custom-validators/plugins.ts b/server/helpers/custom-validators/plugins.ts new file mode 100644 index 000000000..ff687dc3f --- /dev/null +++ b/server/helpers/custom-validators/plugins.ts | |||
@@ -0,0 +1,82 @@ | |||
1 | import { exists, isArray, isSafePath } from './misc' | ||
2 | import * as validator from 'validator' | ||
3 | import { PluginType } from '../../../shared/models/plugins/plugin.type' | ||
4 | import { CONSTRAINTS_FIELDS } from '../../initializers/constants' | ||
5 | import { PluginPackageJson } from '../../../shared/models/plugins/plugin-package-json.model' | ||
6 | import { isUrlValid } from './activitypub/misc' | ||
7 | |||
8 | const PLUGINS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.PLUGINS | ||
9 | |||
10 | function isPluginTypeValid (value: any) { | ||
11 | return exists(value) && validator.isInt('' + value) && PluginType[value] !== undefined | ||
12 | } | ||
13 | |||
14 | function isPluginNameValid (value: string) { | ||
15 | return exists(value) && | ||
16 | validator.isLength(value, PLUGINS_CONSTRAINTS_FIELDS.NAME) && | ||
17 | validator.matches(value, /^[a-z\-]+$/) | ||
18 | } | ||
19 | |||
20 | function isPluginDescriptionValid (value: string) { | ||
21 | return exists(value) && validator.isLength(value, PLUGINS_CONSTRAINTS_FIELDS.DESCRIPTION) | ||
22 | } | ||
23 | |||
24 | function isPluginVersionValid (value: string) { | ||
25 | if (!exists(value)) return false | ||
26 | |||
27 | const parts = (value + '').split('.') | ||
28 | |||
29 | return parts.length === 3 && parts.every(p => validator.isInt(p)) | ||
30 | } | ||
31 | |||
32 | function isPluginEngineValid (engine: any) { | ||
33 | return exists(engine) && exists(engine.peertube) | ||
34 | } | ||
35 | |||
36 | function isStaticDirectoriesValid (staticDirs: any) { | ||
37 | if (!exists(staticDirs) || typeof staticDirs !== 'object') return false | ||
38 | |||
39 | for (const key of Object.keys(staticDirs)) { | ||
40 | if (!isSafePath(staticDirs[key])) return false | ||
41 | } | ||
42 | |||
43 | return true | ||
44 | } | ||
45 | |||
46 | function isClientScriptsValid (clientScripts: any[]) { | ||
47 | return isArray(clientScripts) && | ||
48 | clientScripts.every(c => { | ||
49 | return isSafePath(c.script) && isArray(c.scopes) | ||
50 | }) | ||
51 | } | ||
52 | |||
53 | function isCSSPathsValid (css: any[]) { | ||
54 | return isArray(css) && css.every(c => isSafePath(c)) | ||
55 | } | ||
56 | |||
57 | function isPackageJSONValid (packageJSON: PluginPackageJson, pluginType: PluginType) { | ||
58 | return isPluginNameValid(packageJSON.name) && | ||
59 | isPluginDescriptionValid(packageJSON.description) && | ||
60 | isPluginEngineValid(packageJSON.engine) && | ||
61 | isUrlValid(packageJSON.homepage) && | ||
62 | exists(packageJSON.author) && | ||
63 | isUrlValid(packageJSON.bugs) && | ||
64 | (pluginType === PluginType.THEME || isSafePath(packageJSON.library)) && | ||
65 | isStaticDirectoriesValid(packageJSON.staticDirs) && | ||
66 | isCSSPathsValid(packageJSON.css) && | ||
67 | isClientScriptsValid(packageJSON.clientScripts) | ||
68 | } | ||
69 | |||
70 | function isLibraryCodeValid (library: any) { | ||
71 | return typeof library.register === 'function' | ||
72 | && typeof library.unregister === 'function' | ||
73 | } | ||
74 | |||
75 | export { | ||
76 | isPluginTypeValid, | ||
77 | isPackageJSONValid, | ||
78 | isPluginVersionValid, | ||
79 | isPluginNameValid, | ||
80 | isPluginDescriptionValid, | ||
81 | isLibraryCodeValid | ||
82 | } | ||