<commit_before>import * as express from 'express'
import { body, header, param, query, ValidationChain } from 'express-validator'
import { getResumableUploadPath } from '@server/helpers/upload'
import { isAbleToUploadVideo } from '@server/lib/user'
import { getServerActor } from '@server/models/application/application'
import { ExpressPromiseHandler } from '@server/types/express'
import { MUserAccountId, MVideoFullLight } from '@server/types/models'
import { ServerErrorCode, UserRight, VideoPrivacy } from '../../../../shared'
import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes'
import {
  exists,
  isBooleanValid,
  isDateValid,
  isFileFieldValid,
  isIdValid,
  isUUIDValid,
  toArray,
  toBooleanOrNull,
  toIntOrNull,
  toValueOrNull
} from '../../../helpers/custom-validators/misc'
import { isBooleanBothQueryValid, isNumberArray, isStringArray } from '../../../helpers/custom-validators/search'
import {
  isScheduleVideoUpdatePrivacyValid,
  isVideoCategoryValid,
  isVideoDescriptionValid,
  isVideoFileMimeTypeValid,
  isVideoFileSizeValid,
  isVideoFilterValid,
  isVideoImage,
  isVideoLanguageValid,
  isVideoLicenceValid,
  isVideoNameValid,
  isVideoOriginallyPublishedAtValid,
  isVideoPrivacyValid,
  isVideoSupportValid,
  isVideoTagsValid
} from '../../../helpers/custom-validators/videos'
import { cleanUpReqFiles } from '../../../helpers/express-utils'
import { getDurationFromVideoFile } from '../../../helpers/ffprobe-utils'
import { logger } from '../../../helpers/logger'
import { deleteFileAndCatch } from '../../../helpers/utils'
import { getVideoWithAttributes } from '../../../helpers/video'
import { CONFIG } from '../../../initializers/config'
import { CONSTRAINTS_FIELDS, OVERVIEWS } from '../../../initializers/constants'
import { isLocalVideoAccepted } from '../../../lib/moderation'
import { Hooks } from '../../../lib/plugins/hooks'
import { VideoModel } from '../../../models/video/video'
import { authenticatePromiseIfNeeded } from '../../auth'
import {
  areValidationErrors,
  checkUserCanManageVideo,
  doesVideoChannelOfAccountExist,
  doesVideoExist,
  doesVideoFileOfVideoExist,
  isValidVideoIdParam
} from '../shared'

const videosAddLegacyValidator = getCommonVideoEditAttributes().concat([
  body('videofile')
    .custom((value, { req }) => isFileFieldValid(req.files, 'videofile'))
    .withMessage('Should have a file'),
  body('name')
    .trim()
    .custom(isVideoNameValid).withMessage(
      `Should have a video name between ${CONSTRAINTS_FIELDS.VIDEOS.NAME.min} and ${CONSTRAINTS_FIELDS.VIDEOS.NAME.max} characters long`
    ),
  body('channelId')
    .customSanitizer(toIntOrNull)
    .custom(isIdValid).withMessage('Should have correct video channel id'),

  async (req: express.Request, res: express.Response, next: express.NextFunction) => {
    logger.debug('Checking videosAdd parameters', { parameters: req.body, files: req.files })

    if (areValidationErrors(req, res)) return cleanUpReqFiles(req)

    const videoFile: express.VideoUploadFile = req.files['videofile'][0]
    const user = res.locals.oauth.token.User

    if (!await commonVideoChecksPass({ req, res, user, videoFileSize: videoFile.size, files: req.files })) {
      return cleanUpReqFiles(req)
    }

    try {
      if (!videoFile.duration) await addDurationToVideo(videoFile)
    } catch (err) {
      logger.error('Invalid input file in videosAddLegacyValidator.', { err })

      res.fail({
        status: HttpStatusCode.UNPROCESSABLE_ENTITY_422,
        message: 'Video file unreadable.'
      })
      return cleanUpReqFiles(req)
    }

    if (!await isVideoAccepted(req, res, videoFile)) return cleanUpReqFiles(req)

    return next()
  }
])

