Skip to content
This repository was archived by the owner on Mar 4, 2020. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Fix Avatar alignment issue and initials for long names @mnajdova ([#38](https://github.com/stardust-ui/react/pull/38))
- Changing the default styles for Input component @alinais ([#25](https://github.com/stardust-ui/react/pull/25))
- Upgrade Typescript to version 3.0.1 @luzhon ([#67](https://github.com/stardust-ui/react/pull/67))
- Prevent Fela from rendering CSS property values that could crash all styling on the page @kuzhelov ([#65](https://github.com/stardust-ui/react/pull/65))

### Features
- Behaviors for accessibility roles and other ARIA attributes @smykhailov, @jurokapsiar, @sophieH29 ([#29](https://github.com/stardust-ui/react/pull/29))
Expand Down
8 changes: 8 additions & 0 deletions src/lib/felaRenderer.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
import { createRenderer } from 'fela'
import felaSanitizeCss from './felaSanitizeCssPlugin'
import felaPluginFallbackValue from 'fela-plugin-fallback-value'
import felaPluginPlaceholderPrefixer from 'fela-plugin-placeholder-prefixer'
import felaPluginPrefixer from 'fela-plugin-prefixer'
import rtl from 'fela-plugin-rtl'

const createRendererConfig = (options: any = {}) => ({
plugins: [
// is necessary to prevent accidental style typos
// from breaking ALL the styles on the page
felaSanitizeCss({
skip: ['content'],
}),

felaPluginPlaceholderPrefixer(),
felaPluginPrefixer(),

// Heads up!
// This is required after fela-plugin-prefixer to resolve the array of fallback values prefixer produces.
felaPluginFallbackValue(),
Expand Down
63 changes: 63 additions & 0 deletions src/lib/felaSanitizeCssPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/**
* Checks whether provided CSS property value is safe for being rendered by Fela engine.
*/
const isValidCssValue = (value: any) => {
if (typeof value !== 'string') {
return true
}

const openingBrackets = '({['
const closingBrackets = ')}]'

const openingBracketsStack = []

/**
* This loop logic checks whether braces sequence of input argument is valid.
* Essentially, it ensures that each of the '(', '{', '[' braces
* - is properly matched by its complementary closing character
* - closing brace properly corresponds to the last opened one
*/
for (let i = 0; i < value.length; ++i) {
const currentCharacter = value[i]
if (openingBrackets.includes(currentCharacter)) {
openingBracketsStack.push(currentCharacter)
} else if (closingBrackets.includes(currentCharacter)) {
const lastOpeningBracket = openingBracketsStack.pop()
if (
openingBrackets.indexOf(lastOpeningBracket) !== closingBrackets.indexOf(currentCharacter)
) {
return false
}
}
}

return openingBracketsStack.length === 0
}

export default (config?: { skip?: string[] }) => {
const cssPropertiesToSkip = [...((config && config.skip) || [])]

const sanitizeCssStyleObject = styles => {
const processedStyles = {}

Object.keys(styles).forEach(cssPropertyName => {
const cssPropertyValue = styles[cssPropertyName]

if (typeof cssPropertyValue === 'object') {
processedStyles[cssPropertyName] = sanitizeCssStyleObject(cssPropertyValue)
return
}

const isPropertyToSkip = cssPropertiesToSkip.some(
propToExclude => propToExclude === cssPropertyName,
)
if (isPropertyToSkip || isValidCssValue(cssPropertyValue)) {
processedStyles[cssPropertyName] = cssPropertyValue
}
})

return processedStyles
}

return sanitizeCssStyleObject
}
65 changes: 65 additions & 0 deletions test/specs/lib/felaSanitizeCssPlugin-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import sanitizeCss from 'src/lib/felaSanitizeCssPlugin'

const assertCssPropertyValue = (value: string, isValid: boolean) => {
test(`assert that '${value}' is ${isValid ? 'valid' : 'invalid'}`, () => {
const sanitize = sanitizeCss()

const style = { display: value }
const sanitizedStyle = sanitize(style)

expect(sanitizedStyle).toEqual(isValid ? style : {})
})
}

const sanitize = sanitizeCss()

describe('felaSanitizeCssPlugin', () => {
test('should ensure there are no non-closed brackets in CSS property value', () => {
const style = {
display: 'block',
backgroundImage: 'url(../../',
}

expect(sanitize(style)).toEqual({ display: 'block' })
})

test('should skip numeric CSS property values', () => {
expect(sanitize({ top: 0 })).toEqual({ top: 0 })
})

test('should recursively process nested objects', () => {
const style = {
display: 'inline',
'::before': {
color: 'rgba(',
},
}

expect(sanitize(style)).toEqual({
display: 'inline',
'::before': {},
})
})

test('should skip excluded CSS props', () => {
const sanitize = sanitizeCss({
skip: ['propertyWithInvalidValue'],
})

const style = {
display: 'block',
margin: '0 0 0 0',
propertyWithInvalidValue: 'rgba(',
}

expect(sanitize(style)).toEqual(style)
})

describe('should properly filter invalid bracket sequences', () => {
assertCssPropertyValue('rgba(', false)
assertCssPropertyValue('rgba(0,0', false)
assertCssPropertyValue('rgba(0,0}', false)

assertCssPropertyValue(`url('../../lib')`, true)
})
})