]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/helpers/core-utils.ts
Merge branch 'master' into develop
[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 timeTable = {
16 ms: 1,
17 second: 1000,
18 minute: 60000,
19 hour: 3600000,
20 day: 3600000 * 24,
21 week: 3600000 * 24 * 7,
22 month: 3600000 * 24 * 30
23 }
24
25 export function parseDuration (duration: number | string): number {
26 if (typeof duration === 'number') return duration
27
28 if (typeof duration === 'string') {
29 const split = duration.match(/^([\d\.,]+)\s?(\w+)$/)
30
31 if (split.length === 3) {
32 const len = parseFloat(split[1])
33 let unit = split[2].replace(/s$/i,'').toLowerCase()
34 if (unit === 'm') {
35 unit = 'ms'
36 }
37
38 return (len || 1) * (timeTable[unit] || 0)
39 }
40 }
41
42 throw new Error('Duration could not be properly parsed')
43 }
44
45 export function parseBytes (value: string | number): number {
46 if (typeof value === 'number') return value
47
48 const tgm = /^(\d+)\s*TB\s*(\d+)\s*GB\s*(\d+)\s*MB$/
49 const tg = /^(\d+)\s*TB\s*(\d+)\s*GB$/
50 const tm = /^(\d+)\s*TB\s*(\d+)\s*MB$/
51 const gm = /^(\d+)\s*GB\s*(\d+)\s*MB$/
52 const t = /^(\d+)\s*TB$/
53 const g = /^(\d+)\s*GB$/
54 const m = /^(\d+)\s*MB$/
55 const b = /^(\d+)\s*B$/
56 let match
57
58 if (value.match(tgm)) {
59 match = value.match(tgm)
60 return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024
61 + parseInt(match[2], 10) * 1024 * 1024 * 1024
62 + parseInt(match[3], 10) * 1024 * 1024
63 } else if (value.match(tg)) {
64 match = value.match(tg)
65 return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024
66 + parseInt(match[2], 10) * 1024 * 1024 * 1024
67 } else if (value.match(tm)) {
68 match = value.match(tm)
69 return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024
70 + parseInt(match[2], 10) * 1024 * 1024
71 } else if (value.match(gm)) {
72 match = value.match(gm)
73 return parseInt(match[1], 10) * 1024 * 1024 * 1024
74 + parseInt(match[2], 10) * 1024 * 1024
75 } else if (value.match(t)) {
76 match = value.match(t)
77 return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024
78 } else if (value.match(g)) {
79 match = value.match(g)
80 return parseInt(match[1], 10) * 1024 * 1024 * 1024
81 } else if (value.match(m)) {
82 match = value.match(m)
83 return parseInt(match[1], 10) * 1024 * 1024
84 } else if (value.match(b)) {
85 match = value.match(b)
86 return parseInt(match[1], 10) * 1024
87 } else {
88 return parseInt(value, 10)
89 }
90 }
91
92 function sanitizeUrl (url: string) {
93 const urlObject = new URL(url)
94
95 if (urlObject.protocol === 'https:' && urlObject.port === '443') {
96 urlObject.port = ''
97 } else if (urlObject.protocol === 'http:' && urlObject.port === '80') {
98 urlObject.port = ''
99 }
100
101 return urlObject.href.replace(/\/$/, '')
102 }
103
104 // Don't import remote scheme from constants because we are in core utils
105 function sanitizeHost (host: string, remoteScheme: string) {
106 const toRemove = remoteScheme === 'https' ? 443 : 80
107
108 return host.replace(new RegExp(`:${toRemove}$`), '')
109 }
110
111 function isTestInstance () {
112 return process.env.NODE_ENV === 'test'
113 }
114
115 function isProdInstance () {
116 return process.env.NODE_ENV === 'production'
117 }
118
119 function root () {
120 // We are in /helpers/utils.js
121 const paths = [ __dirname, '..', '..' ]
122
123 // We are under /dist directory
124 if (process.mainModule && process.mainModule.filename.endsWith('.ts') === false) {
125 paths.push('..')
126 }
127
128 return join.apply(null, paths)
129 }
130
131 // Thanks: https://stackoverflow.com/a/12034334
132 function escapeHTML (stringParam) {
133 if (!stringParam) return ''
134
135 const entityMap = {
136 '&': '&',
137 '<': '&lt;',
138 '>': '&gt;',
139 '"': '&quot;',
140 '\'': '&#39;',
141 '/': '&#x2F;',
142 '`': '&#x60;',
143 '=': '&#x3D;'
144 }
145
146 return String(stringParam).replace(/[&<>"'`=\/]/g, s => entityMap[s])
147 }
148
149 function pageToStartAndCount (page: number, itemsPerPage: number) {
150 const start = (page - 1) * itemsPerPage
151
152 return { start, count: itemsPerPage }
153 }
154
155 function buildPath (path: string) {
156 if (isAbsolute(path)) return path
157
158 return join(root(), path)
159 }
160
161 // Consistent with .length, lodash truncate function is not
162 function peertubeTruncate (str: string, maxLength: number) {
163 const options = {
164 length: maxLength
165 }
166 const truncatedStr = truncate(str, options)
167
168 // The truncated string is okay, we can return it
169 if (truncatedStr.length <= maxLength) return truncatedStr
170
171 // Lodash takes into account all UTF characters, whereas String.prototype.length does not: some characters have a length of 2
172 // We always use the .length so we need to truncate more if needed
173 options.length -= truncatedStr.length - maxLength
174 return truncate(str, options)
175 }
176
177 function sha256 (str: string, encoding: HexBase64Latin1Encoding = 'hex') {
178 return createHash('sha256').update(str).digest(encoding)
179 }
180
181 function promisify0<A> (func: (cb: (err: any, result: A) => void) => void): () => Promise<A> {
182 return function promisified (): Promise<A> {
183 return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
184 func.apply(null, [ (err: any, res: A) => err ? reject(err) : resolve(res) ])
185 })
186 }
187 }
188
189 // Thanks to https://gist.github.com/kumasento/617daa7e46f13ecdd9b2
190 function promisify1<T, A> (func: (arg: T, cb: (err: any, result: A) => void) => void): (arg: T) => Promise<A> {
191 return function promisified (arg: T): Promise<A> {
192 return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
193 func.apply(null, [ arg, (err: any, res: A) => err ? reject(err) : resolve(res) ])
194 })
195 }
196 }
197
198 function promisify1WithVoid<T> (func: (arg: T, cb: (err: any) => void) => void): (arg: T) => Promise<void> {
199 return function promisified (arg: T): Promise<void> {
200 return new Promise<void>((resolve: () => void, reject: (err: any) => void) => {
201 func.apply(null, [ arg, (err: any) => err ? reject(err) : resolve() ])
202 })
203 }
204 }
205
206 function promisify2<T, U, A> (func: (arg1: T, arg2: U, cb: (err: any, result: A) => void) => void): (arg1: T, arg2: U) => Promise<A> {
207 return function promisified (arg1: T, arg2: U): Promise<A> {
208 return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
209 func.apply(null, [ arg1, arg2, (err: any, res: A) => err ? reject(err) : resolve(res) ])
210 })
211 }
212 }
213
214 function promisify2WithVoid<T, U> (func: (arg1: T, arg2: U, cb: (err: any) => void) => void): (arg1: T, arg2: U) => Promise<void> {
215 return function promisified (arg1: T, arg2: U): Promise<void> {
216 return new Promise<void>((resolve: () => void, reject: (err: any) => void) => {
217 func.apply(null, [ arg1, arg2, (err: any) => err ? reject(err) : resolve() ])
218 })
219 }
220 }
221
222 const pseudoRandomBytesPromise = promisify1<number, Buffer>(pseudoRandomBytes)
223 const createPrivateKey = promisify1<number, { key: string }>(pem.createPrivateKey)
224 const getPublicKey = promisify1<string, { publicKey: string }>(pem.getPublicKey)
225 const bcryptComparePromise = promisify2<any, string, boolean>(bcrypt.compare)
226 const bcryptGenSaltPromise = promisify1<number, string>(bcrypt.genSalt)
227 const bcryptHashPromise = promisify2<any, string | number, string>(bcrypt.hash)
228 const createTorrentPromise = promisify2<string, any, any>(createTorrent)
229 const execPromise2 = promisify2<string, any, string>(exec)
230 const execPromise = promisify1<string, string>(exec)
231
232 // ---------------------------------------------------------------------------
233
234 export {
235 isTestInstance,
236 isProdInstance,
237
238 root,
239 escapeHTML,
240 pageToStartAndCount,
241 sanitizeUrl,
242 sanitizeHost,
243 buildPath,
244 peertubeTruncate,
245 sha256,
246
247 promisify0,
248 promisify1,
249
250 pseudoRandomBytesPromise,
251 createPrivateKey,
252 getPublicKey,
253 bcryptComparePromise,
254 bcryptGenSaltPromise,
255 bcryptHashPromise,
256 createTorrentPromise,
257 execPromise2,
258 execPromise
259 }