diff --git a/web/ui/react-app/package.json b/web/ui/react-app/package.json index d0e5c959b..11d3c3b63 100644 --- a/web/ui/react-app/package.json +++ b/web/ui/react-app/package.json @@ -11,6 +11,7 @@ "@types/node": "^12.11.1", "@types/react": "^16.8.2", "@types/react-dom": "^16.8.0", + "@types/sanitize-html": "^1.20.2", "@types/react-resize-detector": "^4.0.2", "bootstrap": "^4.2.1", "downshift": "^3.2.2", @@ -25,6 +26,7 @@ "popper.js": "^1.14.3", "react": "^16.7.0", "react-dom": "^16.7.0", + "sanitize-html": "^1.20.1", "react-resize-detector": "^4.2.1", "react-scripts": "^3.2.0", "reactstrap": "^8.0.1", diff --git a/web/ui/react-app/src/ExpressionInput.tsx b/web/ui/react-app/src/ExpressionInput.tsx index 77bdfc672..338145f85 100644 --- a/web/ui/react-app/src/ExpressionInput.tsx +++ b/web/ui/react-app/src/ExpressionInput.tsx @@ -10,6 +10,7 @@ import { import Downshift from 'downshift'; import fuzzy from 'fuzzy'; +import SanitizeHTML from './components/SanitizeHTML'; import { library } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; @@ -72,9 +73,9 @@ class ExpressionInput extends Component { }, })} > - {/* TODO: Find better way than setting inner HTML dangerously. We just want the to not be escaped. - This will be a problem when we save history and the user enters HTML into a query. */} - + + {item.string} + )) } @@ -95,63 +96,68 @@ class ExpressionInput extends Component { render() { return ( - - {(downshift) => ( -
- - - - {this.props.loading ? : } - - - - { - switch (event.key) { - case 'Home': - case 'End': - // We want to be able to jump to the beginning/end of the input field. - // By default, Downshift otherwise jumps to the first/last suggestion item instead. + + {(downshift) => ( +
+ + + + {this.props.loading ? : } + + + { + switch (event.key) { + case 'Home': + case 'End': + // We want to be able to jump to the beginning/end of the input field. + // By default, Downshift otherwise jumps to the first/last suggestion item instead. + (event.nativeEvent as any).preventDownshiftDefault = true; + break; + case 'ArrowUp': + case 'ArrowDown': + if (!downshift.isOpen) { (event.nativeEvent as any).preventDownshiftDefault = true; - break; - case 'ArrowUp': - case 'ArrowDown': - if (!downshift.isOpen) { - (event.nativeEvent as any).preventDownshiftDefault = true; - } - break; - case 'Enter': - downshift.closeMenu(); - break; - case 'Escape': - if (!downshift.isOpen) { - this.exprInputRef.current!.blur(); - } - break; - default: - } + } + break; + case 'Enter': + downshift.closeMenu(); + break; + case 'Escape': + if (!downshift.isOpen) { + this.exprInputRef.current!.blur(); + } + break; + default: } - } as any)} - /> - - - - - {this.renderAutosuggest(downshift)} -
- )} -
+ } + } as any)} + /> + + + +
+ {this.renderAutosuggest(downshift)} +
+ )} +
); } } diff --git a/web/ui/react-app/src/components/SanitizeHTML/index.tsx b/web/ui/react-app/src/components/SanitizeHTML/index.tsx new file mode 100644 index 000000000..89ec470e9 --- /dev/null +++ b/web/ui/react-app/src/components/SanitizeHTML/index.tsx @@ -0,0 +1,30 @@ +/** + * SanitizeHTML to render HTML, this takes care of sanitizing HTML. + */ +import React, { PureComponent } from 'react'; +import sanitizeHTML from 'sanitize-html'; + +interface SanitizeHTMLProps { + inline: Boolean; + allowedTags: string[]; + children: Element | string; +} + +class SanitizeHTML extends PureComponent { + sanitize = (html: any) => { + return sanitizeHTML(html, { + allowedTags: this.props.allowedTags + }); + }; + + render() { + const { inline, children } = this.props; + return inline ? ( + + ) : ( +
+ ); + } +} + +export default SanitizeHTML; diff --git a/web/ui/react-app/src/components/SanitizeHTML/test.js b/web/ui/react-app/src/components/SanitizeHTML/test.js new file mode 100644 index 000000000..e8f8c6235 --- /dev/null +++ b/web/ui/react-app/src/components/SanitizeHTML/test.js @@ -0,0 +1,12 @@ +/** + * SanitizeHTML tests + */ +import React from 'react'; +import ReactDOM from 'react-dom'; +import SanitizeHTML from '../SanitizeHTML'; + +it('renders without crashing', () => { + const div = document.createElement('div'); + ReactDOM.render(, div); + ReactDOM.unmountComponentAtNode(div); +}); diff --git a/web/ui/react-app/yarn.lock b/web/ui/react-app/yarn.lock index 2b09c1cc2..31b15ff9b 100644 --- a/web/ui/react-app/yarn.lock +++ b/web/ui/react-app/yarn.lock @@ -1275,6 +1275,18 @@ dependencies: "@babel/types" "^7.3.0" +"@types/domhandler@*": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@types/domhandler/-/domhandler-2.4.1.tgz#7b3b347f7762180fbcb1ece1ce3dd0ebbb8c64cf" + integrity sha512-cfBw6q6tT5sa1gSPFSRKzF/xxYrrmeiut7E0TxNBObiLSBTuFEHibcfEe3waQPEDbqBsq+ql/TOniw65EyDFMA== + +"@types/domutils@*": + version "1.7.2" + resolved "https://registry.yarnpkg.com/@types/domutils/-/domutils-1.7.2.tgz#89422e579c165994ad5c09ce90325da596cc105d" + integrity sha512-Nnwy1Ztwq42SSNSZSh9EXBJGrOZPR+PQ2sRT4VZy8hnsFXfCil7YlKO2hd2360HyrtFz2qwnKQ13ENrgXNxJbw== + dependencies: + "@types/domhandler" "*" + "@types/eslint-visitor-keys@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" @@ -1287,6 +1299,15 @@ dependencies: "@types/jquery" "*" +"@types/htmlparser2@*": + version "3.10.1" + resolved "https://registry.yarnpkg.com/@types/htmlparser2/-/htmlparser2-3.10.1.tgz#1e65ba81401d53f425c1e2ba5a3d05c90ab742c7" + integrity sha512-fCxmHS4ryCUCfV9+CJZY1UjkbR+6Al/EQdX5Jh03qBj9gdlPG5q+7uNoDgE/ZNXb3XNWSAQgqKIWnbRCbOyyWA== + dependencies: + "@types/domhandler" "*" + "@types/domutils" "*" + "@types/node" "*" + "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff" @@ -1338,7 +1359,7 @@ dependencies: moment ">=2.14.0" -"@types/node@^12.11.1": +"@types/node@*", "@types/node@^12.11.1": version "12.11.1" resolved "https://registry.yarnpkg.com/@types/node/-/node-12.11.1.tgz#1fd7b821f798b7fa29f667a1be8f3442bb8922a3" integrity sha512-TJtwsqZ39pqcljJpajeoofYRfeZ7/I/OMUQ5pR4q5wOKf2ocrUvBAZUMhWsOvKx3dVc/aaV5GluBivt0sWqA5A== @@ -1383,6 +1404,13 @@ "@types/react" "*" popper.js "^1.14.1" +"@types/sanitize-html@^1.20.2": + version "1.20.2" + resolved "https://registry.yarnpkg.com/@types/sanitize-html/-/sanitize-html-1.20.2.tgz#59777f79f015321334e3a9f28882f58c0a0d42b8" + integrity sha512-SrefiiBebGIhxEFkpbbYOwO1S6+zQLWAC4s4tipchlHq1aO9bp0xiapM7Zm0ml20MF+3OePWYdksB1xtneKPxg== + dependencies: + "@types/htmlparser2" "*" + "@types/sizzle@*": version "2.3.2" resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.2.tgz#a811b8c18e2babab7d542b3365887ae2e4d9de47" @@ -1837,7 +1865,7 @@ array-union@^1.0.1: dependencies: array-uniq "^1.0.1" -array-uniq@^1.0.1: +array-uniq@^1.0.1, array-uniq@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY= @@ -4815,7 +4843,7 @@ html-webpack-plugin@4.0.0-beta.5: tapable "^1.1.0" util.promisify "1.0.0" -htmlparser2@^3.3.0: +htmlparser2@^3.10.0, htmlparser2@^3.3.0: version "3.10.1" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ== @@ -6224,6 +6252,16 @@ lodash._reinterpolate@^3.0.0: resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= +lodash.clonedeep@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" + integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= + +lodash.escaperegexp@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347" + integrity sha1-ZHYsSGGAglGKw99Mz11YhtriA0c= + lodash.isfunction@^3.0.9: version "3.0.9" resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz#06de25df4db327ac931981d1bdb067e5af68d051" @@ -6234,11 +6272,26 @@ lodash.isobject@^3.0.2: resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-3.0.2.tgz#3c8fb8d5b5bf4bf90ae06e14f2a530a4ed935e1d" integrity sha1-PI+41bW/S/kK4G4U8qUwpO2TXh0= +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE= + lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= +lodash.mergewith@^4.6.1: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz#617121f89ac55f59047c7aec1ccd6654c6590f55" + integrity sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ== + lodash.sortby@^4.7.0: version "4.7.0" resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" @@ -8951,6 +9004,22 @@ sane@^4.0.3: minimist "^1.1.1" walker "~1.0.5" +sanitize-html@^1.20.1: + version "1.20.1" + resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-1.20.1.tgz#f6effdf55dd398807171215a62bfc21811bacf85" + integrity sha512-txnH8TQjaQvg2Q0HY06G6CDJLVYCpbnxrdO0WN8gjCKaU5J0KbyGYhZxx5QJg3WLZ1lB7XU9kDkfrCXUozqptA== + dependencies: + chalk "^2.4.1" + htmlparser2 "^3.10.0" + lodash.clonedeep "^4.5.0" + lodash.escaperegexp "^4.1.2" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.mergewith "^4.6.1" + postcss "^7.0.5" + srcset "^1.0.0" + xtend "^4.0.1" + sass-loader@7.2.0: version "7.2.0" resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-7.2.0.tgz#e34115239309d15b2527cb62b5dfefb62a96ff7f" @@ -9355,6 +9424,14 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= +srcset@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/srcset/-/srcset-1.0.0.tgz#a5669de12b42f3b1d5e83ed03c71046fc48f41ef" + integrity sha1-pWad4StC87HV6D7QPHEEb8SPQe8= + dependencies: + array-uniq "^1.0.2" + number-is-nan "^1.0.0" + sshpk@^1.7.0: version "1.16.1" resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" @@ -10605,7 +10682,7 @@ xregexp@4.0.0: resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-4.0.0.tgz#e698189de49dd2a18cc5687b05e17c8e43943020" integrity sha512-PHyM+sQouu7xspQQwELlGwwd05mXUFqwFYfqPO0cC7x4fxyHnnuetmQr6CjJiafIDoH4MogHb9dOoJzR/Y4rFg== -xtend@^4.0.0, xtend@~4.0.1: +xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==