Fix issues on server start
[oweals/peertube.git] / server / helpers / custom-validators / videos.ts
1 import { values } from 'lodash'
2 import * as validator from 'validator'
3 import * as Promise from 'bluebird'
4 import * as express from 'express'
5 import 'express-validator'
6 import 'multer'
7
8 import {
9   CONSTRAINTS_FIELDS,
10   VIDEO_CATEGORIES,
11   VIDEO_LICENCES,
12   VIDEO_LANGUAGES,
13   VIDEO_RATE_TYPES,
14   VIDEO_PRIVACIES,
15   database as db
16 } from '../../initializers'
17 import { isUserUsernameValid } from './users'
18 import { isArray, exists } from './misc'
19 import { VideoInstance } from '../../models'
20 import { logger } from '../../helpers'
21 import { VideoRateType } from '../../../shared'
22 import { isActivityPubUrlValid } from './activitypub/misc'
23
24 const VIDEOS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEOS
25 const VIDEO_ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_ABUSES
26 const VIDEO_EVENTS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_EVENTS
27
28 function isVideoCategoryValid (value: number) {
29   return VIDEO_CATEGORIES[value] !== undefined
30 }
31
32 // Maybe we don't know the remote category, but that doesn't matter
33 function isRemoteVideoCategoryValid (value: string) {
34   return validator.isInt('' + value)
35 }
36
37 function isVideoUrlValid (value: string) {
38   return isActivityPubUrlValid(value)
39 }
40
41 function isVideoLicenceValid (value: number) {
42   return VIDEO_LICENCES[value] !== undefined
43 }
44
45 function isVideoPrivacyValid (value: string) {
46   return VIDEO_PRIVACIES[value] !== undefined
47 }
48
49 // Maybe we don't know the remote privacy setting, but that doesn't matter
50 function isRemoteVideoPrivacyValid (value: string) {
51   return validator.isInt('' + value)
52 }
53
54 // Maybe we don't know the remote licence, but that doesn't matter
55 function isRemoteVideoLicenceValid (value: string) {
56   return validator.isInt('' + value)
57 }
58
59 function isVideoLanguageValid (value: number) {
60   return value === null || VIDEO_LANGUAGES[value] !== undefined
61 }
62
63 // Maybe we don't know the remote language, but that doesn't matter
64 function isRemoteVideoLanguageValid (value: string) {
65   return validator.isInt('' + value)
66 }
67
68 function isVideoNSFWValid (value: any) {
69   return typeof value === 'boolean' || (typeof value === 'string' && validator.isBoolean(value))
70 }
71
72 function isVideoTruncatedDescriptionValid (value: string) {
73   return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.TRUNCATED_DESCRIPTION)
74 }
75
76 function isVideoDescriptionValid (value: string) {
77   return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.DESCRIPTION)
78 }
79
80 function isVideoDurationValid (value: string) {
81   // https://www.w3.org/TR/activitystreams-vocabulary/#dfn-duration
82   return exists(value) &&
83     typeof value === 'string' &&
84     value.startsWith('PT') &&
85     value.endsWith('S') &&
86     validator.isInt(value.replace(/[^0-9]+/, ''), VIDEOS_CONSTRAINTS_FIELDS.DURATION)
87 }
88
89 function isVideoNameValid (value: string) {
90   return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.NAME)
91 }
92
93 function isVideoTagValid (tag: string) {
94   return exists(tag) && validator.isLength(tag, VIDEOS_CONSTRAINTS_FIELDS.TAG)
95 }
96
97 function isVideoTagsValid (tags: string[]) {
98   return isArray(tags) &&
99          validator.isInt(tags.length.toString(), VIDEOS_CONSTRAINTS_FIELDS.TAGS) &&
100          tags.every(tag => isVideoTagValid(tag))
101 }
102
103 function isVideoThumbnailValid (value: string) {
104   return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.THUMBNAIL)
105 }
106
107 function isVideoThumbnailDataValid (value: string) {
108   return exists(value) && validator.isByteLength(value, VIDEOS_CONSTRAINTS_FIELDS.THUMBNAIL_DATA)
109 }
110
111 function isVideoAbuseReasonValid (value: string) {
112   return exists(value) && validator.isLength(value, VIDEO_ABUSES_CONSTRAINTS_FIELDS.REASON)
113 }
114
115 function isVideoAbuseReporterUsernameValid (value: string) {
116   return isUserUsernameValid(value)
117 }
118
119 function isVideoViewsValid (value: string) {
120   return exists(value) && validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.VIEWS)
121 }
122
123 function isVideoLikesValid (value: string) {
124   return exists(value) && validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.LIKES)
125 }
126
127 function isVideoDislikesValid (value: string) {
128   return exists(value) && validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.DISLIKES)
129 }
130
131 function isVideoEventCountValid (value: string) {
132   return exists(value) && validator.isInt(value + '', VIDEO_EVENTS_CONSTRAINTS_FIELDS.COUNT)
133 }
134
135 function isVideoRatingTypeValid (value: string) {
136   return values(VIDEO_RATE_TYPES).indexOf(value as VideoRateType) !== -1
137 }
138
139 function isVideoFile (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[]) {
140   // Should have files
141   if (!files) return false
142   if (isArray(files)) return false
143
144   // Should have videofile file
145   const videofile = files['videofile']
146   if (!videofile || videofile.length === 0) return false
147
148   // The file should exist
149   const file = videofile[0]
150   if (!file || !file.originalname) return false
151
152   return new RegExp('^video/(webm|mp4|ogg)$', 'i').test(file.mimetype)
153 }
154
155 function isVideoFileSizeValid (value: string) {
156   return exists(value) && validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.FILE_SIZE)
157 }
158
159 function isVideoFileResolutionValid (value: string) {
160   return exists(value) && validator.isInt(value + '')
161 }
162
163 function isVideoFileExtnameValid (value: string) {
164   return VIDEOS_CONSTRAINTS_FIELDS.EXTNAME.indexOf(value) !== -1
165 }
166
167 function isVideoFileInfoHashValid (value: string) {
168   return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.INFO_HASH)
169 }
170
171 function checkVideoExists (id: string, res: express.Response, callback: () => void) {
172   let promise: Promise<VideoInstance>
173   if (validator.isInt(id)) {
174     promise = db.Video.loadAndPopulateAccountAndPodAndTags(+id)
175   } else { // UUID
176     promise = db.Video.loadByUUIDAndPopulateAccountAndPodAndTags(id)
177   }
178
179   promise.then(video => {
180     if (!video) {
181       return res.status(404)
182                 .json({ error: 'Video not found' })
183                 .end()
184     }
185
186     res.locals.video = video
187     callback()
188   })
189   .catch(err => {
190     logger.error('Error in video request validator.', err)
191     return res.sendStatus(500)
192   })
193 }
194
195 // ---------------------------------------------------------------------------
196
197 export {
198   isVideoCategoryValid,
199   isVideoLicenceValid,
200   isVideoLanguageValid,
201   isVideoNSFWValid,
202   isVideoTruncatedDescriptionValid,
203   isVideoDescriptionValid,
204   isVideoDurationValid,
205   isVideoFileInfoHashValid,
206   isVideoNameValid,
207   isVideoTagsValid,
208   isVideoThumbnailValid,
209   isVideoThumbnailDataValid,
210   isVideoFileExtnameValid,
211   isVideoAbuseReasonValid,
212   isVideoAbuseReporterUsernameValid,
213   isVideoFile,
214   isVideoViewsValid,
215   isVideoLikesValid,
216   isVideoRatingTypeValid,
217   isVideoDislikesValid,
218   isVideoEventCountValid,
219   isVideoFileSizeValid,
220   isVideoPrivacyValid,
221   isRemoteVideoPrivacyValid,
222   isVideoFileResolutionValid,
223   checkVideoExists,
224   isVideoTagValid,
225   isRemoteVideoCategoryValid,
226   isRemoteVideoLicenceValid,
227   isVideoUrlValid,
228   isRemoteVideoLanguageValid
229 }