]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/helpers/core-utils.ts
Don't expose constants directly in initializers/
[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 * as bcrypt from 'bcrypt'
7 import * as createTorrent from 'create-torrent'
8 import { createHash, HexBase64Latin1Encoding, pseudoRandomBytes } from 'crypto'
9 import { isAbsolute, join } from 'path'
10 import * as pem from 'pem'
11 import { URL } from 'url'
12 import { truncate } from 'lodash'
13 import { exec } 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 parseDuration (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 root () {
138 // We are in /helpers/utils.js
139 const paths = [ __dirname, '..', '..' ]
140
141 // We are under /dist directory
142 if (process.mainModule && process.mainModule.filename.endsWith('.ts') === false) {
143 paths.push('..')
144 }
145
146 return join.apply(null, paths)
147 }
148
149 // Thanks: https://stackoverflow.com/a/12034334
150 function escapeHTML (stringParam) {
151 if (!stringParam) return ''
152
153 const entityMap = {
154 '&': '&',
155 '<': '&lt;',
156 '>': '&gt;',
157 '"': '&quot;',
158 '\'': '&#39;',
159 '/': '&#x2F;',
160 '`': '&#x60;',
161 '=': '&#x3D;'
162 }
163
164 return String(stringParam).replace(/[&<>"'`=\/]/g, s => entityMap[s])
165 }
166
167 function pageToStartAndCount (page: number, itemsPerPage: number) {
168 const start = (page - 1) * itemsPerPage
169
170 return { start, count: itemsPerPage }
171 }
172
173 function buildPath (path: string) {
174 if (isAbsolute(path)) return path
175
176 return join(root(), path)
177 }
178
179 // Consistent with .length, lodash truncate function is not
180 function peertubeTruncate (str: string, maxLength: number) {
181 const options = {
182 length: maxLength
183 }
184 const truncatedStr = truncate(str, options)
185
186 // The truncated string is okay, we can return it
187 if (truncatedStr.length <= maxLength) return truncatedStr
188
189 // Lodash takes into account all UTF characters, whereas String.prototype.length does not: some characters have a length of 2
190 // We always use the .length so we need to truncate more if needed
191 options.length -= truncatedStr.length - maxLength
192 return truncate(str, options)
193 }
194
195 function sha256 (str: string | Buffer, encoding: HexBase64Latin1Encoding = 'hex') {
196 return createHash('sha256').update(str).digest(encoding)
197 }
198
199 function sha1 (str: string | Buffer, encoding: HexBase64Latin1Encoding = 'hex') {
200 return createHash('sha1').update(str).digest(encoding)
201 }
202
203 function promisify0<A> (func: (cb: (err: any, result: A) => void) => void): () => Promise<A> {
204 return function promisified (): Promise<A> {
205 return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
206 func.apply(null, [ (err: any, res: A) => err ? reject(err) : resolve(res) ])
207 })
208 }
209 }
210
211 // Thanks to https://gist.github.com/kumasento/617daa7e46f13ecdd9b2
212 function promisify1<T, A> (func: (arg: T, cb: (err: any, result: A) => void) => void): (arg: T) => Promise<A> {
213 return function promisified (arg: T): Promise<A> {
214 return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
215 func.apply(null, [ arg, (err: any, res: A) => err ? reject(err) : resolve(res) ])
216 })
217 }
218 }
219
220 function promisify1WithVoid<T> (func: (arg: T, cb: (err: any) => void) => void): (arg: T) => Promise<void> {
221 return function promisified (arg: T): Promise<void> {
222 return new Promise<void>((resolve: () => void, reject: (err: any) => void) => {
223 func.apply(null, [ arg, (err: any) => err ? reject(err) : resolve() ])
224 })
225 }
226 }
227
228 function promisify2<T, U, A> (func: (arg1: T, arg2: U, cb: (err: any, result: A) => void) => void): (arg1: T, arg2: U) => Promise<A> {
229 return function promisified (arg1: T, arg2: U): Promise<A> {
230 return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
231 func.apply(null, [ arg1, arg2, (err: any, res: A) => err ? reject(err) : resolve(res) ])
232 })
233 }
234 }
235
236 function promisify2WithVoid<T, U> (func: (arg1: T, arg2: U, cb: (err: any) => void) => void): (arg1: T, arg2: U) => Promise<void> {
237 return function promisified (arg1: T, arg2: U): Promise<void> {
238 return new Promise<void>((resolve: () => void, reject: (err: any) => void) => {
239 func.apply(null, [ arg1, arg2, (err: any) => err ? reject(err) : resolve() ])
240 })
241 }
242 }
243
244 const pseudoRandomBytesPromise = promisify1<number, Buffer>(pseudoRandomBytes)
245 const createPrivateKey = promisify1<number, { key: string }>(pem.createPrivateKey)
246 const getPublicKey = promisify1<string, { publicKey: string }>(pem.getPublicKey)
247 const bcryptComparePromise = promisify2<any, string, boolean>(bcrypt.compare)
248 const bcryptGenSaltPromise = promisify1<number, string>(bcrypt.genSalt)
249 const bcryptHashPromise = promisify2<any, string | number, string>(bcrypt.hash)
250 const createTorrentPromise = promisify2<string, any, any>(createTorrent)
251 const execPromise2 = promisify2<string, any, string>(exec)
252 const execPromise = promisify1<string, string>(exec)
253
254 // ---------------------------------------------------------------------------
255
256 export {
257 isTestInstance,
258 isProdInstance,
259
260 objectConverter,
261 root,
262 escapeHTML,
263 pageToStartAndCount,
264 sanitizeUrl,
265 sanitizeHost,
266 buildPath,
267 peertubeTruncate,
268
269 sha256,
270 sha1,
271
272 promisify0,
273 promisify1,
274
275 pseudoRandomBytesPromise,
276 createPrivateKey,
277 getPublicKey,
278 bcryptComparePromise,
279 bcryptGenSaltPromise,
280 bcryptHashPromise,
281 createTorrentPromise,
282 execPromise2,
283 execPromise
284 }