Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
{{#each data.botMessages}}
{
"chatId": "{{../data.chatId}}",
"content": "{{filterControlCharacters result}}",
"content": "{{{filterControlCharacters (escapeQuotes result)}}}",
"buttons": "[{{#each ../data.buttons}}{\"title\": \"{{#if (eq title true)}}Yes{{else if (eq title false)}}No{{else}}{{{title}}}{{/if}}\",\"payload\": \"{{{payload}}}\"}{{#unless @last}},{{/unless}}{{/each}}]",
"authorTimestamp": "{{../data.authorTimestamp}}",
"authorId": "{{../data.authorId}}",
Expand Down
15 changes: 13 additions & 2 deletions GUI/src/components/Flow/NodeTypes/StepNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
import Track from 'components/Track';
import { FC, memo, useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import sanitizeHtml from 'sanitize-html';
import useServiceStore from 'store/new-services.store';
import { StepType } from 'types';
import { NodeDataProps } from 'types/service-flow';
import { validateStep } from 'utils/flow-utils';
import { decodeHtmlEntities } from 'utils/string-util';

type StepNodeProps = {
data: NodeDataProps;
Expand All @@ -21,8 +23,17 @@
fontWeight: 500,
};
const createMarkup = (text: string) => {
const decodedText = decodeHtmlEntities(text);
const sanitizedText = sanitizeHtml(decodedText, {
allowedTags: ['a', 'b', 'strong', 'i', 'em', 'u', 'p', 'br', 'ul', 'ol', 'li', 'code'],
allowedAttributes: {
a: ['href', 'target', 'rel'],
},
disallowedTagsMode: 'discard',
});

return {
__html: text,
__html: sanitizedText,
};
};

Expand Down Expand Up @@ -52,7 +63,7 @@
}, [data, endpoints]);

useEffect(() => {
void updateIsTestedAndPassed();
updateIsTestedAndPassed();

Check warning on line 66 in GUI/src/components/Flow/NodeTypes/StepNode.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint / run-check

Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator
}, [updateIsTestedAndPassed]);

return (
Expand Down
17 changes: 14 additions & 3 deletions GUI/src/components/Markdowify/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,16 @@

const hasSpecialFormat = (m: string) => m.includes('\n\n') && m.indexOf('.') > 0 && m.indexOf(':') > m.indexOf('.');

const htmlLinkToMarkdown = (value: string): string =>
value.replaceAll(
/<a\s+[^>]*href\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))[^>]*>((?:[^<]|<(?!\/a>))*)<\/a>/gi,

Check failure on line 77 in GUI/src/components/Markdowify/index.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint / run-check

Simplify this regular expression to reduce its complexity from 25 to the 20 allowed

Check failure on line 77 in GUI/src/components/Markdowify/index.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint / run-check

Make sure the regex used here, which is vulnerable to super-linear runtime due to backtracking, cannot lead to denial of service

Check warning on line 77 in GUI/src/components/Markdowify/index.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Simplify this regular expression to reduce its complexity from 25 to the 20 allowed.

See more on https://sonarcloud.io/project/issues?id=buerokratt_Service-Module&issues=AZ0LlejZaTjx2ZaKcKCb&open=AZ0LlejZaTjx2ZaKcKCb&pullRequest=942
(_, href1: string, href2: string, href3: string, label: string) => {
const href = (href1 ?? href2 ?? href3 ?? '').trim();
const text = sanitizeHtml(label ?? '', { allowedTags: [], allowedAttributes: {} }).trim() || href;
return href ? `[${text}](${href})` : text;
},
);

function formatMessage(message?: string): string {
const sanitizedMessage = sanitizeHtml(message ?? '');

Expand All @@ -87,9 +97,10 @@
dataImagePattern,
(_, prefix, dataUrl) => `${prefix}[image](${dataUrl})`,
);
const markdownLinksMessage = htmlLinkToMarkdown(finalMessage);

return finalMessage
.replaceAll(/&#x([0-9A-F]+);/gi, (_, hex: string) => String.fromCharCode(parseInt(hex, 16)))
return markdownLinksMessage
.replaceAll(/&#x([0-9A-F]+);/gi, (_, hex: string) => String.fromCodePoint(Number.parseInt(hex, 16)))
.replaceAll('&amp;', '&')
.replaceAll('&gt;', '>')
.replaceAll('&lt;', '<')
Expand All @@ -105,7 +116,7 @@
return `${prefix}${year}. `;
}
}
return `${prefix}${year}\\. `;
return String.raw`${prefix}${year}\. `;
})
.replaceAll(/(?<=\n)\d+\.\s/g, hasSpecialFormat(finalMessage) ? '\n\n$&' : '$&')
.replaceAll(/^(\s+)/g, (match) => match.replaceAll(' ', '&nbsp;'));
Expand Down
1 change: 0 additions & 1 deletion GUI/src/components/chat/bot-message.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ interface ChatMessageProps {

const BotMessage = ({ message }: ChatMessageProps) => {
const renderContent = useCallback(() => {
if (message.message.startsWith('<p>')) return message.message.replace('<p>', '').replace('</p>', '');
return <Markdownify message={message.message} />;
}, [message.message]);

Expand Down
28 changes: 24 additions & 4 deletions GUI/src/components/chat/chat.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,16 @@
.content {
border-radius: 6px 48px 48px 29px;
background-color: blue;
color: white;

a {
color: #a8d4ff;
text-decoration: underline;

&:visited {
color: #c8a8ff;
}
}
}

.icon {
Expand Down Expand Up @@ -223,7 +233,7 @@
pointer-events: none;
user-select: none;
background-color: #f5f5f5;
color: #999;
color: #696969;
}
}

Expand Down Expand Up @@ -268,18 +278,18 @@
font-weight: bold;

&.success {
color: rgb(0, 138, 0);
color: rgb(0, 100, 0);
background: #0f02;
}

&.error {
color: rgb(170, 0, 0);
color: rgb(80, 0, 0);
background: #f002;

Check warning on line 287 in GUI/src/components/chat/chat.module.scss

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Text does not meet the minimal contrast requirement with its background.

See more on https://sonarcloud.io/project/issues?id=buerokratt_Service-Module&issues=AZ0LZCmckUufV0FpBmyz&open=AZ0LZCmckUufV0FpBmyz&pullRequest=942
}

&.info {
color: rgb(0, 0, 203);
color: rgb(0, 0, 80);
background: #00f2;

Check warning on line 292 in GUI/src/components/chat/chat.module.scss

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Text does not meet the minimal contrast requirement with its background.

See more on https://sonarcloud.io/project/issues?id=buerokratt_Service-Module&issues=AZ0LZCmckUufV0FpBmy0&open=AZ0LZCmckUufV0FpBmy0&pullRequest=942
}

&.normal {
Expand Down Expand Up @@ -378,6 +388,16 @@
.main {
.content {
background-color: get-color(sapphire-blue-12);
color: var(--dark-bg-extra-light);

a {
color: #a8d4ff;
text-decoration: underline;

&:visited {
color: #c8a8ff;
}
}
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions GUI/src/services/service-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import { EndpointData } from 'types/endpoint';
import { NodeDataProps } from 'types/service-flow';
import {
decodeHtmlEntities,
getLastDigits,
isNumericString,
removeTrailingUnderscores,
Expand Down Expand Up @@ -186,9 +187,9 @@
try {
let yamlContent = getYamlContent(nodes, edges, name, description, showError);

const mcqNodes = nodes.filter(
(node) => node.data?.stepType === StepType.MultiChoiceQuestion,
) as Node<NodeDataProps>[];

Check warning on line 192 in GUI/src/services/service-builder.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

This assertion is unnecessary since it does not change the type of the expression.

See more on https://sonarcloud.io/project/issues?id=buerokratt_Service-Module&issues=AZ0LZCrNkUufV0FpBmzD&open=AZ0LZCrNkUufV0FpBmzD&pullRequest=942

if (mcqNodes.length > 0) {
const nodesUpToFirstMcq = nodes.slice(
Expand Down Expand Up @@ -420,7 +421,7 @@
try {
allRelations.forEach((r) => {
const [parentNodeId, childNodeId] = r.split(',');
const parentNode = nodes.findLast((node) => node.id === parentNodeId) as Node<NodeDataProps> | undefined;

Check warning on line 424 in GUI/src/services/service-builder.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

This assertion is unnecessary since it does not change the type of the expression.

See more on https://sonarcloud.io/project/issues?id=buerokratt_Service-Module&issues=AZ0LZCrNkUufV0FpBmzE&open=AZ0LZCrNkUufV0FpBmzE&pullRequest=942
if (
!parentNode?.type ||
parentNode.type !== 'custom' ||
Expand All @@ -429,7 +430,7 @@
return;
}

const childNode = nodes.find((node) => node.id === childNodeId) as Node<NodeDataProps> | undefined;

Check warning on line 433 in GUI/src/services/service-builder.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

This assertion is unnecessary since it does not change the type of the expression.

See more on https://sonarcloud.io/project/issues?id=buerokratt_Service-Module&issues=AZ0LZCrNkUufV0FpBmzF&open=AZ0LZCrNkUufV0FpBmzF&pullRequest=942
const parentStepName = toSnakeCase(parentNode.data.label);

if (parentNode.data.stepType === StepType.Textfield) {
Expand Down Expand Up @@ -602,11 +603,10 @@
});

const spacePlaceholder = '___SPACE___';
const rawMessage = typeof parentNode.data.message === 'string' ? decodeHtmlEntities(parentNode.data.message) : '';
const markdownMessage = htmlToMarkdown
.translate(
typeof parentNode.data.message === 'string'
? parentNode.data.message.replace('{{', '${').replace('}}', '}').replaceAll(' ', spacePlaceholder)
: '',
rawMessage.replace('{{', '${').replace('}}', '}').replaceAll(' ', spacePlaceholder),
)
.replaceAll(spacePlaceholder, ' ')
.replaceAll(/\\([-~>[\]_*#().!`=<\\])/g, String.raw`\\$1`);
Expand Down Expand Up @@ -640,8 +640,8 @@
const firstChildNode = conditionRelations[0].split(',')[1];
const secondChildNode = conditionRelations[1].split(',')[1];

const firstChild = nodes.find((node) => node.id === firstChildNode) as Node<NodeDataProps> | undefined;

Check warning on line 643 in GUI/src/services/service-builder.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

This assertion is unnecessary since it does not change the type of the expression.

See more on https://sonarcloud.io/project/issues?id=buerokratt_Service-Module&issues=AZ0LZCrNkUufV0FpBmzG&open=AZ0LZCrNkUufV0FpBmzG&pullRequest=942
const secondChild = nodes.find((node) => node.id === secondChildNode) as Node<NodeDataProps> | undefined;

Check warning on line 644 in GUI/src/services/service-builder.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

This assertion is unnecessary since it does not change the type of the expression.

See more on https://sonarcloud.io/project/issues?id=buerokratt_Service-Module&issues=AZ0LZCrNkUufV0FpBmzH&open=AZ0LZCrNkUufV0FpBmzH&pullRequest=942

const rulesChildren = Array.isArray(parentNode.data.rules?.children) ? parentNode.data.rules.children : [];
const invalidRulesExist = hasInvalidRules(rulesChildren);
Expand Down Expand Up @@ -880,7 +880,7 @@
const nodes = useServiceStore.getState().nodes as Node<NodeDataProps>[];

await saveFlow({
name: !name

Check warning on line 883 in GUI/src/services/service-builder.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Unexpected negated condition.

See more on https://sonarcloud.io/project/issues?id=buerokratt_Service-Module&issues=AZ0LZCrNkUufV0FpBmzI&open=AZ0LZCrNkUufV0FpBmzI&pullRequest=942
? `${t('newService.defaultServiceName').toString()}_${format(new Date(), 'dd_MM_yyyy_HH_mm_ss')}`
: name,
edges,
Expand Down
26 changes: 23 additions & 3 deletions GUI/src/utils/string-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@
};

export const toSnakeCase = (value: string) => {
return value.toLowerCase().trim().replace(/\s+/g, '_').replace(/-+/g, '_').replace(/_+/g, '_');
return value.toLowerCase().trim().replaceAll(/\s+/g, '_').replaceAll(/-+/g, '_').replaceAll(/_+/g, '_');
};

export const fromSnakeCase = (value: string) => {
const parts = value.split('_');

// Check if the last part is a number
const lastPart = parts[parts.length - 1];

Check warning on line 34 in GUI/src/utils/string-util.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `.at(…)` over `[….length - index]`.

See more on https://sonarcloud.io/project/issues?id=buerokratt_Service-Module&issues=AZ0LZCq1kUufV0FpBmy8&open=AZ0LZCq1kUufV0FpBmy8&pullRequest=942
const isLastPartNumber = /^\d+$/.test(lastPart);

if (isLastPartNumber && parts.length > 1) {
Expand Down Expand Up @@ -96,7 +96,7 @@
changed = false;
iterationCount++;

str = str.replace(
str = str.replaceAll(
/\$\{([^${}]*)\$\{([^}]*)\}([^}]*)\}/g,
(match: string, p1: string, p2: string, p3: string): string => {
changed = true;
Expand All @@ -116,7 +116,7 @@
changed = false;
iterationCount++;

str = str.replace(
str = str.replaceAll(
/\$\{([^{}]*)\{([^}]*)\}([^}]*)\}/g,
(match: string, p1: string, p2: string, p3: string): string => {
changed = true;
Expand Down Expand Up @@ -151,3 +151,23 @@

return str.substring(start, end + 1);
}

export function decodeHtmlEntities(value: string): string {
if (typeof value !== 'string' || value.length === 0) return value;

if (typeof document !== 'undefined') {
const textarea = document.createElement('textarea');
textarea.innerHTML = value;
return textarea.value;
}

return value
.replaceAll(/&#x([0-9A-F]+);/gi, (_, hex: string) => String.fromCodePoint(Number.parseInt(hex, 16)))
.replaceAll(/&#(\d+);/g, (_, code: string) => String.fromCodePoint(Number.parseInt(code, 10)))
.replaceAll('&amp;', '&')
.replaceAll('&gt;', '>')
.replaceAll('&lt;', '<')
.replaceAll('&quot;', '"')
.replaceAll('&#39;', "'")
.replaceAll('&apos;', "'");
}
Loading