Browse Source

Merge branch 'master' into 4.x

ADmad 8 years ago
parent
commit
29befe7bf2

+ 3 - 3
README.md

@@ -4,7 +4,7 @@
   </a>
 </p>
 <p align="center">
-    <a href="LICENSE.txt" target="_blank">
+    <a href="LICENSE" target="_blank">
         <img alt="Software License" src="https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square">
     </a>
     <a href="https://travis-ci.org/cakephp/cakephp" target="_blank">
@@ -38,7 +38,7 @@ recommend using the [app skeleton](https://github.com/cakephp/app) as
 a starting point. For existing applications you can run the following:
 
 ``` bash
-$ composer require cakephp/cakephp:"~3.5"
+$ composer require cakephp/cakephp:"~3.6"
 ```
 
 ## Running Tests
@@ -69,7 +69,7 @@ tests for CakePHP by doing the following:
 
 * [Slack](https://cakesf.herokuapp.com/) - Join us on Slack.
 * [#cakephp](https://webchat.freenode.net/?channels=#cakephp) on irc.freenode.net - Come chat with us, we have cake.
-* [Forum](http://discourse.cakephp.org/) - Official CakePHP forum.
+* [Forum](https://discourse.cakephp.org/) - Official CakePHP forum.
 * [GitHub Issues](https://github.com/cakephp/cakephp/issues) - Got issues? Please tell us!
 * [Roadmaps](https://github.com/cakephp/cakephp/wiki#roadmaps) - Want to contribute? Get involved!
 

+ 2 - 2
src/Controller/Component/AuthComponent.php

@@ -439,8 +439,8 @@ class AuthComponent extends Component
      */
     protected function _isLoginAction(Controller $controller)
     {
-        $url = $controller->request->getRequestTarget();
-        $url = Router::normalize($url);
+        $uri = $controller->request->getUri();
+        $url = Router::normalize($uri->getPath());
         $loginAction = Router::normalize($this->_config['loginAction']);
 
         return $loginAction === $url;

+ 12 - 4
src/Controller/Component/SecurityComponent.php

@@ -22,6 +22,7 @@ use Cake\Core\Configure;
 use Cake\Event\Event;
 use Cake\Http\Exception\BadRequestException;
 use Cake\Http\ServerRequest;
+use Cake\Routing\Router;
 use Cake\Utility\Hash;
 use Cake\Utility\Security;
 
@@ -378,14 +379,21 @@ class SecurityComponent extends Component
      */
     protected function _hashParts(Controller $controller)
     {
-        $fieldList = $this->_fieldsList($controller->request->getData());
-        $unlocked = $this->_sortedUnlocked($controller->request->getData());
+        $request = $controller->getRequest();
+
+        // Start the session to ensure we get the correct session id.
+        $session = $request->getSession();
+        $session->start();
+
+        $data = $request->getData();
+        $fieldList = $this->_fieldsList($data);
+        $unlocked = $this->_sortedUnlocked($data);
 
         return [
-            $controller->request->getRequestTarget(),
+            Router::url($request->getRequestTarget()),
             serialize($fieldList),
             $unlocked,
-            session_id(),
+            $session->id()
         ];
     }
 

+ 1 - 1
src/Controller/Controller.php

@@ -346,7 +346,7 @@ class Controller implements EventListenerInterface, EventDispatcherInterface
         ];
         if (isset($deprecated[$name])) {
             $method = $deprecated[$name];
-            deprecationWarning(sprintf('Controller::$%s is deprecated. Use $this->%s instead.', $name, $method));
+            deprecationWarning(sprintf('Controller::$%s is deprecated. Use $this->%s() instead.', $name, $method));
 
             return $this->{$method}();
         }

+ 2 - 2
src/Core/BasePlugin.php

@@ -224,7 +224,7 @@ class BasePlugin implements PluginInterface
      */
     public function routes($routes)
     {
-        $path = $this->getConfigPath() . DS . 'routes.php';
+        $path = $this->getConfigPath() . 'routes.php';
         if (file_exists($path)) {
             require $path;
         }
@@ -235,7 +235,7 @@ class BasePlugin implements PluginInterface
      */
     public function bootstrap(PluginApplicationInterface $app)
     {
-        $bootstrap = $this->getConfigPath() . DS . 'bootstrap.php';
+        $bootstrap = $this->getConfigPath() . 'bootstrap.php';
         if (file_exists($bootstrap)) {
             require $bootstrap;
         }

+ 1 - 1
src/Core/composer.json

@@ -23,7 +23,7 @@
     },
     "require": {
         "php": ">=5.6.0",
-        "cakephp/utility": "^3.0.0"
+        "cakephp/utility": "^3.6.0"
     },
     "suggest": {
         "cakephp/event": "To use PluginApplicationInterface or plugin applications."

+ 3 - 1
src/Core/functions.php

@@ -295,7 +295,9 @@ if (!function_exists('deprecationWarning')) {
             $frame += ['file' => '[internal]', 'line' => '??'];
 
             $message = sprintf(
-                '%s - %s, line: %s',
+                '%s - %s, line: %s' . "\n" .
+                ' You can disable deprecation warnings by setting `Error.errorLevel` to' .
+                ' `E_ALL & ~E_USER_DEPRECATED` in your config/app.php.',
                 $message,
                 $frame['file'],
                 $frame['line']

+ 3 - 3
src/Database/composer.json

@@ -25,9 +25,9 @@
     },
     "require": {
         "php": ">=5.6.0",
-        "cakephp/cache": "^3.0.0",
-        "cakephp/core": "^3.0.0",
-        "cakephp/datasource": "^3.0.0"
+        "cakephp/cache": "^3.6.0",
+        "cakephp/core": "^3.6.0",
+        "cakephp/datasource": "^3.6.0"
     },
     "suggest": {
         "cakephp/log": "Require this if you want to use the built-in query logger"

+ 2 - 3
src/Datasource/QueryTrait.php

@@ -17,7 +17,6 @@ namespace Cake\Datasource;
 use BadMethodCallException;
 use Cake\Collection\Iterator\MapReduce;
 use Cake\Datasource\Exception\RecordNotFoundException;
-use Cake\Datasource\ResultSetDecorator;
 
 /**
  * Contains the characteristics for an object that is attached to a repository and
@@ -380,8 +379,8 @@ trait QueryTrait
      * Registers a new formatter callback function that is to be executed when trying
      * to fetch the results from the database.
      *
-     * Formatting callbacks will get a first parameter, a `ResultSetDecorator`, that
-     * can be traversed and modified at will.
+     * Formatting callbacks will get a first parameter, an object implementing
+     * `\Cake\Collection\CollectionInterface`, that can be traversed and modified at will.
      *
      * Callbacks are required to return an iterator object, which will be used as
      * the return value for this query's result. Formatter functions are applied

+ 1 - 1
src/Datasource/composer.json

@@ -25,7 +25,7 @@
     },
     "require": {
         "php": ">=5.6.0",
-        "cakephp/core": "^3.0.0"
+        "cakephp/core": "^3.6.0"
     },
     "suggest": {
         "cakephp/utility": "If you decide to use EntityTrait.",

+ 0 - 2
src/Error/ErrorHandler.php

@@ -106,8 +106,6 @@ class ErrorHandler extends BaseErrorHandler
      *
      * Template method of BaseErrorHandler.
      *
-     * Only when debug > 2 will a formatted error be displayed.
-     *
      * @param array $error An array of error data.
      * @param bool $debug Whether or not the app is in debug mode.
      * @return void

+ 2 - 2
src/Form/composer.json

@@ -22,8 +22,8 @@
     },
     "require": {
         "php": ">=5.6.0",
-        "cakephp/event": "^3.5.0",
-        "cakephp/validation": "^3.5.0"
+        "cakephp/event": "^3.6.0",
+        "cakephp/validation": "^3.6.0"
     },
     "autoload": {
         "psr-4": {

+ 18 - 1
src/Http/ServerRequest.php

@@ -230,7 +230,7 @@ class ServerRequest implements ArrayAccess, ServerRequestInterface
         'query' => ['get' => 'getQuery()', 'set' => 'withQueryParams()'],
         'params' => ['get' => 'getParam()', 'set' => 'withParam()'],
         'cookies' => ['get' => 'getCookie()', 'set' => 'withCookieParams()'],
-        'url' => ['get' => 'getRequestTarget()', 'set' => 'withRequestTarget()'],
+        'url' => ['get' => 'getPath()', 'set' => 'withRequestTarget()'],
         'base' => ['get' => 'getAttribute("base")', 'set' => 'withAttribute("base")'],
         'webroot' => ['get' => 'getAttribute("webroot")', 'set' => 'withAttribute("webroot")'],
         'here' => ['get' => 'getRequestTarget()', 'set' => 'withRequestTarget()'],
@@ -2345,6 +2345,23 @@ class ServerRequest implements ArrayAccess, ServerRequestInterface
     }
 
     /**
+     * Get the path of current request.
+     *
+     * @return string
+     * @since 3.6.1
+     */
+    public function getPath()
+    {
+        if ($this->requestTarget === null) {
+            return $this->uri->getPath();
+        }
+
+        list($path) = explode('?', $this->requestTarget);
+
+        return $path;
+    }
+
+    /**
      * Array access read implementation
      *
      * @param string $name Name of the key being accessed.

+ 15 - 4
src/Http/Session.php

@@ -246,13 +246,12 @@ class Session
      */
     public function engine($class = null, array $options = [])
     {
-        if ($class instanceof SessionHandlerInterface) {
-            return $this->_engine = $class;
-        }
-
         if ($class === null) {
             return $this->_engine;
         }
+        if ($class instanceof SessionHandlerInterface) {
+            return $this->setEngine($class);
+        }
         $className = App::className($class, 'Http/Session');
 
         if (!$className) {
@@ -271,6 +270,18 @@ class Session
                 'The chosen SessionHandler does not implement SessionHandlerInterface, it cannot be used as an engine.'
             );
         }
+
+        return $this->setEngine($handler);
+    }
+
+    /**
+     * Set the engine property and update the session handler in PHP.
+     *
+     * @param \SessionHandlerInterface $handler The handler to set
+     * @return \SessionHandlerInterface
+     */
+    protected function setEngine(SessionHandlerInterface $handler)
+    {
         if (!headers_sent()) {
             session_set_save_handler($handler, false);
         }

+ 3 - 3
src/ORM/composer.json

@@ -24,10 +24,10 @@
     },
     "require": {
         "php": ">=5.6.0",
-        "cakephp/collection": "^3.0.0",
+        "cakephp/collection": "^3.6.0",
         "cakephp/core": "^3.6.0",
-        "cakephp/datasource": "^3.1.2",
-        "cakephp/database": "^3.1.4",
+        "cakephp/datasource": "^3.6.0",
+        "cakephp/database": "^3.6.0",
         "cakephp/event": "^3.6.0",
         "cakephp/utility": "^3.6.0",
         "cakephp/validation": "^3.6.0"

+ 1 - 1
src/Routing/Router.php

@@ -633,7 +633,7 @@ class Router
         }
 
         if (empty($url)) {
-            $output = isset($here) ? $here : $base . '/';
+            $output = $base . (isset($here) ? $here : '/');
             if ($full) {
                 $output = static::fullBaseUrl() . $output;
             }

+ 4 - 0
src/View/Helper/UrlHelper.php

@@ -161,6 +161,10 @@ class UrlHelper extends Helper
         if (is_array($path)) {
             return $this->build($path, !empty($options['fullBase']));
         }
+        // data URIs only require HTML escaping
+        if (preg_match('/^data:[a-z]+\/[a-z]+;/', $path)) {
+            return h($path);
+        }
         if (strpos($path, '://') !== false || preg_match('/^[a-z]+:/i', $path)) {
             return ltrim($this->build($path), '/');
         }

+ 1 - 0
src/View/View.php

@@ -56,6 +56,7 @@ use RuntimeException;
  * `plugins/SuperHot/Template/Posts/index.ctp`. If a theme template
  * is not found for the current action the default app template file is used.
  *
+ * @property \Cake\View\Helper\BreadCrumbsHelper $BreadCrumbs
  * @property \Cake\View\Helper\FlashHelper $Flash
  * @property \Cake\View\Helper\FormHelper $Form
  * @property \Cake\View\Helper\HtmlHelper $Html

+ 30 - 0
tests/TestCase/Controller/Component/AuthComponentTest.php

@@ -831,6 +831,36 @@ class AuthComponentTest extends TestCase
     }
 
     /**
+     * testNoLoginRedirectForAuthenticatedUser method
+     *
+     * @return void
+     * @triggers Controller.startup $this->Controller
+     */
+    public function testStartupLoginActionIgnoreQueryString()
+    {
+        $request = new ServerRequest([
+            'params' => [
+                'plugin' => null,
+                'controller' => 'auth_test',
+                'action' => 'login'
+            ],
+            'query' => ['redirect' => '/admin/articles'],
+            'url' => '/auth_test/login?redirect=%2Fadmin%2Farticles',
+            'session' => $this->Auth->session
+        ]);
+        $this->Controller->request = $request;
+
+        $this->Auth->session->clear();
+        $this->Auth->setConfig('authenticate', ['Form']);
+        $this->Auth->setConfig('authorize', false);
+        $this->Auth->setConfig('loginAction', ['controller' => 'auth_test', 'action' => 'login']);
+
+        $event = new Event('Controller.startup', $this->Controller);
+        $return = $this->Auth->startup($event);
+        $this->assertNull($return);
+    }
+
+    /**
      * Default to loginRedirect, if set, on authError.
      *
      * @return void

+ 33 - 1
tests/TestCase/Controller/Component/SecurityComponentTest.php

@@ -21,6 +21,7 @@ use Cake\Core\Configure;
 use Cake\Event\Event;
 use Cake\Http\ServerRequest;
 use Cake\Http\Session;
+use Cake\Routing\Router;
 use Cake\TestSuite\TestCase;
 use Cake\Utility\Security;
 
@@ -189,7 +190,7 @@ class SecurityComponentTest extends TestCase
         unset($this->Controller);
     }
 
-    public function validatePost($expectedException = null, $expectedExceptionMessage = null)
+    public function validatePost($expectedException = 'SecurityException', $expectedExceptionMessage = null)
     {
         try {
             return $this->Controller->Security->validatePost($this->Controller);
@@ -745,6 +746,37 @@ class SecurityComponentTest extends TestCase
     }
 
     /**
+     * test validatePost uses full URL
+     *
+     * @return void
+     * @triggers Controller.startup $this->Controller
+     */
+    public function testValidatePostSubdirectory()
+    {
+        // set the base path.
+        $this->Controller->request = $this->Controller->request
+            ->withAttribute('base', 'subdir')
+            ->withAttributE('webroot', 'subdir/');
+        Router::pushRequest($this->Controller->request);
+
+        $event = new Event('Controller.startup', $this->Controller);
+        $this->Security->startup($event);
+
+        // Differs from testValidatePostSimple because of base url
+        $fields = 'cc9b6af3f33147235ae8f8037b0a71399a2425f2%3A';
+        $unlocked = '';
+        $debug = '';
+
+        $this->Controller->request = $this->Controller->request->withParsedBody([
+            'Model' => ['username' => '', 'password' => ''],
+            '_Token' => compact('fields', 'unlocked', 'debug')
+        ]);
+
+        $result = $this->validatePost();
+        $this->assertTrue($result);
+    }
+
+    /**
      * testValidatePostComplex method
      *
      * Tests hash validation for multiple records, including locked fields.

+ 17 - 0
tests/TestCase/Http/ServerRequestTest.php

@@ -212,6 +212,23 @@ class ServerRequestTest extends TestCase
     }
 
     /**
+     * Test getPath().
+     *
+     * @return void
+     */
+    public function testGetPath()
+    {
+        $request = new ServerRequest(['url' => '']);
+        $this->assertSame('/', $request->getPath());
+
+        $request = new ServerRequest(['url' => 'some/path?one=something&two=else']);
+        $this->assertEquals('/some/path', $request->getPath());
+
+        $request = $request->withRequestTarget('/foo/bar?x=y');
+        $this->assertEquals('/foo/bar', $request->getPath());
+    }
+
+    /**
      * Test addParams() method
      *
      * @group deprecated

+ 2 - 2
tests/TestCase/Mailer/EmailTest.php

@@ -879,13 +879,13 @@ class EmailTest extends TestCase
         $this->Email->addAttachments([CORE_PATH . 'config' . DS . 'bootstrap.php']);
         $this->Email->addAttachments([
             'other.txt' => CORE_PATH . 'config' . DS . 'bootstrap.php',
-            'license' => CORE_PATH . 'LICENSE.txt'
+            'license' => CORE_PATH . 'LICENSE'
         ]);
         $expected = [
             'basics.php' => ['file' => CAKE . 'basics.php', 'mimetype' => 'text/plain'],
             'bootstrap.php' => ['file' => CORE_PATH . 'config' . DS . 'bootstrap.php', 'mimetype' => 'text/x-php'],
             'other.txt' => ['file' => CORE_PATH . 'config' . DS . 'bootstrap.php', 'mimetype' => 'text/x-php'],
-            'license' => ['file' => CORE_PATH . 'LICENSE.txt', 'mimetype' => 'text/plain']
+            'license' => ['file' => CORE_PATH . 'LICENSE', 'mimetype' => 'text/plain']
         ];
         $this->assertSame($expected, $this->Email->getAttachments());
         $this->expectException(\InvalidArgumentException::class);

+ 8 - 5
tests/TestCase/Routing/RouterTest.php

@@ -61,7 +61,7 @@ class RouterTest extends TestCase
      *
      * @return void
      */
-    public function testbaseUrl()
+    public function testBaseUrl()
     {
         $this->assertRegExp('/^http(s)?:\/\//', Router::url('/', true));
         $this->assertRegExp('/^http(s)?:\/\//', Router::url(null, true));
@@ -73,7 +73,7 @@ class RouterTest extends TestCase
      *
      * @return void
      */
-    public function testfullBaseURL()
+    public function testFullBaseURL()
     {
         Router::fullBaseUrl('http://example.com');
         $this->assertEquals('http://example.com/', Router::url('/', true));
@@ -571,14 +571,17 @@ class RouterTest extends TestCase
                 'plugin' => null,
                 'controller' => 'subscribe',
             ],
-            'url' => '/magazine/',
+            'url' => '/subscribe',
             'base' => '/magazine',
             'webroot' => '/magazine/'
         ]);
         Router::pushRequest($request);
 
         $result = Router::url();
-        $this->assertEquals('/magazine/', $result);
+        $this->assertEquals('/magazine/subscribe', $result);
+
+        $result = Router::url([]);
+        $this->assertEquals('/magazine/subscribe', $result);
 
         $result = Router::url('/');
         $this->assertEquals('/magazine/', $result);
@@ -933,7 +936,7 @@ class RouterTest extends TestCase
             ],
             'webroot' => '/magazine/',
             'base' => '/magazine',
-            'url' => '/magazine/admin/subscriptions/edit/1',
+            'url' => '/admin/subscriptions/edit/1',
         ]);
         Router::setRequestInfo($request);
 

+ 25 - 0
tests/TestCase/View/Helper/HtmlHelperTest.php

@@ -364,6 +364,31 @@ class HtmlHelperTest extends TestCase
     }
 
     /**
+     * Ensure that data URIs don't get base paths set.
+     *
+     * @return void
+     */
+    public function testImageDataUriBaseDir()
+    {
+        $request = $this->Html->request
+            ->withAttribute('base', 'subdir')
+            ->withAttribute('webroot', 'subdir/');
+        $this->Html->Url->request = $this->Html->request = $request;
+        Router::pushRequest($request);
+
+        $data = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4' .
+            '/8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==';
+        $result = $this->Html->image($data);
+        $expected = ['img' => ['src' => $data, 'alt' => '']];
+        $this->assertHtml($expected, $result);
+
+        $data = 'data:image/png;base64,<evil>';
+        $result = $this->Html->image($data);
+        $expected = ['img' => ['src' => h($data), 'alt' => '']];
+        $this->assertHtml($expected, $result);
+    }
+
+    /**
      * Test image() with query strings.
      *
      * @return void

+ 53 - 2
tests/TestCase/View/Helper/UrlHelperTest.php

@@ -70,7 +70,7 @@ class UrlHelperTest extends TestCase
      *
      * @return void
      */
-    public function testUrlConversion()
+    public function testBuildUrlConversion()
     {
         Router::connect('/:controller/:action/*');
 
@@ -105,9 +105,36 @@ class UrlHelperTest extends TestCase
     }
 
     /**
+     * ensure that build factors in base paths.
+     *
+     * @return void
+     */
+    public function testBuildBasePath()
+    {
+        Router::connect('/:controller/:action/*');
+        $request = new ServerRequest([
+            'params' => [
+                'action' => 'index',
+                'plugin' => null,
+                'controller' => 'subscribe',
+            ],
+            'url' => '/subscribe',
+            'base' => '/magazine',
+            'webroot' => '/magazine/'
+        ]);
+        Router::pushRequest($request);
+
+        $this->assertEquals('/magazine/subscribe', $this->Helper->build());
+        $this->assertEquals(
+            '/magazine/articles/add',
+            $this->Helper->build(['controller' => 'articles', 'action' => 'add'])
+        );
+    }
+
+    /**
      * @return void
      */
-    public function testUrlConversionUnescaped()
+    public function testBuildUrlConversionUnescaped()
     {
         $result = $this->Helper->build('/controller/action/1?one=1&two=2', ['escape' => false]);
         $this->assertEquals('/controller/action/1?one=1&two=2', $result);
@@ -206,6 +233,30 @@ class UrlHelperTest extends TestCase
     }
 
     /**
+     * Test assetUrl and data uris
+     *
+     * @return void
+     */
+    public function testAssetUrlDataUri()
+    {
+        $request = $this->Helper->request
+            ->withAttribute('base', 'subdir')
+            ->withAttribute('webroot', 'subdir/');
+
+        $this->Helper->request = $request;
+        Router::pushRequest($request);
+
+        $data = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4' .
+            '/8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==';
+        $result = $this->Helper->assetUrl($data);
+        $this->assertSame($data, $result);
+
+        $data = 'data:image/png;base64,<evil>';
+        $result = $this->Helper->assetUrl($data);
+        $this->assertHtml(h($data), $result);
+    }
+
+    /**
      * Test assetUrl with no rewriting.
      *
      * @return void