commons.utils.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668
  1. /*
  2. * Copyright 2018 Scytl Secure Electronic Voting SA
  3. *
  4. * All rights reserved
  5. *
  6. * See our extended copyright notice in *file 'Copyright.txt' which is part of this source code package
  7. */
  8. cryptolib.modules.commons = cryptolib.modules.commons || {};
  9. /** namespace commons/utils */
  10. cryptolib.modules.commons.utils = function(box) {
  11. 'use strict';
  12. box.commons = box.commons || {};
  13. /**
  14. * A module that holds utility functionalities.
  15. *
  16. * @exports commons/utils
  17. */
  18. box.commons.utils = {};
  19. var exceptions;
  20. cryptolib('commons.exceptions', function(box) {
  21. exceptions = box.commons.exceptions;
  22. });
  23. /**
  24. * Defines some useful functions for data manipulation and type conversion.
  25. *
  26. * @class
  27. */
  28. box.commons.utils.Converters = function() {};
  29. box.commons.utils.Converters.prototype = {
  30. /**
  31. * Base64 encodes some data.
  32. *
  33. * @function
  34. * @param {string}
  35. * data data to Base64 encode.
  36. * @param {number}
  37. * lineLength line length of encoding (No line breaks if not
  38. * specified).
  39. * @returns {string} base64 encoded data.
  40. */
  41. base64Encode: function(data, lineLength) {
  42. if (lineLength === undefined || lineLength === null) {
  43. return forge.util.encode64(data);
  44. } else {
  45. return forge.util.encode64(data, lineLength);
  46. }
  47. },
  48. /**
  49. * Base64 decodes some data.
  50. *
  51. * @function
  52. * @param {string}
  53. * dataB64 data to Base64 decode.
  54. * @returns {string} base64 decoded data.
  55. */
  56. base64Decode: function(dataB64) {
  57. return forge.util.decode64(dataB64);
  58. },
  59. /**
  60. * Converts a string to a collection of bytes.
  61. *
  62. * @function
  63. * @param {string}
  64. * str string to convert to collection of bytes.
  65. * @returns collection of bytes from string.
  66. */
  67. bytesFromString: function(str) {
  68. var bytes = [];
  69. for (var i = 0; i < str.length; i++) {
  70. bytes.push(str.charCodeAt(i));
  71. }
  72. return bytes;
  73. },
  74. /**
  75. * Converts a collection of bytes to a string.
  76. *
  77. * @function
  78. * @param {string}
  79. * bytes collection of bytes to convert to string.
  80. * @return {string} string from collection of bytes.
  81. */
  82. bytesToString: function(bytes) {
  83. var string = '';
  84. for (var i = 0; i < bytes.length; i++) {
  85. string += String.fromCharCode(bytes[i]);
  86. }
  87. return string;
  88. },
  89. /**
  90. * Converts a string to a hexadecimal string.
  91. *
  92. * @function
  93. * @param {string}
  94. * str String to convert.
  95. * @returns string string in hexadecimal format.
  96. */
  97. hexFromString: function(str) {
  98. return forge.util.bytesToHex(str);
  99. },
  100. /**
  101. * Converts a hexadecimal string to a string.
  102. *
  103. * @function
  104. * @param {string}
  105. * hexStr hexadecimal string to convert.
  106. * @returns {string} String from conversion.
  107. */
  108. hexToString: function(hexStr) {
  109. return forge.util.hexToBytes(hexStr);
  110. },
  111. /**
  112. * Converts a BigInteger object to a base64 string.
  113. *
  114. * @function
  115. * @param {object}
  116. * bigInteger forge.jsbn.BigInteger to convert.
  117. * @returns {string} base64 string from conversion.
  118. */
  119. base64FromBigInteger: function(bigInteger) {
  120. var array = new Uint8Array(bigInteger.toByteArray());
  121. return box.forge.util.binary.base64.encode(array);
  122. },
  123. /**
  124. * Converts a base64 string to a BigInteger object.
  125. *
  126. * @function
  127. * @param {string}
  128. * base64Str base64 string to convert.
  129. * @returns {object} forge.jsbn.BigInteger from conversion.
  130. */
  131. base64ToBigInteger: function(base64Str) {
  132. var buffer = this.base64Decode(base64Str);
  133. var hex = this.hexFromString(buffer);
  134. return new forge.jsbn.BigInteger(hex, 16);
  135. }
  136. };
  137. /**
  138. * Defines a set of bit operator utilities.
  139. *
  140. * @class
  141. */
  142. box.commons.utils.BitOperators = function() {};
  143. box.commons.utils.BitOperators.prototype = {
  144. /**
  145. * Bit-wise concatenates some data to some existing data.
  146. *
  147. * @function
  148. * @param {string}
  149. * data1 existing data, as string.
  150. * @param {string}
  151. * data2 data to concatenate, as string.
  152. * @returns {string} bit-wise concatenation of data, as string.
  153. */
  154. concatenate: function(data1, data2) {
  155. try {
  156. var converters = new box.commons.utils.Converters();
  157. var data1Bytes = converters.bytesFromString(data1);
  158. var data2Bytes = converters.bytesFromString(data2);
  159. var concatDataBytes = data1Bytes.concat(data2Bytes);
  160. return converters.bytesToString(concatDataBytes);
  161. } catch (error) {
  162. throw new exceptions.CryptoLibException(
  163. 'Data could not be bit-wise concatenated.', error);
  164. }
  165. },
  166. /**
  167. * Unpacks a data subset from a bitwise concatenation of data.
  168. *
  169. * @function
  170. * @param {string}
  171. * concatData bit-wise concatenation of data, as string.
  172. * @param {number}
  173. * offset offset of data subset in concatenation.
  174. * @param {number}
  175. * size size of data subset.
  176. * @returns {string} data subset, as string.
  177. */
  178. extract: function(concatData, offset, size) {
  179. try {
  180. var converters = new box.commons.utils.Converters();
  181. var concatDataBytes = converters.bytesFromString(concatData);
  182. var dataSubsetBytes = concatDataBytes.slice(offset, size);
  183. return converters.bytesToString(dataSubsetBytes);
  184. } catch (error) {
  185. throw new exceptions.CryptoLibException(
  186. 'Data could not be bit-wise extracted.', error);
  187. }
  188. }
  189. };
  190. /**
  191. * Defines a set of time utilities.
  192. *
  193. * @class
  194. */
  195. box.commons.utils.TimeUtils = function() {};
  196. box.commons.utils.TimeUtils.prototype = {
  197. /**
  198. * Generates a time stamp in Unix time, i.e. the number of seconds that
  199. * have elapsed since 00:00:00 Coordinated Universal Time (UTC), 1
  200. * January 1970.
  201. *
  202. * @function
  203. * @returns {number} time stamp in seconds, as integer.
  204. */
  205. unixTimeStamp: function() {
  206. try {
  207. return Math.round(new Date().getTime() / 1000);
  208. } catch (error) {
  209. throw new exceptions.CryptoLibException(
  210. 'Could not generate Unix timestamp.', error);
  211. }
  212. },
  213. /**
  214. * Generates a time stamp in Unix time, i.e. the number of seconds that
  215. * have elapsed since 00:00:00 Coordinated Universal Time (UTC), 1
  216. * January 1970. The value is returned as a floating point number.
  217. *
  218. * @function
  219. * @returns {number} time stamp in seconds, as floating point number.
  220. */
  221. unixTimeStampFloat: function() {
  222. try {
  223. return new Date().getTime() / 1000;
  224. } catch (error) {
  225. throw new exceptions.CryptoLibException(
  226. 'Could not generate Unix timestamp.', error);
  227. }
  228. }
  229. };
  230. /**
  231. * Defines a set of xpath utilities.
  232. *
  233. * @class
  234. * @param {string}
  235. * path path in XPathForm notation Eg.
  236. * <form><name>test</name></form> to '/form/name'
  237. */
  238. box.commons.utils.XPath = function(xml, path) {
  239. this.path = path;
  240. this.node = xml;
  241. var pathTree = path ? path.split('/') : [];
  242. for (var i = 0; i < pathTree.length; i++) {
  243. if (pathTree[i] && this.node) {
  244. var found = false;
  245. for (var j = 0; j < this.node.childNodes.length; j++) {
  246. var child = this.node.childNodes[j];
  247. // we found the node
  248. if (child.nodeType === Node.ELEMENT_NODE &&
  249. child.tagName.toLowerCase() === pathTree[i].toLowerCase()) {
  250. this.node = child;
  251. found = true;
  252. break;
  253. }
  254. }
  255. // invalid xpath
  256. if (!found) {
  257. this.node = null;
  258. throw new exceptions.CryptoLibException('Invalid xpath for: ' + path);
  259. }
  260. }
  261. }
  262. };
  263. box.commons.utils.XPath.prototype = {
  264. /**
  265. * Gets the value of a node. For example <node>value</node> returns
  266. * value.
  267. *
  268. * @function
  269. * @returns {string} the value, as string.
  270. */
  271. getValue: function() {
  272. return this.node ? this.node.childNodes[0].nodeValue : '';
  273. },
  274. /**
  275. * Returns the value of an attribute of a node. For example <node
  276. * attr="attr value">node value</node> returns "attr value".
  277. *
  278. * @function
  279. * @param {object}
  280. * attribute attribute to get the value.
  281. * @returns attribute value, as string.
  282. */
  283. getAttribute: function(attribute) {
  284. return this.node ? this.node.getAttribute(attribute) : '';
  285. },
  286. /**
  287. * Gets the node itself-
  288. *
  289. * @function
  290. * @returns {object} xml document, as a DOM object.
  291. */
  292. getXml: function() {
  293. return this.node;
  294. },
  295. /**
  296. * Gets an array of child nodes
  297. *
  298. * @function
  299. * @returns {object} xml document array with all children as DOM
  300. * objects.
  301. */
  302. getChildren: function() {
  303. return this.node ? this.node.childNodes : '';
  304. }
  305. };
  306. /**
  307. * Defines a set of string utilities.
  308. *
  309. * @class
  310. */
  311. box.commons.utils.StringUtils = function() {
  312. };
  313. box.commons.utils.StringUtils.prototype = {
  314. /**
  315. * Check if a string is within another string
  316. *
  317. * @function
  318. * @param {string}
  319. * main text
  320. * @param {string}
  321. * substring substring to search in the main text
  322. * @param caseSensitive
  323. * boolean, case sensitive the search
  324. *
  325. * @returns {boolean} boolean, true if found
  326. */
  327. containsSubString: function(string, substring, caseSensitive) {
  328. var main = string, sub = substring;
  329. if (!caseSensitive) {
  330. main = main.toLowerCase();
  331. sub = sub.toLowerCase();
  332. }
  333. return main.indexOf(sub) > -1;
  334. }
  335. };
  336. /**
  337. * Defines a set of parser utilities.
  338. *
  339. * @class
  340. */
  341. box.commons.utils.Parsers = function() {};
  342. box.commons.utils.Parsers.prototype = {
  343. /**
  344. * Converts a string to an XML document.
  345. *
  346. * @function
  347. * @param {string}
  348. * str String to convert.
  349. * @returns {string} XML document, as string.
  350. */
  351. xmlFromString: function(str) {
  352. if (this.isIE()) {
  353. // Remove null terminating character if exists
  354. var strNoNull = this.removeNullTerminatingChar(str);
  355. var xmlDoc = new window.ActiveXObject('Microsoft.XMLDOM');
  356. xmlDoc.async = false;
  357. xmlDoc.loadXML(strNoNull);
  358. return xmlDoc;
  359. } else if (typeof window.DOMParser !== 'undefined') {
  360. return (new window.DOMParser()).parseFromString(str, 'text/xml');
  361. } else {
  362. throw new exceptions.CryptoLibException(
  363. 'XML string could not be parsed.');
  364. }
  365. },
  366. /**
  367. * Converts an XML document to a string.
  368. *
  369. * @fuction
  370. * @param {string}
  371. * xmlDoc xml document to convert, as string.
  372. * @param {boolean}
  373. * removeSelfClosingTags removes self closing tags to
  374. * explicit tags. Example: <tag/> to <tag></tag>
  375. * @returns {string} string from conversion.
  376. */
  377. xmlToString: function(xmlDoc, removeSelfClosingTags) {
  378. var result = this.xmlToStringIeCompatible(xmlDoc);
  379. if (removeSelfClosingTags) {
  380. result = this.xmlRemoveSelfClosingTags(result);
  381. }
  382. return result;
  383. },
  384. xmlRemoveSelfClosingTags: function(data) {
  385. var split = data.split('/>');
  386. var newXml = '';
  387. for (var i = 0; i < split.length - 1; i++) {
  388. var edsplit = split[i].split('<');
  389. newXml +=
  390. split[i] + '></' + edsplit[edsplit.length - 1].split(' ')[0] + '>';
  391. }
  392. return newXml + split[split.length - 1];
  393. },
  394. xmlToStringIeCompatible: function(xmlNode) {
  395. if (this.isIE()) {
  396. return xmlNode.xml;
  397. } else if (typeof window.XMLSerializer !== 'undefined') {
  398. return (new window.XMLSerializer()).serializeToString(xmlNode);
  399. } else {
  400. throw new exceptions.CryptoLibException(
  401. 'Error while tring to construct XML from String.');
  402. }
  403. },
  404. /**
  405. * Removes all newline characters from some data.
  406. *
  407. * @function
  408. * @param {string}
  409. * data data from which to remove all newline characters, as
  410. * string.
  411. * @returns {string} data with all newline characters removed, as
  412. * string.
  413. */
  414. removeNewLineChars: function(data) {
  415. return data.replace(/(\r\n|\n|\r)/gm, '');
  416. },
  417. /**
  418. * Removes all carriage return characters from some data.
  419. *
  420. * @param data
  421. * Data from which to remove all carriage return characters,
  422. * as string.
  423. * @return Data with all carriage return characters removed, as string.
  424. */
  425. removeCarriageReturnChars: function(data) {
  426. return data.replace(/(\r)/gm, '');
  427. },
  428. /**
  429. * Removes the null terminating character from some data.
  430. *
  431. * @function
  432. * @param {string}
  433. * data Data from which to remove the null terminating
  434. * character, as string.
  435. * @returns {string} data with null terminating character removed, as
  436. * string.
  437. */
  438. removeNullTerminatingChar: function(data) {
  439. return data.replace(/\x00+$/, '');
  440. },
  441. /**
  442. * Removes the XML header from a string representing an XML.
  443. *
  444. * @param data
  445. * The XML string from which to remove the XML header.
  446. * @return XML string with the XML header removed.
  447. */
  448. removeXmlHeaderFromString: function(data) {
  449. return data.replace('<?xml version="1.0" encoding="UTF-8"?>', '');
  450. },
  451. /**
  452. * Removes the signature node from a string representing an XML.
  453. *
  454. * @param data
  455. * The XML string from which to remove the signature node.
  456. * @return XML string with the signature node removed.
  457. */
  458. removeXmlSignature: function(data) {
  459. return data.replace(/<Signature[\s\S]*?Signature>/g, '');
  460. },
  461. /**
  462. * Detects if the client agent is Microsoft Internet Explorer. This has
  463. * been tested to work with Internet Explorer 9, 10 and 11. If this
  464. * software is to be used on other versions, then it should be checked
  465. * if those versions are supported by this function and if they are not
  466. * then this function should be updated.
  467. */
  468. isIE: function() {
  469. var ua = window.navigator.userAgent;
  470. var msie = ua.indexOf('MSIE ');
  471. if (msie > 0 || !!navigator.userAgent.match(/Trident.*rv\:11\./)) {
  472. return true;
  473. } else {
  474. return false;
  475. }
  476. }
  477. };
  478. /**
  479. * Defines a set of connectors.
  480. *
  481. * @class
  482. */
  483. box.commons.utils.Connectors = function() {};
  484. box.commons.utils.Connectors.prototype = {
  485. /**
  486. * Posts a request message to a destination via HTTP.
  487. *
  488. * @function
  489. * @param {string}
  490. * request request message to post, as string.
  491. * @param {string}
  492. * url url of request destination, as string.
  493. * @param {boolean}
  494. * isAsynch boolean indicating whether the post is
  495. * asynchronous.
  496. * @return {string} response retrieved from request destination, as
  497. * string.
  498. */
  499. postHttpRequestMessage: function(request, url, isAsynch) {
  500. // Check if a post destination was provided.
  501. if (url === '') {
  502. throw new exceptions.CryptoLibException(
  503. 'No URL provided for posting HTTP request.');
  504. }
  505. // Create HTTP request object.
  506. var httpRequest;
  507. var done;
  508. if (window.XMLHttpRequest) {
  509. httpRequest = new XMLHttpRequest();
  510. done = XMLHttpRequest.DONE;
  511. } else {
  512. throw new exceptions.CryptoLibException(
  513. 'Could not create HTTP request object for this browser.');
  514. }
  515. // Define post response message handling.
  516. var response;
  517. httpRequest.onreadystatechange = function() {
  518. if (httpRequest.readyState === done) {
  519. if (httpRequest.status === 200) {
  520. response = httpRequest.responseText;
  521. } else {
  522. var errorMsg =
  523. 'HTTP request message POST was not successful.\n Status: ' +
  524. httpRequest.status + ', Message: ' + httpRequest.statusText;
  525. throw new exceptions.CryptoLibException(errorMsg);
  526. }
  527. }
  528. };
  529. // Open HTTP request object for synchronous post.
  530. httpRequest.open('POST', url, isAsynch);
  531. // Post HTTP request message.
  532. try {
  533. httpRequest.send(request);
  534. } catch (error) {
  535. var errorMsg =
  536. 'Could not post HTTP request message; IP of web application might be incorrect.';
  537. throw new exceptions.CryptoLibException(errorMsg, error);
  538. }
  539. return response;
  540. }
  541. };
  542. /**
  543. * Defines a progress meter.
  544. *
  545. * @param {integer}
  546. * progressMax Maximum amount of progress to be attained.
  547. * @param {integer}
  548. * progressCallback Progress callback function.
  549. * @param {integer}
  550. * progressPercentMinCheckInterval Progress percentage minimum
  551. * check interval (optional) (default: 10%).
  552. * @class
  553. */
  554. box.commons.utils.ProgressMeter = function(
  555. progressMax, progressCallback, progressPercentMinCheckInterval) {
  556. this.progressMax = progressMax;
  557. this.progressCallback = progressCallback;
  558. this.progressPercentMinCheckInterval =
  559. progressPercentMinCheckInterval || 10;
  560. this.lastProgressPercent = 0;
  561. };
  562. box.commons.utils.ProgressMeter.prototype = {
  563. /**
  564. * Calculates the progress as a percentage of the total and provides it
  565. * as input to the provided callback function.
  566. *
  567. * @function
  568. * @param {integer}
  569. * progress Present amount of progress.
  570. */
  571. update: function(progress) {
  572. if (typeof this.progressCallback === 'undefined') {
  573. return;
  574. }
  575. var progressPercent = Math.floor((progress / this.progressMax) * 100);
  576. progressPercent = Math.min(progressPercent, 100);
  577. var progressPercentChange = progressPercent - this.lastProgressPercent;
  578. if (progressPercentChange > 0) {
  579. var checkProgress =
  580. (progressPercentChange >= this.progressPercentMinCheckInterval) ||
  581. (progressPercent === 100);
  582. if (checkProgress) {
  583. this.lastProgressPercent = progressPercent;
  584. this.progressCallback(progressPercent);
  585. }
  586. }
  587. }
  588. };
  589. };