diff options
Diffstat (limited to 'server/helpers')
-rw-r--r-- | server/helpers/activitypub.ts | 45 | ||||
-rw-r--r-- | server/helpers/custom-validators/activitypub/activity.ts | 34 | ||||
-rw-r--r-- | server/helpers/custom-validators/activitypub/index.ts | 1 | ||||
-rw-r--r-- | server/helpers/custom-validators/activitypub/misc.ts | 12 | ||||
-rw-r--r-- | server/helpers/custom-validators/activitypub/videos.ts | 223 | ||||
-rw-r--r-- | server/helpers/custom-validators/index.ts | 1 | ||||
-rw-r--r-- | server/helpers/custom-validators/video-authors.ts | 45 | ||||
-rw-r--r-- | server/helpers/custom-validators/videos.ts | 16 | ||||
-rw-r--r-- | server/helpers/requests.ts | 11 |
9 files changed, 190 insertions, 198 deletions
diff --git a/server/helpers/activitypub.ts b/server/helpers/activitypub.ts index ecb509b66..75de2278c 100644 --- a/server/helpers/activitypub.ts +++ b/server/helpers/activitypub.ts | |||
@@ -2,10 +2,48 @@ import * as url from 'url' | |||
2 | 2 | ||
3 | import { database as db } from '../initializers' | 3 | import { database as db } from '../initializers' |
4 | import { logger } from './logger' | 4 | import { logger } from './logger' |
5 | import { doRequest } from './requests' | 5 | import { doRequest, doRequestAndSaveToFile } from './requests' |
6 | import { isRemoteAccountValid } from './custom-validators' | 6 | import { isRemoteAccountValid } from './custom-validators' |
7 | import { ActivityPubActor } from '../../shared/models/activitypub/activitypub-actor' | 7 | import { ActivityPubActor } from '../../shared/models/activitypub/activitypub-actor' |
8 | import { ResultList } from '../../shared/models/result-list.model' | 8 | import { ResultList } from '../../shared/models/result-list.model' |
9 | import { CONFIG } from '../initializers/constants' | ||
10 | import { VideoInstance } from '../models/video/video-interface' | ||
11 | import { ActivityIconObject } from '../../shared/index' | ||
12 | import { join } from 'path' | ||
13 | |||
14 | function generateThumbnailFromUrl (video: VideoInstance, icon: ActivityIconObject) { | ||
15 | const thumbnailName = video.getThumbnailName() | ||
16 | const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, thumbnailName) | ||
17 | |||
18 | const options = { | ||
19 | method: 'GET', | ||
20 | uri: icon.url | ||
21 | } | ||
22 | return doRequestAndSaveToFile(options, thumbnailPath) | ||
23 | } | ||
24 | |||
25 | function getActivityPubUrl (type: 'video' | 'videoChannel', uuid: string) { | ||
26 | if (type === 'video') return CONFIG.WEBSERVER.URL + '/videos/watch/' + uuid | ||
27 | else if (type === 'videoChannel') return CONFIG.WEBSERVER.URL + '/video-channels/' + uuid | ||
28 | |||
29 | return '' | ||
30 | } | ||
31 | |||
32 | async function getOrCreateAccount (accountUrl: string) { | ||
33 | let account = await db.Account.loadByUrl(accountUrl) | ||
34 | |||
35 | // We don't have this account in our database, fetch it on remote | ||
36 | if (!account) { | ||
37 | const { account } = await fetchRemoteAccountAndCreatePod(accountUrl) | ||
38 | |||
39 | if (!account) throw new Error('Cannot fetch remote account.') | ||
40 | |||
41 | // Save our new account in database | ||
42 | await account.save() | ||
43 | } | ||
44 | |||
45 | return account | ||
46 | } | ||
9 | 47 | ||
10 | async function fetchRemoteAccountAndCreatePod (accountUrl: string) { | 48 | async function fetchRemoteAccountAndCreatePod (accountUrl: string) { |
11 | const options = { | 49 | const options = { |
@@ -100,7 +138,10 @@ function activityPubCollectionPagination (url: string, page: number, result: Res | |||
100 | export { | 138 | export { |
101 | fetchRemoteAccountAndCreatePod, | 139 | fetchRemoteAccountAndCreatePod, |
102 | activityPubContextify, | 140 | activityPubContextify, |
103 | activityPubCollectionPagination | 141 | activityPubCollectionPagination, |
142 | getActivityPubUrl, | ||
143 | generateThumbnailFromUrl, | ||
144 | getOrCreateAccount | ||
104 | } | 145 | } |
105 | 146 | ||
106 | // --------------------------------------------------------------------------- | 147 | // --------------------------------------------------------------------------- |
diff --git a/server/helpers/custom-validators/activitypub/activity.ts b/server/helpers/custom-validators/activitypub/activity.ts new file mode 100644 index 000000000..dd671c4cf --- /dev/null +++ b/server/helpers/custom-validators/activitypub/activity.ts | |||
@@ -0,0 +1,34 @@ | |||
1 | import * as validator from 'validator' | ||
2 | import { | ||
3 | isVideoChannelCreateActivityValid, | ||
4 | isVideoTorrentAddActivityValid, | ||
5 | isVideoTorrentUpdateActivityValid, | ||
6 | isVideoChannelUpdateActivityValid | ||
7 | } from './videos' | ||
8 | |||
9 | function isRootActivityValid (activity: any) { | ||
10 | return Array.isArray(activity['@context']) && | ||
11 | ( | ||
12 | (activity.type === 'Collection' || activity.type === 'OrderedCollection') && | ||
13 | validator.isInt(activity.totalItems, { min: 0 }) && | ||
14 | Array.isArray(activity.items) | ||
15 | ) || | ||
16 | ( | ||
17 | validator.isURL(activity.id) && | ||
18 | validator.isURL(activity.actor) | ||
19 | ) | ||
20 | } | ||
21 | |||
22 | function isActivityValid (activity: any) { | ||
23 | return isVideoTorrentAddActivityValid(activity) || | ||
24 | isVideoChannelCreateActivityValid(activity) || | ||
25 | isVideoTorrentUpdateActivityValid(activity) || | ||
26 | isVideoChannelUpdateActivityValid(activity) | ||
27 | } | ||
28 | |||
29 | // --------------------------------------------------------------------------- | ||
30 | |||
31 | export { | ||
32 | isRootActivityValid, | ||
33 | isActivityValid | ||
34 | } | ||
diff --git a/server/helpers/custom-validators/activitypub/index.ts b/server/helpers/custom-validators/activitypub/index.ts index 800f0ddf3..0eba06a7b 100644 --- a/server/helpers/custom-validators/activitypub/index.ts +++ b/server/helpers/custom-validators/activitypub/index.ts | |||
@@ -1,4 +1,5 @@ | |||
1 | export * from './account' | 1 | export * from './account' |
2 | export * from './activity' | ||
2 | export * from './signature' | 3 | export * from './signature' |
3 | export * from './misc' | 4 | export * from './misc' |
4 | export * from './videos' | 5 | export * from './videos' |
diff --git a/server/helpers/custom-validators/activitypub/misc.ts b/server/helpers/custom-validators/activitypub/misc.ts index 806d33483..f049f5a8c 100644 --- a/server/helpers/custom-validators/activitypub/misc.ts +++ b/server/helpers/custom-validators/activitypub/misc.ts | |||
@@ -12,6 +12,16 @@ function isActivityPubUrlValid (url: string) { | |||
12 | return exists(url) && validator.isURL(url, isURLOptions) | 12 | return exists(url) && validator.isURL(url, isURLOptions) |
13 | } | 13 | } |
14 | 14 | ||
15 | function isBaseActivityValid (activity: any, type: string) { | ||
16 | return Array.isArray(activity['@context']) && | ||
17 | activity.type === type && | ||
18 | validator.isURL(activity.id) && | ||
19 | validator.isURL(activity.actor) && | ||
20 | Array.isArray(activity.to) && | ||
21 | activity.to.every(t => validator.isURL(t)) | ||
22 | } | ||
23 | |||
15 | export { | 24 | export { |
16 | isActivityPubUrlValid | 25 | isActivityPubUrlValid, |
26 | isBaseActivityValid | ||
17 | } | 27 | } |
diff --git a/server/helpers/custom-validators/activitypub/videos.ts b/server/helpers/custom-validators/activitypub/videos.ts index e0ffba679..9233a1359 100644 --- a/server/helpers/custom-validators/activitypub/videos.ts +++ b/server/helpers/custom-validators/activitypub/videos.ts | |||
@@ -1,184 +1,117 @@ | |||
1 | import 'express-validator' | 1 | import * as validator from 'validator' |
2 | import { has, values } from 'lodash' | ||
3 | 2 | ||
4 | import { | 3 | import { |
5 | REQUEST_ENDPOINTS, | 4 | ACTIVITY_PUB |
6 | REQUEST_ENDPOINT_ACTIONS, | ||
7 | REQUEST_VIDEO_EVENT_TYPES | ||
8 | } from '../../../initializers' | 5 | } from '../../../initializers' |
9 | import { isArray, isDateValid, isUUIDValid } from '../misc' | 6 | import { isDateValid, isUUIDValid } from '../misc' |
10 | import { | 7 | import { |
11 | isVideoThumbnailDataValid, | ||
12 | isVideoAbuseReasonValid, | ||
13 | isVideoAbuseReporterUsernameValid, | ||
14 | isVideoViewsValid, | 8 | isVideoViewsValid, |
15 | isVideoLikesValid, | ||
16 | isVideoDislikesValid, | ||
17 | isVideoEventCountValid, | ||
18 | isRemoteVideoCategoryValid, | ||
19 | isRemoteVideoLicenceValid, | ||
20 | isRemoteVideoLanguageValid, | ||
21 | isVideoNSFWValid, | 9 | isVideoNSFWValid, |
22 | isVideoTruncatedDescriptionValid, | 10 | isVideoTruncatedDescriptionValid, |
23 | isVideoDurationValid, | 11 | isVideoDurationValid, |
24 | isVideoFileInfoHashValid, | ||
25 | isVideoNameValid, | 12 | isVideoNameValid, |
26 | isVideoTagsValid, | 13 | isVideoTagValid |
27 | isVideoFileExtnameValid, | ||
28 | isVideoFileResolutionValid | ||
29 | } from '../videos' | 14 | } from '../videos' |
30 | import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../video-channels' | 15 | import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../video-channels' |
31 | import { isVideoAuthorNameValid } from '../video-authors' | 16 | import { isBaseActivityValid } from './misc' |
32 | 17 | ||
33 | const ENDPOINT_ACTIONS = REQUEST_ENDPOINT_ACTIONS[REQUEST_ENDPOINTS.VIDEOS] | 18 | function isVideoTorrentAddActivityValid (activity: any) { |
34 | 19 | return isBaseActivityValid(activity, 'Add') && | |
35 | const checkers: { [ id: string ]: (obj: any) => boolean } = {} | 20 | isVideoTorrentObjectValid(activity.object) |
36 | checkers[ENDPOINT_ACTIONS.ADD_VIDEO] = checkAddVideo | 21 | } |
37 | checkers[ENDPOINT_ACTIONS.UPDATE_VIDEO] = checkUpdateVideo | 22 | |
38 | checkers[ENDPOINT_ACTIONS.REMOVE_VIDEO] = checkRemoveVideo | 23 | function isVideoTorrentUpdateActivityValid (activity: any) { |
39 | checkers[ENDPOINT_ACTIONS.REPORT_ABUSE] = checkReportVideo | 24 | return isBaseActivityValid(activity, 'Update') && |
40 | checkers[ENDPOINT_ACTIONS.ADD_CHANNEL] = checkAddVideoChannel | 25 | isVideoTorrentObjectValid(activity.object) |
41 | checkers[ENDPOINT_ACTIONS.UPDATE_CHANNEL] = checkUpdateVideoChannel | ||
42 | checkers[ENDPOINT_ACTIONS.REMOVE_CHANNEL] = checkRemoveVideoChannel | ||
43 | checkers[ENDPOINT_ACTIONS.ADD_AUTHOR] = checkAddAuthor | ||
44 | checkers[ENDPOINT_ACTIONS.REMOVE_AUTHOR] = checkRemoveAuthor | ||
45 | |||
46 | function removeBadRequestVideos (requests: any[]) { | ||
47 | for (let i = requests.length - 1; i >= 0 ; i--) { | ||
48 | const request = requests[i] | ||
49 | const video = request.data | ||
50 | |||
51 | if ( | ||
52 | !video || | ||
53 | checkers[request.type] === undefined || | ||
54 | checkers[request.type](video) === false | ||
55 | ) { | ||
56 | requests.splice(i, 1) | ||
57 | } | ||
58 | } | ||
59 | } | 26 | } |
60 | 27 | ||
61 | function removeBadRequestVideosQadu (requests: any[]) { | 28 | function isVideoTorrentObjectValid (video: any) { |
62 | for (let i = requests.length - 1; i >= 0 ; i--) { | 29 | return video.type === 'Video' && |
63 | const request = requests[i] | 30 | isVideoNameValid(video.name) && |
64 | const video = request.data | 31 | isVideoDurationValid(video.duration) && |
65 | 32 | isUUIDValid(video.uuid) && | |
66 | if ( | 33 | setValidRemoteTags(video) && |
67 | !video || | 34 | isRemoteIdentifierValid(video.category) && |
68 | ( | 35 | isRemoteIdentifierValid(video.licence) && |
69 | isUUIDValid(video.uuid) && | 36 | isRemoteIdentifierValid(video.language) && |
70 | (has(video, 'views') === false || isVideoViewsValid(video.views)) && | 37 | isVideoViewsValid(video.video) && |
71 | (has(video, 'likes') === false || isVideoLikesValid(video.likes)) && | 38 | isVideoNSFWValid(video.nsfw) && |
72 | (has(video, 'dislikes') === false || isVideoDislikesValid(video.dislikes)) | 39 | isDateValid(video.published) && |
73 | ) === false | 40 | isDateValid(video.updated) && |
74 | ) { | 41 | isRemoteVideoContentValid(video.mediaType, video.content) && |
75 | requests.splice(i, 1) | 42 | isRemoteVideoIconValid(video.icon) && |
76 | } | 43 | setValidRemoteVideoUrls(video.url) |
77 | } | ||
78 | } | 44 | } |
79 | 45 | ||
80 | function removeBadRequestVideosEvents (requests: any[]) { | 46 | function isVideoChannelCreateActivityValid (activity: any) { |
81 | for (let i = requests.length - 1; i >= 0 ; i--) { | 47 | return isBaseActivityValid(activity, 'Create') && |
82 | const request = requests[i] | 48 | isVideoChannelObjectValid(activity.object) |
83 | const eventData = request.data | 49 | } |
84 | 50 | ||
85 | if ( | 51 | function isVideoChannelUpdateActivityValid (activity: any) { |
86 | !eventData || | 52 | return isBaseActivityValid(activity, 'Update') && |
87 | ( | 53 | isVideoChannelObjectValid(activity.object) |
88 | isUUIDValid(eventData.uuid) && | 54 | } |
89 | values(REQUEST_VIDEO_EVENT_TYPES).indexOf(eventData.eventType) !== -1 && | 55 | |
90 | isVideoEventCountValid(eventData.count) | 56 | function isVideoChannelObjectValid (videoChannel: any) { |
91 | ) === false | 57 | return videoChannel.type === 'VideoChannel' && |
92 | ) { | 58 | isVideoChannelNameValid(videoChannel.name) && |
93 | requests.splice(i, 1) | 59 | isVideoChannelDescriptionValid(videoChannel.description) && |
94 | } | 60 | isUUIDValid(videoChannel.uuid) |
95 | } | ||
96 | } | 61 | } |
97 | 62 | ||
98 | // --------------------------------------------------------------------------- | 63 | // --------------------------------------------------------------------------- |
99 | 64 | ||
100 | export { | 65 | export { |
101 | removeBadRequestVideos, | 66 | isVideoTorrentAddActivityValid, |
102 | removeBadRequestVideosQadu, | 67 | isVideoChannelCreateActivityValid, |
103 | removeBadRequestVideosEvents | 68 | isVideoTorrentUpdateActivityValid, |
69 | isVideoChannelUpdateActivityValid | ||
104 | } | 70 | } |
105 | 71 | ||
106 | // --------------------------------------------------------------------------- | 72 | // --------------------------------------------------------------------------- |
107 | 73 | ||
108 | function isCommonVideoAttributesValid (video: any) { | 74 | function setValidRemoteTags (video: any) { |
109 | return isDateValid(video.createdAt) && | 75 | if (Array.isArray(video.tag) === false) return false |
110 | isDateValid(video.updatedAt) && | ||
111 | isRemoteVideoCategoryValid(video.category) && | ||
112 | isRemoteVideoLicenceValid(video.licence) && | ||
113 | isRemoteVideoLanguageValid(video.language) && | ||
114 | isVideoNSFWValid(video.nsfw) && | ||
115 | isVideoTruncatedDescriptionValid(video.truncatedDescription) && | ||
116 | isVideoDurationValid(video.duration) && | ||
117 | isVideoNameValid(video.name) && | ||
118 | isVideoTagsValid(video.tags) && | ||
119 | isUUIDValid(video.uuid) && | ||
120 | isVideoViewsValid(video.views) && | ||
121 | isVideoLikesValid(video.likes) && | ||
122 | isVideoDislikesValid(video.dislikes) && | ||
123 | isArray(video.files) && | ||
124 | video.files.every(videoFile => { | ||
125 | if (!videoFile) return false | ||
126 | |||
127 | return ( | ||
128 | isVideoFileInfoHashValid(videoFile.infoHash) && | ||
129 | isVideoFileExtnameValid(videoFile.extname) && | ||
130 | isVideoFileResolutionValid(videoFile.resolution) | ||
131 | ) | ||
132 | }) | ||
133 | } | ||
134 | 76 | ||
135 | function checkAddVideo (video: any) { | 77 | const newTag = video.tag.filter(t => { |
136 | return isCommonVideoAttributesValid(video) && | 78 | return t.type === 'Hashtag' && |
137 | isUUIDValid(video.channelUUID) && | 79 | isVideoTagValid(t.name) |
138 | isVideoThumbnailDataValid(video.thumbnailData) | 80 | }) |
139 | } | ||
140 | 81 | ||
141 | function checkUpdateVideo (video: any) { | 82 | video.tag = newTag |
142 | return isCommonVideoAttributesValid(video) | 83 | return true |
143 | } | 84 | } |
144 | 85 | ||
145 | function checkRemoveVideo (video: any) { | 86 | function isRemoteIdentifierValid (data: any) { |
146 | return isUUIDValid(video.uuid) | 87 | return validator.isInt(data.identifier, { min: 0 }) |
147 | } | 88 | } |
148 | 89 | ||
149 | function checkReportVideo (abuse: any) { | 90 | function isRemoteVideoContentValid (mediaType: string, content: string) { |
150 | return isUUIDValid(abuse.videoUUID) && | 91 | return mediaType === 'text/markdown' && isVideoTruncatedDescriptionValid(content) |
151 | isVideoAbuseReasonValid(abuse.reportReason) && | ||
152 | isVideoAbuseReporterUsernameValid(abuse.reporterUsername) | ||
153 | } | 92 | } |
154 | 93 | ||
155 | function checkAddVideoChannel (videoChannel: any) { | 94 | function isRemoteVideoIconValid (icon: any) { |
156 | return isUUIDValid(videoChannel.uuid) && | 95 | return icon.type === 'Image' && |
157 | isVideoChannelNameValid(videoChannel.name) && | 96 | validator.isURL(icon.url) && |
158 | isVideoChannelDescriptionValid(videoChannel.description) && | 97 | icon.mediaType === 'image/jpeg' && |
159 | isDateValid(videoChannel.createdAt) && | 98 | validator.isInt(icon.width, { min: 0 }) && |
160 | isDateValid(videoChannel.updatedAt) && | 99 | validator.isInt(icon.height, { min: 0 }) |
161 | isUUIDValid(videoChannel.ownerUUID) | ||
162 | } | 100 | } |
163 | 101 | ||
164 | function checkUpdateVideoChannel (videoChannel: any) { | 102 | function setValidRemoteVideoUrls (video: any) { |
165 | return isUUIDValid(videoChannel.uuid) && | 103 | if (Array.isArray(video.url) === false) return false |
166 | isVideoChannelNameValid(videoChannel.name) && | ||
167 | isVideoChannelDescriptionValid(videoChannel.description) && | ||
168 | isDateValid(videoChannel.createdAt) && | ||
169 | isDateValid(videoChannel.updatedAt) && | ||
170 | isUUIDValid(videoChannel.ownerUUID) | ||
171 | } | ||
172 | 104 | ||
173 | function checkRemoveVideoChannel (videoChannel: any) { | 105 | const newUrl = video.url.filter(u => isRemoteVideoUrlValid(u)) |
174 | return isUUIDValid(videoChannel.uuid) | 106 | video.url = newUrl |
175 | } | ||
176 | 107 | ||
177 | function checkAddAuthor (author: any) { | 108 | return true |
178 | return isUUIDValid(author.uuid) && | ||
179 | isVideoAuthorNameValid(author.name) | ||
180 | } | 109 | } |
181 | 110 | ||
182 | function checkRemoveAuthor (author: any) { | 111 | function isRemoteVideoUrlValid (url: any) { |
183 | return isUUIDValid(author.uuid) | 112 | return url.type === 'Link' && |
113 | ACTIVITY_PUB.VIDEO_URL_MIME_TYPES.indexOf(url.mimeType) !== -1 && | ||
114 | validator.isURL(url.url) && | ||
115 | validator.isInt(url.width, { min: 0 }) && | ||
116 | validator.isInt(url.size, { min: 0 }) | ||
184 | } | 117 | } |
diff --git a/server/helpers/custom-validators/index.ts b/server/helpers/custom-validators/index.ts index 869b08870..58a40249b 100644 --- a/server/helpers/custom-validators/index.ts +++ b/server/helpers/custom-validators/index.ts | |||
@@ -3,6 +3,5 @@ export * from './misc' | |||
3 | export * from './pods' | 3 | export * from './pods' |
4 | export * from './pods' | 4 | export * from './pods' |
5 | export * from './users' | 5 | export * from './users' |
6 | export * from './video-authors' | ||
7 | export * from './video-channels' | 6 | export * from './video-channels' |
8 | export * from './videos' | 7 | export * from './videos' |
diff --git a/server/helpers/custom-validators/video-authors.ts b/server/helpers/custom-validators/video-authors.ts deleted file mode 100644 index 48ca9b200..000000000 --- a/server/helpers/custom-validators/video-authors.ts +++ /dev/null | |||
@@ -1,45 +0,0 @@ | |||
1 | import * as Promise from 'bluebird' | ||
2 | import * as validator from 'validator' | ||
3 | import * as express from 'express' | ||
4 | import 'express-validator' | ||
5 | |||
6 | import { database as db } from '../../initializers' | ||
7 | import { AuthorInstance } from '../../models' | ||
8 | import { logger } from '../logger' | ||
9 | |||
10 | import { isUserUsernameValid } from './users' | ||
11 | |||
12 | function isVideoAuthorNameValid (value: string) { | ||
13 | return isUserUsernameValid(value) | ||
14 | } | ||
15 | |||
16 | function checkVideoAuthorExists (id: string, res: express.Response, callback: () => void) { | ||
17 | let promise: Promise<AuthorInstance> | ||
18 | if (validator.isInt(id)) { | ||
19 | promise = db.Author.load(+id) | ||
20 | } else { // UUID | ||
21 | promise = db.Author.loadByUUID(id) | ||
22 | } | ||
23 | |||
24 | promise.then(author => { | ||
25 | if (!author) { | ||
26 | return res.status(404) | ||
27 | .json({ error: 'Video author not found' }) | ||
28 | .end() | ||
29 | } | ||
30 | |||
31 | res.locals.author = author | ||
32 | callback() | ||
33 | }) | ||
34 | .catch(err => { | ||
35 | logger.error('Error in video author request validator.', err) | ||
36 | return res.sendStatus(500) | ||
37 | }) | ||
38 | } | ||
39 | |||
40 | // --------------------------------------------------------------------------- | ||
41 | |||
42 | export { | ||
43 | checkVideoAuthorExists, | ||
44 | isVideoAuthorNameValid | ||
45 | } | ||
diff --git a/server/helpers/custom-validators/videos.ts b/server/helpers/custom-validators/videos.ts index f3fdcaf2d..83407f17b 100644 --- a/server/helpers/custom-validators/videos.ts +++ b/server/helpers/custom-validators/videos.ts | |||
@@ -73,19 +73,26 @@ function isVideoDescriptionValid (value: string) { | |||
73 | } | 73 | } |
74 | 74 | ||
75 | function isVideoDurationValid (value: string) { | 75 | function isVideoDurationValid (value: string) { |
76 | return exists(value) && validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.DURATION) | 76 | // https://www.w3.org/TR/activitystreams-vocabulary/#dfn-duration |
77 | return exists(value) && | ||
78 | typeof value === 'string' && | ||
79 | value.startsWith('PT') && | ||
80 | value.endsWith('S') && | ||
81 | validator.isInt(value.replace(/[^0-9]+/, ''), VIDEOS_CONSTRAINTS_FIELDS.DURATION) | ||
77 | } | 82 | } |
78 | 83 | ||
79 | function isVideoNameValid (value: string) { | 84 | function isVideoNameValid (value: string) { |
80 | return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.NAME) | 85 | return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.NAME) |
81 | } | 86 | } |
82 | 87 | ||
88 | function isVideoTagValid (tag: string) { | ||
89 | return exists(tag) && validator.isLength(tag, VIDEOS_CONSTRAINTS_FIELDS.TAG) | ||
90 | } | ||
91 | |||
83 | function isVideoTagsValid (tags: string[]) { | 92 | function isVideoTagsValid (tags: string[]) { |
84 | return isArray(tags) && | 93 | return isArray(tags) && |
85 | validator.isInt(tags.length.toString(), VIDEOS_CONSTRAINTS_FIELDS.TAGS) && | 94 | validator.isInt(tags.length.toString(), VIDEOS_CONSTRAINTS_FIELDS.TAGS) && |
86 | tags.every(tag => { | 95 | tags.every(tag => isVideoTagValid(tag)) |
87 | return exists(tag) && validator.isLength(tag, VIDEOS_CONSTRAINTS_FIELDS.TAG) | ||
88 | }) | ||
89 | } | 96 | } |
90 | 97 | ||
91 | function isVideoThumbnailValid (value: string) { | 98 | function isVideoThumbnailValid (value: string) { |
@@ -209,6 +216,7 @@ export { | |||
209 | isRemoteVideoPrivacyValid, | 216 | isRemoteVideoPrivacyValid, |
210 | isVideoFileResolutionValid, | 217 | isVideoFileResolutionValid, |
211 | checkVideoExists, | 218 | checkVideoExists, |
219 | isVideoTagValid, | ||
212 | isRemoteVideoCategoryValid, | 220 | isRemoteVideoCategoryValid, |
213 | isRemoteVideoLicenceValid, | 221 | isRemoteVideoLicenceValid, |
214 | isRemoteVideoLanguageValid | 222 | isRemoteVideoLanguageValid |
diff --git a/server/helpers/requests.ts b/server/helpers/requests.ts index 8c4c983f7..31cedd768 100644 --- a/server/helpers/requests.ts +++ b/server/helpers/requests.ts | |||
@@ -10,6 +10,7 @@ import { | |||
10 | import { PodInstance } from '../models' | 10 | import { PodInstance } from '../models' |
11 | import { PodSignature } from '../../shared' | 11 | import { PodSignature } from '../../shared' |
12 | import { signObject } from './peertube-crypto' | 12 | import { signObject } from './peertube-crypto' |
13 | import { createWriteStream } from 'fs' | ||
13 | 14 | ||
14 | function doRequest (requestOptions: request.CoreOptions & request.UriOptions) { | 15 | function doRequest (requestOptions: request.CoreOptions & request.UriOptions) { |
15 | return new Promise<{ response: request.RequestResponse, body: any }>((res, rej) => { | 16 | return new Promise<{ response: request.RequestResponse, body: any }>((res, rej) => { |
@@ -17,6 +18,15 @@ function doRequest (requestOptions: request.CoreOptions & request.UriOptions) { | |||
17 | }) | 18 | }) |
18 | } | 19 | } |
19 | 20 | ||
21 | function doRequestAndSaveToFile (requestOptions: request.CoreOptions & request.UriOptions, destPath: string) { | ||
22 | return new Promise<request.RequestResponse>((res, rej) => { | ||
23 | request(requestOptions) | ||
24 | .on('response', response => res(response as request.RequestResponse)) | ||
25 | .on('error', err => rej(err)) | ||
26 | .pipe(createWriteStream(destPath)) | ||
27 | }) | ||
28 | } | ||
29 | |||
20 | type MakeRetryRequestParams = { | 30 | type MakeRetryRequestParams = { |
21 | url: string, | 31 | url: string, |
22 | method: 'GET' | 'POST', | 32 | method: 'GET' | 'POST', |
@@ -88,6 +98,7 @@ function makeSecureRequest (params: MakeSecureRequestParams) { | |||
88 | 98 | ||
89 | export { | 99 | export { |
90 | doRequest, | 100 | doRequest, |
101 | doRequestAndSaveToFile, | ||
91 | makeRetryRequest, | 102 | makeRetryRequest, |
92 | makeSecureRequest | 103 | makeSecureRequest |
93 | } | 104 | } |