diff --git a/src/hoc/withHtmlAttributes.js b/src/hoc/withHtmlAttributes.js index d35968925..27e6cd812 100644 --- a/src/hoc/withHtmlAttributes.js +++ b/src/hoc/withHtmlAttributes.js @@ -7,6 +7,7 @@ import { applyFilters } from '@wordpress/hooks'; import { useUpdateEffect } from 'react-use'; import { convertInlineStyleStringToObject } from '@utils/convertInlineStyleStringToObject'; +import { sanitizeHtmlAttribute } from '@utils/sanitizeHtmlAttribute'; export const booleanAttributes = [ 'allowfullscreen', @@ -60,16 +61,6 @@ function shallowEqual( obj1, obj2 ) { return true; } -const sanitizeAttributeValue = ( value ) => { - // Replace characters like &, <, >, " with their HTML entity equivalents - return value - .replace( /&/g, '&' ) - .replace( //g, '>' ) - .replace( /"/g, '"' ) - .replace( /'/g, ''' ); -}; - export function withHtmlAttributes( WrappedComponent ) { return ( ( props ) => { const { @@ -88,7 +79,7 @@ export function withHtmlAttributes( WrappedComponent ) { const isSavingPost = useSelect( ( select ) => select( 'core/editor' ).isSavingPost() ); const { style = '', href, ...otherAttributes } = htmlAttributes; const escapedAttributes = Object.keys( otherAttributes ).reduce( ( acc, key ) => { - acc[ key ] = sanitizeAttributeValue( otherAttributes[ key ] ); + acc[ key ] = sanitizeHtmlAttribute( otherAttributes[ key ] ); return acc; }, {} ); const [ processedStyle, setProcessedStyle ] = useState( style ); diff --git a/src/tests/unit/sanitizeHtmlAttribute.test.js b/src/tests/unit/sanitizeHtmlAttribute.test.js new file mode 100644 index 000000000..a81039a01 --- /dev/null +++ b/src/tests/unit/sanitizeHtmlAttribute.test.js @@ -0,0 +1,54 @@ +import { sanitizeHtmlAttribute } from '../../utils/sanitizeHtmlAttribute'; + +describe( 'sanitize HTML attribute', () => { + it( 'should return the same value if it is a string', () => { + const value = sanitizeHtmlAttribute( 'foo' ); + expect( value ).toEqual( 'foo' ); + } ); + + it( 'should convert a number to a string', () => { + const value = sanitizeHtmlAttribute( 500 ); + expect( value ).toEqual( '500' ); + } ); + + it( 'should convert an object to a string', () => { + const value = sanitizeHtmlAttribute( { foo: 'bar' } ); + expect( value ).toEqual( '{"foo":"bar"}' ); + } ); + + it( 'should convert an array to a string', () => { + const value = sanitizeHtmlAttribute( [ 'foo', 'bar' ] ); + expect( value ).toEqual( '["foo","bar"]' ); + } ); + + it( 'should handle undefined', () => { + const value = sanitizeHtmlAttribute( undefined ); + expect( value ).toEqual( '' ); + } ); + + it( 'should handle null', () => { + const value = sanitizeHtmlAttribute( null ); + expect( value ).toEqual( '' ); + } ); + + it( 'should escape HTML special characters', () => { + const value = sanitizeHtmlAttribute( 'foo & "quote" \'single\'' ); + expect( value ).toEqual( 'foo & <bar> "quote" 'single'' ); + } ); + + it( 'should handle boolean values', () => { + expect( sanitizeHtmlAttribute( true ) ).toEqual( 'true' ); + expect( sanitizeHtmlAttribute( false ) ).toEqual( 'false' ); + } ); + + it( 'should handle special number values', () => { + expect( sanitizeHtmlAttribute( NaN ) ).toEqual( 'NaN' ); + expect( sanitizeHtmlAttribute( Infinity ) ).toEqual( 'Infinity' ); + expect( sanitizeHtmlAttribute( -Infinity ) ).toEqual( '-Infinity' ); + } ); + + it( 'should handle empty string', () => { + const value = sanitizeHtmlAttribute( '' ); + expect( value ).toEqual( '' ); + } ); +} ); diff --git a/src/utils/sanitizeHtmlAttribute.js b/src/utils/sanitizeHtmlAttribute.js new file mode 100644 index 000000000..328004663 --- /dev/null +++ b/src/utils/sanitizeHtmlAttribute.js @@ -0,0 +1,25 @@ +export const sanitizeHtmlAttribute = ( value ) => { + if ( null === value || undefined === value ) { + return ''; + } + + let stringValue = ''; + + if ( 'object' === typeof value ) { + try { + stringValue = JSON.stringify( value ); + } catch ( e ) { + return ''; + } + } else { + stringValue = String( value ); + } + + // Replace characters like &, <, >, " with their HTML entity equivalents + return stringValue + .replace( /&/g, '&' ) + .replace( //g, '>' ) + .replace( /"/g, '"' ) + .replace( /'/g, ''' ); +};