Prepare Dislike/Flag/View fixes
[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, 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 isVideoFileInfoHashValid (value: string | null | undefined) {
119   return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.INFO_HASH)
120 }
121
122 function isVideoFileResolutionValid (value: string) {
123   return exists(value) && validator.isInt(value + '')
124 }
125
126 function isVideoFPSResolutionValid (value: string) {
127   return value === null || validator.isInt(value + '')
128 }
129
130 function isVideoFileSizeValid (value: string) {
131   return exists(value) && validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.FILE_SIZE)
132 }
133
134 function isVideoMagnetUriValid (value: string) {
135   if (!exists(value)) return false
136
137   const parsed = magnetUtil.decode(value)
138   return parsed && isVideoFileInfoHashValid(parsed.infoHash)
139 }
140
141 function checkUserCanManageVideo (user: UserModel, video: VideoModel, right: UserRight, res: Response) {
142   // Retrieve the user who did the request
143   if (video.isOwned() === false) {
144     res.status(403)
145        .json({ error: 'Cannot manage a video of another server.' })
146        .end()
147     return false
148   }
149
150   // Check if the user can delete the video
151   // The user can delete it if he has the right
152   // Or if s/he is the video's account
153   const account = video.VideoChannel.Account
154   if (user.hasRight(right) === false && account.userId !== user.id) {
155     res.status(403)
156        .json({ error: 'Cannot manage a video of another user.' })
157        .end()
158     return false
159   }
160
161   return true
162 }
163
164 async function isVideoExist (id: string, res: Response, fetchType: VideoFetchType = 'all') {
165   const userId = res.locals.oauth ? res.locals.oauth.token.User.id : undefined
166
167   const video = await fetchVideo(id, fetchType, userId)
168
169   if (video === null) {
170     res.status(404)
171        .json({ error: 'Video not found' })
172        .end()
173
174     return false
175   }
176
177   if (fetchType !== 'none') res.locals.video = video
178   return true
179 }
180
181 async function isVideoChannelOfAccountExist (channelId: number, user: UserModel, res: Response) {
182   if (user.hasRight(UserRight.UPDATE_ANY_VIDEO) === true) {
183     const videoChannel = await VideoChannelModel.loadAndPopulateAccount(channelId)
184     if (videoChannel === null) {
185       res.status(400)
186          .json({ error: 'Unknown video `video channel` on this instance.' })
187          .end()
188
189       return false
190     }
191
192     res.locals.videoChannel = videoChannel
193     return true
194   }
195
196   const videoChannel = await VideoChannelModel.loadByIdAndAccount(channelId, user.Account.id)
197   if (videoChannel === null) {
198     res.status(400)
199        .json({ error: 'Unknown video `video channel` for this account.' })
200        .end()
201
202     return false
203   }
204
205   res.locals.videoChannel = videoChannel
206   return true
207 }
208
209 // ---------------------------------------------------------------------------
210
211 export {
212   isVideoCategoryValid,
213   checkUserCanManageVideo,
214   isVideoLicenceValid,
215   isVideoLanguageValid,
216   isVideoTruncatedDescriptionValid,
217   isVideoDescriptionValid,
218   isVideoFileInfoHashValid,
219   isVideoNameValid,
220   isVideoTagsValid,
221   isVideoFPSResolutionValid,
222   isScheduleVideoUpdatePrivacyValid,
223   isVideoFile,
224   isVideoMagnetUriValid,
225   isVideoStateValid,
226   isVideoViewsValid,
227   isVideoRatingTypeValid,
228   isVideoFileExtnameValid,
229   isVideoDurationValid,
230   isVideoTagValid,
231   isVideoPrivacyValid,
232   isVideoFileResolutionValid,
233   isVideoFileSizeValid,
234   isVideoExist,
235   isVideoImage,
236   isVideoChannelOfAccountExist,
237   isVideoSupportValid,
238   isVideoFilterValid
239 }