]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/middlewares/validators/videos/videos.ts
Cleanup useless express validator messages
[github/Chocobozzz/PeerTube.git] / server / middlewares / validators / videos / videos.ts
CommitLineData
41fb13c3 1import express from 'express'
f6d6e7f8 2import { body, header, param, query, ValidationChain } from 'express-validator'
276250f0 3import { isTestInstance } from '@server/helpers/core-utils'
f6d6e7f8 4import { getResumableUploadPath } from '@server/helpers/upload'
276250f0 5import { Redis } from '@server/lib/redis'
e6abf95e 6import { getServerActor } from '@server/models/application/application'
f8360396 7import { ExpressPromiseHandler } from '@server/types/express-handler'
71d4af1e 8import { MUserAccountId, MVideoFullLight } from '@server/types/models'
527a52ac 9import { getAllPrivacies } from '@shared/core-utils'
ff9d43f6 10import { HttpStatusCode, ServerErrorCode, UserRight, VideoInclude } from '@shared/models'
b60e5f38 11import {
37024082 12 exists,
2baea0c7
C
13 isBooleanValid,
14 isDateValid,
c729caf6 15 isFileValid,
2baea0c7 16 isIdValid,
1cd3facc 17 toArray,
c8861d5d 18 toBooleanOrNull,
2baea0c7
C
19 toIntOrNull,
20 toValueOrNull
6e46de09 21} from '../../../helpers/custom-validators/misc'
1fd61899 22import { isBooleanBothQueryValid, isNumberArray, isStringArray } from '../../../helpers/custom-validators/search'
2baea0c7 23import {
c729caf6 24 areVideoTagsValid,
2baea0c7 25 isScheduleVideoUpdatePrivacyValid,
ac81d1a0
C
26 isVideoCategoryValid,
27 isVideoDescriptionValid,
f2eb23cd
RK
28 isVideoFileMimeTypeValid,
29 isVideoFileSizeValid,
1cd3facc 30 isVideoFilterValid,
c729caf6 31 isVideoImageValid,
2760b454 32 isVideoIncludeValid,
ac81d1a0
C
33 isVideoLanguageValid,
34 isVideoLicenceValid,
35 isVideoNameValid,
fd8710b8 36 isVideoOriginallyPublishedAtValid,
ac81d1a0 37 isVideoPrivacyValid,
c729caf6 38 isVideoSupportValid
6e46de09 39} from '../../../helpers/custom-validators/videos'
e6abf95e 40import { cleanUpReqFiles } from '../../../helpers/express-utils'
c729caf6 41import { getVideoStreamDuration } from '../../../helpers/ffmpeg'
6e46de09 42import { logger } from '../../../helpers/logger'
f6d6e7f8 43import { deleteFileAndCatch } from '../../../helpers/utils'
0283eaac 44import { getVideoWithAttributes } from '../../../helpers/video'
e6abf95e
C
45import { CONFIG } from '../../../initializers/config'
46import { CONSTRAINTS_FIELDS, OVERVIEWS } from '../../../initializers/constants'
47import { isLocalVideoAccepted } from '../../../lib/moderation'
48import { Hooks } from '../../../lib/plugins/hooks'
e6abf95e 49import { VideoModel } from '../../../models/video/video'
10363c74
C
50import {
51 areValidationErrors,
ff9d43f6 52 checkCanSeeVideo,
10363c74 53 checkUserCanManageVideo,
c729caf6 54 checkUserQuota,
10363c74
C
55 doesVideoChannelOfAccountExist,
56 doesVideoExist,
d4a8e7a6
C
57 doesVideoFileOfVideoExist,
58 isValidVideoIdParam
10363c74 59} from '../shared'
34ca3b52 60
f6d6e7f8 61const videosAddLegacyValidator = getCommonVideoEditAttributes().concat([
0c237b19 62 body('videofile')
c729caf6 63 .custom((_, { req }) => isFileValid({ files: req.files, field: 'videofile', mimeTypeRegex: null, maxSize: null }))
f2eb23cd
RK
64 .withMessage('Should have a file'),
65 body('name')
0221f8c9 66 .trim()
7dab0bd6
RK
67 .custom(isVideoNameValid).withMessage(
68 `Should have a video name between ${CONSTRAINTS_FIELDS.VIDEOS.NAME.min} and ${CONSTRAINTS_FIELDS.VIDEOS.NAME.max} characters long`
69 ),
0f320037 70 body('channelId')
c8861d5d 71 .customSanitizer(toIntOrNull)
396f6f01 72 .custom(isIdValid),
b60e5f38 73
a2431b7d 74 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
b60e5f38
C
75 logger.debug('Checking videosAdd parameters', { parameters: req.body, files: req.files })
76
cf7a61b5 77 if (areValidationErrors(req, res)) return cleanUpReqFiles(req)
a2431b7d 78
f6d6e7f8 79 const videoFile: express.VideoUploadFile = req.files['videofile'][0]
a2431b7d 80 const user = res.locals.oauth.token.User
b60e5f38 81
f6d6e7f8 82 if (!await commonVideoChecksPass({ req, res, user, videoFileSize: videoFile.size, files: req.files })) {
f2eb23cd
RK
83 return cleanUpReqFiles(req)
84 }
85
f6d6e7f8 86 try {
87 if (!videoFile.duration) await addDurationToVideo(videoFile)
88 } catch (err) {
89 logger.error('Invalid input file in videosAddLegacyValidator.', { err })
f2eb23cd 90
76148b27
RK
91 res.fail({
92 status: HttpStatusCode.UNPROCESSABLE_ENTITY_422,
93 message: 'Video file unreadable.'
94 })
f2eb23cd
RK
95 return cleanUpReqFiles(req)
96 }
97
f6d6e7f8 98 if (!await isVideoAccepted(req, res, videoFile)) return cleanUpReqFiles(req)
a2431b7d 99
f6d6e7f8 100 return next()
101 }
102])
103
104/**
105 * Gets called after the last PUT request
106 */
107const videosAddResumableValidator = [
108 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
109 const user = res.locals.oauth.token.User
f6d6e7f8 110 const body: express.CustomUploadXFile<express.UploadXFileMetadata> = req.body
020d3d3d 111 const file = { ...body, duration: undefined, path: getResumableUploadPath(body.name), filename: body.metadata.filename }
f6d6e7f8 112 const cleanup = () => deleteFileAndCatch(file.path)
a2431b7d 113
276250f0
RK
114 const uploadId = req.query.upload_id
115 const sessionExists = await Redis.Instance.doesUploadSessionExist(uploadId)
116
117 if (sessionExists) {
118 const sessionResponse = await Redis.Instance.getUploadSession(uploadId)
119
120 if (!sessionResponse) {
121 res.setHeader('Retry-After', 300) // ask to retry after 5 min, knowing the upload_id is kept for up to 15 min after completion
122
123 return res.fail({
124 status: HttpStatusCode.SERVICE_UNAVAILABLE_503,
125 message: 'The upload is already being processed'
126 })
127 }
128
129 if (isTestInstance()) {
130 res.setHeader('x-resumable-upload-cached', 'true')
131 }
132
133 return res.json(sessionResponse)
134 }
135
136 await Redis.Instance.setUploadSession(uploadId)
137
f6d6e7f8 138 if (!await doesVideoChannelOfAccountExist(file.metadata.channelId, user, res)) return cleanup()
a2431b7d
C
139
140 try {
f6d6e7f8 141 if (!file.duration) await addDurationToVideo(file)
a2431b7d 142 } catch (err) {
f6d6e7f8 143 logger.error('Invalid input file in videosAddResumableValidator.', { err })
a2431b7d 144
76148b27
RK
145 res.fail({
146 status: HttpStatusCode.UNPROCESSABLE_ENTITY_422,
147 message: 'Video file unreadable.'
148 })
f6d6e7f8 149 return cleanup()
a2431b7d
C
150 }
151
f6d6e7f8 152 if (!await isVideoAccepted(req, res, file)) return cleanup()
b4055e1c 153
2e401e85 154 res.locals.videoFileResumable = { ...file, originalname: file.filename }
f6d6e7f8 155
156 return next()
157 }
158]
159
160/**
161 * File is created in POST initialisation, and its body is saved as a 'metadata' field is saved by uploadx for later use.
162 * see https://github.com/kukhariev/node-uploadx/blob/dc9fb4a8ac5a6f481902588e93062f591ec6ef03/packages/core/src/handlers/uploadx.ts
163 *
164 * Uploadx doesn't use next() until the upload completes, so this middleware has to be placed before uploadx
165 * see https://github.com/kukhariev/node-uploadx/blob/dc9fb4a8ac5a6f481902588e93062f591ec6ef03/packages/core/src/handlers/base-handler.ts
166 *
167 */
168const videosAddResumableInitValidator = getCommonVideoEditAttributes().concat([
169 body('filename')
396f6f01 170 .exists(),
f6d6e7f8 171 body('name')
172 .trim()
7dab0bd6
RK
173 .custom(isVideoNameValid).withMessage(
174 `Should have a video name between ${CONSTRAINTS_FIELDS.VIDEOS.NAME.min} and ${CONSTRAINTS_FIELDS.VIDEOS.NAME.max} characters long`
175 ),
f6d6e7f8 176 body('channelId')
177 .customSanitizer(toIntOrNull)
396f6f01 178 .custom(isIdValid),
f6d6e7f8 179
180 header('x-upload-content-length')
181 .isNumeric()
182 .exists()
183 .withMessage('Should specify the file length'),
184 header('x-upload-content-type')
185 .isString()
186 .exists()
187 .withMessage('Should specify the file mimetype'),
188
189 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
190 const videoFileMetadata = {
191 mimetype: req.headers['x-upload-content-type'] as string,
192 size: +req.headers['x-upload-content-length'],
45353742 193 originalname: req.body.filename
f6d6e7f8 194 }
195
196 const user = res.locals.oauth.token.User
197 const cleanup = () => cleanUpReqFiles(req)
198
199 logger.debug('Checking videosAddResumableInitValidator parameters and headers', {
200 parameters: req.body,
201 headers: req.headers,
202 files: req.files
203 })
204
205 if (areValidationErrors(req, res)) return cleanup()
206
207 const files = { videofile: [ videoFileMetadata ] }
208 if (!await commonVideoChecksPass({ req, res, user, videoFileSize: videoFileMetadata.size, files })) return cleanup()
209
210 // multer required unsetting the Content-Type, now we can set it for node-uploadx
211 req.headers['content-type'] = 'application/json; charset=utf-8'
212 // place previewfile in metadata so that uploadx saves it in .META
45570e93 213 if (req.files?.['previewfile']) req.body.previewfile = req.files['previewfile']
a2431b7d
C
214
215 return next()
b60e5f38 216 }
a920fef1 217])
b60e5f38 218
418d092a 219const videosUpdateValidator = getCommonVideoEditAttributes().concat([
d4a8e7a6
C
220 isValidVideoIdParam('id'),
221
360329cc
C
222 body('name')
223 .optional()
0221f8c9 224 .trim()
7dab0bd6
RK
225 .custom(isVideoNameValid).withMessage(
226 `Should have a video name between ${CONSTRAINTS_FIELDS.VIDEOS.NAME.min} and ${CONSTRAINTS_FIELDS.VIDEOS.NAME.max} characters long`
227 ),
0f320037
C
228 body('channelId')
229 .optional()
c8861d5d 230 .customSanitizer(toIntOrNull)
396f6f01 231 .custom(isIdValid),
b60e5f38 232
a2431b7d 233 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
b60e5f38
C
234 logger.debug('Checking videosUpdate parameters', { parameters: req.body })
235
cf7a61b5
C
236 if (areValidationErrors(req, res)) return cleanUpReqFiles(req)
237 if (areErrorsInScheduleUpdate(req, res)) return cleanUpReqFiles(req)
0f6acda1 238 if (!await doesVideoExist(req.params.id, res)) return cleanUpReqFiles(req)
a2431b7d 239
6221f311 240 // Check if the user who did the request is able to update the video
0f320037 241 const user = res.locals.oauth.token.User
453e83ea 242 if (!checkUserCanManageVideo(user, res.locals.videoAll, UserRight.UPDATE_ANY_VIDEO, res)) return cleanUpReqFiles(req)
a2431b7d 243
0f6acda1 244 if (req.body.channelId && !await doesVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req)
0f320037 245
a2431b7d 246 return next()
b60e5f38 247 }
a920fef1 248])
c173e565 249
8d427346 250async function checkVideoFollowConstraints (req: express.Request, res: express.Response, next: express.NextFunction) {
0283eaac 251 const video = getVideoWithAttributes(res)
8d427346
C
252
253 // Anybody can watch local videos
254 if (video.isOwned() === true) return next()
255
256 // Logged user
257 if (res.locals.oauth) {
258 // Users can search or watch remote videos
259 if (CONFIG.SEARCH.REMOTE_URI.USERS === true) return next()
260 }
261
262 // Anybody can search or watch remote videos
263 if (CONFIG.SEARCH.REMOTE_URI.ANONYMOUS === true) return next()
264
265 // Check our instance follows an actor that shared this video
266 const serverActor = await getServerActor()
267 if (await VideoModel.checkVideoHasInstanceFollow(video.id, serverActor.id) === true) return next()
268
76148b27
RK
269 return res.fail({
270 status: HttpStatusCode.FORBIDDEN_403,
81628e50 271 message: 'Cannot get this video regarding follow constraints',
3866ea02 272 type: ServerErrorCode.DOES_NOT_RESPECT_FOLLOW_CONSTRAINTS,
76148b27
RK
273 data: {
274 originUrl: video.url
275 }
276 })
8d427346
C
277}
278
7eba5e1f 279const videosCustomGetValidator = (
71d4af1e 280 fetchType: 'for-api' | 'all' | 'only-video' | 'only-immutable-attributes',
7eba5e1f
C
281 authenticateInQuery = false
282) => {
96f29c0f 283 return [
d4a8e7a6 284 isValidVideoIdParam('id'),
7b1f49de 285
96f29c0f
C
286 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
287 logger.debug('Checking videosGet parameters', { parameters: req.params })
11474c3c 288
96f29c0f 289 if (areValidationErrors(req, res)) return
0f6acda1 290 if (!await doesVideoExist(req.params.id, res, fetchType)) return
191764f3 291
943e5193
C
292 // Controllers does not need to check video rights
293 if (fetchType === 'only-immutable-attributes') return next()
294
71d4af1e 295 const video = getVideoWithAttributes(res) as MVideoFullLight
191764f3 296
ff9d43f6 297 if (!await checkCanSeeVideo({ req, res, video, paramId: req.params.id, authenticateInQuery })) return
8d427346 298
ff9d43f6 299 return next()
81ebea48 300 }
96f29c0f
C
301 ]
302}
303
304const videosGetValidator = videosCustomGetValidator('all')
eccf70f0 305const videosDownloadValidator = videosCustomGetValidator('all', true)
34ca3b52 306
8319d6ae 307const videoFileMetadataGetValidator = getCommonVideoEditAttributes().concat([
d4a8e7a6
C
308 isValidVideoIdParam('id'),
309
310 param('videoFileId')
311 .custom(isIdValid).not().isEmpty().withMessage('Should have a valid videoFileId'),
8319d6ae
RK
312
313 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
314 logger.debug('Checking videoFileMetadataGet parameters', { parameters: req.params })
315
316 if (areValidationErrors(req, res)) return
317 if (!await doesVideoFileOfVideoExist(+req.params.videoFileId, req.params.id, res)) return
318
319 return next()
320 }
321])
322
b60e5f38 323const videosRemoveValidator = [
d4a8e7a6 324 isValidVideoIdParam('id'),
34ca3b52 325
a2431b7d 326 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
b60e5f38 327 logger.debug('Checking videosRemove parameters', { parameters: req.params })
34ca3b52 328
a2431b7d 329 if (areValidationErrors(req, res)) return
0f6acda1 330 if (!await doesVideoExist(req.params.id, res)) return
a2431b7d
C
331
332 // Check if the user who did the request is able to delete the video
453e83ea 333 if (!checkUserCanManageVideo(res.locals.oauth.token.User, res.locals.videoAll, UserRight.REMOVE_ANY_VIDEO, res)) return
a2431b7d
C
334
335 return next()
b60e5f38
C
336 }
337]
34ca3b52 338
764a9657
C
339const videosOverviewValidator = [
340 query('page')
341 .optional()
396f6f01 342 .isInt({ min: 1, max: OVERVIEWS.VIDEOS.SAMPLES_COUNT }),
764a9657
C
343
344 (req: express.Request, res: express.Response, next: express.NextFunction) => {
345 if (areValidationErrors(req, res)) return
346
347 return next()
348 }
349]
350
418d092a 351function getCommonVideoEditAttributes () {
a920fef1
C
352 return [
353 body('thumbnailfile')
c729caf6 354 .custom((value, { req }) => isVideoImageValid(req.files, 'thumbnailfile')).withMessage(
a1587156
C
355 'This thumbnail file is not supported or too large. Please, make sure it is of the following type: ' +
356 CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ')
357 ),
a920fef1 358 body('previewfile')
c729caf6 359 .custom((value, { req }) => isVideoImageValid(req.files, 'previewfile')).withMessage(
a1587156
C
360 'This preview file is not supported or too large. Please, make sure it is of the following type: ' +
361 CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ')
362 ),
a920fef1
C
363
364 body('category')
365 .optional()
366 .customSanitizer(toIntOrNull)
396f6f01 367 .custom(isVideoCategoryValid),
a920fef1
C
368 body('licence')
369 .optional()
370 .customSanitizer(toIntOrNull)
396f6f01 371 .custom(isVideoLicenceValid),
a920fef1
C
372 body('language')
373 .optional()
374 .customSanitizer(toValueOrNull)
396f6f01 375 .custom(isVideoLanguageValid),
a920fef1
C
376 body('nsfw')
377 .optional()
c8861d5d 378 .customSanitizer(toBooleanOrNull)
396f6f01 379 .custom(isBooleanValid).withMessage('Should have a valid nsfw boolean'),
a920fef1
C
380 body('waitTranscoding')
381 .optional()
c8861d5d 382 .customSanitizer(toBooleanOrNull)
396f6f01 383 .custom(isBooleanValid).withMessage('Should have a valid waitTranscoding boolean'),
a920fef1
C
384 body('privacy')
385 .optional()
c8861d5d 386 .customSanitizer(toValueOrNull)
396f6f01 387 .custom(isVideoPrivacyValid),
a920fef1
C
388 body('description')
389 .optional()
390 .customSanitizer(toValueOrNull)
396f6f01 391 .custom(isVideoDescriptionValid),
a920fef1
C
392 body('support')
393 .optional()
394 .customSanitizer(toValueOrNull)
396f6f01 395 .custom(isVideoSupportValid),
a920fef1
C
396 body('tags')
397 .optional()
398 .customSanitizer(toValueOrNull)
c729caf6 399 .custom(areVideoTagsValid)
7dab0bd6
RK
400 .withMessage(
401 `Should have an array of up to ${CONSTRAINTS_FIELDS.VIDEOS.TAGS.max} tags between ` +
402 `${CONSTRAINTS_FIELDS.VIDEOS.TAG.min} and ${CONSTRAINTS_FIELDS.VIDEOS.TAG.max} characters each`
403 ),
a920fef1
C
404 body('commentsEnabled')
405 .optional()
c8861d5d 406 .customSanitizer(toBooleanOrNull)
396f6f01 407 .custom(isBooleanValid).withMessage('Should have commentsEnabled boolean'),
7f2cfe3a 408 body('downloadEnabled')
1e74f19a 409 .optional()
c8861d5d 410 .customSanitizer(toBooleanOrNull)
396f6f01 411 .custom(isBooleanValid).withMessage('Should have downloadEnabled boolean'),
b718fd22 412 body('originallyPublishedAt')
c8861d5d
C
413 .optional()
414 .customSanitizer(toValueOrNull)
396f6f01 415 .custom(isVideoOriginallyPublishedAtValid),
a920fef1
C
416 body('scheduleUpdate')
417 .optional()
418 .customSanitizer(toValueOrNull),
419 body('scheduleUpdate.updateAt')
420 .optional()
70330f63 421 .custom(isDateValid).withMessage('Should have a schedule update date that conforms to ISO 8601'),
a920fef1
C
422 body('scheduleUpdate.privacy')
423 .optional()
2b65c4e5 424 .customSanitizer(toIntOrNull)
396f6f01 425 .custom(isScheduleVideoUpdatePrivacyValid)
ba5a8d89 426 ] as (ValidationChain | ExpressPromiseHandler)[]
a920fef1 427}
fbad87b0 428
1cd3facc
C
429const commonVideosFiltersValidator = [
430 query('categoryOneOf')
431 .optional()
432 .customSanitizer(toArray)
396f6f01 433 .custom(isNumberArray).withMessage('Should have a valid categoryOneOf array'),
1cd3facc
C
434 query('licenceOneOf')
435 .optional()
436 .customSanitizer(toArray)
396f6f01 437 .custom(isNumberArray).withMessage('Should have a valid licenceOneOf array'),
1cd3facc
C
438 query('languageOneOf')
439 .optional()
440 .customSanitizer(toArray)
396f6f01 441 .custom(isStringArray).withMessage('Should have a valid languageOneOf array'),
527a52ac
C
442 query('privacyOneOf')
443 .optional()
444 .customSanitizer(toArray)
396f6f01 445 .custom(isNumberArray).withMessage('Should have a valid privacyOneOf array'),
1cd3facc
C
446 query('tagsOneOf')
447 .optional()
448 .customSanitizer(toArray)
396f6f01 449 .custom(isStringArray).withMessage('Should have a valid tagsOneOf array'),
1cd3facc
C
450 query('tagsAllOf')
451 .optional()
452 .customSanitizer(toArray)
396f6f01 453 .custom(isStringArray).withMessage('Should have a valid tagsAllOf array'),
1cd3facc
C
454 query('nsfw')
455 .optional()
396f6f01 456 .custom(isBooleanBothQueryValid),
1fd61899
C
457 query('isLive')
458 .optional()
459 .customSanitizer(toBooleanOrNull)
396f6f01 460 .custom(isBooleanValid).withMessage('Should have a valid isLive boolean'),
1cd3facc
C
461 query('filter')
462 .optional()
396f6f01 463 .custom(isVideoFilterValid),
2760b454
C
464 query('include')
465 .optional()
396f6f01 466 .custom(isVideoIncludeValid),
2760b454
C
467 query('isLocal')
468 .optional()
469 .customSanitizer(toBooleanOrNull)
396f6f01 470 .custom(isBooleanValid).withMessage('Should have a valid isLocal boolean'),
d324756e
C
471 query('hasHLSFiles')
472 .optional()
473 .customSanitizer(toBooleanOrNull)
396f6f01 474 .custom(isBooleanValid).withMessage('Should have a valid hasHLSFiles boolean'),
d324756e
C
475 query('hasWebtorrentFiles')
476 .optional()
477 .customSanitizer(toBooleanOrNull)
396f6f01 478 .custom(isBooleanValid).withMessage('Should have a valid hasWebtorrentFiles boolean'),
fe987656
C
479 query('skipCount')
480 .optional()
481 .customSanitizer(toBooleanOrNull)
396f6f01 482 .custom(isBooleanValid).withMessage('Should have a valid skipCount boolean'),
37024082
RK
483 query('search')
484 .optional()
396f6f01 485 .custom(exists),
1cd3facc
C
486
487 (req: express.Request, res: express.Response, next: express.NextFunction) => {
488 logger.debug('Checking commons video filters query', { parameters: req.query })
489
490 if (areValidationErrors(req, res)) return
491
2760b454
C
492 // FIXME: deprecated in 4.0, to remove
493 {
494 if (req.query.filter === 'all-local') {
527a52ac 495 req.query.include = VideoInclude.NOT_PUBLISHED_STATE
2760b454 496 req.query.isLocal = true
527a52ac 497 req.query.privacyOneOf = getAllPrivacies()
2760b454 498 } else if (req.query.filter === 'all') {
527a52ac
C
499 req.query.include = VideoInclude.NOT_PUBLISHED_STATE
500 req.query.privacyOneOf = getAllPrivacies()
2760b454
C
501 } else if (req.query.filter === 'local') {
502 req.query.isLocal = true
503 }
504
505 req.query.filter = undefined
506 }
507
508 const user = res.locals.oauth?.token.User
509
d324756e 510 if ((!user || user.hasRight(UserRight.SEE_ALL_VIDEOS) !== true)) {
527a52ac 511 if (req.query.include || req.query.privacyOneOf) {
d324756e
C
512 return res.fail({
513 status: HttpStatusCode.UNAUTHORIZED_401,
514 message: 'You are not allowed to see all videos.'
515 })
516 }
1cd3facc
C
517 }
518
519 return next()
520 }
521]
522
fbad87b0
C
523// ---------------------------------------------------------------------------
524
525export {
f6d6e7f8 526 videosAddLegacyValidator,
527 videosAddResumableValidator,
528 videosAddResumableInitValidator,
529
fbad87b0
C
530 videosUpdateValidator,
531 videosGetValidator,
8319d6ae 532 videoFileMetadataGetValidator,
eccf70f0 533 videosDownloadValidator,
8d427346 534 checkVideoFollowConstraints,
96f29c0f 535 videosCustomGetValidator,
fbad87b0 536 videosRemoveValidator,
fbad87b0 537
418d092a 538 getCommonVideoEditAttributes,
1cd3facc 539
764a9657
C
540 commonVideosFiltersValidator,
541
542 videosOverviewValidator
fbad87b0
C
543}
544
545// ---------------------------------------------------------------------------
546
547function areErrorsInScheduleUpdate (req: express.Request, res: express.Response) {
548 if (req.body.scheduleUpdate) {
549 if (!req.body.scheduleUpdate.updateAt) {
7373507f
C
550 logger.warn('Invalid parameters: scheduleUpdate.updateAt is mandatory.')
551
76148b27 552 res.fail({ message: 'Schedule update at is mandatory.' })
fbad87b0
C
553 return true
554 }
555 }
556
557 return false
558}
b4055e1c 559
f6d6e7f8 560async function commonVideoChecksPass (parameters: {
561 req: express.Request
562 res: express.Response
563 user: MUserAccountId
564 videoFileSize: number
565 files: express.UploadFilesForCheck
566}): Promise<boolean> {
567 const { req, res, user, videoFileSize, files } = parameters
568
569 if (areErrorsInScheduleUpdate(req, res)) return false
570
571 if (!await doesVideoChannelOfAccountExist(req.body.channelId, user, res)) return false
572
573 if (!isVideoFileMimeTypeValid(files)) {
76148b27
RK
574 res.fail({
575 status: HttpStatusCode.UNSUPPORTED_MEDIA_TYPE_415,
576 message: 'This file is not supported. Please, make sure it is of the following type: ' +
577 CONSTRAINTS_FIELDS.VIDEOS.EXTNAME.join(', ')
578 })
f6d6e7f8 579 return false
580 }
581
582 if (!isVideoFileSizeValid(videoFileSize.toString())) {
76148b27
RK
583 res.fail({
584 status: HttpStatusCode.PAYLOAD_TOO_LARGE_413,
c756bae0
RK
585 message: 'This file is too large. It exceeds the maximum file size authorized.',
586 type: ServerErrorCode.MAX_FILE_SIZE_REACHED
76148b27 587 })
f6d6e7f8 588 return false
589 }
590
c729caf6 591 if (await checkUserQuota(user, videoFileSize, res) === false) return false
f6d6e7f8 592
593 return true
594}
595
596export async function isVideoAccepted (
597 req: express.Request,
598 res: express.Response,
599 videoFile: express.VideoUploadFile
600) {
b4055e1c
C
601 // Check we accept this video
602 const acceptParameters = {
603 videoBody: req.body,
604 videoFile,
605 user: res.locals.oauth.token.User
606 }
89cd1275
C
607 const acceptedResult = await Hooks.wrapFun(
608 isLocalVideoAccepted,
609 acceptParameters,
b4055e1c
C
610 'filter:api.video.upload.accept.result'
611 )
612
613 if (!acceptedResult || acceptedResult.accepted !== true) {
614 logger.info('Refused local video.', { acceptedResult, acceptParameters })
76148b27
RK
615 res.fail({
616 status: HttpStatusCode.FORBIDDEN_403,
617 message: acceptedResult.errorMessage || 'Refused local video'
618 })
b4055e1c
C
619 return false
620 }
621
622 return true
623}
f6d6e7f8 624
625async function addDurationToVideo (videoFile: { path: string, duration?: number }) {
f692fc8d 626 const duration = await getVideoStreamDuration(videoFile.path)
f6d6e7f8 627
f692fc8d
C
628 // FFmpeg may not be able to guess video duration
629 // For example with m2v files: https://trac.ffmpeg.org/ticket/9726#comment:2
630 if (isNaN(duration)) videoFile.duration = 0
631 else videoFile.duration = duration
f6d6e7f8 632}