/*
* 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.
*
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] + '>' + edsplit[edsplit.length - 1].split(' ')[0] + '>';
}
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);
}
}
}
};
};