From cc5df3ad91a2b2630f5794b390fa097f6eded557 Mon Sep 17 00:00:00 2001 From: Max-Julian Pogner Date: Mon, 16 Dec 2013 14:59:27 +0100 Subject: [PATCH 01/23] FIX: hook slim.after is called before response was sent Signed-off-by: Max-Julian Pogner --- Slim/Slim.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Slim/Slim.php b/Slim/Slim.php index f8349931b..392564906 100644 --- a/Slim/Slim.php +++ b/Slim/Slim.php @@ -1291,6 +1291,8 @@ public function run() echo $body; } + $this->applyHook('slim.after'); + restore_error_handler(); } @@ -1329,7 +1331,6 @@ public function call() $this->stop(); } catch (\Slim\Exception\Stop $e) { $this->response()->write(ob_get_clean()); - $this->applyHook('slim.after'); } catch (\Exception $e) { if ($this->config('debug')) { throw $e; From 7c2983048a3365bfa2da7e55d6667288551ebe60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rcio=20Almada?= Date: Thu, 26 Dec 2013 18:44:02 -0300 Subject: [PATCH 02/23] Detect circular middleware stacks and throw comprehensible exception. --- Slim/Slim.php | 4 ++++ tests/SlimTest.php | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/Slim/Slim.php b/Slim/Slim.php index 392564906..4d77f0bb4 100644 --- a/Slim/Slim.php +++ b/Slim/Slim.php @@ -1233,6 +1233,10 @@ public function clearHooks($name = null) */ public function add(\Slim\Middleware $newMiddleware) { + if(in_array($newMiddleware, $this->middleware)) { + $middleware_class = get_class($newMiddleware); + throw new \RuntimeException("Circular Middleware setup detected. Tried to queue the same Middleware instance ({$middleware_class}) twice."); + } $newMiddleware->setApplication($this); $newMiddleware->setNextMiddleware($this->middleware[0]); array_unshift($this->middleware, $newMiddleware); diff --git a/tests/SlimTest.php b/tests/SlimTest.php index 604a0e975..82ace7ff4 100644 --- a/tests/SlimTest.php +++ b/tests/SlimTest.php @@ -1184,6 +1184,25 @@ public function testAddMiddleware() $this->assertEquals('Hello', $s->response()->header('X-Slim-Test')); } + /** + * Test exception when adding circular middleware queues + * + * This asserts that the same middleware can NOT be queued twice (usually by accident). + * Circular middleware stack causes a troublesome to debug PHP Fatal error: + * + * > Fatal error: Maximum function nesting level of '100' reached. aborting! + */ + public function testFailureWhenAddingCircularMiddleware() + { + $this->setExpectedException('\RuntimeException'); + $middleware = new CustomMiddleware; + $s = new \Slim\Slim; + $s->add($middleware); + $s->add(new CustomMiddleware); + $s->add($middleware); + $s->run(); + } + /************************************************ * FLASH MESSAGING ************************************************/ From 018b24bc5ff3b2bb0d377c844af12715e5fe6e34 Mon Sep 17 00:00:00 2001 From: Maks Feltrin Date: Tue, 18 Feb 2014 16:18:36 +0100 Subject: [PATCH 03/23] always merge settings if 1st parameter is an array --- Slim/Slim.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Slim/Slim.php b/Slim/Slim.php index 43595074b..e6e4e8e2f 100644 --- a/Slim/Slim.php +++ b/Slim/Slim.php @@ -335,16 +335,16 @@ public static function getDefaultSettings() */ public function config($name, $value = null) { - if (func_num_args() === 1) { - if (is_array($name)) { - $this->settings = array_merge($this->settings, $name); - } else { - return isset($this->settings[$name]) ? $this->settings[$name] : null; - } + $c = $this->container; + + if (is_array($name)) { + $c['settings'] = array_merge($c['settings'], $name); + } elseif (func_num_args() === 1) { + return isset($c['settings'][$name]) ? $c['settings'][$name] : null; } else { - $settings = $this->settings; + $settings = $c['settings']; $settings[$name] = $value; - $this->settings = $settings; + $c['settings'] = $settings; } } From 2606403d55568e5b079a3cda23227c1c8dd50f82 Mon Sep 17 00:00:00 2001 From: Maks Feltrin Date: Fri, 21 Feb 2014 14:56:06 +0100 Subject: [PATCH 04/23] adds the possibility of recursive merging --- Slim/Slim.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Slim/Slim.php b/Slim/Slim.php index e6e4e8e2f..b70e3984c 100644 --- a/Slim/Slim.php +++ b/Slim/Slim.php @@ -338,7 +338,11 @@ public function config($name, $value = null) $c = $this->container; if (is_array($name)) { - $c['settings'] = array_merge($c['settings'], $name); + if (true === $value) { + $c['settings'] = array_merge_recursive($c['settings'], $name); + } else { + $c['settings'] = array_merge($c['settings'], $name); + } } elseif (func_num_args() === 1) { return isset($c['settings'][$name]) ? $c['settings'][$name] : null; } else { From 44c5442b33d5b92ed521bbc5e4406a335052d96c Mon Sep 17 00:00:00 2001 From: Benji Date: Sun, 2 Mar 2014 11:36:41 -0800 Subject: [PATCH 05/23] Clarify apache setup Adds in a hint for the Apache AllowOverride setup to permit the .htaccess rules to rewrite incoming requests --- README.markdown | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.markdown b/README.markdown index 802624b50..8e4fbe84a 100644 --- a/README.markdown +++ b/README.markdown @@ -63,6 +63,10 @@ should contain this code: RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^ index.php [QSA,L] +Additionally, make sure your virtual host is configured with the AllowOverride option so that the .htaccess rewrite rules can be used: + + AllowOverride All + #### Nginx The nginx configuration file should contain this code (along with other settings you may need) in your `location` block: From 4a58325fa408e25039738c2c2f2692d5fb34e700 Mon Sep 17 00:00:00 2001 From: Josh Lockhart Date: Wed, 5 Mar 2014 20:04:46 -0500 Subject: [PATCH 06/23] Duplicate changes provided by jamesmcfadden for develop branch #771 #765 --- Slim/Middleware/PrettyExceptions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Slim/Middleware/PrettyExceptions.php b/Slim/Middleware/PrettyExceptions.php index 599aab43d..8a56442b0 100644 --- a/Slim/Middleware/PrettyExceptions.php +++ b/Slim/Middleware/PrettyExceptions.php @@ -89,7 +89,7 @@ protected function renderBody(&$env, $exception) $message = $exception->getMessage(); $file = $exception->getFile(); $line = $exception->getLine(); - $trace = $exception->getTraceAsString(); + $trace = str_replace(array('#', '\n'), array('
#', '
'), $exception->getTraceAsString()); $html = sprintf('

%s

', $title); $html .= '

The application could not run because of the following error:

'; $html .= '

Details

'; From 25802a17275c13927caef21a6d46d0db4639fa66 Mon Sep 17 00:00:00 2001 From: Josh Lockhart Date: Wed, 5 Mar 2014 20:09:06 -0500 Subject: [PATCH 07/23] Let \Slim\Http\Util parse cookie values that contain an equal sign. See #774 --- Slim/Http/Util.php | 2 +- tests/Http/UtilTest.php | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Slim/Http/Util.php b/Slim/Http/Util.php index 23af623fc..dafedb38d 100644 --- a/Slim/Http/Util.php +++ b/Slim/Http/Util.php @@ -401,7 +401,7 @@ public static function parseCookieHeader($header) $header = rtrim($header, "\r\n"); $headerPieces = preg_split('@\s*[;,]\s*@', $header); foreach ($headerPieces as $c) { - $cParts = explode('=', $c); + $cParts = explode('=', $c, 2); if (count($cParts) === 2) { $key = urldecode($cParts[0]); $value = urldecode($cParts[1]); diff --git a/tests/Http/UtilTest.php b/tests/Http/UtilTest.php index 9df5d7443..dfe0ea1b8 100644 --- a/tests/Http/UtilTest.php +++ b/tests/Http/UtilTest.php @@ -413,6 +413,19 @@ public function testParsesCookieHeader() $this->assertEquals('blue', $result['colors']); } + /** + * Test parses Cookie: HTTP header with `=` in the cookie value + */ + public function testParsesCookieHeaderWithEqualSignInValue() + { + $header = 'foo=bar; one=two=; colors=blue'; + $result = \Slim\Http\Util::parseCookieHeader($header); + $this->assertEquals(3, count($result)); + $this->assertEquals('bar', $result['foo']); + $this->assertEquals('two=', $result['one']); + $this->assertEquals('blue', $result['colors']); + } + public function testParsesCookieHeaderWithCommaSeparator() { $header = 'foo=bar, one=two, colors=blue'; From e20419dc1b98022f78773ef7b77676c9fb3119da Mon Sep 17 00:00:00 2001 From: Marcin Kurzyna Date: Tue, 28 Jan 2014 14:49:55 +0100 Subject: [PATCH 08/23] Fix resolving X-Forwarded-For header This fixes X-Forwarded-For resolving (HTTP_ prefix). This is fixed on develop branch difrently. However current behaviour renders invalid results and IMHO this patch should merged and released as 2.3.NEXT --- Slim/Http/Request.php | 9 +++++---- tests/Http/RequestTest.php | 41 +++++++++++--------------------------- 2 files changed, 17 insertions(+), 33 deletions(-) diff --git a/Slim/Http/Request.php b/Slim/Http/Request.php index 090868886..5ea22e4da 100644 --- a/Slim/Http/Request.php +++ b/Slim/Http/Request.php @@ -576,10 +576,11 @@ public function getUrl() */ public function getIp() { - if (isset($this->env['X_FORWARDED_FOR'])) { - return $this->env['X_FORWARDED_FOR']; - } elseif (isset($this->env['CLIENT_IP'])) { - return $this->env['CLIENT_IP']; + $keys = array('X_FORWARDED_FOR', 'HTTP_X_FORWARDED_FOR', 'CLIENT_IP', 'REMOTE_ADDR'); + foreach ($keys as $key) { + if (isset($this->env[$key])) { + return $this->env[$key]; + } } return $this->env['REMOTE_ADDR']; diff --git a/tests/Http/RequestTest.php b/tests/Http/RequestTest.php index a64d9672e..48887a97c 100644 --- a/tests/Http/RequestTest.php +++ b/tests/Http/RequestTest.php @@ -879,41 +879,24 @@ public function testGetUrlWithHttps() /** * Test get IP + * @dataProvider dataTestIp */ - public function testGetIp() + public function testGetIp(array $server, $expected) { - $env = \Slim\Environment::mock(array( - 'REMOTE_ADDR' => '127.0.0.1' - )); + $env = \Slim\Environment::mock($server); $req = new \Slim\Http\Request($env); - $this->assertEquals('127.0.0.1', $req->getIp()); + $this->assertEquals($expected, $req->getIp()); } - /** - * Test get IP with proxy server and Client-Ip header - */ - public function testGetIpWithClientIp() + public function dataTestIp() { - $env = \Slim\Environment::mock(array( - 'REMOTE_ADDR' => '127.0.0.1', - 'CLIENT_IP' => '127.0.0.2' - )); - $req = new \Slim\Http\Request($env); - $this->assertEquals('127.0.0.2', $req->getIp()); - } - - /** - * Test get IP with proxy server and X-Forwarded-For header - */ - public function testGetIpWithForwardedFor() - { - $env = \Slim\Environment::mock(array( - 'REMOTE_ADDR' => '127.0.0.1', - 'CLIENT_IP' => '127.0.0.2', - 'X_FORWARDED_FOR' => '127.0.0.3' - )); - $req = new \Slim\Http\Request($env); - $this->assertEquals('127.0.0.3', $req->getIp()); + return array( + array(array('REMOTE_ADDR' => '127.0.0.1'), '127.0.0.1'), + array(array('REMOTE_ADDR' => '127.0.0.1', 'CLIENT_IP' => '127.0.0.2'), '127.0.0.2'), + array(array('REMOTE_ADDR' => '127.0.0.1', 'CLIENT_IP' => '127.0.0.2', 'X_FORWARDED_FOR' => '127.0.0.3'), '127.0.0.3'), + array(array('REMOTE_ADDR' => '127.0.0.1', 'CLIENT_IP' => '127.0.0.2', 'HTTP_X_FORWARDED_FOR' => '127.0.0.4'), '127.0.0.4'), + array(array('REMOTE_ADDR' => '127.0.0.1', 'CLIENT_IP' => '127.0.0.2', 'X_FORWARDED_FOR' => '127.0.0.3', 'HTTP_X_FORWARDED_FOR' => '127.0.0.4'), '127.0.0.3'), + ); } /** From a171cf670b15ac6582fdf18d0e22e412456fc064 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=CC=81=20Miguel=20Molina=20Arboledas?= Date: Sun, 16 Feb 2014 20:10:05 +0100 Subject: [PATCH 09/23] Added default value to \Slim\Http\Request::params --- Slim/Http/Request.php | 8 +++++--- tests/Http/RequestTest.php | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Slim/Http/Request.php b/Slim/Http/Request.php index 5ea22e4da..735484b01 100644 --- a/Slim/Http/Request.php +++ b/Slim/Http/Request.php @@ -187,16 +187,18 @@ public function isXhr() * Fetch GET and POST data * * This method returns a union of GET and POST data as a key-value array, or the value - * of the array key if requested; if the array key does not exist, NULL is returned. + * of the array key if requested; if the array key does not exist, NULL is returned, + * unless there is a default value specified. * * @param string $key + * @param mixed $default * @return array|mixed|null */ - public function params($key = null) + public function params($key = null, $default = null) { $union = array_merge($this->get(), $this->post()); if ($key) { - return isset($union[$key]) ? $union[$key] : null; + return isset($union[$key]) ? $union[$key] : $default; } return $union; diff --git a/tests/Http/RequestTest.php b/tests/Http/RequestTest.php index 48887a97c..6d87659c7 100644 --- a/tests/Http/RequestTest.php +++ b/tests/Http/RequestTest.php @@ -190,6 +190,7 @@ public function testParamsFromQueryString() $this->assertEquals(3, count($req->params())); $this->assertEquals('1', $req->params('one')); $this->assertNull($req->params('foo')); + $this->assertEquals(1, $req->params('foo', 1)); } /** From e6cc693412d312166df0f99239bdf8db3f19c2a3 Mon Sep 17 00:00:00 2001 From: Maurus Cuelenaere Date: Sat, 28 Dec 2013 13:11:31 +0100 Subject: [PATCH 10/23] Allow all valid characters in function names of getCallableAsClass This uses the [a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]* regex as specified by https://php.net/manual/en/functions.user-defined.php Conflicts: tests/RouteTest.php --- Slim/Route.php | 2 +- tests/RouteTest.php | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/Slim/Route.php b/Slim/Route.php index 66b661a2b..77448b66a 100644 --- a/Slim/Route.php +++ b/Slim/Route.php @@ -162,7 +162,7 @@ public function getCallable() public function setCallable($callable) { $matches = array(); - if (is_string($callable) && preg_match('!^([^\:]+)\:([[:alnum:]]+)$!', $callable, $matches)) { + if (is_string($callable) && preg_match('!^([^\:]+)\:([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)$!', $callable, $matches)) { $callable = array(new $matches[1], $matches[2]); } diff --git a/tests/RouteTest.php b/tests/RouteTest.php index f3bd08bbc..a6095474e 100644 --- a/tests/RouteTest.php +++ b/tests/RouteTest.php @@ -76,6 +76,7 @@ public function testGetCallableAsClass() $this->assertEquals('getCurrentRoute', $callable[1]); } + public function testGetCallableAsStaticMethod() { $route = new \Slim\Route('/bar', '\Slim\Slim::getInstance'); @@ -84,6 +85,20 @@ public function testGetCallableAsStaticMethod() $this->assertEquals('\Slim\Slim::getInstance', $callable); } + public function example_càllâble_wïth_wéird_chars() + { + + } + + public function testGetCallableWithOddCharsAsClass() + { + $route = new \Slim\Route('/foo', '\RouteTest:example_càllâble_wïth_wéird_chars'); + + $callable = $route->getCallable(); + $this->assertInstanceOf('\RouteTest', $callable[0]); + $this->assertEquals('example_càllâble_wïth_wéird_chars', $callable[1]); + } + public function testSetCallable() { $callable = function () { From 73faca2dd750b94bfe1ee0bccf59d74bca95ca33 Mon Sep 17 00:00:00 2001 From: Maurus Cuelenaere Date: Mon, 30 Dec 2013 12:46:26 +0100 Subject: [PATCH 11/23] Lazy-initialize class in \Slim\Route::setCallable() You don't want to construct all kinds of objects when setting up your routes, so don't initialize those objects until the route is actually dispatched. Add some basic caching so multiple route dispatches don't result in multiple class instantiations. --- Slim/Route.php | 10 +++++++++- tests/RouteTest.php | 45 +++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 50 insertions(+), 5 deletions(-) diff --git a/Slim/Route.php b/Slim/Route.php index 77448b66a..99b47a135 100644 --- a/Slim/Route.php +++ b/Slim/Route.php @@ -163,7 +163,15 @@ public function setCallable($callable) { $matches = array(); if (is_string($callable) && preg_match('!^([^\:]+)\:([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)$!', $callable, $matches)) { - $callable = array(new $matches[1], $matches[2]); + $class = $matches[1]; + $method = $matches[2]; + $callable = function() use ($class, $method) { + static $obj = null; + if ($obj === null) { + $obj = new $class; + } + return call_user_func_array(array($obj, $method), func_get_args()); + }; } if (!is_callable($callable)) { diff --git a/tests/RouteTest.php b/tests/RouteTest.php index a6095474e..316e0f971 100644 --- a/tests/RouteTest.php +++ b/tests/RouteTest.php @@ -29,6 +29,28 @@ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ + +class LazyInitializeTestClass { + public static $initialized = false; + + public function __construct() { + self::$initialized = true; + } + + public function foo() { + } +} + +class FooTestClass { + public static $foo_invoked = false; + public static $foo_invoked_args = array(); + + public function foo() { + self::$foo_invoked = true; + self::$foo_invoked_args = func_get_args(); + } +} + class RouteTest extends PHPUnit_Framework_TestCase { public function testGetPattern() @@ -69,11 +91,26 @@ public function testGetCallable() public function testGetCallableAsClass() { - $route = new \Slim\Route('/foo', '\Slim\Router:getCurrentRoute'); + FooTestClass::$foo_invoked = false; + FooTestClass::$foo_invoked_args = array(); + $route = new \Slim\Route('/foo', '\FooTestClass:foo'); + $route->setParams(array('bar' => '1234')); - $callable = $route->getCallable(); - $this->assertInstanceOf('\Slim\Router', $callable[0]); - $this->assertEquals('getCurrentRoute', $callable[1]); + $this->assertFalse(FooTestClass::$foo_invoked); + $this->assertTrue($route->dispatch()); + $this->assertTrue(FooTestClass::$foo_invoked); + $this->assertEquals(array('1234'), FooTestClass::$foo_invoked_args); + } + + public function testGetCallableAsClassLazyInitialize() + { + LazyInitializeTestClass::$initialized = false; + + $route = new \Slim\Route('/foo', '\LazyInitializeTestClass:foo'); + $this->assertFalse(LazyInitializeTestClass::$initialized); + + $route->dispatch(); + $this->assertTrue(LazyInitializeTestClass::$initialized); } From cc0171e2b43b4f371d5e7aa932ec225ef6da6a98 Mon Sep 17 00:00:00 2001 From: maks feltrin Date: Sat, 8 Mar 2014 13:38:43 +0100 Subject: [PATCH 12/23] added test --- tests/SlimTest.php | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/SlimTest.php b/tests/SlimTest.php index e955e796b..cb109d75b 100644 --- a/tests/SlimTest.php +++ b/tests/SlimTest.php @@ -199,6 +199,43 @@ public function testBatchSetSettings() $this->assertFalse($s->config('debug')); } + /** + * Test set settings recursively + */ + public function testSetSettingsRecursively() + { + $config = array( + 'my_module' => array( + 'paths' => array( + './my_module/path/1', + ), + ) + ); + + $s = new \Slim\Slim($config); + + $override = array( + 'my_module' => array( + 'paths' => array( + './my_module/path/2', + './my_module/path/3', + ), + ) + ); + + $s->config($override, true); + + $expected = array( + 'paths' => array( + './my_module/path/1', + './my_module/path/2', + './my_module/path/3', + ), + ); + + $this->assertEquals($expected, $s->config('my_module')); + } + /************************************************ * MODES ************************************************/ From cc103192af2a1852fc0e74025167b0d88b0aa023 Mon Sep 17 00:00:00 2001 From: maks feltrin Date: Sat, 8 Mar 2014 15:09:34 +0100 Subject: [PATCH 13/23] added recursive setting tests --- tests/SlimTest.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/SlimTest.php b/tests/SlimTest.php index cb109d75b..5062ca019 100644 --- a/tests/SlimTest.php +++ b/tests/SlimTest.php @@ -222,7 +222,8 @@ public function testSetSettingsRecursively() ), ) ); - + + // Test recursive batch behaviour $s->config($override, true); $expected = array( @@ -234,6 +235,12 @@ public function testSetSettingsRecursively() ); $this->assertEquals($expected, $s->config('my_module')); + + // Test default batch behaviour + $s = new \Slim\Slim($config); + $s->config($override); + + $this->assertNotEquals($expected, $s->config('my_module')); } /************************************************ From a5326df03d600bcfbd2f9caa84e665841c7c1541 Mon Sep 17 00:00:00 2001 From: Josh Lockhart Date: Sat, 8 Mar 2014 14:36:14 -0500 Subject: [PATCH 14/23] Fix unit tests after merge --- tests/RouteTest.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/RouteTest.php b/tests/RouteTest.php index 316e0f971..741e4f01a 100644 --- a/tests/RouteTest.php +++ b/tests/RouteTest.php @@ -124,16 +124,15 @@ public function testGetCallableAsStaticMethod() public function example_càllâble_wïth_wéird_chars() { - + return 'test'; } public function testGetCallableWithOddCharsAsClass() { $route = new \Slim\Route('/foo', '\RouteTest:example_càllâble_wïth_wéird_chars'); - $callable = $route->getCallable(); - $this->assertInstanceOf('\RouteTest', $callable[0]); - $this->assertEquals('example_càllâble_wïth_wéird_chars', $callable[1]); + + $this->assertEquals('test', $callable()); } public function testSetCallable() From 09bc2adaa9b4ca12932613d2cbb65f5b359b190a Mon Sep 17 00:00:00 2001 From: Benji Date: Sun, 2 Mar 2014 11:36:41 -0800 Subject: [PATCH 15/23] Clarify apache setup Adds in a hint for the Apache AllowOverride setup to permit the .htaccess rules to rewrite incoming requests --- README.markdown | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.markdown b/README.markdown index 802624b50..8e4fbe84a 100644 --- a/README.markdown +++ b/README.markdown @@ -63,6 +63,10 @@ should contain this code: RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^ index.php [QSA,L] +Additionally, make sure your virtual host is configured with the AllowOverride option so that the .htaccess rewrite rules can be used: + + AllowOverride All + #### Nginx The nginx configuration file should contain this code (along with other settings you may need) in your `location` block: From 9ff02f572742561dc51beb877c157badd85afdc6 Mon Sep 17 00:00:00 2001 From: Josh Lockhart Date: Wed, 5 Mar 2014 20:04:46 -0500 Subject: [PATCH 16/23] Duplicate changes provided by jamesmcfadden for develop branch #771 #765 --- Slim/Middleware/PrettyExceptions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Slim/Middleware/PrettyExceptions.php b/Slim/Middleware/PrettyExceptions.php index 599aab43d..8a56442b0 100644 --- a/Slim/Middleware/PrettyExceptions.php +++ b/Slim/Middleware/PrettyExceptions.php @@ -89,7 +89,7 @@ protected function renderBody(&$env, $exception) $message = $exception->getMessage(); $file = $exception->getFile(); $line = $exception->getLine(); - $trace = $exception->getTraceAsString(); + $trace = str_replace(array('#', '\n'), array('
#', '
'), $exception->getTraceAsString()); $html = sprintf('

%s

', $title); $html .= '

The application could not run because of the following error:

'; $html .= '

Details

'; From df70ec9f130045bb5b4e60b99a633cd0c60ad1e2 Mon Sep 17 00:00:00 2001 From: Josh Lockhart Date: Wed, 5 Mar 2014 20:09:06 -0500 Subject: [PATCH 17/23] Let \Slim\Http\Util parse cookie values that contain an equal sign. See #774 --- Slim/Http/Util.php | 2 +- tests/Http/UtilTest.php | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Slim/Http/Util.php b/Slim/Http/Util.php index 23af623fc..dafedb38d 100644 --- a/Slim/Http/Util.php +++ b/Slim/Http/Util.php @@ -401,7 +401,7 @@ public static function parseCookieHeader($header) $header = rtrim($header, "\r\n"); $headerPieces = preg_split('@\s*[;,]\s*@', $header); foreach ($headerPieces as $c) { - $cParts = explode('=', $c); + $cParts = explode('=', $c, 2); if (count($cParts) === 2) { $key = urldecode($cParts[0]); $value = urldecode($cParts[1]); diff --git a/tests/Http/UtilTest.php b/tests/Http/UtilTest.php index 9df5d7443..dfe0ea1b8 100644 --- a/tests/Http/UtilTest.php +++ b/tests/Http/UtilTest.php @@ -413,6 +413,19 @@ public function testParsesCookieHeader() $this->assertEquals('blue', $result['colors']); } + /** + * Test parses Cookie: HTTP header with `=` in the cookie value + */ + public function testParsesCookieHeaderWithEqualSignInValue() + { + $header = 'foo=bar; one=two=; colors=blue'; + $result = \Slim\Http\Util::parseCookieHeader($header); + $this->assertEquals(3, count($result)); + $this->assertEquals('bar', $result['foo']); + $this->assertEquals('two=', $result['one']); + $this->assertEquals('blue', $result['colors']); + } + public function testParsesCookieHeaderWithCommaSeparator() { $header = 'foo=bar, one=two, colors=blue'; From 5a01e4128e5099091f97ee007eb41756eb368384 Mon Sep 17 00:00:00 2001 From: Marcin Kurzyna Date: Tue, 28 Jan 2014 14:49:55 +0100 Subject: [PATCH 18/23] Fix resolving X-Forwarded-For header This fixes X-Forwarded-For resolving (HTTP_ prefix). This is fixed on develop branch difrently. However current behaviour renders invalid results and IMHO this patch should merged and released as 2.3.NEXT --- Slim/Http/Request.php | 9 +++++---- tests/Http/RequestTest.php | 41 +++++++++++--------------------------- 2 files changed, 17 insertions(+), 33 deletions(-) diff --git a/Slim/Http/Request.php b/Slim/Http/Request.php index 090868886..5ea22e4da 100644 --- a/Slim/Http/Request.php +++ b/Slim/Http/Request.php @@ -576,10 +576,11 @@ public function getUrl() */ public function getIp() { - if (isset($this->env['X_FORWARDED_FOR'])) { - return $this->env['X_FORWARDED_FOR']; - } elseif (isset($this->env['CLIENT_IP'])) { - return $this->env['CLIENT_IP']; + $keys = array('X_FORWARDED_FOR', 'HTTP_X_FORWARDED_FOR', 'CLIENT_IP', 'REMOTE_ADDR'); + foreach ($keys as $key) { + if (isset($this->env[$key])) { + return $this->env[$key]; + } } return $this->env['REMOTE_ADDR']; diff --git a/tests/Http/RequestTest.php b/tests/Http/RequestTest.php index a64d9672e..48887a97c 100644 --- a/tests/Http/RequestTest.php +++ b/tests/Http/RequestTest.php @@ -879,41 +879,24 @@ public function testGetUrlWithHttps() /** * Test get IP + * @dataProvider dataTestIp */ - public function testGetIp() + public function testGetIp(array $server, $expected) { - $env = \Slim\Environment::mock(array( - 'REMOTE_ADDR' => '127.0.0.1' - )); + $env = \Slim\Environment::mock($server); $req = new \Slim\Http\Request($env); - $this->assertEquals('127.0.0.1', $req->getIp()); + $this->assertEquals($expected, $req->getIp()); } - /** - * Test get IP with proxy server and Client-Ip header - */ - public function testGetIpWithClientIp() + public function dataTestIp() { - $env = \Slim\Environment::mock(array( - 'REMOTE_ADDR' => '127.0.0.1', - 'CLIENT_IP' => '127.0.0.2' - )); - $req = new \Slim\Http\Request($env); - $this->assertEquals('127.0.0.2', $req->getIp()); - } - - /** - * Test get IP with proxy server and X-Forwarded-For header - */ - public function testGetIpWithForwardedFor() - { - $env = \Slim\Environment::mock(array( - 'REMOTE_ADDR' => '127.0.0.1', - 'CLIENT_IP' => '127.0.0.2', - 'X_FORWARDED_FOR' => '127.0.0.3' - )); - $req = new \Slim\Http\Request($env); - $this->assertEquals('127.0.0.3', $req->getIp()); + return array( + array(array('REMOTE_ADDR' => '127.0.0.1'), '127.0.0.1'), + array(array('REMOTE_ADDR' => '127.0.0.1', 'CLIENT_IP' => '127.0.0.2'), '127.0.0.2'), + array(array('REMOTE_ADDR' => '127.0.0.1', 'CLIENT_IP' => '127.0.0.2', 'X_FORWARDED_FOR' => '127.0.0.3'), '127.0.0.3'), + array(array('REMOTE_ADDR' => '127.0.0.1', 'CLIENT_IP' => '127.0.0.2', 'HTTP_X_FORWARDED_FOR' => '127.0.0.4'), '127.0.0.4'), + array(array('REMOTE_ADDR' => '127.0.0.1', 'CLIENT_IP' => '127.0.0.2', 'X_FORWARDED_FOR' => '127.0.0.3', 'HTTP_X_FORWARDED_FOR' => '127.0.0.4'), '127.0.0.3'), + ); } /** From 2bf6948b2652b13c9343eeec1805fc958a935a9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=CC=81=20Miguel=20Molina=20Arboledas?= Date: Sun, 16 Feb 2014 20:10:05 +0100 Subject: [PATCH 19/23] Added default value to \Slim\Http\Request::params --- Slim/Http/Request.php | 8 +++++--- tests/Http/RequestTest.php | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Slim/Http/Request.php b/Slim/Http/Request.php index 5ea22e4da..735484b01 100644 --- a/Slim/Http/Request.php +++ b/Slim/Http/Request.php @@ -187,16 +187,18 @@ public function isXhr() * Fetch GET and POST data * * This method returns a union of GET and POST data as a key-value array, or the value - * of the array key if requested; if the array key does not exist, NULL is returned. + * of the array key if requested; if the array key does not exist, NULL is returned, + * unless there is a default value specified. * * @param string $key + * @param mixed $default * @return array|mixed|null */ - public function params($key = null) + public function params($key = null, $default = null) { $union = array_merge($this->get(), $this->post()); if ($key) { - return isset($union[$key]) ? $union[$key] : null; + return isset($union[$key]) ? $union[$key] : $default; } return $union; diff --git a/tests/Http/RequestTest.php b/tests/Http/RequestTest.php index 48887a97c..6d87659c7 100644 --- a/tests/Http/RequestTest.php +++ b/tests/Http/RequestTest.php @@ -190,6 +190,7 @@ public function testParamsFromQueryString() $this->assertEquals(3, count($req->params())); $this->assertEquals('1', $req->params('one')); $this->assertNull($req->params('foo')); + $this->assertEquals(1, $req->params('foo', 1)); } /** From 22d88eb20e01347edf64c43c5890925fc154aa8e Mon Sep 17 00:00:00 2001 From: Maurus Cuelenaere Date: Sat, 28 Dec 2013 13:11:31 +0100 Subject: [PATCH 20/23] Allow all valid characters in function names of getCallableAsClass This uses the [a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]* regex as specified by https://php.net/manual/en/functions.user-defined.php Conflicts: tests/RouteTest.php --- Slim/Route.php | 2 +- tests/RouteTest.php | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/Slim/Route.php b/Slim/Route.php index 66b661a2b..77448b66a 100644 --- a/Slim/Route.php +++ b/Slim/Route.php @@ -162,7 +162,7 @@ public function getCallable() public function setCallable($callable) { $matches = array(); - if (is_string($callable) && preg_match('!^([^\:]+)\:([[:alnum:]]+)$!', $callable, $matches)) { + if (is_string($callable) && preg_match('!^([^\:]+)\:([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)$!', $callable, $matches)) { $callable = array(new $matches[1], $matches[2]); } diff --git a/tests/RouteTest.php b/tests/RouteTest.php index f3bd08bbc..a6095474e 100644 --- a/tests/RouteTest.php +++ b/tests/RouteTest.php @@ -76,6 +76,7 @@ public function testGetCallableAsClass() $this->assertEquals('getCurrentRoute', $callable[1]); } + public function testGetCallableAsStaticMethod() { $route = new \Slim\Route('/bar', '\Slim\Slim::getInstance'); @@ -84,6 +85,20 @@ public function testGetCallableAsStaticMethod() $this->assertEquals('\Slim\Slim::getInstance', $callable); } + public function example_càllâble_wïth_wéird_chars() + { + + } + + public function testGetCallableWithOddCharsAsClass() + { + $route = new \Slim\Route('/foo', '\RouteTest:example_càllâble_wïth_wéird_chars'); + + $callable = $route->getCallable(); + $this->assertInstanceOf('\RouteTest', $callable[0]); + $this->assertEquals('example_càllâble_wïth_wéird_chars', $callable[1]); + } + public function testSetCallable() { $callable = function () { From b3991c6d7e9d0fd87fa7404fdbb589ba76110769 Mon Sep 17 00:00:00 2001 From: Maurus Cuelenaere Date: Mon, 30 Dec 2013 12:46:26 +0100 Subject: [PATCH 21/23] Lazy-initialize class in \Slim\Route::setCallable() You don't want to construct all kinds of objects when setting up your routes, so don't initialize those objects until the route is actually dispatched. Add some basic caching so multiple route dispatches don't result in multiple class instantiations. --- Slim/Route.php | 10 +++++++++- tests/RouteTest.php | 45 +++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 50 insertions(+), 5 deletions(-) diff --git a/Slim/Route.php b/Slim/Route.php index 77448b66a..99b47a135 100644 --- a/Slim/Route.php +++ b/Slim/Route.php @@ -163,7 +163,15 @@ public function setCallable($callable) { $matches = array(); if (is_string($callable) && preg_match('!^([^\:]+)\:([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)$!', $callable, $matches)) { - $callable = array(new $matches[1], $matches[2]); + $class = $matches[1]; + $method = $matches[2]; + $callable = function() use ($class, $method) { + static $obj = null; + if ($obj === null) { + $obj = new $class; + } + return call_user_func_array(array($obj, $method), func_get_args()); + }; } if (!is_callable($callable)) { diff --git a/tests/RouteTest.php b/tests/RouteTest.php index a6095474e..316e0f971 100644 --- a/tests/RouteTest.php +++ b/tests/RouteTest.php @@ -29,6 +29,28 @@ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ + +class LazyInitializeTestClass { + public static $initialized = false; + + public function __construct() { + self::$initialized = true; + } + + public function foo() { + } +} + +class FooTestClass { + public static $foo_invoked = false; + public static $foo_invoked_args = array(); + + public function foo() { + self::$foo_invoked = true; + self::$foo_invoked_args = func_get_args(); + } +} + class RouteTest extends PHPUnit_Framework_TestCase { public function testGetPattern() @@ -69,11 +91,26 @@ public function testGetCallable() public function testGetCallableAsClass() { - $route = new \Slim\Route('/foo', '\Slim\Router:getCurrentRoute'); + FooTestClass::$foo_invoked = false; + FooTestClass::$foo_invoked_args = array(); + $route = new \Slim\Route('/foo', '\FooTestClass:foo'); + $route->setParams(array('bar' => '1234')); - $callable = $route->getCallable(); - $this->assertInstanceOf('\Slim\Router', $callable[0]); - $this->assertEquals('getCurrentRoute', $callable[1]); + $this->assertFalse(FooTestClass::$foo_invoked); + $this->assertTrue($route->dispatch()); + $this->assertTrue(FooTestClass::$foo_invoked); + $this->assertEquals(array('1234'), FooTestClass::$foo_invoked_args); + } + + public function testGetCallableAsClassLazyInitialize() + { + LazyInitializeTestClass::$initialized = false; + + $route = new \Slim\Route('/foo', '\LazyInitializeTestClass:foo'); + $this->assertFalse(LazyInitializeTestClass::$initialized); + + $route->dispatch(); + $this->assertTrue(LazyInitializeTestClass::$initialized); } From 15d8ff3e69908050dd54fcf1f55568906a02e748 Mon Sep 17 00:00:00 2001 From: Josh Lockhart Date: Sat, 8 Mar 2014 14:36:14 -0500 Subject: [PATCH 22/23] Fix unit tests after merge --- tests/RouteTest.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/RouteTest.php b/tests/RouteTest.php index 316e0f971..741e4f01a 100644 --- a/tests/RouteTest.php +++ b/tests/RouteTest.php @@ -124,16 +124,15 @@ public function testGetCallableAsStaticMethod() public function example_càllâble_wïth_wéird_chars() { - + return 'test'; } public function testGetCallableWithOddCharsAsClass() { $route = new \Slim\Route('/foo', '\RouteTest:example_càllâble_wïth_wéird_chars'); - $callable = $route->getCallable(); - $this->assertInstanceOf('\RouteTest', $callable[0]); - $this->assertEquals('example_càllâble_wïth_wéird_chars', $callable[1]); + + $this->assertEquals('test', $callable()); } public function testSetCallable() From b86f11706b80972e3239c8ab0f24382b3b8dcf52 Mon Sep 17 00:00:00 2001 From: Fred Emmott Date: Tue, 18 Mar 2014 15:45:07 -0700 Subject: [PATCH 23/23] Fix race condition in testExpiresAsString This race condition is common when running the tests with HHVM + JIT, due to JIT overhead when not warmed up. --- tests/SlimTest.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/SlimTest.php b/tests/SlimTest.php index e955e796b..047bd0d07 100644 --- a/tests/SlimTest.php +++ b/tests/SlimTest.php @@ -761,7 +761,6 @@ public function testExpiresAsString() 'SCRIPT_NAME' => '/foo', //<-- Physical 'PATH_INFO' => '/bar', //<-- Virtual )); - $expectedDate = gmdate('D, d M Y H:i:s T', strtotime('5 days')); $s = new \Slim\Slim(); $s->get('/bar', function () use ($s) { $s->expires('5 days'); @@ -769,7 +768,12 @@ public function testExpiresAsString() $s->call(); list($status, $header, $body) = $s->response()->finalize(); $this->assertTrue(isset($header['Expires'])); - $this->assertEquals($header['Expires'], $expectedDate); + + $this->assertEquals( + strtotime('5 days'), + strtotime($header['Expires']), + 1 // delta + ); } /**