UNPKG

@microsoft/botbuilder-m365

Version:

M365 extensions for Microsoft BotBuilder, Alpha release.

326 lines 14.7 kB
"use strict"; /* eslint-disable security/detect-object-injection */ /** * @module botbuilder-m365 */ /** * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.ResponseParser = void 0; const BREAKING_CHARACTERS = '`~!@#$%^&*()_+-={}|[]\\:";\'<>?,./ \r\n\t'; const NAME_BREAKING_CHARACTERS = '`~!@#$%^&*()+={}|[]\\:";\'<>?,./ \r\n\t'; const SPACE_CHARACTERS = ' \r\n\t'; const COMMANDS = ['DO', 'SAY']; const DEFAULT_COMMAND = 'SAY'; const IGNORED_TOKENS = ['THEN']; class ResponseParser { static parseAdaptiveCard(text) { const obj = this.parseJSON(text); return obj && obj['type'] === 'AdaptiveCard' ? obj : undefined; } static parseJSON(text) { let obj; try { if (text) { const startJson = text.indexOf('{'); const endJson = text.lastIndexOf('}'); if (startJson >= 0 && endJson > startJson) { const txt = text.substring(startJson, endJson + 1); obj = JSON.parse(txt); } } } catch (err) { // no action } return obj; } static parseResponse(text) { var _a; // See if the response contains a plan object? let plan = this.parseJSON(text); if (plan && ((_a = plan.type) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === 'plan') { plan.type = 'plan'; if (!Array.isArray(plan.commands)) { plan.commands = []; } return plan; } // Parse response using DO/SAY syntax let responses = ''; plan = { type: 'plan', commands: [] }; let tokens = this.tokenizeText(text); if (tokens.length > 0) { // Insert default command if response doesn't start with a command if (COMMANDS.indexOf(tokens[0]) < 0) { tokens = [DEFAULT_COMMAND].concat(tokens); } while (tokens.length > 0) { // Parse command let result; switch (tokens[0]) { case 'DO': result = this.parseDoCommand(tokens); break; case 'SAY': default: result = this.parseSayCommand(tokens); break; } // Did we get a command back? if (result.length > 0) { // Add command to list if generated // - In the case of `DO DO command` the first DO command wouldn't generate if (result.command) { if (result.command.type == 'SAY') { // Check for duplicate SAY const response = result.command.response.trim().toLowerCase(); if (responses.indexOf(response) < 0) { responses += ' ' + response; plan.commands.push(result.command); } } else { plan.commands.push(result.command); } } // Remove consumed tokens tokens = result.length < tokens.length ? tokens.slice(result.length) : []; } else { // Ignore remaining tokens as something is malformed tokens = []; } } } return plan; } static parseDoCommand(tokens) { let length = 0; let command; if (tokens.length > 1) { if (tokens[0] != 'DO') { throw new Error(`ResponseParse.parseDoCommand(): token list passed in doesn't start with 'DO' token.`); } // Parse command (skips initial DO token) let actionName = ''; let entityName = ''; let entityValue = ''; let quoteType = ''; let parseState = DoCommandParseState.findActionName; while (++length < tokens.length) { // Check for ignored tokens const token = tokens[length]; if (IGNORED_TOKENS.indexOf(token) >= 0) { continue; } // Stop processing if a new command is hit // - Ignored if in a quoted string if (COMMANDS.indexOf(token) >= 0 && parseState != DoCommandParseState.inEntityStringValue) { break; } // Check for beginning of another command switch (parseState) { case DoCommandParseState.findActionName: default: // Ignore leading breaking characters if (BREAKING_CHARACTERS.indexOf(token) < 0) { // Assign token to action name and enter new state actionName = token; parseState = DoCommandParseState.inActionName; } break; case DoCommandParseState.inActionName: // Accumulate tokens until you hit a breaking character // - Underscores and dashes are allowed if (NAME_BREAKING_CHARACTERS.indexOf(token) >= 0) { // Initialize command object and enter new state command = { type: 'DO', action: actionName, entities: {} }; parseState = DoCommandParseState.findEntityName; } else { actionName += token; } break; case DoCommandParseState.findEntityName: // Ignore leading breaking characters if (BREAKING_CHARACTERS.indexOf(token) < 0) { // Assign token to entity name and enter new state entityName = token; parseState = DoCommandParseState.inEntityName; } break; case DoCommandParseState.inEntityName: // Accumulate tokens until you hit a breaking character // - Underscores and dashes are allowed if (NAME_BREAKING_CHARACTERS.indexOf(token) >= 0) { // We know the entity name so now we need the value parseState = DoCommandParseState.findEntityValue; } else { entityName += token; } break; case DoCommandParseState.findEntityValue: // Look for either string quotes first non-space or equals token if (token == '"' || token == "'" || token == '`') { // Check for content value if (token == '`' && tokens[length + 1] == '`' && tokens[length + 2] == '`') { length += 2; parseState = DoCommandParseState.inEntityContentValue; } else { // Remember quote type and enter new state quoteType = token; parseState = DoCommandParseState.inEntityStringValue; } } else if (SPACE_CHARACTERS.indexOf(token) < 0 && token != '=') { // Assign token to value and enter new state entityValue = token; parseState = DoCommandParseState.inEntityValue; } break; case DoCommandParseState.inEntityStringValue: // The following code is checking that the tokens are matching and is not exposing sensitive data // Accumulate tokens until end of string is hit // eslint-disable-next-line security/detect-possible-timing-attacks if (token === quoteType) { // Save pair and look for additional pairs command.entities[entityName] = entityValue; parseState = DoCommandParseState.findEntityName; entityName = entityValue = ''; } else { entityValue += token; } break; case DoCommandParseState.inEntityContentValue: if (token == '`' && tokens[length + 1] == '`' && tokens[length + 2] == '`') { // Save pair and look for additional pairs length += 2; command.entities[entityName] = entityValue; parseState = DoCommandParseState.findEntityName; entityName = entityValue = ''; } else { entityValue += token; } break; case DoCommandParseState.inEntityValue: // Accumulate tokens until you hit a space if (SPACE_CHARACTERS.indexOf(token) >= 0) { // Save pair and look for additional pairs command.entities[entityName] = entityValue; parseState = DoCommandParseState.findEntityName; entityName = entityValue = ''; } else { entityValue += token; } break; } } // Create command if not created // - This happens when a DO command without any entities is at the end of the response. if (!command && actionName) { command = { type: 'DO', action: actionName, entities: {} }; } // Append final entity if (command && entityName) { command.entities[entityName] = entityValue; } } return { length, command }; } static parseSayCommand(tokens) { let length = 0; let command; if (tokens.length > 1) { if (tokens[0] != 'SAY') { throw new Error(`ResponseParse.parseSayCommand(): token list passed in doesn't start with 'SAY' token.`); } // Parse command (skips initial DO token) let response = ''; while (++length < tokens.length) { // Check for ignored tokens const token = tokens[length]; if (IGNORED_TOKENS.indexOf(token) >= 0) { continue; } // Stop processing if a new command is hit if (COMMANDS.indexOf(token) >= 0) { break; } // Append token to output response response += token; } // Create command if (response.length > 0) { command = { type: 'SAY', response: response }; } } return { length, command }; } /** * Simple text tokenizer. Breaking characters are added to list as separate tokens. * * @param {string} text Optional. Text string to tokenize. * @returns {[string]} Array of tokens. */ static tokenizeText(text) { const tokens = []; if (text) { let token = ''; const len = text.length; for (let i = 0; i < len; i++) { const ch = text[i]; if (BREAKING_CHARACTERS.indexOf(ch) >= 0) { // Push token onto list if (token.length > 0) { tokens.push(token); } // Push breaking character onto list as a separate token tokens.push(ch); // Start a new empty token token = ''; } else { // Add to existing token token += ch; } } // Push last token onto list if (token.length > 0) { tokens.push(token); } } return tokens; } } exports.ResponseParser = ResponseParser; var DoCommandParseState; (function (DoCommandParseState) { DoCommandParseState[DoCommandParseState["findActionName"] = 0] = "findActionName"; DoCommandParseState[DoCommandParseState["inActionName"] = 1] = "inActionName"; DoCommandParseState[DoCommandParseState["findEntityName"] = 2] = "findEntityName"; DoCommandParseState[DoCommandParseState["inEntityName"] = 3] = "inEntityName"; DoCommandParseState[DoCommandParseState["findEntityValue"] = 4] = "findEntityValue"; DoCommandParseState[DoCommandParseState["inEntityValue"] = 5] = "inEntityValue"; DoCommandParseState[DoCommandParseState["inEntityStringValue"] = 6] = "inEntityStringValue"; DoCommandParseState[DoCommandParseState["inEntityContentValue"] = 7] = "inEntityContentValue"; })(DoCommandParseState || (DoCommandParseState = {})); //# sourceMappingURL=ResponseParser.js.map