]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame_incremental - server/helpers/core-utils.ts
Tests for totalRepliesFromVideoAuthor
[github/Chocobozzz/PeerTube.git] / server / helpers / core-utils.ts
... / ...
CommitLineData
1/*
2 Different from 'utils' because we don't not import other PeerTube modules.
3 Useful to avoid circular dependencies.
4*/
5
6import { createHash, HexBase64Latin1Encoding, pseudoRandomBytes } from 'crypto'
7import { basename, isAbsolute, join, resolve } from 'path'
8import * as pem from 'pem'
9import { URL } from 'url'
10import { truncate } from 'lodash'
11import { exec, ExecOptions } from 'child_process'
12
13const 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
31const 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
41export 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
61export 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
108function 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
121function sanitizeHost (host: string, remoteScheme: string) {
122 const toRemove = remoteScheme === 'https' ? 443 : 80
123
124 return host.replace(new RegExp(`:${toRemove}$`), '')
125}
126
127function isTestInstance () {
128 return process.env.NODE_ENV === 'test'
129}
130
131function isProdInstance () {
132 return process.env.NODE_ENV === 'production'
133}
134
135function getAppNumber () {
136 return process.env.NODE_APP_INSTANCE
137}
138
139let rootPath: string
140function 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
152function 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
169function pageToStartAndCount (page: number, itemsPerPage: number) {
170 const start = (page - 1) * itemsPerPage
171
172 return { start, count: itemsPerPage }
173}
174
175function 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
182function 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
194function sha256 (str: string | Buffer, encoding: HexBase64Latin1Encoding = 'hex') {
195 return createHash('sha256').update(str).digest(encoding)
196}
197
198function sha1 (str: string | Buffer, encoding: HexBase64Latin1Encoding = 'hex') {
199 return createHash('sha1').update(str).digest(encoding)
200}
201
202function execShell (command: string, options?: ExecOptions) {
203 return new Promise<{ err?: Error, stdout: string, stderr: string }>((res, rej) => {
204 exec(command, options, (err, stdout, stderr) => {
205 if (err) return rej({ err, stdout, stderr })
206
207 return res({ stdout, stderr })
208 })
209 })
210}
211
212function promisify0<A> (func: (cb: (err: any, result: A) => void) => void): () => Promise<A> {
213 return function promisified (): Promise<A> {
214 return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
215 func.apply(null, [ (err: any, res: A) => err ? reject(err) : resolve(res) ])
216 })
217 }
218}
219
220// Thanks to https://gist.github.com/kumasento/617daa7e46f13ecdd9b2
221function promisify1<T, A> (func: (arg: T, cb: (err: any, result: A) => void) => void): (arg: T) => Promise<A> {
222 return function promisified (arg: T): Promise<A> {
223 return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
224 func.apply(null, [ arg, (err: any, res: A) => err ? reject(err) : resolve(res) ])
225 })
226 }
227}
228
229function promisify1WithVoid<T> (func: (arg: T, cb: (err: any) => void) => void): (arg: T) => Promise<void> {
230 return function promisified (arg: T): Promise<void> {
231 return new Promise<void>((resolve: () => void, reject: (err: any) => void) => {
232 func.apply(null, [ arg, (err: any) => err ? reject(err) : resolve() ])
233 })
234 }
235}
236
237function promisify2<T, U, A> (func: (arg1: T, arg2: U, cb: (err: any, result: A) => void) => void): (arg1: T, arg2: U) => Promise<A> {
238 return function promisified (arg1: T, arg2: U): Promise<A> {
239 return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
240 func.apply(null, [ arg1, arg2, (err: any, res: A) => err ? reject(err) : resolve(res) ])
241 })
242 }
243}
244
245function promisify2WithVoid<T, U> (func: (arg1: T, arg2: U, cb: (err: any) => void) => void): (arg1: T, arg2: U) => Promise<void> {
246 return function promisified (arg1: T, arg2: U): Promise<void> {
247 return new Promise<void>((resolve: () => void, reject: (err: any) => void) => {
248 func.apply(null, [ arg1, arg2, (err: any) => err ? reject(err) : resolve() ])
249 })
250 }
251}
252
253const pseudoRandomBytesPromise = promisify1<number, Buffer>(pseudoRandomBytes)
254const createPrivateKey = promisify1<number, { key: string }>(pem.createPrivateKey)
255const getPublicKey = promisify1<string, { publicKey: string }>(pem.getPublicKey)
256const execPromise2 = promisify2<string, any, string>(exec)
257const execPromise = promisify1<string, string>(exec)
258
259// ---------------------------------------------------------------------------
260
261export {
262 isTestInstance,
263 isProdInstance,
264 getAppNumber,
265
266 objectConverter,
267 root,
268 escapeHTML,
269 pageToStartAndCount,
270 sanitizeUrl,
271 sanitizeHost,
272 buildPath,
273 execShell,
274 peertubeTruncate,
275
276 sha256,
277 sha1,
278
279 promisify0,
280 promisify1,
281 promisify2,
282
283 pseudoRandomBytesPromise,
284 createPrivateKey,
285 getPublicKey,
286 execPromise2,
287 execPromise
288}