@istvan.xyz/phc-format
Version:
An implementation of the PHC format.
115 lines (114 loc) • 3.81 kB
JavaScript
;
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;