Add blacklist reason field
[oweals/peertube.git] / server / helpers / custom-validators / videos.ts
1 import { Response } from 'express'
2 import 'express-validator'
3 import { values } from 'lodash'
4 import 'multer'
5 import * as validator from 'validator'
6 import { UserRight, VideoPrivacy, VideoRateType } from '../../../shared'
7 import {
8   CONSTRAINTS_FIELDS,
9   VIDEO_ABUSE_STATES,
10   VIDEO_CATEGORIES,
11   VIDEO_LICENCES,
12   VIDEO_MIMETYPE_EXT,
13   VIDEO_PRIVACIES,
14   VIDEO_RATE_TYPES,
15   VIDEO_STATES
16 } from '../../initializers'
17 import { VideoModel } from '../../models/video/video'
18 import { exists, isArray, isFileValid } from './misc'
19 import { VideoChannelModel } from '../../models/video/video-channel'
20 import { UserModel } from '../../models/account/user'
21 import * as magnetUtil from 'magnet-uri'
22 import { VideoAbuseModel } from '../../models/video/video-abuse'
23
24 const VIDEOS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEOS
25 const VIDEO_ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_ABUSES
26
27 function isVideoCategoryValid (value: any) {
28   return value === null || VIDEO_CATEGORIES[ value ] !== undefined
29 }
30
31 function isVideoStateValid (value: any) {
32   return exists(value) && VIDEO_STATES[ value ] !== undefined
33 }
34
35 function isVideoLicenceValid (value: any) {
36   return value === null || VIDEO_LICENCES[ value ] !== undefined
37 }
38
39 function isVideoLanguageValid (value: any) {
40   return value === null ||
41     (typeof value === 'string' && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.LANGUAGE))
42 }
43
44 function isVideoDurationValid (value: string) {
45   return exists(value) && validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.DURATION)
46 }
47
48 function isVideoTruncatedDescriptionValid (value: string) {
49   return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.TRUNCATED_DESCRIPTION)
50 }
51
52 function isVideoDescriptionValid (value: string) {
53   return value === null || (exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.DESCRIPTION))
54 }
55
56 function isVideoSupportValid (value: string) {
57   return value === null || (exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.SUPPORT))
58 }
59
60 function isVideoNameValid (value: string) {
61   return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.NAME)
62 }
63
64 function isVideoTagValid (tag: string) {
65   return exists(tag) && validator.isLength(tag, VIDEOS_CONSTRAINTS_FIELDS.TAG)
66 }
67
68 function isVideoTagsValid (tags: string[]) {
69   return tags === null || (
70     isArray(tags) &&
71     validator.isInt(tags.length.toString(), VIDEOS_CONSTRAINTS_FIELDS.TAGS) &&
72     tags.every(tag => isVideoTagValid(tag))
73   )
74 }
75
76 function isVideoViewsValid (value: string) {
77   return exists(value) && validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.VIEWS)
78 }
79
80 function isVideoRatingTypeValid (value: string) {
81   return value === 'none' || values(VIDEO_RATE_TYPES).indexOf(value as VideoRateType) !== -1
82 }
83
84 const videoFileTypes = Object.keys(VIDEO_MIMETYPE_EXT).map(m => `(${m})`)
85 const videoFileTypesRegex = videoFileTypes.join('|')
86
87 function isVideoFile (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[]) {
88   return isFileValid(files, videoFileTypesRegex, 'videofile', null)
89 }
90
91 const videoImageTypes = CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME
92                                           .map(v => v.replace('.', ''))
93                                           .join('|')
94 const videoImageTypesRegex = `image/(${videoImageTypes})`
95
96 function isVideoImage (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[], field: string) {
97   return isFileValid(files, videoImageTypesRegex, field, CONSTRAINTS_FIELDS.VIDEOS.IMAGE.FILE_SIZE.max, true)
98 }
99
100 function isVideoPrivacyValid (value: number) {
101   return validator.isInt(value + '') && VIDEO_PRIVACIES[ value ] !== undefined
102 }
103
104 function isScheduleVideoUpdatePrivacyValid (value: number) {
105   return validator.isInt(value + '') &&
106     (
107       value === VideoPrivacy.UNLISTED ||
108       value === VideoPrivacy.PUBLIC
109     )
110 }
111
112 function isVideoFileInfoHashValid (value: string | null | undefined) {
113   return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.INFO_HASH)
114 }
115
116 function isVideoFileResolutionValid (value: string) {
117   return exists(value) && validator.isInt(value + '')
118 }
119
120 function isVideoFPSResolutionValid (value: string) {
121   return value === null || validator.isInt(value + '')
122 }
123
124 function isVideoFileSizeValid (value: string) {
125   return exists(value) && validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.FILE_SIZE)
126 }
127
128 function isVideoMagnetUriValid (value: string) {
129   if (!exists(value)) return false
130
131   const parsed = magnetUtil.decode(value)
132   return parsed && isVideoFileInfoHashValid(parsed.infoHash)
133 }
134
135 function checkUserCanManageVideo (user: UserModel, video: VideoModel, right: UserRight, res: Response) {
136   // Retrieve the user who did the request
137   if (video.isOwned() === false) {
138     res.status(403)
139        .json({ error: 'Cannot manage a video of another server.' })
140        .end()
141     return false
142   }
143
144   // Check if the user can delete the video
145   // The user can delete it if he has the right
146   // Or if s/he is the video's account
147   const account = video.VideoChannel.Account
148   if (user.hasRight(right) === false && account.userId !== user.id) {
149     res.status(403)
150        .json({ error: 'Cannot manage a video of another user.' })
151        .end()
152     return false
153   }
154
155   return true
156 }
157
158 async function isVideoExist (id: string, res: Response) {
159   let video: VideoModel | null
160
161   if (validator.isInt(id)) {
162     video = await VideoModel.loadAndPopulateAccountAndServerAndTags(+id)
163   } else { // UUID
164     video = await VideoModel.loadByUUIDAndPopulateAccountAndServerAndTags(id)
165   }
166
167   if (video === null) {
168     res.status(404)
169        .json({ error: 'Video not found' })
170        .end()
171
172     return false
173   }
174
175   res.locals.video = video
176   return true
177 }
178
179 async function isVideoChannelOfAccountExist (channelId: number, user: UserModel, res: Response) {
180   if (user.hasRight(UserRight.UPDATE_ANY_VIDEO) === true) {
181     const videoChannel = await VideoChannelModel.loadAndPopulateAccount(channelId)
182     if (videoChannel === null) {
183       res.status(400)
184          .json({ error: 'Unknown video video channel on this instance.' })
185          .end()
186
187       return false
188     }
189
190     res.locals.videoChannel = videoChannel
191     return true
192   }
193
194   const videoChannel = await VideoChannelModel.loadByIdAndAccount(channelId, user.Account.id)
195   if (videoChannel === null) {
196     res.status(400)
197        .json({ error: 'Unknown video video channel for this account.' })
198        .end()
199
200     return false
201   }
202
203   res.locals.videoChannel = videoChannel
204   return true
205 }
206
207 // ---------------------------------------------------------------------------
208
209 export {
210   isVideoCategoryValid,
211   checkUserCanManageVideo,
212   isVideoLicenceValid,
213   isVideoLanguageValid,
214   isVideoTruncatedDescriptionValid,
215   isVideoDescriptionValid,
216   isVideoFileInfoHashValid,
217   isVideoNameValid,
218   isVideoTagsValid,
219   isVideoFPSResolutionValid,
220   isScheduleVideoUpdatePrivacyValid,
221   isVideoFile,
222   isVideoMagnetUriValid,
223   isVideoStateValid,
224   isVideoViewsValid,
225   isVideoRatingTypeValid,
226   isVideoDurationValid,
227   isVideoTagValid,
228   isVideoPrivacyValid,
229   isVideoFileResolutionValid,
230   isVideoFileSizeValid,
231   isVideoExist,
232   isVideoImage,
233   isVideoChannelOfAccountExist,
234   isVideoSupportValid
235 }