core-utils.ts 8.8 KB

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