diff options
Diffstat (limited to 'server/helpers/custom-validators')
44 files changed, 0 insertions, 2455 deletions
diff --git a/server/helpers/custom-validators/abuses.ts b/server/helpers/custom-validators/abuses.ts deleted file mode 100644 index 94719641a..000000000 --- a/server/helpers/custom-validators/abuses.ts +++ /dev/null | |||
@@ -1,68 +0,0 @@ | |||
1 | import validator from 'validator' | ||
2 | import { abusePredefinedReasonsMap } from '@shared/core-utils/abuse' | ||
3 | import { AbuseCreate, AbuseFilter, AbusePredefinedReasonsString, AbuseVideoIs } from '@shared/models' | ||
4 | import { ABUSE_STATES, CONSTRAINTS_FIELDS } from '../../initializers/constants' | ||
5 | import { exists, isArray } from './misc' | ||
6 | |||
7 | const ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.ABUSES | ||
8 | const ABUSE_MESSAGES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.ABUSE_MESSAGES | ||
9 | |||
10 | function isAbuseReasonValid (value: string) { | ||
11 | return exists(value) && validator.isLength(value, ABUSES_CONSTRAINTS_FIELDS.REASON) | ||
12 | } | ||
13 | |||
14 | function isAbusePredefinedReasonValid (value: AbusePredefinedReasonsString) { | ||
15 | return exists(value) && value in abusePredefinedReasonsMap | ||
16 | } | ||
17 | |||
18 | function isAbuseFilterValid (value: AbuseFilter) { | ||
19 | return value === 'video' || value === 'comment' || value === 'account' | ||
20 | } | ||
21 | |||
22 | function areAbusePredefinedReasonsValid (value: AbusePredefinedReasonsString[]) { | ||
23 | return exists(value) && isArray(value) && value.every(v => v in abusePredefinedReasonsMap) | ||
24 | } | ||
25 | |||
26 | function isAbuseTimestampValid (value: number) { | ||
27 | return value === null || (exists(value) && validator.isInt('' + value, { min: 0 })) | ||
28 | } | ||
29 | |||
30 | function isAbuseTimestampCoherent (endAt: number, { req }) { | ||
31 | const startAt = (req.body as AbuseCreate).video.startAt | ||
32 | |||
33 | return exists(startAt) && endAt > startAt | ||
34 | } | ||
35 | |||
36 | function isAbuseModerationCommentValid (value: string) { | ||
37 | return exists(value) && validator.isLength(value, ABUSES_CONSTRAINTS_FIELDS.MODERATION_COMMENT) | ||
38 | } | ||
39 | |||
40 | function isAbuseStateValid (value: string) { | ||
41 | return exists(value) && ABUSE_STATES[value] !== undefined | ||
42 | } | ||
43 | |||
44 | function isAbuseVideoIsValid (value: AbuseVideoIs) { | ||
45 | return exists(value) && ( | ||
46 | value === 'deleted' || | ||
47 | value === 'blacklisted' | ||
48 | ) | ||
49 | } | ||
50 | |||
51 | function isAbuseMessageValid (value: string) { | ||
52 | return exists(value) && validator.isLength(value, ABUSE_MESSAGES_CONSTRAINTS_FIELDS.MESSAGE) | ||
53 | } | ||
54 | |||
55 | // --------------------------------------------------------------------------- | ||
56 | |||
57 | export { | ||
58 | isAbuseReasonValid, | ||
59 | isAbuseFilterValid, | ||
60 | isAbusePredefinedReasonValid, | ||
61 | isAbuseMessageValid, | ||
62 | areAbusePredefinedReasonsValid, | ||
63 | isAbuseTimestampValid, | ||
64 | isAbuseTimestampCoherent, | ||
65 | isAbuseModerationCommentValid, | ||
66 | isAbuseStateValid, | ||
67 | isAbuseVideoIsValid | ||
68 | } | ||
diff --git a/server/helpers/custom-validators/accounts.ts b/server/helpers/custom-validators/accounts.ts deleted file mode 100644 index f676669ea..000000000 --- a/server/helpers/custom-validators/accounts.ts +++ /dev/null | |||
@@ -1,22 +0,0 @@ | |||
1 | import { isUserDescriptionValid, isUserUsernameValid } from './users' | ||
2 | import { exists } from './misc' | ||
3 | |||
4 | function isAccountNameValid (value: string) { | ||
5 | return isUserUsernameValid(value) | ||
6 | } | ||
7 | |||
8 | function isAccountIdValid (value: string) { | ||
9 | return exists(value) | ||
10 | } | ||
11 | |||
12 | function isAccountDescriptionValid (value: string) { | ||
13 | return isUserDescriptionValid(value) | ||
14 | } | ||
15 | |||
16 | // --------------------------------------------------------------------------- | ||
17 | |||
18 | export { | ||
19 | isAccountIdValid, | ||
20 | isAccountDescriptionValid, | ||
21 | isAccountNameValid | ||
22 | } | ||
diff --git a/server/helpers/custom-validators/activitypub/activity.ts b/server/helpers/custom-validators/activitypub/activity.ts deleted file mode 100644 index 90a918523..000000000 --- a/server/helpers/custom-validators/activitypub/activity.ts +++ /dev/null | |||
@@ -1,151 +0,0 @@ | |||
1 | import validator from 'validator' | ||
2 | import { Activity, ActivityType } from '../../../../shared/models/activitypub' | ||
3 | import { isAbuseReasonValid } from '../abuses' | ||
4 | import { exists } from '../misc' | ||
5 | import { sanitizeAndCheckActorObject } from './actor' | ||
6 | import { isCacheFileObjectValid } from './cache-file' | ||
7 | import { isActivityPubUrlValid, isBaseActivityValid, isObjectValid } from './misc' | ||
8 | import { isPlaylistObjectValid } from './playlist' | ||
9 | import { sanitizeAndCheckVideoCommentObject } from './video-comments' | ||
10 | import { sanitizeAndCheckVideoTorrentObject } from './videos' | ||
11 | import { isWatchActionObjectValid } from './watch-action' | ||
12 | |||
13 | function isRootActivityValid (activity: any) { | ||
14 | return isCollection(activity) || isActivity(activity) | ||
15 | } | ||
16 | |||
17 | function isCollection (activity: any) { | ||
18 | return (activity.type === 'Collection' || activity.type === 'OrderedCollection') && | ||
19 | validator.isInt(activity.totalItems, { min: 0 }) && | ||
20 | Array.isArray(activity.items) | ||
21 | } | ||
22 | |||
23 | function isActivity (activity: any) { | ||
24 | return isActivityPubUrlValid(activity.id) && | ||
25 | exists(activity.actor) && | ||
26 | (isActivityPubUrlValid(activity.actor) || isActivityPubUrlValid(activity.actor.id)) | ||
27 | } | ||
28 | |||
29 | const activityCheckers: { [ P in ActivityType ]: (activity: Activity) => boolean } = { | ||
30 | Create: isCreateActivityValid, | ||
31 | Update: isUpdateActivityValid, | ||
32 | Delete: isDeleteActivityValid, | ||
33 | Follow: isFollowActivityValid, | ||
34 | Accept: isAcceptActivityValid, | ||
35 | Reject: isRejectActivityValid, | ||
36 | Announce: isAnnounceActivityValid, | ||
37 | Undo: isUndoActivityValid, | ||
38 | Like: isLikeActivityValid, | ||
39 | View: isViewActivityValid, | ||
40 | Flag: isFlagActivityValid, | ||
41 | Dislike: isDislikeActivityValid | ||
42 | } | ||
43 | |||
44 | function isActivityValid (activity: any) { | ||
45 | const checker = activityCheckers[activity.type] | ||
46 | // Unknown activity type | ||
47 | if (!checker) return false | ||
48 | |||
49 | return checker(activity) | ||
50 | } | ||
51 | |||
52 | function isFlagActivityValid (activity: any) { | ||
53 | return isBaseActivityValid(activity, 'Flag') && | ||
54 | isAbuseReasonValid(activity.content) && | ||
55 | isActivityPubUrlValid(activity.object) | ||
56 | } | ||
57 | |||
58 | function isLikeActivityValid (activity: any) { | ||
59 | return isBaseActivityValid(activity, 'Like') && | ||
60 | isObjectValid(activity.object) | ||
61 | } | ||
62 | |||
63 | function isDislikeActivityValid (activity: any) { | ||
64 | return isBaseActivityValid(activity, 'Dislike') && | ||
65 | isObjectValid(activity.object) | ||
66 | } | ||
67 | |||
68 | function isAnnounceActivityValid (activity: any) { | ||
69 | return isBaseActivityValid(activity, 'Announce') && | ||
70 | isObjectValid(activity.object) | ||
71 | } | ||
72 | |||
73 | function isViewActivityValid (activity: any) { | ||
74 | return isBaseActivityValid(activity, 'View') && | ||
75 | isActivityPubUrlValid(activity.actor) && | ||
76 | isActivityPubUrlValid(activity.object) | ||
77 | } | ||
78 | |||
79 | function isCreateActivityValid (activity: any) { | ||
80 | return isBaseActivityValid(activity, 'Create') && | ||
81 | ( | ||
82 | isViewActivityValid(activity.object) || | ||
83 | isDislikeActivityValid(activity.object) || | ||
84 | isFlagActivityValid(activity.object) || | ||
85 | isPlaylistObjectValid(activity.object) || | ||
86 | isWatchActionObjectValid(activity.object) || | ||
87 | |||
88 | isCacheFileObjectValid(activity.object) || | ||
89 | sanitizeAndCheckVideoCommentObject(activity.object) || | ||
90 | sanitizeAndCheckVideoTorrentObject(activity.object) | ||
91 | ) | ||
92 | } | ||
93 | |||
94 | function isUpdateActivityValid (activity: any) { | ||
95 | return isBaseActivityValid(activity, 'Update') && | ||
96 | ( | ||
97 | isCacheFileObjectValid(activity.object) || | ||
98 | isPlaylistObjectValid(activity.object) || | ||
99 | sanitizeAndCheckVideoTorrentObject(activity.object) || | ||
100 | sanitizeAndCheckActorObject(activity.object) | ||
101 | ) | ||
102 | } | ||
103 | |||
104 | function isDeleteActivityValid (activity: any) { | ||
105 | // We don't really check objects | ||
106 | return isBaseActivityValid(activity, 'Delete') && | ||
107 | isObjectValid(activity.object) | ||
108 | } | ||
109 | |||
110 | function isFollowActivityValid (activity: any) { | ||
111 | return isBaseActivityValid(activity, 'Follow') && | ||
112 | isObjectValid(activity.object) | ||
113 | } | ||
114 | |||
115 | function isAcceptActivityValid (activity: any) { | ||
116 | return isBaseActivityValid(activity, 'Accept') | ||
117 | } | ||
118 | |||
119 | function isRejectActivityValid (activity: any) { | ||
120 | return isBaseActivityValid(activity, 'Reject') | ||
121 | } | ||
122 | |||
123 | function isUndoActivityValid (activity: any) { | ||
124 | return isBaseActivityValid(activity, 'Undo') && | ||
125 | ( | ||
126 | isFollowActivityValid(activity.object) || | ||
127 | isLikeActivityValid(activity.object) || | ||
128 | isDislikeActivityValid(activity.object) || | ||
129 | isAnnounceActivityValid(activity.object) || | ||
130 | isCreateActivityValid(activity.object) | ||
131 | ) | ||
132 | } | ||
133 | |||
134 | // --------------------------------------------------------------------------- | ||
135 | |||
136 | export { | ||
137 | isRootActivityValid, | ||
138 | isActivityValid, | ||
139 | isFlagActivityValid, | ||
140 | isLikeActivityValid, | ||
141 | isDislikeActivityValid, | ||
142 | isAnnounceActivityValid, | ||
143 | isViewActivityValid, | ||
144 | isCreateActivityValid, | ||
145 | isUpdateActivityValid, | ||
146 | isDeleteActivityValid, | ||
147 | isFollowActivityValid, | ||
148 | isAcceptActivityValid, | ||
149 | isRejectActivityValid, | ||
150 | isUndoActivityValid | ||
151 | } | ||
diff --git a/server/helpers/custom-validators/activitypub/actor.ts b/server/helpers/custom-validators/activitypub/actor.ts deleted file mode 100644 index f43c35b23..000000000 --- a/server/helpers/custom-validators/activitypub/actor.ts +++ /dev/null | |||
@@ -1,142 +0,0 @@ | |||
1 | import validator from 'validator' | ||
2 | import { CONSTRAINTS_FIELDS } from '../../../initializers/constants' | ||
3 | import { exists, isArray, isDateValid } from '../misc' | ||
4 | import { isActivityPubUrlValid, isBaseActivityValid, setValidAttributedTo } from './misc' | ||
5 | import { isHostValid } from '../servers' | ||
6 | import { peertubeTruncate } from '@server/helpers/core-utils' | ||
7 | |||
8 | function isActorEndpointsObjectValid (endpointObject: any) { | ||
9 | if (endpointObject?.sharedInbox) { | ||
10 | return isActivityPubUrlValid(endpointObject.sharedInbox) | ||
11 | } | ||
12 | |||
13 | // Shared inbox is optional | ||
14 | return true | ||
15 | } | ||
16 | |||
17 | function isActorPublicKeyObjectValid (publicKeyObject: any) { | ||
18 | return isActivityPubUrlValid(publicKeyObject.id) && | ||
19 | isActivityPubUrlValid(publicKeyObject.owner) && | ||
20 | isActorPublicKeyValid(publicKeyObject.publicKeyPem) | ||
21 | } | ||
22 | |||
23 | function isActorTypeValid (type: string) { | ||
24 | return type === 'Person' || type === 'Application' || type === 'Group' || type === 'Service' || type === 'Organization' | ||
25 | } | ||
26 | |||
27 | function isActorPublicKeyValid (publicKey: string) { | ||
28 | return exists(publicKey) && | ||
29 | typeof publicKey === 'string' && | ||
30 | publicKey.startsWith('-----BEGIN PUBLIC KEY-----') && | ||
31 | publicKey.includes('-----END PUBLIC KEY-----') && | ||
32 | validator.isLength(publicKey, CONSTRAINTS_FIELDS.ACTORS.PUBLIC_KEY) | ||
33 | } | ||
34 | |||
35 | const actorNameAlphabet = '[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\\-_.:]' | ||
36 | const actorNameRegExp = new RegExp(`^${actorNameAlphabet}+$`) | ||
37 | function isActorPreferredUsernameValid (preferredUsername: string) { | ||
38 | return exists(preferredUsername) && validator.matches(preferredUsername, actorNameRegExp) | ||
39 | } | ||
40 | |||
41 | function isActorPrivateKeyValid (privateKey: string) { | ||
42 | return exists(privateKey) && | ||
43 | typeof privateKey === 'string' && | ||
44 | (privateKey.startsWith('-----BEGIN RSA PRIVATE KEY-----') || privateKey.startsWith('-----BEGIN PRIVATE KEY-----')) && | ||
45 | // Sometimes there is a \n at the end, so just assert the string contains the end mark | ||
46 | (privateKey.includes('-----END RSA PRIVATE KEY-----') || privateKey.includes('-----END PRIVATE KEY-----')) && | ||
47 | validator.isLength(privateKey, CONSTRAINTS_FIELDS.ACTORS.PRIVATE_KEY) | ||
48 | } | ||
49 | |||
50 | function isActorFollowingCountValid (value: string) { | ||
51 | return exists(value) && validator.isInt('' + value, { min: 0 }) | ||
52 | } | ||
53 | |||
54 | function isActorFollowersCountValid (value: string) { | ||
55 | return exists(value) && validator.isInt('' + value, { min: 0 }) | ||
56 | } | ||
57 | |||
58 | function isActorDeleteActivityValid (activity: any) { | ||
59 | return isBaseActivityValid(activity, 'Delete') | ||
60 | } | ||
61 | |||
62 | function sanitizeAndCheckActorObject (actor: any) { | ||
63 | if (!isActorTypeValid(actor.type)) return false | ||
64 | |||
65 | normalizeActor(actor) | ||
66 | |||
67 | return exists(actor) && | ||
68 | isActivityPubUrlValid(actor.id) && | ||
69 | isActivityPubUrlValid(actor.inbox) && | ||
70 | isActorPreferredUsernameValid(actor.preferredUsername) && | ||
71 | isActivityPubUrlValid(actor.url) && | ||
72 | isActorPublicKeyObjectValid(actor.publicKey) && | ||
73 | isActorEndpointsObjectValid(actor.endpoints) && | ||
74 | |||
75 | (!actor.outbox || isActivityPubUrlValid(actor.outbox)) && | ||
76 | (!actor.following || isActivityPubUrlValid(actor.following)) && | ||
77 | (!actor.followers || isActivityPubUrlValid(actor.followers)) && | ||
78 | |||
79 | setValidAttributedTo(actor) && | ||
80 | setValidDescription(actor) && | ||
81 | // If this is a group (a channel), it should be attributed to an account | ||
82 | // In PeerTube we use this to attach a video channel to a specific account | ||
83 | (actor.type !== 'Group' || actor.attributedTo.length !== 0) | ||
84 | } | ||
85 | |||
86 | function normalizeActor (actor: any) { | ||
87 | if (!actor) return | ||
88 | |||
89 | if (!actor.url) { | ||
90 | actor.url = actor.id | ||
91 | } else if (typeof actor.url !== 'string') { | ||
92 | actor.url = actor.url.href || actor.url.url | ||
93 | } | ||
94 | |||
95 | if (!isDateValid(actor.published)) actor.published = undefined | ||
96 | |||
97 | if (actor.summary && typeof actor.summary === 'string') { | ||
98 | actor.summary = peertubeTruncate(actor.summary, { length: CONSTRAINTS_FIELDS.USERS.DESCRIPTION.max }) | ||
99 | |||
100 | if (actor.summary.length < CONSTRAINTS_FIELDS.USERS.DESCRIPTION.min) { | ||
101 | actor.summary = null | ||
102 | } | ||
103 | } | ||
104 | } | ||
105 | |||
106 | function isValidActorHandle (handle: string) { | ||
107 | if (!exists(handle)) return false | ||
108 | |||
109 | const parts = handle.split('@') | ||
110 | if (parts.length !== 2) return false | ||
111 | |||
112 | return isHostValid(parts[1]) | ||
113 | } | ||
114 | |||
115 | function areValidActorHandles (handles: string[]) { | ||
116 | return isArray(handles) && handles.every(h => isValidActorHandle(h)) | ||
117 | } | ||
118 | |||
119 | function setValidDescription (obj: any) { | ||
120 | if (!obj.summary) obj.summary = null | ||
121 | |||
122 | return true | ||
123 | } | ||
124 | |||
125 | // --------------------------------------------------------------------------- | ||
126 | |||
127 | export { | ||
128 | normalizeActor, | ||
129 | actorNameAlphabet, | ||
130 | areValidActorHandles, | ||
131 | isActorEndpointsObjectValid, | ||
132 | isActorPublicKeyObjectValid, | ||
133 | isActorTypeValid, | ||
134 | isActorPublicKeyValid, | ||
135 | isActorPreferredUsernameValid, | ||
136 | isActorPrivateKeyValid, | ||
137 | isActorFollowingCountValid, | ||
138 | isActorFollowersCountValid, | ||
139 | isActorDeleteActivityValid, | ||
140 | sanitizeAndCheckActorObject, | ||
141 | isValidActorHandle | ||
142 | } | ||
diff --git a/server/helpers/custom-validators/activitypub/cache-file.ts b/server/helpers/custom-validators/activitypub/cache-file.ts deleted file mode 100644 index c5b3b4d9f..000000000 --- a/server/helpers/custom-validators/activitypub/cache-file.ts +++ /dev/null | |||
@@ -1,26 +0,0 @@ | |||
1 | import { isActivityPubUrlValid } from './misc' | ||
2 | import { isRemoteVideoUrlValid } from './videos' | ||
3 | import { exists, isDateValid } from '../misc' | ||
4 | import { CacheFileObject } from '../../../../shared/models/activitypub/objects' | ||
5 | |||
6 | function isCacheFileObjectValid (object: CacheFileObject) { | ||
7 | return exists(object) && | ||
8 | object.type === 'CacheFile' && | ||
9 | (object.expires === null || isDateValid(object.expires)) && | ||
10 | isActivityPubUrlValid(object.object) && | ||
11 | (isRemoteVideoUrlValid(object.url) || isPlaylistRedundancyUrlValid(object.url)) | ||
12 | } | ||
13 | |||
14 | // --------------------------------------------------------------------------- | ||
15 | |||
16 | export { | ||
17 | isCacheFileObjectValid | ||
18 | } | ||
19 | |||
20 | // --------------------------------------------------------------------------- | ||
21 | |||
22 | function isPlaylistRedundancyUrlValid (url: any) { | ||
23 | return url.type === 'Link' && | ||
24 | (url.mediaType || url.mimeType) === 'application/x-mpegURL' && | ||
25 | isActivityPubUrlValid(url.href) | ||
26 | } | ||
diff --git a/server/helpers/custom-validators/activitypub/misc.ts b/server/helpers/custom-validators/activitypub/misc.ts deleted file mode 100644 index 7df47cf15..000000000 --- a/server/helpers/custom-validators/activitypub/misc.ts +++ /dev/null | |||
@@ -1,76 +0,0 @@ | |||
1 | import validator from 'validator' | ||
2 | import { CONFIG } from '@server/initializers/config' | ||
3 | import { CONSTRAINTS_FIELDS } from '../../../initializers/constants' | ||
4 | import { exists } from '../misc' | ||
5 | |||
6 | function isUrlValid (url: string) { | ||
7 | const isURLOptions = { | ||
8 | require_host: true, | ||
9 | require_tld: true, | ||
10 | require_protocol: true, | ||
11 | require_valid_protocol: true, | ||
12 | protocols: [ 'http', 'https' ] | ||
13 | } | ||
14 | |||
15 | // We validate 'localhost', so we don't have the top level domain | ||
16 | if (CONFIG.WEBSERVER.HOSTNAME === 'localhost' || CONFIG.WEBSERVER.HOSTNAME === '127.0.0.1') { | ||
17 | isURLOptions.require_tld = false | ||
18 | } | ||
19 | |||
20 | return exists(url) && validator.isURL('' + url, isURLOptions) | ||
21 | } | ||
22 | |||
23 | function isActivityPubUrlValid (url: string) { | ||
24 | return isUrlValid(url) && validator.isLength('' + url, CONSTRAINTS_FIELDS.ACTORS.URL) | ||
25 | } | ||
26 | |||
27 | function isBaseActivityValid (activity: any, type: string) { | ||
28 | return activity.type === type && | ||
29 | isActivityPubUrlValid(activity.id) && | ||
30 | isObjectValid(activity.actor) && | ||
31 | isUrlCollectionValid(activity.to) && | ||
32 | isUrlCollectionValid(activity.cc) | ||
33 | } | ||
34 | |||
35 | function isUrlCollectionValid (collection: any) { | ||
36 | return collection === undefined || | ||
37 | (Array.isArray(collection) && collection.every(t => isActivityPubUrlValid(t))) | ||
38 | } | ||
39 | |||
40 | function isObjectValid (object: any) { | ||
41 | return exists(object) && | ||
42 | ( | ||
43 | isActivityPubUrlValid(object) || isActivityPubUrlValid(object.id) | ||
44 | ) | ||
45 | } | ||
46 | |||
47 | function setValidAttributedTo (obj: any) { | ||
48 | if (Array.isArray(obj.attributedTo) === false) { | ||
49 | obj.attributedTo = [] | ||
50 | return true | ||
51 | } | ||
52 | |||
53 | obj.attributedTo = obj.attributedTo.filter(a => { | ||
54 | return isActivityPubUrlValid(a) || | ||
55 | ((a.type === 'Group' || a.type === 'Person') && isActivityPubUrlValid(a.id)) | ||
56 | }) | ||
57 | |||
58 | return true | ||
59 | } | ||
60 | |||
61 | function isActivityPubVideoDurationValid (value: string) { | ||
62 | // https://www.w3.org/TR/activitystreams-vocabulary/#dfn-duration | ||
63 | return exists(value) && | ||
64 | typeof value === 'string' && | ||
65 | value.startsWith('PT') && | ||
66 | value.endsWith('S') | ||
67 | } | ||
68 | |||
69 | export { | ||
70 | isUrlValid, | ||
71 | isActivityPubUrlValid, | ||
72 | isBaseActivityValid, | ||
73 | setValidAttributedTo, | ||
74 | isObjectValid, | ||
75 | isActivityPubVideoDurationValid | ||
76 | } | ||
diff --git a/server/helpers/custom-validators/activitypub/playlist.ts b/server/helpers/custom-validators/activitypub/playlist.ts deleted file mode 100644 index 49bcadcfd..000000000 --- a/server/helpers/custom-validators/activitypub/playlist.ts +++ /dev/null | |||
@@ -1,29 +0,0 @@ | |||
1 | import validator from 'validator' | ||
2 | import { PlaylistElementObject, PlaylistObject } from '@shared/models' | ||
3 | import { exists, isDateValid, isUUIDValid } from '../misc' | ||
4 | import { isVideoPlaylistNameValid } from '../video-playlists' | ||
5 | import { isActivityPubUrlValid } from './misc' | ||
6 | |||
7 | function isPlaylistObjectValid (object: PlaylistObject) { | ||
8 | return exists(object) && | ||
9 | object.type === 'Playlist' && | ||
10 | validator.isInt(object.totalItems + '') && | ||
11 | isVideoPlaylistNameValid(object.name) && | ||
12 | isUUIDValid(object.uuid) && | ||
13 | isDateValid(object.published) && | ||
14 | isDateValid(object.updated) | ||
15 | } | ||
16 | |||
17 | function isPlaylistElementObjectValid (object: PlaylistElementObject) { | ||
18 | return exists(object) && | ||
19 | object.type === 'PlaylistElement' && | ||
20 | validator.isInt(object.position + '') && | ||
21 | isActivityPubUrlValid(object.url) | ||
22 | } | ||
23 | |||
24 | // --------------------------------------------------------------------------- | ||
25 | |||
26 | export { | ||
27 | isPlaylistObjectValid, | ||
28 | isPlaylistElementObjectValid | ||
29 | } | ||
diff --git a/server/helpers/custom-validators/activitypub/signature.ts b/server/helpers/custom-validators/activitypub/signature.ts deleted file mode 100644 index cfb65361e..000000000 --- a/server/helpers/custom-validators/activitypub/signature.ts +++ /dev/null | |||
@@ -1,22 +0,0 @@ | |||
1 | import { exists } from '../misc' | ||
2 | import { isActivityPubUrlValid } from './misc' | ||
3 | |||
4 | function isSignatureTypeValid (signatureType: string) { | ||
5 | return exists(signatureType) && signatureType === 'RsaSignature2017' | ||
6 | } | ||
7 | |||
8 | function isSignatureCreatorValid (signatureCreator: string) { | ||
9 | return exists(signatureCreator) && isActivityPubUrlValid(signatureCreator) | ||
10 | } | ||
11 | |||
12 | function isSignatureValueValid (signatureValue: string) { | ||
13 | return exists(signatureValue) && signatureValue.length > 0 | ||
14 | } | ||
15 | |||
16 | // --------------------------------------------------------------------------- | ||
17 | |||
18 | export { | ||
19 | isSignatureTypeValid, | ||
20 | isSignatureCreatorValid, | ||
21 | isSignatureValueValid | ||
22 | } | ||
diff --git a/server/helpers/custom-validators/activitypub/video-comments.ts b/server/helpers/custom-validators/activitypub/video-comments.ts deleted file mode 100644 index ea852c491..000000000 --- a/server/helpers/custom-validators/activitypub/video-comments.ts +++ /dev/null | |||
@@ -1,59 +0,0 @@ | |||
1 | import validator from 'validator' | ||
2 | import { ACTIVITY_PUB } from '../../../initializers/constants' | ||
3 | import { exists, isArray, isDateValid } from '../misc' | ||
4 | import { isActivityPubUrlValid } from './misc' | ||
5 | |||
6 | function sanitizeAndCheckVideoCommentObject (comment: any) { | ||
7 | if (!comment) return false | ||
8 | |||
9 | if (!isCommentTypeValid(comment)) return false | ||
10 | |||
11 | normalizeComment(comment) | ||
12 | |||
13 | if (comment.type === 'Tombstone') { | ||
14 | return isActivityPubUrlValid(comment.id) && | ||
15 | isDateValid(comment.published) && | ||
16 | isDateValid(comment.deleted) && | ||
17 | isActivityPubUrlValid(comment.url) | ||
18 | } | ||
19 | |||
20 | return isActivityPubUrlValid(comment.id) && | ||
21 | isCommentContentValid(comment.content) && | ||
22 | isActivityPubUrlValid(comment.inReplyTo) && | ||
23 | isDateValid(comment.published) && | ||
24 | isActivityPubUrlValid(comment.url) && | ||
25 | isArray(comment.to) && | ||
26 | ( | ||
27 | comment.to.indexOf(ACTIVITY_PUB.PUBLIC) !== -1 || | ||
28 | comment.cc.indexOf(ACTIVITY_PUB.PUBLIC) !== -1 | ||
29 | ) // Only accept public comments | ||
30 | } | ||
31 | |||
32 | // --------------------------------------------------------------------------- | ||
33 | |||
34 | export { | ||
35 | sanitizeAndCheckVideoCommentObject | ||
36 | } | ||
37 | |||
38 | // --------------------------------------------------------------------------- | ||
39 | |||
40 | function isCommentContentValid (content: any) { | ||
41 | return exists(content) && validator.isLength('' + content, { min: 1 }) | ||
42 | } | ||
43 | |||
44 | function normalizeComment (comment: any) { | ||
45 | if (!comment) return | ||
46 | |||
47 | if (typeof comment.url !== 'string') { | ||
48 | if (typeof comment.url === 'object') comment.url = comment.url.href || comment.url.url | ||
49 | else comment.url = comment.id | ||
50 | } | ||
51 | } | ||
52 | |||
53 | function isCommentTypeValid (comment: any): boolean { | ||
54 | if (comment.type === 'Note') return true | ||
55 | |||
56 | if (comment.type === 'Tombstone' && comment.formerType === 'Note') return true | ||
57 | |||
58 | return false | ||
59 | } | ||
diff --git a/server/helpers/custom-validators/activitypub/videos.ts b/server/helpers/custom-validators/activitypub/videos.ts deleted file mode 100644 index 07e25b8ba..000000000 --- a/server/helpers/custom-validators/activitypub/videos.ts +++ /dev/null | |||
@@ -1,241 +0,0 @@ | |||
1 | import validator from 'validator' | ||
2 | import { logger } from '@server/helpers/logger' | ||
3 | import { ActivityPubStoryboard, ActivityTrackerUrlObject, ActivityVideoFileMetadataUrlObject, VideoObject } from '@shared/models' | ||
4 | import { LiveVideoLatencyMode, VideoState } from '../../../../shared/models/videos' | ||
5 | import { ACTIVITY_PUB, CONSTRAINTS_FIELDS } from '../../../initializers/constants' | ||
6 | import { peertubeTruncate } from '../../core-utils' | ||
7 | import { isArray, isBooleanValid, isDateValid, isUUIDValid } from '../misc' | ||
8 | import { isLiveLatencyModeValid } from '../video-lives' | ||
9 | import { | ||
10 | isVideoDescriptionValid, | ||
11 | isVideoDurationValid, | ||
12 | isVideoNameValid, | ||
13 | isVideoStateValid, | ||
14 | isVideoTagValid, | ||
15 | isVideoViewsValid | ||
16 | } from '../videos' | ||
17 | import { isActivityPubUrlValid, isActivityPubVideoDurationValid, isBaseActivityValid, setValidAttributedTo } from './misc' | ||
18 | |||
19 | function sanitizeAndCheckVideoTorrentUpdateActivity (activity: any) { | ||
20 | return isBaseActivityValid(activity, 'Update') && | ||
21 | sanitizeAndCheckVideoTorrentObject(activity.object) | ||
22 | } | ||
23 | |||
24 | function sanitizeAndCheckVideoTorrentObject (video: any) { | ||
25 | if (!video || video.type !== 'Video') return false | ||
26 | |||
27 | if (!setValidRemoteTags(video)) { | ||
28 | logger.debug('Video has invalid tags', { video }) | ||
29 | return false | ||
30 | } | ||
31 | if (!setValidRemoteVideoUrls(video)) { | ||
32 | logger.debug('Video has invalid urls', { video }) | ||
33 | return false | ||
34 | } | ||
35 | if (!setRemoteVideoContent(video)) { | ||
36 | logger.debug('Video has invalid content', { video }) | ||
37 | return false | ||
38 | } | ||
39 | if (!setValidAttributedTo(video)) { | ||
40 | logger.debug('Video has invalid attributedTo', { video }) | ||
41 | return false | ||
42 | } | ||
43 | if (!setValidRemoteCaptions(video)) { | ||
44 | logger.debug('Video has invalid captions', { video }) | ||
45 | return false | ||
46 | } | ||
47 | if (!setValidRemoteIcon(video)) { | ||
48 | logger.debug('Video has invalid icons', { video }) | ||
49 | return false | ||
50 | } | ||
51 | if (!setValidStoryboard(video)) { | ||
52 | logger.debug('Video has invalid preview (storyboard)', { video }) | ||
53 | return false | ||
54 | } | ||
55 | |||
56 | // Default attributes | ||
57 | if (!isVideoStateValid(video.state)) video.state = VideoState.PUBLISHED | ||
58 | if (!isBooleanValid(video.waitTranscoding)) video.waitTranscoding = false | ||
59 | if (!isBooleanValid(video.downloadEnabled)) video.downloadEnabled = true | ||
60 | if (!isBooleanValid(video.commentsEnabled)) video.commentsEnabled = false | ||
61 | if (!isBooleanValid(video.isLiveBroadcast)) video.isLiveBroadcast = false | ||
62 | if (!isBooleanValid(video.liveSaveReplay)) video.liveSaveReplay = false | ||
63 | if (!isBooleanValid(video.permanentLive)) video.permanentLive = false | ||
64 | if (!isLiveLatencyModeValid(video.latencyMode)) video.latencyMode = LiveVideoLatencyMode.DEFAULT | ||
65 | |||
66 | return isActivityPubUrlValid(video.id) && | ||
67 | isVideoNameValid(video.name) && | ||
68 | isActivityPubVideoDurationValid(video.duration) && | ||
69 | isVideoDurationValid(video.duration.replace(/[^0-9]+/g, '')) && | ||
70 | isUUIDValid(video.uuid) && | ||
71 | (!video.category || isRemoteNumberIdentifierValid(video.category)) && | ||
72 | (!video.licence || isRemoteNumberIdentifierValid(video.licence)) && | ||
73 | (!video.language || isRemoteStringIdentifierValid(video.language)) && | ||
74 | isVideoViewsValid(video.views) && | ||
75 | isBooleanValid(video.sensitive) && | ||
76 | isDateValid(video.published) && | ||
77 | isDateValid(video.updated) && | ||
78 | (!video.originallyPublishedAt || isDateValid(video.originallyPublishedAt)) && | ||
79 | (!video.uploadDate || isDateValid(video.uploadDate)) && | ||
80 | (!video.content || isRemoteVideoContentValid(video.mediaType, video.content)) && | ||
81 | video.attributedTo.length !== 0 | ||
82 | } | ||
83 | |||
84 | function isRemoteVideoUrlValid (url: any) { | ||
85 | return url.type === 'Link' && | ||
86 | // Video file link | ||
87 | ( | ||
88 | ACTIVITY_PUB.URL_MIME_TYPES.VIDEO.includes(url.mediaType) && | ||
89 | isActivityPubUrlValid(url.href) && | ||
90 | validator.isInt(url.height + '', { min: 0 }) && | ||
91 | validator.isInt(url.size + '', { min: 0 }) && | ||
92 | (!url.fps || validator.isInt(url.fps + '', { min: -1 })) | ||
93 | ) || | ||
94 | // Torrent link | ||
95 | ( | ||
96 | ACTIVITY_PUB.URL_MIME_TYPES.TORRENT.includes(url.mediaType) && | ||
97 | isActivityPubUrlValid(url.href) && | ||
98 | validator.isInt(url.height + '', { min: 0 }) | ||
99 | ) || | ||
100 | // Magnet link | ||
101 | ( | ||
102 | ACTIVITY_PUB.URL_MIME_TYPES.MAGNET.includes(url.mediaType) && | ||
103 | validator.isLength(url.href, { min: 5 }) && | ||
104 | validator.isInt(url.height + '', { min: 0 }) | ||
105 | ) || | ||
106 | // HLS playlist link | ||
107 | ( | ||
108 | (url.mediaType || url.mimeType) === 'application/x-mpegURL' && | ||
109 | isActivityPubUrlValid(url.href) && | ||
110 | isArray(url.tag) | ||
111 | ) || | ||
112 | isAPVideoTrackerUrlObject(url) || | ||
113 | isAPVideoFileUrlMetadataObject(url) | ||
114 | } | ||
115 | |||
116 | function isAPVideoFileUrlMetadataObject (url: any): url is ActivityVideoFileMetadataUrlObject { | ||
117 | return url && | ||
118 | url.type === 'Link' && | ||
119 | url.mediaType === 'application/json' && | ||
120 | isArray(url.rel) && url.rel.includes('metadata') | ||
121 | } | ||
122 | |||
123 | function isAPVideoTrackerUrlObject (url: any): url is ActivityTrackerUrlObject { | ||
124 | return isArray(url.rel) && | ||
125 | url.rel.includes('tracker') && | ||
126 | isActivityPubUrlValid(url.href) | ||
127 | } | ||
128 | |||
129 | // --------------------------------------------------------------------------- | ||
130 | |||
131 | export { | ||
132 | sanitizeAndCheckVideoTorrentUpdateActivity, | ||
133 | isRemoteStringIdentifierValid, | ||
134 | sanitizeAndCheckVideoTorrentObject, | ||
135 | isRemoteVideoUrlValid, | ||
136 | isAPVideoFileUrlMetadataObject, | ||
137 | isAPVideoTrackerUrlObject | ||
138 | } | ||
139 | |||
140 | // --------------------------------------------------------------------------- | ||
141 | |||
142 | function setValidRemoteTags (video: any) { | ||
143 | if (Array.isArray(video.tag) === false) return false | ||
144 | |||
145 | video.tag = video.tag.filter(t => { | ||
146 | return t.type === 'Hashtag' && | ||
147 | isVideoTagValid(t.name) | ||
148 | }) | ||
149 | |||
150 | return true | ||
151 | } | ||
152 | |||
153 | function setValidRemoteCaptions (video: any) { | ||
154 | if (!video.subtitleLanguage) video.subtitleLanguage = [] | ||
155 | |||
156 | if (Array.isArray(video.subtitleLanguage) === false) return false | ||
157 | |||
158 | video.subtitleLanguage = video.subtitleLanguage.filter(caption => { | ||
159 | if (!isActivityPubUrlValid(caption.url)) caption.url = null | ||
160 | |||
161 | return isRemoteStringIdentifierValid(caption) | ||
162 | }) | ||
163 | |||
164 | return true | ||
165 | } | ||
166 | |||
167 | function isRemoteNumberIdentifierValid (data: any) { | ||
168 | return validator.isInt(data.identifier, { min: 0 }) | ||
169 | } | ||
170 | |||
171 | function isRemoteStringIdentifierValid (data: any) { | ||
172 | return typeof data.identifier === 'string' | ||
173 | } | ||
174 | |||
175 | function isRemoteVideoContentValid (mediaType: string, content: string) { | ||
176 | return mediaType === 'text/markdown' && isVideoDescriptionValid(content) | ||
177 | } | ||
178 | |||
179 | function setValidRemoteIcon (video: any) { | ||
180 | if (video.icon && !isArray(video.icon)) video.icon = [ video.icon ] | ||
181 | if (!video.icon) video.icon = [] | ||
182 | |||
183 | video.icon = video.icon.filter(icon => { | ||
184 | return icon.type === 'Image' && | ||
185 | isActivityPubUrlValid(icon.url) && | ||
186 | icon.mediaType === 'image/jpeg' && | ||
187 | validator.isInt(icon.width + '', { min: 0 }) && | ||
188 | validator.isInt(icon.height + '', { min: 0 }) | ||
189 | }) | ||
190 | |||
191 | return video.icon.length !== 0 | ||
192 | } | ||
193 | |||
194 | function setValidRemoteVideoUrls (video: any) { | ||
195 | if (Array.isArray(video.url) === false) return false | ||
196 | |||
197 | video.url = video.url.filter(u => isRemoteVideoUrlValid(u)) | ||
198 | |||
199 | return true | ||
200 | } | ||
201 | |||
202 | function setRemoteVideoContent (video: any) { | ||
203 | if (video.content) { | ||
204 | video.content = peertubeTruncate(video.content, { length: CONSTRAINTS_FIELDS.VIDEOS.DESCRIPTION.max }) | ||
205 | } | ||
206 | |||
207 | return true | ||
208 | } | ||
209 | |||
210 | function setValidStoryboard (video: VideoObject) { | ||
211 | if (!video.preview) return true | ||
212 | if (!Array.isArray(video.preview)) return false | ||
213 | |||
214 | video.preview = video.preview.filter(p => isStorybordValid(p)) | ||
215 | |||
216 | return true | ||
217 | } | ||
218 | |||
219 | function isStorybordValid (preview: ActivityPubStoryboard) { | ||
220 | if (!preview) return false | ||
221 | |||
222 | if ( | ||
223 | preview.type !== 'Image' || | ||
224 | !isArray(preview.rel) || | ||
225 | !preview.rel.includes('storyboard') | ||
226 | ) { | ||
227 | return false | ||
228 | } | ||
229 | |||
230 | preview.url = preview.url.filter(u => { | ||
231 | return u.mediaType === 'image/jpeg' && | ||
232 | isActivityPubUrlValid(u.href) && | ||
233 | validator.isInt(u.width + '', { min: 0 }) && | ||
234 | validator.isInt(u.height + '', { min: 0 }) && | ||
235 | validator.isInt(u.tileWidth + '', { min: 0 }) && | ||
236 | validator.isInt(u.tileHeight + '', { min: 0 }) && | ||
237 | isActivityPubVideoDurationValid(u.tileDuration) | ||
238 | }) | ||
239 | |||
240 | return preview.url.length !== 0 | ||
241 | } | ||
diff --git a/server/helpers/custom-validators/activitypub/watch-action.ts b/server/helpers/custom-validators/activitypub/watch-action.ts deleted file mode 100644 index b9ffa63f6..000000000 --- a/server/helpers/custom-validators/activitypub/watch-action.ts +++ /dev/null | |||
@@ -1,37 +0,0 @@ | |||
1 | import { WatchActionObject } from '@shared/models' | ||
2 | import { exists, isDateValid, isUUIDValid } from '../misc' | ||
3 | import { isVideoTimeValid } from '../video-view' | ||
4 | import { isActivityPubVideoDurationValid, isObjectValid } from './misc' | ||
5 | |||
6 | function isWatchActionObjectValid (action: WatchActionObject) { | ||
7 | return exists(action) && | ||
8 | action.type === 'WatchAction' && | ||
9 | isObjectValid(action.id) && | ||
10 | isActivityPubVideoDurationValid(action.duration) && | ||
11 | isDateValid(action.startTime) && | ||
12 | isDateValid(action.endTime) && | ||
13 | isLocationValid(action.location) && | ||
14 | isUUIDValid(action.uuid) && | ||
15 | isObjectValid(action.object) && | ||
16 | isWatchSectionsValid(action.watchSections) | ||
17 | } | ||
18 | |||
19 | // --------------------------------------------------------------------------- | ||
20 | |||
21 | export { | ||
22 | isWatchActionObjectValid | ||
23 | } | ||
24 | |||
25 | // --------------------------------------------------------------------------- | ||
26 | |||
27 | function isLocationValid (location: any) { | ||
28 | if (!location) return true | ||
29 | |||
30 | return typeof location === 'object' && typeof location.addressCountry === 'string' | ||
31 | } | ||
32 | |||
33 | function isWatchSectionsValid (sections: WatchActionObject['watchSections']) { | ||
34 | return Array.isArray(sections) && sections.every(s => { | ||
35 | return isVideoTimeValid(s.startTimestamp) && isVideoTimeValid(s.endTimestamp) | ||
36 | }) | ||
37 | } | ||
diff --git a/server/helpers/custom-validators/actor-images.ts b/server/helpers/custom-validators/actor-images.ts deleted file mode 100644 index 89f5a2262..000000000 --- a/server/helpers/custom-validators/actor-images.ts +++ /dev/null | |||
@@ -1,24 +0,0 @@ | |||
1 | |||
2 | import { UploadFilesForCheck } from 'express' | ||
3 | import { CONSTRAINTS_FIELDS } from '../../initializers/constants' | ||
4 | import { isFileValid } from './misc' | ||
5 | |||
6 | const imageMimeTypes = CONSTRAINTS_FIELDS.ACTORS.IMAGE.EXTNAME | ||
7 | .map(v => v.replace('.', '')) | ||
8 | .join('|') | ||
9 | const imageMimeTypesRegex = `image/(${imageMimeTypes})` | ||
10 | |||
11 | function isActorImageFile (files: UploadFilesForCheck, fieldname: string) { | ||
12 | return isFileValid({ | ||
13 | files, | ||
14 | mimeTypeRegex: imageMimeTypesRegex, | ||
15 | field: fieldname, | ||
16 | maxSize: CONSTRAINTS_FIELDS.ACTORS.IMAGE.FILE_SIZE.max | ||
17 | }) | ||
18 | } | ||
19 | |||
20 | // --------------------------------------------------------------------------- | ||
21 | |||
22 | export { | ||
23 | isActorImageFile | ||
24 | } | ||
diff --git a/server/helpers/custom-validators/bulk.ts b/server/helpers/custom-validators/bulk.ts deleted file mode 100644 index 9e0ce0be1..000000000 --- a/server/helpers/custom-validators/bulk.ts +++ /dev/null | |||
@@ -1,9 +0,0 @@ | |||
1 | function isBulkRemoveCommentsOfScopeValid (value: string) { | ||
2 | return value === 'my-videos' || value === 'instance' | ||
3 | } | ||
4 | |||
5 | // --------------------------------------------------------------------------- | ||
6 | |||
7 | export { | ||
8 | isBulkRemoveCommentsOfScopeValid | ||
9 | } | ||
diff --git a/server/helpers/custom-validators/feeds.ts b/server/helpers/custom-validators/feeds.ts deleted file mode 100644 index fa35a7da6..000000000 --- a/server/helpers/custom-validators/feeds.ts +++ /dev/null | |||
@@ -1,23 +0,0 @@ | |||
1 | import { exists } from './misc' | ||
2 | |||
3 | function isValidRSSFeed (value: string) { | ||
4 | if (!exists(value)) return false | ||
5 | |||
6 | const feedExtensions = [ | ||
7 | 'xml', | ||
8 | 'json', | ||
9 | 'json1', | ||
10 | 'rss', | ||
11 | 'rss2', | ||
12 | 'atom', | ||
13 | 'atom1' | ||
14 | ] | ||
15 | |||
16 | return feedExtensions.includes(value) | ||
17 | } | ||
18 | |||
19 | // --------------------------------------------------------------------------- | ||
20 | |||
21 | export { | ||
22 | isValidRSSFeed | ||
23 | } | ||
diff --git a/server/helpers/custom-validators/follows.ts b/server/helpers/custom-validators/follows.ts deleted file mode 100644 index 0bec683c1..000000000 --- a/server/helpers/custom-validators/follows.ts +++ /dev/null | |||
@@ -1,30 +0,0 @@ | |||
1 | import { exists, isArray } from './misc' | ||
2 | import { FollowState } from '@shared/models' | ||
3 | |||
4 | function isFollowStateValid (value: FollowState) { | ||
5 | if (!exists(value)) return false | ||
6 | |||
7 | return value === 'pending' || value === 'accepted' || value === 'rejected' | ||
8 | } | ||
9 | |||
10 | function isRemoteHandleValid (value: string) { | ||
11 | if (!exists(value)) return false | ||
12 | if (typeof value !== 'string') return false | ||
13 | |||
14 | return value.includes('@') | ||
15 | } | ||
16 | |||
17 | function isEachUniqueHandleValid (handles: string[]) { | ||
18 | return isArray(handles) && | ||
19 | handles.every(handle => { | ||
20 | return isRemoteHandleValid(handle) && handles.indexOf(handle) === handles.lastIndexOf(handle) | ||
21 | }) | ||
22 | } | ||
23 | |||
24 | // --------------------------------------------------------------------------- | ||
25 | |||
26 | export { | ||
27 | isFollowStateValid, | ||
28 | isRemoteHandleValid, | ||
29 | isEachUniqueHandleValid | ||
30 | } | ||
diff --git a/server/helpers/custom-validators/jobs.ts b/server/helpers/custom-validators/jobs.ts deleted file mode 100644 index c168b3e91..000000000 --- a/server/helpers/custom-validators/jobs.ts +++ /dev/null | |||
@@ -1,21 +0,0 @@ | |||
1 | import { JobState } from '../../../shared/models' | ||
2 | import { exists } from './misc' | ||
3 | import { jobTypes } from '@server/lib/job-queue/job-queue' | ||
4 | |||
5 | const jobStates: JobState[] = [ 'active', 'completed', 'failed', 'waiting', 'delayed', 'paused', 'waiting-children' ] | ||
6 | |||
7 | function isValidJobState (value: JobState) { | ||
8 | return exists(value) && jobStates.includes(value) | ||
9 | } | ||
10 | |||
11 | function isValidJobType (value: any) { | ||
12 | return exists(value) && jobTypes.includes(value) | ||
13 | } | ||
14 | |||
15 | // --------------------------------------------------------------------------- | ||
16 | |||
17 | export { | ||
18 | jobStates, | ||
19 | isValidJobState, | ||
20 | isValidJobType | ||
21 | } | ||
diff --git a/server/helpers/custom-validators/logs.ts b/server/helpers/custom-validators/logs.ts deleted file mode 100644 index 215dbb0e1..000000000 --- a/server/helpers/custom-validators/logs.ts +++ /dev/null | |||
@@ -1,42 +0,0 @@ | |||
1 | import validator from 'validator' | ||
2 | import { CONSTRAINTS_FIELDS } from '@server/initializers/constants' | ||
3 | import { ClientLogLevel, ServerLogLevel } from '@shared/models' | ||
4 | import { exists } from './misc' | ||
5 | |||
6 | const serverLogLevels = new Set<ServerLogLevel>([ 'debug', 'info', 'warn', 'error' ]) | ||
7 | const clientLogLevels = new Set<ClientLogLevel>([ 'warn', 'error' ]) | ||
8 | |||
9 | function isValidLogLevel (value: any) { | ||
10 | return exists(value) && serverLogLevels.has(value) | ||
11 | } | ||
12 | |||
13 | function isValidClientLogMessage (value: any) { | ||
14 | return typeof value === 'string' && validator.isLength(value, CONSTRAINTS_FIELDS.LOGS.CLIENT_MESSAGE) | ||
15 | } | ||
16 | |||
17 | function isValidClientLogLevel (value: any) { | ||
18 | return exists(value) && clientLogLevels.has(value) | ||
19 | } | ||
20 | |||
21 | function isValidClientLogStackTrace (value: any) { | ||
22 | return typeof value === 'string' && validator.isLength(value, CONSTRAINTS_FIELDS.LOGS.CLIENT_STACK_TRACE) | ||
23 | } | ||
24 | |||
25 | function isValidClientLogMeta (value: any) { | ||
26 | return typeof value === 'string' && validator.isLength(value, CONSTRAINTS_FIELDS.LOGS.CLIENT_META) | ||
27 | } | ||
28 | |||
29 | function isValidClientLogUserAgent (value: any) { | ||
30 | return typeof value === 'string' && validator.isLength(value, CONSTRAINTS_FIELDS.LOGS.CLIENT_USER_AGENT) | ||
31 | } | ||
32 | |||
33 | // --------------------------------------------------------------------------- | ||
34 | |||
35 | export { | ||
36 | isValidLogLevel, | ||
37 | isValidClientLogMessage, | ||
38 | isValidClientLogStackTrace, | ||
39 | isValidClientLogMeta, | ||
40 | isValidClientLogLevel, | ||
41 | isValidClientLogUserAgent | ||
42 | } | ||
diff --git a/server/helpers/custom-validators/metrics.ts b/server/helpers/custom-validators/metrics.ts deleted file mode 100644 index 44a863630..000000000 --- a/server/helpers/custom-validators/metrics.ts +++ /dev/null | |||
@@ -1,10 +0,0 @@ | |||
1 | function isValidPlayerMode (value: any) { | ||
2 | // TODO: remove webtorrent in v7 | ||
3 | return value === 'webtorrent' || value === 'web-video' || value === 'p2p-media-loader' | ||
4 | } | ||
5 | |||
6 | // --------------------------------------------------------------------------- | ||
7 | |||
8 | export { | ||
9 | isValidPlayerMode | ||
10 | } | ||
diff --git a/server/helpers/custom-validators/misc.ts b/server/helpers/custom-validators/misc.ts deleted file mode 100644 index 937ae0632..000000000 --- a/server/helpers/custom-validators/misc.ts +++ /dev/null | |||
@@ -1,190 +0,0 @@ | |||
1 | import 'multer' | ||
2 | import { UploadFilesForCheck } from 'express' | ||
3 | import { sep } from 'path' | ||
4 | import validator from 'validator' | ||
5 | import { isShortUUID, shortToUUID } from '@shared/extra-utils' | ||
6 | |||
7 | function exists (value: any) { | ||
8 | return value !== undefined && value !== null | ||
9 | } | ||
10 | |||
11 | function isSafePath (p: string) { | ||
12 | return exists(p) && | ||
13 | (p + '').split(sep).every(part => { | ||
14 | return [ '..' ].includes(part) === false | ||
15 | }) | ||
16 | } | ||
17 | |||
18 | function isSafeFilename (filename: string, extension?: string) { | ||
19 | const regex = extension | ||
20 | ? new RegExp(`^[a-z0-9-]+\\.${extension}$`) | ||
21 | : new RegExp(`^[a-z0-9-]+\\.[a-z0-9]{1,8}$`) | ||
22 | |||
23 | return typeof filename === 'string' && !!filename.match(regex) | ||
24 | } | ||
25 | |||
26 | function isSafePeerTubeFilenameWithoutExtension (filename: string) { | ||
27 | return filename.match(/^[a-z0-9-]+$/) | ||
28 | } | ||
29 | |||
30 | function isArray (value: any): value is any[] { | ||
31 | return Array.isArray(value) | ||
32 | } | ||
33 | |||
34 | function isNotEmptyIntArray (value: any) { | ||
35 | return Array.isArray(value) && value.every(v => validator.isInt('' + v)) && value.length !== 0 | ||
36 | } | ||
37 | |||
38 | function isNotEmptyStringArray (value: any) { | ||
39 | return Array.isArray(value) && value.every(v => typeof v === 'string' && v.length !== 0) && value.length !== 0 | ||
40 | } | ||
41 | |||
42 | function isArrayOf (value: any, validator: (value: any) => boolean) { | ||
43 | return isArray(value) && value.every(v => validator(v)) | ||
44 | } | ||
45 | |||
46 | function isDateValid (value: string) { | ||
47 | return exists(value) && validator.isISO8601(value) | ||
48 | } | ||
49 | |||
50 | function isIdValid (value: string) { | ||
51 | return exists(value) && validator.isInt('' + value) | ||
52 | } | ||
53 | |||
54 | function isUUIDValid (value: string) { | ||
55 | return exists(value) && validator.isUUID('' + value, 4) | ||
56 | } | ||
57 | |||
58 | function areUUIDsValid (values: string[]) { | ||
59 | return isArray(values) && values.every(v => isUUIDValid(v)) | ||
60 | } | ||
61 | |||
62 | function isIdOrUUIDValid (value: string) { | ||
63 | return isIdValid(value) || isUUIDValid(value) | ||
64 | } | ||
65 | |||
66 | function isBooleanValid (value: any) { | ||
67 | return typeof value === 'boolean' || (typeof value === 'string' && validator.isBoolean(value)) | ||
68 | } | ||
69 | |||
70 | function isIntOrNull (value: any) { | ||
71 | return value === null || validator.isInt('' + value) | ||
72 | } | ||
73 | |||
74 | // --------------------------------------------------------------------------- | ||
75 | |||
76 | function isFileValid (options: { | ||
77 | files: UploadFilesForCheck | ||
78 | |||
79 | maxSize: number | null | ||
80 | mimeTypeRegex: string | null | ||
81 | |||
82 | field?: string | ||
83 | |||
84 | optional?: boolean // Default false | ||
85 | }) { | ||
86 | const { files, mimeTypeRegex, field, maxSize, optional = false } = options | ||
87 | |||
88 | // Should have files | ||
89 | if (!files) return optional | ||
90 | |||
91 | const fileArray = isArray(files) | ||
92 | ? files | ||
93 | : files[field] | ||
94 | |||
95 | if (!fileArray || !isArray(fileArray) || fileArray.length === 0) { | ||
96 | return optional | ||
97 | } | ||
98 | |||
99 | // The file exists | ||
100 | const file = fileArray[0] | ||
101 | if (!file?.originalname) return false | ||
102 | |||
103 | // Check size | ||
104 | if ((maxSize !== null) && file.size > maxSize) return false | ||
105 | |||
106 | if (mimeTypeRegex === null) return true | ||
107 | |||
108 | return checkMimetypeRegex(file.mimetype, mimeTypeRegex) | ||
109 | } | ||
110 | |||
111 | function checkMimetypeRegex (fileMimeType: string, mimeTypeRegex: string) { | ||
112 | return new RegExp(`^${mimeTypeRegex}$`, 'i').test(fileMimeType) | ||
113 | } | ||
114 | |||
115 | // --------------------------------------------------------------------------- | ||
116 | |||
117 | function toCompleteUUID (value: string) { | ||
118 | if (isShortUUID(value)) { | ||
119 | try { | ||
120 | return shortToUUID(value) | ||
121 | } catch { | ||
122 | return '' | ||
123 | } | ||
124 | } | ||
125 | |||
126 | return value | ||
127 | } | ||
128 | |||
129 | function toCompleteUUIDs (values: string[]) { | ||
130 | return values.map(v => toCompleteUUID(v)) | ||
131 | } | ||
132 | |||
133 | function toIntOrNull (value: string) { | ||
134 | const v = toValueOrNull(value) | ||
135 | |||
136 | if (v === null || v === undefined) return v | ||
137 | if (typeof v === 'number') return v | ||
138 | |||
139 | return validator.toInt('' + v) | ||
140 | } | ||
141 | |||
142 | function toBooleanOrNull (value: any) { | ||
143 | const v = toValueOrNull(value) | ||
144 | |||
145 | if (v === null || v === undefined) return v | ||
146 | if (typeof v === 'boolean') return v | ||
147 | |||
148 | return validator.toBoolean('' + v) | ||
149 | } | ||
150 | |||
151 | function toValueOrNull (value: string) { | ||
152 | if (value === 'null') return null | ||
153 | |||
154 | return value | ||
155 | } | ||
156 | |||
157 | function toIntArray (value: any) { | ||
158 | if (!value) return [] | ||
159 | if (isArray(value) === false) return [ validator.toInt(value) ] | ||
160 | |||
161 | return value.map(v => validator.toInt(v)) | ||
162 | } | ||
163 | |||
164 | // --------------------------------------------------------------------------- | ||
165 | |||
166 | export { | ||
167 | exists, | ||
168 | isArrayOf, | ||
169 | isNotEmptyIntArray, | ||
170 | isArray, | ||
171 | isIntOrNull, | ||
172 | isIdValid, | ||
173 | isSafePath, | ||
174 | isNotEmptyStringArray, | ||
175 | isUUIDValid, | ||
176 | toCompleteUUIDs, | ||
177 | toCompleteUUID, | ||
178 | isIdOrUUIDValid, | ||
179 | isDateValid, | ||
180 | toValueOrNull, | ||
181 | toBooleanOrNull, | ||
182 | isBooleanValid, | ||
183 | toIntOrNull, | ||
184 | areUUIDsValid, | ||
185 | toIntArray, | ||
186 | isFileValid, | ||
187 | isSafePeerTubeFilenameWithoutExtension, | ||
188 | isSafeFilename, | ||
189 | checkMimetypeRegex | ||
190 | } | ||
diff --git a/server/helpers/custom-validators/plugins.ts b/server/helpers/custom-validators/plugins.ts deleted file mode 100644 index a20de0c4a..000000000 --- a/server/helpers/custom-validators/plugins.ts +++ /dev/null | |||
@@ -1,178 +0,0 @@ | |||
1 | import validator from 'validator' | ||
2 | import { PluginPackageJSON } from '../../../shared/models/plugins/plugin-package-json.model' | ||
3 | import { PluginType } from '../../../shared/models/plugins/plugin.type' | ||
4 | import { CONSTRAINTS_FIELDS } from '../../initializers/constants' | ||
5 | import { isUrlValid } from './activitypub/misc' | ||
6 | import { exists, isArray, isSafePath } from './misc' | ||
7 | |||
8 | const PLUGINS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.PLUGINS | ||
9 | |||
10 | function isPluginTypeValid (value: any) { | ||
11 | return exists(value) && | ||
12 | (value === PluginType.PLUGIN || value === PluginType.THEME) | ||
13 | } | ||
14 | |||
15 | function isPluginNameValid (value: string) { | ||
16 | return exists(value) && | ||
17 | validator.isLength(value, PLUGINS_CONSTRAINTS_FIELDS.NAME) && | ||
18 | validator.matches(value, /^[a-z-0-9]+$/) | ||
19 | } | ||
20 | |||
21 | function isNpmPluginNameValid (value: string) { | ||
22 | return exists(value) && | ||
23 | validator.isLength(value, PLUGINS_CONSTRAINTS_FIELDS.NAME) && | ||
24 | validator.matches(value, /^[a-z\-._0-9]+$/) && | ||
25 | (value.startsWith('peertube-plugin-') || value.startsWith('peertube-theme-')) | ||
26 | } | ||
27 | |||
28 | function isPluginDescriptionValid (value: string) { | ||
29 | return exists(value) && validator.isLength(value, PLUGINS_CONSTRAINTS_FIELDS.DESCRIPTION) | ||
30 | } | ||
31 | |||
32 | function isPluginStableVersionValid (value: string) { | ||
33 | if (!exists(value)) return false | ||
34 | |||
35 | const parts = (value + '').split('.') | ||
36 | |||
37 | return parts.length === 3 && parts.every(p => validator.isInt(p)) | ||
38 | } | ||
39 | |||
40 | function isPluginStableOrUnstableVersionValid (value: string) { | ||
41 | if (!exists(value)) return false | ||
42 | |||
43 | // suffix is beta.x or alpha.x | ||
44 | const [ stable, suffix ] = value.split('-') | ||
45 | if (!isPluginStableVersionValid(stable)) return false | ||
46 | |||
47 | const suffixRegex = /^(rc|alpha|beta)\.\d+$/ | ||
48 | if (suffix && !suffixRegex.test(suffix)) return false | ||
49 | |||
50 | return true | ||
51 | } | ||
52 | |||
53 | function isPluginEngineValid (engine: any) { | ||
54 | return exists(engine) && exists(engine.peertube) | ||
55 | } | ||
56 | |||
57 | function isPluginHomepage (value: string) { | ||
58 | return exists(value) && (!value || isUrlValid(value)) | ||
59 | } | ||
60 | |||
61 | function isPluginBugs (value: string) { | ||
62 | return exists(value) && (!value || isUrlValid(value)) | ||
63 | } | ||
64 | |||
65 | function areStaticDirectoriesValid (staticDirs: any) { | ||
66 | if (!exists(staticDirs) || typeof staticDirs !== 'object') return false | ||
67 | |||
68 | for (const key of Object.keys(staticDirs)) { | ||
69 | if (!isSafePath(staticDirs[key])) return false | ||
70 | } | ||
71 | |||
72 | return true | ||
73 | } | ||
74 | |||
75 | function areClientScriptsValid (clientScripts: any[]) { | ||
76 | return isArray(clientScripts) && | ||
77 | clientScripts.every(c => { | ||
78 | return isSafePath(c.script) && isArray(c.scopes) | ||
79 | }) | ||
80 | } | ||
81 | |||
82 | function areTranslationPathsValid (translations: any) { | ||
83 | if (!exists(translations) || typeof translations !== 'object') return false | ||
84 | |||
85 | for (const key of Object.keys(translations)) { | ||
86 | if (!isSafePath(translations[key])) return false | ||
87 | } | ||
88 | |||
89 | return true | ||
90 | } | ||
91 | |||
92 | function areCSSPathsValid (css: any[]) { | ||
93 | return isArray(css) && css.every(c => isSafePath(c)) | ||
94 | } | ||
95 | |||
96 | function isThemeNameValid (name: string) { | ||
97 | return isPluginNameValid(name) | ||
98 | } | ||
99 | |||
100 | function isPackageJSONValid (packageJSON: PluginPackageJSON, pluginType: PluginType) { | ||
101 | let result = true | ||
102 | const badFields: string[] = [] | ||
103 | |||
104 | if (!isNpmPluginNameValid(packageJSON.name)) { | ||
105 | result = false | ||
106 | badFields.push('name') | ||
107 | } | ||
108 | |||
109 | if (!isPluginDescriptionValid(packageJSON.description)) { | ||
110 | result = false | ||
111 | badFields.push('description') | ||
112 | } | ||
113 | |||
114 | if (!isPluginEngineValid(packageJSON.engine)) { | ||
115 | result = false | ||
116 | badFields.push('engine') | ||
117 | } | ||
118 | |||
119 | if (!isPluginHomepage(packageJSON.homepage)) { | ||
120 | result = false | ||
121 | badFields.push('homepage') | ||
122 | } | ||
123 | |||
124 | if (!exists(packageJSON.author)) { | ||
125 | result = false | ||
126 | badFields.push('author') | ||
127 | } | ||
128 | |||
129 | if (!isPluginBugs(packageJSON.bugs)) { | ||
130 | result = false | ||
131 | badFields.push('bugs') | ||
132 | } | ||
133 | |||
134 | if (pluginType === PluginType.PLUGIN && !isSafePath(packageJSON.library)) { | ||
135 | result = false | ||
136 | badFields.push('library') | ||
137 | } | ||
138 | |||
139 | if (!areStaticDirectoriesValid(packageJSON.staticDirs)) { | ||
140 | result = false | ||
141 | badFields.push('staticDirs') | ||
142 | } | ||
143 | |||
144 | if (!areCSSPathsValid(packageJSON.css)) { | ||
145 | result = false | ||
146 | badFields.push('css') | ||
147 | } | ||
148 | |||
149 | if (!areClientScriptsValid(packageJSON.clientScripts)) { | ||
150 | result = false | ||
151 | badFields.push('clientScripts') | ||
152 | } | ||
153 | |||
154 | if (!areTranslationPathsValid(packageJSON.translations)) { | ||
155 | result = false | ||
156 | badFields.push('translations') | ||
157 | } | ||
158 | |||
159 | return { result, badFields } | ||
160 | } | ||
161 | |||
162 | function isLibraryCodeValid (library: any) { | ||
163 | return typeof library.register === 'function' && | ||
164 | typeof library.unregister === 'function' | ||
165 | } | ||
166 | |||
167 | export { | ||
168 | isPluginTypeValid, | ||
169 | isPackageJSONValid, | ||
170 | isThemeNameValid, | ||
171 | isPluginHomepage, | ||
172 | isPluginStableVersionValid, | ||
173 | isPluginStableOrUnstableVersionValid, | ||
174 | isPluginNameValid, | ||
175 | isPluginDescriptionValid, | ||
176 | isLibraryCodeValid, | ||
177 | isNpmPluginNameValid | ||
178 | } | ||
diff --git a/server/helpers/custom-validators/runners/jobs.ts b/server/helpers/custom-validators/runners/jobs.ts deleted file mode 100644 index 6349e79ba..000000000 --- a/server/helpers/custom-validators/runners/jobs.ts +++ /dev/null | |||
@@ -1,197 +0,0 @@ | |||
1 | import { UploadFilesForCheck } from 'express' | ||
2 | import validator from 'validator' | ||
3 | import { CONSTRAINTS_FIELDS, RUNNER_JOB_STATES } from '@server/initializers/constants' | ||
4 | import { | ||
5 | LiveRTMPHLSTranscodingSuccess, | ||
6 | RunnerJobSuccessPayload, | ||
7 | RunnerJobType, | ||
8 | RunnerJobUpdatePayload, | ||
9 | VideoStudioTranscodingSuccess, | ||
10 | VODAudioMergeTranscodingSuccess, | ||
11 | VODHLSTranscodingSuccess, | ||
12 | VODWebVideoTranscodingSuccess | ||
13 | } from '@shared/models' | ||
14 | import { exists, isArray, isFileValid, isSafeFilename } from '../misc' | ||
15 | |||
16 | const RUNNER_JOBS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.RUNNER_JOBS | ||
17 | |||
18 | const runnerJobTypes = new Set([ 'vod-hls-transcoding', 'vod-web-video-transcoding', 'vod-audio-merge-transcoding' ]) | ||
19 | function isRunnerJobTypeValid (value: RunnerJobType) { | ||
20 | return runnerJobTypes.has(value) | ||
21 | } | ||
22 | |||
23 | function isRunnerJobSuccessPayloadValid (value: RunnerJobSuccessPayload, type: RunnerJobType, files: UploadFilesForCheck) { | ||
24 | return isRunnerJobVODWebVideoResultPayloadValid(value as VODWebVideoTranscodingSuccess, type, files) || | ||
25 | isRunnerJobVODHLSResultPayloadValid(value as VODHLSTranscodingSuccess, type, files) || | ||
26 | isRunnerJobVODAudioMergeResultPayloadValid(value as VODHLSTranscodingSuccess, type, files) || | ||
27 | isRunnerJobLiveRTMPHLSResultPayloadValid(value as LiveRTMPHLSTranscodingSuccess, type) || | ||
28 | isRunnerJobVideoStudioResultPayloadValid(value as VideoStudioTranscodingSuccess, type, files) | ||
29 | } | ||
30 | |||
31 | // --------------------------------------------------------------------------- | ||
32 | |||
33 | function isRunnerJobProgressValid (value: string) { | ||
34 | return validator.isInt(value + '', RUNNER_JOBS_CONSTRAINTS_FIELDS.PROGRESS) | ||
35 | } | ||
36 | |||
37 | function isRunnerJobUpdatePayloadValid (value: RunnerJobUpdatePayload, type: RunnerJobType, files: UploadFilesForCheck) { | ||
38 | return isRunnerJobVODWebVideoUpdatePayloadValid(value, type, files) || | ||
39 | isRunnerJobVODHLSUpdatePayloadValid(value, type, files) || | ||
40 | isRunnerJobVideoStudioUpdatePayloadValid(value, type, files) || | ||
41 | isRunnerJobVODAudioMergeUpdatePayloadValid(value, type, files) || | ||
42 | isRunnerJobLiveRTMPHLSUpdatePayloadValid(value, type, files) | ||
43 | } | ||
44 | |||
45 | // --------------------------------------------------------------------------- | ||
46 | |||
47 | function isRunnerJobTokenValid (value: string) { | ||
48 | return exists(value) && validator.isLength(value, RUNNER_JOBS_CONSTRAINTS_FIELDS.TOKEN) | ||
49 | } | ||
50 | |||
51 | function isRunnerJobAbortReasonValid (value: string) { | ||
52 | return validator.isLength(value, RUNNER_JOBS_CONSTRAINTS_FIELDS.REASON) | ||
53 | } | ||
54 | |||
55 | function isRunnerJobErrorMessageValid (value: string) { | ||
56 | return validator.isLength(value, RUNNER_JOBS_CONSTRAINTS_FIELDS.ERROR_MESSAGE) | ||
57 | } | ||
58 | |||
59 | function isRunnerJobStateValid (value: any) { | ||
60 | return exists(value) && RUNNER_JOB_STATES[value] !== undefined | ||
61 | } | ||
62 | |||
63 | function isRunnerJobArrayOfStateValid (value: any) { | ||
64 | return isArray(value) && value.every(v => isRunnerJobStateValid(v)) | ||
65 | } | ||
66 | |||
67 | // --------------------------------------------------------------------------- | ||
68 | |||
69 | export { | ||
70 | isRunnerJobTypeValid, | ||
71 | isRunnerJobSuccessPayloadValid, | ||
72 | isRunnerJobUpdatePayloadValid, | ||
73 | isRunnerJobTokenValid, | ||
74 | isRunnerJobErrorMessageValid, | ||
75 | isRunnerJobProgressValid, | ||
76 | isRunnerJobAbortReasonValid, | ||
77 | isRunnerJobArrayOfStateValid, | ||
78 | isRunnerJobStateValid | ||
79 | } | ||
80 | |||
81 | // --------------------------------------------------------------------------- | ||
82 | |||
83 | function isRunnerJobVODWebVideoResultPayloadValid ( | ||
84 | _value: VODWebVideoTranscodingSuccess, | ||
85 | type: RunnerJobType, | ||
86 | files: UploadFilesForCheck | ||
87 | ) { | ||
88 | return type === 'vod-web-video-transcoding' && | ||
89 | isFileValid({ files, field: 'payload[videoFile]', mimeTypeRegex: null, maxSize: null }) | ||
90 | } | ||
91 | |||
92 | function isRunnerJobVODHLSResultPayloadValid ( | ||
93 | _value: VODHLSTranscodingSuccess, | ||
94 | type: RunnerJobType, | ||
95 | files: UploadFilesForCheck | ||
96 | ) { | ||
97 | return type === 'vod-hls-transcoding' && | ||
98 | isFileValid({ files, field: 'payload[videoFile]', mimeTypeRegex: null, maxSize: null }) && | ||
99 | isFileValid({ files, field: 'payload[resolutionPlaylistFile]', mimeTypeRegex: null, maxSize: null }) | ||
100 | } | ||
101 | |||
102 | function isRunnerJobVODAudioMergeResultPayloadValid ( | ||
103 | _value: VODAudioMergeTranscodingSuccess, | ||
104 | type: RunnerJobType, | ||
105 | files: UploadFilesForCheck | ||
106 | ) { | ||
107 | return type === 'vod-audio-merge-transcoding' && | ||
108 | isFileValid({ files, field: 'payload[videoFile]', mimeTypeRegex: null, maxSize: null }) | ||
109 | } | ||
110 | |||
111 | function isRunnerJobLiveRTMPHLSResultPayloadValid ( | ||
112 | value: LiveRTMPHLSTranscodingSuccess, | ||
113 | type: RunnerJobType | ||
114 | ) { | ||
115 | return type === 'live-rtmp-hls-transcoding' && (!value || (typeof value === 'object' && Object.keys(value).length === 0)) | ||
116 | } | ||
117 | |||
118 | function isRunnerJobVideoStudioResultPayloadValid ( | ||
119 | _value: VideoStudioTranscodingSuccess, | ||
120 | type: RunnerJobType, | ||
121 | files: UploadFilesForCheck | ||
122 | ) { | ||
123 | return type === 'video-studio-transcoding' && | ||
124 | isFileValid({ files, field: 'payload[videoFile]', mimeTypeRegex: null, maxSize: null }) | ||
125 | } | ||
126 | |||
127 | // --------------------------------------------------------------------------- | ||
128 | |||
129 | function isRunnerJobVODWebVideoUpdatePayloadValid ( | ||
130 | value: RunnerJobUpdatePayload, | ||
131 | type: RunnerJobType, | ||
132 | _files: UploadFilesForCheck | ||
133 | ) { | ||
134 | return type === 'vod-web-video-transcoding' && | ||
135 | (!value || (typeof value === 'object' && Object.keys(value).length === 0)) | ||
136 | } | ||
137 | |||
138 | function isRunnerJobVODHLSUpdatePayloadValid ( | ||
139 | value: RunnerJobUpdatePayload, | ||
140 | type: RunnerJobType, | ||
141 | _files: UploadFilesForCheck | ||
142 | ) { | ||
143 | return type === 'vod-hls-transcoding' && | ||
144 | (!value || (typeof value === 'object' && Object.keys(value).length === 0)) | ||
145 | } | ||
146 | |||
147 | function isRunnerJobVODAudioMergeUpdatePayloadValid ( | ||
148 | value: RunnerJobUpdatePayload, | ||
149 | type: RunnerJobType, | ||
150 | _files: UploadFilesForCheck | ||
151 | ) { | ||
152 | return type === 'vod-audio-merge-transcoding' && | ||
153 | (!value || (typeof value === 'object' && Object.keys(value).length === 0)) | ||
154 | } | ||
155 | |||
156 | function isRunnerJobLiveRTMPHLSUpdatePayloadValid ( | ||
157 | value: RunnerJobUpdatePayload, | ||
158 | type: RunnerJobType, | ||
159 | files: UploadFilesForCheck | ||
160 | ) { | ||
161 | let result = type === 'live-rtmp-hls-transcoding' && !!value && !!files | ||
162 | |||
163 | result &&= isFileValid({ files, field: 'payload[masterPlaylistFile]', mimeTypeRegex: null, maxSize: null, optional: true }) | ||
164 | |||
165 | result &&= isFileValid({ | ||
166 | files, | ||
167 | field: 'payload[resolutionPlaylistFile]', | ||
168 | mimeTypeRegex: null, | ||
169 | maxSize: null, | ||
170 | optional: !value.resolutionPlaylistFilename | ||
171 | }) | ||
172 | |||
173 | if (files['payload[resolutionPlaylistFile]']) { | ||
174 | result &&= isSafeFilename(value.resolutionPlaylistFilename, 'm3u8') | ||
175 | } | ||
176 | |||
177 | return result && | ||
178 | isSafeFilename(value.videoChunkFilename, 'ts') && | ||
179 | ( | ||
180 | ( | ||
181 | value.type === 'remove-chunk' | ||
182 | ) || | ||
183 | ( | ||
184 | value.type === 'add-chunk' && | ||
185 | isFileValid({ files, field: 'payload[videoChunkFile]', mimeTypeRegex: null, maxSize: null }) | ||
186 | ) | ||
187 | ) | ||
188 | } | ||
189 | |||
190 | function isRunnerJobVideoStudioUpdatePayloadValid ( | ||
191 | value: RunnerJobUpdatePayload, | ||
192 | type: RunnerJobType, | ||
193 | _files: UploadFilesForCheck | ||
194 | ) { | ||
195 | return type === 'video-studio-transcoding' && | ||
196 | (!value || (typeof value === 'object' && Object.keys(value).length === 0)) | ||
197 | } | ||
diff --git a/server/helpers/custom-validators/runners/runners.ts b/server/helpers/custom-validators/runners/runners.ts deleted file mode 100644 index 953fac3b5..000000000 --- a/server/helpers/custom-validators/runners/runners.ts +++ /dev/null | |||
@@ -1,30 +0,0 @@ | |||
1 | import validator from 'validator' | ||
2 | import { CONSTRAINTS_FIELDS } from '@server/initializers/constants' | ||
3 | import { exists } from '../misc' | ||
4 | |||
5 | const RUNNERS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.RUNNERS | ||
6 | |||
7 | function isRunnerRegistrationTokenValid (value: string) { | ||
8 | return exists(value) && validator.isLength(value, RUNNERS_CONSTRAINTS_FIELDS.TOKEN) | ||
9 | } | ||
10 | |||
11 | function isRunnerTokenValid (value: string) { | ||
12 | return exists(value) && validator.isLength(value, RUNNERS_CONSTRAINTS_FIELDS.TOKEN) | ||
13 | } | ||
14 | |||
15 | function isRunnerNameValid (value: string) { | ||
16 | return exists(value) && validator.isLength(value, RUNNERS_CONSTRAINTS_FIELDS.NAME) | ||
17 | } | ||
18 | |||
19 | function isRunnerDescriptionValid (value: string) { | ||
20 | return exists(value) && validator.isLength(value, RUNNERS_CONSTRAINTS_FIELDS.DESCRIPTION) | ||
21 | } | ||
22 | |||
23 | // --------------------------------------------------------------------------- | ||
24 | |||
25 | export { | ||
26 | isRunnerRegistrationTokenValid, | ||
27 | isRunnerTokenValid, | ||
28 | isRunnerNameValid, | ||
29 | isRunnerDescriptionValid | ||
30 | } | ||
diff --git a/server/helpers/custom-validators/search.ts b/server/helpers/custom-validators/search.ts deleted file mode 100644 index 6dba5d14e..000000000 --- a/server/helpers/custom-validators/search.ts +++ /dev/null | |||
@@ -1,37 +0,0 @@ | |||
1 | import validator from 'validator' | ||
2 | import { SearchTargetType } from '@shared/models/search/search-target-query.model' | ||
3 | import { isArray, exists } from './misc' | ||
4 | import { CONFIG } from '@server/initializers/config' | ||
5 | |||
6 | function isNumberArray (value: any) { | ||
7 | return isArray(value) && value.every(v => validator.isInt('' + v)) | ||
8 | } | ||
9 | |||
10 | function isStringArray (value: any) { | ||
11 | return isArray(value) && value.every(v => typeof v === 'string') | ||
12 | } | ||
13 | |||
14 | function isBooleanBothQueryValid (value: any) { | ||
15 | return value === 'true' || value === 'false' || value === 'both' | ||
16 | } | ||
17 | |||
18 | function isSearchTargetValid (value: SearchTargetType) { | ||
19 | if (!exists(value)) return true | ||
20 | |||
21 | const searchIndexConfig = CONFIG.SEARCH.SEARCH_INDEX | ||
22 | |||
23 | if (value === 'local') return true | ||
24 | |||
25 | if (value === 'search-index' && searchIndexConfig.ENABLED) return true | ||
26 | |||
27 | return false | ||
28 | } | ||
29 | |||
30 | // --------------------------------------------------------------------------- | ||
31 | |||
32 | export { | ||
33 | isNumberArray, | ||
34 | isStringArray, | ||
35 | isBooleanBothQueryValid, | ||
36 | isSearchTargetValid | ||
37 | } | ||
diff --git a/server/helpers/custom-validators/servers.ts b/server/helpers/custom-validators/servers.ts deleted file mode 100644 index b2aa03b77..000000000 --- a/server/helpers/custom-validators/servers.ts +++ /dev/null | |||
@@ -1,42 +0,0 @@ | |||
1 | import validator from 'validator' | ||
2 | import { CONFIG } from '@server/initializers/config' | ||
3 | import { CONSTRAINTS_FIELDS } from '../../initializers/constants' | ||
4 | import { exists, isArray } from './misc' | ||
5 | |||
6 | function isHostValid (host: string) { | ||
7 | const isURLOptions = { | ||
8 | require_host: true, | ||
9 | require_tld: true | ||
10 | } | ||
11 | |||
12 | // We validate 'localhost', so we don't have the top level domain | ||
13 | if (CONFIG.WEBSERVER.HOSTNAME === 'localhost' || CONFIG.WEBSERVER.HOSTNAME === '127.0.0.1') { | ||
14 | isURLOptions.require_tld = false | ||
15 | } | ||
16 | |||
17 | return exists(host) && validator.isURL(host, isURLOptions) && host.split('://').length === 1 | ||
18 | } | ||
19 | |||
20 | function isEachUniqueHostValid (hosts: string[]) { | ||
21 | return isArray(hosts) && | ||
22 | hosts.every(host => { | ||
23 | return isHostValid(host) && hosts.indexOf(host) === hosts.lastIndexOf(host) | ||
24 | }) | ||
25 | } | ||
26 | |||
27 | function isValidContactBody (value: any) { | ||
28 | return exists(value) && validator.isLength(value, CONSTRAINTS_FIELDS.CONTACT_FORM.BODY) | ||
29 | } | ||
30 | |||
31 | function isValidContactFromName (value: any) { | ||
32 | return exists(value) && validator.isLength(value, CONSTRAINTS_FIELDS.CONTACT_FORM.FROM_NAME) | ||
33 | } | ||
34 | |||
35 | // --------------------------------------------------------------------------- | ||
36 | |||
37 | export { | ||
38 | isValidContactBody, | ||
39 | isValidContactFromName, | ||
40 | isEachUniqueHostValid, | ||
41 | isHostValid | ||
42 | } | ||
diff --git a/server/helpers/custom-validators/user-notifications.ts b/server/helpers/custom-validators/user-notifications.ts deleted file mode 100644 index 2de13ca09..000000000 --- a/server/helpers/custom-validators/user-notifications.ts +++ /dev/null | |||
@@ -1,23 +0,0 @@ | |||
1 | import validator from 'validator' | ||
2 | import { UserNotificationSettingValue } from '@shared/models' | ||
3 | import { exists } from './misc' | ||
4 | |||
5 | function isUserNotificationTypeValid (value: any) { | ||
6 | return exists(value) && validator.isInt('' + value) | ||
7 | } | ||
8 | |||
9 | function isUserNotificationSettingValid (value: any) { | ||
10 | return exists(value) && | ||
11 | validator.isInt('' + value) && | ||
12 | ( | ||
13 | value === UserNotificationSettingValue.NONE || | ||
14 | value === UserNotificationSettingValue.WEB || | ||
15 | value === UserNotificationSettingValue.EMAIL || | ||
16 | value === (UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL) | ||
17 | ) | ||
18 | } | ||
19 | |||
20 | export { | ||
21 | isUserNotificationSettingValid, | ||
22 | isUserNotificationTypeValid | ||
23 | } | ||
diff --git a/server/helpers/custom-validators/user-registration.ts b/server/helpers/custom-validators/user-registration.ts deleted file mode 100644 index 9da0bb08a..000000000 --- a/server/helpers/custom-validators/user-registration.ts +++ /dev/null | |||
@@ -1,25 +0,0 @@ | |||
1 | import validator from 'validator' | ||
2 | import { CONSTRAINTS_FIELDS, USER_REGISTRATION_STATES } from '../../initializers/constants' | ||
3 | import { exists } from './misc' | ||
4 | |||
5 | const USER_REGISTRATIONS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.USER_REGISTRATIONS | ||
6 | |||
7 | function isRegistrationStateValid (value: string) { | ||
8 | return exists(value) && USER_REGISTRATION_STATES[value] !== undefined | ||
9 | } | ||
10 | |||
11 | function isRegistrationModerationResponseValid (value: string) { | ||
12 | return exists(value) && validator.isLength(value, USER_REGISTRATIONS_CONSTRAINTS_FIELDS.MODERATOR_MESSAGE) | ||
13 | } | ||
14 | |||
15 | function isRegistrationReasonValid (value: string) { | ||
16 | return exists(value) && validator.isLength(value, USER_REGISTRATIONS_CONSTRAINTS_FIELDS.REASON_MESSAGE) | ||
17 | } | ||
18 | |||
19 | // --------------------------------------------------------------------------- | ||
20 | |||
21 | export { | ||
22 | isRegistrationStateValid, | ||
23 | isRegistrationModerationResponseValid, | ||
24 | isRegistrationReasonValid | ||
25 | } | ||
diff --git a/server/helpers/custom-validators/users.ts b/server/helpers/custom-validators/users.ts deleted file mode 100644 index f02b3ba65..000000000 --- a/server/helpers/custom-validators/users.ts +++ /dev/null | |||
@@ -1,123 +0,0 @@ | |||
1 | import validator from 'validator' | ||
2 | import { UserRole } from '@shared/models' | ||
3 | import { isEmailEnabled } from '../../initializers/config' | ||
4 | import { CONSTRAINTS_FIELDS, NSFW_POLICY_TYPES } from '../../initializers/constants' | ||
5 | import { exists, isArray, isBooleanValid } from './misc' | ||
6 | |||
7 | const USERS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.USERS | ||
8 | |||
9 | function isUserPasswordValid (value: string) { | ||
10 | return validator.isLength(value, USERS_CONSTRAINTS_FIELDS.PASSWORD) | ||
11 | } | ||
12 | |||
13 | function isUserPasswordValidOrEmpty (value: string) { | ||
14 | // Empty password is only possible if emailing is enabled. | ||
15 | if (value === '') return isEmailEnabled() | ||
16 | |||
17 | return isUserPasswordValid(value) | ||
18 | } | ||
19 | |||
20 | function isUserVideoQuotaValid (value: string) { | ||
21 | return exists(value) && validator.isInt(value + '', USERS_CONSTRAINTS_FIELDS.VIDEO_QUOTA) | ||
22 | } | ||
23 | |||
24 | function isUserVideoQuotaDailyValid (value: string) { | ||
25 | return exists(value) && validator.isInt(value + '', USERS_CONSTRAINTS_FIELDS.VIDEO_QUOTA_DAILY) | ||
26 | } | ||
27 | |||
28 | function isUserUsernameValid (value: string) { | ||
29 | return exists(value) && | ||
30 | validator.matches(value, new RegExp(`^[a-z0-9_]+([a-z0-9_.-]+[a-z0-9_]+)?$`)) && | ||
31 | validator.isLength(value, USERS_CONSTRAINTS_FIELDS.USERNAME) | ||
32 | } | ||
33 | |||
34 | function isUserDisplayNameValid (value: string) { | ||
35 | return value === null || (exists(value) && validator.isLength(value, CONSTRAINTS_FIELDS.USERS.NAME)) | ||
36 | } | ||
37 | |||
38 | function isUserDescriptionValid (value: string) { | ||
39 | return value === null || (exists(value) && validator.isLength(value, CONSTRAINTS_FIELDS.USERS.DESCRIPTION)) | ||
40 | } | ||
41 | |||
42 | function isUserEmailVerifiedValid (value: any) { | ||
43 | return isBooleanValid(value) | ||
44 | } | ||
45 | |||
46 | const nsfwPolicies = new Set(Object.values(NSFW_POLICY_TYPES)) | ||
47 | function isUserNSFWPolicyValid (value: any) { | ||
48 | return exists(value) && nsfwPolicies.has(value) | ||
49 | } | ||
50 | |||
51 | function isUserP2PEnabledValid (value: any) { | ||
52 | return isBooleanValid(value) | ||
53 | } | ||
54 | |||
55 | function isUserVideosHistoryEnabledValid (value: any) { | ||
56 | return isBooleanValid(value) | ||
57 | } | ||
58 | |||
59 | function isUserAutoPlayVideoValid (value: any) { | ||
60 | return isBooleanValid(value) | ||
61 | } | ||
62 | |||
63 | function isUserVideoLanguages (value: any) { | ||
64 | return value === null || (isArray(value) && value.length < CONSTRAINTS_FIELDS.USERS.VIDEO_LANGUAGES.max) | ||
65 | } | ||
66 | |||
67 | function isUserAdminFlagsValid (value: any) { | ||
68 | return exists(value) && validator.isInt('' + value) | ||
69 | } | ||
70 | |||
71 | function isUserBlockedValid (value: any) { | ||
72 | return isBooleanValid(value) | ||
73 | } | ||
74 | |||
75 | function isUserAutoPlayNextVideoValid (value: any) { | ||
76 | return isBooleanValid(value) | ||
77 | } | ||
78 | |||
79 | function isUserAutoPlayNextVideoPlaylistValid (value: any) { | ||
80 | return isBooleanValid(value) | ||
81 | } | ||
82 | |||
83 | function isUserEmailPublicValid (value: any) { | ||
84 | return isBooleanValid(value) | ||
85 | } | ||
86 | |||
87 | function isUserNoModal (value: any) { | ||
88 | return isBooleanValid(value) | ||
89 | } | ||
90 | |||
91 | function isUserBlockedReasonValid (value: any) { | ||
92 | return value === null || (exists(value) && validator.isLength(value, CONSTRAINTS_FIELDS.USERS.BLOCKED_REASON)) | ||
93 | } | ||
94 | |||
95 | function isUserRoleValid (value: any) { | ||
96 | return exists(value) && validator.isInt('' + value) && [ UserRole.ADMINISTRATOR, UserRole.MODERATOR, UserRole.USER ].includes(value) | ||
97 | } | ||
98 | |||
99 | // --------------------------------------------------------------------------- | ||
100 | |||
101 | export { | ||
102 | isUserVideosHistoryEnabledValid, | ||
103 | isUserBlockedValid, | ||
104 | isUserPasswordValid, | ||
105 | isUserPasswordValidOrEmpty, | ||
106 | isUserVideoLanguages, | ||
107 | isUserBlockedReasonValid, | ||
108 | isUserRoleValid, | ||
109 | isUserVideoQuotaValid, | ||
110 | isUserVideoQuotaDailyValid, | ||
111 | isUserUsernameValid, | ||
112 | isUserAdminFlagsValid, | ||
113 | isUserEmailVerifiedValid, | ||
114 | isUserNSFWPolicyValid, | ||
115 | isUserP2PEnabledValid, | ||
116 | isUserAutoPlayVideoValid, | ||
117 | isUserAutoPlayNextVideoValid, | ||
118 | isUserAutoPlayNextVideoPlaylistValid, | ||
119 | isUserDisplayNameValid, | ||
120 | isUserDescriptionValid, | ||
121 | isUserEmailPublicValid, | ||
122 | isUserNoModal | ||
123 | } | ||
diff --git a/server/helpers/custom-validators/video-blacklist.ts b/server/helpers/custom-validators/video-blacklist.ts deleted file mode 100644 index 34fcec38e..000000000 --- a/server/helpers/custom-validators/video-blacklist.ts +++ /dev/null | |||
@@ -1,22 +0,0 @@ | |||
1 | import validator from 'validator' | ||
2 | import { exists } from './misc' | ||
3 | import { CONSTRAINTS_FIELDS } from '../../initializers/constants' | ||
4 | import { VideoBlacklistType } from '../../../shared/models/videos' | ||
5 | |||
6 | const VIDEO_BLACKLIST_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_BLACKLIST | ||
7 | |||
8 | function isVideoBlacklistReasonValid (value: string) { | ||
9 | return value === null || validator.isLength(value, VIDEO_BLACKLIST_CONSTRAINTS_FIELDS.REASON) | ||
10 | } | ||
11 | |||
12 | function isVideoBlacklistTypeValid (value: any) { | ||
13 | return exists(value) && | ||
14 | (value === VideoBlacklistType.AUTO_BEFORE_PUBLISHED || value === VideoBlacklistType.MANUAL) | ||
15 | } | ||
16 | |||
17 | // --------------------------------------------------------------------------- | ||
18 | |||
19 | export { | ||
20 | isVideoBlacklistReasonValid, | ||
21 | isVideoBlacklistTypeValid | ||
22 | } | ||
diff --git a/server/helpers/custom-validators/video-captions.ts b/server/helpers/custom-validators/video-captions.ts deleted file mode 100644 index 0e24655a0..000000000 --- a/server/helpers/custom-validators/video-captions.ts +++ /dev/null | |||
@@ -1,43 +0,0 @@ | |||
1 | import { UploadFilesForCheck } from 'express' | ||
2 | import { readFile } from 'fs-extra' | ||
3 | import { getFileSize } from '@shared/extra-utils' | ||
4 | import { CONSTRAINTS_FIELDS, MIMETYPES, VIDEO_LANGUAGES } from '../../initializers/constants' | ||
5 | import { logger } from '../logger' | ||
6 | import { exists, isFileValid } from './misc' | ||
7 | |||
8 | function isVideoCaptionLanguageValid (value: any) { | ||
9 | return exists(value) && VIDEO_LANGUAGES[value] !== undefined | ||
10 | } | ||
11 | |||
12 | // MacOS sends application/octet-stream | ||
13 | const videoCaptionTypesRegex = [ ...Object.keys(MIMETYPES.VIDEO_CAPTIONS.MIMETYPE_EXT), 'application/octet-stream' ] | ||
14 | .map(m => `(${m})`) | ||
15 | .join('|') | ||
16 | |||
17 | function isVideoCaptionFile (files: UploadFilesForCheck, field: string) { | ||
18 | return isFileValid({ | ||
19 | files, | ||
20 | mimeTypeRegex: videoCaptionTypesRegex, | ||
21 | field, | ||
22 | maxSize: CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.FILE_SIZE.max | ||
23 | }) | ||
24 | } | ||
25 | |||
26 | async function isVTTFileValid (filePath: string) { | ||
27 | const size = await getFileSize(filePath) | ||
28 | const content = await readFile(filePath, 'utf8') | ||
29 | |||
30 | logger.debug('Checking VTT file %s', filePath, { size, content }) | ||
31 | |||
32 | if (size > CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.FILE_SIZE.max) return false | ||
33 | |||
34 | return content?.startsWith('WEBVTT') | ||
35 | } | ||
36 | |||
37 | // --------------------------------------------------------------------------- | ||
38 | |||
39 | export { | ||
40 | isVideoCaptionFile, | ||
41 | isVTTFileValid, | ||
42 | isVideoCaptionLanguageValid | ||
43 | } | ||
diff --git a/server/helpers/custom-validators/video-channel-syncs.ts b/server/helpers/custom-validators/video-channel-syncs.ts deleted file mode 100644 index c5a9afa96..000000000 --- a/server/helpers/custom-validators/video-channel-syncs.ts +++ /dev/null | |||
@@ -1,6 +0,0 @@ | |||
1 | import { VIDEO_CHANNEL_SYNC_STATE } from '@server/initializers/constants' | ||
2 | import { exists } from './misc' | ||
3 | |||
4 | export function isVideoChannelSyncStateValid (value: any) { | ||
5 | return exists(value) && VIDEO_CHANNEL_SYNC_STATE[value] !== undefined | ||
6 | } | ||
diff --git a/server/helpers/custom-validators/video-channels.ts b/server/helpers/custom-validators/video-channels.ts deleted file mode 100644 index 249083f39..000000000 --- a/server/helpers/custom-validators/video-channels.ts +++ /dev/null | |||
@@ -1,32 +0,0 @@ | |||
1 | import validator from 'validator' | ||
2 | import { CONSTRAINTS_FIELDS } from '../../initializers/constants' | ||
3 | import { exists } from './misc' | ||
4 | import { isUserUsernameValid } from './users' | ||
5 | |||
6 | const VIDEO_CHANNELS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_CHANNELS | ||
7 | |||
8 | function isVideoChannelUsernameValid (value: string) { | ||
9 | // Use the same constraints than user username | ||
10 | return isUserUsernameValid(value) | ||
11 | } | ||
12 | |||
13 | function isVideoChannelDescriptionValid (value: string) { | ||
14 | return value === null || validator.isLength(value, VIDEO_CHANNELS_CONSTRAINTS_FIELDS.DESCRIPTION) | ||
15 | } | ||
16 | |||
17 | function isVideoChannelDisplayNameValid (value: string) { | ||
18 | return exists(value) && validator.isLength(value, VIDEO_CHANNELS_CONSTRAINTS_FIELDS.NAME) | ||
19 | } | ||
20 | |||
21 | function isVideoChannelSupportValid (value: string) { | ||
22 | return value === null || (exists(value) && validator.isLength(value, VIDEO_CHANNELS_CONSTRAINTS_FIELDS.SUPPORT)) | ||
23 | } | ||
24 | |||
25 | // --------------------------------------------------------------------------- | ||
26 | |||
27 | export { | ||
28 | isVideoChannelUsernameValid, | ||
29 | isVideoChannelDescriptionValid, | ||
30 | isVideoChannelDisplayNameValid, | ||
31 | isVideoChannelSupportValid | ||
32 | } | ||
diff --git a/server/helpers/custom-validators/video-comments.ts b/server/helpers/custom-validators/video-comments.ts deleted file mode 100644 index 94bdf237a..000000000 --- a/server/helpers/custom-validators/video-comments.ts +++ /dev/null | |||
@@ -1,14 +0,0 @@ | |||
1 | import validator from 'validator' | ||
2 | import { CONSTRAINTS_FIELDS } from '../../initializers/constants' | ||
3 | |||
4 | const VIDEO_COMMENTS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_COMMENTS | ||
5 | |||
6 | function isValidVideoCommentText (value: string) { | ||
7 | return value === null || validator.isLength(value, VIDEO_COMMENTS_CONSTRAINTS_FIELDS.TEXT) | ||
8 | } | ||
9 | |||
10 | // --------------------------------------------------------------------------- | ||
11 | |||
12 | export { | ||
13 | isValidVideoCommentText | ||
14 | } | ||
diff --git a/server/helpers/custom-validators/video-imports.ts b/server/helpers/custom-validators/video-imports.ts deleted file mode 100644 index da8962cb6..000000000 --- a/server/helpers/custom-validators/video-imports.ts +++ /dev/null | |||
@@ -1,46 +0,0 @@ | |||
1 | import 'multer' | ||
2 | import { UploadFilesForCheck } from 'express' | ||
3 | import validator from 'validator' | ||
4 | import { CONSTRAINTS_FIELDS, MIMETYPES, VIDEO_IMPORT_STATES } from '../../initializers/constants' | ||
5 | import { exists, isFileValid } from './misc' | ||
6 | |||
7 | function isVideoImportTargetUrlValid (url: string) { | ||
8 | const isURLOptions = { | ||
9 | require_host: true, | ||
10 | require_tld: true, | ||
11 | require_protocol: true, | ||
12 | require_valid_protocol: true, | ||
13 | protocols: [ 'http', 'https' ] | ||
14 | } | ||
15 | |||
16 | return exists(url) && | ||
17 | validator.isURL('' + url, isURLOptions) && | ||
18 | validator.isLength('' + url, CONSTRAINTS_FIELDS.VIDEO_IMPORTS.URL) | ||
19 | } | ||
20 | |||
21 | function isVideoImportStateValid (value: any) { | ||
22 | return exists(value) && VIDEO_IMPORT_STATES[value] !== undefined | ||
23 | } | ||
24 | |||
25 | // MacOS sends application/octet-stream | ||
26 | const videoTorrentImportRegex = [ ...Object.keys(MIMETYPES.TORRENT.MIMETYPE_EXT), 'application/octet-stream' ] | ||
27 | .map(m => `(${m})`) | ||
28 | .join('|') | ||
29 | |||
30 | function isVideoImportTorrentFile (files: UploadFilesForCheck) { | ||
31 | return isFileValid({ | ||
32 | files, | ||
33 | mimeTypeRegex: videoTorrentImportRegex, | ||
34 | field: 'torrentfile', | ||
35 | maxSize: CONSTRAINTS_FIELDS.VIDEO_IMPORTS.TORRENT_FILE.FILE_SIZE.max, | ||
36 | optional: true | ||
37 | }) | ||
38 | } | ||
39 | |||
40 | // --------------------------------------------------------------------------- | ||
41 | |||
42 | export { | ||
43 | isVideoImportStateValid, | ||
44 | isVideoImportTargetUrlValid, | ||
45 | isVideoImportTorrentFile | ||
46 | } | ||
diff --git a/server/helpers/custom-validators/video-lives.ts b/server/helpers/custom-validators/video-lives.ts deleted file mode 100644 index 69d08ae68..000000000 --- a/server/helpers/custom-validators/video-lives.ts +++ /dev/null | |||
@@ -1,11 +0,0 @@ | |||
1 | import { LiveVideoLatencyMode } from '@shared/models' | ||
2 | |||
3 | function isLiveLatencyModeValid (value: any) { | ||
4 | return [ LiveVideoLatencyMode.DEFAULT, LiveVideoLatencyMode.SMALL_LATENCY, LiveVideoLatencyMode.HIGH_LATENCY ].includes(value) | ||
5 | } | ||
6 | |||
7 | // --------------------------------------------------------------------------- | ||
8 | |||
9 | export { | ||
10 | isLiveLatencyModeValid | ||
11 | } | ||
diff --git a/server/helpers/custom-validators/video-ownership.ts b/server/helpers/custom-validators/video-ownership.ts deleted file mode 100644 index cf15b385a..000000000 --- a/server/helpers/custom-validators/video-ownership.ts +++ /dev/null | |||
@@ -1,20 +0,0 @@ | |||
1 | import { Response } from 'express' | ||
2 | import { MUserId } from '@server/types/models' | ||
3 | import { MVideoChangeOwnershipFull } from '@server/types/models/video/video-change-ownership' | ||
4 | import { HttpStatusCode } from '../../../shared/models/http/http-error-codes' | ||
5 | |||
6 | function checkUserCanTerminateOwnershipChange (user: MUserId, videoChangeOwnership: MVideoChangeOwnershipFull, res: Response) { | ||
7 | if (videoChangeOwnership.NextOwner.userId === user.id) { | ||
8 | return true | ||
9 | } | ||
10 | |||
11 | res.fail({ | ||
12 | status: HttpStatusCode.FORBIDDEN_403, | ||
13 | message: 'Cannot terminate an ownership change of another user' | ||
14 | }) | ||
15 | return false | ||
16 | } | ||
17 | |||
18 | export { | ||
19 | checkUserCanTerminateOwnershipChange | ||
20 | } | ||
diff --git a/server/helpers/custom-validators/video-playlists.ts b/server/helpers/custom-validators/video-playlists.ts deleted file mode 100644 index 180018fc5..000000000 --- a/server/helpers/custom-validators/video-playlists.ts +++ /dev/null | |||
@@ -1,35 +0,0 @@ | |||
1 | import { exists } from './misc' | ||
2 | import validator from 'validator' | ||
3 | import { CONSTRAINTS_FIELDS, VIDEO_PLAYLIST_PRIVACIES, VIDEO_PLAYLIST_TYPES } from '../../initializers/constants' | ||
4 | |||
5 | const PLAYLISTS_CONSTRAINT_FIELDS = CONSTRAINTS_FIELDS.VIDEO_PLAYLISTS | ||
6 | |||
7 | function isVideoPlaylistNameValid (value: any) { | ||
8 | return exists(value) && validator.isLength(value, PLAYLISTS_CONSTRAINT_FIELDS.NAME) | ||
9 | } | ||
10 | |||
11 | function isVideoPlaylistDescriptionValid (value: any) { | ||
12 | return value === null || (exists(value) && validator.isLength(value, PLAYLISTS_CONSTRAINT_FIELDS.DESCRIPTION)) | ||
13 | } | ||
14 | |||
15 | function isVideoPlaylistPrivacyValid (value: number) { | ||
16 | return validator.isInt(value + '') && VIDEO_PLAYLIST_PRIVACIES[value] !== undefined | ||
17 | } | ||
18 | |||
19 | function isVideoPlaylistTimestampValid (value: any) { | ||
20 | return value === null || (exists(value) && validator.isInt('' + value, { min: 0 })) | ||
21 | } | ||
22 | |||
23 | function isVideoPlaylistTypeValid (value: any) { | ||
24 | return exists(value) && VIDEO_PLAYLIST_TYPES[value] !== undefined | ||
25 | } | ||
26 | |||
27 | // --------------------------------------------------------------------------- | ||
28 | |||
29 | export { | ||
30 | isVideoPlaylistNameValid, | ||
31 | isVideoPlaylistDescriptionValid, | ||
32 | isVideoPlaylistPrivacyValid, | ||
33 | isVideoPlaylistTimestampValid, | ||
34 | isVideoPlaylistTypeValid | ||
35 | } | ||
diff --git a/server/helpers/custom-validators/video-rates.ts b/server/helpers/custom-validators/video-rates.ts deleted file mode 100644 index f2b6f7cae..000000000 --- a/server/helpers/custom-validators/video-rates.ts +++ /dev/null | |||
@@ -1,5 +0,0 @@ | |||
1 | function isRatingValid (value: any) { | ||
2 | return value === 'like' || value === 'dislike' | ||
3 | } | ||
4 | |||
5 | export { isRatingValid } | ||
diff --git a/server/helpers/custom-validators/video-redundancies.ts b/server/helpers/custom-validators/video-redundancies.ts deleted file mode 100644 index 50a559c4f..000000000 --- a/server/helpers/custom-validators/video-redundancies.ts +++ /dev/null | |||
@@ -1,12 +0,0 @@ | |||
1 | import { exists } from './misc' | ||
2 | |||
3 | function isVideoRedundancyTarget (value: any) { | ||
4 | return exists(value) && | ||
5 | (value === 'my-videos' || value === 'remote-videos') | ||
6 | } | ||
7 | |||
8 | // --------------------------------------------------------------------------- | ||
9 | |||
10 | export { | ||
11 | isVideoRedundancyTarget | ||
12 | } | ||
diff --git a/server/helpers/custom-validators/video-stats.ts b/server/helpers/custom-validators/video-stats.ts deleted file mode 100644 index 1e22f0654..000000000 --- a/server/helpers/custom-validators/video-stats.ts +++ /dev/null | |||
@@ -1,16 +0,0 @@ | |||
1 | import { VideoStatsTimeserieMetric } from '@shared/models' | ||
2 | |||
3 | const validMetrics = new Set<VideoStatsTimeserieMetric>([ | ||
4 | 'viewers', | ||
5 | 'aggregateWatchTime' | ||
6 | ]) | ||
7 | |||
8 | function isValidStatTimeserieMetric (value: VideoStatsTimeserieMetric) { | ||
9 | return validMetrics.has(value) | ||
10 | } | ||
11 | |||
12 | // --------------------------------------------------------------------------- | ||
13 | |||
14 | export { | ||
15 | isValidStatTimeserieMetric | ||
16 | } | ||
diff --git a/server/helpers/custom-validators/video-studio.ts b/server/helpers/custom-validators/video-studio.ts deleted file mode 100644 index 68dfec8dd..000000000 --- a/server/helpers/custom-validators/video-studio.ts +++ /dev/null | |||
@@ -1,53 +0,0 @@ | |||
1 | import validator from 'validator' | ||
2 | import { CONSTRAINTS_FIELDS } from '@server/initializers/constants' | ||
3 | import { buildTaskFileFieldname } from '@server/lib/video-studio' | ||
4 | import { VideoStudioTask } from '@shared/models' | ||
5 | import { isArray } from './misc' | ||
6 | import { isVideoFileMimeTypeValid, isVideoImageValid } from './videos' | ||
7 | import { forceNumber } from '@shared/core-utils' | ||
8 | |||
9 | function isValidStudioTasksArray (tasks: any) { | ||
10 | if (!isArray(tasks)) return false | ||
11 | |||
12 | return tasks.length >= CONSTRAINTS_FIELDS.VIDEO_STUDIO.TASKS.min && | ||
13 | tasks.length <= CONSTRAINTS_FIELDS.VIDEO_STUDIO.TASKS.max | ||
14 | } | ||
15 | |||
16 | function isStudioCutTaskValid (task: VideoStudioTask) { | ||
17 | if (task.name !== 'cut') return false | ||
18 | if (!task.options) return false | ||
19 | |||
20 | const { start, end } = task.options | ||
21 | if (!start && !end) return false | ||
22 | |||
23 | if (start && !validator.isInt(start + '', CONSTRAINTS_FIELDS.VIDEO_STUDIO.CUT_TIME)) return false | ||
24 | if (end && !validator.isInt(end + '', CONSTRAINTS_FIELDS.VIDEO_STUDIO.CUT_TIME)) return false | ||
25 | |||
26 | if (!start || !end) return true | ||
27 | |||
28 | return forceNumber(start) < forceNumber(end) | ||
29 | } | ||
30 | |||
31 | function isStudioTaskAddIntroOutroValid (task: VideoStudioTask, indice: number, files: Express.Multer.File[]) { | ||
32 | const file = files.find(f => f.fieldname === buildTaskFileFieldname(indice, 'file')) | ||
33 | |||
34 | return (task.name === 'add-intro' || task.name === 'add-outro') && | ||
35 | file && isVideoFileMimeTypeValid([ file ], null) | ||
36 | } | ||
37 | |||
38 | function isStudioTaskAddWatermarkValid (task: VideoStudioTask, indice: number, files: Express.Multer.File[]) { | ||
39 | const file = files.find(f => f.fieldname === buildTaskFileFieldname(indice, 'file')) | ||
40 | |||
41 | return task.name === 'add-watermark' && | ||
42 | file && isVideoImageValid([ file ], null, true) | ||
43 | } | ||
44 | |||
45 | // --------------------------------------------------------------------------- | ||
46 | |||
47 | export { | ||
48 | isValidStudioTasksArray, | ||
49 | |||
50 | isStudioCutTaskValid, | ||
51 | isStudioTaskAddIntroOutroValid, | ||
52 | isStudioTaskAddWatermarkValid | ||
53 | } | ||
diff --git a/server/helpers/custom-validators/video-transcoding.ts b/server/helpers/custom-validators/video-transcoding.ts deleted file mode 100644 index 220530de4..000000000 --- a/server/helpers/custom-validators/video-transcoding.ts +++ /dev/null | |||
@@ -1,12 +0,0 @@ | |||
1 | import { exists } from './misc' | ||
2 | |||
3 | function isValidCreateTranscodingType (value: any) { | ||
4 | return exists(value) && | ||
5 | (value === 'hls' || value === 'webtorrent' || value === 'web-video') // TODO: remove webtorrent in v7 | ||
6 | } | ||
7 | |||
8 | // --------------------------------------------------------------------------- | ||
9 | |||
10 | export { | ||
11 | isValidCreateTranscodingType | ||
12 | } | ||
diff --git a/server/helpers/custom-validators/video-view.ts b/server/helpers/custom-validators/video-view.ts deleted file mode 100644 index 091c92083..000000000 --- a/server/helpers/custom-validators/video-view.ts +++ /dev/null | |||
@@ -1,12 +0,0 @@ | |||
1 | import { exists } from './misc' | ||
2 | |||
3 | function isVideoTimeValid (value: number, videoDuration?: number) { | ||
4 | if (value < 0) return false | ||
5 | if (exists(videoDuration) && value > videoDuration) return false | ||
6 | |||
7 | return true | ||
8 | } | ||
9 | |||
10 | export { | ||
11 | isVideoTimeValid | ||
12 | } | ||
diff --git a/server/helpers/custom-validators/videos.ts b/server/helpers/custom-validators/videos.ts deleted file mode 100644 index 00c6deed4..000000000 --- a/server/helpers/custom-validators/videos.ts +++ /dev/null | |||
@@ -1,218 +0,0 @@ | |||
1 | import { Request, Response, UploadFilesForCheck } from 'express' | ||
2 | import { decode as magnetUriDecode } from 'magnet-uri' | ||
3 | import validator from 'validator' | ||
4 | import { getVideoWithAttributes } from '@server/helpers/video' | ||
5 | import { HttpStatusCode, VideoInclude, VideoPrivacy, VideoRateType } from '@shared/models' | ||
6 | import { | ||
7 | CONSTRAINTS_FIELDS, | ||
8 | MIMETYPES, | ||
9 | VIDEO_CATEGORIES, | ||
10 | VIDEO_LICENCES, | ||
11 | VIDEO_LIVE, | ||
12 | VIDEO_PRIVACIES, | ||
13 | VIDEO_RATE_TYPES, | ||
14 | VIDEO_STATES | ||
15 | } from '../../initializers/constants' | ||
16 | import { exists, isArray, isDateValid, isFileValid } from './misc' | ||
17 | |||
18 | const VIDEOS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEOS | ||
19 | |||
20 | function isVideoIncludeValid (include: VideoInclude) { | ||
21 | return exists(include) && validator.isInt('' + include) | ||
22 | } | ||
23 | |||
24 | function isVideoCategoryValid (value: any) { | ||
25 | return value === null || VIDEO_CATEGORIES[value] !== undefined | ||
26 | } | ||
27 | |||
28 | function isVideoStateValid (value: any) { | ||
29 | return exists(value) && VIDEO_STATES[value] !== undefined | ||
30 | } | ||
31 | |||
32 | function isVideoLicenceValid (value: any) { | ||
33 | return value === null || VIDEO_LICENCES[value] !== undefined | ||
34 | } | ||
35 | |||
36 | function isVideoLanguageValid (value: any) { | ||
37 | return value === null || | ||
38 | (typeof value === 'string' && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.LANGUAGE)) | ||
39 | } | ||
40 | |||
41 | function isVideoDurationValid (value: string) { | ||
42 | return exists(value) && validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.DURATION) | ||
43 | } | ||
44 | |||
45 | function isVideoDescriptionValid (value: string) { | ||
46 | return value === null || (exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.DESCRIPTION)) | ||
47 | } | ||
48 | |||
49 | function isVideoSupportValid (value: string) { | ||
50 | return value === null || (exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.SUPPORT)) | ||
51 | } | ||
52 | |||
53 | function isVideoNameValid (value: string) { | ||
54 | return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.NAME) | ||
55 | } | ||
56 | |||
57 | function isVideoTagValid (tag: string) { | ||
58 | return exists(tag) && validator.isLength(tag, VIDEOS_CONSTRAINTS_FIELDS.TAG) | ||
59 | } | ||
60 | |||
61 | function areVideoTagsValid (tags: string[]) { | ||
62 | return tags === null || ( | ||
63 | isArray(tags) && | ||
64 | validator.isInt(tags.length.toString(), VIDEOS_CONSTRAINTS_FIELDS.TAGS) && | ||
65 | tags.every(tag => isVideoTagValid(tag)) | ||
66 | ) | ||
67 | } | ||
68 | |||
69 | function isVideoViewsValid (value: string) { | ||
70 | return exists(value) && validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.VIEWS) | ||
71 | } | ||
72 | |||
73 | const ratingTypes = new Set(Object.values(VIDEO_RATE_TYPES)) | ||
74 | function isVideoRatingTypeValid (value: string) { | ||
75 | return value === 'none' || ratingTypes.has(value as VideoRateType) | ||
76 | } | ||
77 | |||
78 | function isVideoFileExtnameValid (value: string) { | ||
79 | return exists(value) && (value === VIDEO_LIVE.EXTENSION || MIMETYPES.VIDEO.EXT_MIMETYPE[value] !== undefined) | ||
80 | } | ||
81 | |||
82 | function isVideoFileMimeTypeValid (files: UploadFilesForCheck, field = 'videofile') { | ||
83 | return isFileValid({ | ||
84 | files, | ||
85 | mimeTypeRegex: MIMETYPES.VIDEO.MIMETYPES_REGEX, | ||
86 | field, | ||
87 | maxSize: null | ||
88 | }) | ||
89 | } | ||
90 | |||
91 | const videoImageTypes = CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME | ||
92 | .map(v => v.replace('.', '')) | ||
93 | .join('|') | ||
94 | const videoImageTypesRegex = `image/(${videoImageTypes})` | ||
95 | |||
96 | function isVideoImageValid (files: UploadFilesForCheck, field: string, optional = true) { | ||
97 | return isFileValid({ | ||
98 | files, | ||
99 | mimeTypeRegex: videoImageTypesRegex, | ||
100 | field, | ||
101 | maxSize: CONSTRAINTS_FIELDS.VIDEOS.IMAGE.FILE_SIZE.max, | ||
102 | optional | ||
103 | }) | ||
104 | } | ||
105 | |||
106 | function isVideoPrivacyValid (value: number) { | ||
107 | return VIDEO_PRIVACIES[value] !== undefined | ||
108 | } | ||
109 | |||
110 | function isVideoReplayPrivacyValid (value: number) { | ||
111 | return VIDEO_PRIVACIES[value] !== undefined && value !== VideoPrivacy.PASSWORD_PROTECTED | ||
112 | } | ||
113 | |||
114 | function isScheduleVideoUpdatePrivacyValid (value: number) { | ||
115 | return value === VideoPrivacy.UNLISTED || value === VideoPrivacy.PUBLIC || value === VideoPrivacy.INTERNAL | ||
116 | } | ||
117 | |||
118 | function isVideoOriginallyPublishedAtValid (value: string | null) { | ||
119 | return value === null || isDateValid(value) | ||
120 | } | ||
121 | |||
122 | function isVideoFileInfoHashValid (value: string | null | undefined) { | ||
123 | return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.INFO_HASH) | ||
124 | } | ||
125 | |||
126 | function isVideoFileResolutionValid (value: string) { | ||
127 | return exists(value) && validator.isInt(value + '') | ||
128 | } | ||
129 | |||
130 | function isVideoFPSResolutionValid (value: string) { | ||
131 | return value === null || validator.isInt(value + '') | ||
132 | } | ||
133 | |||
134 | function isVideoFileSizeValid (value: string) { | ||
135 | return exists(value) && validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.FILE_SIZE) | ||
136 | } | ||
137 | |||
138 | function isVideoMagnetUriValid (value: string) { | ||
139 | if (!exists(value)) return false | ||
140 | |||
141 | const parsed = magnetUriDecode(value) | ||
142 | return parsed && isVideoFileInfoHashValid(parsed.infoHash) | ||
143 | } | ||
144 | |||
145 | function isPasswordValid (password: string) { | ||
146 | return password.length >= CONSTRAINTS_FIELDS.VIDEO_PASSWORD.LENGTH.min && | ||
147 | password.length < CONSTRAINTS_FIELDS.VIDEO_PASSWORD.LENGTH.max | ||
148 | } | ||
149 | |||
150 | function isValidPasswordProtectedPrivacy (req: Request, res: Response) { | ||
151 | const fail = (message: string) => { | ||
152 | res.fail({ | ||
153 | status: HttpStatusCode.BAD_REQUEST_400, | ||
154 | message | ||
155 | }) | ||
156 | return false | ||
157 | } | ||
158 | |||
159 | let privacy: VideoPrivacy | ||
160 | const video = getVideoWithAttributes(res) | ||
161 | |||
162 | if (exists(req.body?.privacy)) privacy = req.body.privacy | ||
163 | else if (exists(video?.privacy)) privacy = video.privacy | ||
164 | |||
165 | if (privacy !== VideoPrivacy.PASSWORD_PROTECTED) return true | ||
166 | |||
167 | if (!exists(req.body.videoPasswords) && !exists(req.body.passwords)) return fail('Video passwords are missing.') | ||
168 | |||
169 | const passwords = req.body.videoPasswords || req.body.passwords | ||
170 | |||
171 | if (passwords.length === 0) return fail('At least one video password is required.') | ||
172 | |||
173 | if (new Set(passwords).size !== passwords.length) return fail('Duplicate video passwords are not allowed.') | ||
174 | |||
175 | for (const password of passwords) { | ||
176 | if (typeof password !== 'string') { | ||
177 | return fail('Video password should be a string.') | ||
178 | } | ||
179 | |||
180 | if (!isPasswordValid(password)) { | ||
181 | return fail('Invalid video password. Password length should be at least 2 characters and no more than 100 characters.') | ||
182 | } | ||
183 | } | ||
184 | |||
185 | return true | ||
186 | } | ||
187 | |||
188 | // --------------------------------------------------------------------------- | ||
189 | |||
190 | export { | ||
191 | isVideoCategoryValid, | ||
192 | isVideoLicenceValid, | ||
193 | isVideoLanguageValid, | ||
194 | isVideoDescriptionValid, | ||
195 | isVideoFileInfoHashValid, | ||
196 | isVideoNameValid, | ||
197 | areVideoTagsValid, | ||
198 | isVideoFPSResolutionValid, | ||
199 | isScheduleVideoUpdatePrivacyValid, | ||
200 | isVideoOriginallyPublishedAtValid, | ||
201 | isVideoMagnetUriValid, | ||
202 | isVideoStateValid, | ||
203 | isVideoIncludeValid, | ||
204 | isVideoViewsValid, | ||
205 | isVideoRatingTypeValid, | ||
206 | isVideoFileExtnameValid, | ||
207 | isVideoFileMimeTypeValid, | ||
208 | isVideoDurationValid, | ||
209 | isVideoTagValid, | ||
210 | isVideoPrivacyValid, | ||
211 | isVideoReplayPrivacyValid, | ||
212 | isVideoFileResolutionValid, | ||
213 | isVideoFileSizeValid, | ||
214 | isVideoImageValid, | ||
215 | isVideoSupportValid, | ||
216 | isPasswordValid, | ||
217 | isValidPasswordProtectedPrivacy | ||
218 | } | ||
diff --git a/server/helpers/custom-validators/webfinger.ts b/server/helpers/custom-validators/webfinger.ts deleted file mode 100644 index dd914341e..000000000 --- a/server/helpers/custom-validators/webfinger.ts +++ /dev/null | |||
@@ -1,21 +0,0 @@ | |||
1 | import { REMOTE_SCHEME, WEBSERVER } from '../../initializers/constants' | ||
2 | import { sanitizeHost } from '../core-utils' | ||
3 | import { exists } from './misc' | ||
4 | |||
5 | function isWebfingerLocalResourceValid (value: string) { | ||
6 | if (!exists(value)) return false | ||
7 | if (value.startsWith('acct:') === false) return false | ||
8 | |||
9 | const actorWithHost = value.substr(5) | ||
10 | const actorParts = actorWithHost.split('@') | ||
11 | if (actorParts.length !== 2) return false | ||
12 | |||
13 | const host = actorParts[1] | ||
14 | return sanitizeHost(host, REMOTE_SCHEME.HTTP) === WEBSERVER.HOST | ||
15 | } | ||
16 | |||
17 | // --------------------------------------------------------------------------- | ||
18 | |||
19 | export { | ||
20 | isWebfingerLocalResourceValid | ||
21 | } | ||