]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/helpers/core-utils.ts
Add url field in caption and use it for thumbnails
[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 { createHash, HexBase64Latin1Encoding, pseudoRandomBytes } from 'crypto'
7 import { basename, isAbsolute, join, resolve } from 'path'
8 import * as pem from 'pem'
9 import { URL } from 'url'
10 import { truncate } from 'lodash'
11 import { exec, ExecOptions } from 'child_process'
12
13 const objectConverter = (oldObject: any, keyConverter: (e: string) => string, valueConverter: (e: any) => any) => {
14 if (!oldObject || typeof oldObject !== 'object') {
15 return valueConverter(oldObject)
16 }
17
18 if (Array.isArray(oldObject)) {
19 return oldObject.map(e => objectConverter(e, keyConverter, valueConverter))
20 }
21
22 const newObject = {}
23 Object.keys(oldObject).forEach(oldKey => {
24 const newKey = keyConverter(oldKey)
25 newObject[ newKey ] = objectConverter(oldObject[ oldKey ], keyConverter, valueConverter)
26 })
27
28 return newObject
29 }
30
31 const timeTable = {
32 ms: 1,
33 second: 1000,
34 minute: 60000,
35 hour: 3600000,
36 day: 3600000 * 24,
37 week: 3600000 * 24 * 7,
38 month: 3600000 * 24 * 30
39 }
40
41 export function parseDurationToMs (duration: number | string): number {
42 if (typeof duration === 'number') return duration
43
44 if (typeof duration === 'string') {
45 const split = duration.match(/^([\d\.,]+)\s?(\w+)$/)
46
47 if (split.length === 3) {
48 const len = parseFloat(split[1])
49 let unit = split[2].replace(/s$/i,'').toLowerCase()
50 if (unit === 'm') {
51 unit = 'ms'
52 }
53
54 return (len || 1) * (timeTable[unit] || 0)
55 }
56 }
57
58 throw new Error(`Duration ${duration} could not be properly parsed`)
59 }
60
61 export function parseBytes (value: string | number): number {
62 if (typeof value === 'number') return value
63
64 const tgm = /^(\d+)\s*TB\s*(\d+)\s*GB\s*(\d+)\s*MB$/
65 const tg = /^(\d+)\s*TB\s*(\d+)\s*GB$/
66 const tm = /^(\d+)\s*TB\s*(\d+)\s*MB$/
67 const gm = /^(\d+)\s*GB\s*(\d+)\s*MB$/
68 const t = /^(\d+)\s*TB$/
69 const g = /^(\d+)\s*GB$/
70 const m = /^(\d+)\s*MB$/
71 const b = /^(\d+)\s*B$/
72 let match
73
74 if (value.match(tgm)) {
75 match = value.match(tgm)
76 return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024
77 + parseInt(match[2], 10) * 1024 * 1024 * 1024
78 + parseInt(match[3], 10) * 1024 * 1024
79 } else if (value.match(tg)) {
80 match = value.match(tg)
81 return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024
82 + parseInt(match[2], 10) * 1024 * 1024 * 1024
83 } else if (value.match(tm)) {
84 match = value.match(tm)
85 return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024
86 + parseInt(match[2], 10) * 1024 * 1024
87 } else if (value.match(gm)) {
88 match = value.match(gm)
89 return parseInt(match[1], 10) * 1024 * 1024 * 1024
90 + parseInt(match[2], 10) * 1024 * 1024
91 } else if (value.match(t)) {
92 match = value.match(t)
93 return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024
94 } else if (value.match(g)) {
95 match = value.match(g)
96 return parseInt(match[1], 10) * 1024 * 1024 * 1024
97 } else if (value.match(m)) {
98 match = value.match(m)
99 return parseInt(match[1], 10) * 1024 * 1024
100 } else if (value.match(b)) {
101 match = value.match(b)
102 return parseInt(match[1], 10) * 1024
103 } else {
104 return parseInt(value, 10)
105 }
106 }
107
108 function sanitizeUrl (url: string) {
109 const urlObject = new URL(url)
110
111 if (urlObject.protocol === 'https:' && urlObject.port === '443') {
112 urlObject.port = ''
113 } else if (urlObject.protocol === 'http:' && urlObject.port === '80') {
114 urlObject.port = ''
115 }
116
117 return urlObject.href.replace(/\/$/, '')
118 }
119
120 // Don't import remote scheme from constants because we are in core utils
121 function sanitizeHost (host: string, remoteScheme: string) {
122 const toRemove = remoteScheme === 'https' ? 443 : 80
123
124 return host.replace(new RegExp(`:${toRemove}$`), '')
125 }
126
127 function isTestInstance () {
128 return process.env.NODE_ENV === 'test'
129 }
130
131 function isProdInstance () {
132 return process.env.NODE_ENV === 'production'
133 }
134
135 function getAppNumber () {
136 return process.env.NODE_APP_INSTANCE
137 }
138
139 let rootPath: string
140 function root () {
141 if (rootPath) return rootPath
142
143 // We are in /helpers/utils.js
144 rootPath = join(__dirname, '..', '..')
145
146 if (basename(rootPath) === 'dist') rootPath = resolve(rootPath, '..')
147
148 return rootPath
149 }
150
151 // Thanks: https://stackoverflow.com/a/12034334
152 function escapeHTML (stringParam) {
153 if (!stringParam) return ''
154
155 const entityMap = {
156 '&': '&',
157 '<': '&lt;',
158 '>': '&gt;',
159 '"': '&quot;',
160 '\'': '&#39;',
161 '/': '&#x2F;',
162 '`': '&#x60;',
163 '=': '&#x3D;'
164 }
165
166 return String(stringParam).replace(/[&<>"'`=\/]/g, s => entityMap[s])
167 }
168
169 function pageToStartAndCount (page: number, itemsPerPage: number) {
170 const start = (page - 1) * itemsPerPage
171
172 return { start, count: itemsPerPage }
173 }
174
175 function buildPath (path: string) {
176 if (isAbsolute(path)) return path
177
178 return join(root(), path)
179 }
180
181 // Consistent with .length, lodash truncate function is not
182 function peertubeTruncate (str: string, options: { length: number, separator?: RegExp, omission?: string }) {
183 const truncatedStr = truncate(str, options)
184
185 // The truncated string is okay, we can return it
186 if (truncatedStr.length <= options.length) return truncatedStr
187
188 // Lodash takes into account all UTF characters, whereas String.prototype.length does not: some characters have a length of 2
189 // We always use the .length so we need to truncate more if needed
190 options.length -= truncatedStr.length - options.length
191 return truncate(str, options)
192 }
193
194 function sha256 (str: string | Buffer, encoding: HexBase64Latin1Encoding = 'hex') {
195 return createHash('sha256').update(str).digest(encoding)
196 }
197
198 function sha1 (str: string | Buffer, encoding: HexBase64Latin1Encoding = 'hex') {
199 return createHash('sha1').update(str).digest(encoding)
200 }
201
202
203
204 function execShell (command: string, options?: ExecOptions) {
205 return new Promise<{ err?: Error, stdout: string, stderr: string }>((res, rej) => {
206 exec(command, options, (err, stdout, stderr) => {
207 if (err) return rej({ err, stdout, stderr })
208
209 return res({ stdout, stderr })
210 })
211 })
212 }
213
214 function promisify0<A> (func: (cb: (err: any, result: A) => void) => void): () => Promise<A> {
215 return function promisified (): Promise<A> {
216 return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
217 func.apply(null, [ (err: any, res: A) => err ? reject(err) : resolve(res) ])
218 })
219 }
220 }
221
222 // Thanks to https://gist.github.com/kumasento/617daa7e46f13ecdd9b2
223 function promisify1<T, A> (func: (arg: T, cb: (err: any, result: A) => void) => void): (arg: T) => Promise<A> {
224 return function promisified (arg: T): Promise<A> {
225 return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
226 func.apply(null, [ arg, (err: any, res: A) => err ? reject(err) : resolve(res) ])
227 })
228 }
229 }
230
231 function promisify1WithVoid<T> (func: (arg: T, cb: (err: any) => void) => void): (arg: T) => Promise<void> {
232 return function promisified (arg: T): Promise<void> {
233 return new Promise<void>((resolve: () => void, reject: (err: any) => void) => {
234 func.apply(null, [ arg, (err: any) => err ? reject(err) : resolve() ])
235 })
236 }
237 }
238
239 function promisify2<T, U, A> (func: (arg1: T, arg2: U, cb: (err: any, result: A) => void) => void): (arg1: T, arg2: U) => Promise<A> {
240 return function promisified (arg1: T, arg2: U): Promise<A> {
241 return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
242 func.apply(null, [ arg1, arg2, (err: any, res: A) => err ? reject(err) : resolve(res) ])
243 })
244 }
245 }
246
247 function promisify2WithVoid<T, U> (func: (arg1: T, arg2: U, cb: (err: any) => void) => void): (arg1: T, arg2: U) => Promise<void> {
248 return function promisified (arg1: T, arg2: U): Promise<void> {
249 return new Promise<void>((resolve: () => void, reject: (err: any) => void) => {
250 func.apply(null, [ arg1, arg2, (err: any) => err ? reject(err) : resolve() ])
251 })
252 }
253 }
254
255 const pseudoRandomBytesPromise = promisify1<number, Buffer>(pseudoRandomBytes)
256 const createPrivateKey = promisify1<number, { key: string }>(pem.createPrivateKey)
257 const getPublicKey = promisify1<string, { publicKey: string }>(pem.getPublicKey)
258 const execPromise2 = promisify2<string, any, string>(exec)
259 const execPromise = promisify1<string, string>(exec)
260
261 // ---------------------------------------------------------------------------
262
263 export {
264 isTestInstance,
265 isProdInstance,
266 getAppNumber,
267
268 objectConverter,
269 root,
270 escapeHTML,
271 pageToStartAndCount,
272 sanitizeUrl,
273 sanitizeHost,
274 buildPath,
275 execShell,
276 peertubeTruncate,
277
278 sha256,
279 sha1,
280
281 promisify0,
282 promisify1,
283 promisify2,
284
285 pseudoRandomBytesPromise,
286 createPrivateKey,
287 getPublicKey,
288 execPromise2,
289 execPromise
290 }