WordPress.org

Make WordPress Core

Ticket #32075: 32075-improved-patch-v7.patch

File 32075-improved-patch-v7.patch, 16.8 KB (added by jrf, 5 years ago)

Rebased against master, added check for changability of the memory limit, added unit tests

  • src/wp-admin/admin.php

    From f2628465439c7882cec5f1d2e8090929fa00320c Mon Sep 17 00:00:00 2001
    Date: Sun, 10 Apr 2016 16:07:39 +0200
    Subject: [PATCH] Prevent WP setting the memory limit to a value lower than it
     currently is.
    
    Also fixes a bug in how the memory limits were tested in the first place.
    
    Includes unit tests for the newly added functions in load.php.
    ---
     src/wp-admin/admin.php                            | 16 +---
     src/wp-admin/includes/file.php                    |  3 +-
     src/wp-admin/includes/image-edit.php              |  3 +-
     src/wp-includes/class-wp-image-editor-gd.php      | 10 +--
     src/wp-includes/class-wp-image-editor-imagick.php |  3 +-
     src/wp-includes/default-constants.php             | 43 +++++-----
     src/wp-includes/deprecated.php                    |  3 +-
     src/wp-includes/functions.php                     | 95 +++++++++++++++++++++++
     src/wp-includes/load.php                          | 58 ++++++++++++++
     tests/phpunit/tests/functions.php                 | 16 ++++
     tests/phpunit/tests/load.php                      | 72 +++++++++++++++++
     11 files changed, 271 insertions(+), 51 deletions(-)
     create mode 100644 tests/phpunit/tests/load.php
    
    diff --git a/src/wp-admin/admin.php b/src/wp-admin/admin.php
    index a54ca21..6c01415 100644
    a b else 
    138138        require(ABSPATH . 'wp-admin/menu.php');
    139139
    140140if ( current_user_can( 'manage_options' ) ) {
    141         /**
    142          * Filter the maximum memory limit available for administration screens.
    143          *
    144          * This only applies to administrators, who may require more memory for tasks like updates.
    145          * Memory limits when processing images (uploaded or edited by users of any role) are
    146          * handled separately.
    147          *
    148          * The WP_MAX_MEMORY_LIMIT constant specifically defines the maximum memory limit available
    149          * when in the administration back end. The default is 256M, or 256 megabytes of memory.
    150          *
    151          * @since 3.0.0
    152          *
    153          * @param string 'WP_MAX_MEMORY_LIMIT' The maximum WordPress memory limit. Default 256M.
    154          */
    155         @ini_set( 'memory_limit', apply_filters( 'admin_memory_limit', WP_MAX_MEMORY_LIMIT ) );
     141        wp_raise_memory_limit( 'admin' );
    156142}
    157143
    158144/**
  • src/wp-admin/includes/file.php

    diff --git a/src/wp-admin/includes/file.php b/src/wp-admin/includes/file.php
    index 72f5e22..4b718a7 100644
    a b function unzip_file($file, $to) { 
    558558                return new WP_Error('fs_unavailable', __('Could not access filesystem.'));
    559559
    560560        // Unzip can use a lot of memory, but not this much hopefully
    561         /** This filter is documented in wp-admin/admin.php */
    562         @ini_set( 'memory_limit', apply_filters( 'admin_memory_limit', WP_MAX_MEMORY_LIMIT ) );
     561        wp_raise_memory_limit( 'admin' );
    563562
    564563        $needed_dirs = array();
    565564        $to = trailingslashit($to);
  • src/wp-admin/includes/image-edit.php

    diff --git a/src/wp-admin/includes/image-edit.php b/src/wp-admin/includes/image-edit.php
    index 8947f53..a954d24 100644
    a b function image_edit_apply_changes( $image, $changes ) { 
    586586function stream_preview_image( $post_id ) {
    587587        $post = get_post( $post_id );
    588588
    589         /** This filter is documented in wp-admin/admin.php */
    590         @ini_set( 'memory_limit', apply_filters( 'admin_memory_limit', WP_MAX_MEMORY_LIMIT ) );
     589        wp_raise_memory_limit( 'admin' );
    591590
    592591        $img = wp_get_image_editor( _load_image_to_edit_path( $post_id ) );
    593592
  • src/wp-includes/class-wp-image-editor-gd.php

    diff --git a/src/wp-includes/class-wp-image-editor-gd.php b/src/wp-includes/class-wp-image-editor-gd.php
    index 2093c6b..9cb756d 100644
    a b class WP_Image_Editor_GD extends WP_Image_Editor { 
    9696                if ( ! is_file( $this->file ) && ! preg_match( '|^https?://|', $this->file ) )
    9797                        return new WP_Error( 'error_loading_image', __('File doesn’t exist?'), $this->file );
    9898
    99                 /**
    100                  * Filter the memory limit allocated for image manipulation.
    101                  *
    102                  * @since 3.5.0
    103                  *
    104                  * @param int|string $limit Maximum memory limit to allocate for images. Default WP_MAX_MEMORY_LIMIT.
    105                  *                          Accepts an integer (bytes), or a shorthand string notation, such as '256M'.
    106                  */
    10799                // Set artificially high because GD uses uncompressed images in memory
    108                 @ini_set( 'memory_limit', apply_filters( 'image_memory_limit', WP_MAX_MEMORY_LIMIT ) );
     100                wp_raise_memory_limit( 'image' );
    109101
    110102                $this->image = @imagecreatefromstring( file_get_contents( $this->file ) );
    111103
  • src/wp-includes/class-wp-image-editor-imagick.php

    diff --git a/src/wp-includes/class-wp-image-editor-imagick.php b/src/wp-includes/class-wp-image-editor-imagick.php
    index 7632cbb..5372eaa 100644
    a b class WP_Image_Editor_Imagick extends WP_Image_Editor { 
    137137                if ( ! is_file( $this->file ) && ! preg_match( '|^https?://|', $this->file ) )
    138138                        return new WP_Error( 'error_loading_image', __('File doesn’t exist?'), $this->file );
    139139
    140                 /** This filter is documented in wp-includes/class-wp-image-editor-imagick.php */
    141140                // Even though Imagick uses less PHP memory than GD, set higher limit for users that have low PHP.ini limits
    142                 @ini_set( 'memory_limit', apply_filters( 'image_memory_limit', WP_MAX_MEMORY_LIMIT ) );
     141                wp_raise_memory_limit( 'image' );
    143142
    144143                try {
    145144                        $this->image = new Imagick( $this->file );
  • src/wp-includes/default-constants.php

    diff --git a/src/wp-includes/default-constants.php b/src/wp-includes/default-constants.php
    index c9092bd..cb790fb 100644
    a b  
    1717function wp_initial_constants() {
    1818        global $blog_id;
    1919
    20         // set memory limits
    21         if ( !defined('WP_MEMORY_LIMIT') ) {
    22                 if ( is_multisite() ) {
    23                         define('WP_MEMORY_LIMIT', '64M');
     20        $current_limit     = @ini_get( 'memory_limit' );
     21        $current_limit_int = wp_php_ini_bytes_to_int( $current_limit );
     22
     23        // Define memory limits.
     24        if ( ! defined( 'WP_MEMORY_LIMIT' ) ) {
     25                if ( false === wp_is_ini_value_changable( 'memory_limit' ) ) {
     26                        define( 'WP_MEMORY_LIMIT', $current_limit );
     27                } elseif ( is_multisite() ) {
     28                        define( 'WP_MEMORY_LIMIT', '64M' );
    2429                } else {
    25                         define('WP_MEMORY_LIMIT', '40M');
     30                        define( 'WP_MEMORY_LIMIT', '40M' );
    2631                }
    2732        }
    2833
    2934        if ( ! defined( 'WP_MAX_MEMORY_LIMIT' ) ) {
    30                 define( 'WP_MAX_MEMORY_LIMIT', '256M' );
     35                if ( false === wp_is_ini_value_changable( 'memory_limit' ) ) {
     36                        define( 'WP_MAX_MEMORY_LIMIT', $current_limit );
     37                } elseif ( -1 === $current_limit_int || $current_limit_int > 268435456 ) {
     38                        define( 'WP_MAX_MEMORY_LIMIT', $current_limit );
     39                } else {
     40                        define( 'WP_MAX_MEMORY_LIMIT', '256M' );
     41                }
     42        }
     43
     44        // Set memory limits.
     45        $wp_limit_int = wp_php_ini_bytes_to_int( WP_MEMORY_LIMIT );
     46        if ( -1 !== $current_limit_int && ( -1 === $wp_limit_int || $wp_limit_int > $current_limit_int ) ) {
     47                @ini_set( 'memory_limit', WP_MEMORY_LIMIT );
    3148        }
    3249
    3350        if ( ! isset($blog_id) )
    3451                $blog_id = 1;
    3552
    36         // set memory limits.
    37         if ( function_exists( 'memory_get_usage' ) ) {
    38                 $current_limit = @ini_get( 'memory_limit' );
    39                 $current_limit_int = intval( $current_limit );
    40                 if ( false !== strpos( $current_limit, 'G' ) )
    41                         $current_limit_int *= 1024;
    42                 $wp_limit_int = intval( WP_MEMORY_LIMIT );
    43                 if ( false !== strpos( WP_MEMORY_LIMIT, 'G' ) )
    44                         $wp_limit_int *= 1024;
    45 
    46                 if ( -1 != $current_limit && ( -1 == WP_MEMORY_LIMIT || $current_limit_int < $wp_limit_int ) )
    47                         @ini_set( 'memory_limit', WP_MEMORY_LIMIT );
    48         }
    49 
    5053        if ( !defined('WP_CONTENT_DIR') )
    5154                define( 'WP_CONTENT_DIR', ABSPATH . 'wp-content' ); // no trailing slash, full paths only - WP_CONTENT_URL is defined further down
    5255
  • src/wp-includes/deprecated.php

    diff --git a/src/wp-includes/deprecated.php b/src/wp-includes/deprecated.php
    index f79761b..0247d4a 100644
    a b function wp_load_image( $file ) { 
    31763176                return __('The GD image library is not installed.');
    31773177
    31783178        // Set artificially high because GD uses uncompressed images in memory
    3179         @ini_set( 'memory_limit', apply_filters( 'image_memory_limit', WP_MAX_MEMORY_LIMIT ) );
     3179        wp_raise_memory_limit( 'image' );
     3180
    31803181        $image = imagecreatefromstring( file_get_contents( $file ) );
    31813182
    31823183        if ( !is_resource( $image ) )
  • src/wp-includes/functions.php

    diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php
    index 0e720ee..f3bef58 100644
    a b function mysql_to_rfc3339( $date_string ) { 
    52805280        // Strip timezone information
    52815281        return preg_replace( '/(?:Z|[+-]\d{2}(?::\d{2})?)$/', '', $formatted );
    52825282}
     5283
     5284/**
     5285 * Attempts to raise the PHP memory limit for memory intensive processes.
     5286 *
     5287 * Allows only to raise the existing limit and prevents lowering it.
     5288 *
     5289 * @since 4.6.0
     5290 *
     5291 * @param string $context Context in which the function is called.
     5292 *                        Either 'admin' or 'image'. Defaults to 'admin'.
     5293 */
     5294function wp_raise_memory_limit( $context = 'admin' ) {
     5295        // Exit early if the limit cannot be changed.
     5296        if ( false === wp_is_ini_value_changable( 'memory_limit' ) ) {
     5297                return;
     5298        }
     5299
     5300        $current_limit     = @ini_get( 'memory_limit' );
     5301        $current_limit_int = wp_php_ini_bytes_to_int( $current_limit );
     5302
     5303        if ( -1 === $current_limit_int ) {
     5304                return;
     5305        }
     5306
     5307        $wp_max_limit     = WP_MAX_MEMORY_LIMIT;
     5308        $wp_max_limit_int = wp_php_ini_bytes_to_int( $wp_max_limit );
     5309        $filtered_limit   = $wp_max_limit;
     5310
     5311        switch ( $context ) {
     5312                case 'admin':
     5313                        /**
     5314                         * Filter the memory limit available for administration screens.
     5315                         *
     5316                         * This only applies to administrators, who may require more memory for tasks like updates.
     5317                         * Memory limits when processing images (uploaded or edited by users of any role) are
     5318                         * handled separately.
     5319                         *
     5320                         * The WP_MAX_MEMORY_LIMIT constant specifically defines the maximum memory limit available
     5321                         * when in the administration back end. The default is 256M (256 megabytes
     5322                         * of memory) or the original `memory_limit` php.ini value if this is higher.
     5323                         *
     5324                         * @since 3.0.0
     5325                         * @since 4.6.0 The default takes the original `memory_limit` into account.
     5326                         *
     5327                         * @param int|string $filtered_limit The maximum WordPress memory limit.
     5328                         *                                   Accepts an integer (bytes), or a shorthand string
     5329                         *                                   notation, such as '256M'.
     5330                         */
     5331                        $filtered_limit = apply_filters( 'admin_memory_limit', $filtered_limit );
     5332                        break;
     5333
     5334                case 'image':
     5335                        /**
     5336                         * Filter the memory limit allocated for image manipulation.
     5337                         *
     5338                         * @since 3.5.0
     5339                         * @since 4.6.0 The default takes the original `memory_limit` into account.
     5340                         *
     5341                         * @param int|string $filtered_limit Maximum memory limit to allocate for images.
     5342                         *                                   Default 256M or the original php.ini memory_limit,
     5343                         *                                   whichever is higher.
     5344                         *                                   Accepts an integer (bytes), or a shorthand string
     5345                         *                                   notation, such as '256M'.
     5346                         */
     5347                        $filtered_limit = apply_filters( 'image_memory_limit', $filtered_limit );
     5348                        break;
     5349
     5350                default:
     5351                        /**
     5352                         * Filter the memory limit allocated for arbitrary contexts.
     5353                         *
     5354                         * The dynamic portion of the hook name, `$context`, refers to an arbitrary
     5355                         * context passed on calling the function. This allows for plugins to define
     5356                         * their own contexts for raising the memory limit.
     5357                         *
     5358                         * @since 4.6.0
     5359                         *
     5360                         * @param int|string $filtered_limit Maximum memory limit to allocate for images.
     5361                         *                                   Default 256M or the original php.ini memory_limit,
     5362                         *                                   whichever is higher.
     5363                         *                                   Accepts an integer (bytes), or a shorthand string
     5364                         *                                   notation, such as '256M'.
     5365                         */
     5366                        $filtered_limit = apply_filters( "{$context}_memory_limit", $filtered_limit );
     5367                        break;
     5368        }
     5369
     5370        $filtered_limit_int = wp_php_ini_bytes_to_int( $filtered_limit );
     5371
     5372        if ( -1 === $filtered_limit_int || ( $filtered_limit_int > $wp_max_limit_int && $filtered_limit_int > $current_limit_int ) ) {
     5373                @ini_set( 'memory_limit', $filtered_limit );
     5374        } elseif ( -1 === $wp_max_limit_int || $wp_max_limit_int > $current_limit_int ) {
     5375                @ini_set( 'memory_limit', $wp_max_limit );
     5376        }
     5377}
  • src/wp-includes/load.php

    diff --git a/src/wp-includes/load.php b/src/wp-includes/load.php
    index 2f2d95b..93add7b 100644
    a b function wp_installing( $is_installing = null ) { 
    895895
    896896        return (bool) $installing;
    897897}
     898
     899/**
     900 * Converts a PHP ini shorthand byte value to an integer byte value.
     901 *
     902 * @since 4.6.0
     903 *
     904 * @see http://php.net/manual/en/function.ini-get.php
     905 * @see http://php.net/manual/en/faq.using.php#faq.using.shorthandbytes
     906 *
     907 * @param string $value An PHP ini byte value, either shorthand or ordinary.
     908 * @return int Value in bytes.
     909 */
     910function wp_php_ini_bytes_to_int( $value ) {
     911        $value = trim( $value );
     912        $last  = strtolower( $value[ strlen( $value ) - 1 ] );
     913
     914        switch( $last ) {
     915                // Note: the `break` statement is left out on purpose!
     916                case 'g':
     917                        $value *= 1024;
     918                case 'm':
     919                        $value *= 1024;
     920                case 'k':
     921                        $value *= 1024;
     922                default:
     923                        // Left empty on purpose.
     924                        break;
     925        }
     926
     927    // Deal with large (float) values which run into the maximum integer size.
     928        if ( PHP_INT_MAX < $value ) {
     929                $value = PHP_INT_MAX;
     930        }
     931        return (int) $value;
     932}
     933
     934/**
     935 * Determines whether a PHP ini value is changable at runtime.
     936 *
     937 * @since 4.6.0
     938 *
     939 * @see http://php.net/manual/en/function.ini-get-all.php
     940 *
     941 * @param string $setting The name of the ini setting to check.
     942 * @return bool True if the value is changable at runtime. False otherwise.
     943 */
     944function wp_is_ini_value_changable( $setting ) {
     945        static $ini_all;
     946
     947        if ( ! isset( $ini_all ) ) {
     948                $ini_all = ini_get_all();
     949        }
     950
     951        if ( isset( $ini_all[ $setting ]['access'] ) && ( INI_ALL === $ini_all[ $setting ]['access'] || INI_USER === $ini_all[ $setting ]['access'] ) ) {
     952                return true;
     953        }
     954        return false;
     955}
  • tests/phpunit/tests/functions.php

    diff --git a/tests/phpunit/tests/functions.php b/tests/phpunit/tests/functions.php
    index 6d6e177..023e81e 100644
    a b class Tests_Functions extends WP_UnitTestCase { 
    813813                        array( '2016-03-02T19:13:00', '16-03-02 19:13' )
    814814                );
    815815        }
     816
     817        /**
     818         * Test raising the memory limit.
     819         *
     820         * {@internal Unfortunately as the default for 'WP_MAX_MEMORY_LIMIT' in the
     821         * test suite is -1, we can not test the memory limit negotiations.}}
     822         *
     823         * @ticket 32075
     824         */
     825        function test_wp_raise_memory_limit() {
     826                $original = ini_get( 'memory_limit' );
     827
     828                ini_set( 'memory_limit', '40M' );
     829                wp_raise_memory_limit();
     830                $this->assertEquals( '-1', ini_get( 'memory_limit' ) );
     831        }
    816832}
  • new file tests/phpunit/tests/load.php

    diff --git a/tests/phpunit/tests/load.php b/tests/phpunit/tests/load.php
    new file mode 100644
    index 0000000..84133ff
    - +  
     1<?php
     2
     3/**
     4 * @group load.php
     5 */
     6class Tests_Load extends WP_UnitTestCase {
     7
     8        /**
     9         * Test converting PHP ini byte values to integer byte values.
     10         *
     11         * @dataProvider data_wp_php_ini_bytes_to_int
     12         */
     13        function test_wp_php_ini_bytes_to_int( $value, $expected ) {
     14                $this->assertSame( $expected, wp_php_ini_bytes_to_int( $value ) );
     15        }
     16
     17        function data_wp_php_ini_bytes_to_int() {
     18                $array = array(
     19                        // Integer input
     20                        array( -1, -1 ), // = no memory limit
     21                        array( 8388608, 8388608 ), // 8M
     22
     23                        // String input (memory limit shorthand values)
     24                        array( '32k', 32768 ),
     25                        array( '64K', 65536 ),
     26                        array( '128m', 134217728 ),
     27                        array( '256M', 268435456 ),
     28                        array( '1g', 1073741824 ),
     29                        array( '1024', 1024 ), // No letter will be interpreted as integer value.
     30
     31                        // Edge cases
     32                        array( 'g', 0 ),
     33                        array( 'null', 0 ),
     34                        array( 'off', 0 ),
     35                );
     36
     37                // Test while running into maximum integer size limit on 32bit systems.
     38                if ( 2147483647 === PHP_INT_MAX ) {
     39                        $array[] = array( '2G', 2147483647 );
     40                        $array[] = array( '4G', 2147483647 );
     41                } else {
     42                        $array[] = array( '2G', 2147483648 );
     43                        $array[] = array( '4G', 4294967296 );
     44                }
     45
     46                return $array;
     47        }
     48
     49        /**
     50         * Test the determining of the changability of a PHP ini value.
     51         *
     52         * @dataProvider data_wp_is_ini_value_changable
     53         */
     54        function test_wp_is_ini_value_changable( $setting, $expected ) {
     55                $this->assertSame( $expected, wp_is_ini_value_changable( $setting ) );
     56        }
     57
     58        function data_wp_is_ini_value_changable() {
     59                $array = array(
     60                        array( 'memory_limit', true ), // PHP_INI_ALL
     61                        array( 'log_errors', true ), // PHP_INI_ALL
     62                        array( 'upload_max_filesize', false ), // PHP_INI_PERDIR
     63                        array( 'upload_tmp_dir', false ), // PHP_INI_SYSTEM
     64                );
     65
     66                if ( extension_loaded( 'Tidy' ) && version_compare( PHP_VERSION, '7.0.0', '>' ) ) {
     67                        $array[] = array( 'tidy.clean_output', true ); // PHP_INI_USER
     68                }
     69
     70                return $array;
     71        }
     72}