diff --git a/.changeset/soft-plums-complain.md b/.changeset/soft-plums-complain.md new file mode 100644 index 00000000000..aab545de865 --- /dev/null +++ b/.changeset/soft-plums-complain.md @@ -0,0 +1,5 @@ +--- +'@talend/react-components': patch +--- + +fix: security issue on regexp diff --git a/.changeset/swift-flies-drum.md b/.changeset/swift-flies-drum.md new file mode 100644 index 00000000000..f8decceb4fd --- /dev/null +++ b/.changeset/swift-flies-drum.md @@ -0,0 +1,7 @@ +--- +'@talend/react-cmf': patch +--- + +fix: withoutHOC regex + +report says Polynomial regular expression used on uncontrolled data diff --git a/packages/cmf/__tests__/settings.test.js b/packages/cmf/__tests__/settings.test.js index eb82e8482cf..129db6434bc 100644 --- a/packages/cmf/__tests__/settings.test.js +++ b/packages/cmf/__tests__/settings.test.js @@ -1,7 +1,14 @@ -import { render, screen } from '@testing-library/react'; import { Provider } from 'react-redux'; -import { generateDefaultViewId, mapStateToViewProps, WaitForSettings } from '../src/settings'; + +import { render, screen } from '@testing-library/react'; + import { mock } from '../src'; +import { + generateDefaultViewId, + mapStateToViewProps, + WaitForSettings, + withoutHOC, +} from '../src/settings'; describe('settings', () => { describe('mapStateToViewProps', () => { @@ -62,6 +69,7 @@ describe('settings', () => { expect(generateDefaultViewId()).toBe(undefined); }); }); + describe('WaitForSettings', () => { it('should display using loader if state settings is not initialized', () => { const state = mock.store.state(); @@ -99,4 +107,10 @@ describe('settings', () => { expect(() => screen.getByText('loading')).toThrow(); }); }); + + describe('withoutHOC', () => { + it('should remove all HOC prefix', () => { + expect(withoutHOC('Connect(CMF(Container(MyComponent)))')).toBe('MyComponent'); + }); + }); }); diff --git a/packages/cmf/src/settings.js b/packages/cmf/src/settings.js index a4de373e603..d9fe6171b9d 100644 --- a/packages/cmf/src/settings.js +++ b/packages/cmf/src/settings.js @@ -2,9 +2,10 @@ * Internal. All stuff related to the settings handling in CMF. * @module react-cmf/lib/settings */ -import PropTypes from 'prop-types'; import { connect } from 'react-redux'; + import memoize from 'lodash/memoize'; +import PropTypes from 'prop-types'; /** * if viewId is undefined, try to generate a meaningfull one @@ -29,7 +30,7 @@ export function generateDefaultViewId(viewId, componentName, componentId) { * @param {String} viewId Connect(CMF(Container(MyComponent))) * @return {String} MyComponent */ -function withoutHOC(componentName) { +export function withoutHOC(componentName) { return componentName.match(/.*\((.*?)\)/)[1]; } diff --git a/packages/components/src/FormatValue/FormatValue.component.js b/packages/components/src/FormatValue/FormatValue.component.js index b9b67c5de55..b620b9fd0fc 100644 --- a/packages/components/src/FormatValue/FormatValue.component.js +++ b/packages/components/src/FormatValue/FormatValue.component.js @@ -1,14 +1,14 @@ import { useTranslation } from 'react-i18next'; import classNames from 'classnames'; +import { escapeRegExp } from 'lodash'; import PropTypes from 'prop-types'; -import Icon from '../Icon'; import I18N_DOMAIN_COMPONENTS from '../constants'; +import Icon from '../Icon'; import theme from './FormatValue.module.scss'; -export const REG_EXP_LEADING_TRAILING_WHITE_SPACE_CHARACTERS = /(^\s*)?([\s\S]*?)(\s*$)/; const REG_EXP_REPLACED_WHITE_SPACE_CHARACTERS = /(\t| |\n)/g; const REG_EXP_CAPTUR_LINE_FEEDING = /(\n)/g; const REG_EXP_LINE_FEEDING = /\n/; @@ -104,14 +104,14 @@ export function FormatValueComponent({ value, className }) { let formattedValue = value; if (typeof value === 'string') { - const hiddenCharsRegExpMatch = value.match(REG_EXP_LEADING_TRAILING_WHITE_SPACE_CHARACTERS); + const regExp = new RegExp(`(${escapeRegExp(value.trim())})`); + const hiddenCharsRegExpSplit = value.split(regExp); if ( - hiddenCharsRegExpMatch[1] || - hiddenCharsRegExpMatch[3] || + hiddenCharsRegExpSplit[0] || + hiddenCharsRegExpSplit[2] || REG_EXP_LINE_FEEDING.test(value) ) { - formattedValue = hiddenCharsRegExpMatch - .slice(1) + formattedValue = hiddenCharsRegExpSplit .flatMap((flatMapValue, index) => flatMapValue?.split(SPLIT_REGEX[index])) .filter(isEmptyCharacter) .map((mappedValue, index) => replaceCharacterByIcon(mappedValue, index, t)); diff --git a/packages/jsfc/package.json b/packages/jsfc/package.json index bb793006eb9..d9c7481d7ac 100644 --- a/packages/jsfc/package.json +++ b/packages/jsfc/package.json @@ -9,7 +9,7 @@ "watch": "webpack --watch", "dist-untested": "webpack --config webpack.config.dist.js", "test:cov": "npm run test", - "test": "echo need to fix all tests before" + "test": "talend-scripts test" }, "author": "json-schema-form", "contributors": [ diff --git a/packages/jsfc/src/lib/canonical-title-map.spec.js b/packages/jsfc/src/canonical-title-map.spec.js similarity index 100% rename from packages/jsfc/src/lib/canonical-title-map.spec.js rename to packages/jsfc/src/canonical-title-map.spec.js diff --git a/packages/jsfc/src/lib/canonical-title-map.ts b/packages/jsfc/src/canonical-title-map.ts similarity index 86% rename from packages/jsfc/src/lib/canonical-title-map.ts rename to packages/jsfc/src/canonical-title-map.ts index 9721a7b0d87..e73dbfb5b3f 100644 --- a/packages/jsfc/src/lib/canonical-title-map.ts +++ b/packages/jsfc/src/canonical-title-map.ts @@ -1,6 +1,6 @@ // Takes a titleMap in either object or list format and returns one // in the list format. -export default function(titleMap: Array, originalEnum?: any) { +export default function (titleMap: Array, originalEnum?: any) { if (!Array.isArray(titleMap)) { const canonical = []; if (originalEnum) { diff --git a/packages/jsfc/src/index.js b/packages/jsfc/src/index.js index 8728284627e..aae62c10aae 100644 --- a/packages/jsfc/src/index.js +++ b/packages/jsfc/src/index.js @@ -1,17 +1,17 @@ import tv4index from 'tv4'; // eslint-disable-next-line prettier/prettier -import canonicalTitleMapImp from './lib/canonical-title-map'; +import * as schemaDefaultsImp from './schema-defaults'; // eslint-disable-next-line prettier/prettier -import * as schemaDefaultsImp from './lib/schema-defaults'; +import * as sfPathImp from './sf-path'; // eslint-disable-next-line prettier/prettier -import * as sfPathImp from './lib/sf-path'; +import canonicalTitleMapImp from './canonical-title-map'; -export { merge } from './lib/merge'; -export { select } from './lib/select'; -export { jsonref } from './lib/resolve'; -export { traverseSchema, traverseForm } from './lib/traverse'; -export { validate } from './lib/validate'; +export { merge } from './merge'; +export { select } from './select'; +export { jsonref } from './resolve'; +export { traverseSchema, traverseForm } from './traverse'; +export { validate } from './validate'; export const sfPath = sfPathImp; export const schemaDefaults = schemaDefaultsImp; diff --git a/packages/jsfc/src/lib/sf-path.spec.js b/packages/jsfc/src/lib/sf-path.spec.js deleted file mode 100644 index 4612c33fc91..00000000000 --- a/packages/jsfc/src/lib/sf-path.spec.js +++ /dev/null @@ -1,38 +0,0 @@ -import chai from 'chai'; -import { describe, it } from 'mocha'; -import { parse, stringify, normalize, name } from './sf-path'; - -chai.should(); - -describe('sf-path.js', () => { - it('should hold functions for working with object paths and keys', () => { - parse.should.be.an('function'); - stringify.should.be.an('function'); - normalize.should.be.an('function'); - name.should.be.an('function'); - }); - - // describe('parse', () => { - // it('should ', () => { - // should.be.eq(); - // }); - // }); - // - // describe('stringify', () => { - // it('should ', () => { - // should.be.eq(); - // }); - // }); - // - // describe('normalize', () => { - // it('should ', () => { - // should.be.eq(); - // }); - // }); - // - // describe('name', () => { - // it('should ', () => { - // should.be.eq(); - // }); - // }); -}); diff --git a/packages/jsfc/src/lib/traverse.spec.js b/packages/jsfc/src/lib/traverse.spec.js deleted file mode 100644 index 11fdc0500e8..00000000000 --- a/packages/jsfc/src/lib/traverse.spec.js +++ /dev/null @@ -1,24 +0,0 @@ -import chai from 'chai'; -import { describe, it } from 'mocha'; -import { traverseSchema, traverseForm } from './traverse'; - -chai.should(); - -describe('traverse.js', () => { - it('should hold functions for applying functions on branches of a json-schema or ui-schema', () => { - traverseSchema.should.be.an('function'); - traverseForm.should.be.an('function'); - }); - - // describe('traverseSchema', () => { - // it('should ', () => { - // should.be.eq(); - // }); - // }); - // - // describe('traverseForm', () => { - // it('should ', () => { - // should.be.eq(); - // }); - // }); -}); diff --git a/packages/jsfc/src/lib/merge.js b/packages/jsfc/src/merge.js similarity index 97% rename from packages/jsfc/src/lib/merge.js rename to packages/jsfc/src/merge.js index 13c1b578fb3..f9d866fbb60 100644 --- a/packages/jsfc/src/lib/merge.js +++ b/packages/jsfc/src/merge.js @@ -40,26 +40,26 @@ export function merge( //simple case, we have a "...", just put the formItemRest there if (stdForm.form && idxRest !== -1) { let formKeys = form - .map(function(obj) { + .map(function (obj) { if (typeof obj === 'string') { return obj; } else if (obj.key) { return obj.key; } }) - .filter(function(element) { + .filter(function (element) { return element !== undefined; }); formItemRest = formItemRest.concat( stdForm.form - .map(function(obj) { + .map(function (obj) { let isInside = formKeys.indexOf(obj.key[0]) !== -1; if (!isInside) { return obj; } }) - .filter(function(element) { + .filter(function (element) { return element !== undefined; }), ); diff --git a/packages/jsfc/src/lib/merge.spec.js b/packages/jsfc/src/merge.spec.js similarity index 100% rename from packages/jsfc/src/lib/merge.spec.js rename to packages/jsfc/src/merge.spec.js diff --git a/packages/jsfc/src/lib/resolve.js b/packages/jsfc/src/resolve.js similarity index 78% rename from packages/jsfc/src/lib/resolve.js rename to packages/jsfc/src/resolve.js index 478b2fce880..ee81a78d056 100644 --- a/packages/jsfc/src/lib/resolve.js +++ b/packages/jsfc/src/resolve.js @@ -1,7 +1,7 @@ -import * as JsonRefs from './../../lib/json-refs-standalone'; +import * as JsonRefs from './../lib/json-refs-standalone'; export function jsonref(schema, callBack) { - let promise = new Promise(function(resolve, reject) { + let promise = new Promise(function (resolve, reject) { JsonRefs.resolveRefs(schema, { filter: ['relative', 'local', 'remote'], }) diff --git a/packages/jsfc/src/lib/resolve.spec.js b/packages/jsfc/src/resolve.spec.js similarity index 94% rename from packages/jsfc/src/lib/resolve.spec.js rename to packages/jsfc/src/resolve.spec.js index 989c57e71d5..b8ae8cabf42 100644 --- a/packages/jsfc/src/lib/resolve.spec.js +++ b/packages/jsfc/src/resolve.spec.js @@ -83,8 +83,7 @@ describe('resolve.js', () => { type: 'object', properties: { relative: { - $ref: - 'https://raw.githubusercontent.com/json-schema-org/json-schema-org.github.io/master/geo', + $ref: 'https://raw.githubusercontent.com/json-schema-org/json-schema-org.github.io/master/geo', }, }, }; @@ -112,7 +111,7 @@ describe('resolve.js', () => { }); it('should resolve relative json-ref via callback', done => { - jsonref(schema, function(error, resolved) { + jsonref(schema, function (error, resolved) { if (error) done(error); resolved.properties.storage.oneOf[0].properties.should.have.property('device'); done(); @@ -121,7 +120,7 @@ describe('resolve.js', () => { //I believe this only fails in phantomjs due to https://github.com/ariya/phantomjs/issues/11195 it('should resolve remote json-ref via callback', done => { - jsonref(remote, function(error, resolved) { + jsonref(remote, function (error, resolved) { if (error) done(error); //resolved.properties.relative.latitude.type.should.equal('number'); done(); diff --git a/packages/jsfc/src/lib/schema-defaults.spec.js b/packages/jsfc/src/schema-defaults.test.js similarity index 78% rename from packages/jsfc/src/lib/schema-defaults.spec.js rename to packages/jsfc/src/schema-defaults.test.js index dd0a0a5d81c..96a96682238 100644 --- a/packages/jsfc/src/lib/schema-defaults.spec.js +++ b/packages/jsfc/src/schema-defaults.test.js @@ -1,9 +1,5 @@ -import chai from 'chai'; -import { describe, it } from 'mocha'; import { select } from './select'; -chai.should(); - describe('select.js', () => { const data = { name: 'Freddy', @@ -36,28 +32,31 @@ describe('select.js', () => { }; it('should provide a function for getting and setting an object value', () => { - select.should.be.an('function'); + expect(typeof select).toBe('function'); }); describe('select', () => { it('should get a value from an object', () => { let value = select('frienemies[1].weapon.boomstick.method[0]', data); - value.should.eq('boom'); + expect(value).toBe('boom'); }); it('should set a value on an object', () => { let value = select('weapon.glove.method[1]', data, 'slice'); - data.weapon.glove.method.should.be.deep.equal(['stab', 'slice']); + expect(value).toBe('slice'); + expect(data.weapon.glove.method[1]).toBe('slice'); + expect(data.weapon.glove.method).toEqual(['stab', 'slice']); }); it('should create any undefined objects or arrays in the path when setting a value', () => { let data = {}; let value = select('property.array[1].value', data, 'something'); - data.should.be.deep.equal({ + expect(data).toMatchObject({ property: { array: [, { value: 'something' }], }, }); + expect(value).toBe('something'); }); }); }); diff --git a/packages/jsfc/src/lib/schema-defaults.ts b/packages/jsfc/src/schema-defaults.ts similarity index 100% rename from packages/jsfc/src/lib/schema-defaults.ts rename to packages/jsfc/src/schema-defaults.ts diff --git a/packages/jsfc/src/lib/select.js b/packages/jsfc/src/select.js similarity index 100% rename from packages/jsfc/src/lib/select.js rename to packages/jsfc/src/select.js diff --git a/packages/jsfc/src/lib/select.spec.js b/packages/jsfc/src/select.test.js similarity index 94% rename from packages/jsfc/src/lib/select.spec.js rename to packages/jsfc/src/select.test.js index 280089de3c0..798cf775ebf 100644 --- a/packages/jsfc/src/lib/select.spec.js +++ b/packages/jsfc/src/select.test.js @@ -1,19 +1,15 @@ -import chai from 'chai'; -import { describe, it } from 'mocha'; import { defaultForm, createDefaults } from './schema-defaults'; -chai.should(); - describe('schema-defaults.js', () => { it('should hold functions for generating a default form schema from defaults it creates', () => { - defaultForm.should.be.an('function'); - createDefaults.should.be.an('function'); + expect(typeof defaultForm).toBe('function'); + expect(typeof createDefaults).toBe('function'); }); describe('createDefaults', () => { it('should create default rules', () => { const rules = createDefaults(); - rules.should.be.an('object'); + expect(typeof rules).toBe('object'); }); }); @@ -55,7 +51,6 @@ describe('schema-defaults.js', () => { }, }, }; - const form = [ { title: 'Name', @@ -199,9 +194,8 @@ describe('schema-defaults.js', () => { ], }, ]; - const f = defaultForm(schema, createDefaults()); - f.form.should.be.deep.equal(form); + expect(f.form).toMatchObject(form); }); }); }); diff --git a/packages/jsfc/src/sf-path.test.js b/packages/jsfc/src/sf-path.test.js new file mode 100644 index 00000000000..41e2472997b --- /dev/null +++ b/packages/jsfc/src/sf-path.test.js @@ -0,0 +1,10 @@ +import { parse, stringify, normalize, name } from './sf-path'; + +describe('sf-path.js', () => { + it('should hold functions for working with object paths and keys', () => { + expect(typeof parse).toBe('function'); + expect(typeof stringify).toBe('function'); + expect(typeof normalize).toBe('function'); + expect(typeof name).toBe('function'); + }); +}); diff --git a/packages/jsfc/src/lib/sf-path.ts b/packages/jsfc/src/sf-path.ts similarity index 94% rename from packages/jsfc/src/lib/sf-path.ts rename to packages/jsfc/src/sf-path.ts index e68709949eb..2ff737d6a1f 100644 --- a/packages/jsfc/src/lib/sf-path.ts +++ b/packages/jsfc/src/sf-path.ts @@ -18,7 +18,7 @@ export function name(key: Array, separator?: string, formName = '', omit let fieldSeparator = separator || '-'; if (omitNumbers) { - fieldKey = fieldKey.filter(function(currentKey: any) { + fieldKey = fieldKey.filter(function (currentKey: any) { return typeof currentKey !== 'number'; }); } diff --git a/packages/jsfc/src/traverse.test.js b/packages/jsfc/src/traverse.test.js new file mode 100644 index 00000000000..dc13f77fd3f --- /dev/null +++ b/packages/jsfc/src/traverse.test.js @@ -0,0 +1,8 @@ +import { traverseSchema, traverseForm } from './traverse'; + +describe('traverse.js', () => { + it('should hold functions for applying functions on branches of a json-schema or ui-schema', () => { + expect(typeof traverseSchema).toBe('function'); + expect(typeof traverseForm).toBe('function'); + }); +}); diff --git a/packages/jsfc/src/lib/traverse.ts b/packages/jsfc/src/traverse.ts similarity index 97% rename from packages/jsfc/src/lib/traverse.ts rename to packages/jsfc/src/traverse.ts index 76c1ce7b32c..07cca79ef19 100644 --- a/packages/jsfc/src/lib/traverse.ts +++ b/packages/jsfc/src/traverse.ts @@ -7,7 +7,7 @@ export function traverseSchema(schema, fn, path, ignoreArrays) { path = path || []; - const traverse = function( + const traverse = function ( schemaObject: any, processorFunction: Function, pathArray: Array, diff --git a/packages/jsfc/src/lib/validate.js b/packages/jsfc/src/validate.js similarity index 100% rename from packages/jsfc/src/lib/validate.js rename to packages/jsfc/src/validate.js diff --git a/packages/jsfc/src/lib/validate.spec.js b/packages/jsfc/src/validate.test.js similarity index 71% rename from packages/jsfc/src/lib/validate.spec.js rename to packages/jsfc/src/validate.test.js index 59b2239e1f7..e6c61b1c605 100644 --- a/packages/jsfc/src/lib/validate.spec.js +++ b/packages/jsfc/src/validate.test.js @@ -1,12 +1,8 @@ -import chai from 'chai'; -import { describe, it } from 'mocha'; import { validate } from './validate'; -let should = chai.should(); - describe('validate.js', () => { it('should hold a validation function for testing against tv4 until an option to pass in a validator is created', () => { - validate.should.be.an('function'); + expect(typeof validate).toBe('function'); }); describe('validate', () => { @@ -14,16 +10,15 @@ describe('validate.js', () => { it('should return a result object {"error":null, "missing":[], "valid":true}, with valid set to true when the data is valid for the schema', () => { let value = 'Batman'; let result = validate(form, value, {}); - should.not.exist(result.error); - result.missing.should.be.a('array'); - result.missing.length.should.equal(0); - result.valid.should.equal(true); + expect(result.error).toBe(null); + expect(result.missing).toEqual([]); + expect(result.valid).toBe(true); }); it('should return an error object with a message "Invalid type: array (expected string)" when the data is not valid', () => { let value = [0]; let result = validate(form, value, {}); - result.error.message.should.eq('Invalid type: array (expected string)'); + expect(result.error.message).toBe('Invalid type: array (expected string)'); }); it('should return an error object with a message "CUSTOM_ERROR_INVALID_INPUT" when the integer value is not valid', () => { @@ -31,7 +26,7 @@ describe('validate.js', () => { const testForm = { type: 'number', key: ['hero'], schema: { type: 'number' } }; const event = { target: { validity: { valid: false } } }; let result = validate(testForm, value, event); - result.error.message.should.eq('CUSTOM_ERROR_INVALID_INPUT'); + expect(result.error.message).toBe('CUSTOM_ERROR_INVALID_INPUT'); }); }); });