Skip to content

Commit

Permalink
FIO 7246: DOMPurify when PDF option is set (#5342)
Browse files Browse the repository at this point in the history
* upgrade DOMPurify

* sanitize for PDFs

* reformat component tests and add sanitize test
  • Loading branch information
brendanbond authored and lane-formio committed Sep 26, 2023
1 parent cc09452 commit 60e317b
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 87 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
"custom-event-polyfill": "^1.0.7",
"dialog-polyfill": "^0.5.6",
"dom-autoscroller": "^2.3.4",
"dompurify": "^2.4.1",
"dompurify": "^3.0.5",
"downloadjs": "^1.4.7",
"dragula": "^3.7.3",
"eventemitter3": "^4.0.7",
Expand Down
3 changes: 1 addition & 2 deletions src/components/_classes/component/Component.js
Original file line number Diff line number Diff line change
Expand Up @@ -964,8 +964,7 @@ export default class Component extends Element {
* @returns {*}
*/
sanitize(dirty, forceSanitize, options) {
// No need to sanitize when generating PDF'S since no users interact with the form.
if ((!this.shouldSanitizeValue && !forceSanitize) || ((this.options.pdf) && !forceSanitize)) {
if (!this.shouldSanitizeValue && !forceSanitize) {
return dirty;
}
return FormioUtils.sanitize(
Expand Down
166 changes: 86 additions & 80 deletions src/components/_classes/component/Component.unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -259,95 +259,101 @@ describe('Component', () => {
}).catch(done);
});
});
});

