WebShare: Prevent external formatting insertion (#3921)
This commit is contained in:
parent
d893ca6cfe
commit
3e59a07012
@ -93,6 +93,7 @@ import { IS_IOS, IS_VOICE_RECORDING_SUPPORTED } from '../../util/windowEnvironme
|
||||
import windowSize from '../../util/windowSize';
|
||||
import applyIosAutoCapitalizationFix from '../middle/composer/helpers/applyIosAutoCapitalizationFix';
|
||||
import buildAttachment, { prepareAttachmentsToSend } from '../middle/composer/helpers/buildAttachment';
|
||||
import { escapeHtml } from '../middle/composer/helpers/cleanHtml';
|
||||
import { buildCustomEmojiHtml } from '../middle/composer/helpers/customEmoji';
|
||||
import { isSelectionInsideInput } from '../middle/composer/helpers/selection';
|
||||
import renderText from './helpers/renderText';
|
||||
@ -1038,7 +1039,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
|
||||
useEffect(() => {
|
||||
if (requestedDraftText) {
|
||||
setHtml(requestedDraftText);
|
||||
setHtml(escapeHtml(requestedDraftText));
|
||||
resetOpenChatWithDraft();
|
||||
|
||||
requestNextMutation(() => {
|
||||
|
||||
65
src/components/middle/composer/helpers/cleanHtml.ts
Normal file
65
src/components/middle/composer/helpers/cleanHtml.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import { ApiMessageEntityTypes } from '../../../../api/types';
|
||||
|
||||
import { DEBUG } from '../../../../config';
|
||||
import cleanDocsHtml from '../../../../lib/cleanDocsHtml';
|
||||
import { ENTITY_CLASS_BY_NODE_NAME } from '../../../../util/parseMessageInput';
|
||||
|
||||
const STYLE_TAG_REGEX = /<style>(.*?)<\/style>/gs;
|
||||
|
||||
export function preparePastedHtml(html: string) {
|
||||
let fragment = document.createElement('div');
|
||||
try {
|
||||
html = cleanDocsHtml(html);
|
||||
} catch (err) {
|
||||
if (DEBUG) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
fragment.innerHTML = html.replace(/\u00a0/g, ' ').replace(STYLE_TAG_REGEX, ''); // Strip   and styles
|
||||
|
||||
const textContents = fragment.querySelectorAll<HTMLDivElement>('.text-content');
|
||||
if (textContents.length) {
|
||||
fragment = textContents[textContents.length - 1]; // Replace with the last copied message
|
||||
}
|
||||
|
||||
Array.from(fragment.getElementsByTagName('*')).forEach((node) => {
|
||||
if (!(node instanceof HTMLElement)) {
|
||||
node.remove();
|
||||
return;
|
||||
}
|
||||
node.removeAttribute('style');
|
||||
|
||||
// Fix newlines
|
||||
if (node.tagName === 'BR') node.replaceWith('\n');
|
||||
if (node.tagName === 'P') node.appendChild(document.createTextNode('\n'));
|
||||
if (node.tagName === 'IMG' && !node.dataset.entityType) node.replaceWith(node.getAttribute('alt') || '');
|
||||
// We do not intercept copy logic, so we remove some nodes here
|
||||
if (node.dataset.ignoreOnPaste) node.remove();
|
||||
|
||||
if (ENTITY_CLASS_BY_NODE_NAME[node.tagName]) {
|
||||
node.setAttribute('data-entity-type', ENTITY_CLASS_BY_NODE_NAME[node.tagName]);
|
||||
}
|
||||
// Strip non-entity tags
|
||||
if (!node.dataset.entityType && node.textContent === node.innerText) node.replaceWith(node.textContent);
|
||||
// Append entity parameters for parsing
|
||||
if (node.dataset.alt) node.setAttribute('alt', node.dataset.alt);
|
||||
switch (node.dataset.entityType) {
|
||||
case ApiMessageEntityTypes.MentionName:
|
||||
node.replaceWith(node.textContent || '');
|
||||
break;
|
||||
case ApiMessageEntityTypes.CustomEmoji:
|
||||
node.textContent = node.dataset.alt || '';
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
return fragment.innerHTML.trimEnd();
|
||||
}
|
||||
|
||||
export function escapeHtml(html: string) {
|
||||
const fragment = document.createElement('div');
|
||||
const text = document.createTextNode(html);
|
||||
fragment.appendChild(text);
|
||||
return fragment.innerHTML;
|
||||
}
|
||||
@ -2,72 +2,22 @@ import type { StateHookSetter } from '../../../../lib/teact/teact';
|
||||
import { useEffect } from '../../../../lib/teact/teact';
|
||||
|
||||
import type { ApiAttachment, ApiFormattedText, ApiMessage } from '../../../../api/types';
|
||||
import { ApiMessageEntityTypes } from '../../../../api/types';
|
||||
|
||||
import {
|
||||
DEBUG, EDITABLE_INPUT_ID, EDITABLE_INPUT_MODAL_ID, EDITABLE_STORY_INPUT_ID,
|
||||
EDITABLE_INPUT_ID, EDITABLE_INPUT_MODAL_ID, EDITABLE_STORY_INPUT_ID,
|
||||
} from '../../../../config';
|
||||
import cleanDocsHtml from '../../../../lib/cleanDocsHtml';
|
||||
import { containsCustomEmoji, stripCustomEmoji } from '../../../../global/helpers/symbols';
|
||||
import parseMessageInput, { ENTITY_CLASS_BY_NODE_NAME } from '../../../../util/parseMessageInput';
|
||||
import parseMessageInput from '../../../../util/parseMessageInput';
|
||||
import buildAttachment from '../helpers/buildAttachment';
|
||||
import { preparePastedHtml } from '../helpers/cleanHtml';
|
||||
import getFilesFromDataTransferItems from '../helpers/getFilesFromDataTransferItems';
|
||||
|
||||
const MAX_MESSAGE_LENGTH = 4096;
|
||||
|
||||
const STYLE_TAG_REGEX = /<style>(.*?)<\/style>/gs;
|
||||
const TYPE_HTML = 'text/html';
|
||||
const DOCUMENT_TYPE_WORD = 'urn:schemas-microsoft-com:office:word';
|
||||
const NAMESPACE_PREFIX_WORD = 'xmlns:w';
|
||||
|
||||
function preparePastedHtml(html: string) {
|
||||
let fragment = document.createElement('div');
|
||||
try {
|
||||
html = cleanDocsHtml(html);
|
||||
} catch (err) {
|
||||
if (DEBUG) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
fragment.innerHTML = html.replace(/\u00a0/g, ' ').replace(STYLE_TAG_REGEX, ''); // Strip   and styles
|
||||
|
||||
const textContents = fragment.querySelectorAll<HTMLDivElement>('.text-content');
|
||||
if (textContents.length) {
|
||||
fragment = textContents[textContents.length - 1]; // Replace with the last copied message
|
||||
}
|
||||
|
||||
Array.from(fragment.getElementsByTagName('*')).forEach((node) => {
|
||||
if (!(node instanceof HTMLElement)) return;
|
||||
node.removeAttribute('style');
|
||||
|
||||
// Fix newlines
|
||||
if (node.tagName === 'BR') node.replaceWith('\n');
|
||||
if (node.tagName === 'P') node.appendChild(document.createTextNode('\n'));
|
||||
if (node.tagName === 'IMG' && !node.dataset.entityType) node.replaceWith(node.getAttribute('alt') || '');
|
||||
// We do not intercept copy logic, so we remove some nodes here
|
||||
if (node.dataset.ignoreOnPaste) node.remove();
|
||||
|
||||
if (ENTITY_CLASS_BY_NODE_NAME[node.tagName]) {
|
||||
node.setAttribute('data-entity-type', ENTITY_CLASS_BY_NODE_NAME[node.tagName]);
|
||||
}
|
||||
// Strip non-entity tags
|
||||
if (!node.dataset.entityType && node.textContent === node.innerText) node.replaceWith(node.textContent);
|
||||
// Append entity parameters for parsing
|
||||
if (node.dataset.alt) node.setAttribute('alt', node.dataset.alt);
|
||||
switch (node.dataset.entityType) {
|
||||
case ApiMessageEntityTypes.MentionName:
|
||||
node.replaceWith(node.textContent || '');
|
||||
break;
|
||||
case ApiMessageEntityTypes.CustomEmoji:
|
||||
node.textContent = node.dataset.alt || '';
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
return fragment.innerHTML.trimEnd();
|
||||
}
|
||||
|
||||
const useClipboardPaste = (
|
||||
isActive: boolean,
|
||||
insertTextAndUpdateCursor: (text: ApiFormattedText, inputId?: string) => void,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user