Add tmp and redundancy directories
[oweals/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 }