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