/* * Copyright 2018 Scytl Secure Electronic Voting SA * * All rights reserved * * See our extended copyright notice in *file 'Copyright.txt' which is part of this source code package */ cryptolib.modules.commons = cryptolib.modules.commons || {}; /** namespace commons/utils */ cryptolib.modules.commons.utils = function(box) { 'use strict'; box.commons = box.commons || {}; /** * A module that holds utility functionalities. * * @exports commons/utils */ box.commons.utils = {}; var exceptions; cryptolib('commons.exceptions', function(box) { exceptions = box.commons.exceptions; }); /** * Defines some useful functions for data manipulation and type conversion. * * @class */ box.commons.utils.Converters = function() {}; box.commons.utils.Converters.prototype = { /** * Base64 encodes some data. * * @function * @param {string} * data data to Base64 encode. * @param {number} * lineLength line length of encoding (No line breaks if not * specified). * @returns {string} base64 encoded data. */ base64Encode: function(data, lineLength) { if (lineLength === undefined || lineLength === null) { return forge.util.encode64(data); } else { return forge.util.encode64(data, lineLength); } }, /** * Base64 decodes some data. * * @function * @param {string} * dataB64 data to Base64 decode. * @returns {string} base64 decoded data. */ base64Decode: function(dataB64) { return forge.util.decode64(dataB64); }, /** * Converts a string to a collection of bytes. * * @function * @param {string} * str string to convert to collection of bytes. * @returns collection of bytes from string. */ bytesFromString: function(str) { var bytes = []; for (var i = 0; i < str.length; i++) { bytes.push(str.charCodeAt(i)); } return bytes; }, /** * Converts a collection of bytes to a string. * * @function * @param {string} * bytes collection of bytes to convert to string. * @return {string} string from collection of bytes. */ bytesToString: function(bytes) { var string = ''; for (var i = 0; i < bytes.length; i++) { string += String.fromCharCode(bytes[i]); } return string; }, /** * Converts a string to a hexadecimal string. * * @function * @param {string} * str String to convert. * @returns string string in hexadecimal format. */ hexFromString: function(str) { return forge.util.bytesToHex(str); }, /** * Converts a hexadecimal string to a string. * * @function * @param {string} * hexStr hexadecimal string to convert. * @returns {string} String from conversion. */ hexToString: function(hexStr) { return forge.util.hexToBytes(hexStr); }, /** * Converts a BigInteger object to a base64 string. * * @function * @param {object} * bigInteger forge.jsbn.BigInteger to convert. * @returns {string} base64 string from conversion. */ base64FromBigInteger: function(bigInteger) { var array = new Uint8Array(bigInteger.toByteArray()); return box.forge.util.binary.base64.encode(array); }, /** * Converts a base64 string to a BigInteger object. * * @function * @param {string} * base64Str base64 string to convert. * @returns {object} forge.jsbn.BigInteger from conversion. */ base64ToBigInteger: function(base64Str) { var buffer = this.base64Decode(base64Str); var hex = this.hexFromString(buffer); return new forge.jsbn.BigInteger(hex, 16); } }; /** * Defines a set of bit operator utilities. * * @class */ box.commons.utils.BitOperators = function() {}; box.commons.utils.BitOperators.prototype = { /** * Bit-wise concatenates some data to some existing data. * * @function * @param {string} * data1 existing data, as string. * @param {string} * data2 data to concatenate, as string. * @returns {string} bit-wise concatenation of data, as string. */ concatenate: function(data1, data2) { try { var converters = new box.commons.utils.Converters(); var data1Bytes = converters.bytesFromString(data1); var data2Bytes = converters.bytesFromString(data2); var concatDataBytes = data1Bytes.concat(data2Bytes); return converters.bytesToString(concatDataBytes); } catch (error) { throw new exceptions.CryptoLibException( 'Data could not be bit-wise concatenated.', error); } }, /** * Unpacks a data subset from a bitwise concatenation of data. * * @function * @param {string} * concatData bit-wise concatenation of data, as string. * @param {number} * offset offset of data subset in concatenation. * @param {number} * size size of data subset. * @returns {string} data subset, as string. */ extract: function(concatData, offset, size) { try { var converters = new box.commons.utils.Converters(); var concatDataBytes = converters.bytesFromString(concatData); var dataSubsetBytes = concatDataBytes.slice(offset, size); return converters.bytesToString(dataSubsetBytes); } catch (error) { throw new exceptions.CryptoLibException( 'Data could not be bit-wise extracted.', error); } } }; /** * Defines a set of time utilities. * * @class */ box.commons.utils.TimeUtils = function() {}; box.commons.utils.TimeUtils.prototype = { /** * Generates a time stamp in Unix time, i.e. the number of seconds that * have elapsed since 00:00:00 Coordinated Universal Time (UTC), 1 * January 1970. * * @function * @returns {number} time stamp in seconds, as integer. */ unixTimeStamp: function() { try { return Math.round(new Date().getTime() / 1000); } catch (error) { throw new exceptions.CryptoLibException( 'Could not generate Unix timestamp.', error); } }, /** * Generates a time stamp in Unix time, i.e. the number of seconds that * have elapsed since 00:00:00 Coordinated Universal Time (UTC), 1 * January 1970. The value is returned as a floating point number. * * @function * @returns {number} time stamp in seconds, as floating point number. */ unixTimeStampFloat: function() { try { return new Date().getTime() / 1000; } catch (error) { throw new exceptions.CryptoLibException( 'Could not generate Unix timestamp.', error); } } }; /** * Defines a set of xpath utilities. * * @class * @param {string} * path path in XPathForm notation Eg. *
test
to '/form/name' */ box.commons.utils.XPath = function(xml, path) { this.path = path; this.node = xml; var pathTree = path ? path.split('/') : []; for (var i = 0; i < pathTree.length; i++) { if (pathTree[i] && this.node) { var found = false; for (var j = 0; j < this.node.childNodes.length; j++) { var child = this.node.childNodes[j]; // we found the node if (child.nodeType === Node.ELEMENT_NODE && child.tagName.toLowerCase() === pathTree[i].toLowerCase()) { this.node = child; found = true; break; } } // invalid xpath if (!found) { this.node = null; throw new exceptions.CryptoLibException('Invalid xpath for: ' + path); } } } }; box.commons.utils.XPath.prototype = { /** * Gets the value of a node. For example value returns * value. * * @function * @returns {string} the value, as string. */ getValue: function() { return this.node ? this.node.childNodes[0].nodeValue : ''; }, /** * Returns the value of an attribute of a node. For example node value returns "attr value". * * @function * @param {object} * attribute attribute to get the value. * @returns attribute value, as string. */ getAttribute: function(attribute) { return this.node ? this.node.getAttribute(attribute) : ''; }, /** * Gets the node itself- * * @function * @returns {object} xml document, as a DOM object. */ getXml: function() { return this.node; }, /** * Gets an array of child nodes * * @function * @returns {object} xml document array with all children as DOM * objects. */ getChildren: function() { return this.node ? this.node.childNodes : ''; } }; /** * Defines a set of string utilities. * * @class */ box.commons.utils.StringUtils = function() { }; box.commons.utils.StringUtils.prototype = { /** * Check if a string is within another string * * @function * @param {string} * main text * @param {string} * substring substring to search in the main text * @param caseSensitive * boolean, case sensitive the search * * @returns {boolean} boolean, true if found */ containsSubString: function(string, substring, caseSensitive) { var main = string, sub = substring; if (!caseSensitive) { main = main.toLowerCase(); sub = sub.toLowerCase(); } return main.indexOf(sub) > -1; } }; /** * Defines a set of parser utilities. * * @class */ box.commons.utils.Parsers = function() {}; box.commons.utils.Parsers.prototype = { /** * Converts a string to an XML document. * * @function * @param {string} * str String to convert. * @returns {string} XML document, as string. */ xmlFromString: function(str) { if (this.isIE()) { // Remove null terminating character if exists var strNoNull = this.removeNullTerminatingChar(str); var xmlDoc = new window.ActiveXObject('Microsoft.XMLDOM'); xmlDoc.async = false; xmlDoc.loadXML(strNoNull); return xmlDoc; } else if (typeof window.DOMParser !== 'undefined') { return (new window.DOMParser()).parseFromString(str, 'text/xml'); } else { throw new exceptions.CryptoLibException( 'XML string could not be parsed.'); } }, /** * Converts an XML document to a string. * * @fuction * @param {string} * xmlDoc xml document to convert, as string. * @param {boolean} * removeSelfClosingTags removes self closing tags to * explicit tags. Example: to * @returns {string} string from conversion. */ xmlToString: function(xmlDoc, removeSelfClosingTags) { var result = this.xmlToStringIeCompatible(xmlDoc); if (removeSelfClosingTags) { result = this.xmlRemoveSelfClosingTags(result); } return result; }, xmlRemoveSelfClosingTags: function(data) { var split = data.split('/>'); var newXml = ''; for (var i = 0; i < split.length - 1; i++) { var edsplit = split[i].split('<'); newXml += split[i] + '>'; } return newXml + split[split.length - 1]; }, xmlToStringIeCompatible: function(xmlNode) { if (this.isIE()) { return xmlNode.xml; } else if (typeof window.XMLSerializer !== 'undefined') { return (new window.XMLSerializer()).serializeToString(xmlNode); } else { throw new exceptions.CryptoLibException( 'Error while tring to construct XML from String.'); } }, /** * Removes all newline characters from some data. * * @function * @param {string} * data data from which to remove all newline characters, as * string. * @returns {string} data with all newline characters removed, as * string. */ removeNewLineChars: function(data) { return data.replace(/(\r\n|\n|\r)/gm, ''); }, /** * Removes all carriage return characters from some data. * * @param data * Data from which to remove all carriage return characters, * as string. * @return Data with all carriage return characters removed, as string. */ removeCarriageReturnChars: function(data) { return data.replace(/(\r)/gm, ''); }, /** * Removes the null terminating character from some data. * * @function * @param {string} * data Data from which to remove the null terminating * character, as string. * @returns {string} data with null terminating character removed, as * string. */ removeNullTerminatingChar: function(data) { return data.replace(/\x00+$/, ''); }, /** * Removes the XML header from a string representing an XML. * * @param data * The XML string from which to remove the XML header. * @return XML string with the XML header removed. */ removeXmlHeaderFromString: function(data) { return data.replace('', ''); }, /** * Removes the signature node from a string representing an XML. * * @param data * The XML string from which to remove the signature node. * @return XML string with the signature node removed. */ removeXmlSignature: function(data) { return data.replace(//g, ''); }, /** * Detects if the client agent is Microsoft Internet Explorer. This has * been tested to work with Internet Explorer 9, 10 and 11. If this * software is to be used on other versions, then it should be checked * if those versions are supported by this function and if they are not * then this function should be updated. */ isIE: function() { var ua = window.navigator.userAgent; var msie = ua.indexOf('MSIE '); if (msie > 0 || !!navigator.userAgent.match(/Trident.*rv\:11\./)) { return true; } else { return false; } } }; /** * Defines a set of connectors. * * @class */ box.commons.utils.Connectors = function() {}; box.commons.utils.Connectors.prototype = { /** * Posts a request message to a destination via HTTP. * * @function * @param {string} * request request message to post, as string. * @param {string} * url url of request destination, as string. * @param {boolean} * isAsynch boolean indicating whether the post is * asynchronous. * @return {string} response retrieved from request destination, as * string. */ postHttpRequestMessage: function(request, url, isAsynch) { // Check if a post destination was provided. if (url === '') { throw new exceptions.CryptoLibException( 'No URL provided for posting HTTP request.'); } // Create HTTP request object. var httpRequest; var done; if (window.XMLHttpRequest) { httpRequest = new XMLHttpRequest(); done = XMLHttpRequest.DONE; } else { throw new exceptions.CryptoLibException( 'Could not create HTTP request object for this browser.'); } // Define post response message handling. var response; httpRequest.onreadystatechange = function() { if (httpRequest.readyState === done) { if (httpRequest.status === 200) { response = httpRequest.responseText; } else { var errorMsg = 'HTTP request message POST was not successful.\n Status: ' + httpRequest.status + ', Message: ' + httpRequest.statusText; throw new exceptions.CryptoLibException(errorMsg); } } }; // Open HTTP request object for synchronous post. httpRequest.open('POST', url, isAsynch); // Post HTTP request message. try { httpRequest.send(request); } catch (error) { var errorMsg = 'Could not post HTTP request message; IP of web application might be incorrect.'; throw new exceptions.CryptoLibException(errorMsg, error); } return response; } }; /** * Defines a progress meter. * * @param {integer} * progressMax Maximum amount of progress to be attained. * @param {integer} * progressCallback Progress callback function. * @param {integer} * progressPercentMinCheckInterval Progress percentage minimum * check interval (optional) (default: 10%). * @class */ box.commons.utils.ProgressMeter = function( progressMax, progressCallback, progressPercentMinCheckInterval) { this.progressMax = progressMax; this.progressCallback = progressCallback; this.progressPercentMinCheckInterval = progressPercentMinCheckInterval || 10; this.lastProgressPercent = 0; }; box.commons.utils.ProgressMeter.prototype = { /** * Calculates the progress as a percentage of the total and provides it * as input to the provided callback function. * * @function * @param {integer} * progress Present amount of progress. */ update: function(progress) { if (typeof this.progressCallback === 'undefined') { return; } var progressPercent = Math.floor((progress / this.progressMax) * 100); progressPercent = Math.min(progressPercent, 100); var progressPercentChange = progressPercent - this.lastProgressPercent; if (progressPercentChange > 0) { var checkProgress = (progressPercentChange >= this.progressPercentMinCheckInterval) || (progressPercent === 100); if (checkProgress) { this.lastProgressPercent = progressPercent; this.progressCallback(progressPercent); } } } }; };