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