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