UNPKG

@istvan.xyz/phc-format

Version:

An implementation of the PHC format.

115 lines (114 loc) 3.81 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const patterns_1 = require("./patterns"); const b64Regex = /^([a-zA-Z0-9/+.-]+|)$/; const decimalRegex = /^((-)?[1-9]\d*|0)$/; const versionRegex = /^v=(\d+)$/; const keyValueStringToObject = (string) => { const object = {}; string.split(',').forEach(ps => { const tokens = ps.split('='); if (tokens.length < 2) { throw new TypeError('params must be in the format name=value'); } object[tokens.shift()] = tokens.join('='); }); return object; }; /** * Parses data from a PHC string. * @param {string} phcString A PHC string to parse. * @return {Object} The object containing the data parsed from the PHC string. */ function deserialize(phcString) { if (phcString === '') { throw new TypeError('phcString must be a non-empty string'); } if (phcString[0] !== '$') { throw new TypeError('phcString must contain a $ as first char'); } const fields = phcString.split('$'); // Remove first empty $ fields.shift(); // Parse Fields let maxFields = 5; if (!versionRegex.test(fields[1])) maxFields--; if (fields.length > maxFields) { throw new TypeError(`phcString contains too many fields: ${fields.length}/${maxFields}`); } // Parse Identifier const id = fields.shift(); if (!id) { throw new Error('id cannot be undefined at this point.'); } if (!patterns_1.idRegex.test(id)) { throw new TypeError(`id must satisfy ${patterns_1.idRegex}`); } let version; // Parse Version if (versionRegex.test(fields[0])) { const versionString = fields.shift(); if (!versionString) { throw new Error('paramString cannot be undefined at this point.'); } version = parseInt(versionString.match(versionRegex)[1], 10); } let hash; let salt; if (b64Regex.test(fields[fields.length - 1])) { if (fields.length > 1 && b64Regex.test(fields[fields.length - 2])) { // Parse Hash hash = Buffer.from(fields.pop(), 'base64'); // Parse Salt salt = Buffer.from(fields.pop(), 'base64'); } else { // Parse Salt salt = Buffer.from(fields.pop(), 'base64'); } } // Parse Parameters let params; if (fields.length > 0) { const paramString = fields.pop(); if (!paramString) { throw new Error('paramString cannot be undefined at this point.'); } const currentParams = keyValueStringToObject(paramString); if (!Object.keys(currentParams).every(p => patterns_1.nameRegex.test(p))) { throw new TypeError(`params names must satisfy ${patterns_1.nameRegex}`); } const pv = Object.values(currentParams); if (!pv.every(v => patterns_1.valueRegex.test(v))) { throw new TypeError(`params values must satisfy ${patterns_1.valueRegex}`); } const pk = Object.keys(currentParams); // Convert Decimal Strings into Numbers pk.forEach(k => { currentParams[k] = decimalRegex.test(currentParams[k]) ? parseInt(currentParams[k], 10) : currentParams[k]; }); params = currentParams; } if (fields.length > 0) { throw new TypeError(`phcString contains unrecognized fields: ${fields}`); } // Build the output object const result = { id }; if (version) { result.version = version; } if (params) { result.params = params; } if (salt) { result.salt = salt; } if (hash) { result.hash = hash; } return result; } exports.default = deserialize;