aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/helpers/custom-validators
diff options
context:
space:
mode:
Diffstat (limited to 'server/helpers/custom-validators')
-rw-r--r--server/helpers/custom-validators/abuses.ts68
-rw-r--r--server/helpers/custom-validators/accounts.ts22
-rw-r--r--server/helpers/custom-validators/activitypub/activity.ts151
-rw-r--r--server/helpers/custom-validators/activitypub/actor.ts142
-rw-r--r--server/helpers/custom-validators/activitypub/cache-file.ts26
-rw-r--r--server/helpers/custom-validators/activitypub/misc.ts76
-rw-r--r--server/helpers/custom-validators/activitypub/playlist.ts29
-rw-r--r--server/helpers/custom-validators/activitypub/signature.ts22
-rw-r--r--server/helpers/custom-validators/activitypub/video-comments.ts59
-rw-r--r--server/helpers/custom-validators/activitypub/videos.ts241
-rw-r--r--server/helpers/custom-validators/activitypub/watch-action.ts37
-rw-r--r--server/helpers/custom-validators/actor-images.ts24
-rw-r--r--server/helpers/custom-validators/bulk.ts9
-rw-r--r--server/helpers/custom-validators/feeds.ts23
-rw-r--r--server/helpers/custom-validators/follows.ts30
-rw-r--r--server/helpers/custom-validators/jobs.ts21
-rw-r--r--server/helpers/custom-validators/logs.ts42
-rw-r--r--server/helpers/custom-validators/metrics.ts10
-rw-r--r--server/helpers/custom-validators/misc.ts190
-rw-r--r--server/helpers/custom-validators/plugins.ts178
-rw-r--r--server/helpers/custom-validators/runners/jobs.ts197
-rw-r--r--server/helpers/custom-validators/runners/runners.ts30
-rw-r--r--server/helpers/custom-validators/search.ts37
-rw-r--r--server/helpers/custom-validators/servers.ts42
-rw-r--r--server/helpers/custom-validators/user-notifications.ts23
-rw-r--r--server/helpers/custom-validators/user-registration.ts25
-rw-r--r--server/helpers/custom-validators/users.ts123
-rw-r--r--server/helpers/custom-validators/video-blacklist.ts22
-rw-r--r--server/helpers/custom-validators/video-captions.ts43
-rw-r--r--server/helpers/custom-validators/video-channel-syncs.ts6
-rw-r--r--server/helpers/custom-validators/video-channels.ts32
-rw-r--r--server/helpers/custom-validators/video-comments.ts14
-rw-r--r--server/helpers/custom-validators/video-imports.ts46
-rw-r--r--server/helpers/custom-validators/video-lives.ts11
-rw-r--r--server/helpers/custom-validators/video-ownership.ts20
-rw-r--r--server/helpers/custom-validators/video-playlists.ts35
-rw-r--r--server/helpers/custom-validators/video-rates.ts5
-rw-r--r--server/helpers/custom-validators/video-redundancies.ts12
-rw-r--r--server/helpers/custom-validators/video-stats.ts16
-rw-r--r--server/helpers/custom-validators/video-studio.ts53
-rw-r--r--server/helpers/custom-validators/video-transcoding.ts12
-rw-r--r--server/helpers/custom-validators/video-view.ts12
-rw-r--r--server/helpers/custom-validators/videos.ts218
-rw-r--r--server/helpers/custom-validators/webfinger.ts21
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 @@
1import validator from 'validator'
2import { abusePredefinedReasonsMap } from '@shared/core-utils/abuse'
3import { AbuseCreate, AbuseFilter, AbusePredefinedReasonsString, AbuseVideoIs } from '@shared/models'
4import { ABUSE_STATES, CONSTRAINTS_FIELDS } from '../../initializers/constants'
5import { exists, isArray } from './misc'
6
7const ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.ABUSES
8const ABUSE_MESSAGES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.ABUSE_MESSAGES
9
10function isAbuseReasonValid (value: string) {
11 return exists(value) && validator.isLength(value, ABUSES_CONSTRAINTS_FIELDS.REASON)
12}
13
14function isAbusePredefinedReasonValid (value: AbusePredefinedReasonsString) {
15 return exists(value) && value in abusePredefinedReasonsMap
16}
17
18function isAbuseFilterValid (value: AbuseFilter) {
19 return value === 'video' || value === 'comment' || value === 'account'
20}
21
22function areAbusePredefinedReasonsValid (value: AbusePredefinedReasonsString[]) {
23 return exists(value) && isArray(value) && value.every(v => v in abusePredefinedReasonsMap)
24}
25
26function isAbuseTimestampValid (value: number) {
27 return value === null || (exists(value) && validator.isInt('' + value, { min: 0 }))
28}
29
30function isAbuseTimestampCoherent (endAt: number, { req }) {
31 const startAt = (req.body as AbuseCreate).video.startAt
32
33 return exists(startAt) && endAt > startAt
34}
35
36function isAbuseModerationCommentValid (value: string) {
37 return exists(value) && validator.isLength(value, ABUSES_CONSTRAINTS_FIELDS.MODERATION_COMMENT)
38}
39
40function isAbuseStateValid (value: string) {
41 return exists(value) && ABUSE_STATES[value] !== undefined
42}
43
44function isAbuseVideoIsValid (value: AbuseVideoIs) {
45 return exists(value) && (
46 value === 'deleted' ||
47 value === 'blacklisted'
48 )
49}
50
51function isAbuseMessageValid (value: string) {
52 return exists(value) && validator.isLength(value, ABUSE_MESSAGES_CONSTRAINTS_FIELDS.MESSAGE)
53}
54
55// ---------------------------------------------------------------------------
56
57export {
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 @@
1import { isUserDescriptionValid, isUserUsernameValid } from './users'
2import { exists } from './misc'
3
4function isAccountNameValid (value: string) {
5 return isUserUsernameValid(value)
6}
7
8function isAccountIdValid (value: string) {
9 return exists(value)
10}
11
12function isAccountDescriptionValid (value: string) {
13 return isUserDescriptionValid(value)
14}
15
16// ---------------------------------------------------------------------------
17
18export {
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 @@
1import validator from 'validator'
2import { Activity, ActivityType } from '../../../../shared/models/activitypub'
3import { isAbuseReasonValid } from '../abuses'
4import { exists } from '../misc'
5import { sanitizeAndCheckActorObject } from './actor'
6import { isCacheFileObjectValid } from './cache-file'
7import { isActivityPubUrlValid, isBaseActivityValid, isObjectValid } from './misc'
8import { isPlaylistObjectValid } from './playlist'
9import { sanitizeAndCheckVideoCommentObject } from './video-comments'
10import { sanitizeAndCheckVideoTorrentObject } from './videos'
11import { isWatchActionObjectValid } from './watch-action'
12
13function isRootActivityValid (activity: any) {
14 return isCollection(activity) || isActivity(activity)
15}
16
17function 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
23function isActivity (activity: any) {
24 return isActivityPubUrlValid(activity.id) &&
25 exists(activity.actor) &&
26 (isActivityPubUrlValid(activity.actor) || isActivityPubUrlValid(activity.actor.id))
27}
28
29const 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
44function 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
52function isFlagActivityValid (activity: any) {
53 return isBaseActivityValid(activity, 'Flag') &&
54 isAbuseReasonValid(activity.content) &&
55 isActivityPubUrlValid(activity.object)
56}
57
58function isLikeActivityValid (activity: any) {
59 return isBaseActivityValid(activity, 'Like') &&
60 isObjectValid(activity.object)
61}
62
63function isDislikeActivityValid (activity: any) {
64 return isBaseActivityValid(activity, 'Dislike') &&
65 isObjectValid(activity.object)
66}
67
68function isAnnounceActivityValid (activity: any) {
69 return isBaseActivityValid(activity, 'Announce') &&
70 isObjectValid(activity.object)
71}
72
73function isViewActivityValid (activity: any) {
74 return isBaseActivityValid(activity, 'View') &&
75 isActivityPubUrlValid(activity.actor) &&
76 isActivityPubUrlValid(activity.object)
77}
78
79function 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
94function 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
104function isDeleteActivityValid (activity: any) {
105 // We don't really check objects
106 return isBaseActivityValid(activity, 'Delete') &&
107 isObjectValid(activity.object)
108}
109
110function isFollowActivityValid (activity: any) {
111 return isBaseActivityValid(activity, 'Follow') &&
112 isObjectValid(activity.object)
113}
114
115function isAcceptActivityValid (activity: any) {
116 return isBaseActivityValid(activity, 'Accept')
117}
118
119function isRejectActivityValid (activity: any) {
120 return isBaseActivityValid(activity, 'Reject')
121}
122
123function 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
136export {
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 @@
1import validator from 'validator'
2import { CONSTRAINTS_FIELDS } from '../../../initializers/constants'
3import { exists, isArray, isDateValid } from '../misc'
4import { isActivityPubUrlValid, isBaseActivityValid, setValidAttributedTo } from './misc'
5import { isHostValid } from '../servers'
6import { peertubeTruncate } from '@server/helpers/core-utils'
7
8function isActorEndpointsObjectValid (endpointObject: any) {
9 if (endpointObject?.sharedInbox) {
10 return isActivityPubUrlValid(endpointObject.sharedInbox)
11 }
12
13 // Shared inbox is optional
14 return true
15}
16
17function isActorPublicKeyObjectValid (publicKeyObject: any) {
18 return isActivityPubUrlValid(publicKeyObject.id) &&
19 isActivityPubUrlValid(publicKeyObject.owner) &&
20 isActorPublicKeyValid(publicKeyObject.publicKeyPem)
21}
22
23function isActorTypeValid (type: string) {
24 return type === 'Person' || type === 'Application' || type === 'Group' || type === 'Service' || type === 'Organization'
25}
26
27function 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
35const actorNameAlphabet = '[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\\-_.:]'
36const actorNameRegExp = new RegExp(`^${actorNameAlphabet}+$`)
37function isActorPreferredUsernameValid (preferredUsername: string) {
38 return exists(preferredUsername) && validator.matches(preferredUsername, actorNameRegExp)
39}
40
41function 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
50function isActorFollowingCountValid (value: string) {
51 return exists(value) && validator.isInt('' + value, { min: 0 })
52}
53
54function isActorFollowersCountValid (value: string) {
55 return exists(value) && validator.isInt('' + value, { min: 0 })
56}
57
58function isActorDeleteActivityValid (activity: any) {
59 return isBaseActivityValid(activity, 'Delete')
60}
61
62function 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
86function 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
106function 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
115function areValidActorHandles (handles: string[]) {
116 return isArray(handles) && handles.every(h => isValidActorHandle(h))
117}
118
119function setValidDescription (obj: any) {
120 if (!obj.summary) obj.summary = null
121
122 return true
123}
124
125// ---------------------------------------------------------------------------
126
127export {
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 @@
1import { isActivityPubUrlValid } from './misc'
2import { isRemoteVideoUrlValid } from './videos'
3import { exists, isDateValid } from '../misc'
4import { CacheFileObject } from '../../../../shared/models/activitypub/objects'
5
6function 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
16export {
17 isCacheFileObjectValid
18}
19
20// ---------------------------------------------------------------------------
21
22function 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 @@
1import validator from 'validator'
2import { CONFIG } from '@server/initializers/config'
3import { CONSTRAINTS_FIELDS } from '../../../initializers/constants'
4import { exists } from '../misc'
5
6function 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
23function isActivityPubUrlValid (url: string) {
24 return isUrlValid(url) && validator.isLength('' + url, CONSTRAINTS_FIELDS.ACTORS.URL)
25}
26
27function 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
35function isUrlCollectionValid (collection: any) {
36 return collection === undefined ||
37 (Array.isArray(collection) && collection.every(t => isActivityPubUrlValid(t)))
38}
39
40function isObjectValid (object: any) {
41 return exists(object) &&
42 (
43 isActivityPubUrlValid(object) || isActivityPubUrlValid(object.id)
44 )
45}
46
47function 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
61function 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
69export {
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 @@
1import validator from 'validator'
2import { PlaylistElementObject, PlaylistObject } from '@shared/models'
3import { exists, isDateValid, isUUIDValid } from '../misc'
4import { isVideoPlaylistNameValid } from '../video-playlists'
5import { isActivityPubUrlValid } from './misc'
6
7function 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
17function isPlaylistElementObjectValid (object: PlaylistElementObject) {
18 return exists(object) &&
19 object.type === 'PlaylistElement' &&
20 validator.isInt(object.position + '') &&
21 isActivityPubUrlValid(object.url)
22}
23
24// ---------------------------------------------------------------------------
25
26export {
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 @@
1import { exists } from '../misc'
2import { isActivityPubUrlValid } from './misc'
3
4function isSignatureTypeValid (signatureType: string) {
5 return exists(signatureType) && signatureType === 'RsaSignature2017'
6}
7
8function isSignatureCreatorValid (signatureCreator: string) {
9 return exists(signatureCreator) && isActivityPubUrlValid(signatureCreator)
10}
11
12function isSignatureValueValid (signatureValue: string) {
13 return exists(signatureValue) && signatureValue.length > 0
14}
15
16// ---------------------------------------------------------------------------
17
18export {
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 @@
1import validator from 'validator'
2import { ACTIVITY_PUB } from '../../../initializers/constants'
3import { exists, isArray, isDateValid } from '../misc'
4import { isActivityPubUrlValid } from './misc'
5
6function 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
34export {
35 sanitizeAndCheckVideoCommentObject
36}
37
38// ---------------------------------------------------------------------------
39
40function isCommentContentValid (content: any) {
41 return exists(content) && validator.isLength('' + content, { min: 1 })
42}
43
44function 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
53function 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 @@
1import validator from 'validator'
2import { logger } from '@server/helpers/logger'
3import { ActivityPubStoryboard, ActivityTrackerUrlObject, ActivityVideoFileMetadataUrlObject, VideoObject } from '@shared/models'
4import { LiveVideoLatencyMode, VideoState } from '../../../../shared/models/videos'
5import { ACTIVITY_PUB, CONSTRAINTS_FIELDS } from '../../../initializers/constants'
6import { peertubeTruncate } from '../../core-utils'
7import { isArray, isBooleanValid, isDateValid, isUUIDValid } from '../misc'
8import { isLiveLatencyModeValid } from '../video-lives'
9import {
10 isVideoDescriptionValid,
11 isVideoDurationValid,
12 isVideoNameValid,
13 isVideoStateValid,
14 isVideoTagValid,
15 isVideoViewsValid
16} from '../videos'
17import { isActivityPubUrlValid, isActivityPubVideoDurationValid, isBaseActivityValid, setValidAttributedTo } from './misc'
18
19function sanitizeAndCheckVideoTorrentUpdateActivity (activity: any) {
20 return isBaseActivityValid(activity, 'Update') &&
21 sanitizeAndCheckVideoTorrentObject(activity.object)
22}
23
24function 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
84function 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
116function 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
123function isAPVideoTrackerUrlObject (url: any): url is ActivityTrackerUrlObject {
124 return isArray(url.rel) &&
125 url.rel.includes('tracker') &&
126 isActivityPubUrlValid(url.href)
127}
128
129// ---------------------------------------------------------------------------
130
131export {
132 sanitizeAndCheckVideoTorrentUpdateActivity,
133 isRemoteStringIdentifierValid,
134 sanitizeAndCheckVideoTorrentObject,
135 isRemoteVideoUrlValid,
136 isAPVideoFileUrlMetadataObject,
137 isAPVideoTrackerUrlObject
138}
139
140// ---------------------------------------------------------------------------
141
142function 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
153function 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
167function isRemoteNumberIdentifierValid (data: any) {
168 return validator.isInt(data.identifier, { min: 0 })
169}
170
171function isRemoteStringIdentifierValid (data: any) {
172 return typeof data.identifier === 'string'
173}
174
175function isRemoteVideoContentValid (mediaType: string, content: string) {
176 return mediaType === 'text/markdown' && isVideoDescriptionValid(content)
177}
178
179function 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
194function 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
202function 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
210function 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
219function 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 @@
1import { WatchActionObject } from '@shared/models'
2import { exists, isDateValid, isUUIDValid } from '../misc'
3import { isVideoTimeValid } from '../video-view'
4import { isActivityPubVideoDurationValid, isObjectValid } from './misc'
5
6function 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
21export {
22 isWatchActionObjectValid
23}
24
25// ---------------------------------------------------------------------------
26
27function isLocationValid (location: any) {
28 if (!location) return true
29
30 return typeof location === 'object' && typeof location.addressCountry === 'string'
31}
32
33function 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
2import { UploadFilesForCheck } from 'express'
3import { CONSTRAINTS_FIELDS } from '../../initializers/constants'
4import { isFileValid } from './misc'
5
6const imageMimeTypes = CONSTRAINTS_FIELDS.ACTORS.IMAGE.EXTNAME
7 .map(v => v.replace('.', ''))
8 .join('|')
9const imageMimeTypesRegex = `image/(${imageMimeTypes})`
10
11function 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
22export {
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 @@
1function isBulkRemoveCommentsOfScopeValid (value: string) {
2 return value === 'my-videos' || value === 'instance'
3}
4
5// ---------------------------------------------------------------------------
6
7export {
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 @@
1import { exists } from './misc'
2
3function 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
21export {
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 @@
1import { exists, isArray } from './misc'
2import { FollowState } from '@shared/models'
3
4function isFollowStateValid (value: FollowState) {
5 if (!exists(value)) return false
6
7 return value === 'pending' || value === 'accepted' || value === 'rejected'
8}
9
10function isRemoteHandleValid (value: string) {
11 if (!exists(value)) return false
12 if (typeof value !== 'string') return false
13
14 return value.includes('@')
15}
16
17function 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
26export {
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 @@
1import { JobState } from '../../../shared/models'
2import { exists } from './misc'
3import { jobTypes } from '@server/lib/job-queue/job-queue'
4
5const jobStates: JobState[] = [ 'active', 'completed', 'failed', 'waiting', 'delayed', 'paused', 'waiting-children' ]
6
7function isValidJobState (value: JobState) {
8 return exists(value) && jobStates.includes(value)
9}
10
11function isValidJobType (value: any) {
12 return exists(value) && jobTypes.includes(value)
13}
14
15// ---------------------------------------------------------------------------
16
17export {
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 @@
1import validator from 'validator'
2import { CONSTRAINTS_FIELDS } from '@server/initializers/constants'
3import { ClientLogLevel, ServerLogLevel } from '@shared/models'
4import { exists } from './misc'
5
6const serverLogLevels = new Set<ServerLogLevel>([ 'debug', 'info', 'warn', 'error' ])
7const clientLogLevels = new Set<ClientLogLevel>([ 'warn', 'error' ])
8
9function isValidLogLevel (value: any) {
10 return exists(value) && serverLogLevels.has(value)
11}
12
13function isValidClientLogMessage (value: any) {
14 return typeof value === 'string' && validator.isLength(value, CONSTRAINTS_FIELDS.LOGS.CLIENT_MESSAGE)
15}
16
17function isValidClientLogLevel (value: any) {
18 return exists(value) && clientLogLevels.has(value)
19}
20
21function isValidClientLogStackTrace (value: any) {
22 return typeof value === 'string' && validator.isLength(value, CONSTRAINTS_FIELDS.LOGS.CLIENT_STACK_TRACE)
23}
24
25function isValidClientLogMeta (value: any) {
26 return typeof value === 'string' && validator.isLength(value, CONSTRAINTS_FIELDS.LOGS.CLIENT_META)
27}
28
29function isValidClientLogUserAgent (value: any) {
30 return typeof value === 'string' && validator.isLength(value, CONSTRAINTS_FIELDS.LOGS.CLIENT_USER_AGENT)
31}
32
33// ---------------------------------------------------------------------------
34
35export {
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 @@
1function isValidPlayerMode (value: any) {
2 // TODO: remove webtorrent in v7
3 return value === 'webtorrent' || value === 'web-video' || value === 'p2p-media-loader'
4}
5
6// ---------------------------------------------------------------------------
7
8export {
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 @@
1import 'multer'
2import { UploadFilesForCheck } from 'express'
3import { sep } from 'path'
4import validator from 'validator'
5import { isShortUUID, shortToUUID } from '@shared/extra-utils'
6
7function exists (value: any) {
8 return value !== undefined && value !== null
9}
10
11function isSafePath (p: string) {
12 return exists(p) &&
13 (p + '').split(sep).every(part => {
14 return [ '..' ].includes(part) === false
15 })
16}
17
18function 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
26function isSafePeerTubeFilenameWithoutExtension (filename: string) {
27 return filename.match(/^[a-z0-9-]+$/)
28}
29
30function isArray (value: any): value is any[] {
31 return Array.isArray(value)
32}
33
34function isNotEmptyIntArray (value: any) {
35 return Array.isArray(value) && value.every(v => validator.isInt('' + v)) && value.length !== 0
36}
37
38function isNotEmptyStringArray (value: any) {
39 return Array.isArray(value) && value.every(v => typeof v === 'string' && v.length !== 0) && value.length !== 0
40}
41
42function isArrayOf (value: any, validator: (value: any) => boolean) {
43 return isArray(value) && value.every(v => validator(v))
44}
45
46function isDateValid (value: string) {
47 return exists(value) && validator.isISO8601(value)
48}
49
50function isIdValid (value: string) {
51 return exists(value) && validator.isInt('' + value)
52}
53
54function isUUIDValid (value: string) {
55 return exists(value) && validator.isUUID('' + value, 4)
56}
57
58function areUUIDsValid (values: string[]) {
59 return isArray(values) && values.every(v => isUUIDValid(v))
60}
61
62function isIdOrUUIDValid (value: string) {
63 return isIdValid(value) || isUUIDValid(value)
64}
65
66function isBooleanValid (value: any) {
67 return typeof value === 'boolean' || (typeof value === 'string' && validator.isBoolean(value))
68}
69
70function isIntOrNull (value: any) {
71 return value === null || validator.isInt('' + value)
72}
73
74// ---------------------------------------------------------------------------
75
76function 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
111function checkMimetypeRegex (fileMimeType: string, mimeTypeRegex: string) {
112 return new RegExp(`^${mimeTypeRegex}$`, 'i').test(fileMimeType)
113}
114
115// ---------------------------------------------------------------------------
116
117function 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
129function toCompleteUUIDs (values: string[]) {
130 return values.map(v => toCompleteUUID(v))
131}
132
133function 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
142function 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
151function toValueOrNull (value: string) {
152 if (value === 'null') return null
153
154 return value
155}
156
157function 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
166export {
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 @@
1import validator from 'validator'
2import { PluginPackageJSON } from '../../../shared/models/plugins/plugin-package-json.model'
3import { PluginType } from '../../../shared/models/plugins/plugin.type'
4import { CONSTRAINTS_FIELDS } from '../../initializers/constants'
5import { isUrlValid } from './activitypub/misc'
6import { exists, isArray, isSafePath } from './misc'
7
8const PLUGINS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.PLUGINS
9
10function isPluginTypeValid (value: any) {
11 return exists(value) &&
12 (value === PluginType.PLUGIN || value === PluginType.THEME)
13}
14
15function 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
21function 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
28function isPluginDescriptionValid (value: string) {
29 return exists(value) && validator.isLength(value, PLUGINS_CONSTRAINTS_FIELDS.DESCRIPTION)
30}
31
32function 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
40function 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
53function isPluginEngineValid (engine: any) {
54 return exists(engine) && exists(engine.peertube)
55}
56
57function isPluginHomepage (value: string) {
58 return exists(value) && (!value || isUrlValid(value))
59}
60
61function isPluginBugs (value: string) {
62 return exists(value) && (!value || isUrlValid(value))
63}
64
65function 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
75function areClientScriptsValid (clientScripts: any[]) {
76 return isArray(clientScripts) &&
77 clientScripts.every(c => {
78 return isSafePath(c.script) && isArray(c.scopes)
79 })
80}
81
82function 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
92function areCSSPathsValid (css: any[]) {
93 return isArray(css) && css.every(c => isSafePath(c))
94}
95
96function isThemeNameValid (name: string) {
97 return isPluginNameValid(name)
98}
99
100function 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
162function isLibraryCodeValid (library: any) {
163 return typeof library.register === 'function' &&
164 typeof library.unregister === 'function'
165}
166
167export {
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 @@
1import { UploadFilesForCheck } from 'express'
2import validator from 'validator'
3import { CONSTRAINTS_FIELDS, RUNNER_JOB_STATES } from '@server/initializers/constants'
4import {
5 LiveRTMPHLSTranscodingSuccess,
6 RunnerJobSuccessPayload,
7 RunnerJobType,
8 RunnerJobUpdatePayload,
9 VideoStudioTranscodingSuccess,
10 VODAudioMergeTranscodingSuccess,
11 VODHLSTranscodingSuccess,
12 VODWebVideoTranscodingSuccess
13} from '@shared/models'
14import { exists, isArray, isFileValid, isSafeFilename } from '../misc'
15
16const RUNNER_JOBS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.RUNNER_JOBS
17
18const runnerJobTypes = new Set([ 'vod-hls-transcoding', 'vod-web-video-transcoding', 'vod-audio-merge-transcoding' ])
19function isRunnerJobTypeValid (value: RunnerJobType) {
20 return runnerJobTypes.has(value)
21}
22
23function 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
33function isRunnerJobProgressValid (value: string) {
34 return validator.isInt(value + '', RUNNER_JOBS_CONSTRAINTS_FIELDS.PROGRESS)
35}
36
37function 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
47function isRunnerJobTokenValid (value: string) {
48 return exists(value) && validator.isLength(value, RUNNER_JOBS_CONSTRAINTS_FIELDS.TOKEN)
49}
50
51function isRunnerJobAbortReasonValid (value: string) {
52 return validator.isLength(value, RUNNER_JOBS_CONSTRAINTS_FIELDS.REASON)
53}
54
55function isRunnerJobErrorMessageValid (value: string) {
56 return validator.isLength(value, RUNNER_JOBS_CONSTRAINTS_FIELDS.ERROR_MESSAGE)
57}
58
59function isRunnerJobStateValid (value: any) {
60 return exists(value) && RUNNER_JOB_STATES[value] !== undefined
61}
62
63function isRunnerJobArrayOfStateValid (value: any) {
64 return isArray(value) && value.every(v => isRunnerJobStateValid(v))
65}
66
67// ---------------------------------------------------------------------------
68
69export {
70 isRunnerJobTypeValid,
71 isRunnerJobSuccessPayloadValid,
72 isRunnerJobUpdatePayloadValid,
73 isRunnerJobTokenValid,
74 isRunnerJobErrorMessageValid,
75 isRunnerJobProgressValid,
76 isRunnerJobAbortReasonValid,
77 isRunnerJobArrayOfStateValid,
78 isRunnerJobStateValid
79}
80
81// ---------------------------------------------------------------------------
82
83function 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
92function 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
102function 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
111function 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
118function 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
129function 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
138function 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
147function 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
156function 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
190function 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 @@
1import validator from 'validator'
2import { CONSTRAINTS_FIELDS } from '@server/initializers/constants'
3import { exists } from '../misc'
4
5const RUNNERS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.RUNNERS
6
7function isRunnerRegistrationTokenValid (value: string) {
8 return exists(value) && validator.isLength(value, RUNNERS_CONSTRAINTS_FIELDS.TOKEN)
9}
10
11function isRunnerTokenValid (value: string) {
12 return exists(value) && validator.isLength(value, RUNNERS_CONSTRAINTS_FIELDS.TOKEN)
13}
14
15function isRunnerNameValid (value: string) {
16 return exists(value) && validator.isLength(value, RUNNERS_CONSTRAINTS_FIELDS.NAME)
17}
18
19function isRunnerDescriptionValid (value: string) {
20 return exists(value) && validator.isLength(value, RUNNERS_CONSTRAINTS_FIELDS.DESCRIPTION)
21}
22
23// ---------------------------------------------------------------------------
24
25export {
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 @@
1import validator from 'validator'
2import { SearchTargetType } from '@shared/models/search/search-target-query.model'
3import { isArray, exists } from './misc'
4import { CONFIG } from '@server/initializers/config'
5
6function isNumberArray (value: any) {
7 return isArray(value) && value.every(v => validator.isInt('' + v))
8}
9
10function isStringArray (value: any) {
11 return isArray(value) && value.every(v => typeof v === 'string')
12}
13
14function isBooleanBothQueryValid (value: any) {
15 return value === 'true' || value === 'false' || value === 'both'
16}
17
18function 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
32export {
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 @@
1import validator from 'validator'
2import { CONFIG } from '@server/initializers/config'
3import { CONSTRAINTS_FIELDS } from '../../initializers/constants'
4import { exists, isArray } from './misc'
5
6function 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
20function isEachUniqueHostValid (hosts: string[]) {
21 return isArray(hosts) &&
22 hosts.every(host => {
23 return isHostValid(host) && hosts.indexOf(host) === hosts.lastIndexOf(host)
24 })
25}
26
27function isValidContactBody (value: any) {
28 return exists(value) && validator.isLength(value, CONSTRAINTS_FIELDS.CONTACT_FORM.BODY)
29}
30
31function isValidContactFromName (value: any) {
32 return exists(value) && validator.isLength(value, CONSTRAINTS_FIELDS.CONTACT_FORM.FROM_NAME)
33}
34
35// ---------------------------------------------------------------------------
36
37export {
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 @@
1import validator from 'validator'
2import { UserNotificationSettingValue } from '@shared/models'
3import { exists } from './misc'
4
5function isUserNotificationTypeValid (value: any) {
6 return exists(value) && validator.isInt('' + value)
7}
8
9function 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
20export {
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 @@
1import validator from 'validator'
2import { CONSTRAINTS_FIELDS, USER_REGISTRATION_STATES } from '../../initializers/constants'
3import { exists } from './misc'
4
5const USER_REGISTRATIONS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.USER_REGISTRATIONS
6
7function isRegistrationStateValid (value: string) {
8 return exists(value) && USER_REGISTRATION_STATES[value] !== undefined
9}
10
11function isRegistrationModerationResponseValid (value: string) {
12 return exists(value) && validator.isLength(value, USER_REGISTRATIONS_CONSTRAINTS_FIELDS.MODERATOR_MESSAGE)
13}
14
15function isRegistrationReasonValid (value: string) {
16 return exists(value) && validator.isLength(value, USER_REGISTRATIONS_CONSTRAINTS_FIELDS.REASON_MESSAGE)
17}
18
19// ---------------------------------------------------------------------------
20
21export {
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 @@
1import validator from 'validator'
2import { UserRole } from '@shared/models'
3import { isEmailEnabled } from '../../initializers/config'
4import { CONSTRAINTS_FIELDS, NSFW_POLICY_TYPES } from '../../initializers/constants'
5import { exists, isArray, isBooleanValid } from './misc'
6
7const USERS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.USERS
8
9function isUserPasswordValid (value: string) {
10 return validator.isLength(value, USERS_CONSTRAINTS_FIELDS.PASSWORD)
11}
12
13function 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
20function isUserVideoQuotaValid (value: string) {
21 return exists(value) && validator.isInt(value + '', USERS_CONSTRAINTS_FIELDS.VIDEO_QUOTA)
22}
23
24function isUserVideoQuotaDailyValid (value: string) {
25 return exists(value) && validator.isInt(value + '', USERS_CONSTRAINTS_FIELDS.VIDEO_QUOTA_DAILY)
26}
27
28function 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
34function isUserDisplayNameValid (value: string) {
35 return value === null || (exists(value) && validator.isLength(value, CONSTRAINTS_FIELDS.USERS.NAME))
36}
37
38function isUserDescriptionValid (value: string) {
39 return value === null || (exists(value) && validator.isLength(value, CONSTRAINTS_FIELDS.USERS.DESCRIPTION))
40}
41
42function isUserEmailVerifiedValid (value: any) {
43 return isBooleanValid(value)
44}
45
46const nsfwPolicies = new Set(Object.values(NSFW_POLICY_TYPES))
47function isUserNSFWPolicyValid (value: any) {
48 return exists(value) && nsfwPolicies.has(value)
49}
50
51function isUserP2PEnabledValid (value: any) {
52 return isBooleanValid(value)
53}
54
55function isUserVideosHistoryEnabledValid (value: any) {
56 return isBooleanValid(value)
57}
58
59function isUserAutoPlayVideoValid (value: any) {
60 return isBooleanValid(value)
61}
62
63function isUserVideoLanguages (value: any) {
64 return value === null || (isArray(value) && value.length < CONSTRAINTS_FIELDS.USERS.VIDEO_LANGUAGES.max)
65}
66
67function isUserAdminFlagsValid (value: any) {
68 return exists(value) && validator.isInt('' + value)
69}
70
71function isUserBlockedValid (value: any) {
72 return isBooleanValid(value)
73}
74
75function isUserAutoPlayNextVideoValid (value: any) {
76 return isBooleanValid(value)
77}
78
79function isUserAutoPlayNextVideoPlaylistValid (value: any) {
80 return isBooleanValid(value)
81}
82
83function isUserEmailPublicValid (value: any) {
84 return isBooleanValid(value)
85}
86
87function isUserNoModal (value: any) {
88 return isBooleanValid(value)
89}
90
91function isUserBlockedReasonValid (value: any) {
92 return value === null || (exists(value) && validator.isLength(value, CONSTRAINTS_FIELDS.USERS.BLOCKED_REASON))
93}
94
95function isUserRoleValid (value: any) {
96 return exists(value) && validator.isInt('' + value) && [ UserRole.ADMINISTRATOR, UserRole.MODERATOR, UserRole.USER ].includes(value)
97}
98
99// ---------------------------------------------------------------------------
100
101export {
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 @@
1import validator from 'validator'
2import { exists } from './misc'
3import { CONSTRAINTS_FIELDS } from '../../initializers/constants'
4import { VideoBlacklistType } from '../../../shared/models/videos'
5
6const VIDEO_BLACKLIST_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_BLACKLIST
7
8function isVideoBlacklistReasonValid (value: string) {
9 return value === null || validator.isLength(value, VIDEO_BLACKLIST_CONSTRAINTS_FIELDS.REASON)
10}
11
12function isVideoBlacklistTypeValid (value: any) {
13 return exists(value) &&
14 (value === VideoBlacklistType.AUTO_BEFORE_PUBLISHED || value === VideoBlacklistType.MANUAL)
15}
16
17// ---------------------------------------------------------------------------
18
19export {
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 @@
1import { UploadFilesForCheck } from 'express'
2import { readFile } from 'fs-extra'
3import { getFileSize } from '@shared/extra-utils'
4import { CONSTRAINTS_FIELDS, MIMETYPES, VIDEO_LANGUAGES } from '../../initializers/constants'
5import { logger } from '../logger'
6import { exists, isFileValid } from './misc'
7
8function isVideoCaptionLanguageValid (value: any) {
9 return exists(value) && VIDEO_LANGUAGES[value] !== undefined
10}
11
12// MacOS sends application/octet-stream
13const videoCaptionTypesRegex = [ ...Object.keys(MIMETYPES.VIDEO_CAPTIONS.MIMETYPE_EXT), 'application/octet-stream' ]
14 .map(m => `(${m})`)
15 .join('|')
16
17function 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
26async 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
39export {
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 @@
1import { VIDEO_CHANNEL_SYNC_STATE } from '@server/initializers/constants'
2import { exists } from './misc'
3
4export 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 @@
1import validator from 'validator'
2import { CONSTRAINTS_FIELDS } from '../../initializers/constants'
3import { exists } from './misc'
4import { isUserUsernameValid } from './users'
5
6const VIDEO_CHANNELS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_CHANNELS
7
8function isVideoChannelUsernameValid (value: string) {
9 // Use the same constraints than user username
10 return isUserUsernameValid(value)
11}
12
13function isVideoChannelDescriptionValid (value: string) {
14 return value === null || validator.isLength(value, VIDEO_CHANNELS_CONSTRAINTS_FIELDS.DESCRIPTION)
15}
16
17function isVideoChannelDisplayNameValid (value: string) {
18 return exists(value) && validator.isLength(value, VIDEO_CHANNELS_CONSTRAINTS_FIELDS.NAME)
19}
20
21function isVideoChannelSupportValid (value: string) {
22 return value === null || (exists(value) && validator.isLength(value, VIDEO_CHANNELS_CONSTRAINTS_FIELDS.SUPPORT))
23}
24
25// ---------------------------------------------------------------------------
26
27export {
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 @@
1import validator from 'validator'
2import { CONSTRAINTS_FIELDS } from '../../initializers/constants'
3
4const VIDEO_COMMENTS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_COMMENTS
5
6function isValidVideoCommentText (value: string) {
7 return value === null || validator.isLength(value, VIDEO_COMMENTS_CONSTRAINTS_FIELDS.TEXT)
8}
9
10// ---------------------------------------------------------------------------
11
12export {
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 @@
1import 'multer'
2import { UploadFilesForCheck } from 'express'
3import validator from 'validator'
4import { CONSTRAINTS_FIELDS, MIMETYPES, VIDEO_IMPORT_STATES } from '../../initializers/constants'
5import { exists, isFileValid } from './misc'
6
7function 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
21function isVideoImportStateValid (value: any) {
22 return exists(value) && VIDEO_IMPORT_STATES[value] !== undefined
23}
24
25// MacOS sends application/octet-stream
26const videoTorrentImportRegex = [ ...Object.keys(MIMETYPES.TORRENT.MIMETYPE_EXT), 'application/octet-stream' ]
27 .map(m => `(${m})`)
28 .join('|')
29
30function 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
42export {
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 @@
1import { LiveVideoLatencyMode } from '@shared/models'
2
3function isLiveLatencyModeValid (value: any) {
4 return [ LiveVideoLatencyMode.DEFAULT, LiveVideoLatencyMode.SMALL_LATENCY, LiveVideoLatencyMode.HIGH_LATENCY ].includes(value)
5}
6
7// ---------------------------------------------------------------------------
8
9export {
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 @@
1import { Response } from 'express'
2import { MUserId } from '@server/types/models'
3import { MVideoChangeOwnershipFull } from '@server/types/models/video/video-change-ownership'
4import { HttpStatusCode } from '../../../shared/models/http/http-error-codes'
5
6function 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
18export {
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 @@
1import { exists } from './misc'
2import validator from 'validator'
3import { CONSTRAINTS_FIELDS, VIDEO_PLAYLIST_PRIVACIES, VIDEO_PLAYLIST_TYPES } from '../../initializers/constants'
4
5const PLAYLISTS_CONSTRAINT_FIELDS = CONSTRAINTS_FIELDS.VIDEO_PLAYLISTS
6
7function isVideoPlaylistNameValid (value: any) {
8 return exists(value) && validator.isLength(value, PLAYLISTS_CONSTRAINT_FIELDS.NAME)
9}
10
11function isVideoPlaylistDescriptionValid (value: any) {
12 return value === null || (exists(value) && validator.isLength(value, PLAYLISTS_CONSTRAINT_FIELDS.DESCRIPTION))
13}
14
15function isVideoPlaylistPrivacyValid (value: number) {
16 return validator.isInt(value + '') && VIDEO_PLAYLIST_PRIVACIES[value] !== undefined
17}
18
19function isVideoPlaylistTimestampValid (value: any) {
20 return value === null || (exists(value) && validator.isInt('' + value, { min: 0 }))
21}
22
23function isVideoPlaylistTypeValid (value: any) {
24 return exists(value) && VIDEO_PLAYLIST_TYPES[value] !== undefined
25}
26
27// ---------------------------------------------------------------------------
28
29export {
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 @@
1function isRatingValid (value: any) {
2 return value === 'like' || value === 'dislike'
3}
4
5export { 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 @@
1import { exists } from './misc'
2
3function isVideoRedundancyTarget (value: any) {
4 return exists(value) &&
5 (value === 'my-videos' || value === 'remote-videos')
6}
7
8// ---------------------------------------------------------------------------
9
10export {
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 @@
1import { VideoStatsTimeserieMetric } from '@shared/models'
2
3const validMetrics = new Set<VideoStatsTimeserieMetric>([
4 'viewers',
5 'aggregateWatchTime'
6])
7
8function isValidStatTimeserieMetric (value: VideoStatsTimeserieMetric) {
9 return validMetrics.has(value)
10}
11
12// ---------------------------------------------------------------------------
13
14export {
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 @@
1import validator from 'validator'
2import { CONSTRAINTS_FIELDS } from '@server/initializers/constants'
3import { buildTaskFileFieldname } from '@server/lib/video-studio'
4import { VideoStudioTask } from '@shared/models'
5import { isArray } from './misc'
6import { isVideoFileMimeTypeValid, isVideoImageValid } from './videos'
7import { forceNumber } from '@shared/core-utils'
8
9function 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
16function 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
31function 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
38function 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
47export {
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 @@
1import { exists } from './misc'
2
3function isValidCreateTranscodingType (value: any) {
4 return exists(value) &&
5 (value === 'hls' || value === 'webtorrent' || value === 'web-video') // TODO: remove webtorrent in v7
6}
7
8// ---------------------------------------------------------------------------
9
10export {
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 @@
1import { exists } from './misc'
2
3function 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
10export {
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 @@
1import { Request, Response, UploadFilesForCheck } from 'express'
2import { decode as magnetUriDecode } from 'magnet-uri'
3import validator from 'validator'
4import { getVideoWithAttributes } from '@server/helpers/video'
5import { HttpStatusCode, VideoInclude, VideoPrivacy, VideoRateType } from '@shared/models'
6import {
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'
16import { exists, isArray, isDateValid, isFileValid } from './misc'
17
18const VIDEOS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEOS
19
20function isVideoIncludeValid (include: VideoInclude) {
21 return exists(include) && validator.isInt('' + include)
22}
23
24function isVideoCategoryValid (value: any) {
25 return value === null || VIDEO_CATEGORIES[value] !== undefined
26}
27
28function isVideoStateValid (value: any) {
29 return exists(value) && VIDEO_STATES[value] !== undefined
30}
31
32function isVideoLicenceValid (value: any) {
33 return value === null || VIDEO_LICENCES[value] !== undefined
34}
35
36function isVideoLanguageValid (value: any) {
37 return value === null ||
38 (typeof value === 'string' && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.LANGUAGE))
39}
40
41function isVideoDurationValid (value: string) {
42 return exists(value) && validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.DURATION)
43}
44
45function isVideoDescriptionValid (value: string) {
46 return value === null || (exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.DESCRIPTION))
47}
48
49function isVideoSupportValid (value: string) {
50 return value === null || (exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.SUPPORT))
51}
52
53function isVideoNameValid (value: string) {
54 return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.NAME)
55}
56
57function isVideoTagValid (tag: string) {
58 return exists(tag) && validator.isLength(tag, VIDEOS_CONSTRAINTS_FIELDS.TAG)
59}
60
61function 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
69function isVideoViewsValid (value: string) {
70 return exists(value) && validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.VIEWS)
71}
72
73const ratingTypes = new Set(Object.values(VIDEO_RATE_TYPES))
74function isVideoRatingTypeValid (value: string) {
75 return value === 'none' || ratingTypes.has(value as VideoRateType)
76}
77
78function isVideoFileExtnameValid (value: string) {
79 return exists(value) && (value === VIDEO_LIVE.EXTENSION || MIMETYPES.VIDEO.EXT_MIMETYPE[value] !== undefined)
80}
81
82function isVideoFileMimeTypeValid (files: UploadFilesForCheck, field = 'videofile') {
83 return isFileValid({
84 files,
85 mimeTypeRegex: MIMETYPES.VIDEO.MIMETYPES_REGEX,
86 field,
87 maxSize: null
88 })
89}
90
91const videoImageTypes = CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME
92 .map(v => v.replace('.', ''))
93 .join('|')
94const videoImageTypesRegex = `image/(${videoImageTypes})`
95
96function 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
106function isVideoPrivacyValid (value: number) {
107 return VIDEO_PRIVACIES[value] !== undefined
108}
109
110function isVideoReplayPrivacyValid (value: number) {
111 return VIDEO_PRIVACIES[value] !== undefined && value !== VideoPrivacy.PASSWORD_PROTECTED
112}
113
114function isScheduleVideoUpdatePrivacyValid (value: number) {
115 return value === VideoPrivacy.UNLISTED || value === VideoPrivacy.PUBLIC || value === VideoPrivacy.INTERNAL
116}
117
118function isVideoOriginallyPublishedAtValid (value: string | null) {
119 return value === null || isDateValid(value)
120}
121
122function isVideoFileInfoHashValid (value: string | null | undefined) {
123 return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.INFO_HASH)
124}
125
126function isVideoFileResolutionValid (value: string) {
127 return exists(value) && validator.isInt(value + '')
128}
129
130function isVideoFPSResolutionValid (value: string) {
131 return value === null || validator.isInt(value + '')
132}
133
134function isVideoFileSizeValid (value: string) {
135 return exists(value) && validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.FILE_SIZE)
136}
137
138function isVideoMagnetUriValid (value: string) {
139 if (!exists(value)) return false
140
141 const parsed = magnetUriDecode(value)
142 return parsed && isVideoFileInfoHashValid(parsed.infoHash)
143}
144
145function isPasswordValid (password: string) {
146 return password.length >= CONSTRAINTS_FIELDS.VIDEO_PASSWORD.LENGTH.min &&
147 password.length < CONSTRAINTS_FIELDS.VIDEO_PASSWORD.LENGTH.max
148}
149
150function 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
190export {
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 @@
1import { REMOTE_SCHEME, WEBSERVER } from '../../initializers/constants'
2import { sanitizeHost } from '../core-utils'
3import { exists } from './misc'
4
5function 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
19export {
20 isWebfingerLocalResourceValid
21}