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