]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/helpers/core-utils.ts
Bumped to version v5.2.1
[github/Chocobozzz/PeerTube.git] / server / helpers / core-utils.ts
CommitLineData
a1587156
C
1/* eslint-disable no-useless-call */
2
1840c2f7 3/*
272abc0b 4 Different from 'utils' because we don't import other PeerTube modules.
1840c2f7
C
5 Useful to avoid circular dependencies.
6*/
7
ba5a8d89 8import { exec, ExecOptions } from 'child_process'
a3e5f804 9import { ED25519KeyPairOptions, generateKeyPair, randomBytes, RSAKeyPairOptions, scrypt } from 'crypto'
ba5a8d89 10import { truncate } from 'lodash'
db4b15f2 11import { pipeline } from 'stream'
225a89c2 12import { URL } from 'url'
db4b15f2 13import { promisify } from 'util'
0c9668f7 14import { promisify1, promisify2, promisify3 } from '@shared/core-utils'
a4101923
C
15
16const objectConverter = (oldObject: any, keyConverter: (e: string) => string, valueConverter: (e: any) => any) => {
17 if (!oldObject || typeof oldObject !== 'object') {
18 return valueConverter(oldObject)
19 }
20
74dc3bca 21 if (Array.isArray(oldObject)) {
a4101923
C
22 return oldObject.map(e => objectConverter(e, keyConverter, valueConverter))
23 }
24
25 const newObject = {}
26 Object.keys(oldObject).forEach(oldKey => {
27 const newKey = keyConverter(oldKey)
a1587156 28 newObject[newKey] = objectConverter(oldObject[oldKey], keyConverter, valueConverter)
a4101923
C
29 })
30
31 return newObject
32}
225a89c2 33
ea54cd04
C
34function mapToJSON (map: Map<any, any>) {
35 const obj: any = {}
36
37 for (const [ k, v ] of map) {
38 obj[k] = v
39 }
40
41 return obj
42}
43
44// ---------------------------------------------------------------------------
45
06215f15 46const timeTable = {
a1587156
C
47 ms: 1,
48 second: 1000,
49 minute: 60000,
50 hour: 3600000,
51 day: 3600000 * 24,
52 week: 3600000 * 24 * 7,
53 month: 3600000 * 24 * 30
06215f15 54}
0e5ff97f 55
8f0bc73d 56export function parseDurationToMs (duration: number | string): number {
fb719404 57 if (duration === null) return null
06215f15 58 if (typeof duration === 'number') return duration
c371016d 59 if (!isNaN(+duration)) return +duration
06215f15
C
60
61 if (typeof duration === 'string') {
a1587156 62 const split = duration.match(/^([\d.,]+)\s?(\w+)$/)
06215f15
C
63
64 if (split.length === 3) {
65 const len = parseFloat(split[1])
a1587156 66 let unit = split[2].replace(/s$/i, '').toLowerCase()
06215f15
C
67 if (unit === 'm') {
68 unit = 'ms'
69 }
70
71 return (len || 1) * (timeTable[unit] || 0)
72 }
73 }
74
ae9bbed4 75 throw new Error(`Duration ${duration} could not be properly parsed`)
06215f15
C
76}
77
0e5ff97f
BY
78export function parseBytes (value: string | number): number {
79 if (typeof value === 'number') return value
c371016d 80 if (!isNaN(+value)) return +value
0e5ff97f
BY
81
82 const tgm = /^(\d+)\s*TB\s*(\d+)\s*GB\s*(\d+)\s*MB$/
83 const tg = /^(\d+)\s*TB\s*(\d+)\s*GB$/
84 const tm = /^(\d+)\s*TB\s*(\d+)\s*MB$/
85 const gm = /^(\d+)\s*GB\s*(\d+)\s*MB$/
86 const t = /^(\d+)\s*TB$/
87 const g = /^(\d+)\s*GB$/
88 const m = /^(\d+)\s*MB$/
89 const b = /^(\d+)\s*B$/
c371016d
C
90
91 let match: RegExpMatchArray
0e5ff97f
BY
92
93 if (value.match(tgm)) {
94 match = value.match(tgm)
a1587156
C
95 return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 +
96 parseInt(match[2], 10) * 1024 * 1024 * 1024 +
97 parseInt(match[3], 10) * 1024 * 1024
c371016d
C
98 }
99
100 if (value.match(tg)) {
0e5ff97f 101 match = value.match(tg)
a1587156
C
102 return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 +
103 parseInt(match[2], 10) * 1024 * 1024 * 1024
c371016d
C
104 }
105
106 if (value.match(tm)) {
0e5ff97f 107 match = value.match(tm)
a1587156
C
108 return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 +
109 parseInt(match[2], 10) * 1024 * 1024
c371016d
C
110 }
111
112 if (value.match(gm)) {
0e5ff97f 113 match = value.match(gm)
a1587156
C
114 return parseInt(match[1], 10) * 1024 * 1024 * 1024 +
115 parseInt(match[2], 10) * 1024 * 1024
c371016d
C
116 }
117
118 if (value.match(t)) {
0e5ff97f
BY
119 match = value.match(t)
120 return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024
c371016d
C
121 }
122
123 if (value.match(g)) {
0e5ff97f
BY
124 match = value.match(g)
125 return parseInt(match[1], 10) * 1024 * 1024 * 1024
c371016d
C
126 }
127
128 if (value.match(m)) {
0e5ff97f
BY
129 match = value.match(m)
130 return parseInt(match[1], 10) * 1024 * 1024
c371016d
C
131 }
132
133 if (value.match(b)) {
0e5ff97f
BY
134 match = value.match(b)
135 return parseInt(match[1], 10) * 1024
0e5ff97f 136 }
c371016d
C
137
138 return parseInt(value, 10)
0e5ff97f
BY
139}
140
ea54cd04
C
141// ---------------------------------------------------------------------------
142
225a89c2
C
143function sanitizeUrl (url: string) {
144 const urlObject = new URL(url)
145
146 if (urlObject.protocol === 'https:' && urlObject.port === '443') {
147 urlObject.port = ''
148 } else if (urlObject.protocol === 'http:' && urlObject.port === '80') {
149 urlObject.port = ''
150 }
151
152 return urlObject.href.replace(/\/$/, '')
153}
154
155// Don't import remote scheme from constants because we are in core utils
156function sanitizeHost (host: string, remoteScheme: string) {
604abfbe 157 const toRemove = remoteScheme === 'https' ? 443 : 80
225a89c2
C
158
159 return host.replace(new RegExp(`:${toRemove}$`), '')
160}
1840c2f7 161
ea54cd04
C
162// ---------------------------------------------------------------------------
163
1840c2f7
C
164function isTestInstance () {
165 return process.env.NODE_ENV === 'test'
166}
167
9452d4fd
C
168function isDevInstance () {
169 return process.env.NODE_ENV === 'dev'
170}
171
172function isTestOrDevInstance () {
173 return isTestInstance() || isDevInstance()
174}
175
00f9e41e
C
176function isProdInstance () {
177 return process.env.NODE_ENV === 'production'
178}
179
1a12f66d 180function getAppNumber () {
4abc7b05 181 return process.env.NODE_APP_INSTANCE || ''
1a12f66d
C
182}
183
ea54cd04
C
184// ---------------------------------------------------------------------------
185
c73e83da 186// Consistent with .length, lodash truncate function is not
687c6180 187function peertubeTruncate (str: string, options: { length: number, separator?: RegExp, omission?: string }) {
c73e83da
C
188 const truncatedStr = truncate(str, options)
189
190 // The truncated string is okay, we can return it
687c6180 191 if (truncatedStr.length <= options.length) return truncatedStr
c73e83da
C
192
193 // Lodash takes into account all UTF characters, whereas String.prototype.length does not: some characters have a length of 2
194 // We always use the .length so we need to truncate more if needed
687c6180 195 options.length -= truncatedStr.length - options.length
c73e83da
C
196 return truncate(str, options)
197}
198
ea54cd04
C
199function pageToStartAndCount (page: number, itemsPerPage: number) {
200 const start = (page - 1) * itemsPerPage
201
202 return { start, count: itemsPerPage }
203}
204
205// ---------------------------------------------------------------------------
206
207type SemVersion = { major: number, minor: number, patch: number }
208function parseSemVersion (s: string) {
209 const parsed = s.match(/^v?(\d+)\.(\d+)\.(\d+)$/i)
210
211 return {
212 major: parseInt(parsed[1]),
213 minor: parseInt(parsed[2]),
214 patch: parseInt(parsed[3])
215 } as SemVersion
216}
217
218// ---------------------------------------------------------------------------
219
f023a19c
C
220function execShell (command: string, options?: ExecOptions) {
221 return new Promise<{ err?: Error, stdout: string, stderr: string }>((res, rej) => {
222 exec(command, options, (err, stdout, stderr) => {
a1587156 223 // eslint-disable-next-line prefer-promise-reject-errors
f023a19c
C
224 if (err) return rej({ err, stdout, stderr })
225
226 return res({ stdout, stderr })
227 })
228 })
229}
230
ea54cd04
C
231// ---------------------------------------------------------------------------
232
5d7cb63e
C
233function generateRSAKeyPairPromise (size: number) {
234 return new Promise<{ publicKey: string, privateKey: string }>((res, rej) => {
235 const options: RSAKeyPairOptions<'pem', 'pem'> = {
236 modulusLength: size,
237 publicKeyEncoding: {
238 type: 'spki',
239 format: 'pem'
240 },
241 privateKeyEncoding: {
242 type: 'pkcs1',
243 format: 'pem'
244 }
245 }
246
247 generateKeyPair('rsa', options, (err, publicKey, privateKey) => {
248 if (err) return rej(err)
249
250 return res({ publicKey, privateKey })
251 })
252 })
253}
254
255function generateED25519KeyPairPromise () {
256 return new Promise<{ publicKey: string, privateKey: string }>((res, rej) => {
257 const options: ED25519KeyPairOptions<'pem', 'pem'> = {
258 publicKeyEncoding: {
259 type: 'spki',
260 format: 'pem'
261 },
262 privateKeyEncoding: {
263 type: 'pkcs8',
264 format: 'pem'
265 }
266 }
267
268 generateKeyPair('ed25519', options, (err, publicKey, privateKey) => {
269 if (err) return rej(err)
270
271 return res({ publicKey, privateKey })
272 })
273 })
274}
275
276// ---------------------------------------------------------------------------
277
a1587156 278const randomBytesPromise = promisify1<number, Buffer>(randomBytes)
a3e5f804 279const scryptPromise = promisify3<string, string, number, Buffer>(scrypt)
499d9015
C
280const execPromise2 = promisify2<string, any, string>(exec)
281const execPromise = promisify1<string, string>(exec)
db4b15f2 282const pipelinePromise = promisify(pipeline)
6fcd19ba 283
1840c2f7
C
284// ---------------------------------------------------------------------------
285
286export {
287 isTestInstance,
9452d4fd 288 isTestOrDevInstance,
00f9e41e 289 isProdInstance,
1a12f66d 290 getAppNumber,
00f9e41e 291
a4101923 292 objectConverter,
ea54cd04
C
293 mapToJSON,
294
225a89c2
C
295 sanitizeUrl,
296 sanitizeHost,
ea54cd04 297
f023a19c 298 execShell,
ea54cd04
C
299
300 pageToStartAndCount,
c73e83da 301 peertubeTruncate,
09209296 302
a3e5f804
C
303 scryptPromise,
304
a1587156 305 randomBytesPromise,
5d7cb63e
C
306
307 generateRSAKeyPairPromise,
308 generateED25519KeyPairPromise,
309
499d9015 310 execPromise2,
db4b15f2 311 execPromise,
ae71acca
C
312 pipelinePromise,
313
0c9668f7 314 parseSemVersion
1840c2f7 315}