aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/helpers
diff options
context:
space:
mode:
Diffstat (limited to 'server/helpers')
-rw-r--r--server/helpers/core-utils.ts14
-rw-r--r--server/helpers/custom-validators/activitypub/videos.ts10
-rw-r--r--server/helpers/custom-validators/misc.ts2
-rw-r--r--server/helpers/custom-validators/plugins.ts22
-rw-r--r--server/helpers/custom-validators/servers.ts4
-rw-r--r--server/helpers/custom-validators/video-studio.ts3
-rw-r--r--server/helpers/custom-validators/videos.ts5
-rw-r--r--server/helpers/ffmpeg/ffmpeg-commons.ts2
-rw-r--r--server/helpers/ffmpeg/ffmpeg-vod.ts13
-rw-r--r--server/helpers/ffmpeg/ffprobe-utils.ts9
-rw-r--r--server/helpers/otp.ts58
-rw-r--r--server/helpers/peertube-crypto.ts49
-rw-r--r--server/helpers/upload.ts6
-rw-r--r--server/helpers/video.ts5
-rw-r--r--server/helpers/webtorrent.ts16
-rw-r--r--server/helpers/youtube-dl/youtube-dl-cli.ts15
-rw-r--r--server/helpers/youtube-dl/youtube-dl-wrapper.ts2
17 files changed, 192 insertions, 43 deletions
diff --git a/server/helpers/core-utils.ts b/server/helpers/core-utils.ts
index c762f6a29..73bd994c1 100644
--- a/server/helpers/core-utils.ts
+++ b/server/helpers/core-utils.ts
@@ -6,7 +6,7 @@
6*/ 6*/
7 7
8import { exec, ExecOptions } from 'child_process' 8import { exec, ExecOptions } from 'child_process'
9import { ED25519KeyPairOptions, generateKeyPair, randomBytes, RSAKeyPairOptions } from 'crypto' 9import { ED25519KeyPairOptions, generateKeyPair, randomBytes, RSAKeyPairOptions, scrypt } from 'crypto'
10import { truncate } from 'lodash' 10import { truncate } from 'lodash'
11import { pipeline } from 'stream' 11import { pipeline } from 'stream'
12import { URL } from 'url' 12import { URL } from 'url'
@@ -311,7 +311,17 @@ function promisify2<T, U, A> (func: (arg1: T, arg2: U, cb: (err: any, result: A)
311 } 311 }
312} 312}
313 313
314// eslint-disable-next-line max-len
315function promisify3<T, U, V, A> (func: (arg1: T, arg2: U, arg3: V, cb: (err: any, result: A) => void) => void): (arg1: T, arg2: U, arg3: V) => Promise<A> {
316 return function promisified (arg1: T, arg2: U, arg3: V): Promise<A> {
317 return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
318 func.apply(null, [ arg1, arg2, arg3, (err: any, res: A) => err ? reject(err) : resolve(res) ])
319 })
320 }
321}
322
314const randomBytesPromise = promisify1<number, Buffer>(randomBytes) 323const randomBytesPromise = promisify1<number, Buffer>(randomBytes)
324const scryptPromise = promisify3<string, string, number, Buffer>(scrypt)
315const execPromise2 = promisify2<string, any, string>(exec) 325const execPromise2 = promisify2<string, any, string>(exec)
316const execPromise = promisify1<string, string>(exec) 326const execPromise = promisify1<string, string>(exec)
317const pipelinePromise = promisify(pipeline) 327const pipelinePromise = promisify(pipeline)
@@ -339,6 +349,8 @@ export {
339 promisify1, 349 promisify1,
340 promisify2, 350 promisify2,
341 351
352 scryptPromise,
353
342 randomBytesPromise, 354 randomBytesPromise,
343 355
344 generateRSAKeyPairPromise, 356 generateRSAKeyPairPromise,
diff --git a/server/helpers/custom-validators/activitypub/videos.ts b/server/helpers/custom-validators/activitypub/videos.ts
index 2a2f008b9..97b3577af 100644
--- a/server/helpers/custom-validators/activitypub/videos.ts
+++ b/server/helpers/custom-validators/activitypub/videos.ts
@@ -7,11 +7,11 @@ import { peertubeTruncate } from '../../core-utils'
7import { isArray, isBooleanValid, isDateValid, isUUIDValid } from '../misc' 7import { isArray, isBooleanValid, isDateValid, isUUIDValid } from '../misc'
8import { isLiveLatencyModeValid } from '../video-lives' 8import { isLiveLatencyModeValid } from '../video-lives'
9import { 9import {
10 isVideoDescriptionValid,
10 isVideoDurationValid, 11 isVideoDurationValid,
11 isVideoNameValid, 12 isVideoNameValid,
12 isVideoStateValid, 13 isVideoStateValid,
13 isVideoTagValid, 14 isVideoTagValid,
14 isVideoTruncatedDescriptionValid,
15 isVideoViewsValid 15 isVideoViewsValid
16} from '../videos' 16} from '../videos'
17import { isActivityPubUrlValid, isActivityPubVideoDurationValid, isBaseActivityValid, setValidAttributedTo } from './misc' 17import { isActivityPubUrlValid, isActivityPubVideoDurationValid, isBaseActivityValid, setValidAttributedTo } from './misc'
@@ -32,7 +32,7 @@ function sanitizeAndCheckVideoTorrentObject (video: any) {
32 logger.debug('Video has invalid urls', { video }) 32 logger.debug('Video has invalid urls', { video })
33 return false 33 return false
34 } 34 }
35 if (!setRemoteVideoTruncatedContent(video)) { 35 if (!setRemoteVideoContent(video)) {
36 logger.debug('Video has invalid content', { video }) 36 logger.debug('Video has invalid content', { video })
37 return false 37 return false
38 } 38 }
@@ -168,7 +168,7 @@ function isRemoteStringIdentifierValid (data: any) {
168} 168}
169 169
170function isRemoteVideoContentValid (mediaType: string, content: string) { 170function isRemoteVideoContentValid (mediaType: string, content: string) {
171 return mediaType === 'text/markdown' && isVideoTruncatedDescriptionValid(content) 171 return mediaType === 'text/markdown' && isVideoDescriptionValid(content)
172} 172}
173 173
174function setValidRemoteIcon (video: any) { 174function setValidRemoteIcon (video: any) {
@@ -194,9 +194,9 @@ function setValidRemoteVideoUrls (video: any) {
194 return true 194 return true
195} 195}
196 196
197function setRemoteVideoTruncatedContent (video: any) { 197function setRemoteVideoContent (video: any) {
198 if (video.content) { 198 if (video.content) {
199 video.content = peertubeTruncate(video.content, { length: CONSTRAINTS_FIELDS.VIDEOS.TRUNCATED_DESCRIPTION.max }) 199 video.content = peertubeTruncate(video.content, { length: CONSTRAINTS_FIELDS.VIDEOS.DESCRIPTION.max })
200 } 200 }
201 201
202 return true 202 return true
diff --git a/server/helpers/custom-validators/misc.ts b/server/helpers/custom-validators/misc.ts
index 17750379d..3dc5504e3 100644
--- a/server/helpers/custom-validators/misc.ts
+++ b/server/helpers/custom-validators/misc.ts
@@ -86,7 +86,7 @@ function isFileValid (options: {
86 86
87 // The file exists 87 // The file exists
88 const file = fileArray[0] 88 const file = fileArray[0]
89 if (!file || !file.originalname) return false 89 if (!file?.originalname) return false
90 90
91 // Check size 91 // Check size
92 if ((maxSize !== null) && file.size > maxSize) return false 92 if ((maxSize !== null) && file.size > maxSize) return false
diff --git a/server/helpers/custom-validators/plugins.ts b/server/helpers/custom-validators/plugins.ts
index 60b29dc89..a20de0c4a 100644
--- a/server/helpers/custom-validators/plugins.ts
+++ b/server/helpers/custom-validators/plugins.ts
@@ -1,9 +1,9 @@
1import { exists, isArray, isSafePath } from './misc'
2import validator from 'validator' 1import validator from 'validator'
2import { PluginPackageJSON } from '../../../shared/models/plugins/plugin-package-json.model'
3import { PluginType } from '../../../shared/models/plugins/plugin.type' 3import { PluginType } from '../../../shared/models/plugins/plugin.type'
4import { CONSTRAINTS_FIELDS } from '../../initializers/constants' 4import { CONSTRAINTS_FIELDS } from '../../initializers/constants'
5import { PluginPackageJSON } from '../../../shared/models/plugins/plugin-package-json.model'
6import { isUrlValid } from './activitypub/misc' 5import { isUrlValid } from './activitypub/misc'
6import { exists, isArray, isSafePath } from './misc'
7 7
8const PLUGINS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.PLUGINS 8const PLUGINS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.PLUGINS
9 9
@@ -29,7 +29,7 @@ function isPluginDescriptionValid (value: string) {
29 return exists(value) && validator.isLength(value, PLUGINS_CONSTRAINTS_FIELDS.DESCRIPTION) 29 return exists(value) && validator.isLength(value, PLUGINS_CONSTRAINTS_FIELDS.DESCRIPTION)
30} 30}
31 31
32function isPluginVersionValid (value: string) { 32function isPluginStableVersionValid (value: string) {
33 if (!exists(value)) return false 33 if (!exists(value)) return false
34 34
35 const parts = (value + '').split('.') 35 const parts = (value + '').split('.')
@@ -37,6 +37,19 @@ function isPluginVersionValid (value: string) {
37 return parts.length === 3 && parts.every(p => validator.isInt(p)) 37 return parts.length === 3 && parts.every(p => validator.isInt(p))
38} 38}
39 39
40function isPluginStableOrUnstableVersionValid (value: string) {
41 if (!exists(value)) return false
42
43 // suffix is beta.x or alpha.x
44 const [ stable, suffix ] = value.split('-')
45 if (!isPluginStableVersionValid(stable)) return false
46
47 const suffixRegex = /^(rc|alpha|beta)\.\d+$/
48 if (suffix && !suffixRegex.test(suffix)) return false
49
50 return true
51}
52
40function isPluginEngineValid (engine: any) { 53function isPluginEngineValid (engine: any) {
41 return exists(engine) && exists(engine.peertube) 54 return exists(engine) && exists(engine.peertube)
42} 55}
@@ -156,7 +169,8 @@ export {
156 isPackageJSONValid, 169 isPackageJSONValid,
157 isThemeNameValid, 170 isThemeNameValid,
158 isPluginHomepage, 171 isPluginHomepage,
159 isPluginVersionValid, 172 isPluginStableVersionValid,
173 isPluginStableOrUnstableVersionValid,
160 isPluginNameValid, 174 isPluginNameValid,
161 isPluginDescriptionValid, 175 isPluginDescriptionValid,
162 isLibraryCodeValid, 176 isLibraryCodeValid,
diff --git a/server/helpers/custom-validators/servers.ts b/server/helpers/custom-validators/servers.ts
index b9f45c282..94fda05aa 100644
--- a/server/helpers/custom-validators/servers.ts
+++ b/server/helpers/custom-validators/servers.ts
@@ -1,6 +1,6 @@
1import validator from 'validator' 1import validator from 'validator'
2import { CONFIG } from '@server/initializers/config'
2import { CONSTRAINTS_FIELDS } from '../../initializers/constants' 3import { CONSTRAINTS_FIELDS } from '../../initializers/constants'
3import { isTestOrDevInstance } from '../core-utils'
4import { exists, isArray } from './misc' 4import { exists, isArray } from './misc'
5 5
6function isHostValid (host: string) { 6function isHostValid (host: string) {
@@ -10,7 +10,7 @@ function isHostValid (host: string) {
10 } 10 }
11 11
12 // We validate 'localhost', so we don't have the top level domain 12 // We validate 'localhost', so we don't have the top level domain
13 if (isTestOrDevInstance()) { 13 if (CONFIG.WEBSERVER.HOSTNAME === 'localhost') {
14 isURLOptions.require_tld = false 14 isURLOptions.require_tld = false
15 } 15 }
16 16
diff --git a/server/helpers/custom-validators/video-studio.ts b/server/helpers/custom-validators/video-studio.ts
index 19e7906d5..68dfec8dd 100644
--- a/server/helpers/custom-validators/video-studio.ts
+++ b/server/helpers/custom-validators/video-studio.ts
@@ -4,6 +4,7 @@ import { buildTaskFileFieldname } from '@server/lib/video-studio'
4import { VideoStudioTask } from '@shared/models' 4import { VideoStudioTask } from '@shared/models'
5import { isArray } from './misc' 5import { isArray } from './misc'
6import { isVideoFileMimeTypeValid, isVideoImageValid } from './videos' 6import { isVideoFileMimeTypeValid, isVideoImageValid } from './videos'
7import { forceNumber } from '@shared/core-utils'
7 8
8function isValidStudioTasksArray (tasks: any) { 9function isValidStudioTasksArray (tasks: any) {
9 if (!isArray(tasks)) return false 10 if (!isArray(tasks)) return false
@@ -24,7 +25,7 @@ function isStudioCutTaskValid (task: VideoStudioTask) {
24 25
25 if (!start || !end) return true 26 if (!start || !end) return true
26 27
27 return parseInt(start + '') < parseInt(end + '') 28 return forceNumber(start) < forceNumber(end)
28} 29}
29 30
30function isStudioTaskAddIntroOutroValid (task: VideoStudioTask, indice: number, files: Express.Multer.File[]) { 31function isStudioTaskAddIntroOutroValid (task: VideoStudioTask, indice: number, files: Express.Multer.File[]) {
diff --git a/server/helpers/custom-validators/videos.ts b/server/helpers/custom-validators/videos.ts
index 3ebfe2937..9e8177f77 100644
--- a/server/helpers/custom-validators/videos.ts
+++ b/server/helpers/custom-validators/videos.ts
@@ -45,10 +45,6 @@ function isVideoDurationValid (value: string) {
45 return exists(value) && validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.DURATION) 45 return exists(value) && validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.DURATION)
46} 46}
47 47
48function isVideoTruncatedDescriptionValid (value: string) {
49 return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.TRUNCATED_DESCRIPTION)
50}
51
52function isVideoDescriptionValid (value: string) { 48function isVideoDescriptionValid (value: string) {
53 return value === null || (exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.DESCRIPTION)) 49 return value === null || (exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.DESCRIPTION))
54} 50}
@@ -151,7 +147,6 @@ export {
151 isVideoCategoryValid, 147 isVideoCategoryValid,
152 isVideoLicenceValid, 148 isVideoLicenceValid,
153 isVideoLanguageValid, 149 isVideoLanguageValid,
154 isVideoTruncatedDescriptionValid,
155 isVideoDescriptionValid, 150 isVideoDescriptionValid,
156 isVideoFileInfoHashValid, 151 isVideoFileInfoHashValid,
157 isVideoNameValid, 152 isVideoNameValid,
diff --git a/server/helpers/ffmpeg/ffmpeg-commons.ts b/server/helpers/ffmpeg/ffmpeg-commons.ts
index b01989899..3906a2089 100644
--- a/server/helpers/ffmpeg/ffmpeg-commons.ts
+++ b/server/helpers/ffmpeg/ffmpeg-commons.ts
@@ -38,7 +38,7 @@ function getFFmpegVersion () {
38 return execPromise(`${ffmpegPath} -version`) 38 return execPromise(`${ffmpegPath} -version`)
39 .then(stdout => { 39 .then(stdout => {
40 const parsed = stdout.match(/ffmpeg version .?(\d+\.\d+(\.\d+)?)/) 40 const parsed = stdout.match(/ffmpeg version .?(\d+\.\d+(\.\d+)?)/)
41 if (!parsed || !parsed[1]) return rej(new Error(`Could not find ffmpeg version in ${stdout}`)) 41 if (!parsed?.[1]) return rej(new Error(`Could not find ffmpeg version in ${stdout}`))
42 42
43 // Fix ffmpeg version that does not include patch version (4.4 for example) 43 // Fix ffmpeg version that does not include patch version (4.4 for example)
44 let version = parsed[1] 44 let version = parsed[1]
diff --git a/server/helpers/ffmpeg/ffmpeg-vod.ts b/server/helpers/ffmpeg/ffmpeg-vod.ts
index 7a81a1313..d84703eb9 100644
--- a/server/helpers/ffmpeg/ffmpeg-vod.ts
+++ b/server/helpers/ffmpeg/ffmpeg-vod.ts
@@ -1,14 +1,15 @@
1import { MutexInterface } from 'async-mutex'
1import { Job } from 'bullmq' 2import { Job } from 'bullmq'
2import { FfmpegCommand } from 'fluent-ffmpeg' 3import { FfmpegCommand } from 'fluent-ffmpeg'
3import { readFile, writeFile } from 'fs-extra' 4import { readFile, writeFile } from 'fs-extra'
4import { dirname } from 'path' 5import { dirname } from 'path'
6import { VIDEO_TRANSCODING_FPS } from '@server/initializers/constants'
5import { pick } from '@shared/core-utils' 7import { pick } from '@shared/core-utils'
6import { AvailableEncoders, VideoResolution } from '@shared/models' 8import { AvailableEncoders, VideoResolution } from '@shared/models'
7import { logger, loggerTagsFactory } from '../logger' 9import { logger, loggerTagsFactory } from '../logger'
8import { getFFmpeg, runCommand } from './ffmpeg-commons' 10import { getFFmpeg, runCommand } from './ffmpeg-commons'
9import { presetCopy, presetOnlyAudio, presetVOD } from './ffmpeg-presets' 11import { presetCopy, presetOnlyAudio, presetVOD } from './ffmpeg-presets'
10import { computeFPS, ffprobePromise, getVideoStreamDimensionsInfo, getVideoStreamFPS } from './ffprobe-utils' 12import { computeFPS, ffprobePromise, getVideoStreamDimensionsInfo, getVideoStreamFPS } from './ffprobe-utils'
11import { VIDEO_TRANSCODING_FPS } from '@server/initializers/constants'
12 13
13const lTags = loggerTagsFactory('ffmpeg') 14const lTags = loggerTagsFactory('ffmpeg')
14 15
@@ -22,6 +23,10 @@ interface BaseTranscodeVODOptions {
22 inputPath: string 23 inputPath: string
23 outputPath: string 24 outputPath: string
24 25
26 // Will be released after the ffmpeg started
27 // To prevent a bug where the input file does not exist anymore when running ffmpeg
28 inputFileMutexReleaser: MutexInterface.Releaser
29
25 availableEncoders: AvailableEncoders 30 availableEncoders: AvailableEncoders
26 profile: string 31 profile: string
27 32
@@ -94,6 +99,12 @@ async function transcodeVOD (options: TranscodeVODOptions) {
94 99
95 command = await builders[options.type](command, options) 100 command = await builders[options.type](command, options)
96 101
102 command.on('start', () => {
103 setTimeout(() => {
104 options.inputFileMutexReleaser()
105 }, 1000)
106 })
107
97 await runCommand({ command, job: options.job }) 108 await runCommand({ command, job: options.job })
98 109
99 await fixHLSPlaylistIfNeeded(options) 110 await fixHLSPlaylistIfNeeded(options)
diff --git a/server/helpers/ffmpeg/ffprobe-utils.ts b/server/helpers/ffmpeg/ffprobe-utils.ts
index 2c6253d44..fb270b3cb 100644
--- a/server/helpers/ffmpeg/ffprobe-utils.ts
+++ b/server/helpers/ffmpeg/ffprobe-utils.ts
@@ -15,6 +15,7 @@ import {
15import { VideoResolution, VideoTranscodingFPS } from '@shared/models' 15import { VideoResolution, VideoTranscodingFPS } from '@shared/models'
16import { CONFIG } from '../../initializers/config' 16import { CONFIG } from '../../initializers/config'
17import { VIDEO_TRANSCODING_FPS } from '../../initializers/constants' 17import { VIDEO_TRANSCODING_FPS } from '../../initializers/constants'
18import { toEven } from '../core-utils'
18import { logger } from '../logger' 19import { logger } from '../logger'
19 20
20/** 21/**
@@ -96,8 +97,9 @@ function computeResolutionsToTranscode (options: {
96 type: 'vod' | 'live' 97 type: 'vod' | 'live'
97 includeInput: boolean 98 includeInput: boolean
98 strictLower: boolean 99 strictLower: boolean
100 hasAudio: boolean
99}) { 101}) {
100 const { input, type, includeInput, strictLower } = options 102 const { input, type, includeInput, strictLower, hasAudio } = options
101 103
102 const configResolutions = type === 'vod' 104 const configResolutions = type === 'vod'
103 ? CONFIG.TRANSCODING.RESOLUTIONS 105 ? CONFIG.TRANSCODING.RESOLUTIONS
@@ -125,12 +127,15 @@ function computeResolutionsToTranscode (options: {
125 if (input < resolution) continue 127 if (input < resolution) continue
126 // We only want lower resolutions than input file 128 // We only want lower resolutions than input file
127 if (strictLower && input === resolution) continue 129 if (strictLower && input === resolution) continue
130 // Audio resolutio but no audio in the video
131 if (resolution === VideoResolution.H_NOVIDEO && !hasAudio) continue
128 132
129 resolutionsEnabled.add(resolution) 133 resolutionsEnabled.add(resolution)
130 } 134 }
131 135
132 if (includeInput) { 136 if (includeInput) {
133 resolutionsEnabled.add(input) 137 // Always use an even resolution to avoid issues with ffmpeg
138 resolutionsEnabled.add(toEven(input))
134 } 139 }
135 140
136 return Array.from(resolutionsEnabled) 141 return Array.from(resolutionsEnabled)
diff --git a/server/helpers/otp.ts b/server/helpers/otp.ts
new file mode 100644
index 000000000..a32cc9621
--- /dev/null
+++ b/server/helpers/otp.ts
@@ -0,0 +1,58 @@
1import { Secret, TOTP } from 'otpauth'
2import { CONFIG } from '@server/initializers/config'
3import { WEBSERVER } from '@server/initializers/constants'
4import { decrypt } from './peertube-crypto'
5
6async function isOTPValid (options: {
7 encryptedSecret: string
8 token: string
9}) {
10 const { token, encryptedSecret } = options
11
12 const secret = await decrypt(encryptedSecret, CONFIG.SECRETS.PEERTUBE)
13
14 const totp = new TOTP({
15 ...baseOTPOptions(),
16
17 secret
18 })
19
20 const delta = totp.validate({
21 token,
22 window: 1
23 })
24
25 if (delta === null) return false
26
27 return true
28}
29
30function generateOTPSecret (email: string) {
31 const totp = new TOTP({
32 ...baseOTPOptions(),
33
34 label: email,
35 secret: new Secret()
36 })
37
38 return {
39 secret: totp.secret.base32,
40 uri: totp.toString()
41 }
42}
43
44export {
45 isOTPValid,
46 generateOTPSecret
47}
48
49// ---------------------------------------------------------------------------
50
51function baseOTPOptions () {
52 return {
53 issuer: WEBSERVER.HOST,
54 algorithm: 'SHA1',
55 digits: 6,
56 period: 30
57 }
58}
diff --git a/server/helpers/peertube-crypto.ts b/server/helpers/peertube-crypto.ts
index 8aca50900..ae7d11800 100644
--- a/server/helpers/peertube-crypto.ts
+++ b/server/helpers/peertube-crypto.ts
@@ -1,11 +1,11 @@
1import { compare, genSalt, hash } from 'bcrypt' 1import { compare, genSalt, hash } from 'bcrypt'
2import { createSign, createVerify } from 'crypto' 2import { createCipheriv, createDecipheriv, createSign, createVerify } from 'crypto'
3import { Request } from 'express' 3import { Request } from 'express'
4import { cloneDeep } from 'lodash' 4import { cloneDeep } from 'lodash'
5import { sha256 } from '@shared/extra-utils' 5import { sha256 } from '@shared/extra-utils'
6import { BCRYPT_SALT_SIZE, HTTP_SIGNATURE, PRIVATE_RSA_KEY_SIZE } from '../initializers/constants' 6import { BCRYPT_SALT_SIZE, ENCRYPTION, HTTP_SIGNATURE, PRIVATE_RSA_KEY_SIZE } from '../initializers/constants'
7import { MActor } from '../types/models' 7import { MActor } from '../types/models'
8import { generateRSAKeyPairPromise, promisify1, promisify2 } from './core-utils' 8import { generateRSAKeyPairPromise, promisify1, promisify2, randomBytesPromise, scryptPromise } from './core-utils'
9import { jsonld } from './custom-jsonld-signature' 9import { jsonld } from './custom-jsonld-signature'
10import { logger } from './logger' 10import { logger } from './logger'
11 11
@@ -21,9 +21,13 @@ function createPrivateAndPublicKeys () {
21 return generateRSAKeyPairPromise(PRIVATE_RSA_KEY_SIZE) 21 return generateRSAKeyPairPromise(PRIVATE_RSA_KEY_SIZE)
22} 22}
23 23
24// ---------------------------------------------------------------------------
24// User password checks 25// User password checks
26// ---------------------------------------------------------------------------
25 27
26function comparePassword (plainPassword: string, hashPassword: string) { 28function comparePassword (plainPassword: string, hashPassword: string) {
29 if (!plainPassword) return Promise.resolve(false)
30
27 return bcryptComparePromise(plainPassword, hashPassword) 31 return bcryptComparePromise(plainPassword, hashPassword)
28} 32}
29 33
@@ -33,7 +37,9 @@ async function cryptPassword (password: string) {
33 return bcryptHashPromise(password, salt) 37 return bcryptHashPromise(password, salt)
34} 38}
35 39
40// ---------------------------------------------------------------------------
36// HTTP Signature 41// HTTP Signature
42// ---------------------------------------------------------------------------
37 43
38function isHTTPSignatureDigestValid (rawBody: Buffer, req: Request): boolean { 44function isHTTPSignatureDigestValid (rawBody: Buffer, req: Request): boolean {
39 if (req.headers[HTTP_SIGNATURE.HEADER_NAME] && req.headers['digest']) { 45 if (req.headers[HTTP_SIGNATURE.HEADER_NAME] && req.headers['digest']) {
@@ -62,7 +68,9 @@ function parseHTTPSignature (req: Request, clockSkew?: number) {
62 return parsed 68 return parsed
63} 69}
64 70
71// ---------------------------------------------------------------------------
65// JSONLD 72// JSONLD
73// ---------------------------------------------------------------------------
66 74
67function isJsonLDSignatureVerified (fromActor: MActor, signedDocument: any): Promise<boolean> { 75function isJsonLDSignatureVerified (fromActor: MActor, signedDocument: any): Promise<boolean> {
68 if (signedDocument.signature.type === 'RsaSignature2017') { 76 if (signedDocument.signature.type === 'RsaSignature2017') {
@@ -112,6 +120,8 @@ async function signJsonLDObject <T> (byActor: MActor, data: T) {
112 return Object.assign(data, { signature }) 120 return Object.assign(data, { signature })
113} 121}
114 122
123// ---------------------------------------------------------------------------
124
115function buildDigest (body: any) { 125function buildDigest (body: any) {
116 const rawBody = typeof body === 'string' ? body : JSON.stringify(body) 126 const rawBody = typeof body === 'string' ? body : JSON.stringify(body)
117 127
@@ -119,6 +129,34 @@ function buildDigest (body: any) {
119} 129}
120 130
121// --------------------------------------------------------------------------- 131// ---------------------------------------------------------------------------
132// Encryption
133// ---------------------------------------------------------------------------
134
135async function encrypt (str: string, secret: string) {
136 const iv = await randomBytesPromise(ENCRYPTION.IV)
137
138 const key = await scryptPromise(secret, ENCRYPTION.SALT, 32)
139 const cipher = createCipheriv(ENCRYPTION.ALGORITHM, key, iv)
140
141 let encrypted = iv.toString(ENCRYPTION.ENCODING) + ':'
142 encrypted += cipher.update(str, 'utf8', ENCRYPTION.ENCODING)
143 encrypted += cipher.final(ENCRYPTION.ENCODING)
144
145 return encrypted
146}
147
148async function decrypt (encryptedArg: string, secret: string) {
149 const [ ivStr, encryptedStr ] = encryptedArg.split(':')
150
151 const iv = Buffer.from(ivStr, 'hex')
152 const key = await scryptPromise(secret, ENCRYPTION.SALT, 32)
153
154 const decipher = createDecipheriv(ENCRYPTION.ALGORITHM, key, iv)
155
156 return decipher.update(encryptedStr, ENCRYPTION.ENCODING, 'utf8') + decipher.final('utf8')
157}
158
159// ---------------------------------------------------------------------------
122 160
123export { 161export {
124 isHTTPSignatureDigestValid, 162 isHTTPSignatureDigestValid,
@@ -129,7 +167,10 @@ export {
129 comparePassword, 167 comparePassword,
130 createPrivateAndPublicKeys, 168 createPrivateAndPublicKeys,
131 cryptPassword, 169 cryptPassword,
132 signJsonLDObject 170 signJsonLDObject,
171
172 encrypt,
173 decrypt
133} 174}
134 175
135// --------------------------------------------------------------------------- 176// ---------------------------------------------------------------------------
diff --git a/server/helpers/upload.ts b/server/helpers/upload.ts
index 3cb17edd0..f5f476913 100644
--- a/server/helpers/upload.ts
+++ b/server/helpers/upload.ts
@@ -1,10 +1,10 @@
1import { join } from 'path' 1import { join } from 'path'
2import { RESUMABLE_UPLOAD_DIRECTORY } from '../initializers/constants' 2import { DIRECTORIES } from '@server/initializers/constants'
3 3
4function getResumableUploadPath (filename?: string) { 4function getResumableUploadPath (filename?: string) {
5 if (filename) return join(RESUMABLE_UPLOAD_DIRECTORY, filename) 5 if (filename) return join(DIRECTORIES.RESUMABLE_UPLOAD, filename)
6 6
7 return RESUMABLE_UPLOAD_DIRECTORY 7 return DIRECTORIES.RESUMABLE_UPLOAD
8} 8}
9 9
10// --------------------------------------------------------------------------- 10// ---------------------------------------------------------------------------
diff --git a/server/helpers/video.ts b/server/helpers/video.ts
index f5f645d3e..c688ef1e3 100644
--- a/server/helpers/video.ts
+++ b/server/helpers/video.ts
@@ -2,6 +2,7 @@ import { Response } from 'express'
2import { CONFIG } from '@server/initializers/config' 2import { CONFIG } from '@server/initializers/config'
3import { isStreamingPlaylist, MStreamingPlaylistVideo, MVideo } from '@server/types/models' 3import { isStreamingPlaylist, MStreamingPlaylistVideo, MVideo } from '@server/types/models'
4import { VideoPrivacy, VideoState } from '@shared/models' 4import { VideoPrivacy, VideoState } from '@shared/models'
5import { forceNumber } from '@shared/core-utils'
5 6
6function getVideoWithAttributes (res: Response) { 7function getVideoWithAttributes (res: Response) {
7 return res.locals.videoAPI || res.locals.videoAll || res.locals.onlyVideo 8 return res.locals.videoAPI || res.locals.videoAll || res.locals.onlyVideo
@@ -14,14 +15,14 @@ function extractVideo (videoOrPlaylist: MVideo | MStreamingPlaylistVideo) {
14} 15}
15 16
16function isPrivacyForFederation (privacy: VideoPrivacy) { 17function isPrivacyForFederation (privacy: VideoPrivacy) {
17 const castedPrivacy = parseInt(privacy + '', 10) 18 const castedPrivacy = forceNumber(privacy)
18 19
19 return castedPrivacy === VideoPrivacy.PUBLIC || 20 return castedPrivacy === VideoPrivacy.PUBLIC ||
20 (CONFIG.FEDERATION.VIDEOS.FEDERATE_UNLISTED === true && castedPrivacy === VideoPrivacy.UNLISTED) 21 (CONFIG.FEDERATION.VIDEOS.FEDERATE_UNLISTED === true && castedPrivacy === VideoPrivacy.UNLISTED)
21} 22}
22 23
23function isStateForFederation (state: VideoState) { 24function isStateForFederation (state: VideoState) {
24 const castedState = parseInt(state + '', 10) 25 const castedState = forceNumber(state)
25 26
26 return castedState === VideoState.PUBLISHED || castedState === VideoState.WAITING_FOR_LIVE || castedState === VideoState.LIVE_ENDED 27 return castedState === VideoState.PUBLISHED || castedState === VideoState.WAITING_FOR_LIVE || castedState === VideoState.LIVE_ENDED
27} 28}
diff --git a/server/helpers/webtorrent.ts b/server/helpers/webtorrent.ts
index 88bdb16b6..a3c93e6fe 100644
--- a/server/helpers/webtorrent.ts
+++ b/server/helpers/webtorrent.ts
@@ -1,6 +1,6 @@
1import { decode, encode } from 'bencode' 1import { decode, encode } from 'bencode'
2import createTorrent from 'create-torrent' 2import createTorrent from 'create-torrent'
3import { createWriteStream, ensureDir, readFile, remove, writeFile } from 'fs-extra' 3import { createWriteStream, ensureDir, pathExists, readFile, remove, writeFile } from 'fs-extra'
4import magnetUtil from 'magnet-uri' 4import magnetUtil from 'magnet-uri'
5import parseTorrent from 'parse-torrent' 5import parseTorrent from 'parse-torrent'
6import { dirname, join } from 'path' 6import { dirname, join } from 'path'
@@ -134,6 +134,11 @@ async function updateTorrentMetadata (videoOrPlaylist: MVideo | MStreamingPlayli
134 134
135 const oldTorrentPath = join(CONFIG.STORAGE.TORRENTS_DIR, videoFile.torrentFilename) 135 const oldTorrentPath = join(CONFIG.STORAGE.TORRENTS_DIR, videoFile.torrentFilename)
136 136
137 if (!await pathExists(oldTorrentPath)) {
138 logger.info('Do not update torrent metadata %s of video %s because the file does not exist anymore.', video.uuid, oldTorrentPath)
139 return
140 }
141
137 const torrentContent = await readFile(oldTorrentPath) 142 const torrentContent = await readFile(oldTorrentPath)
138 const decoded = decode(torrentContent) 143 const decoded = decode(torrentContent)
139 144
@@ -151,7 +156,7 @@ async function updateTorrentMetadata (videoOrPlaylist: MVideo | MStreamingPlayli
151 logger.info('Updating torrent metadata %s -> %s.', oldTorrentPath, newTorrentPath) 156 logger.info('Updating torrent metadata %s -> %s.', oldTorrentPath, newTorrentPath)
152 157
153 await writeFile(newTorrentPath, encode(decoded)) 158 await writeFile(newTorrentPath, encode(decoded))
154 await remove(join(CONFIG.STORAGE.TORRENTS_DIR, videoFile.torrentFilename)) 159 await remove(oldTorrentPath)
155 160
156 videoFile.torrentFilename = newTorrentFilename 161 videoFile.torrentFilename = newTorrentFilename
157 videoFile.infoHash = sha1(encode(decoded.info)) 162 videoFile.infoHash = sha1(encode(decoded.info))
@@ -164,7 +169,10 @@ function generateMagnetUri (
164) { 169) {
165 const xs = videoFile.getTorrentUrl() 170 const xs = videoFile.getTorrentUrl()
166 const announce = trackerUrls 171 const announce = trackerUrls
167 let urlList = [ videoFile.getFileUrl(video) ] 172
173 let urlList = video.hasPrivateStaticPath()
174 ? []
175 : [ videoFile.getFileUrl(video) ]
168 176
169 const redundancies = videoFile.RedundancyVideos 177 const redundancies = videoFile.RedundancyVideos
170 if (isArray(redundancies)) urlList = urlList.concat(redundancies.map(r => r.fileUrl)) 178 if (isArray(redundancies)) urlList = urlList.concat(redundancies.map(r => r.fileUrl))
@@ -240,6 +248,8 @@ function buildAnnounceList () {
240} 248}
241 249
242function buildUrlList (video: MVideo, videoFile: MVideoFile) { 250function buildUrlList (video: MVideo, videoFile: MVideoFile) {
251 if (video.hasPrivateStaticPath()) return []
252
243 return [ videoFile.getFileUrl(video) ] 253 return [ videoFile.getFileUrl(video) ]
244} 254}
245 255
diff --git a/server/helpers/youtube-dl/youtube-dl-cli.ts b/server/helpers/youtube-dl/youtube-dl-cli.ts
index fc4c40787..a2f630953 100644
--- a/server/helpers/youtube-dl/youtube-dl-cli.ts
+++ b/server/helpers/youtube-dl/youtube-dl-cli.ts
@@ -128,14 +128,14 @@ export class YoutubeDLCLI {
128 const data = await this.run({ url, args: completeArgs, processOptions }) 128 const data = await this.run({ url, args: completeArgs, processOptions })
129 if (!data) return undefined 129 if (!data) return undefined
130 130
131 const info = data.map(this.parseInfo) 131 const info = data.map(d => JSON.parse(d))
132 132
133 return info.length === 1 133 return info.length === 1
134 ? info[0] 134 ? info[0]
135 : info 135 : info
136 } 136 }
137 137
138 getListInfo (options: { 138 async getListInfo (options: {
139 url: string 139 url: string
140 latestVideosCount?: number 140 latestVideosCount?: number
141 processOptions: execa.NodeOptions 141 processOptions: execa.NodeOptions
@@ -151,12 +151,17 @@ export class YoutubeDLCLI {
151 additionalYoutubeDLArgs.push('--playlist-end', options.latestVideosCount.toString()) 151 additionalYoutubeDLArgs.push('--playlist-end', options.latestVideosCount.toString())
152 } 152 }
153 153
154 return this.getInfo({ 154 const result = await this.getInfo({
155 url: options.url, 155 url: options.url,
156 format: YoutubeDLCLI.getYoutubeDLVideoFormat([], false), 156 format: YoutubeDLCLI.getYoutubeDLVideoFormat([], false),
157 processOptions: options.processOptions, 157 processOptions: options.processOptions,
158 additionalYoutubeDLArgs 158 additionalYoutubeDLArgs
159 }) 159 })
160
161 if (!result) return result
162 if (!Array.isArray(result)) return [ result ]
163
164 return result
160 } 165 }
161 166
162 async getSubs (options: { 167 async getSubs (options: {
@@ -241,8 +246,4 @@ export class YoutubeDLCLI {
241 246
242 return args 247 return args
243 } 248 }
244
245 private parseInfo (data: string) {
246 return JSON.parse(data)
247 }
248} 249}
diff --git a/server/helpers/youtube-dl/youtube-dl-wrapper.ts b/server/helpers/youtube-dl/youtube-dl-wrapper.ts
index 966b8df78..ac3cd190e 100644
--- a/server/helpers/youtube-dl/youtube-dl-wrapper.ts
+++ b/server/helpers/youtube-dl/youtube-dl-wrapper.ts
@@ -77,7 +77,7 @@ class YoutubeDLWrapper {
77 77
78 const subtitles = files.reduce((acc, filename) => { 78 const subtitles = files.reduce((acc, filename) => {
79 const matched = filename.match(/\.([a-z]{2})(-[a-z]+)?\.(vtt|ttml)/i) 79 const matched = filename.match(/\.([a-z]{2})(-[a-z]+)?\.(vtt|ttml)/i)
80 if (!matched || !matched[1]) return acc 80 if (!matched?.[1]) return acc
81 81
82 return [ 82 return [
83 ...acc, 83 ...acc,