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