1 import * as express from 'express'
2 import 'express-validator'
3 import { body, param, query } from 'express-validator/check'
4 import { UserRight, VideoPrivacy } from '../../../shared'
5 import { isBooleanValid, isIdOrUUIDValid, isIdValid, isUUIDValid, toIntOrNull, toValueOrNull } from '../../helpers/custom-validators/misc'
7 isVideoAbuseReasonValid,
9 isVideoChannelOfAccountExist,
10 isVideoDescriptionValid,
18 isVideoRatingTypeValid,
21 } from '../../helpers/custom-validators/videos'
22 import { getDurationFromVideoFile } from '../../helpers/ffmpeg-utils'
23 import { logger } from '../../helpers/logger'
24 import { CONSTRAINTS_FIELDS } from '../../initializers'
25 import { UserModel } from '../../models/account/user'
26 import { VideoModel } from '../../models/video/video'
27 import { VideoShareModel } from '../../models/video/video-share'
28 import { authenticate } from '../oauth'
29 import { areValidationErrors } from './utils'
31 const videosAddValidator = [
32 body('videofile').custom((value, { req }) => isVideoFile(req.files)).withMessage(
33 'This file is not supported. Please, make sure it is of the following type : '
34 + CONSTRAINTS_FIELDS.VIDEOS.EXTNAME.join(', ')
36 body('thumbnailfile').custom((value, { req }) => isVideoImage(req.files, 'thumbnailfile')).withMessage(
37 'This thumbnail file is not supported. Please, make sure it is of the following type : '
38 + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ')
40 body('previewfile').custom((value, { req }) => isVideoImage(req.files, 'previewfile')).withMessage(
41 'This preview file is not supported. Please, make sure it is of the following type : '
42 + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ')
44 body('name').custom(isVideoNameValid).withMessage('Should have a valid name'),
47 .customSanitizer(toIntOrNull)
48 .custom(isVideoCategoryValid).withMessage('Should have a valid category'),
51 .customSanitizer(toIntOrNull)
52 .custom(isVideoLicenceValid).withMessage('Should have a valid licence'),
55 .customSanitizer(toValueOrNull)
56 .custom(isVideoLanguageValid).withMessage('Should have a valid language'),
59 .custom(isBooleanValid).withMessage('Should have a valid NSFW attribute'),
62 .customSanitizer(toValueOrNull)
63 .custom(isVideoDescriptionValid).withMessage('Should have a valid description'),
66 .customSanitizer(toValueOrNull)
67 .custom(isVideoSupportValid).withMessage('Should have a valid support text'),
70 .customSanitizer(toValueOrNull)
71 .custom(isVideoTagsValid).withMessage('Should have correct tags'),
72 body('commentsEnabled')
74 .custom(isBooleanValid).withMessage('Should have comments enabled boolean'),
78 .custom(isVideoPrivacyValid).withMessage('Should have correct video privacy'),
82 .withMessage('Should have correct video channel id'),
84 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
85 logger.debug('Checking videosAdd parameters', { parameters: req.body, files: req.files })
87 if (areValidationErrors(req, res)) return
88 if (areErrorsInVideoImageFiles(req, res)) return
90 const videoFile: Express.Multer.File = req.files['videofile'][0]
91 const user = res.locals.oauth.token.User
93 if (!await isVideoChannelOfAccountExist(req.body.channelId, user.Account.id, res)) return
95 const isAble = await user.isAbleToUploadVideo(videoFile)
96 if (isAble === false) {
98 .json({ error: 'The user video quota is exceeded with this video.' })
107 duration = await getDurationFromVideoFile(videoFile.path)
109 logger.error('Invalid input file in videosAddValidator.', { err })
111 .json({ error: 'Invalid input file.' })
117 videoFile['duration'] = duration
123 const videosUpdateValidator = [
124 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
125 body('thumbnailfile').custom((value, { req }) => isVideoImage(req.files, 'thumbnailfile')).withMessage(
126 'This thumbnail file is not supported. Please, make sure it is of the following type : '
127 + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ')
129 body('previewfile').custom((value, { req }) => isVideoImage(req.files, 'previewfile')).withMessage(
130 'This preview file is not supported. Please, make sure it is of the following type : '
131 + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ')
135 .custom(isVideoNameValid).withMessage('Should have a valid name'),
138 .customSanitizer(toIntOrNull)
139 .custom(isVideoCategoryValid).withMessage('Should have a valid category'),
142 .customSanitizer(toIntOrNull)
143 .custom(isVideoLicenceValid).withMessage('Should have a valid licence'),
146 .customSanitizer(toValueOrNull)
147 .custom(isVideoLanguageValid).withMessage('Should have a valid language'),
151 .custom(isBooleanValid).withMessage('Should have a valid NSFW attribute'),
155 .custom(isVideoPrivacyValid).withMessage('Should have correct video privacy'),
158 .customSanitizer(toValueOrNull)
159 .custom(isVideoDescriptionValid).withMessage('Should have a valid description'),
162 .customSanitizer(toValueOrNull)
163 .custom(isVideoSupportValid).withMessage('Should have a valid support text'),
166 .customSanitizer(toValueOrNull)
167 .custom(isVideoTagsValid).withMessage('Should have correct tags'),
168 body('commentsEnabled')
171 .custom(isBooleanValid).withMessage('Should have comments enabled boolean'),
175 .custom(isIdValid).withMessage('Should have correct video channel id'),
177 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
178 logger.debug('Checking videosUpdate parameters', { parameters: req.body })
180 if (areValidationErrors(req, res)) return
181 if (areErrorsInVideoImageFiles(req, res)) return
182 if (!await isVideoExist(req.params.id, res)) return
184 const video = res.locals.video
186 // Check if the user who did the request is able to update the video
187 const user = res.locals.oauth.token.User
188 if (!checkUserCanManageVideo(user, res.locals.video, UserRight.UPDATE_ANY_VIDEO, res)) return
190 if (video.privacy !== VideoPrivacy.PRIVATE && req.body.privacy === VideoPrivacy.PRIVATE) {
191 return res.status(409)
192 .json({ error: 'Cannot set "private" a video that was not private anymore.' })
196 if (req.body.channelId && !await isVideoChannelOfAccountExist(req.body.channelId, user.Account.id, res)) return
202 const videosGetValidator = [
203 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
205 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
206 logger.debug('Checking videosGet parameters', { parameters: req.params })
208 if (areValidationErrors(req, res)) return
209 if (!await isVideoExist(req.params.id, res)) return
211 const video = res.locals.video
213 // Video is public, anyone can access it
214 if (video.privacy === VideoPrivacy.PUBLIC) return next()
216 // Video is unlisted, check we used the uuid to fetch it
217 if (video.privacy === VideoPrivacy.UNLISTED) {
218 if (isUUIDValid(req.params.id)) return next()
220 // Don't leak this unlisted video
221 return res.status(404).end()
224 // Video is private, check the user
225 authenticate(req, res, () => {
226 if (video.VideoChannel.Account.userId !== res.locals.oauth.token.User.id) {
227 return res.status(403)
228 .json({ error: 'Cannot get this private video of another user' })
237 const videosRemoveValidator = [
238 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
240 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
241 logger.debug('Checking videosRemove parameters', { parameters: req.params })
243 if (areValidationErrors(req, res)) return
244 if (!await isVideoExist(req.params.id, res)) return
246 // Check if the user who did the request is able to delete the video
247 if (!checkUserCanManageVideo(res.locals.oauth.token.User, res.locals.video, UserRight.REMOVE_ANY_VIDEO, res)) return
253 const videosSearchValidator = [
254 query('search').not().isEmpty().withMessage('Should have a valid search'),
256 (req: express.Request, res: express.Response, next: express.NextFunction) => {
257 logger.debug('Checking videosSearch parameters', { parameters: req.params })
259 if (areValidationErrors(req, res)) return
265 const videoAbuseReportValidator = [
266 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
267 body('reason').custom(isVideoAbuseReasonValid).withMessage('Should have a valid reason'),
269 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
270 logger.debug('Checking videoAbuseReport parameters', { parameters: req.body })
272 if (areValidationErrors(req, res)) return
273 if (!await isVideoExist(req.params.id, res)) return
279 const videoRateValidator = [
280 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
281 body('rating').custom(isVideoRatingTypeValid).withMessage('Should have a valid rate type'),
283 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
284 logger.debug('Checking videoRate parameters', { parameters: req.body })
286 if (areValidationErrors(req, res)) return
287 if (!await isVideoExist(req.params.id, res)) return
293 const videosShareValidator = [
294 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
295 param('accountId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid account id'),
297 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
298 logger.debug('Checking videoShare parameters', { parameters: req.params })
300 if (areValidationErrors(req, res)) return
301 if (!await isVideoExist(req.params.id, res)) return
303 const share = await VideoShareModel.load(req.params.accountId, res.locals.video.id, undefined)
305 return res.status(404)
309 res.locals.videoShare = share
314 // ---------------------------------------------------------------------------
318 videosUpdateValidator,
320 videosRemoveValidator,
321 videosSearchValidator,
322 videosShareValidator,
324 videoAbuseReportValidator,
329 // ---------------------------------------------------------------------------
331 function checkUserCanManageVideo (user: UserModel, video: VideoModel, right: UserRight, res: express.Response) {
332 // Retrieve the user who did the request
333 if (video.isOwned() === false) {
335 .json({ error: 'Cannot remove video of another server, blacklist it' })
340 // Check if the user can delete the video
341 // The user can delete it if he has the right
342 // Or if s/he is the video's account
343 const account = video.VideoChannel.Account
344 if (user.hasRight(right) === false && account.userId !== user.id) {
346 .json({ error: 'Cannot remove video of another user' })
354 function areErrorsInVideoImageFiles (req: express.Request, res: express.Response) {
355 // Files are optional
356 if (!req.files) return false
358 for (const imageField of [ 'thumbnail', 'preview' ]) {
359 if (!req.files[ imageField ]) continue
361 const imageFile = req.files[ imageField ][ 0 ] as Express.Multer.File
362 if (imageFile.size > CONSTRAINTS_FIELDS.VIDEOS.IMAGE.FILE_SIZE.max) {
364 .send({ error: `The size of the ${imageField} is too big (>${CONSTRAINTS_FIELDS.VIDEOS.IMAGE.FILE_SIZE.max}).` })