2018-01-04 15:15:21 -05:00
/ * *
* @ description MeshCentral Common Library
* @ author Ylian Saint - Hilaire
2020-01-02 21:30:12 -05:00
* @ copyright Intel Corporation 2018 - 2020
2018-01-04 15:15:21 -05:00
* @ license Apache - 2.0
* @ version v0 . 0.1
* /
2018-08-29 20:40:30 -04:00
/*xjslint node: true */
/*xjslint plusplus: true */
/*xjslint maxlen: 256 */
/*jshint node: true */
/*jshint strict: false */
/*jshint esversion: 6 */
"use strict" ;
2018-08-27 15:24:15 -04:00
2019-01-05 15:04:18 -05:00
const fs = require ( "fs" ) ;
2018-08-29 20:40:30 -04:00
const crypto = require ( "crypto" ) ;
2017-08-28 12:27:45 -04:00
// Binary encoding and decoding functions
2018-08-29 20:40:30 -04:00
module . exports . ReadShort = function ( v , p ) { return ( v . charCodeAt ( p ) << 8 ) + v . charCodeAt ( p + 1 ) ; } ;
module . exports . ReadShortX = function ( v , p ) { return ( v . charCodeAt ( p + 1 ) << 8 ) + v . charCodeAt ( p ) ; } ;
module . exports . ReadInt = function ( v , p ) { return ( v . charCodeAt ( p ) * 0x1000000 ) + ( v . charCodeAt ( p + 1 ) << 16 ) + ( v . charCodeAt ( p + 2 ) << 8 ) + v . charCodeAt ( p + 3 ) ; } ; // We use "*0x1000000" instead of "<<24" because the shift converts the number to signed int32.
module . exports . ReadIntX = function ( v , p ) { return ( v . charCodeAt ( p + 3 ) * 0x1000000 ) + ( v . charCodeAt ( p + 2 ) << 16 ) + ( v . charCodeAt ( p + 1 ) << 8 ) + v . charCodeAt ( p ) ; } ;
module . exports . ShortToStr = function ( v ) { return String . fromCharCode ( ( v >> 8 ) & 0xFF , v & 0xFF ) ; } ;
module . exports . ShortToStrX = function ( v ) { return String . fromCharCode ( v & 0xFF , ( v >> 8 ) & 0xFF ) ; } ;
module . exports . IntToStr = function ( v ) { return String . fromCharCode ( ( v >> 24 ) & 0xFF , ( v >> 16 ) & 0xFF , ( v >> 8 ) & 0xFF , v & 0xFF ) ; } ;
module . exports . IntToStrX = function ( v ) { return String . fromCharCode ( v & 0xFF , ( v >> 8 ) & 0xFF , ( v >> 16 ) & 0xFF , ( v >> 24 ) & 0xFF ) ; } ;
2020-02-18 15:02:42 -05:00
module . exports . MakeToArray = function ( v ) { if ( ! v || v == null || typeof v == 'object' ) return v ; return [ v ] ; } ;
module . exports . SplitArray = function ( v ) { return v . split ( ',' ) ; } ;
2018-08-29 20:40:30 -04:00
module . exports . Clone = function ( v ) { return JSON . parse ( JSON . stringify ( v ) ) ; } ;
module . exports . IsFilenameValid = ( function ( ) { var x1 = /^[^\\/:\*\?"<>\|]+$/ , x2 = /^\./ , x3 = /^(nul|prn|con|lpt[0-9]|com[0-9])(\.|$)/i ; return function isFilenameValid ( fname ) { return module . exports . validateString ( fname , 1 , 4096 ) && x1 . test ( fname ) && ! x2 . test ( fname ) && ! x3 . test ( fname ) && ( fname [ 0 ] != '.' ) ; } ; } ) ( ) ;
2019-09-25 20:06:35 -04:00
module . exports . makeFilename = function ( v ) { return v . split ( '\\' ) . join ( '' ) . split ( '/' ) . join ( '' ) . split ( ':' ) . join ( '' ) . split ( '*' ) . join ( '' ) . split ( '?' ) . join ( '' ) . split ( '"' ) . join ( '' ) . split ( '<' ) . join ( '' ) . split ( '>' ) . join ( '' ) . split ( '|' ) . join ( '' ) . split ( ' ' ) . join ( '' ) . split ( '\'' ) . join ( '' ) ; }
2017-08-28 12:27:45 -04:00
// Move an element from one position in an array to a new position
module . exports . ArrayElementMove = function ( arr , from , to ) { arr . splice ( to , 0 , arr . splice ( from , 1 ) [ 0 ] ) ; } ;
2019-10-29 19:17:29 -04:00
// Format a string with arguments, "replaces {0} and {1}..."
module . exports . format = function ( format ) { var args = Array . prototype . slice . call ( arguments , 1 ) ; return format . replace ( /{(\d+)}/g , function ( match , number ) { return typeof args [ number ] != 'undefined' ? args [ number ] : match ; } ) ; } ;
2017-08-28 12:27:45 -04:00
// Print object for HTML
2018-08-29 20:40:30 -04:00
module . exports . ObjectToStringEx = function ( x , c ) {
var r = "" , i ;
2017-08-28 12:27:45 -04:00
if ( x != 0 && ( ! x || x == null ) ) return "(Null)" ;
2018-08-29 20:40:30 -04:00
if ( x instanceof Array ) { for ( i in x ) { r += '<br />' + gap ( c ) + "Item #" + i + ": " + module . exports . ObjectToStringEx ( x [ i ] , c + 1 ) ; } }
else if ( x instanceof Object ) { for ( i in x ) { r += '<br />' + gap ( c ) + i + " = " + module . exports . ObjectToStringEx ( x [ i ] , c + 1 ) ; } }
2017-08-28 12:27:45 -04:00
else { r += x ; }
return r ;
2018-08-29 20:40:30 -04:00
} ;
2017-08-28 12:27:45 -04:00
// Print object for console
2018-08-29 20:40:30 -04:00
module . exports . ObjectToStringEx2 = function ( x , c ) {
var r = "" , i ;
2017-08-28 12:27:45 -04:00
if ( x != 0 && ( ! x || x == null ) ) return "(Null)" ;
2018-08-29 20:40:30 -04:00
if ( x instanceof Array ) { for ( i in x ) { r += '\r\n' + gap2 ( c ) + "Item #" + i + ": " + module . exports . ObjectToStringEx2 ( x [ i ] , c + 1 ) ; } }
else if ( x instanceof Object ) { for ( i in x ) { r += '\r\n' + gap2 ( c ) + i + " = " + module . exports . ObjectToStringEx2 ( x [ i ] , c + 1 ) ; } }
2017-08-28 12:27:45 -04:00
else { r += x ; }
return r ;
2018-08-29 20:40:30 -04:00
} ;
2017-08-28 12:27:45 -04:00
// Create an ident gap
2018-08-29 20:40:30 -04:00
module . exports . gap = function ( c ) { var x = '' ; for ( var i = 0 ; i < ( c * 4 ) ; i ++ ) { x += ' ' ; } return x ; } ;
module . exports . gap2 = function ( c ) { var x = '' ; for ( var i = 0 ; i < ( c * 4 ) ; i ++ ) { x += ' ' ; } return x ; } ;
2017-08-28 12:27:45 -04:00
// Print an object in html
2018-08-29 20:40:30 -04:00
module . exports . ObjectToString = function ( x ) { return module . exports . ObjectToStringEx ( x , 0 ) ; } ;
module . exports . ObjectToString2 = function ( x ) { return module . exports . ObjectToStringEx2 ( x , 0 ) ; } ;
2017-08-28 12:27:45 -04:00
// Convert a hex string to a raw string
2018-08-29 20:40:30 -04:00
module . exports . hex2rstr = function ( d ) {
2017-08-28 12:27:45 -04:00
var r = '' , m = ( '' + d ) . match ( /../g ) , t ;
2018-08-29 20:40:30 -04:00
while ( t = m . shift ( ) ) { r += String . fromCharCode ( '0x' + t ) ; }
return r ;
} ;
2017-08-28 12:27:45 -04:00
// Convert decimal to hex
2018-08-29 20:40:30 -04:00
module . exports . char2hex = function ( i ) { return ( i + 0x100 ) . toString ( 16 ) . substr ( - 2 ) . toUpperCase ( ) ; } ;
2017-08-28 12:27:45 -04:00
// Convert a raw string to a hex string
2018-08-29 20:40:30 -04:00
module . exports . rstr2hex = function ( input ) {
2017-08-28 12:27:45 -04:00
var r = '' , i ;
for ( i = 0 ; i < input . length ; i ++ ) { r += module . exports . char2hex ( input . charCodeAt ( i ) ) ; }
return r ;
2018-08-29 20:40:30 -04:00
} ;
2017-08-28 12:27:45 -04:00
// UTF-8 encoding & decoding functions
2018-08-29 20:40:30 -04:00
module . exports . encode _utf8 = function ( s ) { return unescape ( encodeURIComponent ( s ) ) ; } ;
module . exports . decode _utf8 = function ( s ) { return decodeURIComponent ( escape ( s ) ) ; } ;
2017-08-28 12:27:45 -04:00
// Convert a string into a blob
2018-08-29 20:40:30 -04:00
module . exports . data2blob = function ( data ) {
2017-08-28 12:27:45 -04:00
var bytes = new Array ( data . length ) ;
for ( var i = 0 ; i < data . length ; i ++ ) bytes [ i ] = data . charCodeAt ( i ) ;
var blob = new Blob ( [ new Uint8Array ( bytes ) ] ) ;
return blob ;
2018-08-29 20:40:30 -04:00
} ;
2017-08-28 12:27:45 -04:00
// Generate random numbers
2018-08-29 20:40:30 -04:00
module . exports . random = function ( max ) { return Math . floor ( Math . random ( ) * max ) ; } ;
2017-08-28 12:27:45 -04:00
// Split a comma seperated string, ignoring commas in quotes.
module . exports . quoteSplit = function ( str ) {
var tmp = '' , quote = 0 , result = [ ] ;
for ( var i in str ) { if ( str [ i ] == '"' ) { quote = ( quote + 1 ) % 2 ; } if ( ( str [ i ] == ',' ) && ( quote == 0 ) ) { tmp = tmp . trim ( ) ; result . push ( tmp ) ; tmp = '' ; } else { tmp += str [ i ] ; } }
if ( tmp . length > 0 ) result . push ( tmp . trim ( ) ) ;
return result ;
2018-08-29 20:40:30 -04:00
} ;
2017-08-28 12:27:45 -04:00
// Convert list of "name = value" into object
module . exports . parseNameValueList = function ( list ) {
var result = [ ] ;
for ( var i in list ) {
var j = list [ i ] . indexOf ( '=' ) ;
if ( j > 0 ) {
var v = list [ i ] . substring ( j + 1 ) . trim ( ) ;
if ( ( v [ 0 ] == '"' ) && ( v [ v . length - 1 ] == '"' ) ) { v = v . substring ( 1 , v . length - 1 ) ; }
result [ list [ i ] . substring ( 0 , j ) . trim ( ) ] = v ;
}
}
return result ;
2018-08-29 20:40:30 -04:00
} ;
2017-08-28 12:27:45 -04:00
// Compute the MD5 digest hash for a set of values
module . exports . ComputeDigesthash = function ( username , password , realm , method , path , qop , nonce , nc , cnonce ) {
2020-02-18 15:02:42 -05:00
var ha1 = crypto . createHash ( 'md5' ) . update ( username + ":" + realm + ":" + password ) . digest ( 'hex' ) ;
var ha2 = crypto . createHash ( 'md5' ) . update ( method + ":" + path ) . digest ( 'hex' ) ;
2017-08-28 12:27:45 -04:00
return crypto . createHash ( 'md5' ) . update ( ha1 + ":" + nonce + ":" + nc + ":" + cnonce + ":" + qop + ":" + ha2 ) . digest ( "hex" ) ;
2018-08-29 20:40:30 -04:00
} ;
2017-08-28 12:27:45 -04:00
2018-08-29 20:40:30 -04:00
module . exports . toNumber = function ( str ) { var x = parseInt ( str ) ; if ( x == str ) return x ; return str ; } ;
module . exports . escapeHtml = function ( string ) { return String ( string ) . replace ( /[&<>"'`=\/]/g , function ( s ) { return { '&' : '&' , '<' : '<' , '>' : '>' , '"' : '"' , "'" : ''' , '/' : '/' , '`' : '`' , '=' : '=' } [ s ] ; } ) ; } ;
module . exports . escapeHtmlBreaks = function ( string ) { return String ( string ) . replace ( /[&<>"'`=\/]/g , function ( s ) { return { '&' : '&' , '<' : '<' , '>' : '>' , '"' : '"' , "'" : ''' , '/' : '/' , '`' : '`' , '=' : '=' , '\r' : '<br />' , '\n' : '' } [ s ] ; } ) ; } ;
2019-08-11 01:34:21 -04:00
module . exports . zeroPad = function ( num , c ) { if ( c == null ) { c = 2 ; } var s = "000000" + num ; return s . substr ( s . length - c ) ; }
2018-01-04 18:59:57 -05:00
// Lowercase all the names in a object recursively
2019-04-11 16:41:51 -04:00
// Allow for exception keys, child of exceptions will not get lower-cased.
module . exports . objKeysToLower = function ( obj , exceptions ) {
2018-01-04 18:59:57 -05:00
for ( var i in obj ) {
2019-04-11 16:41:51 -04:00
if ( ( typeof obj [ i ] == 'object' ) && ( ( exceptions == null ) || ( exceptions . indexOf ( i . toLowerCase ( ) ) == - 1 ) ) ) { module . exports . objKeysToLower ( obj [ i ] , exceptions ) ; } // LowerCase all key names in the child object
2019-05-27 14:58:31 -04:00
if ( i . toLowerCase ( ) !== i ) { obj [ i . toLowerCase ( ) ] = obj [ i ] ; delete obj [ i ] ; } // LowerCase all key names
2018-08-29 20:40:30 -04:00
}
2018-01-04 18:59:57 -05:00
return obj ;
2018-08-29 20:40:30 -04:00
} ;
2018-04-05 19:45:56 -04:00
2018-07-13 22:18:43 -04:00
// Escape and unexcape feild names so there are no invalid characters for MongoDB
2019-10-11 18:14:38 -04:00
module . exports . escapeFieldName = function ( name ) { if ( ( name . indexOf ( '%' ) == - 1 ) && ( name . indexOf ( '.' ) == - 1 ) && ( name . indexOf ( '$' ) == - 1 ) ) return name ; return name . split ( '%' ) . join ( '%25' ) . split ( '.' ) . join ( '%2E' ) . split ( '$' ) . join ( '%24' ) ; } ;
module . exports . unEscapeFieldName = function ( name ) { if ( name . indexOf ( '%' ) == - 1 ) return name ; return name . split ( '%2E' ) . join ( '.' ) . split ( '%24' ) . join ( '$' ) . split ( '%25' ) . join ( '%' ) ; } ;
2018-07-13 22:18:43 -04:00
// Escape all links
2019-10-11 18:14:38 -04:00
module . exports . escapeLinksFieldName = function ( docx ) { var doc = Object . assign ( { } , docx ) ; if ( doc . links != null ) { doc . links = Object . assign ( { } , doc . links ) ; for ( var i in doc . links ) { var ue = module . exports . escapeFieldName ( i ) ; if ( ue !== i ) { doc . links [ ue ] = doc . links [ i ] ; delete doc . links [ i ] ; } } } return doc ; } ;
2018-08-29 20:40:30 -04:00
module . exports . unEscapeLinksFieldName = function ( doc ) { if ( doc . links != null ) { for ( var j in doc . links ) { var ue = module . exports . unEscapeFieldName ( j ) ; if ( ue !== j ) { doc . links [ ue ] = doc . links [ j ] ; delete doc . links [ j ] ; } } } return doc ; } ;
2019-12-30 01:38:53 -05:00
//module.exports.escapeAllLinksFieldName = function (docs) { for (var i in docs) { module.exports.escapeLinksFieldName(docs[i]); } return docs; };
module . exports . unEscapeAllLinksFieldName = function ( docs ) { for ( var i in docs ) { docs [ i ] = module . exports . unEscapeLinksFieldName ( docs [ i ] ) ; } return docs ; } ;
2018-07-13 22:18:43 -04:00
2018-04-05 19:45:56 -04:00
// Validation methods
2018-08-29 20:40:30 -04:00
module . exports . validateString = function ( str , minlen , maxlen ) { return ( ( str != null ) && ( typeof str == 'string' ) && ( ( minlen == null ) || ( str . length >= minlen ) ) && ( ( maxlen == null ) || ( str . length <= maxlen ) ) ) ; } ;
module . exports . validateInt = function ( int , minval , maxval ) { return ( ( int != null ) && ( typeof int == 'number' ) && ( ( minval == null ) || ( int >= minval ) ) && ( ( maxval == null ) || ( int <= maxval ) ) ) ; } ;
module . exports . validateArray = function ( array , minlen , maxlen ) { return ( ( array != null ) && Array . isArray ( array ) && ( ( minlen == null ) || ( array . length >= minlen ) ) && ( ( maxlen == null ) || ( array . length <= maxlen ) ) ) ; } ;
module . exports . validateStrArray = function ( array , minlen , maxlen ) { if ( ( ( array != null ) && Array . isArray ( array ) ) == false ) return false ; for ( var i in array ) { if ( ( typeof array [ i ] != 'string' ) && ( ( minlen == null ) || ( array [ i ] . length >= minlen ) ) && ( ( maxlen == null ) || ( array [ i ] . length <= maxlen ) ) ) return false ; } return true ; } ;
module . exports . validateObject = function ( obj ) { return ( ( obj != null ) && ( typeof obj == 'object' ) ) ; } ;
2018-10-16 17:51:54 -04:00
module . exports . validateEmail = function ( email , minlen , maxlen ) { if ( module . exports . validateString ( email , minlen , maxlen ) == false ) return false ; var emailReg = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ ; return emailReg . test ( email ) ; } ;
2019-05-30 15:40:10 -04:00
module . exports . validateUsername = function ( username , minlen , maxlen ) { return ( module . exports . validateString ( username , minlen , maxlen ) && ( username . indexOf ( ' ' ) == - 1 ) && ( username . indexOf ( '"' ) == - 1 ) && ( username . indexOf ( ',' ) == - 1 ) ) ; } ;
2018-12-20 17:14:37 -05:00
// Check password requirements
module . exports . checkPasswordRequirements = function ( password , requirements ) {
if ( ( requirements == null ) || ( requirements == '' ) || ( typeof requirements != 'object' ) ) return true ;
if ( requirements . min ) { if ( password . length < requirements . min ) return false ; }
if ( requirements . max ) { if ( password . length > requirements . max ) return false ; }
var num = 0 , lower = 0 , upper = 0 , nonalpha = 0 ;
for ( var i = 0 ; i < password . length ; i ++ ) {
if ( /\d/ . test ( password [ i ] ) ) { num ++ ; }
if ( /[a-z]/ . test ( password [ i ] ) ) { lower ++ ; }
if ( /[A-Z]/ . test ( password [ i ] ) ) { upper ++ ; }
if ( /\W/ . test ( password [ i ] ) ) { nonalpha ++ ; }
}
if ( requirements . num && ( num < requirements . num ) ) return false ;
if ( requirements . lower && ( lower < requirements . lower ) ) return false ;
if ( requirements . upper && ( upper < requirements . upper ) ) return false ;
if ( requirements . nonalpha && ( nonalpha < requirements . nonalpha ) ) return false ;
return true ;
2019-01-05 15:04:18 -05:00
}
2019-01-28 18:47:54 -05:00
// Limits the number of tasks running to a fixed limit placing the rest in a pending queue.
// This is useful to limit the number of agents upgrading at the same time, to not swamp
// the network with traffic.
2019-02-08 17:17:35 -05:00
// taskLimiterQueue.launch(somethingToDo, argument, priority);
2019-01-28 18:47:54 -05:00
//
// function somethingToDo(argument, taskid, taskLimiterQueue) {
// setTimeout(function () { taskLimiterQueue.completed(taskid); }, Math.random() * 2000);
// }
2019-02-08 17:17:35 -05:00
module . exports . createTaskLimiterQueue = function ( maxTasks , maxTaskTime , cleaningInterval ) {
var obj = { maxTasks : maxTasks , maxTaskTime : ( maxTaskTime * 1000 ) , nextTaskId : 0 , currentCount : 0 , current : { } , pending : [ [ ] , [ ] , [ ] ] , timer : null } ;
2019-01-28 18:47:54 -05:00
// Add a task to the super queue
2019-02-08 17:17:35 -05:00
// Priority: 0 = High, 1 = Medium, 2 = Low
obj . launch = function ( func , arg , pri ) {
if ( typeof pri != 'number' ) { pri = 2 ; }
2019-01-28 18:47:54 -05:00
if ( obj . currentCount < obj . maxTasks ) {
// Run this task now
const id = obj . nextTaskId ++ ;
obj . current [ id ] = Date . now ( ) + obj . maxTaskTime ;
obj . currentCount ++ ;
//console.log('ImmidiateLaunch ' + id);
func ( arg , id , obj ) ; // Start the task
if ( obj . timer == null ) { obj . timer = setInterval ( obj . clean , cleaningInterval * 1000 ) ; }
} else {
// Hold this task
//console.log('Holding');
2019-02-08 17:17:35 -05:00
obj . pending [ pri ] . push ( { func : func , arg : arg } ) ;
2019-01-28 18:47:54 -05:00
}
}
// Called when a task is completed
obj . completed = function ( taskid ) {
//console.log('Completed ' + taskid);
if ( obj . current [ taskid ] ) { delete obj . current [ taskid ] ; obj . currentCount -- ; } else { return ; }
2019-02-08 17:17:35 -05:00
while ( ( obj . currentCount < obj . maxTasks ) && ( ( obj . pending [ 0 ] . length > 0 ) || ( obj . pending [ 1 ] . length > 0 ) || ( obj . pending [ 2 ] . length > 0 ) ) ) {
2019-01-28 18:47:54 -05:00
// Run this task now
2019-02-08 17:17:35 -05:00
var t = null ;
if ( obj . pending [ 0 ] . length > 0 ) { t = obj . pending [ 0 ] . shift ( ) ; }
else if ( obj . pending [ 1 ] . length > 0 ) { t = obj . pending [ 1 ] . shift ( ) ; }
else if ( obj . pending [ 2 ] . length > 0 ) { t = obj . pending [ 2 ] . shift ( ) ; }
const id = obj . nextTaskId ++ ;
2019-01-28 18:47:54 -05:00
obj . current [ id ] = Date . now ( ) + obj . maxTaskTime ;
obj . currentCount ++ ;
//console.log('PendingLaunch ' + id);
t . func ( t . arg , id , obj ) ; // Start the task
}
2019-02-25 17:35:08 -05:00
if ( ( obj . currentCount == 0 ) && ( obj . pending [ 0 ] . length == 0 ) && ( obj . pending [ 1 ] . length == 0 ) && ( obj . pending [ 2 ] . length == 0 ) && ( obj . timer != null ) ) {
2019-02-08 17:17:35 -05:00
// All done, clear the timer
clearInterval ( obj . timer ) ; obj . timer = null ;
}
2019-01-28 18:47:54 -05:00
}
// Look for long standing tasks and clean them up
obj . clean = function ( ) {
const t = Date . now ( ) ;
for ( var i in obj . current ) { if ( obj . current [ i ] < t ) { obj . completed ( parseInt ( i ) ) ; } }
}
return obj ;
2019-02-08 17:17:35 -05:00
}
2020-01-07 18:10:12 -05:00
// Convert string translations to a standardized JSON we can use in GitHub
// Strings are sorder by english source and object keys are sorted
module . exports . translationsToJson = function ( t ) {
var arr2 = [ ] , arr = t . strings ;
for ( var i in arr ) {
var names = [ ] , el = arr [ i ] , el2 = { } ;
for ( var j in el ) { names . push ( j ) ; }
2020-03-22 16:33:21 -04:00
names . sort ( function ( a , b ) { if ( a == b ) { return 0 ; } if ( a == 'xloc' ) { return 1 ; } if ( b == 'xloc' ) { return - 1 ; } return a - b } ) ;
2020-01-07 18:10:12 -05:00
for ( var j in names ) { el2 [ names [ j ] ] = el [ names [ j ] ] ; }
2020-01-07 20:12:09 -05:00
if ( el2 . xloc != null ) { el2 . xloc . sort ( ) ; }
2020-01-07 18:10:12 -05:00
arr2 . push ( el2 ) ;
}
arr2 . sort ( function ( a , b ) { if ( a . en > b . en ) return 1 ; if ( a . en < b . en ) return - 1 ; return 0 ; } ) ;
return JSON . stringify ( { strings : arr2 } , null , ' ' ) ;
2020-01-31 17:44:11 -05:00
}
module . exports . copyFile = function ( source , target , cb ) {
var cbCalled = false , rd = fs . createReadStream ( source ) ;
rd . on ( 'error' , function ( err ) { done ( err ) ; } ) ;
var wr = fs . createWriteStream ( target ) ;
wr . on ( 'error' , function ( err ) { done ( err ) ; } ) ;
wr . on ( 'close' , function ( ex ) { done ( ) ; } ) ;
rd . pipe ( wr ) ;
function done ( err ) { if ( ! cbCalled ) { cb ( err ) ; cbCalled = true ; } }
2020-01-07 18:10:12 -05:00
}