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