UNPKG

@haystacks/async

Version:

A framework to build any number or any kind of native application or automation solution.

451 lines (426 loc) 24.3 kB
/** * @file dataStringParsing.js * @module dataStringParsing * @description Contains all system defined business rules for parsing strings as related to data. * @requires module:ruleParsing * @requires module:loggers * @requires {@link https://www.npmjs.com/package/@haystacks/constants|@haystacks/constants} * @requires {@link https://nodejs.org/api/crypto.html|crypto} * @requires {@link https://nodejs.org/api/buffer.html|buffer} * @requires {@link https://www.npmjs.com/package/path|path} * @author Seth Hollingsead * @date 2022/04/25 * @copyright Copyright © 2022-… by Seth Hollingsead. All rights reserved */ // Internal imports import ruleParsing from '../ruleParsing.js'; import loggers from '../../../executrix/loggers.js'; // External imports import hayConst from '@haystacks/constants'; import crypto from 'crypto'; import { Buffer } from 'buffer'; import path from 'path'; const {bas, biz, gen, msg, num, sys, wrd} = hayConst; const baseFileName = path.basename(import.meta.url, path.extname(import.meta.url)); // framework.businessRules.rules.stringParsing.dataStringParsing. const namespacePrefix = wrd.cframework + bas.cDot + sys.cbusinessRules + bas.cDot + wrd.crules + bas.cDot + wrd.cstring + wrd.cParsing + bas.cDot + baseFileName + bas.cDot; /** * @function getAttributeName * @description Takes a string representation of a JSON attribute and gets the name (left hand assignment key). * @param {string} inputData The string representation of the JSON attribute that should be parsed. * @param {string} inputMetaData Not used for this business rule. * @return {string} The name of the attribute. * @author Seth Hollingsead * @date 2022/01/25 */ async function getAttributeName(inputData, inputMetaData) { let functionName = getAttributeName.name; await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function); await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + inputData); await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + inputMetaData); let returnData = false; if (inputData) { let attributeArray = inputData.split(bas.cColon); // attributeArray is: await loggers.consoleLog(namespacePrefix + functionName, msg.cattributeArrayIs + JSON.stringify(attributeArray)); // attributeArray[0] is: await loggers.consoleLog(namespacePrefix + functionName, msg.cattributeArray0Is + attributeArray[0]); returnData = await ruleParsing.processRulesInternal([attributeArray[0], [/"/g, '']], [biz.creplaceCharacterWithCharacter]); returnData = returnData.trim(); } // End-if (inputData) await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData)); await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function); return returnData; } /** * @function getAttributeValue * @description Takes a string representation of a JSON attribute and gets the value (Right hand assignment value). * @param {string} inputData The string representation of the JSON attribute that should be parsed. * @param {string} inputMetaData Not used for this business rule. * @return {string} The value of the attribute. * @author Seth Hollingsead * @date 2022/01/10 */ async function getAttributeValue(inputData, inputMetaData) { let functionName = getAttributeValue.name; await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function); await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + inputData); await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + inputMetaData); let returnData = false; if (inputData) { let attributeArray = inputData.split(bas.cColon); // attributeArray is: await loggers.consoleLog(namespacePrefix + functionName, msg.cattributeArrayIs + attributeArray); // attributeArray[0] is: await loggers.consoleLog(namespacePrefix + functionName, msg.cattributeArray1Is + attributeArray[1]); returnData = await ruleParsing.processRulesInternal([attributeArray[1], [/"/g, '']], [biz.creplaceCharacterWithCharacter]); returnData = returnData.trim(); } // End-if (inputData) await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData)); await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function); return returnData; } /** * @function getValueFromAssignmentOperationString * @description Parses the input string and finds the value on the right side of the '=' sign. * @param {string} inputData The string that should be parsed for the value on the right side of the assignment operator. * @param {string} inputMetaData Not used for this business rule. * @return {string} The string value of whatever is on the right side of the assignment operator. * @author Seth Hollingsead * @date 2022/01/23 */ async function getValueFromAssignmentOperationString(inputData, inputMetaData) { let functionName = getValueFromAssignmentOperationString.name; await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function); await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + inputData); await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + inputMetaData); let returnData = false; if (inputData) { let parsedString = inputData.split(bas.cEqual); await loggers.consoleLog(namespacePrefix + functionName, msg.cparsedStringSpaceTerm + bas.cSpace + num.c1 + msg.cSpaceIsColonSpace + parsedString[0]); await loggers.consoleLog(namespacePrefix + functionName, msg.cparsedStringSpaceTerm + bas.cSpace + num.c2 + msg.cSpaceIsColonSpace + parsedString[1]); returnData = parsedString[1].replace(/['"]+/g, ''); } // End-if (inputData) await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + returnData); await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function); return returnData; } /** * @function getDataCategoryFromDataContextName * @description Gets the data category from the context name, E.g. Input: Page_ProjectList, data category is 'Page'. * @param {string} inputData The data context name, which should also contain the data category separated by underscore. * @param {string} inputMetaData Not used for this business rule. * @return {string} The data category, such as Page or Test. * @author Seth Hollingsead * @date 2022/01/24 */ async function getDataCategoryFromDataContextName(inputData, inputMetaData) { let functionName = getDataCategoryFromDataContextName.name; await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function); await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + inputData); await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + inputMetaData); let returnData = ''; if (inputData) { let dataCategory = inputData.split(bas.cUnderscore); returnData = dataCategory[0]; // Data Category should be: await loggers.consoleLog(namespacePrefix + functionName, msg.cDataCategoryShouldBe + dataCategory[0]); } // End-if (inputData) await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + returnData); await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function); return returnData; } /** * @function getDataCategoryDetailNameFromDataContextName * @description Gets the data category detail name from the context name, E.G. Input: Page_ProjectList, data category is 'ProjectList'. * @param {string} inputData The data context name, which should also contain the * data category and data category detail name separated by an underscore. * @param {string} inputMetaData Not used for this business rule. * @return {string} The data category detail name, such as ProjectDetails or ProjectList. * @author Seth Hollingsead * @date 2022/01/24 */ async function getDataCategoryDetailNameFromDataContextName(inputData, inputMetaData) { let functionName = getDataCategoryDetailNameFromDataContextName.name; await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function); await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + inputData); await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + inputMetaData); let returnData = ''; if (inputData) { let dataCategoryDetailName = inputData.split(bas.cUnderscore); returnData = dataCategoryDetailName[1]; // Data Category Detail Name should be: await loggers.consoleLog(namespacePrefix + functionName, msg.cDataCategoryDetailNameShouldBe + dataCategoryDetailName[1]); } // End-if (inputData) await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + returnData); await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function); return returnData; } /** * @function getKeywordNameFromDataContextName * @description Gets the keyword name from the context name, E.g. Input: Keywords_ProjectDetails_DeleteEntireProject, keyword is: 'DeleteEntireProject'. * @param {string} inputData The data context name, which should also contain the * data category and data category detail name and keyword name separated by an underscore. * @param {string} inputMetaData Not used for this business rule. * @return {string} The keyword name, such as DeleteEntireProject or EditProjectInfoClick. * @author Seth Hollingsead * @date 2022/01/24 */ async function getKeywordNameFromDataContextName(inputData, inputMetaData) { let functionName = getKeywordNameFromDataContextName.name; await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function); await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + inputData); await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + inputMetaData); let returnData = ''; if (inputData) { let dataCategoryKeywordName = inputData.split(bas.cUnderscore); returnData = dataCategoryKeywordName[2]; // Keyword Name should be: await loggers.consoleLog(namespacePrefix + functionName, msg.cKeywordNameShouldBe + dataCategoryKeywordName[2]); } // End-if (inputData) await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + returnData); await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function); return returnData; } /** * @function loadDataFile * @description Loads data from a specified file and stores it in the specified data hive path. * @param {string} inputData The full path and file name for the file that should be loaded into memory. * @param {string} inputMetaData The data hive path where the data should be stored once it is loaded. * @return {boolean} The data that was loaded, because sometimes a client command might need to use this to load data. * @author Seth Hollingsead * @date 2022/01/25 */ async function loadDataFile(inputData, inputMetaData) { let functionName = loadDataFile.name; await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function); await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + inputData); await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + inputMetaData); let returnData = false; if (!inputData) { // WARNING: No data to load, please specify a valid path & filename! await loggers.consoleLog(namespacePrefix + functionName, msg.cLoadDataFileMessage1 + msg.cloadDataFileMessage2); } else { // Else-clause if (!inputData) let loadedData = {}; if (inputData.includes(gen.cDotxml) || inputData.includes(gen.cDotXml) || inputData.includes(gen.cDotXML)) { // Attempting to load XML data! await loggers.consoleLog(namespacePrefix + functionName, msg.cAttemptingToLoadXmlData); loadedData = await ruleParsing.processRulesInternal([inputData, ''], [biz.cgetXmlData]); } else if (inputData.includes(gen.cDotcsv) || inputData.includes(gen.cDotCsv) || inputData.includes(gen.cDotCSV)) { // Attempting to load CSV data! await loggers.consoleLog(namespacePrefix + functionName, msg.cAttemptingToLoadCsvData); loadedData = await ruleParsing.processRulesInternal([inputData, ''], [biz.cgetCsvData]); } else if (inputData.includes(gen.cDotjson) || inputData.includes(gen.cDotJson) || inputData.includes(gen.cDotJSON)) { // Attempting to load JSON data! await loggers.consoleLog(namespacePrefix + functionName, msg.cAttemptingToLoadJsonData); loadedData = await ruleParsing.processRulesInternal([inputData, ''], [biz.cgetJsonData]); } else { // WARNING: Invalid file format, file formats supported are: await loggers.consoleLog(namespacePrefix + functionName, msg.cloadedDataFileMessage3 + await ruleParsing.processRulesInternal(['', ''], [biz.csupportedFileFormatsAre])); } // Loaded data is: await loggers.consoleLog(namespacePrefix + functionName, msg.cLoadedDataIs + JSON.stringify(loadedData)); returnData = loadedData; if (loadedData !== null && loadedData && inputMetaData) { await ruleParsing.processRulesInternal([inputMetaData, loadedData], [biz.cstoreData]); } } // End-else-clause if (!inputData) await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData)); await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function); return returnData; } /** * @function saveDataFile * @description Saves data from a specified data to a specified path and file name. * @param {string} inputData The full path and file name were the data should be saved. * @param {object} inputMetaData The data that should be saved out to the specified file. * @return {boolean} True or False value to indicate if the file was saved successfully or not. * @author Seth Hollingsead * @date 2022/03/17 */ async function saveDataFile(inputData, inputMetaData) { let functionName = saveDataFile.name; await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function); await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + inputData); await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + inputMetaData); let returnData = false; if (!inputData) { // WARNING: No data to save, please specify a valid path & filename! await loggers.consoleLog(namespacePrefix + functionName, msg.csaveDataFileMessage1 + msg.cloadDataFileMessage2); } else { let supportedFileFormats = await ruleParsing.processRulesInternal(['', ''], [biz.csupportedFileFormatsAre]); if (inputData.includes(gen.cDotxml) || inputData.includes(gen.cDotXml) || inputData.includes(gen.cDotXML)) { // WARNING: Invalid file format, file formats supported are: await loggers.consoleLog(namespacePrefix + functionName, msg.cloadedDataFileMessage3 + supportedFileFormats); } else if (inputData.includes(gen.cDotcsv) || inputData.includes(gen.cDotCsv) || inputData.includes(gen.cDotCSV)) { // WARNING: Invalid file format, file formats supported are: await loggers.consoleLog(namespacePrefix + functionName, msg.cloadedDataFileMessage3 + supportedFileFormats); } else if (inputData.includes(gen.cDotjson) || inputData.includes(gen.cDotJson) || inputData.includes(gen.cDotJSON)) { returnData = await ruleParsing.processRulesInternal([inputData, inputMetaData], [biz.cwriteJsonData]); // Should return true if the write is successful. } else { // WARNING: Invalid file format, file formats supported are: await loggers.consoleLog(namespacePrefix + functionName, msg.cloadedDataFileMessage3 + supportedFileFormats); } } await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData)); await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function); return returnData; } /** * @function getUserNameFromEmail * @description Converts an email input into a username. * @param {string} inputData A string that contains an email address value. * @param {string} inputMetaData Not used for this business rule. * @return {string} A string value of the sub-string from before the '@' symbol. * @author Seth Hollingsead * @date 2022/01/21 */ async function getUserNameFromEmail(inputData, inputMetaData) { let functionName = getUserNameFromEmail.name; await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function); await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + inputData); await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + inputMetaData); let returnData = false; if (inputData) { await loggers.consoleLog(namespacePrefix + functionName, msg.cIndexOfTheSpace + bas.cAt + sys.cSpaceIsColonSpace + inputData.indexOf(bas.cAt)); returnData = inputData.substring(0, inputData.indexOf(bas.cAt)); } // End-if (inputData) await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + returnData); await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function); return returnData; } /** * @function encryptStringAes256 * @description Takes a string and runs it through an encryption algorithm, returning an encrypted version of the string. * The algorithm used for the encryption is AES-256, I've included it as part of the function/rule name because * we want to be able to distinguish it from other algorithms that might implement other encryption algorithms, such as RSA. * @param {string} inputData The string to be encrypted. * @param {string} inputMetaData Another string that will be used as the encryption seed. * Can be formatted with and "_", with a prefix and post-fix for public/private keys. * @return {string} The encrypted string. * @author Seth Hollingsead * @date 2024/09/23 */ async function encryptStringAes256(inputData, inputMetaData) { let functionName = encryptStringAes256.name; await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function); await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + inputData); await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + inputMetaData); let returnData = false; if (inputData && inputMetaData && inputData !== '' && inputMetaData !== '') { try { // Treat the entire inputMetaData (public + private keys combined) as the encryption key const encryptionKey = crypto.createHash(gen.csha256).update(inputMetaData).digest(); // Create a 256-bit key const iv = crypto.randomBytes(16); // Generate a random IV // Create the cipher using AES-256-CTR const cipher = crypto.createCipheriv(gen.caes_256_ctr, encryptionKey, iv); // Encrypt the inputData const encrypted = Buffer.concat([cipher.update(inputData, gen.cUTF8), cipher.final()]); // Combine the IV and the encrypted data, encode them in hex format for storage returnData = iv.toString(gen.chex) + bas.cColon + encrypted.toString(gen.chex); // Encryption successful await loggers.consoleLog(namespacePrefix + functionName, msg.cEncryptionSuccessful); } catch (error) { // ERROR: Encryption failed: console.log(msg.cErrorEncryptionFailed + error.message); await loggers.consoleLog(namespacePrefix + functionName, msg.cErrorEncryptionFailed + error.message); } } else { // ERROR: Invalid input strings. console.log(msg.cErrorInvalidInputStrings) await loggers.consoleLog(namespacePrefix + functionName, msg.cErrorInvalidInputStrings); } await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + returnData); await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function); return returnData; } /** * @function decryptStringAes256 * @description Takes an encrypted string and decrypts it using the AES-256 algorithm, returning the original string. * @param {string} inputData The encrypted string to decrypt. * @param {string} inputMetaData The string used as the decryption seed (same key used for encryption). * @return {string} The decrypted string. * @author Seth Hollingsead * @date 2024/09/23 */ export async function decryptStringAes256(inputData, inputMetaData) { let functionName = decryptStringAes256.name; await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function); await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + inputData); await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + inputMetaData); let returnData = false; if (inputData && inputMetaData && inputData !== '' && inputMetaData !== '') { try { // Split the encrypted data into the IV and the encrypted string const [ivHex, encryptedHex] = inputData.split(bas.cColon); const iv = Buffer.from(ivHex, gen.chex); const encryptedText = Buffer.from(encryptedHex, gen.chex); // Derive the encryption key const encryptionKey = crypto.createHash(gen.csha256).update(inputMetaData).digest(); // Create decipher using AES-256-CTR const decipher = crypto.createDecipheriv(gen.caes_256_ctr, encryptionKey, iv); // Decrypt the encrypted text const decrypted = Buffer.concat([decipher.update(encryptedText), decipher.final()]); returnData = decrypted.toString(gen.cUTF8); // Decryption successful await loggers.consoleLog(namespacePrefix + functionName, msg.cDecryptionSuccessful); } catch (error) { // ERROR: Decryption failed: console.log(msg.cErrorEncryptionFailed + error.message); await loggers.consoleLog(namespacePrefix + functionName, msg.cErrorEncryptionFailed + error.message); } } else { // ERROR: Invalid input strings. console.log(msg.cErrorInvalidInputStrings); await loggers.consoleLog(namespacePrefix + functionName, msg.cErrorInvalidInputStrings); } await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + returnData); await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function); return returnData; } /** * @function obfuscateString * @description Takes a string and converts it to an obfuscated version of the same string. * This means converting the string into something that is unintelligible or unreadable from the original string. * In our case we will convert the string into all stars '*', just like as if the string was a password. * @param {string} inputData The string to be obfuscated, or converted into all stars of the same length. * @param {string} inputMetaData Not used for this business rule. * @return {string} A string of '*' characters of the same length as the input string. * @author Seth Hollingsead * @date 2024/09/23 */ export async function obfuscateString(inputData, inputMetaData) { let functionName = obfuscateString.name; await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function); await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + inputData); await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + inputMetaData); let returnData = false; if (inputData && inputData !== '') { // Convert the input string to a string of '*' characters of the same length returnData = bas.cStar.repeat(inputData.length); // Obfuscation successful await loggers.consoleLog(namespacePrefix + functionName, msg.cObfuscationSuccessful); } else { // ERROR: Invalid input string. console.log(msg.cErrorInvalidInputString); await loggers.consoleLog(namespacePrefix + functionName, msg.cErrorInvalidInputString); } await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + returnData); await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function); return returnData; } export default { getAttributeName, getAttributeValue, getValueFromAssignmentOperationString, getDataCategoryFromDataContextName, getDataCategoryDetailNameFromDataContextName, getKeywordNameFromDataContextName, loadDataFile, saveDataFile, getUserNameFromEmail, encryptStringAes256, decryptStringAes256, obfuscateString };