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