Skip to content
Open
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
66 changes: 51 additions & 15 deletions globals/sanitize-functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,48 @@
*/

/**
* Function to sanitize alpha color.
* Check whether a value is a well formed CSS variable expression.
*
* @param string $value Hex or RGBA color.
* @param mixed $value Value to check.
*
* @return bool
*/
function neve_is_css_var( $value ) {
if ( ! is_string( $value ) ) {
return false;
}

$value = trim( $value );

if ( $value === '' || strlen( $value ) > 200 ) {
return false;
}

$hex = '#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})';
$color_fn = '(?:rgb|rgba|hsl|hsla)\(\s*[0-9a-z.,%\/\s-]+\)';
$keyword = '[a-z]+(?:-[a-z]+)*';
$fallback = '(?:(?P>nv_var)|' . $hex . '|' . $color_fn . '|' . $keyword . ')';
$pattern = '/^(?P<nv_var>var\(\s*--[a-z0-9_-]+\s*(?:,\s*' . $fallback . '\s*)?\))$/i';

return (bool) preg_match( $pattern, $value );
}

/**
* Sanitize a color control value.
*
* @param mixed $value Color value: CSS variable, gradient, rgba() or hex.
*
* @return string
*/
function neve_sanitize_colors( $value ) {
$is_var = ( strpos( $value, 'var' ) !== false );
if ( ! is_string( $value ) && ! is_numeric( $value ) ) {
return '';
}

$value = (string) $value;

if ( $is_var ) {
return sanitize_text_field( $value );
if ( neve_is_css_var( $value ) ) {
return trim( $value );
}

if ( false !== strpos( $value, 'gradient' ) ) {
Expand All @@ -31,19 +62,25 @@ function neve_sanitize_colors( $value ) {
$mode = ( false === strpos( $value, 'rgba' ) ) ? 'hex' : 'rgba';
if ( 'rgba' === $mode ) {
return neve_sanitize_rgba( $value );
} else {
return sanitize_hex_color( $value );
}

$hex_color = sanitize_hex_color( $value );

return null === $hex_color ? '' : $hex_color;
}

/**
* Sanitize rgba color.
*
* @param string $value Color in rgba format.
* @param mixed $value Color in rgba format.
*
* @return string
*/
function neve_sanitize_rgba( $value ) {
if ( ! is_string( $value ) ) {
return 'rgba(0,0,0,0)';
}

$red = 'rgba(0,0,0,0)';
$green = 'rgba(0,0,0,0)';
$blue = 'rgba(0,0,0,0)';
Expand Down Expand Up @@ -165,18 +202,17 @@ function neve_sanitize_background( $value ) {
}


$value['imageUrl'] = esc_url( $value['imageUrl'] );
$value['colorValue'] = neve_sanitize_colors( $value['colorValue'] );
$value['overlayColorValue'] = neve_sanitize_colors( $value['overlayColorValue'] );

$value['imageUrl'] = esc_url( $value['imageUrl'] ?? '' );
$value['colorValue'] = neve_sanitize_colors( $value['colorValue'] ?? '' );
$value['overlayColorValue'] = neve_sanitize_colors( $value['overlayColorValue'] ?? '' );

$value['overlayOpacity'] = (int) $value['overlayOpacity'];
$value['overlayOpacity'] = (int) ( $value['overlayOpacity'] ?? 0 );
if ( $value['overlayOpacity'] > 100 || $value['overlayOpacity'] < 0 ) {
$value['overlayOpacity'] = 50;
}

$value['fixed'] = (bool) $value['fixed'];
$value['useFeatured'] = (bool) $value['useFeatured'];
$value['fixed'] = (bool) ( $value['fixed'] ?? false );
$value['useFeatured'] = (bool) ( $value['useFeatured'] ?? false );

return $value;
}
Expand Down
82 changes: 82 additions & 0 deletions tests/test-neve-sanitization.php
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,88 @@ public function test_sanitize_responsive_int_json() {
$this->do_assertion_for_sanitize_responsive_int_json( $input_value, $expected_value );
}

/**
* Test that color sanitization does not fatal on array or non-string values.
*/
public function test_sanitize_colors_with_non_string_values() {
$this->assertSame( '', neve_sanitize_colors( [ '#ffffff', '#000000' ] ) );
$this->assertSame( '', neve_sanitize_colors( [ 'mobile' => '#ffffff' ] ) );
$this->assertSame( '', neve_sanitize_colors( [] ) );

// Other non-color values are rejected too.
$this->assertSame( '', neve_sanitize_colors( null ) );
$this->assertSame( '', neve_sanitize_colors( true ) );
$this->assertSame( '', neve_sanitize_colors( new stdClass() ) );

// Invalid strings return an empty string, never null.
$this->assertSame( '', neve_sanitize_colors( 'not-a-color' ) );
$this->assertSame( '', neve_sanitize_colors( '' ) );

// Valid values keep working as before.
$this->assertSame( '#ff0000', neve_sanitize_colors( '#ff0000' ) );
$this->assertSame( 'rgba(255,0,0,1)', neve_sanitize_colors( 'rgba(255, 0, 0, 1)' ) );
$this->assertSame( 'var(--nv-primary-accent)', neve_sanitize_colors( 'var(--nv-primary-accent)' ) );
}

/**
* Test that only well formed CSS variable expressions are treated as CSS variables.
*/
public function test_is_css_var() {
$valid = [
'var(--nv-primary-accent)',
'var(--nv-c-1,#E5E7EB)',
'var(--nv-c-1, #e5e7eb)',
'var( --nv-site-bg , #fff )',
'var(--secondarybtnbg, transparent)',
'var(--x, rgba(0,0,0,.5))',
'var(--x, hsl(120 50% 50%))',
'var(--x, var(--y, #fff))',
];

foreach ( $valid as $value ) {
$this->assertTrue( neve_is_css_var( $value ), $value . ' should be a CSS var' );
}

$invalid = [
'avatar',
'varsity',
'#var',
'var',
'var()',
'var(--)',
'var(--x',
'var(--x))',
'var(--x);color:red',
'var(--x)}body{background:red}',
'var(--x, url(evil.css))',
'var(--x, "quoted")',
'linear-gradient(var(--a), var(--b))',
'',
[ 'var(--x)' ],
null,
];

foreach ( $invalid as $value ) {
$this->assertFalse( neve_is_css_var( $value ), var_export( $value, true ) . ' should not be a CSS var' );
}
}

/**
* Test that color sanitization does not pass off arbitrary strings as CSS variables.
*/
public function test_sanitize_colors_rejects_fake_css_vars() {
// Strings that merely contain "var" are not CSS variables.
$this->assertSame( '', neve_sanitize_colors( 'avatar' ) );
$this->assertSame( '', neve_sanitize_colors( 'var(--x);color:red' ) );
$this->assertSame( '', neve_sanitize_colors( 'var(--x)}body{background:red}' ) );
$this->assertSame( '', neve_sanitize_colors( 'var(--x, url(evil.css))' ) );

// Well formed CSS variables pass through untouched.
$this->assertSame( 'var(--nv-site-bg)', neve_sanitize_colors( 'var(--nv-site-bg)' ) );
$this->assertSame( 'var(--nv-c-1, #e5e7eb)', neve_sanitize_colors( ' var(--nv-c-1, #e5e7eb) ' ) );
$this->assertSame( 'var(--x, var(--y, #fff))', neve_sanitize_colors( 'var(--x, var(--y, #fff))' ) );
}

/**
* Private reusable function for the assertion of sanitize responsive int json.
*
Expand Down
Loading