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