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