]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/helpers/core-utils.ts
Update server dependencies
[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, options: { length: number, separator?: RegExp, omission?: string }) {
183 const truncatedStr = truncate(str, options)
184
185 // The truncated string is okay, we can return it
186 if (truncatedStr.length <= options.length) return truncatedStr
187
188 // Lodash takes into account all UTF characters, whereas String.prototype.length does not: some characters have a length of 2
189 // We always use the .length so we need to truncate more if needed
190 options.length -= truncatedStr.length - options.length
191 return truncate(str, options)
192 }
193
194 function sha256 (str: string | Buffer, encoding: HexBase64Latin1Encoding = 'hex') {
195 return createHash('sha256').update(str).digest(encoding)
196 }
197
198 function sha1 (str: string | Buffer, encoding: HexBase64Latin1Encoding = 'hex') {
199 return createHash('sha1').update(str).digest(encoding)
200 }
201
202 function execShell (command: string, options?: ExecOptions) {
203 return new Promise<{ err?: Error, stdout: string, stderr: string }>((res, rej) => {
204 exec(command, options, (err, stdout, stderr) => {
205 if (err) return rej({ err, stdout, stderr })
206
207 return res({ stdout, stderr })
208 })
209 })
210 }
211
212 function promisify0<A> (func: (cb: (err: any, result: A) => void) => void): () => Promise<A> {
213 return function promisified (): Promise<A> {
214 return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
215 func.apply(null, [ (err: any, res: A) => err ? reject(err) : resolve(res) ])
216 })
217 }
218 }
219
220 // Thanks to https://gist.github.com/kumasento/617daa7e46f13ecdd9b2
221 function promisify1<T, A> (func: (arg: T, cb: (err: any, result: A) => void) => void): (arg: T) => Promise<A> {
222 return function promisified (arg: T): Promise<A> {
223 return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
224 func.apply(null, [ arg, (err: any, res: A) => err ? reject(err) : resolve(res) ])
225 })
226 }
227 }
228
229 function promisify1WithVoid<T> (func: (arg: T, cb: (err: any) => void) => void): (arg: T) => Promise<void> {
230 return function promisified (arg: T): Promise<void> {
231 return new Promise<void>((resolve: () => void, reject: (err: any) => void) => {
232 func.apply(null, [ arg, (err: any) => err ? reject(err) : resolve() ])
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 function promisify2WithVoid<T, U> (func: (arg1: T, arg2: U, cb: (err: any) => void) => void): (arg1: T, arg2: U) => Promise<void> {
246 return function promisified (arg1: T, arg2: U): Promise<void> {
247 return new Promise<void>((resolve: () => void, reject: (err: any) => void) => {
248 func.apply(null, [ arg1, arg2, (err: any) => err ? reject(err) : resolve() ])
249 })
250 }
251 }
252
253 const pseudoRandomBytesPromise = promisify1<number, Buffer>(pseudoRandomBytes)
254 const createPrivateKey = promisify1<number, { key: string }>(pem.createPrivateKey)
255 const getPublicKey = promisify1<string, { publicKey: string }>(pem.getPublicKey)
256 const execPromise2 = promisify2<string, any, string>(exec)
257 const execPromise = promisify1<string, string>(exec)
258
259 // ---------------------------------------------------------------------------
260
261 export {
262 isTestInstance,
263 isProdInstance,
264 getAppNumber,
265
266 objectConverter,
267 root,
268 escapeHTML,
269 pageToStartAndCount,
270 sanitizeUrl,
271 sanitizeHost,
272 buildPath,
273 execShell,
274 peertubeTruncate,
275
276 sha256,
277 sha1,
278
279 promisify0,
280 promisify1,
281 promisify2,
282
283 pseudoRandomBytesPromise,
284 createPrivateKey,
285 getPublicKey,
286 execPromise2,
287 execPromise
288 }