Add downloadingEnabled property to video model
[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 userId = res.locals.oauth ? res.locals.oauth.token.User.id : undefined
158
159   const video = await fetchVideo(id, fetchType, userId)
160
161   if (video === null) {
162     res.status(404)
163        .json({ error: 'Video not found' })
164        .end()
165
166     return false
167   }
168
169   if (fetchType !== 'none') res.locals.video = video
170   return true
171 }
172
173 async function isVideoChannelOfAccountExist (channelId: number, user: UserModel, res: Response) {
174   if (user.hasRight(UserRight.UPDATE_ANY_VIDEO) === true) {
175     const videoChannel = await VideoChannelModel.loadAndPopulateAccount(channelId)
176     if (videoChannel === null) {
177       res.status(400)
178          .json({ error: 'Unknown video `video channel` on this instance.' })
179          .end()
180
181       return false
182     }
183
184     res.locals.videoChannel = videoChannel
185     return true
186   }
187
188   const videoChannel = await VideoChannelModel.loadByIdAndAccount(channelId, user.Account.id)
189   if (videoChannel === null) {
190     res.status(400)
191        .json({ error: 'Unknown video `video channel` for this account.' })
192        .end()
193
194     return false
195   }
196
197   res.locals.videoChannel = videoChannel
198   return true
199 }
200
201 // ---------------------------------------------------------------------------
202
203 export {
204   isVideoCategoryValid,
205   checkUserCanManageVideo,
206   isVideoLicenceValid,
207   isVideoLanguageValid,
208   isVideoTruncatedDescriptionValid,
209   isVideoDescriptionValid,
210   isVideoFileInfoHashValid,
211   isVideoNameValid,
212   isVideoTagsValid,
213   isVideoFPSResolutionValid,
214   isScheduleVideoUpdatePrivacyValid,
215   isVideoFile,
216   isVideoMagnetUriValid,
217   isVideoStateValid,
218   isVideoViewsValid,
219   isVideoRatingTypeValid,
220   isVideoDurationValid,
221   isVideoTagValid,
222   isVideoPrivacyValid,
223   isVideoFileResolutionValid,
224   isVideoFileSizeValid,
225   isVideoExist,
226   isVideoImage,
227   isVideoChannelOfAccountExist,
228   isVideoSupportValid
229 }