Add video abuse to activity pub
[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 isVideoViewsValid (value: string) {
116   return exists(value) && validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.VIEWS)
117 }
118
119 function isVideoLikesValid (value: string) {
120   return exists(value) && validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.LIKES)
121 }
122
123 function isVideoDislikesValid (value: string) {
124   return exists(value) && validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.DISLIKES)
125 }
126
127 function isVideoEventCountValid (value: string) {
128   return exists(value) && validator.isInt(value + '', VIDEO_EVENTS_CONSTRAINTS_FIELDS.COUNT)
129 }
130
131 function isVideoRatingTypeValid (value: string) {
132   return values(VIDEO_RATE_TYPES).indexOf(value as VideoRateType) !== -1
133 }
134
135 function isVideoFile (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[]) {
136   // Should have files
137   if (!files) return false
138   if (isArray(files)) return false
139
140   // Should have videofile file
141   const videofile = files['videofile']
142   if (!videofile || videofile.length === 0) return false
143
144   // The file should exist
145   const file = videofile[0]
146   if (!file || !file.originalname) return false
147
148   return new RegExp('^video/(webm|mp4|ogg)$', 'i').test(file.mimetype)
149 }
150
151 function isVideoFileSizeValid (value: string) {
152   return exists(value) && validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.FILE_SIZE)
153 }
154
155 function isVideoFileResolutionValid (value: string) {
156   return exists(value) && validator.isInt(value + '')
157 }
158
159 function isVideoFileExtnameValid (value: string) {
160   return VIDEOS_CONSTRAINTS_FIELDS.EXTNAME.indexOf(value) !== -1
161 }
162
163 function isVideoFileInfoHashValid (value: string) {
164   return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.INFO_HASH)
165 }
166
167 function checkVideoExists (id: string, res: express.Response, callback: () => void) {
168   let promise: Promise<VideoInstance>
169   if (validator.isInt(id)) {
170     promise = db.Video.loadAndPopulateAccountAndServerAndTags(+id)
171   } else { // UUID
172     promise = db.Video.loadByUUIDAndPopulateAccountAndServerAndTags(id)
173   }
174
175   promise.then(video => {
176     if (!video) {
177       return res.status(404)
178                 .json({ error: 'Video not found' })
179                 .end()
180     }
181
182     res.locals.video = video
183     callback()
184   })
185   .catch(err => {
186     logger.error('Error in video request validator.', err)
187     return res.sendStatus(500)
188   })
189 }
190
191 // ---------------------------------------------------------------------------
192
193 export {
194   isVideoCategoryValid,
195   isVideoLicenceValid,
196   isVideoLanguageValid,
197   isVideoNSFWValid,
198   isVideoTruncatedDescriptionValid,
199   isVideoDescriptionValid,
200   isVideoDurationValid,
201   isVideoFileInfoHashValid,
202   isVideoNameValid,
203   isVideoTagsValid,
204   isVideoThumbnailValid,
205   isVideoThumbnailDataValid,
206   isVideoFileExtnameValid,
207   isVideoAbuseReasonValid,
208   isVideoFile,
209   isVideoViewsValid,
210   isVideoLikesValid,
211   isVideoRatingTypeValid,
212   isVideoDislikesValid,
213   isVideoEventCountValid,
214   isVideoFileSizeValid,
215   isVideoPrivacyValid,
216   isRemoteVideoPrivacyValid,
217   isVideoFileResolutionValid,
218   checkVideoExists,
219   isVideoTagValid,
220   isRemoteVideoCategoryValid,
221   isRemoteVideoLicenceValid,
222   isVideoUrlValid,
223   isRemoteVideoLanguageValid
224 }