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