it('Should return value for HTML mode', () => {
return Harness.testCreate(Component, comp1).then((component) => {
assert.equal(component.itemValueForHTMLMode(['option 1', 'option 2', 'option 3']), 'option 1, option 2, option 3');
assert.equal(component.itemValueForHTMLMode(['option 1', ['option 2', 'option 3']]), 'option 1, option 2, option 3');
assert.equal(component.itemValueForHTMLMode(['2020-03-18T15:00:00.000Z', '2020-03-31T09:05:00.000Z']), '2020-03-18T15:00:00.000Z, 2020-03-31T09:05:00.000Z');
assert.equal(component.itemValueForHTMLMode('test'), 'test');
it('Should return value for HTML mode', () => {
return Harness.testCreate(Component, comp1).then((component) => {
assert.equal(component.itemValueForHTMLMode(['option 1', 'option 2', 'option 3']), 'option 1, option 2, option 3');
assert.equal(component.itemValueForHTMLMode(['option 1', ['option 2', 'option 3']]), 'option 1, option 2, option 3');
assert.equal(component.itemValueForHTMLMode(['2020-03-18T15:00:00.000Z', '2020-03-31T09:05:00.000Z']), '2020-03-18T15:00:00.000Z, 2020-03-31T09:05:00.000Z');
assert.equal(component.itemValueForHTMLMode('test'), 'test');
});
});
});

it('Should protect against change loops', function(done) {
const formElement = document.createElement('div');
const form = new Webform(formElement);
const formJson = {
components: [
{
key: 'textField',
label: 'Text Field',
type: 'textfield',
calculateValue: "value = value + '_calculated'",
},
],
};
it('Should protect against change loops', function(done) {
const formElement = document.createElement('div');
const form = new Webform(formElement);
const formJson = {
components: [
{
key: 'textField',
label: 'Text Field',
type: 'textfield',
calculateValue: "value = value + '_calculated'",
},
],
};

form.setForm(formJson).then(() => {
const textField = form.getComponent('textField');
const spy = sinon.spy(textField, 'calculateComponentValue');
form.onChange({ textField: 'test' });
form.setForm(formJson).then(() => {
const textField = form.getComponent('textField');
const spy = sinon.spy(textField, 'calculateComponentValue');
form.onChange({ textField: 'test' });

setTimeout(() => {
expect(spy.calledOnce).to.be.true;
setTimeout(() => {
expect(spy.calledOnce).to.be.true;

done();
}, 500);
})
.catch((err) => done(err));
});
done();
}, 500);
})
.catch((err) => done(err));
});

it('Should mark as invalid only invalid fields in multiple components', function(done) {
const formElement = document.createElement('div');
const form = new Webform(formElement);
const formJson = {
components: [
{
label: 'Email',
tableView: true,
multiple: true,
validate: {
required: true
it('Should mark as invalid only invalid fields in multiple components', function(done) {
const formElement = document.createElement('div');
const form = new Webform(formElement);
const formJson = {
components: [
{
label: 'Email',
tableView: true,
multiple: true,
validate: {
required: true
},
key: 'email',
type: 'email',
input: true
},
key: 'email',
type: 'email',
input: true
},
],
};

form.setForm(formJson).then(() => {
return form.setSubmission({
data: {
email: [
'oleg@form.io',
'oleg@form',
'',
]
}
});
})
.then(() => {
setTimeout(() => {
const email = form.getComponent('email');
expect(email.refs.input[0].classList.contains('is-invalid')).to.be.false;
expect(email.refs.input[1].classList.contains('is-invalid')).to.be.true;
expect(email.refs.input[2].classList.contains('is-invalid')).to.be.true;
done();
}, 300);
})
.catch(done);
});
],
};

describe('shouldDisplayRedAsterisk', () => {
it('modalPreview template should have className "field-required" if component is required', done => {
Harness.testCreate(Component, _merge({}, comp4, {
validate: { required: true }
})).then(cmp => {
assert.equal(!!cmp.element.querySelector('.field-required'), true);
done();
}, done)
form.setForm(formJson).then(() => {
return form.setSubmission({
data: {
email: [
'oleg@form.io',
'oleg@form',
'',
]
}
});
})
.then(() => {
setTimeout(() => {
const email = form.getComponent('email');
expect(email.refs.input[0].classList.contains('is-invalid')).to.be.false;
expect(email.refs.input[1].classList.contains('is-invalid')).to.be.true;
expect(email.refs.input[2].classList.contains('is-invalid')).to.be.true;
done();
}, 300);
})
.catch(done);
});

it('Should sanitize HTML even if options.pdf is set', (done) => {
const component = new Component({}, { pdf: true });
assert.equal(component.sanitize('<a href="javascript:console.log("untrusted")></a>'), '<a></a>');
done();
});

describe('shouldDisplayRedAsterisk', () => {
it('modalPreview template should have className "field-required" if component is required', done => {
Harness.testCreate(Component, _merge({}, comp4, {
validate: { required: true }
})).then(cmp => {
assert.equal(!!cmp.element.querySelector('.field-required'), true);
done();
}, done)
.catch(done);
});
});
});
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3339,10 +3339,10 @@ domhandler@^2.3.0:
dependencies:
domelementtype "1"

dompurify@^2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.4.1.tgz#f9cb1a275fde9af6f2d0a2644ef648dd6847b631"
integrity sha512-ewwFzHzrrneRjxzmK6oVz/rZn9VWspGFRDb4/rRtIsM1n36t9AKma/ye8syCpcw+XJ25kOK/hOG7t1j2I2yBqA==
dompurify@^3.0.1, dompurify@^3.0.5:
version "3.0.5"
resolved "https://registry.npmjs.org/dompurify/-/dompurify-3.0.5.tgz#eb3d9cfa10037b6e73f32c586682c4b2ab01fbed"
integrity sha512-F9e6wPGtY+8KNMRAVfxeCOHU0/NPWMSENNq4pQctuXRqqdEPW7q3CrLbR5Nse044WwacyjHGOMlvNsBe1y6z9A==

domutils@1.5, domutils@1.5.1:
version "1.5.1"
Expand Down

0 comments on commit 60e317b

Please sign in to comment.