]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/helpers/core-utils.ts
3f737c1d6ef94c67fd542756d78bf374ce1cef7e
[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 import { isArray } from './custom-validators/misc'
15
16 const 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
34 const 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
44 export 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
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 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
151 function 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
168 function pageToStartAndCount (page: number, itemsPerPage: number) {
169 const start = (page - 1) * itemsPerPage
170
171 return { start, count: itemsPerPage }
172 }
173
174 function 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
181 function 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
196 function sha256 (str: string | Buffer, encoding: HexBase64Latin1Encoding = 'hex') {
197 return createHash('sha256').update(str).digest(encoding)
198 }
199
200 function sha1 (str: string | Buffer, encoding: HexBase64Latin1Encoding = 'hex') {
201 return createHash('sha1').update(str).digest(encoding)
202 }
203
204 function 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
213 function 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
221 function 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
229 function 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
237 function 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
245 const pseudoRandomBytesPromise = promisify1<number, Buffer>(pseudoRandomBytes)
246 const createPrivateKey = promisify1<number, { key: string }>(pem.createPrivateKey)
247 const getPublicKey = promisify1<string, { publicKey: string }>(pem.getPublicKey)
248 const bcryptComparePromise = promisify2<any, string, boolean>(bcrypt.compare)
249 const bcryptGenSaltPromise = promisify1<number, string>(bcrypt.genSalt)
250 const bcryptHashPromise = promisify2<any, string | number, string>(bcrypt.hash)
251 const createTorrentPromise = promisify2<string, any, any>(createTorrent)
252 const execPromise2 = promisify2<string, any, string>(exec)
253 const execPromise = promisify1<string, string>(exec)
254
255 // ---------------------------------------------------------------------------
256
257 export {
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 }