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