Skip to content

Commit

Permalink
Merge pull request #1463 from microsoft/u/juliaroldi/sanitizeLinks
Browse files Browse the repository at this point in the history
Clear local file paths and remove link
  • Loading branch information
juliaroldi authored Dec 9, 2022
2 parents b99aa8b + 871ca69 commit 3add441
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 1 deletion.
3 changes: 2 additions & 1 deletion packages/roosterjs-editor-plugins/lib/plugins/Paste/Paste.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import convertPastedContentFromWord from './wordConverter/convertPastedContentFr
import getPasteSource from './sourceValidations/getPasteSource';
import handleLineMerge from './lineMerge/handleLineMerge';
import sanitizeHtmlColorsFromPastedContent from './sanitizeHtmlColorsFromPastedContent/sanitizeHtmlColorsFromPastedContent';
import sanitizeLinks from './sanitizeLinks/sanitizeLinks';
import { EditorPlugin, IEditor, PluginEvent, PluginEventType } from 'roosterjs-editor-types';
import { GOOGLE_SHEET_NODE_NAME } from './sourceValidations/constants';
import { KnownSourceType } from './sourceValidations/KnownSourceType';
Expand Down Expand Up @@ -83,7 +84,7 @@ export default class Paste implements EditorPlugin {
handleLineMerge(fragment);
break;
}

sanitizeLinks(sanitizingOption);
sanitizeHtmlColorsFromPastedContent(sanitizingOption);

// Replace unknown tags with SPAN
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { chainSanitizerCallback } from 'roosterjs-editor-dom';
import { HtmlSanitizerOptions } from 'roosterjs-editor-types';

const HTTP = 'http:';
const HTTPS = 'https:';

/**
* @internal
* Clear local paths and remove link
* @param sanitizingOption the sanitizingOption of BeforePasteEvent
* */
export default function sanitizeLinks(sanitizingOption: Required<HtmlSanitizerOptions>) {
chainSanitizerCallback(
sanitizingOption.attributeCallbacks,
'href',
(value: string, element: HTMLElement) => validateLink(value, element)
);
}

function validateLink(link: string, htmlElement: HTMLElement) {
let url;
try {
url = new URL(link);
} catch {
url = undefined;
}

if (url && (url.protocol === HTTP || url.protocol === HTTPS)) {
return link;
}
htmlElement.removeAttribute('href');
return '';
}
80 changes: 80 additions & 0 deletions packages/roosterjs-editor-plugins/test/paste/sanitizeLinksTest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import sanitizeLinks from '../../lib/plugins/Paste/sanitizeLinks/sanitizeLinks';
import { HtmlSanitizer } from 'roosterjs-editor-dom';
import {
BeforePasteEvent,
SanitizeHtmlOptions,
PluginEventType,
ClipboardData,
} from 'roosterjs-editor-types';

describe('sanitizeLinks', () => {
function callSanitizer(fragment: DocumentFragment, sanitizingOption: SanitizeHtmlOptions) {
const sanitizer = new HtmlSanitizer(sanitizingOption);
sanitizer.convertGlobalCssToInlineCss(fragment);
sanitizer.sanitize(fragment);
}

function runTest(source: string, expected: string) {
const doc = new DOMParser().parseFromString(source, 'text/html');
const fragment = doc.createDocumentFragment();
while (doc.body.firstChild) {
fragment.appendChild(doc.body.firstChild);
}

const event = createBeforePasteEventMock(fragment);
sanitizeLinks(event.sanitizingOption);
callSanitizer(fragment, event.sanitizingOption);

while (fragment.firstChild) {
doc.body.appendChild(fragment.firstChild);
}

expect(doc.body.innerHTML).toBe(expected);
}

it('sanitize anchor', () => {
runTest('<a href="/text.txt"></a>', '<a></a>');
});

it('not sanitize anchor', () => {
runTest(
'<a href="https://microsoft.github.io/"></a>',
'<a href="https://microsoft.github.io/"></a>'
);
});

it('sanitize div', () => {
runTest('<div><a href="/text.txt"></a></div>', '<div><a></a></div>');
});

it('not sanitize div', () => {
runTest(
'<div><a href="https://microsoft.github.io/"></a></div>',
'<div><a href="https://microsoft.github.io/"></a></div>'
);
});
});

function createBeforePasteEventMock(fragment: DocumentFragment) {
return ({
eventType: PluginEventType.BeforePaste,
clipboardData: <ClipboardData>{},
fragment: fragment,
sanitizingOption: {
elementCallbacks: {},
attributeCallbacks: {},
cssStyleCallbacks: {},
additionalTagReplacements: {},
additionalAllowedAttributes: [],
additionalAllowedCssClasses: [],
additionalDefaultStyleValues: {},
additionalGlobalStyleNodes: [],
additionalPredefinedCssForElement: {},
preserveHtmlComments: false,
unknownTagReplacement: null,
},
htmlBefore: '',
htmlAfter: '',
htmlAttributes: {},
} as unknown) as BeforePasteEvent;
}

0 comments on commit 3add441

Please sign in to comment.