CSSMin::serializeStringValue: Update implementation to new specification
authorFomafix <fomafix@googlemail.com>
Wed, 11 Apr 2018 04:23:50 +0000 (06:23 +0200)
committerFomafix <fomafix@googlemail.com>
Fri, 13 Apr 2018 03:58:25 +0000 (05:58 +0200)
The current version of https://www.w3.org/TR/cssom/ is
https://www.w3.org/TR/2016/WD-cssom-1-20160317/

The new specification for CSS string serialization
https://www.w3.org/TR/2016/WD-cssom-1-20160317/#serialize-a-string
has some changes compared to the old specification
https://www.w3.org/TR/2013/WD-cssom-20131205/#serialize-a-string
* U+0000 get replaced by the REPLACEMENT CHARACTER (U+FFFD) instead of
  throwing an exception.
* U+0080 to U+009F are not escaped.

The old implementation has a bug because it selects the byte range
[\x7f-\x9f] and not unicode codepoint range [\u007f-\u009f]. This
breaks the encoding because CSS is in UTF-8 not in ISO 8859-x.

Also add tests to cover CSSMin::serializeStringValue.

Bug: T192048
Change-Id: I894824c216b95dbba461308488fba33121ffea54

includes/libs/CSSMin.php
tests/phpunit/includes/libs/CSSMinTest.php

index 1cbcbde..73825e8 100644 (file)
@@ -173,18 +173,14 @@ class CSSMin {
 
        /**
         * Serialize a string (escape and quote) for use as a CSS string value.
-        * http://www.w3.org/TR/2013/WD-cssom-20131205/#serialize-a-string
+        * https://www.w3.org/TR/2016/WD-cssom-1-20160317/#serialize-a-string
         *
         * @param string $value
         * @return string
-        * @throws Exception
         */
        public static function serializeStringValue( $value ) {
-               if ( strstr( $value, "\0" ) ) {
-                       throw new Exception( "Invalid character in CSS string" );
-               }
-               $value = strtr( $value, [ '\\' => '\\\\', '"' => '\\"' ] );
-               $value = preg_replace_callback( '/[\x01-\x1f\x7f-\x9f]/', function ( $match ) {
+               $value = strtr( $value, [ "\0" => "\\fffd ", '\\' => '\\\\', '"' => '\\"' ] );
+               $value = preg_replace_callback( '/[\x01-\x1f\x7f]/', function ( $match ) {
                        return '\\' . base_convert( ord( $match[0] ), 10, 16 ) . ' ';
                }, $value );
                return '"' . $value . '"';
index f2d5ef3..59a1c7c 100644 (file)
@@ -22,6 +22,43 @@ class CSSMinTest extends MediaWikiTestCase {
                ] );
        }
 
+       /**
+        * @dataProvider serializeStringValueProvider
+        * @covers CSSMin::serializeStringValue
+        */
+       public function testSerializeStringValue( $input, $expected ) {
+               $output = CSSMin::serializeStringValue( $input );
+               $this->assertEquals(
+                       $expected,
+                       $output,
+                       'Serialized output must be in the expected form.'
+               );
+       }
+
+       public function serializeStringValueProvider() {
+               return [
+                       [ 'Hello World!', '"Hello World!"' ],
+                       [ "Null\0Null", "\"Null\\fffd Null\"" ],
+                       [ '"', '"\\""' ],
+                       [ "'", '"\'"' ],
+                       [ "\\", '"\\\\"' ],
+                       [ "Tab\tTab", '"Tab\\9 Tab"' ],
+                       [ "Space  tab \t space", '"Space  tab \\9  space"' ],
+                       [ "Line\nfeed", '"Line\\a feed"' ],
+                       [ "Return\rreturn", '"Return\\d return"' ],
+                       [ "Next\xc2\x85line", "\"Next\xc2\x85line\"" ],
+                       [ "Del\x7fDel", '"Del\\7f Del"' ],
+                       [ "nb\xc2\xa0sp", "\"nb\xc2\xa0sp\"" ],
+                       [ "AMP&amp;AMP", "\"AMP&amp;AMP\"" ],
+                       [ '!"#$%&\'()*+,-./0123456789:;<=>?', '"!\\"#$%&\'()*+,-./0123456789:;<=>?"' ],
+                       [ '@[\\]^_`{|}~', '"@[\\\\]^_`{|}~"' ],
+                       [ 'ä', '"ä"' ],
+                       [ 'Ä', '"Ä"' ],
+                       [ '€', '"€"' ],
+                       [ '𝒞', '"𝒞"' ], // U+1D49E 'MATHEMATICAL SCRIPT CAPITAL C'
+               ];
+       }
+
        /**
         * @dataProvider mimeTypeProvider
         * @covers CSSMin::getMimeType