]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blobdiff - server/middlewares/validators/videos/videos.ts
Add banners support
[github/Chocobozzz/PeerTube.git] / server / middlewares / validators / videos / videos.ts
index efab67a01372d112be25a6710d510a88910a0c7f..4d31d3dcb076e6cd4a1644fe2b9719ca519e1b38 100644 (file)
@@ -2,12 +2,16 @@ import * as express from 'express'
 import { body, param, query, ValidationChain } from 'express-validator'
 import { isAbleToUploadVideo } from '@server/lib/user'
 import { getServerActor } from '@server/models/application/application'
-import { MVideoFullLight } from '@server/types/models'
+import { ExpressPromiseHandler } from '@server/types/express'
+import { MVideoWithRights } from '@server/types/models'
 import { ServerErrorCode, UserRight, VideoChangeOwnershipStatus, VideoPrivacy } from '../../../../shared'
+import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
 import { VideoChangeOwnershipAccept } from '../../../../shared/models/videos/video-change-ownership-accept.model'
 import {
+  exists,
   isBooleanValid,
   isDateValid,
+  isFileFieldValid,
   isIdOrUUIDValid,
   isIdValid,
   isUUIDValid,
@@ -22,7 +26,8 @@ import {
   isScheduleVideoUpdatePrivacyValid,
   isVideoCategoryValid,
   isVideoDescriptionValid,
-  isVideoFile,
+  isVideoFileMimeTypeValid,
+  isVideoFileSizeValid,
   isVideoFilterValid,
   isVideoImage,
   isVideoLanguageValid,
@@ -34,7 +39,7 @@ import {
   isVideoTagsValid
 } from '../../../helpers/custom-validators/videos'
 import { cleanUpReqFiles } from '../../../helpers/express-utils'
-import { getDurationFromVideoFile } from '../../../helpers/ffmpeg-utils'
+import { getDurationFromVideoFile } from '../../../helpers/ffprobe-utils'
 import { logger } from '../../../helpers/logger'
 import {
   checkUserCanManageVideo,
@@ -49,16 +54,17 @@ import { isLocalVideoAccepted } from '../../../lib/moderation'
 import { Hooks } from '../../../lib/plugins/hooks'
 import { AccountModel } from '../../../models/account/account'
 import { VideoModel } from '../../../models/video/video'
-import { authenticatePromiseIfNeeded } from '../../oauth'
+import { authenticatePromiseIfNeeded } from '../../auth'
 import { areValidationErrors } from '../utils'
 
 const videosAddValidator = getCommonVideoEditAttributes().concat([
   body('videofile')
-    .custom((value, { req }) => isVideoFile(req.files)).withMessage(
-      'This file is not supported or too large. Please, make sure it is of the following type: ' +
-      CONSTRAINTS_FIELDS.VIDEOS.EXTNAME.join(', ')
-    ),
-  body('name').custom(isVideoNameValid).withMessage('Should have a valid name'),
+    .custom((value, { req }) => isFileFieldValid(req.files, 'videofile'))
+    .withMessage('Should have a file'),
+  body('name')
+    .trim()
+    .custom(isVideoNameValid)
+    .withMessage('Should have a valid name'),
   body('channelId')
     .customSanitizer(toIntOrNull)
     .custom(isIdValid).withMessage('Should have correct video channel id'),
@@ -74,8 +80,27 @@ const videosAddValidator = getCommonVideoEditAttributes().concat([
 
     if (!await doesVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req)
 
+    if (!isVideoFileMimeTypeValid(req.files)) {
+      res.status(HttpStatusCode.UNSUPPORTED_MEDIA_TYPE_415)
+         .json({
+           error: 'This file is not supported. Please, make sure it is of the following type: ' +
+                  CONSTRAINTS_FIELDS.VIDEOS.EXTNAME.join(', ')
+         })
+
+      return cleanUpReqFiles(req)
+    }
+
+    if (!isVideoFileSizeValid(videoFile.size.toString())) {
+      res.status(HttpStatusCode.PAYLOAD_TOO_LARGE_413)
+         .json({
+           error: 'This file is too large.'
+         })
+
+      return cleanUpReqFiles(req)
+    }
+
     if (await isAbleToUploadVideo(user.id, videoFile.size) === false) {
-      res.status(403)
+      res.status(HttpStatusCode.PAYLOAD_TOO_LARGE_413)
          .json({ error: 'The user video quota is exceeded with this video.' })
 
       return cleanUpReqFiles(req)
@@ -87,8 +112,8 @@ const videosAddValidator = getCommonVideoEditAttributes().concat([
       duration = await getDurationFromVideoFile(videoFile.path)
     } catch (err) {
       logger.error('Invalid input file in videosAddValidator.', { err })
-      res.status(400)
-         .json({ error: 'Invalid input file.' })
+      res.status(HttpStatusCode.UNPROCESSABLE_ENTITY_422)
+         .json({ error: 'Video file unreadable.' })
 
       return cleanUpReqFiles(req)
     }
@@ -105,6 +130,7 @@ const videosUpdateValidator = getCommonVideoEditAttributes().concat([
   param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
   body('name')
     .optional()
+    .trim()
     .custom(isVideoNameValid).withMessage('Should have a valid name'),
   body('channelId')
     .optional()
@@ -147,7 +173,7 @@ async function checkVideoFollowConstraints (req: express.Request, res: express.R
   const serverActor = await getServerActor()
   if (await VideoModel.checkVideoHasInstanceFollow(video.id, serverActor.id) === true) return next()
 
-  return res.status(403)
+  return res.status(HttpStatusCode.FORBIDDEN_403)
             .json({
               errorCode: ServerErrorCode.DOES_NOT_RESPECT_FOLLOW_CONSTRAINTS,
               error: 'Cannot get this video regarding follow constraints.',
@@ -171,18 +197,17 @@ const videosCustomGetValidator = (
       // Controllers does not need to check video rights
       if (fetchType === 'only-immutable-attributes') return next()
 
-      const video = getVideoWithAttributes(res)
-      const videoAll = video as MVideoFullLight
+      const video = getVideoWithAttributes(res) as MVideoWithRights
 
       // Video private or blacklisted
-      if (videoAll.requiresAuth()) {
+      if (video.requiresAuth()) {
         await authenticatePromiseIfNeeded(req, res, authenticateInQuery)
 
         const user = res.locals.oauth ? res.locals.oauth.token.User : null
 
         // Only the owner or a user that have blacklist rights can see the video
-        if (!user || !user.canGetVideo(videoAll)) {
-          return res.status(403)
+        if (!user || !user.canGetVideo(video)) {
+          return res.status(HttpStatusCode.FORBIDDEN_403)
                     .json({ error: 'Cannot get this private/internal or blacklisted video.' })
         }
 
@@ -197,7 +222,7 @@ const videosCustomGetValidator = (
         if (isUUIDValid(req.params.id)) return next()
 
         // Don't leak this unlisted video
-        return res.status(404).end()
+        return res.status(HttpStatusCode.NOT_FOUND_404).end()
       }
     }
   ]
@@ -250,7 +275,7 @@ const videosChangeOwnershipValidator = [
 
     const nextOwner = await AccountModel.loadLocalByName(req.body.username)
     if (!nextOwner) {
-      res.status(400)
+      res.status(HttpStatusCode.BAD_REQUEST_400)
         .json({ error: 'Changing video ownership to a remote account is not supported yet' })
 
       return
@@ -276,7 +301,7 @@ const videosTerminateChangeOwnershipValidator = [
     const videoChangeOwnership = res.locals.videoChangeOwnership
 
     if (videoChangeOwnership.status !== VideoChangeOwnershipStatus.WAITING) {
-      res.status(403)
+      res.status(HttpStatusCode.FORBIDDEN_403)
          .json({ error: 'Ownership already accepted or refused' })
       return
     }
@@ -294,7 +319,7 @@ const videosAcceptChangeOwnershipValidator = [
     const videoChangeOwnership = res.locals.videoChangeOwnership
     const isAble = await isAbleToUploadVideo(user.id, videoChangeOwnership.Video.getMaxQualityFile().size)
     if (isAble === false) {
-      res.status(403)
+      res.status(HttpStatusCode.PAYLOAD_TOO_LARGE_413)
         .json({ error: 'The user video quota is exceeded with this video.' })
 
       return
@@ -388,7 +413,7 @@ function getCommonVideoEditAttributes () {
       .optional()
       .customSanitizer(toIntOrNull)
       .custom(isScheduleVideoUpdatePrivacyValid).withMessage('Should have correct schedule update privacy')
-  ] as (ValidationChain | express.Handler)[]
+  ] as (ValidationChain | ExpressPromiseHandler)[]
 }
 
 const commonVideosFiltersValidator = [
@@ -422,6 +447,9 @@ const commonVideosFiltersValidator = [
     .optional()
     .customSanitizer(toBooleanOrNull)
     .custom(isBooleanValid).withMessage('Should have a valid skip count boolean'),
+  query('search')
+    .optional()
+    .custom(exists).withMessage('Should have a valid search'),
 
   (req: express.Request, res: express.Response, next: express.NextFunction) => {
     logger.debug('Checking commons video filters query', { parameters: req.query })
@@ -433,7 +461,7 @@ const commonVideosFiltersValidator = [
       (req.query.filter === 'all-local' || req.query.filter === 'all') &&
       (!user || user.hasRight(UserRight.SEE_ALL_VIDEOS) === false)
     ) {
-      res.status(401)
+      res.status(HttpStatusCode.UNAUTHORIZED_401)
          .json({ error: 'You are not allowed to see all local videos.' })
 
       return
@@ -473,7 +501,7 @@ function areErrorsInScheduleUpdate (req: express.Request, res: express.Response)
     if (!req.body.scheduleUpdate.updateAt) {
       logger.warn('Invalid parameters: scheduleUpdate.updateAt is mandatory.')
 
-      res.status(400)
+      res.status(HttpStatusCode.BAD_REQUEST_400)
          .json({ error: 'Schedule update at is mandatory.' })
 
       return true
@@ -498,7 +526,7 @@ async function isVideoAccepted (req: express.Request, res: express.Response, vid
 
   if (!acceptedResult || acceptedResult.accepted !== true) {
     logger.info('Refused local video.', { acceptedResult, acceptParameters })
-    res.status(403)
+    res.status(HttpStatusCode.FORBIDDEN_403)
        .json({ error: acceptedResult.errorMessage || 'Refused local video' })
 
     return false