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