common.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. /**
  2. * @description MeshCentral Common Library
  3. * @author Ylian Saint-Hilaire
  4. * @copyright Intel Corporation 2018-2022
  5. * @license Apache-2.0
  6. * @version v0.0.1
  7. */
  8. /*xjslint node: true */
  9. /*xjslint plusplus: true */
  10. /*xjslint maxlen: 256 */
  11. /*jshint node: true */
  12. /*jshint strict: false */
  13. /*jshint esversion: 6 */
  14. 'use strict';
  15. const fs = require('fs');
  16. const crypto = require('crypto');
  17. const path = require('path');
  18. // Binary encoding and decoding functions
  19. module.exports.ReadShort = function (v, p) { return (v.charCodeAt(p) << 8) + v.charCodeAt(p + 1); };
  20. module.exports.ReadShortX = function (v, p) { return (v.charCodeAt(p + 1) << 8) + v.charCodeAt(p); };
  21. 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.
  22. 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); };
  23. module.exports.ShortToStr = function (v) { return String.fromCharCode((v >> 8) & 0xFF, v & 0xFF); };
  24. module.exports.ShortToStrX = function (v) { return String.fromCharCode(v & 0xFF, (v >> 8) & 0xFF); };
  25. module.exports.IntToStr = function (v) { return String.fromCharCode((v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF); };
  26. module.exports.IntToStrX = function (v) { return String.fromCharCode(v & 0xFF, (v >> 8) & 0xFF, (v >> 16) & 0xFF, (v >> 24) & 0xFF); };
  27. module.exports.MakeToArray = function (v) { if (!v || v == null || typeof v == 'object') return v; return [v]; };
  28. module.exports.SplitArray = function (v) { return v.split(','); };
  29. module.exports.Clone = function (v) { return JSON.parse(JSON.stringify(v)); };
  30. 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] != '.'); }; })();
  31. 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(''); }
  32. module.exports.joinPath = function (base, path_) { return path.isAbsolute(path_) ? path_ : path.join(base, path_); }
  33. // Move an element from one position in an array to a new position
  34. module.exports.ArrayElementMove = function(arr, from, to) { arr.splice(to, 0, arr.splice(from, 1)[0]); };
  35. // Format a string with arguments, "replaces {0} and {1}..."
  36. 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; }); };
  37. // Print object for HTML
  38. module.exports.ObjectToStringEx = function (x, c) {
  39. var r = '', i;
  40. if (x != 0 && (!x || x == null)) return "(Null)";
  41. if (x instanceof Array) { for (i in x) { r += '<br />' + gap(c) + "Item #" + i + ": " + module.exports.ObjectToStringEx(x[i], c + 1); } }
  42. else if (x instanceof Object) { for (i in x) { r += '<br />' + gap(c) + i + " = " + module.exports.ObjectToStringEx(x[i], c + 1); } }
  43. else { r += x; }
  44. return r;
  45. };
  46. // Print object for console
  47. module.exports.ObjectToStringEx2 = function (x, c) {
  48. var r = '', i;
  49. if (x != 0 && (!x || x == null)) return "(Null)";
  50. if (x instanceof Array) { for (i in x) { r += '\r\n' + gap2(c) + "Item #" + i + ": " + module.exports.ObjectToStringEx2(x[i], c + 1); } }
  51. else if (x instanceof Object) { for (i in x) { r += '\r\n' + gap2(c) + i + " = " + module.exports.ObjectToStringEx2(x[i], c + 1); } }
  52. else { r += x; }
  53. return r;
  54. };
  55. // Create an ident gap
  56. module.exports.gap = function (c) { var x = ''; for (var i = 0; i < (c * 4); i++) { x += '&nbsp;'; } return x; };
  57. module.exports.gap2 = function (c) { var x = ''; for (var i = 0; i < (c * 4); i++) { x += ' '; } return x; };
  58. // Print an object in html
  59. module.exports.ObjectToString = function (x) { return module.exports.ObjectToStringEx(x, 0); };
  60. module.exports.ObjectToString2 = function (x) { return module.exports.ObjectToStringEx2(x, 0); };
  61. // Convert a hex string to a raw string
  62. module.exports.hex2rstr = function (d) {
  63. var r = '', m = ('' + d).match(/../g), t;
  64. while (t = m.shift()) { r += String.fromCharCode('0x' + t); }
  65. return r;
  66. };
  67. // Convert decimal to hex
  68. module.exports.char2hex = function (i) { return (i + 0x100).toString(16).substr(-2).toUpperCase(); };
  69. // Convert a raw string to a hex string
  70. module.exports.rstr2hex = function (input) {
  71. var r = '', i;
  72. for (i = 0; i < input.length; i++) { r += module.exports.char2hex(input.charCodeAt(i)); }
  73. return r;
  74. };
  75. // UTF-8 encoding & decoding functions
  76. module.exports.encode_utf8 = function (s) { return unescape(encodeURIComponent(s)); };
  77. module.exports.decode_utf8 = function (s) { return decodeURIComponent(escape(s)); };
  78. // Convert a string into a blob
  79. module.exports.data2blob = function (data) {
  80. var bytes = new Array(data.length);
  81. for (var i = 0; i < data.length; i++) bytes[i] = data.charCodeAt(i);
  82. var blob = new Blob([new Uint8Array(bytes)]);
  83. return blob;
  84. };
  85. // Generate random numbers between 0 and max without bias.
  86. module.exports.random = function (max) {
  87. const crypto = require('crypto');
  88. var maxmask = 1, r;
  89. while (maxmask < max) { maxmask = (maxmask << 1) + 1; }
  90. do { r = (crypto.randomBytes(4).readUInt32BE(0) & maxmask); } while (r > max);
  91. return r;
  92. };
  93. // Split a comma separated string, ignoring commas in quotes.
  94. module.exports.quoteSplit = function (str) {
  95. var tmp = '', quote = 0, result = [];
  96. 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]; } }
  97. if (tmp.length > 0) result.push(tmp.trim());
  98. return result;
  99. };
  100. // Convert list of "name = value" into object
  101. module.exports.parseNameValueList = function (list) {
  102. var result = [];
  103. for (var i in list) {
  104. var j = list[i].indexOf('=');
  105. if (j > 0) {
  106. var v = list[i].substring(j + 1).trim();
  107. if ((v[0] == '"') && (v[v.length - 1] == '"')) { v = v.substring(1, v.length - 1); }
  108. result[list[i].substring(0, j).trim()] = v;
  109. }
  110. }
  111. return result;
  112. };
  113. // Compute the MD5 digest hash for a set of values
  114. module.exports.ComputeDigesthash = function (username, password, realm, method, path, qop, nonce, nc, cnonce) {
  115. var ha1 = crypto.createHash('md5').update(username + ":" + realm + ":" + password).digest('hex');
  116. var ha2 = crypto.createHash('md5').update(method + ":" + path).digest('hex');
  117. return crypto.createHash('md5').update(ha1 + ":" + nonce + ":" + nc + ":" + cnonce + ":" + qop + ":" + ha2).digest("hex");
  118. };
  119. module.exports.toNumber = function (str) { var x = parseInt(str); if (x == str) return x; return str; };
  120. module.exports.escapeHtml = function (string) { return String(string).replace(/[&<>"'`=\/]/g, function (s) { return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;', '/': '&#x2F;', '`': '&#x60;', '=': '&#x3D;' }[s]; }); };
  121. module.exports.escapeHtmlBreaks = function (string) { return String(string).replace(/[&<>"'`=\/]/g, function (s) { return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;', '/': '&#x2F;', '`': '&#x60;', '=': '&#x3D;', '\r': '<br />', '\n': '' }[s]; }); };
  122. module.exports.zeroPad = function(num, c) { if (c == null) { c = 2; } var s = '000000' + num; return s.substr(s.length - c); }
  123. // Lowercase all the names in a object recursively
  124. // Allow for exception keys, child of exceptions will not get lower-cased.
  125. // Exceptions is an array of "keyname" or "parent\keyname"
  126. module.exports.objKeysToLower = function (obj, exceptions, parent) {
  127. for (var i in obj) {
  128. if ((typeof obj[i] == 'object') &&
  129. ((exceptions == null) || (exceptions.indexOf(i.toLowerCase()) == -1) && ((parent == null) || (exceptions.indexOf(parent.toLowerCase() + '/' + i.toLowerCase()) == -1)))
  130. ) {
  131. module.exports.objKeysToLower(obj[i], exceptions, i); // LowerCase all key names in the child object
  132. }
  133. if (i.toLowerCase() !== i) { obj[i.toLowerCase()] = obj[i]; delete obj[i]; } // LowerCase all key names
  134. }
  135. return obj;
  136. };
  137. // Escape and unescape field names so there are no invalid characters for MongoDB
  138. 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'); };
  139. module.exports.unEscapeFieldName = function (name) { if (name.indexOf('%') == -1) return name; return name.split('%2E').join('.').split('%24').join('$').split('%25').join('%'); };
  140. // Escape all links, SSH and RDP usernames
  141. // This is required for databases like NeDB that don't accept "." as part of a field name.
  142. module.exports.escapeLinksFieldNameEx = function (docx) { if ((docx.links == null) && (docx.ssh == null) && (docx.rdp == null)) { return docx; } return module.exports.escapeLinksFieldName(docx); };
  143. module.exports.escapeLinksFieldName = function (docx) {
  144. var doc = Object.assign({}, docx);
  145. 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]; } } }
  146. if (doc.ssh != null) { doc.ssh = Object.assign({}, doc.ssh); for (var i in doc.ssh) { var ue = module.exports.escapeFieldName(i); if (ue !== i) { doc.ssh[ue] = doc.ssh[i]; delete doc.ssh[i]; } } }
  147. if (doc.rdp != null) { doc.rdp = Object.assign({}, doc.rdp); for (var i in doc.rdp) { var ue = module.exports.escapeFieldName(i); if (ue !== i) { doc.rdp[ue] = doc.rdp[i]; delete doc.rdp[i]; } } }
  148. return doc;
  149. };
  150. module.exports.unEscapeLinksFieldName = function (doc) {
  151. 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]; } } }
  152. if (doc.ssh != null) { for (var j in doc.ssh) { var ue = module.exports.unEscapeFieldName(j); if (ue !== j) { doc.ssh[ue] = doc.ssh[j]; delete doc.ssh[j]; } } }
  153. if (doc.rdp != null) { for (var j in doc.rdp) { var ue = module.exports.unEscapeFieldName(j); if (ue !== j) { doc.rdp[ue] = doc.rdp[j]; delete doc.rdp[j]; } } }
  154. return doc;
  155. };
  156. //module.exports.escapeAllLinksFieldName = function (docs) { for (var i in docs) { module.exports.escapeLinksFieldName(docs[i]); } return docs; };
  157. module.exports.unEscapeAllLinksFieldName = function (docs) { for (var i in docs) { docs[i] = module.exports.unEscapeLinksFieldName(docs[i]); } return docs; };
  158. // Escape field names for aceBase
  159. var aceEscFields = ['links', 'ssh', 'rdp', 'notify'];
  160. module.exports.aceEscapeFieldNames = function (docx) { var doc = Object.assign({}, docx); for (var k in aceEscFields) { if (typeof doc[aceEscFields[k]] == 'object') { doc[aceEscFields[k]] = Object.assign({}, doc[aceEscFields[k]]); for (var i in doc[aceEscFields[k]]) { var ue = encodeURIComponent(i); if (ue !== i) { doc[aceEscFields[k]][ue] = doc[aceEscFields[k]][i]; delete doc[aceEscFields[k]][i]; } } } } return doc; };
  161. module.exports.aceUnEscapeFieldNames = function (doc) { for (var k in aceEscFields) { if (typeof doc[aceEscFields[k]] == 'object') { for (var j in doc[aceEscFields[k]]) { var ue = decodeURIComponent(j); if (ue !== j) { doc[aceEscFields[k]][ue] = doc[aceEscFields[k]][j]; delete doc[aceEscFields[k]][j]; } } } } return doc; };
  162. module.exports.aceUnEscapeAllFieldNames = function (docs) { for (var i in docs) { docs[i] = module.exports.aceUnEscapeFieldNames(docs[i]); } return docs; };
  163. // Validation methods
  164. module.exports.validateString = function (str, minlen, maxlen) { return ((str != null) && (typeof str == 'string') && ((minlen == null) || (str.length >= minlen)) && ((maxlen == null) || (str.length <= maxlen))); };
  165. module.exports.validateInt = function (int, minval, maxval) { return ((int != null) && (typeof int == 'number') && ((minval == null) || (int >= minval)) && ((maxval == null) || (int <= maxval))); };
  166. module.exports.validateArray = function (array, minlen, maxlen) { return ((array != null) && Array.isArray(array) && ((minlen == null) || (array.length >= minlen)) && ((maxlen == null) || (array.length <= maxlen))); };
  167. 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; };
  168. module.exports.validateObject = function (obj) { return ((obj != null) && (typeof obj == 'object')); };
  169. 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); };
  170. module.exports.validateUsername = function (username, minlen, maxlen) { return (module.exports.validateString(username, minlen, maxlen) && (username.indexOf(' ') == -1) && (username.indexOf('"') == -1) && (username.indexOf(',') == -1)); };
  171. module.exports.isAlphaNumeric = function (str) { return (str.match(/^[A-Za-z0-9]+$/) != null); };
  172. module.exports.validateAlphaNumericArray = function (array, minlen, maxlen) { if (((array != null) && Array.isArray(array)) == false) return false; for (var i in array) { if ((typeof array[i] != 'string') || (module.exports.isAlphaNumeric(array[i]) == false) || ((minlen != null) && (array[i].length < minlen)) || ((maxlen != null) && (array[i].length > maxlen)) ) return false; } return true; };
  173. module.exports.getEmailDomain = function(email) {
  174. if (!module.exports.validateEmail(email, 1, 1024)) {
  175. return '';
  176. }
  177. const i = email.indexOf('@');
  178. return email.substring(i + 1).toLowerCase();
  179. }
  180. module.exports.validateEmailDomain = function(email, allowedDomains) {
  181. // Check if this request is for an allows email domain
  182. if ((allowedDomains != null) && Array.isArray(allowedDomains)) {
  183. const emaildomain = module.exports.getEmailDomain(email);
  184. if (emaildomain === '') {
  185. return false;
  186. }
  187. var emailok = false;
  188. for (var i in allowedDomains) { if (emaildomain == allowedDomains[i].toLowerCase()) { emailok = true; } }
  189. return emailok;
  190. }
  191. return true;
  192. }
  193. // Check password requirements
  194. module.exports.checkPasswordRequirements = function(password, requirements) {
  195. if ((requirements == null) || (requirements == '') || (typeof requirements != 'object')) return true;
  196. if (requirements.min) { if (password.length < requirements.min) return false; }
  197. if (requirements.max) { if (password.length > requirements.max) return false; }
  198. var numeric = 0, lower = 0, upper = 0, nonalpha = 0;
  199. for (var i = 0; i < password.length; i++) {
  200. if (/\d/.test(password[i])) { numeric++; }
  201. if (/[a-z]/.test(password[i])) { lower++; }
  202. if (/[A-Z]/.test(password[i])) { upper++; }
  203. if (/\W/.test(password[i])) { nonalpha++; }
  204. }
  205. if (requirements.numeric && (numeric < requirements.numeric)) return false;
  206. if (requirements.lower && (lower < requirements.lower)) return false;
  207. if (requirements.upper && (upper < requirements.upper)) return false;
  208. if (requirements.nonalpha && (nonalpha < requirements.nonalpha)) return false;
  209. return true;
  210. }
  211. // Limits the number of tasks running to a fixed limit placing the rest in a pending queue.
  212. // This is useful to limit the number of agents upgrading at the same time, to not swamp
  213. // the network with traffic.
  214. module.exports.createTaskLimiterQueue = function (maxTasks, maxTaskTime, cleaningInterval) {
  215. var obj = { maxTasks: maxTasks, maxTaskTime: (maxTaskTime * 1000), nextTaskId: 0, currentCount: 0, current: {}, pending: [[], [], []], timer: null };
  216. // Add a task to the super queue
  217. // Priority: 0 = High, 1 = Medium, 2 = Low
  218. obj.launch = function (func, arg, pri) {
  219. if (typeof pri != 'number') { pri = 2; }
  220. if (obj.currentCount < obj.maxTasks) {
  221. // Run this task now
  222. const id = obj.nextTaskId++;
  223. obj.current[id] = Date.now() + obj.maxTaskTime;
  224. obj.currentCount++;
  225. //console.log('ImmidiateLaunch ' + id);
  226. func(arg, id, obj); // Start the task
  227. if (obj.timer == null) { obj.timer = setInterval(obj.clean, cleaningInterval * 1000); }
  228. } else {
  229. // Hold this task
  230. //console.log('Holding');
  231. obj.pending[pri].push({ func: func, arg: arg });
  232. }
  233. }
  234. // Called when a task is completed
  235. obj.completed = function (taskid) {
  236. //console.log('Completed ' + taskid);
  237. if (obj.current[taskid]) { delete obj.current[taskid]; obj.currentCount--; } else { return; }
  238. while ((obj.currentCount < obj.maxTasks) && ((obj.pending[0].length > 0) || (obj.pending[1].length > 0) || (obj.pending[2].length > 0))) {
  239. // Run this task now
  240. var t = null;
  241. if (obj.pending[0].length > 0) { t = obj.pending[0].shift(); }
  242. else if (obj.pending[1].length > 0) { t = obj.pending[1].shift(); }
  243. else if (obj.pending[2].length > 0) { t = obj.pending[2].shift(); }
  244. const id = obj.nextTaskId++;
  245. obj.current[id] = Date.now() + obj.maxTaskTime;
  246. obj.currentCount++;
  247. //console.log('PendingLaunch ' + id);
  248. t.func(t.arg, id, obj); // Start the task
  249. }
  250. if ((obj.currentCount == 0) && (obj.pending[0].length == 0) && (obj.pending[1].length == 0) && (obj.pending[2].length == 0) && (obj.timer != null)) {
  251. // All done, clear the timer
  252. clearInterval(obj.timer); obj.timer = null;
  253. }
  254. }
  255. // Look for long standing tasks and clean them up
  256. obj.clean = function () {
  257. const t = Date.now();
  258. for (var i in obj.current) { if (obj.current[i] < t) { obj.completed(parseInt(i)); } }
  259. }
  260. return obj;
  261. }
  262. // Convert string translations to a standardized JSON we can use in GitHub
  263. // Strings are sorder by english source and object keys are sorted
  264. module.exports.translationsToJson = function(t) {
  265. var arr2 = [], arr = t.strings;
  266. for (var i in arr) {
  267. var names = [], el = arr[i], el2 = {};
  268. for (var j in el) { names.push(j); }
  269. names.sort(function (a, b) { if (a == b) { return 0; } if (a == 'xloc') { return 1; } if (b == 'xloc') { return -1; } return a - b });
  270. for (var j in names) { el2[names[j]] = el[names[j]]; }
  271. if (el2.xloc != null) { el2.xloc.sort(); }
  272. arr2.push(el2);
  273. }
  274. arr2.sort(function (a, b) { if (a.en > b.en) return 1; if (a.en < b.en) return -1; return 0; });
  275. return JSON.stringify({ strings: arr2 }, null, ' ');
  276. }
  277. module.exports.copyFile = function(source, target, cb) {
  278. var cbCalled = false, rd = fs.createReadStream(source);
  279. rd.on('error', function (err) { done(err); });
  280. var wr = fs.createWriteStream(target);
  281. wr.on('error', function (err) { done(err); });
  282. wr.on('close', function (ex) { done(); });
  283. rd.pipe(wr);
  284. function done(err) { if (!cbCalled) { cb(err); cbCalled = true; } }
  285. }
  286. module.exports.meshServerRightsArrayToNumber = function (val) {
  287. if (val == null) return null;
  288. if (typeof val == 'number') return val;
  289. if (Array.isArray(val)) {
  290. var newAccRights = 0;
  291. for (var j in val) {
  292. var r = val[j].toLowerCase();
  293. if (r == 'fulladmin') { newAccRights = 4294967295; } // 0xFFFFFFFF
  294. if (r == 'serverbackup') { newAccRights |= 1; }
  295. if (r == 'manageusers') { newAccRights |= 2; }
  296. if (r == 'serverrestore') { newAccRights |= 4; }
  297. if (r == 'fileaccess') { newAccRights |= 8; }
  298. if (r == 'serverupdate') { newAccRights |= 16; }
  299. if (r == 'locked') { newAccRights |= 32; }
  300. if (r == 'nonewgroups') { newAccRights |= 64; }
  301. if (r == 'notools') { newAccRights |= 128; }
  302. if (r == 'usergroups') { newAccRights |= 256; }
  303. if (r == 'recordings') { newAccRights |= 512; }
  304. if (r == 'locksettings') { newAccRights |= 1024; }
  305. if (r == 'allevents') { newAccRights |= 2048; }
  306. if (r == 'nonewdevices') { newAccRights |= 4096; }
  307. }
  308. return newAccRights;
  309. }
  310. return null;
  311. }
  312. // Sort an object by key
  313. module.exports.sortObj = function (obj) { return Object.keys(obj).sort().reduce(function (result, key) { result[key] = obj[key]; return result; }, {}); }
  314. // Validate an object to make sure it can be stored in MongoDB
  315. module.exports.validateObjectForMongo = function (obj, maxStrLen) {
  316. return validateObjectForMongoRec(obj, maxStrLen);
  317. }
  318. function validateObjectForMongoRec(obj, maxStrLen) {
  319. if (typeof obj != 'object') return false;
  320. for (var i in obj) {
  321. // Check the key name is not too long
  322. if (i.length > 100) return false;
  323. // Check if all chars are alpha-numeric or underscore.
  324. for (var j in i) { const c = i.charCodeAt(j); if ((c < 48) || ((c > 57) && (c < 65)) || ((c > 90) && (c < 97) && (c != 95)) || (c > 122)) return false; }
  325. // If the value is a string, check it's not too long
  326. if ((typeof obj[i] == 'string') && (obj[i].length > maxStrLen)) return false;
  327. // If the value is an object, check it.
  328. if ((typeof obj[i] == 'object') && (Array.isArray(obj[i]) == false) && (validateObjectForMongoRec(obj[i], maxStrLen) == false)) return false;
  329. }
  330. return true;
  331. }
  332. // Parse a version string of the type n.n.n.n
  333. module.exports.parseVersion = function (verstr) {
  334. if (typeof verstr != 'string') return null;
  335. const r = [], verstrsplit = verstr.split('.');
  336. if (verstrsplit.length != 4) return null;
  337. for (var i in verstrsplit) {
  338. var n = parseInt(verstrsplit[i]);
  339. if (isNaN(n) || (n < 0) || (n > 65535)) return null;
  340. r.push(n);
  341. }
  342. return r;
  343. }
  344. // Move old files. If we are about to overwrite a file, we can move if first just in case the change needs to be reverted
  345. module.exports.moveOldFiles = function (filelist) {
  346. // Fine an old extension that works for all files in the file list
  347. var oldFileExt, oldFileExtCount = 0, extOk;
  348. do {
  349. extOk = true;
  350. if (++oldFileExtCount == 1) { oldFileExt = '-old'; } else { oldFileExt = '-old' + oldFileExtCount; }
  351. for (var i in filelist) { if (fs.existsSync(filelist[i] + oldFileExt) == true) { extOk = false; } }
  352. } while (extOk == false);
  353. for (var i in filelist) { try { fs.renameSync(filelist[i], filelist[i] + oldFileExt); } catch (ex) { } }
  354. }
  355. // Convert strArray to Array, returns array if strArray or null if any other type
  356. module.exports.convertStrArray = function (object, split) {
  357. if (split && typeof object === 'string') {
  358. return object.split(split)
  359. } else if (typeof object === 'string') {
  360. return Array(object);
  361. } else if (Array.isArray(object)) {
  362. return object
  363. } else {
  364. return []
  365. }
  366. }
  367. module.exports.uniqueArray = function (a) {
  368. var seen = {};
  369. var out = [];
  370. var len = a.length;
  371. var j = 0;
  372. for(var i = 0; i < len; i++) {
  373. var item = a[i];
  374. if(seen[item] !== 1) {
  375. seen[item] = 1;
  376. out[j++] = item;
  377. }
  378. }
  379. return out;
  380. }