Skip to content

Commit

Permalink
Stripes 953v2 - Export sanitize logic for usage at the module level. (#…
Browse files Browse the repository at this point in the history
…79)

* Only use DOMPurify's output if it changed something

* export sanitize functionality for the module level so that sanitization can happen prior to the form state loop

* actually add sanitizer.js

* rename export to SANITIZE_CONFIG, document module-level sanitization

* docs tweaks

* document return type

* Update CHANGELOG.md
  • Loading branch information
JohnC-80 authored Dec 10, 2024
1 parent 266441e commit 8071177
Show file tree
Hide file tree
Showing 6 changed files with 56 additions and 12 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## IN PROGRESS
* Only change `value` prop to `ReactQuill` if `DOMPurify` made changes. Refs STRIPES-953.
* Export `sanitize` function for module-level value sanitization. Refs STRIPES-953 also.

## [3.4.1](https://github.com/folio-org/stripes-template-editor/tree/v3.4.1) (2024-11-13)
[Full Changelog](https://github.com/folio-org/stripes-template-editor/compare/v3.4.0...v3.4.1)
Expand Down
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,40 @@ Version 2.0. See the file "[LICENSE](LICENSE)" for more information.

This is a NPM module to aid with embedding the Quill editor in [Stripes](https://github.com/folio-org/stripes-core/) applications for building templates with token substitution.


## Value Sanitization

In any case where a user-created HTML string will be rendered directly to the UI, it should be sanitized to eliminate any issues with malformed tags/attributes. This library exports a `sanitize` function that should be used within the ui-module prior to passing the value to the form. The function accepts the value to be rendered and an optional overriding configuration for the sanitization library. It will return the sanitized string if any removals were necessary, otherwise it will return the original parameter value.

```
import { sanitize, TemplateEditor } from '@folio/stripes-template-editor'
const value = persistedValue // value obtained from backend...
const appliedValue = sanitize(value);
<Form initialValues={{ template: appliedValue }}>
<Field component="TemplateEditor">
</Form>
```

If the sanitization needs to be adjusted for specific use-cases, it can be imported and extended...

```
import { SANITIZE_CONFIG } from '@folio/stripes-template-editor`;
const localConfig = { ...SANITIZE_CONFIG, ...MY_CONFIG, };
const appliedValue = sanitize(value, localConfig);
```

For the configuration possibilities, reference the [`DOMPurify` configuration details](https://github.com/cure53/DOMPurify?tab=readme-ov-file#can-i-configure-dompurify) if needed!


## Attribution

@skomorokh extracted this from ui-circulation in [this commit](https://github.com/folio-org/ui-circulation/commit/ead94d580d7e0be4e8b9f17d9fc99a2e43fb8cae). The code was largely written by @maximdidenkoepam and @skomorokh probably should have made more of an effort to bring the commit history along. However, you can view it at the originating module.
Expand Down
2 changes: 1 addition & 1 deletion index.js
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { TemplateEditor, PreviewModal, TokensSection, tokensReducer } from './src';
export { TemplateEditor, PreviewModal, TokensSection, tokensReducer, sanitize, SANITIZE_CONFIG } from './src';
14 changes: 3 additions & 11 deletions src/TemplateEditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import ReactQuill, { Quill } from 'react-quill';
import { v4 as uuidv4 } from 'uuid';
import DOMPurify from 'dompurify';

import {
isNull,
forEach,
Expand All @@ -19,6 +19,7 @@ import EditorToolbar from './EditorToolbar';
import PreviewModal from './PreviewModal';
import ControlHeader from './ControlHeader';
import ValidationContainer from './ValidationContainer';
import { sanitize } from './sanitizer';

import tokensReducer from './tokens-reducer';
import IndentStyle from './Attributors/indent';
Expand Down Expand Up @@ -212,16 +213,7 @@ class TemplateEditor extends React.Component {

const invalid = (touched || submitFailed) && !valid && !showTokensDialog;

// DOMPurify reverses the order of attributes, so any supplied tags with attributes will ALWAYS
// have different output - so we just check for any changes DOMPurify might have made before using
// the string it produces.
let appliedValue = DOMPurify.sanitize(value, { ADD_TAGS: ['Barcode'], ADD_ATTR: ['target', 'rel'] });
if (value !== appliedValue) {
const removed = DOMPurify.removed.map((item) => item.attribute?.name || item.element?.outerHTML);
if (removed && removed.length === 0) {
appliedValue = value;
}
}
const appliedValue = sanitize(value);

return (
<>
Expand Down
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export { default as TemplateEditor } from './TemplateEditor';
export { default as PreviewModal } from './PreviewModal';
export { default as TokensSection } from './TokensSection';
export { default as tokensReducer } from './tokens-reducer';
export { sanitize, SANITIZE_CONFIG } from './sanitizer';
16 changes: 16 additions & 0 deletions src/sanitizer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import DOMPurify from 'dompurify';

export const SANITIZE_CONFIG = { ADD_TAGS: ['Barcode'], ADD_ATTR: ['target', 'rel'] };

export const sanitize = (value, config = SANITIZE_CONFIG) => {
// since DOMPurify has a known issue of reversing the order of attributes in perfectly admissible HTML
// we check to see if the value was affected - and if not, we just return the unaffected value.
let resultValue = DOMPurify.sanitize(value, config);
if (value !== resultValue) {
const removed = DOMPurify.removed.map((item) => item.attribute?.name || item.element?.outerHTML);
if (removed && removed.length === 0) {
resultValue = value;
}
}
return resultValue;
};

0 comments on commit 8071177

Please sign in to comment.