]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame_incremental - server/helpers/core-utils.ts
Move config in its own file
[github/Chocobozzz/PeerTube.git] / server / helpers / core-utils.ts
... / ...
CommitLineData
1/*
2 Different from 'utils' because we don't not import other PeerTube modules.
3 Useful to avoid circular dependencies.
4*/
5
6import * as bcrypt from 'bcrypt'
7import * as createTorrent from 'create-torrent'
8import { createHash, HexBase64Latin1Encoding, pseudoRandomBytes } from 'crypto'
9import { isAbsolute, join } from 'path'
10import * as pem from 'pem'
11import { URL } from 'url'
12import { truncate } from 'lodash'
13import { exec } from 'child_process'
14import { isArray } from './custom-validators/misc'
15
16const objectConverter = (oldObject: any, keyConverter: (e: string) => string, valueConverter: (e: any) => any) => {
17 if (!oldObject || typeof oldObject !== 'object') {
18 return valueConverter(oldObject)
19 }
20
21 if (isArray(oldObject)) {
22 return oldObject.map(e => objectConverter(e, keyConverter, valueConverter))
23 }
24
25 const newObject = {}
26 Object.keys(oldObject).forEach(oldKey => {
27 const newKey = keyConverter(oldKey)
28 newObject[ newKey ] = objectConverter(oldObject[ oldKey ], keyConverter, valueConverter)
29 })
30
31 return newObject
32}
33
34const timeTable = {
35 ms: 1,
36 second: 1000,
37 minute: 60000,
38 hour: 3600000,
39 day: 3600000 * 24,
40 week: 3600000 * 24 * 7,
41 month: 3600000 * 24 * 30
42}
43
44export function parseDuration (duration: number | string): number {
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
64export 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
111function 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
124function sanitizeHost (host: string, remoteScheme: string) {
125 const toRemove = remoteScheme === 'https' ? 443 : 80
126
127 return host.replace(new RegExp(`:${toRemove}$`), '')
128}
129
130function isTestInstance () {
131 return process.env.NODE_ENV === 'test'
132}
133
134function isProdInstance () {
135 return process.env.NODE_ENV === 'production'
136}
137
138function root () {
139 // We are in /helpers/utils.js
140 const paths = [ __dirname, '..', '..' ]
141
142 // We are under /dist directory
143 if (process.mainModule && process.mainModule.filename.endsWith('.ts') === false) {
144 paths.push('..')
145 }
146
147 return join.apply(null, paths)
148}
149
150// Thanks: https://stackoverflow.com/a/12034334
151function escapeHTML (stringParam) {
152 if (!stringParam) return ''
153
154 const entityMap = {
155 '&': '&',
156 '<': '&lt;',
157 '>': '&gt;',
158 '"': '&quot;',
159 '\'': '&#39;',
160 '/': '&#x2F;',
161 '`': '&#x60;',
162 '=': '&#x3D;'
163 }
164
165 return String(stringParam).replace(/[&<>"'`=\/]/g, s => entityMap[s])
166}
167
168function pageToStartAndCount (page: number, itemsPerPage: number) {
169 const start = (page - 1) * itemsPerPage
170
171 return { start, count: itemsPerPage }
172}
173
174function buildPath (path: string) {
175 if (isAbsolute(path)) return path
176
177 return join(root(), path)
178}
179
180// Consistent with .length, lodash truncate function is not
181function peertubeTruncate (str: string, maxLength: number) {
182 const options = {
183 length: maxLength
184 }
185 const truncatedStr = truncate(str, options)
186
187 // The truncated string is okay, we can return it
188 if (truncatedStr.length <= maxLength) return truncatedStr
189
190 // Lodash takes into account all UTF characters, whereas String.prototype.length does not: some characters have a length of 2
191 // We always use the .length so we need to truncate more if needed
192 options.length -= truncatedStr.length - maxLength
193 return truncate(str, options)
194}
195
196function sha256 (str: string | Buffer, encoding: HexBase64Latin1Encoding = 'hex') {
197 return createHash('sha256').update(str).digest(encoding)
198}
199
200function sha1 (str: string | Buffer, encoding: HexBase64Latin1Encoding = 'hex') {
201 return createHash('sha1').update(str).digest(encoding)
202}
203
204function promisify0<A> (func: (cb: (err: any, result: A) => void) => void): () => Promise<A> {
205 return function promisified (): Promise<A> {
206 return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
207 func.apply(null, [ (err: any, res: A) => err ? reject(err) : resolve(res) ])
208 })
209 }
210}
211
212// Thanks to https://gist.github.com/kumasento/617daa7e46f13ecdd9b2
213function promisify1<T, A> (func: (arg: T, cb: (err: any, result: A) => void) => void): (arg: T) => Promise<A> {
214 return function promisified (arg: T): Promise<A> {
215 return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
216 func.apply(null, [ arg, (err: any, res: A) => err ? reject(err) : resolve(res) ])
217 })
218 }
219}
220
221function promisify1WithVoid<T> (func: (arg: T, cb: (err: any) => void) => void): (arg: T) => Promise<void> {
222 return function promisified (arg: T): Promise<void> {
223 return new Promise<void>((resolve: () => void, reject: (err: any) => void) => {
224 func.apply(null, [ arg, (err: any) => err ? reject(err) : resolve() ])
225 })
226 }
227}
228
229function promisify2<T, U, A> (func: (arg1: T, arg2: U, cb: (err: any, result: A) => void) => void): (arg1: T, arg2: U) => Promise<A> {
230 return function promisified (arg1: T, arg2: U): Promise<A> {
231 return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
232 func.apply(null, [ arg1, arg2, (err: any, res: A) => err ? reject(err) : resolve(res) ])
233 })
234 }
235}
236
237function promisify2WithVoid<T, U> (func: (arg1: T, arg2: U, cb: (err: any) => void) => void): (arg1: T, arg2: U) => Promise<void> {
238 return function promisified (arg1: T, arg2: U): Promise<void> {
239 return new Promise<void>((resolve: () => void, reject: (err: any) => void) => {
240 func.apply(null, [ arg1, arg2, (err: any) => err ? reject(err) : resolve() ])
241 })
242 }
243}
244
245const pseudoRandomBytesPromise = promisify1<number, Buffer>(pseudoRandomBytes)
246const createPrivateKey = promisify1<number, { key: string }>(pem.createPrivateKey)
247const getPublicKey = promisify1<string, { publicKey: string }>(pem.getPublicKey)
248const bcryptComparePromise = promisify2<any, string, boolean>(bcrypt.compare)
249const bcryptGenSaltPromise = promisify1<number, string>(bcrypt.genSalt)
250const bcryptHashPromise = promisify2<any, string | number, string>(bcrypt.hash)
251const createTorrentPromise = promisify2<string, any, any>(createTorrent)
252const execPromise2 = promisify2<string, any, string>(exec)
253const execPromise = promisify1<string, string>(exec)
254
255// ---------------------------------------------------------------------------
256
257export {
258 isTestInstance,
259 isProdInstance,
260
261 objectConverter,
262 root,
263 escapeHTML,
264 pageToStartAndCount,
265 sanitizeUrl,
266 sanitizeHost,
267 buildPath,
268 peertubeTruncate,
269
270 sha256,
271 sha1,
272
273 promisify0,
274 promisify1,
275
276 pseudoRandomBytesPromise,
277 createPrivateKey,
278 getPublicKey,
279 bcryptComparePromise,
280 bcryptGenSaltPromise,
281 bcryptHashPromise,
282 createTorrentPromise,
283 execPromise2,
284 execPromise
285}