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