Do not import live streaming
[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 * as rimraf from 'rimraf'
12 import { URL } from 'url'
13 import { truncate } from 'lodash'
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 root () {
68   // We are in /helpers/utils.js
69   const paths = [ __dirname, '..', '..' ]
70
71   // We are under /dist directory
72   if (process.mainModule && process.mainModule.filename.endsWith('.ts') === false) {
73     paths.push('..')
74   }
75
76   return join.apply(null, paths)
77 }
78
79 // Thanks: https://stackoverflow.com/a/12034334
80 function escapeHTML (stringParam) {
81   if (!stringParam) return ''
82
83   const entityMap = {
84     '&': '&',
85     '<': '&lt;',
86     '>': '&gt;',
87     '"': '&quot;',
88     '\'': '&#39;',
89     '/': '&#x2F;',
90     '`': '&#x60;',
91     '=': '&#x3D;'
92   }
93
94   return String(stringParam).replace(/[&<>"'`=\/]/g, s => entityMap[s])
95 }
96
97 function pageToStartAndCount (page: number, itemsPerPage: number) {
98   const start = (page - 1) * itemsPerPage
99
100   return { start, count: itemsPerPage }
101 }
102
103 function buildPath (path: string) {
104   if (isAbsolute(path)) return path
105
106   return join(root(), path)
107 }
108
109 // Consistent with .length, lodash truncate function is not
110 function peertubeTruncate (str: string, maxLength: number) {
111   const options = {
112     length: maxLength
113   }
114   const truncatedStr = truncate(str, options)
115
116   // The truncated string is okay, we can return it
117   if (truncatedStr.length <= maxLength) return truncatedStr
118
119   // Lodash takes into account all UTF characters, whereas String.prototype.length does not: some characters have a length of 2
120   // We always use the .length so we need to truncate more if needed
121   options.length -= truncatedStr.length - maxLength
122   return truncate(str, options)
123 }
124
125 function sha256 (str: string) {
126   return createHash('sha256').update(str).digest('hex')
127 }
128
129 function promisify0<A> (func: (cb: (err: any, result: A) => void) => void): () => Promise<A> {
130   return function promisified (): Promise<A> {
131     return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
132       func.apply(null, [ (err: any, res: A) => err ? reject(err) : resolve(res) ])
133     })
134   }
135 }
136
137 // Thanks to https://gist.github.com/kumasento/617daa7e46f13ecdd9b2
138 function promisify1<T, A> (func: (arg: T, cb: (err: any, result: A) => void) => void): (arg: T) => Promise<A> {
139   return function promisified (arg: T): Promise<A> {
140     return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
141       func.apply(null, [ arg, (err: any, res: A) => err ? reject(err) : resolve(res) ])
142     })
143   }
144 }
145
146 function promisify1WithVoid<T> (func: (arg: T, cb: (err: any) => void) => void): (arg: T) => Promise<void> {
147   return function promisified (arg: T): Promise<void> {
148     return new Promise<void>((resolve: () => void, reject: (err: any) => void) => {
149       func.apply(null, [ arg, (err: any) => err ? reject(err) : resolve() ])
150     })
151   }
152 }
153
154 function promisify2<T, U, A> (func: (arg1: T, arg2: U, cb: (err: any, result: A) => void) => void): (arg1: T, arg2: U) => Promise<A> {
155   return function promisified (arg1: T, arg2: U): Promise<A> {
156     return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
157       func.apply(null, [ arg1, arg2, (err: any, res: A) => err ? reject(err) : resolve(res) ])
158     })
159   }
160 }
161
162 function promisify2WithVoid<T, U> (func: (arg1: T, arg2: U, cb: (err: any) => void) => void): (arg1: T, arg2: U) => Promise<void> {
163   return function promisified (arg1: T, arg2: U): Promise<void> {
164     return new Promise<void>((resolve: () => void, reject: (err: any) => void) => {
165       func.apply(null, [ arg1, arg2, (err: any) => err ? reject(err) : resolve() ])
166     })
167   }
168 }
169
170 const pseudoRandomBytesPromise = promisify1<number, Buffer>(pseudoRandomBytes)
171 const createPrivateKey = promisify1<number, { key: string }>(pem.createPrivateKey)
172 const getPublicKey = promisify1<string, { publicKey: string }>(pem.getPublicKey)
173 const bcryptComparePromise = promisify2<any, string, boolean>(bcrypt.compare)
174 const bcryptGenSaltPromise = promisify1<number, string>(bcrypt.genSalt)
175 const bcryptHashPromise = promisify2<any, string | number, string>(bcrypt.hash)
176 const createTorrentPromise = promisify2<string, any, any>(createTorrent)
177
178 // ---------------------------------------------------------------------------
179
180 export {
181   isTestInstance,
182   root,
183   escapeHTML,
184   pageToStartAndCount,
185   sanitizeUrl,
186   sanitizeHost,
187   buildPath,
188   peertubeTruncate,
189   sha256,
190
191   promisify0,
192   promisify1,
193
194   pseudoRandomBytesPromise,
195   createPrivateKey,
196   getPublicKey,
197   bcryptComparePromise,
198   bcryptGenSaltPromise,
199   bcryptHashPromise,
200   createTorrentPromise
201 }