]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/helpers/core-utils.ts
0ec45eb2e276833d115f5bfabfc35f6712511835
[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 { randomBytes } from 'crypto'
10 import { truncate } from 'lodash'
11 import { createPrivateKey as createPrivateKey_1, getPublicKey as getPublicKey_1 } from 'pem'
12 import { pipeline } from 'stream'
13 import { URL } from 'url'
14 import { promisify } from 'util'
15
16 const objectConverter = (oldObject: any, keyConverter: (e: string) => string, valueConverter: (e: any) => any) => {
17 if (!oldObject || typeof oldObject !== 'object') {
18 return valueConverter(oldObject)
19 }
20
21 if (Array.isArray(oldObject)) {
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)
28 newObject[newKey] = objectConverter(oldObject[oldKey], keyConverter, valueConverter)
29 })
30
31 return newObject
32 }
33
34 function 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
46 const timeTable = {
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
54 }
55
56 export function parseDurationToMs (duration: number | string): number {
57 if (duration === null) return null
58 if (typeof duration === 'number') return duration
59
60 if (typeof duration === 'string') {
61 const split = duration.match(/^([\d.,]+)\s?(\w+)$/)
62
63 if (split.length === 3) {
64 const len = parseFloat(split[1])
65 let unit = split[2].replace(/s$/i, '').toLowerCase()
66 if (unit === 'm') {
67 unit = 'ms'
68 }
69
70 return (len || 1) * (timeTable[unit] || 0)
71 }
72 }
73
74 throw new Error(`Duration ${duration} could not be properly parsed`)
75 }
76
77 export function parseBytes (value: string | number): number {
78 if (typeof value === 'number') return value
79
80 const tgm = /^(\d+)\s*TB\s*(\d+)\s*GB\s*(\d+)\s*MB$/
81 const tg = /^(\d+)\s*TB\s*(\d+)\s*GB$/
82 const tm = /^(\d+)\s*TB\s*(\d+)\s*MB$/
83 const gm = /^(\d+)\s*GB\s*(\d+)\s*MB$/
84 const t = /^(\d+)\s*TB$/
85 const g = /^(\d+)\s*GB$/
86 const m = /^(\d+)\s*MB$/
87 const b = /^(\d+)\s*B$/
88 let match
89
90 if (value.match(tgm)) {
91 match = value.match(tgm)
92 return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 +
93 parseInt(match[2], 10) * 1024 * 1024 * 1024 +
94 parseInt(match[3], 10) * 1024 * 1024
95 } else if (value.match(tg)) {
96 match = value.match(tg)
97 return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 +
98 parseInt(match[2], 10) * 1024 * 1024 * 1024
99 } else if (value.match(tm)) {
100 match = value.match(tm)
101 return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 +
102 parseInt(match[2], 10) * 1024 * 1024
103 } else if (value.match(gm)) {
104 match = value.match(gm)
105 return parseInt(match[1], 10) * 1024 * 1024 * 1024 +
106 parseInt(match[2], 10) * 1024 * 1024
107 } else if (value.match(t)) {
108 match = value.match(t)
109 return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024
110 } else if (value.match(g)) {
111 match = value.match(g)
112 return parseInt(match[1], 10) * 1024 * 1024 * 1024
113 } else if (value.match(m)) {
114 match = value.match(m)
115 return parseInt(match[1], 10) * 1024 * 1024
116 } else if (value.match(b)) {
117 match = value.match(b)
118 return parseInt(match[1], 10) * 1024
119 } else {
120 return parseInt(value, 10)
121 }
122 }
123
124 // ---------------------------------------------------------------------------
125
126 function sanitizeUrl (url: string) {
127 const urlObject = new URL(url)
128
129 if (urlObject.protocol === 'https:' && urlObject.port === '443') {
130 urlObject.port = ''
131 } else if (urlObject.protocol === 'http:' && urlObject.port === '80') {
132 urlObject.port = ''
133 }
134
135 return urlObject.href.replace(/\/$/, '')
136 }
137
138 // Don't import remote scheme from constants because we are in core utils
139 function sanitizeHost (host: string, remoteScheme: string) {
140 const toRemove = remoteScheme === 'https' ? 443 : 80
141
142 return host.replace(new RegExp(`:${toRemove}$`), '')
143 }
144
145 // ---------------------------------------------------------------------------
146
147 function isTestInstance () {
148 return process.env.NODE_ENV === 'test'
149 }
150
151 function isProdInstance () {
152 return process.env.NODE_ENV === 'production'
153 }
154
155 function getAppNumber () {
156 return process.env.NODE_APP_INSTANCE || ''
157 }
158
159 // ---------------------------------------------------------------------------
160
161 // Consistent with .length, lodash truncate function is not
162 function peertubeTruncate (str: string, options: { length: number, separator?: RegExp, omission?: string }) {
163 const truncatedStr = truncate(str, options)
164
165 // The truncated string is okay, we can return it
166 if (truncatedStr.length <= options.length) return truncatedStr
167
168 // Lodash takes into account all UTF characters, whereas String.prototype.length does not: some characters have a length of 2
169 // We always use the .length so we need to truncate more if needed
170 options.length -= truncatedStr.length - options.length
171 return truncate(str, options)
172 }
173
174 function pageToStartAndCount (page: number, itemsPerPage: number) {
175 const start = (page - 1) * itemsPerPage
176
177 return { start, count: itemsPerPage }
178 }
179
180 // ---------------------------------------------------------------------------
181
182 type SemVersion = { major: number, minor: number, patch: number }
183 function parseSemVersion (s: string) {
184 const parsed = s.match(/^v?(\d+)\.(\d+)\.(\d+)$/i)
185
186 return {
187 major: parseInt(parsed[1]),
188 minor: parseInt(parsed[2]),
189 patch: parseInt(parsed[3])
190 } as SemVersion
191 }
192
193 // ---------------------------------------------------------------------------
194
195 function execShell (command: string, options?: ExecOptions) {
196 return new Promise<{ err?: Error, stdout: string, stderr: string }>((res, rej) => {
197 exec(command, options, (err, stdout, stderr) => {
198 // eslint-disable-next-line prefer-promise-reject-errors
199 if (err) return rej({ err, stdout, stderr })
200
201 return res({ stdout, stderr })
202 })
203 })
204 }
205
206 // ---------------------------------------------------------------------------
207
208 function isOdd (num: number) {
209 return (num % 2) !== 0
210 }
211
212 function toEven (num: number) {
213 if (isOdd(num)) return num + 1
214
215 return num
216 }
217
218 // ---------------------------------------------------------------------------
219
220 function promisify0<A> (func: (cb: (err: any, result: A) => void) => void): () => Promise<A> {
221 return function promisified (): Promise<A> {
222 return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
223 func.apply(null, [ (err: any, res: A) => err ? reject(err) : resolve(res) ])
224 })
225 }
226 }
227
228 // Thanks to https://gist.github.com/kumasento/617daa7e46f13ecdd9b2
229 function promisify1<T, A> (func: (arg: T, cb: (err: any, result: A) => void) => void): (arg: T) => Promise<A> {
230 return function promisified (arg: T): Promise<A> {
231 return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
232 func.apply(null, [ arg, (err: any, res: A) => err ? reject(err) : resolve(res) ])
233 })
234 }
235 }
236
237 function promisify2<T, U, A> (func: (arg1: T, arg2: U, cb: (err: any, result: A) => void) => void): (arg1: T, arg2: U) => Promise<A> {
238 return function promisified (arg1: T, arg2: U): Promise<A> {
239 return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
240 func.apply(null, [ arg1, arg2, (err: any, res: A) => err ? reject(err) : resolve(res) ])
241 })
242 }
243 }
244
245 const randomBytesPromise = promisify1<number, Buffer>(randomBytes)
246 const createPrivateKey = promisify1<number, { key: string }>(createPrivateKey_1)
247 const getPublicKey = promisify1<string, { publicKey: string }>(getPublicKey_1)
248 const execPromise2 = promisify2<string, any, string>(exec)
249 const execPromise = promisify1<string, string>(exec)
250 const pipelinePromise = promisify(pipeline)
251
252 // ---------------------------------------------------------------------------
253
254 export {
255 isTestInstance,
256 isProdInstance,
257 getAppNumber,
258
259 objectConverter,
260 mapToJSON,
261
262 sanitizeUrl,
263 sanitizeHost,
264
265 execShell,
266
267 pageToStartAndCount,
268 peertubeTruncate,
269
270 promisify0,
271 promisify1,
272 promisify2,
273
274 randomBytesPromise,
275 createPrivateKey,
276 getPublicKey,
277 execPromise2,
278 execPromise,
279 pipelinePromise,
280
281 parseSemVersion,
282
283 isOdd,
284 toEven
285 }