Merge "Fix comment for grep in SpecialWantedfiles.php"
[lhc/web/wiklou.git] / includes / libs / CSSMin.php
index 6eb5258..f415c9b 100644 (file)
@@ -31,13 +31,12 @@ class CSSMin {
 
        /* Constants */
 
+       /** @var string Strip marker for comments. **/
+       const PLACEHOLDER = "\x7fPLACEHOLDER\x7f";
+
        /**
-        * Maximum file size to still qualify for in-line embedding as a data-URI
-        *
-        * 24,576 is used because Internet Explorer has a 32,768 byte limit for data URIs,
-        * which when base64 encoded will result in a 1/3 increase in size.
+        * Internet Explorer data URI length limit. See encodeImageAsDataURI().
         */
-       const EMBED_SIZE_LIMIT = 24576;
        const DATA_URI_SIZE_LIMIT = 32768;
        const URL_REGEX = 'url\(\s*[\'"]?(?P<file>[^\?\)\'"]*?)(?P<query>\?[^\)\'"]*?|)[\'"]?\s*\)';
        const EMBED_REGEX = '\/\*\s*\@embed\s*\*\/';
@@ -110,17 +109,17 @@ class CSSMin {
         * @param string $file Image file to encode.
         * @param string|null $type File's MIME type or null. If null, CSSMin will
         *     try to autodetect the type.
-        * @param int|bool $sizeLimit If the size of the target file is greater than
-        *     this value, decline to encode the image file and return false
-        *     instead. If $sizeLimit is false, no limit is enforced.
+        * @param bool $ie8Compat By default, a data URI will only be produced if it can be made short
+        *     enough to fit in Internet Explorer 8 (and earlier) URI length limit (32,768 bytes). Pass
+        *     `false` to remove this limitation.
         * @return string|bool Image contents encoded as a data URI or false.
         */
-       public static function encodeImageAsDataURI( $file, $type = null,
-               $sizeLimit = self::EMBED_SIZE_LIMIT
-       ) {
-               if ( $sizeLimit !== false && filesize( $file ) >= $sizeLimit ) {
+       public static function encodeImageAsDataURI( $file, $type = null, $ie8Compat = true ) {
+               // Fast-fail for files that definitely exceed the maximum data URI length
+               if ( $ie8Compat && filesize( $file ) >= self::DATA_URI_SIZE_LIMIT ) {
                        return false;
                }
+
                if ( $type === null ) {
                        $type = self::getMimeType( $file );
                }
@@ -128,22 +127,41 @@ class CSSMin {
                        return false;
                }
 
-               $contents = file_get_contents( $file );
-               // Only whitespace and printable ASCII characters
-               $isText = (bool)preg_match( '/^[\r\n\t\x20-\x7e]+$/', $contents );
+               return self::encodeStringAsDataURI( file_get_contents( $file ), $type, $ie8Compat );
+       }
 
-               if ( $isText ) {
-                       // Do not base64-encode non-binary files (sane SVGs), unless that'd exceed URI length limit.
+       /**
+        * Encode file contents as a data URI with chosen MIME type.
+        *
+        * The URI will be base64-encoded for binary files or just percent-encoded otherwise.
+        *
+        * @since 1.25
+        *
+        * @param string $contents File contents to encode.
+        * @param string $type File's MIME type.
+        * @param bool $ie8Compat See encodeImageAsDataURI().
+        * @return string|bool Image contents encoded as a data URI or false.
+        */
+       public static function encodeStringAsDataURI( $contents, $type, $ie8Compat = true ) {
+               // Try #1: Non-encoded data URI
+               // The regular expression matches ASCII whitespace and printable characters.
+               if ( preg_match( '/^[\r\n\t\x20-\x7e]+$/', $contents ) ) {
+                       // Do not base64-encode non-binary files (sane SVGs).
                        // (This often produces longer URLs, but they compress better, yielding a net smaller size.)
                        $uri = 'data:' . $type . ',' . rawurlencode( $contents );
-                       if ( strlen( $uri ) >= self::DATA_URI_SIZE_LIMIT ) {
-                               $uri = 'data:' . $type . ';base64,' . base64_encode( $contents );
+                       if ( !$ie8Compat || strlen( $uri ) < self::DATA_URI_SIZE_LIMIT ) {
+                               return $uri;
                        }
-               } else {
-                       $uri = 'data:' . $type . ';base64,' . base64_encode( $contents );
                }
 
-               return $uri;
+               // Try #2: Encoded data URI
+               $uri = 'data:' . $type . ';base64,' . base64_encode( $contents );
+               if ( !$ie8Compat || strlen( $uri ) < self::DATA_URI_SIZE_LIMIT ) {
+                       return $uri;
+               }
+
+               // A data URI couldn't be produced
+               return false;
        }
 
        /**
@@ -217,19 +235,22 @@ class CSSMin {
                        $remote = substr( $remote, 0, -1 );
                }
 
+               // Disallow U+007F DELETE, which is illegal anyway, and which
+               // we use for comment placeholders.
+               $source = str_replace( "\x7f", "?", $source );
+
                // Replace all comments by a placeholder so they will not interfere with the remapping.
                // Warning: This will also catch on anything looking like the start of a comment between
                // quotation marks (e.g. "foo /* bar").
                $comments = array();
-               $placeholder = uniqid( '', true );
 
                $pattern = '/(?!' . CSSMin::EMBED_REGEX . ')(' . CSSMin::COMMENT_REGEX . ')/s';
 
                $source = preg_replace_callback(
                        $pattern,
-                       function ( $match ) use ( &$comments, $placeholder ) {
+                       function ( $match ) use ( &$comments ) {
                                $comments[] = $match[ 0 ];
-                               return $placeholder . ( count( $comments ) - 1 ) . 'x';
+                               return CSSMin::PLACEHOLDER . ( count( $comments ) - 1 ) . 'x';
                        },
                        $source
                );
@@ -242,13 +263,13 @@ class CSSMin {
 
                $source = preg_replace_callback(
                        $pattern,
-                       function ( $matchOuter ) use ( $local, $remote, $embedData, $placeholder ) {
+                       function ( $matchOuter ) use ( $local, $remote, $embedData ) {
                                $rule = $matchOuter[0];
 
                                // Check for global @embed comment and remove it. Allow other comments to be present
                                // before @embed (they have been replaced with placeholders at this point).
                                $embedAll = false;
-                               $rule = preg_replace( '/^((?:\s+|' . $placeholder . '(\d+)x)*)' . CSSMin::EMBED_REGEX . '\s*/', '$1', $rule, 1, $embedAll );
+                               $rule = preg_replace( '/^((?:\s+|' . CSSMin::PLACEHOLDER . '(\d+)x)*)' . CSSMin::EMBED_REGEX . '\s*/', '$1', $rule, 1, $embedAll );
 
                                // Build two versions of current rule: with remapped URLs
                                // and with embedded data: URIs (where possible).
@@ -313,7 +334,7 @@ class CSSMin {
                        }, $source );
 
                // Re-insert comments
-               $pattern = '/' . $placeholder . '(\d+)x/';
+               $pattern = '/' . CSSMin::PLACEHOLDER . '(\d+)x/';
                $source = preg_replace_callback( $pattern, function( $match ) use ( &$comments ) {
                        return $comments[ $match[1] ];
                }, $source );
@@ -344,7 +365,7 @@ class CSSMin {
         * @return bool
         */
        public static function isLocalUrl( $maybeUrl ) {
-               if ( !self::isRemoteUrl( $maybeUrl ) && $maybeUrl !== '' && $maybeUrl[0] === '/' ) {
+               if ( $maybeUrl !== '' && $maybeUrl[0] === '/' && !self::isRemoteUrl( $maybeUrl ) ) {
                        return true;
                }
                return false;