]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/helpers/core-utils.ts
Underline links in feed popover when hovering
[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, mkdirp } from 'fs-extra'
10 import { isAbsolute, join } from 'path'
11 import * as pem from 'pem'
12 import * as rimraf from 'rimraf'
13 import { URL } from 'url'
14 import { truncate } from 'lodash'
15
16 const 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 }
25 export 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
45 function 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
58 function sanitizeHost (host: string, remoteScheme: string) {
59 const toRemove = remoteScheme === 'https' ? 443 : 80
60
61 return host.replace(new RegExp(`:${toRemove}$`), '')
62 }
63
64 function isTestInstance () {
65 return process.env.NODE_ENV === 'test'
66 }
67
68 function 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
81 function 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
98 function pageToStartAndCount (page: number, itemsPerPage: number) {
99 const start = (page - 1) * itemsPerPage
100
101 return { start, count: itemsPerPage }
102 }
103
104 function 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
111 function 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
126 function sha256 (str: string) {
127 return createHash('sha256').update(str).digest('hex')
128 }
129
130 function 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
139 function 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
147 function 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
155 function 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
163 function 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
171 const copyFilePromise = promisify2WithVoid<string, string>(copyFile)
172 const readFileBufferPromise = promisify1<string, Buffer>(readFile)
173 const unlinkPromise = promisify1WithVoid<string>(unlink)
174 const renamePromise = promisify2WithVoid<string, string>(rename)
175 const writeFilePromise = promisify2WithVoid<string, any>(writeFile)
176 const readdirPromise = promisify1<string, string[]>(readdir)
177 const mkdirpPromise = promisify1<string, string>(mkdirp)
178 // we cannot modify the Promise types, so we should make the promisify instance check mkdirp
179 const pseudoRandomBytesPromise = promisify1<number, Buffer>(pseudoRandomBytes)
180 const createPrivateKey = promisify1<number, { key: string }>(pem.createPrivateKey)
181 const getPublicKey = promisify1<string, { publicKey: string }>(pem.getPublicKey)
182 const bcryptComparePromise = promisify2<any, string, boolean>(bcrypt.compare)
183 const bcryptGenSaltPromise = promisify1<number, string>(bcrypt.genSalt)
184 const bcryptHashPromise = promisify2<any, string | number, string>(bcrypt.hash)
185 const createTorrentPromise = promisify2<string, any, any>(createTorrent)
186 const rimrafPromise = promisify1WithVoid<string>(rimraf)
187 const statPromise = promisify1<string, Stats>(stat)
188
189 // ---------------------------------------------------------------------------
190
191 export {
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 }