Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add workaround to preserve CSS calc() functions #1116

Merged
merged 2 commits into from
May 4, 2018
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
77 changes: 76 additions & 1 deletion includes/sanitizers/class-amp-style-sanitizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,16 @@ class AMP_Style_Sanitizer extends AMP_Base_Sanitizer {
*/
private $parse_css_duration = 0.0;

/**
* Placeholders for calc() values that are temporarily removed from CSS since they cause parse errors.
*
* @since 1.0
* @see AMP_Style_Sanitizer::add_calc_placeholders()
*
* @var array
*/
private $calc_placeholders = array();

/**
* AMP_Base_Sanitizer constructor.
*
Expand Down Expand Up @@ -521,7 +531,7 @@ private function process_stylesheet( $stylesheet, $node, $options = array() ) {

$cache_key = md5( $stylesheet . serialize( $cache_impacting_options ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize

$cache_group = 'amp-parsed-stylesheet-v1';
$cache_group = 'amp-parsed-stylesheet-v2';
if ( wp_using_ext_object_cache() ) {
$parsed = wp_cache_get( $cache_key, $cache_group );
} else {
Expand Down Expand Up @@ -579,6 +589,9 @@ private function parse_stylesheet( $stylesheet_string, $options = array() ) {
$options
);

// Find calc() functions and replace with placeholders since PHP-CSS-Parser can't handle them.
$stylesheet_string = $this->add_calc_placeholders( $stylesheet_string );

$stylesheet = array();
$validation_errors = array();
try {
Expand Down Expand Up @@ -645,6 +658,15 @@ function ( $value ) {
$selectors_parsed[ $selector ] = $classes;
}

// Restore calc() functions that were replaced with placeholders.
if ( ! empty( $this->calc_placeholders ) ) {
$declaration = str_replace(
array_keys( $this->calc_placeholders ),
array_values( $this->calc_placeholders ),
$declaration
);
}

$stylesheet[] = array(
$selectors_parsed,
$declaration,
Expand All @@ -653,6 +675,9 @@ function ( $value ) {
$stylesheet[] = $split_stylesheet[ $i ];
}
}

// Reset calc placeholders.
$this->calc_placeholders = array();
} catch ( Exception $exception ) {
$validation_errors[] = array(
'code' => 'css_parse_error',
Expand All @@ -665,6 +690,56 @@ function ( $value ) {
return compact( 'stylesheet', 'validation_errors' );
}

/**
* Add placeholders for calc() functions which the PHP-CSS-Parser doesn't handle them properly yet.
*
* @since 1.0
* @link https://github.com/sabberworm/PHP-CSS-Parser/issues/79
*
* @param string $css CSS.
* @return string CSS with calc() functions replaced with placeholders.
*/
private function add_calc_placeholders( $css ) {
$offset = 0;
while ( preg_match( '/(?:-\w+-)?\bcalc\(/', $css, $matches, PREG_OFFSET_CAPTURE, $offset ) ) {
$match_string = $matches[0][0];
$match_offset = $matches[0][1];
$css_length = strlen( $css );
$open_parens = 1;
$start_offset = $match_offset + strlen( $match_string );
$final_offset = $start_offset;
for ( ; $final_offset < $css_length; $final_offset++ ) {
if ( '(' === $css[ $final_offset ] ) {
$open_parens++;
} elseif ( ')' === $css[ $final_offset ] ) {
$open_parens--;
} elseif ( ';' === $css[ $final_offset ] || '}' === $css[ $final_offset ] ) {
break; // Stop looking since clearly came to the end of the property. Unbalanced parentheses.
}

// Found the end of the calc() function, so replace it with a placeholder function.
if ( 0 === $open_parens ) {
$matched_calc = substr( $css, $match_offset, $final_offset - $match_offset + 1 );
$placeholder = sprintf( '-wp-calc-placeholder(%d)', count( $this->calc_placeholders ) );

// Store the placeholder function so the original calc() can be put in its place.
$this->calc_placeholders[ $placeholder ] = $matched_calc;

// Update the CSS to replace the matched calc() with the placeholder function.
$css = substr( $css, 0, $match_offset ) . $placeholder . substr( $css, $final_offset + 1 );

// Update offset based on difference of length of placeholder vs original matched calc().
$final_offset += strlen( $placeholder ) - strlen( $matched_calc );
break;
}
}

// Start matching at the next byte after the match.
$offset = $final_offset + 1;
}
return $css;
}

/**
* Process CSS list.
*
Expand Down
15 changes: 15 additions & 0 deletions tests/test-amp-style-sanitizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,21 @@ public function get_link_and_style_test_data() {
),
array(),
),
'styles_with_calc_functions' => array(
implode( '', array(
'<html amp><head>',
'<style amp-custom>body { color: red; width: -webkit-calc( 1px + 2vh * 3pt - ( 4em / 5 ) ); outline: solid 1px blue; }</style>',
'<style amp-custom>.alignwide{ max-width: calc(50% + 22.5rem); border: solid 1px red; }</style>',
'<style amp-custom>.alignwide{ height: calc(10% + ( 1px ); color: red; content: ")";}</style>', // Test unbalanced parentheses.
'</head><body><div class="alignwide"></div></body></html>',
) ),
array(
'body{color:red;width:-webkit-calc( 1px + 2vh * 3pt - ( 4em / 5 ) );outline:solid 1px blue;}',
'.alignwide{max-width:calc(50% + 22.5rem);border:solid 1px red;}',
'.alignwide{color:red;content:")";}',
),
array(),
),
);
}

Expand Down