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