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