diff --git a/package-lock.json b/package-lock.json index a878fbf..28700c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@oat-sa/tao-core-sdk", - "version": "1.6.3", + "version": "1.7.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1116,6 +1116,12 @@ "integrity": "sha512-z9ezG0Z+26qaCz3w/sYzN/JyBLmN2xhzCfNihYRA7CMYukEbkeY3/tLknVhwm+rp4584yDDuSHPVMuoYmwZP1A==", "dev": true }, + "@oat-sa/prettier-config": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@oat-sa/prettier-config/-/prettier-config-0.1.1.tgz", + "integrity": "sha512-igtRpb6eaRtkomSjONelI/tXjSfk3yUHcm67jlzGsdVAX9M5S4RUJTiGIoBSDSSnfHu530rEdmM8FNvJG//rbQ==", + "dev": true + }, "@oat-sa/tao-core-libs": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/@oat-sa/tao-core-libs/-/tao-core-libs-0.2.0.tgz", @@ -2449,9 +2455,9 @@ "dev": true }, "fastestsmallesttextencoderdecoder": { - "version": "1.0.22", - "resolved": "https://registry.npmjs.org/fastestsmallesttextencoderdecoder/-/fastestsmallesttextencoderdecoder-1.0.22.tgz", - "integrity": "sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw==" + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/fastestsmallesttextencoderdecoder/-/fastestsmallesttextencoderdecoder-1.0.14.tgz", + "integrity": "sha512-ov+uDh4DMZHpZvcGwlCb9tfntaHwRI7SK+/6XkdXhksZLJcMoTJ20FZx3GvujnsGjMvJVQ71LkduEUEPwX1BvQ==" }, "fd-slicer": { "version": "1.1.0", @@ -3847,6 +3853,12 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, + "prettier": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.1.2.tgz", + "integrity": "sha512-16c7K+x4qVlJg9rEbXl7HEGmQyZlG4R9AgP+oHKRMsMsuk8s+ATStlf1NpDqyBI1HpVyfjLOeMhH2LvuNvV5Vg==", + "dev": true + }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", diff --git a/package.json b/package.json index 20dd3b7..6a40bec 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@oat-sa/tao-core-sdk", - "version": "1.6.3", + "version": "1.7.0", "displayName": "TAO Core SDK", "description": "Core libraries of TAO", "homepage": "https://github.com/oat-sa/tao-core-sdk-fe#readme", @@ -43,6 +43,7 @@ "@oat-sa/browserslist-config-tao": "^0.1.0", "@oat-sa/eslint-config-tao": "^0.1.1", "@oat-sa/expr-eval": "^1.3.0", + "@oat-sa/prettier-config": "^0.1.1", "@oat-sa/tao-core-libs": "^0.2.0", "@oat-sa/tao-qunit-testrunner": "^0.1.3", "async": "^0.2.10", @@ -68,6 +69,7 @@ "nyc": "^14.1.1", "open-cli": "^5.0.0", "popper.js": "1.15.0", + "prettier": "^2.1.2", "promise-limit": "^2.7.0", "qunit": "^2.9.2", "raphael": "2.1.4", @@ -84,11 +86,12 @@ "tooltip.js": "1.3.2" }, "dependencies": { - "fastestsmallesttextencoderdecoder": "^1.0.14", + "fastestsmallesttextencoderdecoder": "1.0.14", "idb-wrapper": "1.7.0", "webcrypto-shim": "0.1.4" }, "browserslist": [ "extends @oat-sa/browserslist-config-tao" - ] + ], + "prettier": "@oat-sa/prettier-config" } diff --git a/src/core/digest.js b/src/core/digest.js index 36a8560..0cbf01f 100644 --- a/src/core/digest.js +++ b/src/core/digest.js @@ -13,7 +13,7 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * - * Copyright (c) 2018-2019 (original work) Open Assessment Technologies SA + * Copyright (c) 2018-2020 (original work) Open Assessment Technologies SA * */ @@ -24,13 +24,12 @@ * * @author Bertrand Chevrier */ -import _ from 'lodash'; import 'webcrypto-shim'; import { TextEncoder } from 'fastestsmallesttextencoderdecoder'; //get the native implementation of the CryptoSubtle -var subtle = window.crypto.subtle || window.crypto.webkitSubtle; -var supportedAlgorithms = [ +const subtle = window.crypto.subtle || window.crypto.webkitSubtle; +const supportedAlgorithms = [ 'SHA-1', //considered as not safe anymore 'SHA-256', 'SHA-384', @@ -39,37 +38,44 @@ var supportedAlgorithms = [ /** * Encode a buffer to an hexadecimal string - * @param {Number[]|ArrayBuffer} buffer - * @returns {String} the hex representation of the buffer + * @param {number[]|ArrayBuffer} buffer + * @returns {string} the hex representation of the buffer */ -var bufferToHexString = function bufferToHexString(buffer) { - return [].map - .call(new Uint8Array(buffer), function(val) { - return ('00' + val.toString(16)).slice(-2); - }) - .join(''); -}; +function bufferToHexString(buffer) { + return [...new Uint8Array(buffer)].map(val => `00${val.toString(16)}`.slice(-2)).join(''); +} /** - * Create a hash/checksum from a given string - * @param {String} utf8String - the string to hash - * @param {String} [selectedAlgorithm = 'SHA-256'] - how to hash + * Create a hash/checksum from a given string, blob or buffer + * @param {string|Blob|ArrayBuffer|Uint8Array} data - the data to hash + * @param {string} [selectedAlgorithm] - hashing algorithm * @returns {Promise} resolves with the hash of the string * @throws {TypeError} if the algorithm is not available or the input string is missing */ -export default function digest(utf8String, selectedAlgorithm) { - var algorithm; - if (!_.isString(selectedAlgorithm)) { - selectedAlgorithm = 'SHA-256'; - } - algorithm = selectedAlgorithm.toUpperCase(); - if (!_.contains(supportedAlgorithms, algorithm)) { - throw new TypeError('Unsupported digest algorithm : ' + algorithm); +export default function digest(data, selectedAlgorithm = 'SHA-256') { + let algorithm = selectedAlgorithm.toUpperCase(); + if (!supportedAlgorithms.includes(algorithm)) { + throw new TypeError(`Unsupported digest algorithm : ${algorithm}`); } - if (!_.isString(utf8String)) { - throw new TypeError('Please encode a string, not a ' + typeof utf8String); + + let dataPromise; + if (data instanceof Uint8Array) { + dataPromise = Promise.resolve(data); + } else if (data instanceof ArrayBuffer) { + dataPromise = Promise.resolve(new Uint8Array([data])); + } else if (data instanceof Blob) { + dataPromise = new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.addEventListener('loadend', () => resolve(reader.result)); + reader.addEventListener('abort', reject); + reader.addEventListener('error', reject); + reader.readAsArrayBuffer(data); + }); + } else if (typeof data === 'string') { + dataPromise = Promise.resolve(new TextEncoder('utf-8').encode(data)); + } else { + throw new TypeError(`Unsupported data type to digest with ${algorithm}`); } - return subtle.digest(algorithm, new TextEncoder('utf-8').encode(utf8String)).then(function(buffer) { - return bufferToHexString(buffer); - }); + + return dataPromise.then(rawData => subtle.digest(algorithm, rawData)).then(buffer => bufferToHexString(buffer)); } diff --git a/test/core/digest/test.js b/test/core/digest/test.js index 0294a53..e23619f 100644 --- a/test/core/digest/test.js +++ b/test/core/digest/test.js @@ -25,35 +25,39 @@ define(['core/digest'], function(digest) { 'use strict'; + const hello = new Uint8Array([72, 101, 108, 108, 111]); // "Hello" in binary + const blob = new Blob([hello], { type: 'text/plain' }); + const file = new File([hello], 'hello.txt', { type: 'text/plain' }); + QUnit.module('API'); - QUnit.test('module', function(assert) { + QUnit.test('module', assert => { assert.equal(typeof digest, 'function', 'The module exposes an function'); }); QUnit.module('Behavior'); - QUnit.test('valid inputs', function(assert) { + QUnit.test('valid inputs', assert => { assert.throws( - function() { - digest(); - }, + () => digest(), TypeError, 'An input string is required' ); assert.throws( - function() { - digest({}); - }, + () => digest({}), TypeError, 'An input string is required' ); assert.throws( - function() { - digest('foo', 'MD5'); - }, + () => digest(null), + TypeError, + 'A valid input is required' + ); + + assert.throws( + () => digest('foo', 'MD5'), TypeError, 'A valid algorithm is required' ); @@ -93,7 +97,31 @@ define(['core/digest'], function(digest) { algo: 'SHA-512', output: '00d7c8367e02fb59989bb05fa86421d7afbbf397b0babc2d2f2fb02da9ec65092e782242ac47a912de2d9e6979c543c1b42a93f2ca258a07f0095e67d28571e6' - } + }, + { + title: 'sha256 from blob', + input: blob, + algo: 'SHA-256', + output: '185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969' + }, + { + title: 'sha256 from file', + input: file, + algo: 'SHA-256', + output: '185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969' + }, + { + title: 'sha256 from ArrayBuffer', + input: hello.buffer, + algo: 'SHA-256', + output: '6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d' + }, + { + title: 'sha256 from Uint8Array', + input: hello, + algo: 'SHA-256', + output: '185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969' + }, ]) .test('digest', function(data, assert) { var ready = assert.async();