/**
 * Gets called after the last PUT request
 */
const videosAddResumableValidator = [
  async (req: express.Request, res: express.Response, next: express.NextFunction) => {
    const user = res.locals.oauth.token.User

    const body: express.CustomUploadXFile<express.UploadXFileMetadata> = req.body
    const file = { ...body, duration: undefined, path: getResumableUploadPath(body.id), filename: body.metadata.filename }

    const cleanup = () => deleteFileAndCatch(file.path)

    if (!await doesVideoChannelOfAccountExist(file.metadata.channelId, user, res)) return cleanup()

    try {
      if (!file.duration) await addDurationToVideo(file)
    } catch (err) {
      logger.error('Invalid input file in videosAddResumableValidator.', { err })

      res.fail({
        status: HttpStatusCode.UNPROCESSABLE_ENTITY_422,
        message: 'Video file unreadable.'
      })
      return cleanup()
    }

    if (!await isVideoAccepted(req, res, file)) return cleanup()

    res.locals.videoFileResumable = file

    return next()
  }
]

/**
 * File is created in POST initialisation, and its body is saved as a 'metadata' field is saved by uploadx for later use.
 * see https://github.com/kukhariev/node-uploadx/blob/dc9fb4a8ac5a6f481902588e93062f591ec6ef03/packages/core/src/handlers/uploadx.ts
 *
 * Uploadx doesn't use next() until the upload completes, so this middleware has to be placed before uploadx
 * see https://github.com/kukhariev/node-uploadx/blob/dc9fb4a8ac5a6f481902588e93062f591ec6ef03/packages/core/src/handlers/base-handler.ts
 *
 */
const videosAddResumableInitValidator = getCommonVideoEditAttributes().concat([
  body('filename')
    .isString()
    .exists()
    .withMessage('Should have a valid filename'),
  body('name')
    .trim()
    .custom(isVideoNameValid).withMessage(
      `Should have a video name between ${CONSTRAINTS_FIELDS.VIDEOS.NAME.min} and ${CONSTRAINTS_FIELDS.VIDEOS.NAME.max} characters long`
    ),
  body('channelId')
    .customSanitizer(toIntOrNull)
    .custom(isIdValid).withMessage('Should have correct video channel id'),

  header('x-upload-content-length')
    .isNumeric()
    .exists()
    .withMessage('Should specify the file length'),
  header('x-upload-content-type')
    .isString()
    .exists()
    .withMessage('Should specify the file mimetype'),

  async (req: express.Request, res: express.Response, next: express.NextFunction) => {
    const videoFileMetadata = {
      mimetype: req.headers['x-upload-content-type'] as string,
      size: +req.headers['x-upload-content-length'],
      originalname: req.body.name
    }

    const user = res.locals.oauth.token.User
    const cleanup = () => cleanUpReqFiles(req)

    logger.debug('Checking videosAddResumableInitValidator parameters and headers', {
      parameters: req.body,
      headers: req.headers,
      files: req.files
    })

    if (areValidationErrors(req, res)) return cleanup()

    const files = { videofile: [ videoFileMetadata ] }
    if (!await commonVideoChecksPass({ req, res, user, videoFileSize: videoFileMetadata.size, files })) return cleanup()

    // multer required unsetting the Content-Type, now we can set it for node-uploadx
    req.headers['content-type'] = 'application/json; charset=utf-8'
    // place previewfile in metadata so that uploadx saves it in .META
    if (req.files['previewfile']) req.body.previewfile = req.files['previewfile']

    return next()
  }
])

