expliciting type checks and predicates (server only)
[oweals/peertube.git] / server / helpers / custom-validators / videos.ts
1 import { Response } from 'express'
2 import 'express-validator'
3 import { values } from 'lodash'
4 import 'multer'
5 import * as validator from 'validator'
6 import { UserRight, VideoPrivacy, VideoRateType } from '../../../shared'
7 import {
8   CONSTRAINTS_FIELDS,
9   VIDEO_CATEGORIES,
10   VIDEO_LICENCES,
11   VIDEO_MIMETYPE_EXT,
12   VIDEO_PRIVACIES,
13   VIDEO_RATE_TYPES,
14   VIDEO_STATES
15 } from '../../initializers'
16 import { VideoModel } from '../../models/video/video'
17 import { exists, isArray, isFileValid } from './misc'
18 import { VideoChannelModel } from '../../models/video/video-channel'
19 import { UserModel } from '../../models/account/user'
20
21 const VIDEOS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEOS
22 const VIDEO_ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_ABUSES
23
24 function isVideoCategoryValid (value: any) {
25   return value === null || VIDEO_CATEGORIES[ value ] !== undefined
26 }
27
28 function isVideoStateValid (value: any) {
29   return exists(value) && VIDEO_STATES[ value ] !== undefined
30 }
31
32 function isVideoLicenceValid (value: any) {
33   return value === null || VIDEO_LICENCES[ value ] !== undefined
34 }
35
36 function isVideoLanguageValid (value: any) {
37   return value === null ||
38     (typeof value === 'string' && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.LANGUAGE))
39 }
40
41 function isVideoDurationValid (value: string) {
42   return exists(value) && validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.DURATION)
43 }
44
45 function isVideoTruncatedDescriptionValid (value: string) {
46   return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.TRUNCATED_DESCRIPTION)
47 }
48
49 function isVideoDescriptionValid (value: string) {
50   return value === null || (exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.DESCRIPTION))
51 }
52
53 function isVideoSupportValid (value: string) {
54   return value === null || (exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.SUPPORT))
55 }
56
57 function isVideoNameValid (value: string) {
58   return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.NAME)
59 }
60
61 function isVideoTagValid (tag: string) {
62   return exists(tag) && validator.isLength(tag, VIDEOS_CONSTRAINTS_FIELDS.TAG)
63 }
64
65 function isVideoTagsValid (tags: string[]) {
66   return tags === null || (
67     isArray(tags) &&
68     validator.isInt(tags.length.toString(), VIDEOS_CONSTRAINTS_FIELDS.TAGS) &&
69     tags.every(tag => isVideoTagValid(tag))
70   )
71 }
72
73 function isVideoAbuseReasonValid (value: string) {
74   return exists(value) && validator.isLength(value, VIDEO_ABUSES_CONSTRAINTS_FIELDS.REASON)
75 }
76
77 function isVideoViewsValid (value: string) {
78   return exists(value) && validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.VIEWS)
79 }
80
81 function isVideoRatingTypeValid (value: string) {
82   return value === 'none' || values(VIDEO_RATE_TYPES).indexOf(value as VideoRateType) !== -1
83 }
84
85 const videoFileTypes = Object.keys(VIDEO_MIMETYPE_EXT).map(m => `(${m})`)
86 const videoFileTypesRegex = videoFileTypes.join('|')
87
88 function isVideoFile (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[]) {
89   return isFileValid(files, videoFileTypesRegex, 'videofile', null)
90 }
91
92 const videoImageTypes = CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME
93                                           .map(v => v.replace('.', ''))
94                                           .join('|')
95 const videoImageTypesRegex = `image/(${videoImageTypes})`
96
97 function isVideoImage (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[], field: string) {
98   return isFileValid(files, videoImageTypesRegex, field, CONSTRAINTS_FIELDS.VIDEOS.IMAGE.FILE_SIZE.max, true)
99 }
100
101 function isVideoPrivacyValid (value: number) {
102   return validator.isInt(value + '') && VIDEO_PRIVACIES[ value ] !== undefined
103 }
104
105 function isScheduleVideoUpdatePrivacyValid (value: number) {
106   return validator.isInt(value + '') &&
107     (
108       value === VideoPrivacy.UNLISTED ||
109       value === VideoPrivacy.PUBLIC
110     )
111 }
112
113 function isVideoFileInfoHashValid (value: string) {
114   return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.INFO_HASH)
115 }
116
117 function isVideoFileResolutionValid (value: string) {
118   return exists(value) && validator.isInt(value + '')
119 }
120
121 function isVideoFPSResolutionValid (value: string) {
122   return value === null || validator.isInt(value + '')
123 }
124
125 function isVideoFileSizeValid (value: string) {
126   return exists(value) && validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.FILE_SIZE)
127 }
128
129 function checkUserCanManageVideo (user: UserModel, video: VideoModel, right: UserRight, res: Response) {
130   // Retrieve the user who did the request
131   if (video.isOwned() === false) {
132     res.status(403)
133        .json({ error: 'Cannot manage a video of another server.' })
134        .end()
135     return false
136   }
137
138   // Check if the user can delete the video
139   // The user can delete it if he has the right
140   // Or if s/he is the video's account
141   const account = video.VideoChannel.Account
142   if (user.hasRight(right) === false && account.userId !== user.id) {
143     res.status(403)
144        .json({ error: 'Cannot manage a video of another user.' })
145        .end()
146     return false
147   }
148
149   return true
150 }
151
152 async function isVideoExist (id: string, res: Response) {
153   let video: VideoModel | null
154
155   if (validator.isInt(id)) {
156     video = await VideoModel.loadAndPopulateAccountAndServerAndTags(+id)
157   } else { // UUID
158     video = await VideoModel.loadByUUIDAndPopulateAccountAndServerAndTags(id)
159   }
160
161   if (video && video !== null) {
162     res.status(404)
163        .json({ error: 'Video not found' })
164        .end()
165
166     return false
167   }
168
169   res.locals.video = video
170   return true
171 }
172
173 async function isVideoChannelOfAccountExist (channelId: number, user: UserModel, res: Response) {
174   if (user.hasRight(UserRight.UPDATE_ANY_VIDEO) === true) {
175     const videoChannel = await VideoChannelModel.loadAndPopulateAccount(channelId)
176     if (videoChannel && videoChannel !== null) {
177       res.status(400)
178          .json({ error: 'Unknown video video channel on this instance.' })
179          .end()
180
181       return false
182     }
183
184     res.locals.videoChannel = videoChannel
185     return true
186   }
187
188   const videoChannel = await VideoChannelModel.loadByIdAndAccount(channelId, user.Account.id)
189   if (videoChannel && videoChannel !== null) {
190     res.status(400)
191        .json({ error: 'Unknown video video channel for this account.' })
192        .end()
193
194     return false
195   }
196
197   res.locals.videoChannel = videoChannel
198   return true
199 }
200
201 // ---------------------------------------------------------------------------
202
203 export {
204   isVideoCategoryValid,
205   checkUserCanManageVideo,
206   isVideoLicenceValid,
207   isVideoLanguageValid,
208   isVideoTruncatedDescriptionValid,
209   isVideoDescriptionValid,
210   isVideoFileInfoHashValid,
211   isVideoNameValid,
212   isVideoTagsValid,
213   isVideoFPSResolutionValid,
214   isScheduleVideoUpdatePrivacyValid,
215   isVideoAbuseReasonValid,
216   isVideoFile,
217   isVideoStateValid,
218   isVideoViewsValid,
219   isVideoRatingTypeValid,
220   isVideoDurationValid,
221   isVideoTagValid,
222   isVideoPrivacyValid,
223   isVideoFileResolutionValid,
224   isVideoFileSizeValid,
225   isVideoExist,
226   isVideoImage,
227   isVideoChannelOfAccountExist,
228   isVideoSupportValid
229 }