* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { sanitize } from 'dompurify';
import { Location } from 'history';
import { keyBy } from 'lodash';
import * as React from 'react';
import withIndexationContext, {
WithIndexationContextProps
} from '../../../components/hoc/withIndexationContext';
+import { sanitizeString } from '../../../helpers/sanitize';
import {
getAppState,
getCurrentUser,
<div
className="about-page-section"
// eslint-disable-next-line react/no-danger
- dangerouslySetInnerHTML={{ __html: sanitize(customText) }}
+ dangerouslySetInnerHTML={{ __html: sanitizeString(customText) }}
/>
)}
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as classNames from 'classnames';
-import { sanitize } from 'dompurify';
import * as React from 'react';
import { ResetButtonLink, SubmitButton } from 'sonar-ui-common/components/controls/buttons';
import Modal from 'sonar-ui-common/components/controls/Modal';
import { activateRule, Profile } from '../../../api/quality-profiles';
import SeverityHelper from '../../../components/shared/SeverityHelper';
import { SEVERITIES } from '../../../helpers/constants';
+import { sanitizeString } from '../../../helpers/sanitize';
import { sortProfiles } from '../../quality-profiles/utils';
interface Props {
value={this.state.params[param.key] || ''}
/>
)}
- <div
- className="note"
- // eslint-disable-next-line react/no-danger
- dangerouslySetInnerHTML={{ __html: sanitize(param.htmlDesc || '') }}
- />
+ {param.htmlDesc !== undefined && (
+ <div
+ className="note"
+ // eslint-disable-next-line react/no-danger
+ dangerouslySetInnerHTML={{ __html: sanitizeString(param.htmlDesc) }}
+ />
+ )}
</div>
))
)}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { sanitize } from 'dompurify';
import * as React from 'react';
import { ResetButtonLink, SubmitButton } from 'sonar-ui-common/components/controls/buttons';
import Modal from 'sonar-ui-common/components/controls/Modal';
import SeverityHelper from '../../../components/shared/SeverityHelper';
import TypeHelper from '../../../components/shared/TypeHelper';
import { RULE_STATUSES, RULE_TYPES, SEVERITIES } from '../../../helpers/constants';
+import { sanitizeString } from '../../../helpers/sanitize';
interface Props {
customRule?: T.RuleDetails;
value={this.state.params[param.key] || ''}
/>
)}
- <div
- className="modal-field-description"
- // eslint-disable-next-line react/no-danger
- dangerouslySetInnerHTML={{ __html: sanitize(param.htmlDesc || '') }}
- />
+ {param.htmlDesc !== undefined && (
+ <div
+ className="modal-field-description"
+ // eslint-disable-next-line react/no-danger
+ dangerouslySetInnerHTML={{ __html: sanitizeString(param.htmlDesc) }}
+ />
+ )}
</div>
);
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { sanitize } from 'dompurify';
import * as React from 'react';
import { Button, ResetButtonLink } from 'sonar-ui-common/components/controls/buttons';
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n';
import { updateRule } from '../../../api/rules';
import FormattingTips from '../../../components/common/FormattingTips';
+import { sanitizeString } from '../../../helpers/sanitize';
import RemoveExtendedDescriptionModal from './RemoveExtendedDescriptionModal';
interface Props {
<div
className="rule-desc spacer-bottom markdown"
// eslint-disable-next-line react/no-danger
- dangerouslySetInnerHTML={{ __html: sanitize(this.props.ruleDetails.htmlNote) }}
+ dangerouslySetInnerHTML={{ __html: sanitizeString(this.props.ruleDetails.htmlNote) }}
/>
)}
{this.props.canWrite && (
return (
<div className="js-rule-description">
- {hasDescription ? (
+ {hasDescription && ruleDetails.htmlDesc !== undefined ? (
<div
className="coding-rules-detail-description rule-desc markdown"
// eslint-disable-next-line react/no-danger
- dangerouslySetInnerHTML={{ __html: sanitize(ruleDetails.htmlDesc || '') }}
+ dangerouslySetInnerHTML={{ __html: sanitizeString(ruleDetails.htmlDesc) }}
/>
) : (
<div className="coding-rules-detail-description rule-desc markdown">
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { sanitize } from 'dompurify';
import * as React from 'react';
import { translate } from 'sonar-ui-common/helpers/l10n';
+import { sanitizeString } from '../../../helpers/sanitize';
interface Props {
params: T.RuleParameter[];
<tr className="coding-rules-detail-parameter" key={param.key}>
<td className="coding-rules-detail-parameter-name">{param.key}</td>
<td className="coding-rules-detail-parameter-description">
- <p
- // eslint-disable-next-line react/no-danger
- dangerouslySetInnerHTML={{ __html: sanitize(param.htmlDesc || '') }}
- />
+ {param.htmlDesc !== undefined && (
+ <p
+ // eslint-disable-next-line react/no-danger
+ dangerouslySetInnerHTML={{ __html: sanitizeString(param.htmlDesc) }}
+ />
+ )}
{param.defaultValue !== undefined && (
<div className="note spacer-top">
{translate('coding_rules.parameters.default_value')}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
import { shallow } from 'enzyme';
import * as React from 'react';
-import { mockQualityProfile, mockRule } from '../../../../helpers/testMocks';
+import {
+ mockQualityProfile,
+ mockRule,
+ mockRuleActivation,
+ mockRuleDetails,
+ mockRuleDetailsParameter
+} from '../../../../helpers/testMocks';
import ActivationFormModal from '../ActivationFormModal';
-it('render correctly', () => {
+it('should render correctly', () => {
+ expect(shallowRender()).toMatchSnapshot('default');
expect(
- shallow(
- <ActivationFormModal
- modalHeader="title"
- onClose={jest.fn()}
- onDone={jest.fn()}
- profiles={[mockQualityProfile()]}
- rule={mockRule()}
- />
- )
- ).toMatchSnapshot();
+ shallowRender({
+ profiles: [
+ mockQualityProfile(),
+ mockQualityProfile({ depth: 2, actions: { edit: true }, language: 'js' })
+ ]
+ })
+ ).toMatchSnapshot('with deep profiles');
+ expect(shallowRender({ rule: mockRuleDetails({ templateKey: 'foobar' }) })).toMatchSnapshot(
+ 'custom rule'
+ );
+ expect(shallowRender({ activation: mockRuleActivation() })).toMatchSnapshot('update mode');
+ const wrapper = shallowRender();
+ wrapper.setState({ submitting: true });
+ expect(wrapper).toMatchSnapshot('submitting');
});
+
+function shallowRender(props: Partial<ActivationFormModal['props']> = {}) {
+ return shallow<ActivationFormModal>(
+ <ActivationFormModal
+ modalHeader="title"
+ onClose={jest.fn()}
+ onDone={jest.fn()}
+ profiles={[mockQualityProfile()]}
+ rule={mockRule({
+ params: [
+ mockRuleDetailsParameter(),
+ mockRuleDetailsParameter({ key: '2', type: 'TEXT', htmlDesc: undefined })
+ ]
+ })}
+ {...props}
+ />
+ );
+}
import * as React from 'react';
import { submit, waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
import { createRule } from '../../../../api/rules';
-import { mockRule } from '../../../../helpers/testMocks';
+import { mockRule, mockRuleDetailsParameter } from '../../../../helpers/testMocks';
import CustomRuleFormModal from '../CustomRuleFormModal';
jest.mock('../../../../api/rules', () => ({ createRule: jest.fn() }));
it('should render correctly', () => {
- expect(shallowRender()).toMatchSnapshot();
+ expect(shallowRender()).toMatchSnapshot('default');
});
it('should handle re-activation', async () => {
<CustomRuleFormModal
onClose={jest.fn()}
onDone={jest.fn()}
- templateRule={{ ...mockRule(), createdAt: 'date', repo: 'squid' }}
+ templateRule={{
+ ...mockRule({
+ params: [
+ mockRuleDetailsParameter(),
+ mockRuleDetailsParameter({ key: '2', type: 'TEXT', htmlDesc: undefined })
+ ]
+ }),
+ createdAt: 'date',
+ repo: 'squid'
+ }}
{...props}
/>
);
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`render correctly 1`] = `
+exports[`should render correctly: custom rule 1`] = `
<Modal
contentLabel="title"
onRequestClose={[MockFunction]}
valueRenderer={[Function]}
/>
</div>
+ <div
+ className="modal-field"
+ >
+ <p
+ className="note"
+ >
+ coding_rules.custom_rule.activation_notice
+ </p>
+ </div>
+ </div>
+ <footer
+ className="modal-foot"
+ >
+ <SubmitButton
+ disabled={true}
+ >
+ coding_rules.activate
+ </SubmitButton>
+ <ResetButtonLink
+ disabled={false}
+ onClick={[MockFunction]}
+ >
+ cancel
+ </ResetButtonLink>
+ </footer>
+ </form>
+</Modal>
+`;
+
+exports[`should render correctly: default 1`] = `
+<Modal
+ contentLabel="title"
+ onRequestClose={[MockFunction]}
+ size="small"
+>
+ <form
+ onSubmit={[Function]}
+ >
+ <div
+ className="modal-head"
+ >
+ <h2>
+ title
+ </h2>
+ </div>
+ <div
+ className="modal-body modal-container"
+ >
+ <Alert
+ variant="info"
+ >
+ coding_rules.active_in_all_profiles
+ </Alert>
+ <div
+ className="modal-field"
+ >
+ <label>
+ coding_rules.quality_profile
+ </label>
+ <Select
+ className="js-profile"
+ clearable={false}
+ disabled={false}
+ onChange={[Function]}
+ options={Array []}
+ value=""
+ />
+ </div>
+ <div
+ className="modal-field"
+ >
+ <label>
+ severity
+ </label>
+ <Select
+ className="js-severity"
+ clearable={false}
+ disabled={false}
+ onChange={[Function]}
+ optionRenderer={[Function]}
+ options={
+ Array [
+ Object {
+ "label": "severity.BLOCKER",
+ "value": "BLOCKER",
+ },
+ Object {
+ "label": "severity.CRITICAL",
+ "value": "CRITICAL",
+ },
+ Object {
+ "label": "severity.MAJOR",
+ "value": "MAJOR",
+ },
+ Object {
+ "label": "severity.MINOR",
+ "value": "MINOR",
+ },
+ Object {
+ "label": "severity.INFO",
+ "value": "INFO",
+ },
+ ]
+ }
+ searchable={false}
+ value="MAJOR"
+ valueRenderer={[Function]}
+ />
+ </div>
+ <div
+ className="modal-field"
+ key="1"
+ >
+ <label
+ title="1"
+ >
+ 1
+ </label>
+ <input
+ disabled={false}
+ name="1"
+ onChange={[Function]}
+ placeholder="1"
+ type="text"
+ value="1"
+ />
+ <div
+ className="note"
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "description",
+ }
+ }
+ />
+ </div>
+ <div
+ className="modal-field"
+ key="2"
+ >
+ <label
+ title="2"
+ >
+ 2
+ </label>
+ <textarea
+ disabled={false}
+ name="2"
+ onChange={[Function]}
+ placeholder="1"
+ rows={3}
+ value="1"
+ />
+ </div>
+ </div>
+ <footer
+ className="modal-foot"
+ >
+ <SubmitButton
+ disabled={true}
+ >
+ coding_rules.activate
+ </SubmitButton>
+ <ResetButtonLink
+ disabled={false}
+ onClick={[MockFunction]}
+ >
+ cancel
+ </ResetButtonLink>
+ </footer>
+ </form>
+</Modal>
+`;
+
+exports[`should render correctly: submitting 1`] = `
+<Modal
+ contentLabel="title"
+ onRequestClose={[MockFunction]}
+ size="small"
+>
+ <form
+ onSubmit={[Function]}
+ >
+ <div
+ className="modal-head"
+ >
+ <h2>
+ title
+ </h2>
+ </div>
+ <div
+ className="modal-body modal-container"
+ >
+ <Alert
+ variant="info"
+ >
+ coding_rules.active_in_all_profiles
+ </Alert>
+ <div
+ className="modal-field"
+ >
+ <label>
+ coding_rules.quality_profile
+ </label>
+ <Select
+ className="js-profile"
+ clearable={false}
+ disabled={true}
+ onChange={[Function]}
+ options={Array []}
+ value=""
+ />
+ </div>
+ <div
+ className="modal-field"
+ >
+ <label>
+ severity
+ </label>
+ <Select
+ className="js-severity"
+ clearable={false}
+ disabled={true}
+ onChange={[Function]}
+ optionRenderer={[Function]}
+ options={
+ Array [
+ Object {
+ "label": "severity.BLOCKER",
+ "value": "BLOCKER",
+ },
+ Object {
+ "label": "severity.CRITICAL",
+ "value": "CRITICAL",
+ },
+ Object {
+ "label": "severity.MAJOR",
+ "value": "MAJOR",
+ },
+ Object {
+ "label": "severity.MINOR",
+ "value": "MINOR",
+ },
+ Object {
+ "label": "severity.INFO",
+ "value": "INFO",
+ },
+ ]
+ }
+ searchable={false}
+ value="MAJOR"
+ valueRenderer={[Function]}
+ />
+ </div>
+ <div
+ className="modal-field"
+ key="1"
+ >
+ <label
+ title="1"
+ >
+ 1
+ </label>
+ <input
+ disabled={true}
+ name="1"
+ onChange={[Function]}
+ placeholder="1"
+ type="text"
+ value="1"
+ />
+ <div
+ className="note"
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "description",
+ }
+ }
+ />
+ </div>
+ <div
+ className="modal-field"
+ key="2"
+ >
+ <label
+ title="2"
+ >
+ 2
+ </label>
+ <textarea
+ disabled={true}
+ name="2"
+ onChange={[Function]}
+ placeholder="1"
+ rows={3}
+ value="1"
+ />
+ </div>
+ </div>
+ <footer
+ className="modal-foot"
+ >
+ <i
+ className="spinner spacer-right"
+ />
+ <SubmitButton
+ disabled={true}
+ >
+ coding_rules.activate
+ </SubmitButton>
+ <ResetButtonLink
+ disabled={true}
+ onClick={[MockFunction]}
+ >
+ cancel
+ </ResetButtonLink>
+ </footer>
+ </form>
+</Modal>
+`;
+
+exports[`should render correctly: update mode 1`] = `
+<Modal
+ contentLabel="title"
+ onRequestClose={[MockFunction]}
+ size="small"
+>
+ <form
+ onSubmit={[Function]}
+ >
+ <div
+ className="modal-head"
+ >
+ <h2>
+ title
+ </h2>
+ </div>
+ <div
+ className="modal-body modal-container"
+ >
+ <div
+ className="modal-field"
+ >
+ <label>
+ coding_rules.quality_profile
+ </label>
+ <Select
+ className="js-profile"
+ clearable={false}
+ disabled={false}
+ onChange={[Function]}
+ options={Array []}
+ value=""
+ />
+ </div>
+ <div
+ className="modal-field"
+ >
+ <label>
+ severity
+ </label>
+ <Select
+ className="js-severity"
+ clearable={false}
+ disabled={false}
+ onChange={[Function]}
+ optionRenderer={[Function]}
+ options={
+ Array [
+ Object {
+ "label": "severity.BLOCKER",
+ "value": "BLOCKER",
+ },
+ Object {
+ "label": "severity.CRITICAL",
+ "value": "CRITICAL",
+ },
+ Object {
+ "label": "severity.MAJOR",
+ "value": "MAJOR",
+ },
+ Object {
+ "label": "severity.MINOR",
+ "value": "MINOR",
+ },
+ Object {
+ "label": "severity.INFO",
+ "value": "INFO",
+ },
+ ]
+ }
+ searchable={false}
+ value="MAJOR"
+ valueRenderer={[Function]}
+ />
+ </div>
+ <div
+ className="modal-field"
+ key="1"
+ >
+ <label
+ title="1"
+ >
+ 1
+ </label>
+ <input
+ disabled={false}
+ name="1"
+ onChange={[Function]}
+ placeholder="1"
+ type="text"
+ value="1"
+ />
+ <div
+ className="note"
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "description",
+ }
+ }
+ />
+ </div>
+ <div
+ className="modal-field"
+ key="2"
+ >
+ <label
+ title="2"
+ >
+ 2
+ </label>
+ <textarea
+ disabled={false}
+ name="2"
+ onChange={[Function]}
+ placeholder="1"
+ rows={3}
+ value="1"
+ />
+ </div>
</div>
<footer
className="modal-foot"
>
<SubmitButton
disabled={true}
+ >
+ save
+ </SubmitButton>
+ <ResetButtonLink
+ disabled={false}
+ onClick={[MockFunction]}
+ >
+ cancel
+ </ResetButtonLink>
+ </footer>
+ </form>
+</Modal>
+`;
+
+exports[`should render correctly: with deep profiles 1`] = `
+<Modal
+ contentLabel="title"
+ onRequestClose={[MockFunction]}
+ size="small"
+>
+ <form
+ onSubmit={[Function]}
+ >
+ <div
+ className="modal-head"
+ >
+ <h2>
+ title
+ </h2>
+ </div>
+ <div
+ className="modal-body modal-container"
+ >
+ <div
+ className="modal-field"
+ >
+ <label>
+ coding_rules.quality_profile
+ </label>
+ <Select
+ className="js-profile"
+ clearable={false}
+ disabled={true}
+ onChange={[Function]}
+ options={
+ Array [
+ Object {
+ "label": "name",
+ "value": "key",
+ },
+ ]
+ }
+ value="key"
+ />
+ </div>
+ <div
+ className="modal-field"
+ >
+ <label>
+ severity
+ </label>
+ <Select
+ className="js-severity"
+ clearable={false}
+ disabled={false}
+ onChange={[Function]}
+ optionRenderer={[Function]}
+ options={
+ Array [
+ Object {
+ "label": "severity.BLOCKER",
+ "value": "BLOCKER",
+ },
+ Object {
+ "label": "severity.CRITICAL",
+ "value": "CRITICAL",
+ },
+ Object {
+ "label": "severity.MAJOR",
+ "value": "MAJOR",
+ },
+ Object {
+ "label": "severity.MINOR",
+ "value": "MINOR",
+ },
+ Object {
+ "label": "severity.INFO",
+ "value": "INFO",
+ },
+ ]
+ }
+ searchable={false}
+ value="MAJOR"
+ valueRenderer={[Function]}
+ />
+ </div>
+ <div
+ className="modal-field"
+ key="1"
+ >
+ <label
+ title="1"
+ >
+ 1
+ </label>
+ <input
+ disabled={false}
+ name="1"
+ onChange={[Function]}
+ placeholder="1"
+ type="text"
+ value="1"
+ />
+ <div
+ className="note"
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "description",
+ }
+ }
+ />
+ </div>
+ <div
+ className="modal-field"
+ key="2"
+ >
+ <label
+ title="2"
+ >
+ 2
+ </label>
+ <textarea
+ disabled={false}
+ name="2"
+ onChange={[Function]}
+ placeholder="1"
+ rows={3}
+ value="1"
+ />
+ </div>
+ </div>
+ <footer
+ className="modal-foot"
+ >
+ <SubmitButton
+ disabled={false}
>
coding_rules.activate
</SubmitButton>
className="modal-field-descriptor text-right"
/>
</div>
+ <div
+ className="modal-field"
+ key="1"
+ >
+ <label
+ className="capitalize"
+ htmlFor="1"
+ >
+ 1
+ </label>
+ <input
+ disabled={false}
+ id="1"
+ name="1"
+ onChange={[Function]}
+ placeholder="1"
+ type="text"
+ value=""
+ />
+ <div
+ className="modal-field-description"
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "description",
+ }
+ }
+ />
+ </div>
+ <div
+ className="modal-field"
+ key="2"
+ >
+ <label
+ className="capitalize"
+ htmlFor="2"
+ >
+ 2
+ </label>
+ <textarea
+ disabled={false}
+ id="2"
+ name="2"
+ onChange={[Function]}
+ placeholder="1"
+ rows={3}
+ value=""
+ />
+ </div>
</div>
<div
className="modal-foot"
</Modal>
`;
-exports[`should render correctly 1`] = `
+exports[`should render correctly: default 1`] = `
<Modal
contentLabel="coding_rules.create_custom_rule"
onRequestClose={[MockFunction]}
className="modal-field-descriptor text-right"
/>
</div>
+ <div
+ className="modal-field"
+ key="1"
+ >
+ <label
+ className="capitalize"
+ htmlFor="1"
+ >
+ 1
+ </label>
+ <input
+ disabled={false}
+ id="1"
+ name="1"
+ onChange={[Function]}
+ placeholder="1"
+ type="text"
+ value=""
+ />
+ <div
+ className="modal-field-description"
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "description",
+ }
+ }
+ />
+ </div>
+ <div
+ className="modal-field"
+ key="2"
+ >
+ <label
+ className="capitalize"
+ htmlFor="2"
+ >
+ 2
+ </label>
+ <textarea
+ disabled={false}
+ id="2"
+ name="2"
+ onChange={[Function]}
+ placeholder="1"
+ rows={3}
+ value=""
+ />
+ </div>
</div>
<div
className="modal-foot"
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as classNames from 'classnames';
-import { sanitize } from 'dompurify';
import * as React from 'react';
import { Button, DeleteButton, EditButton } from 'sonar-ui-common/components/controls/buttons';
import Dropdown, { DropdownOverlay } from 'sonar-ui-common/components/controls/Dropdown';
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n';
import IssueChangelogDiff from '../../../components/issue/components/IssueChangelogDiff';
import Avatar from '../../../components/ui/Avatar';
+import { sanitizeString } from '../../../helpers/sanitize';
import { Hotspot, ReviewHistoryType } from '../../../types/security-hotspots';
import { getHotspotReviewHistory } from '../utils';
import HotspotCommentPopup from './HotspotCommentPopup';
{type === ReviewHistoryType.Comment && key && html && markdown && (
<div className="spacer-top display-flex-space-between">
- <div className="markdown" dangerouslySetInnerHTML={{ __html: sanitize(html) }} />
+ <div
+ className="markdown"
+ // eslint-disable-next-line react/no-danger
+ dangerouslySetInnerHTML={{ __html: sanitizeString(html) }}
+ />
{updatable && (
<div>
<div className="dropdown">
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { sanitize } from 'dompurify';
import * as React from 'react';
import BoxedTabs from 'sonar-ui-common/components/controls/BoxedTabs';
import Tab from 'sonar-ui-common/components/controls/Tabs';
import { translate } from 'sonar-ui-common/helpers/l10n';
+import { sanitizeString } from '../../../helpers/sanitize';
import { Hotspot } from '../../../types/security-hotspots';
interface Props {
<div className="bordered huge-spacer-bottom">
<div
className="markdown big-padded"
- dangerouslySetInnerHTML={{ __html: sanitize(currentTab.content) }}
+ // eslint-disable-next-line react/no-danger
+ dangerouslySetInnerHTML={{ __html: sanitizeString(currentTab.content) }}
/>
</div>
</>
SettingCategoryDefinition,
SettingFieldDefinition
} from '../../../types/settings';
-import { getDefaultValue, getEmptyValue, sanitizeTranslation } from '../utils';
+import { getDefaultValue, getEmptyValue } from '../utils';
const fields = [
{ key: 'foo', type: 'STRING' } as SettingFieldDefinition,
}
);
});
-
-describe('sanitizeTranslation', () => {
- it('should preserve formatting tags', () => {
- const allowed = `
- Hi this is <i>in italics</i> and <ul>
- <li> lists </li>
- <li> are allowed</li>
- </ul>
- <p>
- as well. This is <b>Amazing</b> and this <strong>bold</strong> <br>
- and <code>code.is.accepted too</code>
- </p>
- `;
-
- const clean = sanitizeTranslation(allowed);
- expect(clean).toBe(allowed);
- });
-
- /*
- * Test code borrowed from OWASP's sanitizer tests
- * https://github.com/OWASP/java-html-sanitizer/blob/master/src/test/resources/org/owasp/html/htmllexerinput1.html
- */
- it('should strip everything else', () => {
- const clean = sanitizeTranslation(`<?xml version="not-even-close"?>
-
- <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
-
- <!-- a test input for HtmlLexer -->
-
- <html>
- <head>
- <title>Test File For HtmlLexer & HtmlParser</title>
- <link rel=stylesheet type="text/css" src=foo/bar.css />
- <body
- bgcolor=white
- linkcolor = "blue"
- onload="document.writeln(
- "<p>properly escaped code in a handler</p>");"
- >
-
- <script type="text/javascript"><!--
- document.writeln("<p>Some initialization code in global context</p>");
- --></script>
-
- <script type="text/javascript">
- // hi there
- document.writeln("<p>More initialization</p>");
- </script>
-
- <div id=clickydiv onclick="handleClicky(event)"
- ondblclick=this.onclick(event);return(false)>
- Clicky
- </div>
-
- <input id=foo>
- <gxp:attr name="onchange">alert("<b>hi</b>");</gxp:attr>
- </input>
-
- <pre><div id=notarealtag onclick=notcode()></pre>
-
- <!-- some tokenization corner cases -->
-
- < notatag <atag/>
-
- </ notatag> </redundantlyclosed/>
-
- <messyattributes a=b=c d="e"f=g h =i j= k l = m checked n="o"/>
-
- < < < all in one text block > > >
-
- <xmp>Make sure that <!-- comments don't obscure the xmp close</xmp>
- <% # some php code here
- write("<pre>$horriblySyntacticConstruct1</pre>\n\n");
- %>
- <script type="text/javascript"><!--
- alert("hello world");
- // --></script>
-
- <script>/* </script> */alert('hi');</script>
- <script><!--/* </script> */alert('hi');--></script>
-
- <xmp style=color:blue><!--/* </xmp> */alert('hi');--></xmp>
-
- <style><!-- p { contentf: '</style>' } --></style>
- <style>Foo<!-- > </style> --></style>
- <textarea><!-- Zoicks </textarea>--></textarea>
- <!-- An escaping text span start may share its U+002D HYPHEN-MINUS characters
- - with its corresponding escaping text span end. -->
- <script><!--></script>
- <script><!---></script>
- <script><!----></script>
- </body>
- </html>
- <![CDATA[ No such thing as a CDATA> section in HTML ]]>
- <script>a<b</script>
- <img src=foo.gif /><a href=><a href=/>
- <span title=malformed attribs' do=don't id=foo checked onclick="a<b">Bar</span>`);
-
- expect(clean.replace(/\s+/g, '')).toBe(
- `Clickyalert("<b>hi</b>");<divid=notarealtagonclick=notcode()><notatag<<<allinonetextblock>>><%#somephpcodeherewrite("$horriblySyntacticConstruct1");%>*/alert('hi');*/alert('hi');-->*/alert('hi');-->'}-->--><!--Zoicks-->sectioninHTML]]>Bar`
- );
- });
-});
import AlertErrorIcon from 'sonar-ui-common/components/icons/AlertErrorIcon';
import AlertSuccessIcon from 'sonar-ui-common/components/icons/AlertSuccessIcon';
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n';
+import { sanitizeStringRestricted } from '../../../helpers/sanitize';
import {
getSettingsAppChangedValue,
getSettingsAppValidationMessage,
getPropertyDescription,
getPropertyName,
getSettingValue,
- isDefaultOrInherited,
- sanitizeTranslation
+ isDefaultOrInherited
} from '../utils';
import DefinitionActions from './DefinitionActions';
import Input from './inputs/Input';
{description && (
<div
className="markdown small spacer-top"
- dangerouslySetInnerHTML={{ __html: sanitizeTranslation(description) }}
+ // eslint-disable-next-line react/no-danger
+ dangerouslySetInnerHTML={{ __html: sanitizeStringRestricted(description) }}
/>
)}
*/
import { groupBy, isEqual, sortBy } from 'lodash';
import * as React from 'react';
+import { sanitizeStringRestricted } from '../../../helpers/sanitize';
import { Setting, SettingCategoryDefinition } from '../../../types/settings';
-import { getSubCategoryDescription, getSubCategoryName, sanitizeTranslation } from '../utils';
+import { getSubCategoryDescription, getSubCategoryName } from '../utils';
import DefinitionsList from './DefinitionsList';
import EmailForm from './EmailForm';
{subCategory.description != null && (
<div
className="settings-sub-category-description markdown"
- dangerouslySetInnerHTML={{ __html: sanitizeTranslation(subCategory.description) }}
+ // eslint-disable-next-line react/no-danger
+ dangerouslySetInnerHTML={{
+ __html: sanitizeStringRestricted(subCategory.description)
+ }}
/>
)}
<DefinitionsList
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { sanitize } from 'dompurify';
import { hasMessage, translate } from 'sonar-ui-common/helpers/l10n';
import { Setting, SettingCategoryDefinition, SettingDefinition } from '../../types/settings';
value: any;
}
-export function sanitizeTranslation(html: string) {
- return sanitize(html, {
- ALLOWED_TAGS: ['b', 'br', 'code', 'i', 'li', 'p', 'strong', 'ul']
- });
-}
-
export function getPropertyName(definition: SettingDefinition) {
const key = `property.${definition.key}.name`;
return hasMessage(key) ? translate(key) : definition.name;
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { sanitize } from 'dompurify';
import * as React from 'react';
import { ButtonLink } from 'sonar-ui-common/components/controls/buttons';
import Modal from 'sonar-ui-common/components/controls/Modal';
import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { dismissAnalysisWarning, getTask } from '../../api/ce';
+import { sanitizeStringRestricted } from '../../helpers/sanitize';
import { TaskWarning } from '../../types/tasks';
import { withCurrentUser } from '../hoc/withCurrentUser';
<span
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{
- __html: sanitize(message.trim().replace(/\n/g, '<br />'), {
- ALLOWED_ATTR: ['target', 'href']
- })
+ __html: sanitizeStringRestricted(message.trim().replace(/\n/g, '<br />'))
}}
/>
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { sanitize } from 'dompurify';
import * as React from 'react';
import { DeleteButton, EditButton } from 'sonar-ui-common/components/controls/buttons';
import Toggler from 'sonar-ui-common/components/controls/Toggler';
import DateFromNow from 'sonar-ui-common/components/intl/DateFromNow';
import { PopupPlacement } from 'sonar-ui-common/components/ui/popups';
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n';
+import { sanitizeString } from '../../../helpers/sanitize';
import Avatar from '../../ui/Avatar';
import CommentDeletePopup from '../popups/CommentDeletePopup';
import CommentPopup from '../popups/CommentPopup';
</div>
<div
className="issue-comment-text markdown"
- dangerouslySetInnerHTML={{ __html: sanitize(comment.htmlText) }}
+ // eslint-disable-next-line react/no-danger
+ dangerouslySetInnerHTML={{ __html: sanitizeString(comment.htmlText) }}
/>
<div className="issue-comment-age">
<span className="a11y-hidden">{translate('issue.comment.posted_on')}</span>
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+import { sanitizeString, sanitizeStringRestricted } from '../sanitize';
+
+describe('sanitizeStringRestricted', () => {
+ it('should preserve only specific formatting tags', () => {
+ expect(
+ sanitizeStringRestricted(`
+ Hi <a href="http://example.com" target="_blank">this</a> is <i>in italics</i> and <ul>
+ <li> lists </li>
+ <li> are allowed</li>
+ </ul>
+ <p>
+ as well. This is <b>Amazing</b> and this <strong>bold</strong> <br>
+ and <code>code.is.accepted too</code>
+ </p>
+ `)
+ ).toBe(`
+ Hi <a target="_blank" href="http://example.com">this</a> is <i>in italics</i> and <ul>
+ <li> lists </li>
+ <li> are allowed</li>
+ </ul>
+ <p>
+ as well. This is <b>Amazing</b> and this <strong>bold</strong> <br>
+ and <code>code.is.accepted too</code>
+ </p>
+ `);
+ });
+
+ /*
+ * Test code borrowed from OWASP's sanitizer tests
+ * https://github.com/OWASP/java-html-sanitizer/blob/master/src/test/resources/org/owasp/html/htmllexerinput1.html
+ */
+ it('should strip everything else', () => {
+ const clean = sanitizeStringRestricted(`<?xml version="not-even-close"?>
+
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+
+ <!-- a test input for HtmlLexer -->
+
+ <html>
+ <head>
+ <title>Test File For HtmlLexer & HtmlParser</title>
+ <link rel=stylesheet type="text/css" src=foo/bar.css />
+ <body
+ bgcolor=white
+ linkcolor = "blue"
+ onload="document.writeln(
+ "<p>properly escaped code in a handler</p>");"
+ >
+
+ <script type="text/javascript"><!--
+ document.writeln("<p>Some initialization code in global context</p>");
+ --></script>
+
+ <script type="text/javascript">
+ // hi there
+ document.writeln("<p>More initialization</p>");
+ </script>
+
+ <div id=clickydiv onclick="handleClicky(event)"
+ ondblclick=this.onclick(event);return(false)>
+ Clicky
+ </div>
+
+ <input id=foo>
+ <gxp:attr name="onchange">alert("<b>hi</b>");</gxp:attr>
+ </input>
+
+ <pre><div id=notarealtag onclick=notcode()></pre>
+
+ <!-- some tokenization corner cases -->
+
+ < notatag <atag/>
+
+ </ notatag> </redundantlyclosed/>
+
+ <messyattributes a=b=c d="e"f=g h =i j= k l = m checked n="o"/>
+
+ < < < all in one text block > > >
+
+ <xmp>Make sure that <!-- comments don't obscure the xmp close</xmp>
+ <% # some php code here
+ write("<pre>$horriblySyntacticConstruct1</pre>\n\n");
+ %>
+ <script type="text/javascript"><!--
+ alert("hello world");
+ // --></script>
+
+ <script>/* </script> */alert('hi');</script>
+ <script><!--/* </script> */alert('hi');--></script>
+
+ <xmp style=color:blue><!--/* </xmp> */alert('hi');--></xmp>
+
+ <style><!-- p { contentf: '</style>' } --></style>
+ <style>Foo<!-- > </style> --></style>
+ <textarea><!-- Zoicks </textarea>--></textarea>
+ <!-- An escaping text span start may share its U+002D HYPHEN-MINUS characters
+ - with its corresponding escaping text span end. -->
+ <script><!--></script>
+ <script><!---></script>
+ <script><!----></script>
+ </body>
+ </html>
+ <![CDATA[ No such thing as a CDATA> section in HTML ]]>
+ <script>a<b</script>
+ <img src=foo.gif /><a href=><a href=/>
+ <span title=malformed attribs' do=don't id=foo checked onclick="a<b">Bar</span>`);
+
+ expect(clean.replace(/\s+/g, '')).toBe(
+ `Clickyalert("<b>hi</b>");<divid=notarealtagonclick=notcode()><notatag<<<allinonetextblock>>><%#somephpcodeherewrite("$horriblySyntacticConstruct1");%>*/alert('hi');*/alert('hi');-->*/alert('hi');-->'}-->--><!--Zoicks-->sectioninHTML]]><ahref=""></a><ahref="/">Bar</a>`
+ );
+ });
+});
+
+describe('sanitizeString', () => {
+ it('should not allow MathML and SVG', () => {
+ const tainted = `
+ Hi <a href="javascript:alert('hello')" target="_blank">this</a> is <i>in italics</i> and <ul>
+ <li> lists </li>
+ <li> are allowed</li>
+ </ul>
+ <p class="some-class">
+ as well. This is <b>Amazing</b> and this <strong>bold</strong> <br>
+ and <code>code.is.accepted too</code>
+ </p>
+ <svg><text>SVG isn't allowed</text></svg>
+ <math xmlns="http://www.w3.org/1998/Math/MathML">
+ <infinity />
+ </math>`;
+ const clean = `
+ Hi <a>this</a> is <i>in italics</i> and <ul>
+ <li> lists </li>
+ <li> are allowed</li>
+ </ul>
+ <p class="some-class">
+ as well. This is <b>Amazing</b> and this <strong>bold</strong> <br>
+ and <code>code.is.accepted too</code>
+ </p>`;
+
+ expect(sanitizeString(tainted).trimRight()).toBe(clean);
+ });
+});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+import { sanitize } from 'dompurify';
+
+export function sanitizeStringRestricted(html: string) {
+ return sanitize(html, {
+ ALLOWED_TAGS: ['b', 'br', 'code', 'i', 'li', 'p', 'strong', 'ul', 'a'],
+ ALLOWED_ATTR: ['target', 'href']
+ });
+}
+
+export function sanitizeString(html: string) {
+ return sanitize(html, { USE_PROFILES: { html: true } });
+}
} as T.Rule;
}
+export function mockRuleActivation(overrides: Partial<T.RuleActivation> = {}): T.RuleActivation {
+ return {
+ createdAt: '2020-02-01',
+ inherit: 'NONE',
+ params: [{ key: 'foo', value: 'Bar' }],
+ qProfile: 'baz',
+ severity: 'MAJOR',
+ ...overrides
+ };
+}
+
export function mockRuleDetails(overrides: Partial<T.RuleDetails> = {}): T.RuleDetails {
return {
key: 'squid:S1337',