const videosUpdateValidator = getCommonVideoEditAttributes().concat([
  isValidVideoIdParam('id'),

  body('name')<commit_msg>Fix upload without files<commit_after>import * as express from 'express'
import { body, header, param, query, ValidationChain } from 'express-validator'
import { getResumableUploadPath } from '@server/helpers/upload'
import { isAbleToUploadVideo } from '@server/lib/user'
import { getServerActor } from '@server/models/application/application'
import { ExpressPromiseHandler } from '@server/types/express'
import { MUserAccountId, MVideoFullLight } from '@server/types/models'
import { ServerErrorCode, UserRight, VideoPrivacy } from '../../../../shared'
import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes'
import {
  exists,
  isBooleanValid,
  isDateValid,
  isFileFieldValid,
  isIdValid,
  isUUIDValid,
  toArray,
  toBooleanOrNull,
  toIntOrNull,
  toValueOrNull
} from '../../../helpers/custom-validators/misc'
import { isBooleanBothQueryValid, isNumberArray, isStringArray } from '../../../helpers/custom-validators/search'
import {
  isScheduleVideoUpdatePrivacyValid,
  isVideoCategoryValid,
  isVideoDescriptionValid,
  isVideoFileMimeTypeValid,
  isVideoFileSizeValid,
  isVideoFilterValid,
  isVideoImage,
  isVideoLanguageValid,
  isVideoLicenceValid,
  isVideoNameValid,
  isVideoOriginallyPublishedAtValid,
  isVideoPrivacyValid,
  isVideoSupportValid,
  isVideoTagsValid
} from '../../../helpers/custom-validators/videos'
import { cleanUpReqFiles } from '../../../helpers/express-utils'
import { getDurationFromVideoFile } from '../../../helpers/ffprobe-utils'
import { logger } from '../../../helpers/logger'
import { deleteFileAndCatch } from '../../../helpers/utils'
import { getVideoWithAttributes } from '../../../helpers/video'
import { CONFIG } from '../../../initializers/config'
import { CONSTRAINTS_FIELDS, OVERVIEWS } from '../../../initializers/constants'
import { isLocalVideoAccepted } from '../../../lib/moderation'
import { Hooks } from '../../../lib/plugins/hooks'
import { VideoModel } from '../../../models/video/video'
import { authenticatePromiseIfNeeded } from '../../auth'
import {
  areValidationErrors,
  checkUserCanManageVideo,
  doesVideoChannelOfAccountExist,
  doesVideoExist,
  doesVideoFileOfVideoExist,
  isValidVideoIdParam
} from '../shared'

const videosAddLegacyValidator = getCommonVideoEditAttributes().concat([
  body('videofile')
    .custom((value, { req }) => isFileFieldValid(req.files, 'videofile'))
    .withMessage('Should have a file'),
  body('name')
    .trim()
    .custom(isVideoNameValid).withMessage(
      `Should have a video name between ${CONSTRAINTS_FIELDS.VIDEOS.NAME.min} and ${CONSTRAINTS_FIELDS.VIDEOS.NAME.max} characters long`
    ),
  body('channelId')
    .customSanitizer(toIntOrNull)
    .custom(isIdValid).withMessage('Should have correct video channel id'),

  async (req: express.Request, res: express.Response, next: express.NextFunction) => {
    logger.debug('Checking videosAdd parameters', { parameters: req.body, files: req.files })

    if (areValidationErrors(req, res)) return cleanUpReqFiles(req)

    const videoFile: express.VideoUploadFile = req.files['videofile'][0]
    const user = res.locals.oauth.token.User

    if (!await commonVideoChecksPass({ req, res, user, videoFileSize: videoFile.size, files: req.files })) {
      return cleanUpReqFiles(req)
    }

    try {
      if (!videoFile.duration) await addDurationToVideo(videoFile)
    } catch (err) {
      logger.error('Invalid input file in videosAddLegacyValidator.', { err })

      res.fail({
        status: HttpStatusCode.UNPROCESSABLE_ENTITY_422,
        message: 'Video file unreadable.'
      })
      return cleanUpReqFiles(req)
    }

    if (!await isVideoAccepted(req, res, videoFile)) return cleanUpReqFiles(req)

    return next()
  }
])

/**
 * Gets called after the last PUT request
 */
const videosAddResumableValidator = [
  async (req: express.Request, res: express.Response, next: express.NextFunction) => {
    const user = res.locals.oauth.token.User

    const body: express.CustomUploadXFile<express.UploadXFileMetadata> = req.body
    const file = { ...body, duration: undefined, path: getResumableUploadPath(body.id), filename: body.metadata.filename }

    const cleanup = () => deleteFileAndCatch(file.path)

    if (!await doesVideoChannelOfAccountExist(file.metadata.channelId, user, res)) return cleanup()

    try {
      if (!file.duration) await addDurationToVideo(file)
    } catch (err) {
      logger.error('Invalid input file in videosAddResumableValidator.', { err })

      res.fail({
        status: HttpStatusCode.UNPROCESSABLE_ENTITY_422,
        message: 'Video file unreadable.'
      })
      return cleanup()
    }

    if (!await isVideoAccepted(req, res, file)) return cleanup()

    res.locals.videoFileResumable = file

    return next()
  }
]

/**
 * File is created in POST initialisation, and its body is saved as a 'metadata' field is saved by uploadx for later use.
 * see https://github.com/kukhariev/node-uploadx/blob/dc9fb4a8ac5a6f481902588e93062f591ec6ef03/packages/core/src/handlers/uploadx.ts
 *
 * Uploadx doesn't use next() until the upload completes, so this middleware has to be placed before uploadx
 * see https://github.com/kukhariev/node-uploadx/blob/dc9fb4a8ac5a6f481902588e93062f591ec6ef03/packages/core/src/handlers/base-handler.ts
 *
 */
const videosAddResumableInitValidator = getCommonVideoEditAttributes().concat([
  body('filename')
    .isString()
    .exists()
    .withMessage('Should have a valid filename'),
  body('name')
    .trim()
    .custom(isVideoNameValid).withMessage(
      `Should have a video name between ${CONSTRAINTS_FIELDS.VIDEOS.NAME.min} and ${CONSTRAINTS_FIELDS.VIDEOS.NAME.max} characters long`
    ),
  body('channelId')
    .customSanitizer(toIntOrNull)
    .custom(isIdValid).withMessage('Should have correct video channel id'),

  header('x-upload-content-length')
    .isNumeric()
    .exists()
    .withMessage('Should specify the file length'),
  header('x-upload-content-type')
    .isString()
    .exists()
    .withMessage('Should specify the file mimetype'),

  async (req: express.Request, res: express.Response, next: express.NextFunction) => {
    const videoFileMetadata = {
      mimetype: req.headers['x-upload-content-type'] as string,
      size: +req.headers['x-upload-content-length'],
      originalname: req.body.name
    }

    const user = res.locals.oauth.token.User
    const cleanup = () => cleanUpReqFiles(req)

    logger.debug('Checking videosAddResumableInitValidator parameters and headers', {
      parameters: req.body,
      headers: req.headers,
      files: req.files
    })

    if (areValidationErrors(req, res)) return cleanup()

    const files = { videofile: [ videoFileMetadata ] }
    if (!await commonVideoChecksPass({ req, res, user, videoFileSize: videoFileMetadata.size, files })) return cleanup()

    // multer required unsetting the Content-Type, now we can set it for node-uploadx
    req.headers['content-type'] = 'application/json; charset=utf-8'
    // place previewfile in metadata so that uploadx saves it in .META
    if (req.files?.['previewfile']) req.body.previewfile = req.files['previewfile']

    return next()
  }
])

const videosUpdateValidator = getCommonVideoEditAttributes().concat([
  isValidVideoIdParam('id'),

  body('name')
{'fraction_non_alphanumeric': 0.1302408563782337, 'fraction_numerical': 0.007959925890345158, 'mean_word_length': 3.915345699831366, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 17, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3623081', 'n_tokens_mistral': 4493, 'n_tokens_neox': 4425, 'n_words': 992}