diff options
Diffstat (limited to 'server/helpers')
40 files changed, 557 insertions, 164 deletions
diff --git a/server/helpers/activitypub.ts b/server/helpers/activitypub.ts index f1430055f..951a25669 100644 --- a/server/helpers/activitypub.ts +++ b/server/helpers/activitypub.ts | |||
@@ -2,7 +2,7 @@ import * as Bluebird from 'bluebird' | |||
2 | import * as validator from 'validator' | 2 | import * as validator from 'validator' |
3 | import { ResultList } from '../../shared/models' | 3 | import { ResultList } from '../../shared/models' |
4 | import { Activity } from '../../shared/models/activitypub' | 4 | import { Activity } from '../../shared/models/activitypub' |
5 | import { ACTIVITY_PUB } from '../initializers' | 5 | import { ACTIVITY_PUB } from '../initializers/constants' |
6 | import { ActorModel } from '../models/activitypub/actor' | 6 | import { ActorModel } from '../models/activitypub/actor' |
7 | import { signJsonLDObject } from './peertube-crypto' | 7 | import { signJsonLDObject } from './peertube-crypto' |
8 | import { pageToStartAndCount } from './core-utils' | 8 | import { pageToStartAndCount } from './core-utils' |
@@ -15,7 +15,7 @@ function activityPubContextify <T> (data: T) { | |||
15 | 'https://w3id.org/security/v1', | 15 | 'https://w3id.org/security/v1', |
16 | { | 16 | { |
17 | RsaSignature2017: 'https://w3id.org/security#RsaSignature2017', | 17 | RsaSignature2017: 'https://w3id.org/security#RsaSignature2017', |
18 | pt: 'https://joinpeertube.org/ns', | 18 | pt: 'https://joinpeertube.org/ns#', |
19 | sc: 'http://schema.org#', | 19 | sc: 'http://schema.org#', |
20 | Hashtag: 'as:Hashtag', | 20 | Hashtag: 'as:Hashtag', |
21 | uuid: 'sc:identifier', | 21 | uuid: 'sc:identifier', |
@@ -24,15 +24,54 @@ function activityPubContextify <T> (data: T) { | |||
24 | subtitleLanguage: 'sc:subtitleLanguage', | 24 | subtitleLanguage: 'sc:subtitleLanguage', |
25 | sensitive: 'as:sensitive', | 25 | sensitive: 'as:sensitive', |
26 | language: 'sc:inLanguage', | 26 | language: 'sc:inLanguage', |
27 | views: 'sc:Number', | ||
28 | state: 'sc:Number', | ||
29 | size: 'sc:Number', | ||
30 | fps: 'sc:Number', | ||
31 | commentsEnabled: 'sc:Boolean', | ||
32 | waitTranscoding: 'sc:Boolean', | ||
33 | expires: 'sc:expires', | 27 | expires: 'sc:expires', |
34 | support: 'sc:Text', | 28 | CacheFile: 'pt:CacheFile', |
35 | CacheFile: 'pt:CacheFile' | 29 | Infohash: 'pt:Infohash', |
30 | originallyPublishedAt: 'sc:datePublished', | ||
31 | views: { | ||
32 | '@type': 'sc:Number', | ||
33 | '@id': 'pt:views' | ||
34 | }, | ||
35 | state: { | ||
36 | '@type': 'sc:Number', | ||
37 | '@id': 'pt:state' | ||
38 | }, | ||
39 | size: { | ||
40 | '@type': 'sc:Number', | ||
41 | '@id': 'pt:size' | ||
42 | }, | ||
43 | fps: { | ||
44 | '@type': 'sc:Number', | ||
45 | '@id': 'pt:fps' | ||
46 | }, | ||
47 | startTimestamp: { | ||
48 | '@type': 'sc:Number', | ||
49 | '@id': 'pt:startTimestamp' | ||
50 | }, | ||
51 | stopTimestamp: { | ||
52 | '@type': 'sc:Number', | ||
53 | '@id': 'pt:stopTimestamp' | ||
54 | }, | ||
55 | position: { | ||
56 | '@type': 'sc:Number', | ||
57 | '@id': 'pt:position' | ||
58 | }, | ||
59 | commentsEnabled: { | ||
60 | '@type': 'sc:Boolean', | ||
61 | '@id': 'pt:commentsEnabled' | ||
62 | }, | ||
63 | downloadEnabled: { | ||
64 | '@type': 'sc:Boolean', | ||
65 | '@id': 'pt:downloadEnabled' | ||
66 | }, | ||
67 | waitTranscoding: { | ||
68 | '@type': 'sc:Boolean', | ||
69 | '@id': 'pt:waitTranscoding' | ||
70 | }, | ||
71 | support: { | ||
72 | '@type': 'sc:Text', | ||
73 | '@id': 'pt:support' | ||
74 | } | ||
36 | }, | 75 | }, |
37 | { | 76 | { |
38 | likes: { | 77 | likes: { |
@@ -43,6 +82,10 @@ function activityPubContextify <T> (data: T) { | |||
43 | '@id': 'as:dislikes', | 82 | '@id': 'as:dislikes', |
44 | '@type': '@id' | 83 | '@type': '@id' |
45 | }, | 84 | }, |
85 | playlists: { | ||
86 | '@id': 'pt:playlists', | ||
87 | '@type': '@id' | ||
88 | }, | ||
46 | shares: { | 89 | shares: { |
47 | '@id': 'as:shares', | 90 | '@id': 'as:shares', |
48 | '@type': '@id' | 91 | '@type': '@id' |
@@ -64,7 +107,7 @@ async function activityPubCollectionPagination (baseUrl: string, handler: Activi | |||
64 | 107 | ||
65 | return { | 108 | return { |
66 | id: baseUrl, | 109 | id: baseUrl, |
67 | type: 'OrderedCollection', | 110 | type: 'OrderedCollectionPage', |
68 | totalItems: result.total, | 111 | totalItems: result.total, |
69 | first: baseUrl + '?page=1' | 112 | first: baseUrl + '?page=1' |
70 | } | 113 | } |
diff --git a/server/helpers/audit-logger.ts b/server/helpers/audit-logger.ts index 00311fce1..f536da439 100644 --- a/server/helpers/audit-logger.ts +++ b/server/helpers/audit-logger.ts | |||
@@ -4,15 +4,14 @@ import { diff } from 'deep-object-diff' | |||
4 | import { chain } from 'lodash' | 4 | import { chain } from 'lodash' |
5 | import * as flatten from 'flat' | 5 | import * as flatten from 'flat' |
6 | import * as winston from 'winston' | 6 | import * as winston from 'winston' |
7 | import { CONFIG } from '../initializers' | ||
8 | import { jsonLoggerFormat, labelFormatter } from './logger' | 7 | import { jsonLoggerFormat, labelFormatter } from './logger' |
9 | import { VideoDetails, User, VideoChannel, VideoAbuse, VideoImport } from '../../shared' | 8 | import { User, VideoAbuse, VideoChannel, VideoDetails, VideoImport } from '../../shared' |
10 | import { VideoComment } from '../../shared/models/videos/video-comment.model' | 9 | import { VideoComment } from '../../shared/models/videos/video-comment.model' |
11 | import { CustomConfig } from '../../shared/models/server/custom-config.model' | 10 | import { CustomConfig } from '../../shared/models/server/custom-config.model' |
12 | import { UserModel } from '../models/account/user' | 11 | import { CONFIG } from '../initializers/config' |
13 | 12 | ||
14 | function getAuditIdFromRes (res: express.Response) { | 13 | function getAuditIdFromRes (res: express.Response) { |
15 | return (res.locals.oauth.token.User as UserModel).username | 14 | return res.locals.oauth.token.User.username |
16 | } | 15 | } |
17 | 16 | ||
18 | enum AUDIT_TYPE { | 17 | enum AUDIT_TYPE { |
@@ -117,7 +116,8 @@ const videoKeysToKeep = [ | |||
117 | 'channel-uuid', | 116 | 'channel-uuid', |
118 | 'channel-name', | 117 | 'channel-name', |
119 | 'support', | 118 | 'support', |
120 | 'commentsEnabled' | 119 | 'commentsEnabled', |
120 | 'downloadEnabled' | ||
121 | ] | 121 | ] |
122 | class VideoAuditView extends EntityAuditView { | 122 | class VideoAuditView extends EntityAuditView { |
123 | constructor (private video: VideoDetails) { | 123 | constructor (private video: VideoDetails) { |
diff --git a/server/helpers/captions-utils.ts b/server/helpers/captions-utils.ts index 0fb11a125..7174d4654 100644 --- a/server/helpers/captions-utils.ts +++ b/server/helpers/captions-utils.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { join } from 'path' | 1 | import { join } from 'path' |
2 | import { CONFIG } from '../initializers' | 2 | import { CONFIG } from '../initializers/config' |
3 | import { VideoCaptionModel } from '../models/video/video-caption' | 3 | import { VideoCaptionModel } from '../models/video/video-caption' |
4 | import * as srt2vtt from 'srt-to-vtt' | 4 | import * as srt2vtt from 'srt-to-vtt' |
5 | import { createReadStream, createWriteStream, remove, move } from 'fs-extra' | 5 | import { createReadStream, createWriteStream, remove, move } from 'fs-extra' |
diff --git a/server/helpers/core-utils.ts b/server/helpers/core-utils.ts index 3fb824e36..305d3b71e 100644 --- a/server/helpers/core-utils.ts +++ b/server/helpers/core-utils.ts | |||
@@ -11,14 +11,13 @@ import * as pem from 'pem' | |||
11 | import { URL } from 'url' | 11 | import { URL } from 'url' |
12 | import { truncate } from 'lodash' | 12 | import { truncate } from 'lodash' |
13 | import { exec } from 'child_process' | 13 | import { exec } from 'child_process' |
14 | import { isArray } from './custom-validators/misc' | ||
15 | 14 | ||
16 | const objectConverter = (oldObject: any, keyConverter: (e: string) => string, valueConverter: (e: any) => any) => { | 15 | const objectConverter = (oldObject: any, keyConverter: (e: string) => string, valueConverter: (e: any) => any) => { |
17 | if (!oldObject || typeof oldObject !== 'object') { | 16 | if (!oldObject || typeof oldObject !== 'object') { |
18 | return valueConverter(oldObject) | 17 | return valueConverter(oldObject) |
19 | } | 18 | } |
20 | 19 | ||
21 | if (isArray(oldObject)) { | 20 | if (Array.isArray(oldObject)) { |
22 | return oldObject.map(e => objectConverter(e, keyConverter, valueConverter)) | 21 | return oldObject.map(e => objectConverter(e, keyConverter, valueConverter)) |
23 | } | 22 | } |
24 | 23 | ||
@@ -41,7 +40,7 @@ const timeTable = { | |||
41 | month: 3600000 * 24 * 30 | 40 | month: 3600000 * 24 * 30 |
42 | } | 41 | } |
43 | 42 | ||
44 | export function parseDuration (duration: number | string): number { | 43 | export function parseDurationToMs (duration: number | string): number { |
45 | if (typeof duration === 'number') return duration | 44 | if (typeof duration === 'number') return duration |
46 | 45 | ||
47 | if (typeof duration === 'string') { | 46 | if (typeof duration === 'string') { |
@@ -58,7 +57,7 @@ export function parseDuration (duration: number | string): number { | |||
58 | } | 57 | } |
59 | } | 58 | } |
60 | 59 | ||
61 | throw new Error('Duration could not be properly parsed') | 60 | throw new Error(`Duration ${duration} could not be properly parsed`) |
62 | } | 61 | } |
63 | 62 | ||
64 | export function parseBytes (value: string | number): number { | 63 | export function parseBytes (value: string | number): number { |
@@ -193,10 +192,14 @@ function peertubeTruncate (str: string, maxLength: number) { | |||
193 | return truncate(str, options) | 192 | return truncate(str, options) |
194 | } | 193 | } |
195 | 194 | ||
196 | function sha256 (str: string, encoding: HexBase64Latin1Encoding = 'hex') { | 195 | function sha256 (str: string | Buffer, encoding: HexBase64Latin1Encoding = 'hex') { |
197 | return createHash('sha256').update(str).digest(encoding) | 196 | return createHash('sha256').update(str).digest(encoding) |
198 | } | 197 | } |
199 | 198 | ||
199 | function sha1 (str: string | Buffer, encoding: HexBase64Latin1Encoding = 'hex') { | ||
200 | return createHash('sha1').update(str).digest(encoding) | ||
201 | } | ||
202 | |||
200 | function promisify0<A> (func: (cb: (err: any, result: A) => void) => void): () => Promise<A> { | 203 | function promisify0<A> (func: (cb: (err: any, result: A) => void) => void): () => Promise<A> { |
201 | return function promisified (): Promise<A> { | 204 | return function promisified (): Promise<A> { |
202 | return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => { | 205 | return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => { |
@@ -262,7 +265,9 @@ export { | |||
262 | sanitizeHost, | 265 | sanitizeHost, |
263 | buildPath, | 266 | buildPath, |
264 | peertubeTruncate, | 267 | peertubeTruncate, |
268 | |||
265 | sha256, | 269 | sha256, |
270 | sha1, | ||
266 | 271 | ||
267 | promisify0, | 272 | promisify0, |
268 | promisify1, | 273 | promisify1, |
diff --git a/server/helpers/custom-jsonld-signature.ts b/server/helpers/custom-jsonld-signature.ts index 27a187db1..a3bceb047 100644 --- a/server/helpers/custom-jsonld-signature.ts +++ b/server/helpers/custom-jsonld-signature.ts | |||
@@ -1,13 +1,77 @@ | |||
1 | import * as AsyncLRU from 'async-lru' | 1 | import * as AsyncLRU from 'async-lru' |
2 | import * as jsonld from 'jsonld' | 2 | import * as jsonld from 'jsonld' |
3 | import * as jsig from 'jsonld-signatures' | 3 | import * as jsig from 'jsonld-signatures' |
4 | import { logger } from './logger' | ||
5 | |||
6 | const CACHE = { | ||
7 | 'https://w3id.org/security/v1': { | ||
8 | '@context': { | ||
9 | 'id': '@id', | ||
10 | 'type': '@type', | ||
11 | |||
12 | 'dc': 'http://purl.org/dc/terms/', | ||
13 | 'sec': 'https://w3id.org/security#', | ||
14 | 'xsd': 'http://www.w3.org/2001/XMLSchema#', | ||
15 | |||
16 | 'EcdsaKoblitzSignature2016': 'sec:EcdsaKoblitzSignature2016', | ||
17 | 'Ed25519Signature2018': 'sec:Ed25519Signature2018', | ||
18 | 'EncryptedMessage': 'sec:EncryptedMessage', | ||
19 | 'GraphSignature2012': 'sec:GraphSignature2012', | ||
20 | 'LinkedDataSignature2015': 'sec:LinkedDataSignature2015', | ||
21 | 'LinkedDataSignature2016': 'sec:LinkedDataSignature2016', | ||
22 | 'CryptographicKey': 'sec:Key', | ||
23 | |||
24 | 'authenticationTag': 'sec:authenticationTag', | ||
25 | 'canonicalizationAlgorithm': 'sec:canonicalizationAlgorithm', | ||
26 | 'cipherAlgorithm': 'sec:cipherAlgorithm', | ||
27 | 'cipherData': 'sec:cipherData', | ||
28 | 'cipherKey': 'sec:cipherKey', | ||
29 | 'created': { '@id': 'dc:created', '@type': 'xsd:dateTime' }, | ||
30 | 'creator': { '@id': 'dc:creator', '@type': '@id' }, | ||
31 | 'digestAlgorithm': 'sec:digestAlgorithm', | ||
32 | 'digestValue': 'sec:digestValue', | ||
33 | 'domain': 'sec:domain', | ||
34 | 'encryptionKey': 'sec:encryptionKey', | ||
35 | 'expiration': { '@id': 'sec:expiration', '@type': 'xsd:dateTime' }, | ||
36 | 'expires': { '@id': 'sec:expiration', '@type': 'xsd:dateTime' }, | ||
37 | 'initializationVector': 'sec:initializationVector', | ||
38 | 'iterationCount': 'sec:iterationCount', | ||
39 | 'nonce': 'sec:nonce', | ||
40 | 'normalizationAlgorithm': 'sec:normalizationAlgorithm', | ||
41 | 'owner': { '@id': 'sec:owner', '@type': '@id' }, | ||
42 | 'password': 'sec:password', | ||
43 | 'privateKey': { '@id': 'sec:privateKey', '@type': '@id' }, | ||
44 | 'privateKeyPem': 'sec:privateKeyPem', | ||
45 | 'publicKey': { '@id': 'sec:publicKey', '@type': '@id' }, | ||
46 | 'publicKeyBase58': 'sec:publicKeyBase58', | ||
47 | 'publicKeyPem': 'sec:publicKeyPem', | ||
48 | 'publicKeyWif': 'sec:publicKeyWif', | ||
49 | 'publicKeyService': { '@id': 'sec:publicKeyService', '@type': '@id' }, | ||
50 | 'revoked': { '@id': 'sec:revoked', '@type': 'xsd:dateTime' }, | ||
51 | 'salt': 'sec:salt', | ||
52 | 'signature': 'sec:signature', | ||
53 | 'signatureAlgorithm': 'sec:signingAlgorithm', | ||
54 | 'signatureValue': 'sec:signatureValue' | ||
55 | } | ||
56 | } | ||
57 | } | ||
4 | 58 | ||
5 | const nodeDocumentLoader = jsonld.documentLoaders.node() | 59 | const nodeDocumentLoader = jsonld.documentLoaders.node() |
6 | 60 | ||
7 | const lru = new AsyncLRU({ | 61 | const lru = new AsyncLRU({ |
8 | max: 10, | 62 | max: 10, |
9 | load: (key, cb) => { | 63 | load: (url, cb) => { |
10 | nodeDocumentLoader(key, cb) | 64 | if (CACHE[ url ] !== undefined) { |
65 | logger.debug('Using cache for JSON-LD %s.', url) | ||
66 | |||
67 | return cb(null, { | ||
68 | contextUrl: null, | ||
69 | document: CACHE[ url ], | ||
70 | documentUrl: url | ||
71 | }) | ||
72 | } | ||
73 | |||
74 | nodeDocumentLoader(url, cb) | ||
11 | } | 75 | } |
12 | }) | 76 | }) |
13 | 77 | ||
diff --git a/server/helpers/custom-validators/accounts.ts b/server/helpers/custom-validators/accounts.ts index 191de1496..146c7708e 100644 --- a/server/helpers/custom-validators/accounts.ts +++ b/server/helpers/custom-validators/accounts.ts | |||
@@ -5,7 +5,6 @@ import * as validator from 'validator' | |||
5 | import { AccountModel } from '../../models/account/account' | 5 | import { AccountModel } from '../../models/account/account' |
6 | import { isUserDescriptionValid, isUserUsernameValid } from './users' | 6 | import { isUserDescriptionValid, isUserUsernameValid } from './users' |
7 | import { exists } from './misc' | 7 | import { exists } from './misc' |
8 | import { CONFIG } from '../../initializers' | ||
9 | 8 | ||
10 | function isAccountNameValid (value: string) { | 9 | function isAccountNameValid (value: string) { |
11 | return isUserUsernameValid(value) | 10 | return isUserUsernameValid(value) |
@@ -19,7 +18,7 @@ function isAccountDescriptionValid (value: string) { | |||
19 | return isUserDescriptionValid(value) | 18 | return isUserDescriptionValid(value) |
20 | } | 19 | } |
21 | 20 | ||
22 | function isAccountIdExist (id: number | string, res: Response, sendNotFound = true) { | 21 | function doesAccountIdExist (id: number | string, res: Response, sendNotFound = true) { |
23 | let promise: Bluebird<AccountModel> | 22 | let promise: Bluebird<AccountModel> |
24 | 23 | ||
25 | if (validator.isInt('' + id)) { | 24 | if (validator.isInt('' + id)) { |
@@ -28,26 +27,20 @@ function isAccountIdExist (id: number | string, res: Response, sendNotFound = tr | |||
28 | promise = AccountModel.loadByUUID('' + id) | 27 | promise = AccountModel.loadByUUID('' + id) |
29 | } | 28 | } |
30 | 29 | ||
31 | return isAccountExist(promise, res, sendNotFound) | 30 | return doesAccountExist(promise, res, sendNotFound) |
32 | } | 31 | } |
33 | 32 | ||
34 | function isLocalAccountNameExist (name: string, res: Response, sendNotFound = true) { | 33 | function doesLocalAccountNameExist (name: string, res: Response, sendNotFound = true) { |
35 | const promise = AccountModel.loadLocalByName(name) | 34 | const promise = AccountModel.loadLocalByName(name) |
36 | 35 | ||
37 | return isAccountExist(promise, res, sendNotFound) | 36 | return doesAccountExist(promise, res, sendNotFound) |
38 | } | 37 | } |
39 | 38 | ||
40 | function isAccountNameWithHostExist (nameWithDomain: string, res: Response, sendNotFound = true) { | 39 | function doesAccountNameWithHostExist (nameWithDomain: string, res: Response, sendNotFound = true) { |
41 | const [ accountName, host ] = nameWithDomain.split('@') | 40 | return doesAccountExist(AccountModel.loadByNameWithHost(nameWithDomain), res, sendNotFound) |
42 | |||
43 | let promise: Bluebird<AccountModel> | ||
44 | if (!host || host === CONFIG.WEBSERVER.HOST) promise = AccountModel.loadLocalByName(accountName) | ||
45 | else promise = AccountModel.loadByNameAndHost(accountName, host) | ||
46 | |||
47 | return isAccountExist(promise, res, sendNotFound) | ||
48 | } | 41 | } |
49 | 42 | ||
50 | async function isAccountExist (p: Bluebird<AccountModel>, res: Response, sendNotFound: boolean) { | 43 | async function doesAccountExist (p: Bluebird<AccountModel>, res: Response, sendNotFound: boolean) { |
51 | const account = await p | 44 | const account = await p |
52 | 45 | ||
53 | if (!account) { | 46 | if (!account) { |
@@ -69,9 +62,9 @@ async function isAccountExist (p: Bluebird<AccountModel>, res: Response, sendNot | |||
69 | 62 | ||
70 | export { | 63 | export { |
71 | isAccountIdValid, | 64 | isAccountIdValid, |
72 | isAccountIdExist, | 65 | doesAccountIdExist, |
73 | isLocalAccountNameExist, | 66 | doesLocalAccountNameExist, |
74 | isAccountDescriptionValid, | 67 | isAccountDescriptionValid, |
75 | isAccountNameWithHostExist, | 68 | doesAccountNameWithHostExist, |
76 | isAccountNameValid | 69 | isAccountNameValid |
77 | } | 70 | } |
diff --git a/server/helpers/custom-validators/activitypub/activity.ts b/server/helpers/custom-validators/activitypub/activity.ts index b24590d9d..e0d170d9d 100644 --- a/server/helpers/custom-validators/activitypub/activity.ts +++ b/server/helpers/custom-validators/activitypub/activity.ts | |||
@@ -9,6 +9,7 @@ import { isViewActivityValid } from './view' | |||
9 | import { exists } from '../misc' | 9 | import { exists } from '../misc' |
10 | import { isCacheFileObjectValid } from './cache-file' | 10 | import { isCacheFileObjectValid } from './cache-file' |
11 | import { isFlagActivityValid } from './flag' | 11 | import { isFlagActivityValid } from './flag' |
12 | import { isPlaylistObjectValid } from './playlist' | ||
12 | 13 | ||
13 | function isRootActivityValid (activity: any) { | 14 | function isRootActivityValid (activity: any) { |
14 | return Array.isArray(activity['@context']) && ( | 15 | return Array.isArray(activity['@context']) && ( |
@@ -78,6 +79,7 @@ function checkCreateActivity (activity: any) { | |||
78 | isViewActivityValid(activity.object) || | 79 | isViewActivityValid(activity.object) || |
79 | isDislikeActivityValid(activity.object) || | 80 | isDislikeActivityValid(activity.object) || |
80 | isFlagActivityValid(activity.object) || | 81 | isFlagActivityValid(activity.object) || |
82 | isPlaylistObjectValid(activity.object) || | ||
81 | 83 | ||
82 | isCacheFileObjectValid(activity.object) || | 84 | isCacheFileObjectValid(activity.object) || |
83 | sanitizeAndCheckVideoCommentObject(activity.object) || | 85 | sanitizeAndCheckVideoCommentObject(activity.object) || |
@@ -89,6 +91,7 @@ function checkUpdateActivity (activity: any) { | |||
89 | return isBaseActivityValid(activity, 'Update') && | 91 | return isBaseActivityValid(activity, 'Update') && |
90 | ( | 92 | ( |
91 | isCacheFileObjectValid(activity.object) || | 93 | isCacheFileObjectValid(activity.object) || |
94 | isPlaylistObjectValid(activity.object) || | ||
92 | sanitizeAndCheckVideoTorrentObject(activity.object) || | 95 | sanitizeAndCheckVideoTorrentObject(activity.object) || |
93 | sanitizeAndCheckActorObject(activity.object) | 96 | sanitizeAndCheckActorObject(activity.object) |
94 | ) | 97 | ) |
diff --git a/server/helpers/custom-validators/activitypub/actor.ts b/server/helpers/custom-validators/activitypub/actor.ts index c05f60f14..deb331abb 100644 --- a/server/helpers/custom-validators/activitypub/actor.ts +++ b/server/helpers/custom-validators/activitypub/actor.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import * as validator from 'validator' | 1 | import * as validator from 'validator' |
2 | import { CONSTRAINTS_FIELDS } from '../../../initializers' | 2 | import { CONSTRAINTS_FIELDS } from '../../../initializers/constants' |
3 | import { exists, isArray } from '../misc' | 3 | import { exists, isArray } from '../misc' |
4 | import { truncate } from 'lodash' | 4 | import { truncate } from 'lodash' |
5 | import { isActivityPubUrlValid, isBaseActivityValid, setValidAttributedTo } from './misc' | 5 | import { isActivityPubUrlValid, isBaseActivityValid, setValidAttributedTo } from './misc' |
diff --git a/server/helpers/custom-validators/activitypub/cache-file.ts b/server/helpers/custom-validators/activitypub/cache-file.ts index e2bd0c55e..21d5c53ca 100644 --- a/server/helpers/custom-validators/activitypub/cache-file.ts +++ b/server/helpers/custom-validators/activitypub/cache-file.ts | |||
@@ -8,9 +8,19 @@ function isCacheFileObjectValid (object: CacheFileObject) { | |||
8 | object.type === 'CacheFile' && | 8 | object.type === 'CacheFile' && |
9 | isDateValid(object.expires) && | 9 | isDateValid(object.expires) && |
10 | isActivityPubUrlValid(object.object) && | 10 | isActivityPubUrlValid(object.object) && |
11 | isRemoteVideoUrlValid(object.url) | 11 | (isRemoteVideoUrlValid(object.url) || isPlaylistRedundancyUrlValid(object.url)) |
12 | } | 12 | } |
13 | 13 | ||
14 | // --------------------------------------------------------------------------- | ||
15 | |||
14 | export { | 16 | export { |
15 | isCacheFileObjectValid | 17 | isCacheFileObjectValid |
16 | } | 18 | } |
19 | |||
20 | // --------------------------------------------------------------------------- | ||
21 | |||
22 | function isPlaylistRedundancyUrlValid (url: any) { | ||
23 | return url.type === 'Link' && | ||
24 | (url.mediaType || url.mimeType) === 'application/x-mpegURL' && | ||
25 | isActivityPubUrlValid(url.href) | ||
26 | } | ||
diff --git a/server/helpers/custom-validators/activitypub/misc.ts b/server/helpers/custom-validators/activitypub/misc.ts index f1762d11c..5afcfbedc 100644 --- a/server/helpers/custom-validators/activitypub/misc.ts +++ b/server/helpers/custom-validators/activitypub/misc.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import * as validator from 'validator' | 1 | import * as validator from 'validator' |
2 | import { CONSTRAINTS_FIELDS } from '../../../initializers' | 2 | import { CONSTRAINTS_FIELDS } from '../../../initializers/constants' |
3 | import { isTestInstance } from '../../core-utils' | 3 | import { isTestInstance } from '../../core-utils' |
4 | import { exists } from '../misc' | 4 | import { exists } from '../misc' |
5 | 5 | ||
@@ -25,8 +25,7 @@ function isActivityPubUrlValid (url: string) { | |||
25 | } | 25 | } |
26 | 26 | ||
27 | function isBaseActivityValid (activity: any, type: string) { | 27 | function isBaseActivityValid (activity: any, type: string) { |
28 | return (activity['@context'] === undefined || Array.isArray(activity['@context'])) && | 28 | return activity.type === type && |
29 | activity.type === type && | ||
30 | isActivityPubUrlValid(activity.id) && | 29 | isActivityPubUrlValid(activity.id) && |
31 | isObjectValid(activity.actor) && | 30 | isObjectValid(activity.actor) && |
32 | isUrlCollectionValid(activity.to) && | 31 | isUrlCollectionValid(activity.to) && |
diff --git a/server/helpers/custom-validators/activitypub/playlist.ts b/server/helpers/custom-validators/activitypub/playlist.ts new file mode 100644 index 000000000..6c7bdb193 --- /dev/null +++ b/server/helpers/custom-validators/activitypub/playlist.ts | |||
@@ -0,0 +1,27 @@ | |||
1 | import { exists, isDateValid } from '../misc' | ||
2 | import { PlaylistObject } from '../../../../shared/models/activitypub/objects/playlist-object' | ||
3 | import * as validator from 'validator' | ||
4 | import { PlaylistElementObject } from '../../../../shared/models/activitypub/objects/playlist-element-object' | ||
5 | import { isActivityPubUrlValid } from './misc' | ||
6 | |||
7 | function isPlaylistObjectValid (object: PlaylistObject) { | ||
8 | return exists(object) && | ||
9 | object.type === 'Playlist' && | ||
10 | validator.isInt(object.totalItems + '') && | ||
11 | isDateValid(object.published) && | ||
12 | isDateValid(object.updated) | ||
13 | } | ||
14 | |||
15 | function isPlaylistElementObjectValid (object: PlaylistElementObject) { | ||
16 | return exists(object) && | ||
17 | object.type === 'PlaylistElement' && | ||
18 | validator.isInt(object.position + '') && | ||
19 | isActivityPubUrlValid(object.url) | ||
20 | } | ||
21 | |||
22 | // --------------------------------------------------------------------------- | ||
23 | |||
24 | export { | ||
25 | isPlaylistObjectValid, | ||
26 | isPlaylistElementObjectValid | ||
27 | } | ||
diff --git a/server/helpers/custom-validators/activitypub/video-comments.ts b/server/helpers/custom-validators/activitypub/video-comments.ts index 0415db21c..26c8c4cc6 100644 --- a/server/helpers/custom-validators/activitypub/video-comments.ts +++ b/server/helpers/custom-validators/activitypub/video-comments.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import * as validator from 'validator' | 1 | import * as validator from 'validator' |
2 | import { ACTIVITY_PUB, CONSTRAINTS_FIELDS } from '../../../initializers' | 2 | import { ACTIVITY_PUB } from '../../../initializers/constants' |
3 | import { exists, isArray, isDateValid } from '../misc' | 3 | import { exists, isArray, isDateValid } from '../misc' |
4 | import { isActivityPubUrlValid, isBaseActivityValid } from './misc' | 4 | import { isActivityPubUrlValid } from './misc' |
5 | 5 | ||
6 | function sanitizeAndCheckVideoCommentObject (comment: any) { | 6 | function sanitizeAndCheckVideoCommentObject (comment: any) { |
7 | if (!comment || comment.type !== 'Note') return false | 7 | if (!comment || comment.type !== 'Note') return false |
diff --git a/server/helpers/custom-validators/activitypub/videos.ts b/server/helpers/custom-validators/activitypub/videos.ts index 0f34aab21..3ba6b0744 100644 --- a/server/helpers/custom-validators/activitypub/videos.ts +++ b/server/helpers/custom-validators/activitypub/videos.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import * as validator from 'validator' | 1 | import * as validator from 'validator' |
2 | import { ACTIVITY_PUB, CONSTRAINTS_FIELDS } from '../../../initializers' | 2 | import { ACTIVITY_PUB, CONSTRAINTS_FIELDS } from '../../../initializers/constants' |
3 | import { peertubeTruncate } from '../../core-utils' | 3 | import { peertubeTruncate } from '../../core-utils' |
4 | import { exists, isBooleanValid, isDateValid, isUUIDValid } from '../misc' | 4 | import { exists, isArray, isBooleanValid, isDateValid, isUUIDValid } from '../misc' |
5 | import { | 5 | import { |
6 | isVideoDurationValid, | 6 | isVideoDurationValid, |
7 | isVideoNameValid, | 7 | isVideoNameValid, |
@@ -12,7 +12,6 @@ import { | |||
12 | } from '../videos' | 12 | } from '../videos' |
13 | import { isActivityPubUrlValid, isBaseActivityValid, setValidAttributedTo } from './misc' | 13 | import { isActivityPubUrlValid, isBaseActivityValid, setValidAttributedTo } from './misc' |
14 | import { VideoState } from '../../../../shared/models/videos' | 14 | import { VideoState } from '../../../../shared/models/videos' |
15 | import { isVideoAbuseReasonValid } from '../video-abuses' | ||
16 | 15 | ||
17 | function sanitizeAndCheckVideoTorrentUpdateActivity (activity: any) { | 16 | function sanitizeAndCheckVideoTorrentUpdateActivity (activity: any) { |
18 | return isBaseActivityValid(activity, 'Update') && | 17 | return isBaseActivityValid(activity, 'Update') && |
@@ -40,6 +39,7 @@ function sanitizeAndCheckVideoTorrentObject (video: any) { | |||
40 | // Default attributes | 39 | // Default attributes |
41 | if (!isVideoStateValid(video.state)) video.state = VideoState.PUBLISHED | 40 | if (!isVideoStateValid(video.state)) video.state = VideoState.PUBLISHED |
42 | if (!isBooleanValid(video.waitTranscoding)) video.waitTranscoding = false | 41 | if (!isBooleanValid(video.waitTranscoding)) video.waitTranscoding = false |
42 | if (!isBooleanValid(video.downloadEnabled)) video.downloadEnabled = true | ||
43 | 43 | ||
44 | return isActivityPubUrlValid(video.id) && | 44 | return isActivityPubUrlValid(video.id) && |
45 | isVideoNameValid(video.name) && | 45 | isVideoNameValid(video.name) && |
@@ -51,8 +51,10 @@ function sanitizeAndCheckVideoTorrentObject (video: any) { | |||
51 | isVideoViewsValid(video.views) && | 51 | isVideoViewsValid(video.views) && |
52 | isBooleanValid(video.sensitive) && | 52 | isBooleanValid(video.sensitive) && |
53 | isBooleanValid(video.commentsEnabled) && | 53 | isBooleanValid(video.commentsEnabled) && |
54 | isBooleanValid(video.downloadEnabled) && | ||
54 | isDateValid(video.published) && | 55 | isDateValid(video.published) && |
55 | isDateValid(video.updated) && | 56 | isDateValid(video.updated) && |
57 | (!video.originallyPublishedAt || isDateValid(video.originallyPublishedAt)) && | ||
56 | (!video.content || isRemoteVideoContentValid(video.mediaType, video.content)) && | 58 | (!video.content || isRemoteVideoContentValid(video.mediaType, video.content)) && |
57 | isRemoteVideoIconValid(video.icon) && | 59 | isRemoteVideoIconValid(video.icon) && |
58 | video.url.length !== 0 && | 60 | video.url.length !== 0 && |
@@ -81,6 +83,11 @@ function isRemoteVideoUrlValid (url: any) { | |||
81 | ACTIVITY_PUB.URL_MIME_TYPES.MAGNET.indexOf(url.mediaType || url.mimeType) !== -1 && | 83 | ACTIVITY_PUB.URL_MIME_TYPES.MAGNET.indexOf(url.mediaType || url.mimeType) !== -1 && |
82 | validator.isLength(url.href, { min: 5 }) && | 84 | validator.isLength(url.href, { min: 5 }) && |
83 | validator.isInt(url.height + '', { min: 0 }) | 85 | validator.isInt(url.height + '', { min: 0 }) |
86 | ) || | ||
87 | ( | ||
88 | (url.mediaType || url.mimeType) === 'application/x-mpegURL' && | ||
89 | isActivityPubUrlValid(url.href) && | ||
90 | isArray(url.tag) | ||
84 | ) | 91 | ) |
85 | } | 92 | } |
86 | 93 | ||
diff --git a/server/helpers/custom-validators/logs.ts b/server/helpers/custom-validators/logs.ts new file mode 100644 index 000000000..30d0ce262 --- /dev/null +++ b/server/helpers/custom-validators/logs.ts | |||
@@ -0,0 +1,14 @@ | |||
1 | import { exists } from './misc' | ||
2 | import { LogLevel } from '../../../shared/models/server/log-level.type' | ||
3 | |||
4 | const logLevels: LogLevel[] = [ 'debug', 'info', 'warn', 'error' ] | ||
5 | |||
6 | function isValidLogLevel (value: any) { | ||
7 | return exists(value) && logLevels.indexOf(value) !== -1 | ||
8 | } | ||
9 | |||
10 | // --------------------------------------------------------------------------- | ||
11 | |||
12 | export { | ||
13 | isValidLogLevel | ||
14 | } | ||
diff --git a/server/helpers/custom-validators/misc.ts b/server/helpers/custom-validators/misc.ts index b6f0ebe6f..3a3deab0c 100644 --- a/server/helpers/custom-validators/misc.ts +++ b/server/helpers/custom-validators/misc.ts | |||
@@ -13,6 +13,10 @@ function isNotEmptyIntArray (value: any) { | |||
13 | return Array.isArray(value) && value.every(v => validator.isInt('' + v)) && value.length !== 0 | 13 | return Array.isArray(value) && value.every(v => validator.isInt('' + v)) && value.length !== 0 |
14 | } | 14 | } |
15 | 15 | ||
16 | function isArrayOf (value: any, validator: (value: any) => boolean) { | ||
17 | return isArray(value) && value.every(v => validator(v)) | ||
18 | } | ||
19 | |||
16 | function isDateValid (value: string) { | 20 | function isDateValid (value: string) { |
17 | return exists(value) && validator.isISO8601(value) | 21 | return exists(value) && validator.isISO8601(value) |
18 | } | 22 | } |
@@ -45,12 +49,19 @@ function toValueOrNull (value: string) { | |||
45 | return value | 49 | return value |
46 | } | 50 | } |
47 | 51 | ||
48 | function toArray (value: string) { | 52 | function toArray (value: any) { |
49 | if (value && isArray(value) === false) return [ value ] | 53 | if (value && isArray(value) === false) return [ value ] |
50 | 54 | ||
51 | return value | 55 | return value |
52 | } | 56 | } |
53 | 57 | ||
58 | function toIntArray (value: any) { | ||
59 | if (!value) return [] | ||
60 | if (isArray(value) === false) return [ validator.toInt(value) ] | ||
61 | |||
62 | return value.map(v => validator.toInt(v)) | ||
63 | } | ||
64 | |||
54 | function isFileValid ( | 65 | function isFileValid ( |
55 | files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[], | 66 | files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[], |
56 | mimeTypeRegex: string, | 67 | mimeTypeRegex: string, |
@@ -82,6 +93,7 @@ function isFileValid ( | |||
82 | 93 | ||
83 | export { | 94 | export { |
84 | exists, | 95 | exists, |
96 | isArrayOf, | ||
85 | isNotEmptyIntArray, | 97 | isNotEmptyIntArray, |
86 | isArray, | 98 | isArray, |
87 | isIdValid, | 99 | isIdValid, |
@@ -92,5 +104,6 @@ export { | |||
92 | isBooleanValid, | 104 | isBooleanValid, |
93 | toIntOrNull, | 105 | toIntOrNull, |
94 | toArray, | 106 | toArray, |
107 | toIntArray, | ||
95 | isFileValid | 108 | isFileValid |
96 | } | 109 | } |
diff --git a/server/helpers/custom-validators/servers.ts b/server/helpers/custom-validators/servers.ts index 18c80ec8f..5c8bf0d2d 100644 --- a/server/helpers/custom-validators/servers.ts +++ b/server/helpers/custom-validators/servers.ts | |||
@@ -3,7 +3,7 @@ import 'express-validator' | |||
3 | 3 | ||
4 | import { isArray, exists } from './misc' | 4 | import { isArray, exists } from './misc' |
5 | import { isTestInstance } from '../core-utils' | 5 | import { isTestInstance } from '../core-utils' |
6 | import { CONSTRAINTS_FIELDS } from '../../initializers' | 6 | import { CONSTRAINTS_FIELDS } from '../../initializers/constants' |
7 | 7 | ||
8 | function isHostValid (host: string) { | 8 | function isHostValid (host: string) { |
9 | const isURLOptions = { | 9 | const isURLOptions = { |
diff --git a/server/helpers/custom-validators/users.ts b/server/helpers/custom-validators/users.ts index 80652b479..56bc10b16 100644 --- a/server/helpers/custom-validators/users.ts +++ b/server/helpers/custom-validators/users.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import 'express-validator' | 1 | import 'express-validator' |
2 | import * as validator from 'validator' | 2 | import * as validator from 'validator' |
3 | import { UserRole } from '../../../shared' | 3 | import { UserRole } from '../../../shared' |
4 | import { CONSTRAINTS_FIELDS, NSFW_POLICY_TYPES } from '../../initializers' | 4 | import { CONSTRAINTS_FIELDS, NSFW_POLICY_TYPES } from '../../initializers/constants' |
5 | import { exists, isFileValid, isBooleanValid } from './misc' | 5 | import { exists, isBooleanValid, isFileValid } from './misc' |
6 | import { values } from 'lodash' | 6 | import { values } from 'lodash' |
7 | 7 | ||
8 | const USERS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.USERS | 8 | const USERS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.USERS |
@@ -54,6 +54,10 @@ function isUserAutoPlayVideoValid (value: any) { | |||
54 | return isBooleanValid(value) | 54 | return isBooleanValid(value) |
55 | } | 55 | } |
56 | 56 | ||
57 | function isUserAdminFlagsValid (value: any) { | ||
58 | return exists(value) && validator.isInt('' + value) | ||
59 | } | ||
60 | |||
57 | function isUserBlockedValid (value: any) { | 61 | function isUserBlockedValid (value: any) { |
58 | return isBooleanValid(value) | 62 | return isBooleanValid(value) |
59 | } | 63 | } |
@@ -85,6 +89,7 @@ export { | |||
85 | isUserVideoQuotaValid, | 89 | isUserVideoQuotaValid, |
86 | isUserVideoQuotaDailyValid, | 90 | isUserVideoQuotaDailyValid, |
87 | isUserUsernameValid, | 91 | isUserUsernameValid, |
92 | isUserAdminFlagsValid, | ||
88 | isUserEmailVerifiedValid, | 93 | isUserEmailVerifiedValid, |
89 | isUserNSFWPolicyValid, | 94 | isUserNSFWPolicyValid, |
90 | isUserWebTorrentEnabledValid, | 95 | isUserWebTorrentEnabledValid, |
diff --git a/server/helpers/custom-validators/video-abuses.ts b/server/helpers/custom-validators/video-abuses.ts index 290efb149..a61dcee1c 100644 --- a/server/helpers/custom-validators/video-abuses.ts +++ b/server/helpers/custom-validators/video-abuses.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { Response } from 'express' | 1 | import { Response } from 'express' |
2 | import * as validator from 'validator' | 2 | import * as validator from 'validator' |
3 | import { CONSTRAINTS_FIELDS, VIDEO_ABUSE_STATES } from '../../initializers' | 3 | import { CONSTRAINTS_FIELDS, VIDEO_ABUSE_STATES } from '../../initializers/constants' |
4 | import { exists } from './misc' | 4 | import { exists } from './misc' |
5 | import { VideoAbuseModel } from '../../models/video/video-abuse' | 5 | import { VideoAbuseModel } from '../../models/video/video-abuse' |
6 | 6 | ||
@@ -18,7 +18,7 @@ function isVideoAbuseStateValid (value: string) { | |||
18 | return exists(value) && VIDEO_ABUSE_STATES[ value ] !== undefined | 18 | return exists(value) && VIDEO_ABUSE_STATES[ value ] !== undefined |
19 | } | 19 | } |
20 | 20 | ||
21 | async function isVideoAbuseExist (abuseId: number, videoId: number, res: Response) { | 21 | async function doesVideoAbuseExist (abuseId: number, videoId: number, res: Response) { |
22 | const videoAbuse = await VideoAbuseModel.loadByIdAndVideoId(abuseId, videoId) | 22 | const videoAbuse = await VideoAbuseModel.loadByIdAndVideoId(abuseId, videoId) |
23 | 23 | ||
24 | if (videoAbuse === null) { | 24 | if (videoAbuse === null) { |
@@ -36,7 +36,7 @@ async function isVideoAbuseExist (abuseId: number, videoId: number, res: Respons | |||
36 | // --------------------------------------------------------------------------- | 36 | // --------------------------------------------------------------------------- |
37 | 37 | ||
38 | export { | 38 | export { |
39 | isVideoAbuseExist, | 39 | doesVideoAbuseExist, |
40 | isVideoAbuseStateValid, | 40 | isVideoAbuseStateValid, |
41 | isVideoAbuseReasonValid, | 41 | isVideoAbuseReasonValid, |
42 | isVideoAbuseModerationCommentValid | 42 | isVideoAbuseModerationCommentValid |
diff --git a/server/helpers/custom-validators/video-blacklist.ts b/server/helpers/custom-validators/video-blacklist.ts index b36b08d8b..3743f7023 100644 --- a/server/helpers/custom-validators/video-blacklist.ts +++ b/server/helpers/custom-validators/video-blacklist.ts | |||
@@ -1,7 +1,9 @@ | |||
1 | import { Response } from 'express' | 1 | import { Response } from 'express' |
2 | import * as validator from 'validator' | 2 | import * as validator from 'validator' |
3 | import { CONSTRAINTS_FIELDS } from '../../initializers' | 3 | import { exists } from './misc' |
4 | import { CONSTRAINTS_FIELDS } from '../../initializers/constants' | ||
4 | import { VideoBlacklistModel } from '../../models/video/video-blacklist' | 5 | import { VideoBlacklistModel } from '../../models/video/video-blacklist' |
6 | import { VideoBlacklistType } from '../../../shared/models/videos' | ||
5 | 7 | ||
6 | const VIDEO_BLACKLIST_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_BLACKLIST | 8 | const VIDEO_BLACKLIST_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_BLACKLIST |
7 | 9 | ||
@@ -9,7 +11,7 @@ function isVideoBlacklistReasonValid (value: string) { | |||
9 | return value === null || validator.isLength(value, VIDEO_BLACKLIST_CONSTRAINTS_FIELDS.REASON) | 11 | return value === null || validator.isLength(value, VIDEO_BLACKLIST_CONSTRAINTS_FIELDS.REASON) |
10 | } | 12 | } |
11 | 13 | ||
12 | async function isVideoBlacklistExist (videoId: number, res: Response) { | 14 | async function doesVideoBlacklistExist (videoId: number, res: Response) { |
13 | const videoBlacklist = await VideoBlacklistModel.loadByVideoId(videoId) | 15 | const videoBlacklist = await VideoBlacklistModel.loadByVideoId(videoId) |
14 | 16 | ||
15 | if (videoBlacklist === null) { | 17 | if (videoBlacklist === null) { |
@@ -24,9 +26,14 @@ async function isVideoBlacklistExist (videoId: number, res: Response) { | |||
24 | return true | 26 | return true |
25 | } | 27 | } |
26 | 28 | ||
29 | function isVideoBlacklistTypeValid (value: any) { | ||
30 | return exists(value) && validator.isInt('' + value) && VideoBlacklistType[value] !== undefined | ||
31 | } | ||
32 | |||
27 | // --------------------------------------------------------------------------- | 33 | // --------------------------------------------------------------------------- |
28 | 34 | ||
29 | export { | 35 | export { |
30 | isVideoBlacklistReasonValid, | 36 | isVideoBlacklistReasonValid, |
31 | isVideoBlacklistExist | 37 | isVideoBlacklistTypeValid, |
38 | doesVideoBlacklistExist | ||
32 | } | 39 | } |
diff --git a/server/helpers/custom-validators/video-captions.ts b/server/helpers/custom-validators/video-captions.ts index b33d90e18..3b6569a8a 100644 --- a/server/helpers/custom-validators/video-captions.ts +++ b/server/helpers/custom-validators/video-captions.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { CONSTRAINTS_FIELDS, MIMETYPES, VIDEO_LANGUAGES } from '../../initializers' | 1 | import { CONSTRAINTS_FIELDS, MIMETYPES, VIDEO_LANGUAGES } from '../../initializers/constants' |
2 | import { exists, isFileValid } from './misc' | 2 | import { exists, isFileValid } from './misc' |
3 | import { Response } from 'express' | 3 | import { Response } from 'express' |
4 | import { VideoModel } from '../../models/video/video' | 4 | import { VideoModel } from '../../models/video/video' |
@@ -16,7 +16,7 @@ function isVideoCaptionFile (files: { [ fieldname: string ]: Express.Multer.File | |||
16 | return isFileValid(files, videoCaptionTypesRegex, field, CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.FILE_SIZE.max) | 16 | return isFileValid(files, videoCaptionTypesRegex, field, CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.FILE_SIZE.max) |
17 | } | 17 | } |
18 | 18 | ||
19 | async function isVideoCaptionExist (video: VideoModel, language: string, res: Response) { | 19 | async function doesVideoCaptionExist (video: VideoModel, language: string, res: Response) { |
20 | const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(video.id, language) | 20 | const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(video.id, language) |
21 | 21 | ||
22 | if (!videoCaption) { | 22 | if (!videoCaption) { |
@@ -36,5 +36,5 @@ async function isVideoCaptionExist (video: VideoModel, language: string, res: Re | |||
36 | export { | 36 | export { |
37 | isVideoCaptionFile, | 37 | isVideoCaptionFile, |
38 | isVideoCaptionLanguageValid, | 38 | isVideoCaptionLanguageValid, |
39 | isVideoCaptionExist | 39 | doesVideoCaptionExist |
40 | } | 40 | } |
diff --git a/server/helpers/custom-validators/video-channels.ts b/server/helpers/custom-validators/video-channels.ts index f13519c1d..fd56b9a70 100644 --- a/server/helpers/custom-validators/video-channels.ts +++ b/server/helpers/custom-validators/video-channels.ts | |||
@@ -2,7 +2,7 @@ import * as express from 'express' | |||
2 | import 'express-validator' | 2 | import 'express-validator' |
3 | import 'multer' | 3 | import 'multer' |
4 | import * as validator from 'validator' | 4 | import * as validator from 'validator' |
5 | import { CONFIG, CONSTRAINTS_FIELDS } from '../../initializers' | 5 | import { CONSTRAINTS_FIELDS } from '../../initializers/constants' |
6 | import { VideoChannelModel } from '../../models/video/video-channel' | 6 | import { VideoChannelModel } from '../../models/video/video-channel' |
7 | import { exists } from './misc' | 7 | import { exists } from './misc' |
8 | 8 | ||
@@ -20,29 +20,25 @@ function isVideoChannelSupportValid (value: string) { | |||
20 | return value === null || (exists(value) && validator.isLength(value, VIDEO_CHANNELS_CONSTRAINTS_FIELDS.SUPPORT)) | 20 | return value === null || (exists(value) && validator.isLength(value, VIDEO_CHANNELS_CONSTRAINTS_FIELDS.SUPPORT)) |
21 | } | 21 | } |
22 | 22 | ||
23 | async function isLocalVideoChannelNameExist (name: string, res: express.Response) { | 23 | async function doesLocalVideoChannelNameExist (name: string, res: express.Response) { |
24 | const videoChannel = await VideoChannelModel.loadLocalByNameAndPopulateAccount(name) | 24 | const videoChannel = await VideoChannelModel.loadLocalByNameAndPopulateAccount(name) |
25 | 25 | ||
26 | return processVideoChannelExist(videoChannel, res) | 26 | return processVideoChannelExist(videoChannel, res) |
27 | } | 27 | } |
28 | 28 | ||
29 | async function isVideoChannelIdExist (id: string, res: express.Response) { | 29 | async function doesVideoChannelIdExist (id: number | string, res: express.Response) { |
30 | let videoChannel: VideoChannelModel | 30 | let videoChannel: VideoChannelModel |
31 | if (validator.isInt(id)) { | 31 | if (validator.isInt('' + id)) { |
32 | videoChannel = await VideoChannelModel.loadAndPopulateAccount(+id) | 32 | videoChannel = await VideoChannelModel.loadAndPopulateAccount(+id) |
33 | } else { // UUID | 33 | } else { // UUID |
34 | videoChannel = await VideoChannelModel.loadByUUIDAndPopulateAccount(id) | 34 | videoChannel = await VideoChannelModel.loadByUUIDAndPopulateAccount('' + id) |
35 | } | 35 | } |
36 | 36 | ||
37 | return processVideoChannelExist(videoChannel, res) | 37 | return processVideoChannelExist(videoChannel, res) |
38 | } | 38 | } |
39 | 39 | ||
40 | async function isVideoChannelNameWithHostExist (nameWithDomain: string, res: express.Response) { | 40 | async function doesVideoChannelNameWithHostExist (nameWithDomain: string, res: express.Response) { |
41 | const [ name, host ] = nameWithDomain.split('@') | 41 | const videoChannel = await VideoChannelModel.loadByNameWithHostAndPopulateAccount(nameWithDomain) |
42 | let videoChannel: VideoChannelModel | ||
43 | |||
44 | if (!host || host === CONFIG.WEBSERVER.HOST) videoChannel = await VideoChannelModel.loadLocalByNameAndPopulateAccount(name) | ||
45 | else videoChannel = await VideoChannelModel.loadByNameAndHostAndPopulateAccount(name, host) | ||
46 | 42 | ||
47 | return processVideoChannelExist(videoChannel, res) | 43 | return processVideoChannelExist(videoChannel, res) |
48 | } | 44 | } |
@@ -50,12 +46,12 @@ async function isVideoChannelNameWithHostExist (nameWithDomain: string, res: exp | |||
50 | // --------------------------------------------------------------------------- | 46 | // --------------------------------------------------------------------------- |
51 | 47 | ||
52 | export { | 48 | export { |
53 | isVideoChannelNameWithHostExist, | 49 | doesVideoChannelNameWithHostExist, |
54 | isLocalVideoChannelNameExist, | 50 | doesLocalVideoChannelNameExist, |
55 | isVideoChannelDescriptionValid, | 51 | isVideoChannelDescriptionValid, |
56 | isVideoChannelNameValid, | 52 | isVideoChannelNameValid, |
57 | isVideoChannelSupportValid, | 53 | isVideoChannelSupportValid, |
58 | isVideoChannelIdExist | 54 | doesVideoChannelIdExist |
59 | } | 55 | } |
60 | 56 | ||
61 | function processVideoChannelExist (videoChannel: VideoChannelModel, res: express.Response) { | 57 | function processVideoChannelExist (videoChannel: VideoChannelModel, res: express.Response) { |
diff --git a/server/helpers/custom-validators/video-comments.ts b/server/helpers/custom-validators/video-comments.ts index 2b3f66063..0707e2af2 100644 --- a/server/helpers/custom-validators/video-comments.ts +++ b/server/helpers/custom-validators/video-comments.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import 'express-validator' | 1 | import 'express-validator' |
2 | import 'multer' | 2 | import 'multer' |
3 | import * as validator from 'validator' | 3 | import * as validator from 'validator' |
4 | import { CONSTRAINTS_FIELDS } from '../../initializers' | 4 | import { CONSTRAINTS_FIELDS } from '../../initializers/constants' |
5 | 5 | ||
6 | const VIDEO_COMMENTS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_COMMENTS | 6 | const VIDEO_COMMENTS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_COMMENTS |
7 | 7 | ||
diff --git a/server/helpers/custom-validators/video-imports.ts b/server/helpers/custom-validators/video-imports.ts index ce9e9193c..f4235e2fa 100644 --- a/server/helpers/custom-validators/video-imports.ts +++ b/server/helpers/custom-validators/video-imports.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import 'express-validator' | 1 | import 'express-validator' |
2 | import 'multer' | 2 | import 'multer' |
3 | import * as validator from 'validator' | 3 | import * as validator from 'validator' |
4 | import { CONSTRAINTS_FIELDS, MIMETYPES, VIDEO_IMPORT_STATES } from '../../initializers' | 4 | import { CONSTRAINTS_FIELDS, MIMETYPES, VIDEO_IMPORT_STATES } from '../../initializers/constants' |
5 | import { exists, isFileValid } from './misc' | 5 | import { exists, isFileValid } from './misc' |
6 | import * as express from 'express' | 6 | import * as express from 'express' |
7 | import { VideoImportModel } from '../../models/video/video-import' | 7 | import { VideoImportModel } from '../../models/video/video-import' |
@@ -30,7 +30,7 @@ function isVideoImportTorrentFile (files: { [ fieldname: string ]: Express.Multe | |||
30 | return isFileValid(files, videoTorrentImportRegex, 'torrentfile', CONSTRAINTS_FIELDS.VIDEO_IMPORTS.TORRENT_FILE.FILE_SIZE.max, true) | 30 | return isFileValid(files, videoTorrentImportRegex, 'torrentfile', CONSTRAINTS_FIELDS.VIDEO_IMPORTS.TORRENT_FILE.FILE_SIZE.max, true) |
31 | } | 31 | } |
32 | 32 | ||
33 | async function isVideoImportExist (id: number, res: express.Response) { | 33 | async function doesVideoImportExist (id: number, res: express.Response) { |
34 | const videoImport = await VideoImportModel.loadAndPopulateVideo(id) | 34 | const videoImport = await VideoImportModel.loadAndPopulateVideo(id) |
35 | 35 | ||
36 | if (!videoImport) { | 36 | if (!videoImport) { |
@@ -50,6 +50,6 @@ async function isVideoImportExist (id: number, res: express.Response) { | |||
50 | export { | 50 | export { |
51 | isVideoImportStateValid, | 51 | isVideoImportStateValid, |
52 | isVideoImportTargetUrlValid, | 52 | isVideoImportTargetUrlValid, |
53 | isVideoImportExist, | 53 | doesVideoImportExist, |
54 | isVideoImportTorrentFile | 54 | isVideoImportTorrentFile |
55 | } | 55 | } |
diff --git a/server/helpers/custom-validators/video-playlists.ts b/server/helpers/custom-validators/video-playlists.ts new file mode 100644 index 000000000..2fe426560 --- /dev/null +++ b/server/helpers/custom-validators/video-playlists.ts | |||
@@ -0,0 +1,55 @@ | |||
1 | import { exists } from './misc' | ||
2 | import * as validator from 'validator' | ||
3 | import { CONSTRAINTS_FIELDS, VIDEO_PLAYLIST_PRIVACIES, VIDEO_PLAYLIST_TYPES } from '../../initializers/constants' | ||
4 | import * as express from 'express' | ||
5 | import { VideoPlaylistModel } from '../../models/video/video-playlist' | ||
6 | |||
7 | const PLAYLISTS_CONSTRAINT_FIELDS = CONSTRAINTS_FIELDS.VIDEO_PLAYLISTS | ||
8 | |||
9 | function isVideoPlaylistNameValid (value: any) { | ||
10 | return exists(value) && validator.isLength(value, PLAYLISTS_CONSTRAINT_FIELDS.NAME) | ||
11 | } | ||
12 | |||
13 | function isVideoPlaylistDescriptionValid (value: any) { | ||
14 | return value === null || (exists(value) && validator.isLength(value, PLAYLISTS_CONSTRAINT_FIELDS.DESCRIPTION)) | ||
15 | } | ||
16 | |||
17 | function isVideoPlaylistPrivacyValid (value: number) { | ||
18 | return validator.isInt(value + '') && VIDEO_PLAYLIST_PRIVACIES[ value ] !== undefined | ||
19 | } | ||
20 | |||
21 | function isVideoPlaylistTimestampValid (value: any) { | ||
22 | return value === null || (exists(value) && validator.isInt('' + value, { min: 0 })) | ||
23 | } | ||
24 | |||
25 | function isVideoPlaylistTypeValid (value: any) { | ||
26 | return exists(value) && VIDEO_PLAYLIST_TYPES[ value ] !== undefined | ||
27 | } | ||
28 | |||
29 | async function doesVideoPlaylistExist (id: number | string, res: express.Response, fetchType: 'summary' | 'all' = 'summary') { | ||
30 | const videoPlaylist = fetchType === 'summary' | ||
31 | ? await VideoPlaylistModel.loadWithAccountAndChannelSummary(id, undefined) | ||
32 | : await VideoPlaylistModel.loadWithAccountAndChannel(id, undefined) | ||
33 | |||
34 | if (!videoPlaylist) { | ||
35 | res.status(404) | ||
36 | .json({ error: 'Video playlist not found' }) | ||
37 | .end() | ||
38 | |||
39 | return false | ||
40 | } | ||
41 | |||
42 | res.locals.videoPlaylist = videoPlaylist | ||
43 | return true | ||
44 | } | ||
45 | |||
46 | // --------------------------------------------------------------------------- | ||
47 | |||
48 | export { | ||
49 | doesVideoPlaylistExist, | ||
50 | isVideoPlaylistNameValid, | ||
51 | isVideoPlaylistDescriptionValid, | ||
52 | isVideoPlaylistPrivacyValid, | ||
53 | isVideoPlaylistTimestampValid, | ||
54 | isVideoPlaylistTypeValid | ||
55 | } | ||
diff --git a/server/helpers/custom-validators/video-rates.ts b/server/helpers/custom-validators/video-rates.ts new file mode 100644 index 000000000..f2b6f7cae --- /dev/null +++ b/server/helpers/custom-validators/video-rates.ts | |||
@@ -0,0 +1,5 @@ | |||
1 | function isRatingValid (value: any) { | ||
2 | return value === 'like' || value === 'dislike' | ||
3 | } | ||
4 | |||
5 | export { isRatingValid } | ||
diff --git a/server/helpers/custom-validators/videos.ts b/server/helpers/custom-validators/videos.ts index 95e256b8f..214db17a1 100644 --- a/server/helpers/custom-validators/videos.ts +++ b/server/helpers/custom-validators/videos.ts | |||
@@ -5,15 +5,16 @@ import 'multer' | |||
5 | import * as validator from 'validator' | 5 | import * as validator from 'validator' |
6 | import { UserRight, VideoFilter, VideoPrivacy, VideoRateType } from '../../../shared' | 6 | import { UserRight, VideoFilter, VideoPrivacy, VideoRateType } from '../../../shared' |
7 | import { | 7 | import { |
8 | CONSTRAINTS_FIELDS, MIMETYPES, | 8 | CONSTRAINTS_FIELDS, |
9 | MIMETYPES, | ||
9 | VIDEO_CATEGORIES, | 10 | VIDEO_CATEGORIES, |
10 | VIDEO_LICENCES, | 11 | VIDEO_LICENCES, |
11 | VIDEO_PRIVACIES, | 12 | VIDEO_PRIVACIES, |
12 | VIDEO_RATE_TYPES, | 13 | VIDEO_RATE_TYPES, |
13 | VIDEO_STATES | 14 | VIDEO_STATES |
14 | } from '../../initializers' | 15 | } from '../../initializers/constants' |
15 | import { VideoModel } from '../../models/video/video' | 16 | import { VideoModel } from '../../models/video/video' |
16 | import { exists, isArray, isFileValid } from './misc' | 17 | import { exists, isArray, isDateValid, isFileValid } from './misc' |
17 | import { VideoChannelModel } from '../../models/video/video-channel' | 18 | import { VideoChannelModel } from '../../models/video/video-channel' |
18 | import { UserModel } from '../../models/account/user' | 19 | import { UserModel } from '../../models/account/user' |
19 | import * as magnetUtil from 'magnet-uri' | 20 | import * as magnetUtil from 'magnet-uri' |
@@ -115,6 +116,10 @@ function isScheduleVideoUpdatePrivacyValid (value: number) { | |||
115 | ) | 116 | ) |
116 | } | 117 | } |
117 | 118 | ||
119 | function isVideoOriginallyPublishedAtValid (value: string | null) { | ||
120 | return value === null || isDateValid(value) | ||
121 | } | ||
122 | |||
118 | function isVideoFileInfoHashValid (value: string | null | undefined) { | 123 | function isVideoFileInfoHashValid (value: string | null | undefined) { |
119 | return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.INFO_HASH) | 124 | return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.INFO_HASH) |
120 | } | 125 | } |
@@ -161,7 +166,7 @@ function checkUserCanManageVideo (user: UserModel, video: VideoModel, right: Use | |||
161 | return true | 166 | return true |
162 | } | 167 | } |
163 | 168 | ||
164 | async function isVideoExist (id: string, res: Response, fetchType: VideoFetchType = 'all') { | 169 | async function doesVideoExist (id: number | string, res: Response, fetchType: VideoFetchType = 'all') { |
165 | const userId = res.locals.oauth ? res.locals.oauth.token.User.id : undefined | 170 | const userId = res.locals.oauth ? res.locals.oauth.token.User.id : undefined |
166 | 171 | ||
167 | const video = await fetchVideo(id, fetchType, userId) | 172 | const video = await fetchVideo(id, fetchType, userId) |
@@ -178,7 +183,7 @@ async function isVideoExist (id: string, res: Response, fetchType: VideoFetchTyp | |||
178 | return true | 183 | return true |
179 | } | 184 | } |
180 | 185 | ||
181 | async function isVideoChannelOfAccountExist (channelId: number, user: UserModel, res: Response) { | 186 | async function doesVideoChannelOfAccountExist (channelId: number, user: UserModel, res: Response) { |
182 | if (user.hasRight(UserRight.UPDATE_ANY_VIDEO) === true) { | 187 | if (user.hasRight(UserRight.UPDATE_ANY_VIDEO) === true) { |
183 | const videoChannel = await VideoChannelModel.loadAndPopulateAccount(channelId) | 188 | const videoChannel = await VideoChannelModel.loadAndPopulateAccount(channelId) |
184 | if (videoChannel === null) { | 189 | if (videoChannel === null) { |
@@ -220,6 +225,7 @@ export { | |||
220 | isVideoTagsValid, | 225 | isVideoTagsValid, |
221 | isVideoFPSResolutionValid, | 226 | isVideoFPSResolutionValid, |
222 | isScheduleVideoUpdatePrivacyValid, | 227 | isScheduleVideoUpdatePrivacyValid, |
228 | isVideoOriginallyPublishedAtValid, | ||
223 | isVideoFile, | 229 | isVideoFile, |
224 | isVideoMagnetUriValid, | 230 | isVideoMagnetUriValid, |
225 | isVideoStateValid, | 231 | isVideoStateValid, |
@@ -231,9 +237,9 @@ export { | |||
231 | isVideoPrivacyValid, | 237 | isVideoPrivacyValid, |
232 | isVideoFileResolutionValid, | 238 | isVideoFileResolutionValid, |
233 | isVideoFileSizeValid, | 239 | isVideoFileSizeValid, |
234 | isVideoExist, | 240 | doesVideoExist, |
235 | isVideoImage, | 241 | isVideoImage, |
236 | isVideoChannelOfAccountExist, | 242 | doesVideoChannelOfAccountExist, |
237 | isVideoSupportValid, | 243 | isVideoSupportValid, |
238 | isVideoFilterValid | 244 | isVideoFilterValid |
239 | } | 245 | } |
diff --git a/server/helpers/custom-validators/webfinger.ts b/server/helpers/custom-validators/webfinger.ts index 80a7e4a9d..dd914341e 100644 --- a/server/helpers/custom-validators/webfinger.ts +++ b/server/helpers/custom-validators/webfinger.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { CONFIG, REMOTE_SCHEME } from '../../initializers' | 1 | import { REMOTE_SCHEME, WEBSERVER } from '../../initializers/constants' |
2 | import { sanitizeHost } from '../core-utils' | 2 | import { sanitizeHost } from '../core-utils' |
3 | import { exists } from './misc' | 3 | import { exists } from './misc' |
4 | 4 | ||
@@ -11,7 +11,7 @@ function isWebfingerLocalResourceValid (value: string) { | |||
11 | if (actorParts.length !== 2) return false | 11 | if (actorParts.length !== 2) return false |
12 | 12 | ||
13 | const host = actorParts[1] | 13 | const host = actorParts[1] |
14 | return sanitizeHost(host, REMOTE_SCHEME.HTTP) === CONFIG.WEBSERVER.HOST | 14 | return sanitizeHost(host, REMOTE_SCHEME.HTTP) === WEBSERVER.HOST |
15 | } | 15 | } |
16 | 16 | ||
17 | // --------------------------------------------------------------------------- | 17 | // --------------------------------------------------------------------------- |
diff --git a/server/helpers/database-utils.ts b/server/helpers/database-utils.ts index 1005d2cf1..39c74b2fd 100644 --- a/server/helpers/database-utils.ts +++ b/server/helpers/database-utils.ts | |||
@@ -62,14 +62,13 @@ function updateInstanceWithAnother <T extends Model<T>> (instanceToUpdate: Model | |||
62 | const obj = baseInstance.toJSON() | 62 | const obj = baseInstance.toJSON() |
63 | 63 | ||
64 | for (const key of Object.keys(obj)) { | 64 | for (const key of Object.keys(obj)) { |
65 | instanceToUpdate.set(key, obj[key]) | 65 | instanceToUpdate[key] = obj[key] |
66 | } | 66 | } |
67 | } | 67 | } |
68 | 68 | ||
69 | function resetSequelizeInstance (instance: Model<any>, savedFields: object) { | 69 | function resetSequelizeInstance (instance: Model<any>, savedFields: object) { |
70 | Object.keys(savedFields).forEach(key => { | 70 | Object.keys(savedFields).forEach(key => { |
71 | const value = savedFields[key] | 71 | instance[key] = savedFields[key] |
72 | instance.set(key, value) | ||
73 | }) | 72 | }) |
74 | } | 73 | } |
75 | 74 | ||
diff --git a/server/helpers/express-utils.ts b/server/helpers/express-utils.ts index 9a72ee96d..e0a1d56a5 100644 --- a/server/helpers/express-utils.ts +++ b/server/helpers/express-utils.ts | |||
@@ -1,11 +1,11 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import * as multer from 'multer' | 2 | import * as multer from 'multer' |
3 | import { CONFIG, REMOTE_SCHEME } from '../initializers' | 3 | import { REMOTE_SCHEME } from '../initializers/constants' |
4 | import { logger } from './logger' | 4 | import { logger } from './logger' |
5 | import { deleteFileAsync, generateRandomString } from './utils' | 5 | import { deleteFileAsync, generateRandomString } from './utils' |
6 | import { extname } from 'path' | 6 | import { extname } from 'path' |
7 | import { isArray } from './custom-validators/misc' | 7 | import { isArray } from './custom-validators/misc' |
8 | import { UserModel } from '../models/account/user' | 8 | import { CONFIG } from '../initializers/config' |
9 | 9 | ||
10 | function buildNSFWFilter (res?: express.Response, paramNSFW?: string) { | 10 | function buildNSFWFilter (res?: express.Response, paramNSFW?: string) { |
11 | if (paramNSFW === 'true') return true | 11 | if (paramNSFW === 'true') return true |
@@ -13,7 +13,7 @@ function buildNSFWFilter (res?: express.Response, paramNSFW?: string) { | |||
13 | if (paramNSFW === 'both') return undefined | 13 | if (paramNSFW === 'both') return undefined |
14 | 14 | ||
15 | if (res && res.locals.oauth) { | 15 | if (res && res.locals.oauth) { |
16 | const user: UserModel = res.locals.oauth.token.User | 16 | const user = res.locals.oauth.token.User |
17 | 17 | ||
18 | // User does not want NSFW videos | 18 | // User does not want NSFW videos |
19 | if (user.nsfwPolicy === 'do_not_list') return false | 19 | if (user.nsfwPolicy === 'do_not_list') return false |
@@ -59,7 +59,7 @@ function getHostWithPort (host: string) { | |||
59 | return host | 59 | return host |
60 | } | 60 | } |
61 | 61 | ||
62 | function badRequest (req: express.Request, res: express.Response, next: express.NextFunction) { | 62 | function badRequest (req: express.Request, res: express.Response) { |
63 | return res.type('json').status(400).end() | 63 | return res.type('json').status(400).end() |
64 | } | 64 | } |
65 | 65 | ||
@@ -100,7 +100,7 @@ function createReqFiles ( | |||
100 | } | 100 | } |
101 | 101 | ||
102 | function isUserAbleToSearchRemoteURI (res: express.Response) { | 102 | function isUserAbleToSearchRemoteURI (res: express.Response) { |
103 | const user: UserModel = res.locals.oauth ? res.locals.oauth.token.User : undefined | 103 | const user = res.locals.oauth ? res.locals.oauth.token.User : undefined |
104 | 104 | ||
105 | return CONFIG.SEARCH.REMOTE_URI.ANONYMOUS === true || | 105 | return CONFIG.SEARCH.REMOTE_URI.ANONYMOUS === true || |
106 | (CONFIG.SEARCH.REMOTE_URI.USERS === true && user !== undefined) | 106 | (CONFIG.SEARCH.REMOTE_URI.USERS === true && user !== undefined) |
diff --git a/server/helpers/ffmpeg-utils.ts b/server/helpers/ffmpeg-utils.ts index 132f4690e..76b744de8 100644 --- a/server/helpers/ffmpeg-utils.ts +++ b/server/helpers/ffmpeg-utils.ts | |||
@@ -1,11 +1,12 @@ | |||
1 | import * as ffmpeg from 'fluent-ffmpeg' | 1 | import * as ffmpeg from 'fluent-ffmpeg' |
2 | import { join } from 'path' | 2 | import { dirname, join } from 'path' |
3 | import { getTargetBitrate, VideoResolution } from '../../shared/models/videos' | 3 | import { getTargetBitrate, VideoResolution } from '../../shared/models/videos' |
4 | import { CONFIG, FFMPEG_NICE, VIDEO_TRANSCODING_FPS } from '../initializers/constants' | 4 | import { FFMPEG_NICE, VIDEO_TRANSCODING_FPS } from '../initializers/constants' |
5 | import { processImage } from './image-utils' | 5 | import { processImage } from './image-utils' |
6 | import { logger } from './logger' | 6 | import { logger } from './logger' |
7 | import { checkFFmpegEncoders } from '../initializers/checker-before-init' | 7 | import { checkFFmpegEncoders } from '../initializers/checker-before-init' |
8 | import { remove } from 'fs-extra' | 8 | import { readFile, remove, writeFile } from 'fs-extra' |
9 | import { CONFIG } from '../initializers/config' | ||
9 | 10 | ||
10 | function computeResolutionsToTranscode (videoFileHeight: number) { | 11 | function computeResolutionsToTranscode (videoFileHeight: number) { |
11 | const resolutionsEnabled: number[] = [] | 12 | const resolutionsEnabled: number[] = [] |
@@ -29,12 +30,21 @@ function computeResolutionsToTranscode (videoFileHeight: number) { | |||
29 | return resolutionsEnabled | 30 | return resolutionsEnabled |
30 | } | 31 | } |
31 | 32 | ||
32 | async function getVideoFileResolution (path: string) { | 33 | async function getVideoFileSize (path: string) { |
33 | const videoStream = await getVideoFileStream(path) | 34 | const videoStream = await getVideoFileStream(path) |
34 | 35 | ||
35 | return { | 36 | return { |
36 | videoFileResolution: Math.min(videoStream.height, videoStream.width), | 37 | width: videoStream.width, |
37 | isPortraitMode: videoStream.height > videoStream.width | 38 | height: videoStream.height |
39 | } | ||
40 | } | ||
41 | |||
42 | async function getVideoFileResolution (path: string) { | ||
43 | const size = await getVideoFileSize(path) | ||
44 | |||
45 | return { | ||
46 | videoFileResolution: Math.min(size.height, size.width), | ||
47 | isPortraitMode: size.height > size.width | ||
38 | } | 48 | } |
39 | } | 49 | } |
40 | 50 | ||
@@ -95,7 +105,7 @@ async function generateImageFromVideoFile (fromPath: string, folder: string, ima | |||
95 | }) | 105 | }) |
96 | 106 | ||
97 | const destination = join(folder, imageName) | 107 | const destination = join(folder, imageName) |
98 | await processImage({ path: pendingImagePath }, destination, size) | 108 | await processImage(pendingImagePath, destination, size) |
99 | } catch (err) { | 109 | } catch (err) { |
100 | logger.error('Cannot generate image from video %s.', fromPath, { err }) | 110 | logger.error('Cannot generate image from video %s.', fromPath, { err }) |
101 | 111 | ||
@@ -110,52 +120,41 @@ async function generateImageFromVideoFile (fromPath: string, folder: string, ima | |||
110 | type TranscodeOptions = { | 120 | type TranscodeOptions = { |
111 | inputPath: string | 121 | inputPath: string |
112 | outputPath: string | 122 | outputPath: string |
113 | resolution?: VideoResolution | 123 | resolution: VideoResolution |
114 | isPortraitMode?: boolean | 124 | isPortraitMode?: boolean |
125 | |||
126 | hlsPlaylist?: { | ||
127 | videoFilename: string | ||
128 | } | ||
115 | } | 129 | } |
116 | 130 | ||
117 | function transcode (options: TranscodeOptions) { | 131 | function transcode (options: TranscodeOptions) { |
118 | return new Promise<void>(async (res, rej) => { | 132 | return new Promise<void>(async (res, rej) => { |
119 | try { | 133 | try { |
120 | let fps = await getVideoFileFPS(options.inputPath) | ||
121 | // On small/medium resolutions, limit FPS | ||
122 | if ( | ||
123 | options.resolution !== undefined && | ||
124 | options.resolution < VIDEO_TRANSCODING_FPS.KEEP_ORIGIN_FPS_RESOLUTION_MIN && | ||
125 | fps > VIDEO_TRANSCODING_FPS.AVERAGE | ||
126 | ) { | ||
127 | fps = VIDEO_TRANSCODING_FPS.AVERAGE | ||
128 | } | ||
129 | |||
130 | let command = ffmpeg(options.inputPath, { niceness: FFMPEG_NICE.TRANSCODING }) | 134 | let command = ffmpeg(options.inputPath, { niceness: FFMPEG_NICE.TRANSCODING }) |
131 | .output(options.outputPath) | 135 | .output(options.outputPath) |
132 | command = await presetH264(command, options.resolution, fps) | 136 | |
137 | if (options.hlsPlaylist) { | ||
138 | command = await buildHLSCommand(command, options) | ||
139 | } else { | ||
140 | command = await buildx264Command(command, options) | ||
141 | } | ||
133 | 142 | ||
134 | if (CONFIG.TRANSCODING.THREADS > 0) { | 143 | if (CONFIG.TRANSCODING.THREADS > 0) { |
135 | // if we don't set any threads ffmpeg will chose automatically | 144 | // if we don't set any threads ffmpeg will chose automatically |
136 | command = command.outputOption('-threads ' + CONFIG.TRANSCODING.THREADS) | 145 | command = command.outputOption('-threads ' + CONFIG.TRANSCODING.THREADS) |
137 | } | 146 | } |
138 | 147 | ||
139 | if (options.resolution !== undefined) { | ||
140 | // '?x720' or '720x?' for example | ||
141 | const size = options.isPortraitMode === true ? `${options.resolution}x?` : `?x${options.resolution}` | ||
142 | command = command.size(size) | ||
143 | } | ||
144 | |||
145 | if (fps) { | ||
146 | // Hard FPS limits | ||
147 | if (fps > VIDEO_TRANSCODING_FPS.MAX) fps = VIDEO_TRANSCODING_FPS.MAX | ||
148 | else if (fps < VIDEO_TRANSCODING_FPS.MIN) fps = VIDEO_TRANSCODING_FPS.MIN | ||
149 | |||
150 | command = command.withFPS(fps) | ||
151 | } | ||
152 | |||
153 | command | 148 | command |
154 | .on('error', (err, stdout, stderr) => { | 149 | .on('error', (err, stdout, stderr) => { |
155 | logger.error('Error in transcoding job.', { stdout, stderr }) | 150 | logger.error('Error in transcoding job.', { stdout, stderr }) |
156 | return rej(err) | 151 | return rej(err) |
157 | }) | 152 | }) |
158 | .on('end', res) | 153 | .on('end', () => { |
154 | return onTranscodingSuccess(options) | ||
155 | .then(() => res()) | ||
156 | .catch(err => rej(err)) | ||
157 | }) | ||
159 | .run() | 158 | .run() |
160 | } catch (err) { | 159 | } catch (err) { |
161 | return rej(err) | 160 | return rej(err) |
@@ -166,6 +165,7 @@ function transcode (options: TranscodeOptions) { | |||
166 | // --------------------------------------------------------------------------- | 165 | // --------------------------------------------------------------------------- |
167 | 166 | ||
168 | export { | 167 | export { |
168 | getVideoFileSize, | ||
169 | getVideoFileResolution, | 169 | getVideoFileResolution, |
170 | getDurationFromVideoFile, | 170 | getDurationFromVideoFile, |
171 | generateImageFromVideoFile, | 171 | generateImageFromVideoFile, |
@@ -178,6 +178,71 @@ export { | |||
178 | 178 | ||
179 | // --------------------------------------------------------------------------- | 179 | // --------------------------------------------------------------------------- |
180 | 180 | ||
181 | async function buildx264Command (command: ffmpeg.FfmpegCommand, options: TranscodeOptions) { | ||
182 | let fps = await getVideoFileFPS(options.inputPath) | ||
183 | // On small/medium resolutions, limit FPS | ||
184 | if ( | ||
185 | options.resolution !== undefined && | ||
186 | options.resolution < VIDEO_TRANSCODING_FPS.KEEP_ORIGIN_FPS_RESOLUTION_MIN && | ||
187 | fps > VIDEO_TRANSCODING_FPS.AVERAGE | ||
188 | ) { | ||
189 | fps = VIDEO_TRANSCODING_FPS.AVERAGE | ||
190 | } | ||
191 | |||
192 | command = await presetH264(command, options.resolution, fps) | ||
193 | |||
194 | if (options.resolution !== undefined) { | ||
195 | // '?x720' or '720x?' for example | ||
196 | const size = options.isPortraitMode === true ? `${options.resolution}x?` : `?x${options.resolution}` | ||
197 | command = command.size(size) | ||
198 | } | ||
199 | |||
200 | if (fps) { | ||
201 | // Hard FPS limits | ||
202 | if (fps > VIDEO_TRANSCODING_FPS.MAX) fps = VIDEO_TRANSCODING_FPS.MAX | ||
203 | else if (fps < VIDEO_TRANSCODING_FPS.MIN) fps = VIDEO_TRANSCODING_FPS.MIN | ||
204 | |||
205 | command = command.withFPS(fps) | ||
206 | } | ||
207 | |||
208 | return command | ||
209 | } | ||
210 | |||
211 | async function buildHLSCommand (command: ffmpeg.FfmpegCommand, options: TranscodeOptions) { | ||
212 | const videoPath = getHLSVideoPath(options) | ||
213 | |||
214 | command = await presetCopy(command) | ||
215 | |||
216 | command = command.outputOption('-hls_time 4') | ||
217 | .outputOption('-hls_list_size 0') | ||
218 | .outputOption('-hls_playlist_type vod') | ||
219 | .outputOption('-hls_segment_filename ' + videoPath) | ||
220 | .outputOption('-hls_segment_type fmp4') | ||
221 | .outputOption('-f hls') | ||
222 | .outputOption('-hls_flags single_file') | ||
223 | |||
224 | return command | ||
225 | } | ||
226 | |||
227 | function getHLSVideoPath (options: TranscodeOptions) { | ||
228 | return `${dirname(options.outputPath)}/${options.hlsPlaylist.videoFilename}` | ||
229 | } | ||
230 | |||
231 | async function onTranscodingSuccess (options: TranscodeOptions) { | ||
232 | if (!options.hlsPlaylist) return | ||
233 | |||
234 | // Fix wrong mapping with some ffmpeg versions | ||
235 | const fileContent = await readFile(options.outputPath) | ||
236 | |||
237 | const videoFileName = options.hlsPlaylist.videoFilename | ||
238 | const videoFilePath = getHLSVideoPath(options) | ||
239 | |||
240 | const newContent = fileContent.toString() | ||
241 | .replace(`#EXT-X-MAP:URI="${videoFilePath}",`, `#EXT-X-MAP:URI="${videoFileName}",`) | ||
242 | |||
243 | await writeFile(options.outputPath, newContent) | ||
244 | } | ||
245 | |||
181 | function getVideoFileStream (path: string) { | 246 | function getVideoFileStream (path: string) { |
182 | return new Promise<any>((res, rej) => { | 247 | return new Promise<any>((res, rej) => { |
183 | ffmpeg.ffprobe(path, (err, metadata) => { | 248 | ffmpeg.ffprobe(path, (err, metadata) => { |
@@ -348,3 +413,10 @@ async function presetH264 (command: ffmpeg.FfmpegCommand, resolution: VideoResol | |||
348 | 413 | ||
349 | return localCommand | 414 | return localCommand |
350 | } | 415 | } |
416 | |||
417 | async function presetCopy (command: ffmpeg.FfmpegCommand): Promise<ffmpeg.FfmpegCommand> { | ||
418 | return command | ||
419 | .format('mp4') | ||
420 | .videoCodec('copy') | ||
421 | .audioCodec('copy') | ||
422 | } | ||
diff --git a/server/helpers/image-utils.ts b/server/helpers/image-utils.ts index e43ea3f1d..bd81aa3ba 100644 --- a/server/helpers/image-utils.ts +++ b/server/helpers/image-utils.ts | |||
@@ -4,18 +4,19 @@ import { readFile, remove } from 'fs-extra' | |||
4 | import { logger } from './logger' | 4 | import { logger } from './logger' |
5 | 5 | ||
6 | async function processImage ( | 6 | async function processImage ( |
7 | physicalFile: { path: string }, | 7 | path: string, |
8 | destination: string, | 8 | destination: string, |
9 | newSize: { width: number, height: number } | 9 | newSize: { width: number, height: number }, |
10 | keepOriginal = false | ||
10 | ) { | 11 | ) { |
11 | if (physicalFile.path === destination) { | 12 | if (path === destination) { |
12 | throw new Error('Sharp needs an input path different that the output path.') | 13 | throw new Error('Sharp needs an input path different that the output path.') |
13 | } | 14 | } |
14 | 15 | ||
15 | logger.debug('Processing image %s to %s.', physicalFile.path, destination) | 16 | logger.debug('Processing image %s to %s.', path, destination) |
16 | 17 | ||
17 | // Avoid sharp cache | 18 | // Avoid sharp cache |
18 | const buf = await readFile(physicalFile.path) | 19 | const buf = await readFile(path) |
19 | const sharpInstance = sharp(buf) | 20 | const sharpInstance = sharp(buf) |
20 | 21 | ||
21 | await remove(destination) | 22 | await remove(destination) |
@@ -24,7 +25,7 @@ async function processImage ( | |||
24 | .resize(newSize.width, newSize.height) | 25 | .resize(newSize.width, newSize.height) |
25 | .toFile(destination) | 26 | .toFile(destination) |
26 | 27 | ||
27 | await remove(physicalFile.path) | 28 | if (keepOriginal !== true) await remove(path) |
28 | } | 29 | } |
29 | 30 | ||
30 | // --------------------------------------------------------------------------- | 31 | // --------------------------------------------------------------------------- |
diff --git a/server/helpers/logger.ts b/server/helpers/logger.ts index 203e637a8..734523b01 100644 --- a/server/helpers/logger.ts +++ b/server/helpers/logger.ts | |||
@@ -2,11 +2,13 @@ | |||
2 | import { mkdirpSync } from 'fs-extra' | 2 | import { mkdirpSync } from 'fs-extra' |
3 | import * as path from 'path' | 3 | import * as path from 'path' |
4 | import * as winston from 'winston' | 4 | import * as winston from 'winston' |
5 | import { CONFIG } from '../initializers' | 5 | import { CONFIG } from '../initializers/config' |
6 | import { omit } from 'lodash' | ||
6 | 7 | ||
7 | const label = CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT | 8 | const label = CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT |
8 | 9 | ||
9 | // Create the directory if it does not exist | 10 | // Create the directory if it does not exist |
11 | // FIXME: use async | ||
10 | mkdirpSync(CONFIG.STORAGE.LOG_DIR) | 12 | mkdirpSync(CONFIG.STORAGE.LOG_DIR) |
11 | 13 | ||
12 | function loggerReplacer (key: string, value: any) { | 14 | function loggerReplacer (key: string, value: any) { |
@@ -22,13 +24,10 @@ function loggerReplacer (key: string, value: any) { | |||
22 | } | 24 | } |
23 | 25 | ||
24 | const consoleLoggerFormat = winston.format.printf(info => { | 26 | const consoleLoggerFormat = winston.format.printf(info => { |
25 | const obj = { | 27 | const obj = omit(info, 'label', 'timestamp', 'level', 'message') |
26 | meta: info.meta, | ||
27 | err: info.err, | ||
28 | sql: info.sql | ||
29 | } | ||
30 | 28 | ||
31 | let additionalInfos = JSON.stringify(obj, loggerReplacer, 2) | 29 | let additionalInfos = JSON.stringify(obj, loggerReplacer, 2) |
30 | |||
32 | if (additionalInfos === undefined || additionalInfos === '{}') additionalInfos = '' | 31 | if (additionalInfos === undefined || additionalInfos === '{}') additionalInfos = '' |
33 | else additionalInfos = ' ' + additionalInfos | 32 | else additionalInfos = ' ' + additionalInfos |
34 | 33 | ||
@@ -57,7 +56,7 @@ const logger = winston.createLogger({ | |||
57 | filename: path.join(CONFIG.STORAGE.LOG_DIR, 'peertube.log'), | 56 | filename: path.join(CONFIG.STORAGE.LOG_DIR, 'peertube.log'), |
58 | handleExceptions: true, | 57 | handleExceptions: true, |
59 | maxsize: 1024 * 1024 * 12, | 58 | maxsize: 1024 * 1024 * 12, |
60 | maxFiles: 5, | 59 | maxFiles: 20, |
61 | format: winston.format.combine( | 60 | format: winston.format.combine( |
62 | winston.format.timestamp(), | 61 | winston.format.timestamp(), |
63 | jsonLoggerFormat | 62 | jsonLoggerFormat |
diff --git a/server/helpers/peertube-crypto.ts b/server/helpers/peertube-crypto.ts index ab9ec077e..9148df2eb 100644 --- a/server/helpers/peertube-crypto.ts +++ b/server/helpers/peertube-crypto.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { Request } from 'express' | 1 | import { Request } from 'express' |
2 | import { BCRYPT_SALT_SIZE, HTTP_SIGNATURE, PRIVATE_RSA_KEY_SIZE } from '../initializers' | 2 | import { BCRYPT_SALT_SIZE, HTTP_SIGNATURE, PRIVATE_RSA_KEY_SIZE } from '../initializers/constants' |
3 | import { ActorModel } from '../models/activitypub/actor' | 3 | import { ActorModel } from '../models/activitypub/actor' |
4 | import { bcryptComparePromise, bcryptGenSaltPromise, bcryptHashPromise, createPrivateKey, getPublicKey, sha256 } from './core-utils' | 4 | import { bcryptComparePromise, bcryptGenSaltPromise, bcryptHashPromise, createPrivateKey, getPublicKey, sha256 } from './core-utils' |
5 | import { jsig, jsonld } from './custom-jsonld-signature' | 5 | import { jsig, jsonld } from './custom-jsonld-signature' |
diff --git a/server/helpers/requests.ts b/server/helpers/requests.ts index 3fc776f1a..2e30c94a1 100644 --- a/server/helpers/requests.ts +++ b/server/helpers/requests.ts | |||
@@ -1,13 +1,16 @@ | |||
1 | import * as Bluebird from 'bluebird' | 1 | import * as Bluebird from 'bluebird' |
2 | import { createWriteStream } from 'fs-extra' | 2 | import { createWriteStream, remove } from 'fs-extra' |
3 | import * as request from 'request' | 3 | import * as request from 'request' |
4 | import { ACTIVITY_PUB, CONFIG } from '../initializers' | 4 | import { ACTIVITY_PUB } from '../initializers/constants' |
5 | import { processImage } from './image-utils' | 5 | import { processImage } from './image-utils' |
6 | import { join } from 'path' | 6 | import { join } from 'path' |
7 | import { logger } from './logger' | ||
8 | import { CONFIG } from '../initializers/config' | ||
7 | 9 | ||
8 | function doRequest <T> ( | 10 | function doRequest <T> ( |
9 | requestOptions: request.CoreOptions & request.UriOptions & { activityPub?: boolean } | 11 | requestOptions: request.CoreOptions & request.UriOptions & { activityPub?: boolean }, |
10 | ): Bluebird<{ response: request.RequestResponse, body: any }> { | 12 | bodyKBLimit = 1000 // 1MB |
13 | ): Bluebird<{ response: request.RequestResponse, body: T }> { | ||
11 | if (requestOptions.activityPub === true) { | 14 | if (requestOptions.activityPub === true) { |
12 | if (!Array.isArray(requestOptions.headers)) requestOptions.headers = {} | 15 | if (!Array.isArray(requestOptions.headers)) requestOptions.headers = {} |
13 | requestOptions.headers['accept'] = ACTIVITY_PUB.ACCEPT_HEADER | 16 | requestOptions.headers['accept'] = ACTIVITY_PUB.ACCEPT_HEADER |
@@ -15,16 +18,29 @@ function doRequest <T> ( | |||
15 | 18 | ||
16 | return new Bluebird<{ response: request.RequestResponse, body: T }>((res, rej) => { | 19 | return new Bluebird<{ response: request.RequestResponse, body: T }>((res, rej) => { |
17 | request(requestOptions, (err, response, body) => err ? rej(err) : res({ response, body })) | 20 | request(requestOptions, (err, response, body) => err ? rej(err) : res({ response, body })) |
21 | .on('data', onRequestDataLengthCheck(bodyKBLimit)) | ||
18 | }) | 22 | }) |
19 | } | 23 | } |
20 | 24 | ||
21 | function doRequestAndSaveToFile (requestOptions: request.CoreOptions & request.UriOptions, destPath: string) { | 25 | function doRequestAndSaveToFile ( |
26 | requestOptions: request.CoreOptions & request.UriOptions, | ||
27 | destPath: string, | ||
28 | bodyKBLimit = 10000 // 10MB | ||
29 | ) { | ||
22 | return new Bluebird<void>((res, rej) => { | 30 | return new Bluebird<void>((res, rej) => { |
23 | const file = createWriteStream(destPath) | 31 | const file = createWriteStream(destPath) |
24 | file.on('finish', () => res()) | 32 | file.on('finish', () => res()) |
25 | 33 | ||
26 | request(requestOptions) | 34 | request(requestOptions) |
27 | .on('error', err => rej(err)) | 35 | .on('data', onRequestDataLengthCheck(bodyKBLimit)) |
36 | .on('error', err => { | ||
37 | file.close() | ||
38 | |||
39 | remove(destPath) | ||
40 | .catch(err => logger.error('Cannot remove %s after request failure.', destPath, { err })) | ||
41 | |||
42 | return rej(err) | ||
43 | }) | ||
28 | .pipe(file) | 44 | .pipe(file) |
29 | }) | 45 | }) |
30 | } | 46 | } |
@@ -34,7 +50,14 @@ async function downloadImage (url: string, destDir: string, destName: string, si | |||
34 | await doRequestAndSaveToFile({ method: 'GET', uri: url }, tmpPath) | 50 | await doRequestAndSaveToFile({ method: 'GET', uri: url }, tmpPath) |
35 | 51 | ||
36 | const destPath = join(destDir, destName) | 52 | const destPath = join(destDir, destName) |
37 | await processImage({ path: tmpPath }, destPath, size) | 53 | |
54 | try { | ||
55 | await processImage(tmpPath, destPath, size) | ||
56 | } catch (err) { | ||
57 | await remove(tmpPath) | ||
58 | |||
59 | throw err | ||
60 | } | ||
38 | } | 61 | } |
39 | 62 | ||
40 | // --------------------------------------------------------------------------- | 63 | // --------------------------------------------------------------------------- |
@@ -44,3 +67,21 @@ export { | |||
44 | doRequestAndSaveToFile, | 67 | doRequestAndSaveToFile, |
45 | downloadImage | 68 | downloadImage |
46 | } | 69 | } |
70 | |||
71 | // --------------------------------------------------------------------------- | ||
72 | |||
73 | // Thanks to https://github.com/request/request/issues/2470#issuecomment-268929907 <3 | ||
74 | function onRequestDataLengthCheck (bodyKBLimit: number) { | ||
75 | let bufferLength = 0 | ||
76 | const bytesLimit = bodyKBLimit * 1000 | ||
77 | |||
78 | return function (chunk) { | ||
79 | bufferLength += chunk.length | ||
80 | if (bufferLength > bytesLimit) { | ||
81 | this.abort() | ||
82 | |||
83 | const error = new Error(`Response was too large - aborted after ${bytesLimit} bytes.`) | ||
84 | this.emit('error', error) | ||
85 | } | ||
86 | } | ||
87 | } | ||
diff --git a/server/helpers/signup.ts b/server/helpers/signup.ts index cdce7989d..5eb56b3cf 100644 --- a/server/helpers/signup.ts +++ b/server/helpers/signup.ts | |||
@@ -1,6 +1,7 @@ | |||
1 | import { CONFIG } from '../initializers' | ||
2 | import { UserModel } from '../models/account/user' | 1 | import { UserModel } from '../models/account/user' |
3 | import * as ipaddr from 'ipaddr.js' | 2 | import * as ipaddr from 'ipaddr.js' |
3 | import { CONFIG } from '../initializers/config' | ||
4 | |||
4 | const isCidr = require('is-cidr') | 5 | const isCidr = require('is-cidr') |
5 | 6 | ||
6 | async function isSignupAllowed () { | 7 | async function isSignupAllowed () { |
diff --git a/server/helpers/utils.ts b/server/helpers/utils.ts index 3c3406e38..94ceb15e0 100644 --- a/server/helpers/utils.ts +++ b/server/helpers/utils.ts | |||
@@ -1,5 +1,4 @@ | |||
1 | import { ResultList } from '../../shared' | 1 | import { ResultList } from '../../shared' |
2 | import { CONFIG } from '../initializers' | ||
3 | import { ApplicationModel } from '../models/application/application' | 2 | import { ApplicationModel } from '../models/application/application' |
4 | import { execPromise, execPromise2, pseudoRandomBytesPromise, sha256 } from './core-utils' | 3 | import { execPromise, execPromise2, pseudoRandomBytesPromise, sha256 } from './core-utils' |
5 | import { logger } from './logger' | 4 | import { logger } from './logger' |
@@ -7,7 +6,7 @@ import { join } from 'path' | |||
7 | import { Instance as ParseTorrent } from 'parse-torrent' | 6 | import { Instance as ParseTorrent } from 'parse-torrent' |
8 | import { remove } from 'fs-extra' | 7 | import { remove } from 'fs-extra' |
9 | import * as memoizee from 'memoizee' | 8 | import * as memoizee from 'memoizee' |
10 | import { isArray } from './custom-validators/misc' | 9 | import { CONFIG } from '../initializers/config' |
11 | 10 | ||
12 | function deleteFileAsync (path: string) { | 11 | function deleteFileAsync (path: string) { |
13 | remove(path) | 12 | remove(path) |
diff --git a/server/helpers/video.ts b/server/helpers/video.ts index 1bd21467d..c90fe06c7 100644 --- a/server/helpers/video.ts +++ b/server/helpers/video.ts | |||
@@ -1,10 +1,12 @@ | |||
1 | import { VideoModel } from '../models/video/video' | 1 | import { VideoModel } from '../models/video/video' |
2 | 2 | ||
3 | type VideoFetchType = 'all' | 'only-video' | 'id' | 'none' | 3 | type VideoFetchType = 'all' | 'only-video' | 'only-video-with-rights' | 'id' | 'none' |
4 | 4 | ||
5 | function fetchVideo (id: number | string, fetchType: VideoFetchType, userId?: number) { | 5 | function fetchVideo (id: number | string, fetchType: VideoFetchType, userId?: number) { |
6 | if (fetchType === 'all') return VideoModel.loadAndPopulateAccountAndServerAndTags(id, undefined, userId) | 6 | if (fetchType === 'all') return VideoModel.loadAndPopulateAccountAndServerAndTags(id, undefined, userId) |
7 | 7 | ||
8 | if (fetchType === 'only-video-with-rights') return VideoModel.loadWithRights(id) | ||
9 | |||
8 | if (fetchType === 'only-video') return VideoModel.load(id) | 10 | if (fetchType === 'only-video') return VideoModel.load(id) |
9 | 11 | ||
10 | if (fetchType === 'id' || fetchType === 'none') return VideoModel.loadOnlyId(id) | 12 | if (fetchType === 'id' || fetchType === 'none') return VideoModel.loadOnlyId(id) |
diff --git a/server/helpers/webfinger.ts b/server/helpers/webfinger.ts index 156376943..049808846 100644 --- a/server/helpers/webfinger.ts +++ b/server/helpers/webfinger.ts | |||
@@ -3,7 +3,7 @@ import { WebFingerData } from '../../shared' | |||
3 | import { ActorModel } from '../models/activitypub/actor' | 3 | import { ActorModel } from '../models/activitypub/actor' |
4 | import { isTestInstance } from './core-utils' | 4 | import { isTestInstance } from './core-utils' |
5 | import { isActivityPubUrlValid } from './custom-validators/activitypub/misc' | 5 | import { isActivityPubUrlValid } from './custom-validators/activitypub/misc' |
6 | import { CONFIG } from '../initializers' | 6 | import { WEBSERVER } from '../initializers/constants' |
7 | 7 | ||
8 | const webfinger = new WebFinger({ | 8 | const webfinger = new WebFinger({ |
9 | webfist_fallback: false, | 9 | webfist_fallback: false, |
@@ -19,7 +19,7 @@ async function loadActorUrlOrGetFromWebfinger (uriArg: string) { | |||
19 | const [ name, host ] = uri.split('@') | 19 | const [ name, host ] = uri.split('@') |
20 | let actor: ActorModel | 20 | let actor: ActorModel |
21 | 21 | ||
22 | if (host === CONFIG.WEBSERVER.HOST) { | 22 | if (host === WEBSERVER.HOST) { |
23 | actor = await ActorModel.loadLocalByName(name) | 23 | actor = await ActorModel.loadLocalByName(name) |
24 | } else { | 24 | } else { |
25 | actor = await ActorModel.loadByNameAndHost(name, host) | 25 | actor = await ActorModel.loadByNameAndHost(name, host) |
diff --git a/server/helpers/webtorrent.ts b/server/helpers/webtorrent.ts index 3c9a0b96a..14dfe0d28 100644 --- a/server/helpers/webtorrent.ts +++ b/server/helpers/webtorrent.ts | |||
@@ -2,7 +2,7 @@ import { logger } from './logger' | |||
2 | import { generateVideoImportTmpPath } from './utils' | 2 | import { generateVideoImportTmpPath } from './utils' |
3 | import * as WebTorrent from 'webtorrent' | 3 | import * as WebTorrent from 'webtorrent' |
4 | import { createWriteStream, ensureDir, remove } from 'fs-extra' | 4 | import { createWriteStream, ensureDir, remove } from 'fs-extra' |
5 | import { CONFIG } from '../initializers' | 5 | import { CONFIG } from '../initializers/config' |
6 | import { dirname, join } from 'path' | 6 | import { dirname, join } from 'path' |
7 | 7 | ||
8 | async function downloadWebTorrentVideo (target: { magnetUri: string, torrentName?: string }, timeout: number) { | 8 | async function downloadWebTorrentVideo (target: { magnetUri: string, torrentName?: string }, timeout: number) { |
diff --git a/server/helpers/youtube-dl.ts b/server/helpers/youtube-dl.ts index b74351b42..b3079370f 100644 --- a/server/helpers/youtube-dl.ts +++ b/server/helpers/youtube-dl.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { truncate } from 'lodash' | 1 | import { truncate } from 'lodash' |
2 | import { CONSTRAINTS_FIELDS, VIDEO_CATEGORIES } from '../initializers' | 2 | import { CONSTRAINTS_FIELDS, VIDEO_CATEGORIES } from '../initializers/constants' |
3 | import { logger } from './logger' | 3 | import { logger } from './logger' |
4 | import { generateVideoImportTmpPath } from './utils' | 4 | import { generateVideoImportTmpPath } from './utils' |
5 | import { join } from 'path' | 5 | import { join } from 'path' |
@@ -16,6 +16,7 @@ export type YoutubeDLInfo = { | |||
16 | nsfw?: boolean | 16 | nsfw?: boolean |
17 | tags?: string[] | 17 | tags?: string[] |
18 | thumbnailUrl?: string | 18 | thumbnailUrl?: string |
19 | originallyPublishedAt?: Date | ||
19 | } | 20 | } |
20 | 21 | ||
21 | const processOptions = { | 22 | const processOptions = { |
@@ -47,6 +48,11 @@ function downloadYoutubeDLVideo (url: string, timeout: number) { | |||
47 | 48 | ||
48 | const options = [ '-f', 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best', '-o', path ] | 49 | const options = [ '-f', 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best', '-o', path ] |
49 | 50 | ||
51 | if (process.env.FFMPEG_PATH) { | ||
52 | options.push('--ffmpeg-location') | ||
53 | options.push(process.env.FFMPEG_PATH) | ||
54 | } | ||
55 | |||
50 | return new Promise<string>(async (res, rej) => { | 56 | return new Promise<string>(async (res, rej) => { |
51 | const youtubeDL = await safeGetYoutubeDL() | 57 | const youtubeDL = await safeGetYoutubeDL() |
52 | youtubeDL.exec(url, options, processOptions, err => { | 58 | youtubeDL.exec(url, options, processOptions, err => { |
@@ -142,13 +148,33 @@ async function safeGetYoutubeDL () { | |||
142 | return youtubeDL | 148 | return youtubeDL |
143 | } | 149 | } |
144 | 150 | ||
151 | function buildOriginallyPublishedAt (obj: any) { | ||
152 | let originallyPublishedAt: Date = null | ||
153 | |||
154 | const uploadDateMatcher = /^(\d{4})(\d{2})(\d{2})$/.exec(obj.upload_date) | ||
155 | if (uploadDateMatcher) { | ||
156 | originallyPublishedAt = new Date() | ||
157 | originallyPublishedAt.setHours(0, 0, 0, 0) | ||
158 | |||
159 | const year = parseInt(uploadDateMatcher[1], 10) | ||
160 | // Month starts from 0 | ||
161 | const month = parseInt(uploadDateMatcher[2], 10) - 1 | ||
162 | const day = parseInt(uploadDateMatcher[3], 10) | ||
163 | |||
164 | originallyPublishedAt.setFullYear(year, month, day) | ||
165 | } | ||
166 | |||
167 | return originallyPublishedAt | ||
168 | } | ||
169 | |||
145 | // --------------------------------------------------------------------------- | 170 | // --------------------------------------------------------------------------- |
146 | 171 | ||
147 | export { | 172 | export { |
148 | updateYoutubeDLBinary, | 173 | updateYoutubeDLBinary, |
149 | downloadYoutubeDLVideo, | 174 | downloadYoutubeDLVideo, |
150 | getYoutubeDLInfo, | 175 | getYoutubeDLInfo, |
151 | safeGetYoutubeDL | 176 | safeGetYoutubeDL, |
177 | buildOriginallyPublishedAt | ||
152 | } | 178 | } |
153 | 179 | ||
154 | // --------------------------------------------------------------------------- | 180 | // --------------------------------------------------------------------------- |
@@ -180,7 +206,8 @@ function buildVideoInfo (obj: any) { | |||
180 | licence: getLicence(obj.license), | 206 | licence: getLicence(obj.license), |
181 | nsfw: isNSFW(obj), | 207 | nsfw: isNSFW(obj), |
182 | tags: getTags(obj.tags), | 208 | tags: getTags(obj.tags), |
183 | thumbnailUrl: obj.thumbnail || undefined | 209 | thumbnailUrl: obj.thumbnail || undefined, |
210 | originallyPublishedAt: buildOriginallyPublishedAt(obj) | ||
184 | } | 211 | } |
185 | } | 212 | } |
186 | 213 | ||