Add torrent tests
[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 { 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 && 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 sha256 (str: string) {
99   return createHash('sha256').update(str).digest('hex')
100 }
101
102 function promisify0<A> (func: (cb: (err: any, result: A) => void) => void): () => Promise<A> {
103   return function promisified (): Promise<A> {
104     return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
105       func.apply(null, [ (err: any, res: A) => err ? reject(err) : resolve(res) ])
106     })
107   }
108 }
109
110 // Thanks to https://gist.github.com/kumasento/617daa7e46f13ecdd9b2
111 function promisify1<T, A> (func: (arg: T, cb: (err: any, result: A) => void) => void): (arg: T) => Promise<A> {
112   return function promisified (arg: T): Promise<A> {
113     return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
114       func.apply(null, [ arg, (err: any, res: A) => err ? reject(err) : resolve(res) ])
115     })
116   }
117 }
118
119 function promisify1WithVoid<T> (func: (arg: T, cb: (err: any) => void) => void): (arg: T) => Promise<void> {
120   return function promisified (arg: T): Promise<void> {
121     return new Promise<void>((resolve: () => void, reject: (err: any) => void) => {
122       func.apply(null, [ arg, (err: any) => err ? reject(err) : resolve() ])
123     })
124   }
125 }
126
127 function promisify2<T, U, A> (func: (arg1: T, arg2: U, cb: (err: any, result: A) => void) => void): (arg1: T, arg2: U) => Promise<A> {
128   return function promisified (arg1: T, arg2: U): Promise<A> {
129     return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
130       func.apply(null, [ arg1, arg2, (err: any, res: A) => err ? reject(err) : resolve(res) ])
131     })
132   }
133 }
134
135 function promisify2WithVoid<T, U> (func: (arg1: T, arg2: U, cb: (err: any) => void) => void): (arg1: T, arg2: U) => Promise<void> {
136   return function promisified (arg1: T, arg2: U): Promise<void> {
137     return new Promise<void>((resolve: () => void, reject: (err: any) => void) => {
138       func.apply(null, [ arg1, arg2, (err: any) => err ? reject(err) : resolve() ])
139     })
140   }
141 }
142
143 const copyFilePromise = promisify2WithVoid<string, string>(copyFile)
144 const readFileBufferPromise = promisify1<string, Buffer>(readFile)
145 const unlinkPromise = promisify1WithVoid<string>(unlink)
146 const renamePromise = promisify2WithVoid<string, string>(rename)
147 const writeFilePromise = promisify2WithVoid<string, any>(writeFile)
148 const readdirPromise = promisify1<string, string[]>(readdir)
149 const mkdirpPromise = promisify1<string, string>(mkdirp)
150 // we cannot modify the Promise types, so we should make the promisify instance check mkdirp
151 const pseudoRandomBytesPromise = promisify1<number, Buffer>(pseudoRandomBytes)
152 const createPrivateKey = promisify1<number, { key: string }>(pem.createPrivateKey)
153 const getPublicKey = promisify1<string, { publicKey: string }>(pem.getPublicKey)
154 const bcryptComparePromise = promisify2<any, string, boolean>(bcrypt.compare)
155 const bcryptGenSaltPromise = promisify1<number, string>(bcrypt.genSalt)
156 const bcryptHashPromise = promisify2<any, string | number, string>(bcrypt.hash)
157 const createTorrentPromise = promisify2<string, any, any>(createTorrent)
158 const rimrafPromise = promisify1WithVoid<string>(rimraf)
159 const statPromise = promisify1<string, Stats>(stat)
160
161 // ---------------------------------------------------------------------------
162
163 export {
164   isTestInstance,
165   root,
166   escapeHTML,
167   pageToStartAndCount,
168   sanitizeUrl,
169   sanitizeHost,
170   buildPath,
171   peertubeTruncate,
172   sha256,
173
174   promisify0,
175   promisify1,
176
177   copyFilePromise,
178   readdirPromise,
179   readFileBufferPromise,
180   unlinkPromise,
181   renamePromise,
182   writeFilePromise,
183   mkdirpPromise,
184   pseudoRandomBytesPromise,
185   createPrivateKey,
186   getPublicKey,
187   bcryptComparePromise,
188   bcryptGenSaltPromise,
189   bcryptHashPromise,
190   createTorrentPromise,
191   rimrafPromise,
192   statPromise
193 }