]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/helpers/core-utils.ts
Implement contact form on server side
[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 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, encoding: HexBase64Latin1Encoding = 'hex') {
197 return createHash('sha256').update(str).digest(encoding)
198 }
199
200 function promisify0<A> (func: (cb: (err: any, result: A) => void) => void): () => Promise<A> {
201 return function promisified (): Promise<A> {
202 return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
203 func.apply(null, [ (err: any, res: A) => err ? reject(err) : resolve(res) ])
204 })
205 }
206 }
207
208 // Thanks to https://gist.github.com/kumasento/617daa7e46f13ecdd9b2
209 function promisify1<T, A> (func: (arg: T, cb: (err: any, result: A) => void) => void): (arg: T) => Promise<A> {
210 return function promisified (arg: T): Promise<A> {
211 return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
212 func.apply(null, [ arg, (err: any, res: A) => err ? reject(err) : resolve(res) ])
213 })
214 }
215 }
216
217 function promisify1WithVoid<T> (func: (arg: T, cb: (err: any) => void) => void): (arg: T) => Promise<void> {
218 return function promisified (arg: T): Promise<void> {
219 return new Promise<void>((resolve: () => void, reject: (err: any) => void) => {
220 func.apply(null, [ arg, (err: any) => err ? reject(err) : resolve() ])
221 })
222 }
223 }
224
225 function promisify2<T, U, A> (func: (arg1: T, arg2: U, cb: (err: any, result: A) => void) => void): (arg1: T, arg2: U) => Promise<A> {
226 return function promisified (arg1: T, arg2: U): Promise<A> {
227 return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
228 func.apply(null, [ arg1, arg2, (err: any, res: A) => err ? reject(err) : resolve(res) ])
229 })
230 }
231 }
232
233 function promisify2WithVoid<T, U> (func: (arg1: T, arg2: U, cb: (err: any) => void) => void): (arg1: T, arg2: U) => Promise<void> {
234 return function promisified (arg1: T, arg2: U): Promise<void> {
235 return new Promise<void>((resolve: () => void, reject: (err: any) => void) => {
236 func.apply(null, [ arg1, arg2, (err: any) => err ? reject(err) : resolve() ])
237 })
238 }
239 }
240
241 const pseudoRandomBytesPromise = promisify1<number, Buffer>(pseudoRandomBytes)
242 const createPrivateKey = promisify1<number, { key: string }>(pem.createPrivateKey)
243 const getPublicKey = promisify1<string, { publicKey: string }>(pem.getPublicKey)
244 const bcryptComparePromise = promisify2<any, string, boolean>(bcrypt.compare)
245 const bcryptGenSaltPromise = promisify1<number, string>(bcrypt.genSalt)
246 const bcryptHashPromise = promisify2<any, string | number, string>(bcrypt.hash)
247 const createTorrentPromise = promisify2<string, any, any>(createTorrent)
248 const execPromise2 = promisify2<string, any, string>(exec)
249 const execPromise = promisify1<string, string>(exec)
250
251 // ---------------------------------------------------------------------------
252
253 export {
254 isTestInstance,
255 isProdInstance,
256
257 objectConverter,
258 root,
259 escapeHTML,
260 pageToStartAndCount,
261 sanitizeUrl,
262 sanitizeHost,
263 buildPath,
264 peertubeTruncate,
265 sha256,
266
267 promisify0,
268 promisify1,
269
270 pseudoRandomBytesPromise,
271 createPrivateKey,
272 getPublicKey,
273 bcryptComparePromise,
274 bcryptGenSaltPromise,
275 bcryptHashPromise,
276 createTorrentPromise,
277 execPromise2,
278 execPromise
279 }