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